/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sw=4 et tw=78: * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * Read and process GC trace logs. */ #include "gc/GCTraceFormat.h" #define __STDC_FORMAT_MACROS #include #include #include #include #include #include #include #include #include #include #include #include // State of the program enum Heap { Nursery, TenuredHeap, HeapKinds }; enum FinalizerKind { NoFinalizer, HasFinalizer, FinalizerKinds }; enum State { StateMutator, StateMinorGC, StateMajorGC }; typedef uint64_t address; typedef uint8_t AllocKind; typedef uint8_t ClassId; typedef uint64_t TypeId; struct AllocInfo { const uint64_t serial; const AllocKind kind; const Heap initialHeap; TypeId typeId; AllocInfo(uint64_t allocCount, uint8_t kind, Heap loc) : serial(allocCount), kind(kind), initialHeap(loc), typeId(0) { assert(kind < AllocKinds); assert(initialHeap < HeapKinds); } }; struct ClassInfo { const ClassId id; const char* name; const uint32_t flags; const FinalizerKind hasFinalizer; ClassInfo(ClassId id, const char* name, uint32_t flags, FinalizerKind hasFinalizer) : id(id), name(name), flags(flags), hasFinalizer(hasFinalizer) {} }; struct TypeInfo { const TypeId id; const ClassId classId; const uint32_t flags; const char* name; TypeInfo(TypeId id, ClassId classId, uint32_t flags) : id(id), classId(classId), flags(flags), name(nullptr) {} const char* getName() { if (name) return name; static char buffer[32]; sprintf(buffer, "type %ld", id); return buffer; } }; typedef std::unordered_map AllocMap; typedef std::unordered_map ClassMap; typedef std::vector ClassVector; typedef std::unordered_map TypeMap; typedef std::vector TypeVector; uint64_t thingSizes[AllocKinds]; AllocMap nurseryThings; AllocMap tenuredThings; ClassMap classMap; ClassVector classes; TypeMap typeMap; TypeVector types; uint64_t allocCount = 0; // Collected data const unsigned MaxClasses = 128; const unsigned LifetimeBinLog = 10; const unsigned MaxLifetimeBins = 40; const unsigned AugHeapKinds = HeapKinds + 1; const unsigned HeapTotal = HeapKinds; const unsigned AugAllocKinds = AllocKinds + 1; const unsigned AllocKindTotal = AllocKinds; const unsigned AugLifetimeBins = MaxLifetimeBins + 1; const unsigned LifetimeBinTotal = MaxLifetimeBins; const unsigned AugClasses = MaxClasses + 1; const unsigned ClassTotal = MaxClasses; struct EmptyArrayTag {}; template struct Array { Array() {} Array(const EmptyArrayTag&) { zero(); } void zero() { memset(&elements, 0, sizeof(elements)); } T& operator[](size_t index) { assert(index < length); return elements[index]; } private: T elements[length]; }; unsigned timesliceSize; unsigned lifetimeBins; std::vector gcBytesAllocatedInSlice; std::vector gcBytesFreedInSlice; Array, HeapKinds> allocCountByHeapAndKind; Array, HeapKinds> allocCountByHeapAndLifetime; Array, AllocKinds>, HeapKinds> allocCountByHeapKindAndLifetime; Array objectCountByClass; std::vector objectCountByType; Array, HeapKinds> objectCountByHeapAndClass; Array, MaxClasses>, HeapKinds> objectCountByHeapClassAndLifetime; Array, FinalizerKinds> heapObjectCountByFinalizerAndLifetime; Array, MaxClasses> finalizedHeapObjectCountByClassAndLifetime; std::vector, HeapKinds> > objectCountByTypeHeapAndLifetime; static void MOZ_FORMAT_PRINTF(1, 2) die(const char* format, ...) { va_list va; va_start(va, format); vfprintf(stderr, format, va); fprintf(stderr, "\n"); va_end(va); exit(1); } const uint64_t FirstBinSize = 100; const unsigned BinLog = 2; static unsigned getBin(uint64_t lifetime) { /* * Calculate a bin number for a given lifetime. * * We use a logarithmic scale, starting with a bin size of 100 and doubling * from there. */ static double logDivisor = log(BinLog); if (lifetime < FirstBinSize) return 0; return unsigned(log(lifetime / FirstBinSize) / logDivisor) + 1; } static unsigned binLimit(unsigned bin) { return unsigned(pow(BinLog, bin) * FirstBinSize); } static void testBinning() { assert(getBin(0) == 0); assert(getBin(FirstBinSize - 1) == 0); assert(getBin(FirstBinSize) == 1); assert(getBin(2 * FirstBinSize - 1) == 1); assert(getBin(2 * FirstBinSize) == 2); assert(getBin(4 * FirstBinSize - 1) == 2); assert(getBin(4 * FirstBinSize) == 3); assert(binLimit(0) == FirstBinSize); assert(binLimit(1) == 2 * FirstBinSize); assert(binLimit(2) == 4 * FirstBinSize); assert(binLimit(3) == 8 * FirstBinSize); } static const char* allocKindName(AllocKind kind) { static const char* AllocKindNames[] = { "Object0", "Object0Bg", "Object2", "Object2Bg", "Object4", "Object4Bg", "Object8", "Object8Bg", "Object12", "Object12Bg", "Object16", "Object16Bg", "Script", "LazyScript", "Shape", "BaseShape", "TypeObject", "FatInlineString", "String", "ExternalString", "Symbol", "JitCode", "Total" }; assert(sizeof(AllocKindNames) / sizeof(const char*) == AugAllocKinds); assert(kind < AugAllocKinds); return AllocKindNames[kind]; } static const char* heapName(unsigned heap) { static const char* HeapNames[] = { "nursery", "tenured heap", "all" }; assert(heap < AugHeapKinds); return HeapNames[heap]; } static const char* heapLabel(unsigned heap) { static const char* HeapLabels[] = { "Nursery", "Tenured heap", "Total" }; assert(heap < AugHeapKinds); return HeapLabels[heap]; } static void outputGcBytesAllocated(FILE* file) { fprintf(file, "# Total GC bytes allocated by timeslice\n"); fprintf(file, "# Total allocations: %" PRIu64 "\n", allocCount); fprintf(file, "Time, GCBytesAllocated\n"); uint64_t timesliceCount = allocCount / timesliceSize + 1; uint64_t total = 0; for (uint64_t i = 0; i < timesliceCount; ++i) { total += gcBytesAllocatedInSlice[i]; fprintf(file, "%12" PRIu64 ", %12" PRIu64 "\n", i * timesliceSize, total); } } static void outputGcBytesUsed(FILE* file) { fprintf(file, "# Total GC bytes used by timeslice\n"); fprintf(file, "# Total allocations: %" PRIu64 "\n", allocCount); fprintf(file, "Time, GCBytesUsed\n"); uint64_t timesliceCount = allocCount / timesliceSize + 1; uint64_t total = 0; for (uint64_t i = 0; i < timesliceCount; ++i) { total += gcBytesAllocatedInSlice[i] - gcBytesFreedInSlice[i]; fprintf(file, "%12" PRIu64 ", %12" PRIu64 "\n", i * timesliceSize, total); } } static void outputThingCounts(FILE* file) { fprintf(file, "# GC thing allocation count in nursery and tenured heap by kind\n"); fprintf(file, "# This shows what kind of things we are allocating in the nursery\n"); fprintf(file, "# Total allocations: %" PRIu64 "\n", allocCount); fprintf(file, "Kind, Nursery, Tenured heap\n"); for (unsigned i = 0; i < AllocKinds; ++i) { fprintf(file, "%15s, %8" PRIu64 ", %8" PRIu64 "\n", allocKindName(i), allocCountByHeapAndKind[Nursery][i], allocCountByHeapAndKind[TenuredHeap][i]); } } static void outputObjectCounts(FILE* file) { fprintf(file, "# Object allocation count in nursery and tenured heap by class\n"); fprintf(file, "# This shows what kind of objects we are allocating in the nursery\n"); fprintf(file, "# Total allocations: %" PRIu64 "\n", allocCount); fprintf(file, "Class, Nursery, Tenured heap, Total\n"); for (unsigned i = 0; i < classes.size(); ++i) { fprintf(file, "%30s, %8" PRIu64 ", %8" PRIu64 ", %8" PRIu64 "\n", classes[i].name, objectCountByHeapAndClass[Nursery][i], objectCountByHeapAndClass[TenuredHeap][i], objectCountByClass[i]); } } static void outputLifetimeByHeap(FILE* file) { fprintf(file, "# Lifetime of all things (in log2 bins) by initial heap\n"); fprintf(file, "# NB invalid unless execution was traced with appropriate zeal\n"); fprintf(file, "# Total allocations: %" PRIu64 "\n", allocCount); fprintf(file, "Lifetime"); for (unsigned i = 0; i < HeapKinds; ++i) fprintf(file, ", %s", heapLabel(i)); fprintf(file, "\n"); for (unsigned i = 0; i < lifetimeBins; ++i) { fprintf(file, "%8d", binLimit(i)); for (unsigned j = 0; j < HeapKinds; ++j) fprintf(file, ", %8" PRIu64, allocCountByHeapAndLifetime[j][i]); fprintf(file, "\n"); } } static void outputLifetimeByHasFinalizer(FILE* file) { fprintf(file, "# Lifetime of heap allocated objects by prescence of finalizer\n"); fprintf(file, "# NB invalid unless execution was traced with appropriate zeal\n"); fprintf(file, "# Total allocations: %" PRIu64 "\n", allocCount); fprintf(file, "Lifetime, NoFinalizer, HasFinalizer\n"); for (unsigned i = 0; i < lifetimeBins; ++i) { fprintf(file, "%8d", binLimit(i)); for (unsigned j = 0; j < FinalizerKinds; ++j) fprintf(file, ", %8" PRIu64, heapObjectCountByFinalizerAndLifetime[j][i]); fprintf(file, "\n"); } } static void outputFinalizedHeapObjectLifetimeByClass(FILE* file) { fprintf(file, "# Lifetime of finalized heap objects by class\n"); fprintf(file, "# NB invalid unless execution was traced with appropriate zeal\n"); fprintf(file, "# Total allocations: %" PRIu64 "\n", allocCount); fprintf(file, "Lifetime"); for (unsigned i = 0; i < classes.size(); ++i) fprintf(file, ", %15s", classes[i].name); fprintf(file, "\n"); for (unsigned i = 0; i < lifetimeBins; ++i) { fprintf(file, "%8d", binLimit(i)); for (unsigned j = 0; j < classes.size(); ++j) { fprintf(file, ", %8" PRIu64, finalizedHeapObjectCountByClassAndLifetime[j][i]); } fprintf(file, "\n"); } } static void outputLifetimeByKind(FILE* file, unsigned initialHeap) { assert(initialHeap < AugHeapKinds); fprintf(file, "# Lifetime of %s things (in log2 bins) by kind\n", heapName(initialHeap)); fprintf(file, "# NB invalid unless execution was traced with appropriate zeal\n"); fprintf(file, "# Total allocations: %" PRIu64 "\n", allocCount); fprintf(file, "Lifetime"); for (unsigned i = 0; i < AllocKinds; ++i) fprintf(file, ", %15s", allocKindName(i)); fprintf(file, "\n"); for (unsigned i = 0; i < lifetimeBins; ++i) { fprintf(file, "%8d", binLimit(i)); for (unsigned j = 0; j < AllocKinds; ++j) fprintf(file, ", %8" PRIu64, allocCountByHeapKindAndLifetime[initialHeap][j][i]); fprintf(file, "\n"); } } static void outputLifetimeByClass(FILE* file, unsigned initialHeap) { assert(initialHeap < AugHeapKinds); fprintf(file, "# Lifetime of %s things (in log2 bins) by class\n", heapName(initialHeap)); fprintf(file, "# NB invalid unless execution was traced with appropriate zeal\n"); fprintf(file, "# Total allocations: %" PRIu64 "\n", allocCount); fprintf(file, "Lifetime"); for (unsigned i = 0; i < classes.size(); ++i) fprintf(file, ", %15s", classes[i].name); fprintf(file, "\n"); for (unsigned i = 0; i < lifetimeBins; ++i) { fprintf(file, "%8d", binLimit(i)); for (unsigned j = 0; j < classes.size(); ++j) fprintf(file, ", %8" PRIu64, objectCountByHeapClassAndLifetime[initialHeap][j][i]); fprintf(file, "\n"); } } static void outputLifetimeByType(FILE* file, unsigned initialHeap) { assert(initialHeap < AugHeapKinds); fprintf(file, "# Lifetime of %s things (in log2 bins) by type\n", heapName(initialHeap)); fprintf(file, "# NB invalid unless execution was traced with appropriate zeal\n"); fprintf(file, "# Total allocations: %" PRIu64 "\n", allocCount); // There are many types but few are frequently used. const size_t minObjectCount = 1; const size_t outputEntries = 10; std::vector topTypes; for (size_t i = 0; i < types.size(); ++i) { if (objectCountByType.at(i) > minObjectCount) topTypes.push_back(i); } std::sort(topTypes.begin(), topTypes.end(), [] (TypeId a, TypeId b) { return objectCountByType.at(a) > objectCountByType.at(b); }); size_t count = std::min(outputEntries, topTypes.size()); fprintf(file, "Lifetime"); for (unsigned i = 0; i < count; ++i) fprintf(file, ", %15s", types[topTypes[i]].getName()); fprintf(file, "\n"); for (unsigned i = 0; i < lifetimeBins; ++i) { fprintf(file, "%8d", binLimit(i)); for (unsigned j = 0; j < count; ++j) fprintf(file, ", %8" PRIu64, objectCountByTypeHeapAndLifetime.at(topTypes[j])[initialHeap][i]); fprintf(file, "\n"); } } static void processAlloc(const AllocInfo& info, uint64_t finalizeTime) { uint64_t lifetime = finalizeTime - info.serial; unsigned timeslice = info.serial / timesliceSize; unsigned lifetimeBin = getBin(lifetime); assert(lifetimeBin < lifetimeBins); ++allocCountByHeapAndKind[info.initialHeap][info.kind]; ++allocCountByHeapAndLifetime[info.initialHeap][lifetimeBin]; ++allocCountByHeapKindAndLifetime[info.initialHeap][info.kind][lifetimeBin]; if (info.kind <= LastObjectAllocKind) { const TypeInfo& typeInfo = types[info.typeId]; const ClassInfo& classInfo = classes[typeInfo.classId]; ++objectCountByType.at(typeInfo.id); ++objectCountByClass[classInfo.id]; ++objectCountByHeapAndClass[info.initialHeap][classInfo.id]; ++objectCountByHeapClassAndLifetime[info.initialHeap][classInfo.id][lifetimeBin]; ++objectCountByTypeHeapAndLifetime.at(typeInfo.id)[info.initialHeap][lifetimeBin]; if (info.initialHeap == TenuredHeap) { FinalizerKind f = classes[classInfo.id].hasFinalizer; ++heapObjectCountByFinalizerAndLifetime[f][lifetimeBin]; if (f == HasFinalizer) ++finalizedHeapObjectCountByClassAndLifetime[classInfo.id][lifetimeBin]; } } uint64_t size = thingSizes[info.kind]; gcBytesAllocatedInSlice[timeslice] += size; gcBytesFreedInSlice[finalizeTime / timesliceSize] += size; } static bool readTrace(FILE* file, uint64_t& trace) { if (fread(&trace, sizeof(trace), 1, file) != 1) { if (feof(file)) return false; else die("Error reading input"); } return true; } static GCTraceEvent getTraceEvent(uint64_t trace) { uint64_t event = trace >> TraceEventShift; assert(event < GCTraceEventCount); return (GCTraceEvent)event; } static uint64_t getTracePayload(uint64_t trace) { return trace & ((1lu << TracePayloadBits) - 1); } static uint8_t getTraceExtra(uint64_t trace) { uint64_t extra = (trace >> TraceExtraShift) & ((1 << TraceExtraBits) - 1); assert(extra < 256); return (uint8_t)extra; } static uint64_t expectTrace(FILE* file, GCTraceEvent event) { uint64_t trace; if (!readTrace(file, trace)) die("End of file while expecting trace %d", event); if (getTraceEvent(trace) != event) die("Expected trace %d but got trace %d", event, getTraceEvent(trace)); return getTracePayload(trace); } static uint64_t expectDataAddress(FILE* file) { return expectTrace(file, TraceDataAddress); } static uint32_t expectDataInt(FILE* file) { return (uint32_t)expectTrace(file, TraceDataInt); } static char* expectDataString(FILE* file) { uint64_t length = expectTrace(file, TraceDataString); assert(length < 256); // Sanity check char* string = static_cast(malloc(length + 1)); if (!string) die("Out of memory while reading string data"); const unsigned charsPerWord = sizeof(uint64_t); unsigned wordCount = (length + charsPerWord - 1) / charsPerWord; for (unsigned i = 0; i < wordCount; ++i) { if (fread(&string[i * charsPerWord], sizeof(char), charsPerWord, file) != charsPerWord) die("Error or EOF while reading string data"); } string[length] = 0; return string; } static void createClassInfo(const char* name, uint32_t flags, FinalizerKind hasFinalizer, address clasp = 0) { ClassId id = classes.size(); classes.push_back(ClassInfo(id, name, flags, hasFinalizer)); if (clasp) classMap.emplace(clasp, id); } static void readClassInfo(FILE* file, address clasp) { assert(clasp); char* name = expectDataString(file); uint32_t flags = expectDataInt(file); FinalizerKind hasFinalizer = expectDataInt(file) != 0 ? HasFinalizer : NoFinalizer; createClassInfo(name, flags, hasFinalizer, clasp); } static ClassId lookupClassId(address clasp) { auto i = classMap.find(clasp); assert(i != classMap.end()); ClassId id = i->second; assert(id < classes.size()); return id; } static void createTypeInfo(ClassId classId, uint32_t flags, address typeObject = 0) { TypeId id = types.size(); types.push_back(TypeInfo(id, classId, flags)); if (typeObject) typeMap.emplace(typeObject, id); objectCountByType.push_back(0); objectCountByTypeHeapAndLifetime.push_back(EmptyArrayTag()); } static void readTypeInfo(FILE* file, address typeObject) { assert(typeObject); address clasp = expectDataAddress(file); uint32_t flags = expectDataInt(file); createTypeInfo(lookupClassId(clasp), flags, typeObject); } static TypeId lookupTypeId(address typeObject) { auto i = typeMap.find(typeObject); assert(i != typeMap.end()); TypeId id = i->second; assert(id < types.size()); return id; } static void setTypeName(address typeObject, const char* name) { TypeId id = lookupTypeId(typeObject); types[id].name = name; } static void allocHeapThing(address thing, AllocKind kind) { uint64_t allocTime = allocCount++; tenuredThings.emplace(thing, AllocInfo(allocTime, kind, TenuredHeap)); } static void allocNurseryThing(address thing, AllocKind kind) { uint64_t allocTime = allocCount++; nurseryThings.emplace(thing, AllocInfo(allocTime, kind, Nursery)); } static void setObjectType(address obj, address typeObject) { auto j = nurseryThings.find(obj); if (j == nurseryThings.end()) { j = tenuredThings.find(obj); if (j == tenuredThings.end()) die("Can't find allocation for object %p", obj); } j->second.typeId = lookupTypeId(typeObject); } static void promoteToTenured(address src, address dst) { auto i = nurseryThings.find(src); assert(i != nurseryThings.end()); AllocInfo alloc = i->second; tenuredThings.emplace(dst, alloc); nurseryThings.erase(i); } static void finalizeThing(const AllocInfo& info) { processAlloc(info, allocCount); } static void sweepNursery() { for (auto i = nurseryThings.begin(); i != nurseryThings.end(); ++i) { finalizeThing(i->second); } nurseryThings.clear(); } static void finalizeTenuredThing(address thing) { auto i = tenuredThings.find(thing); assert(i != tenuredThings.end()); finalizeThing(i->second); tenuredThings.erase(i); } static void updateTimeslices(std::vector& data, uint64_t lastTime, uint64_t currentTime, uint64_t value) { unsigned firstSlice = (lastTime / timesliceSize) + 1; unsigned lastSlice = currentTime / timesliceSize; for (unsigned i = firstSlice; i <= lastSlice; ++i) data[i] = value; } static void processTraceFile(const char* filename) { FILE* file; file = fopen(filename, "r"); if (!file) die("Can't read file: %s", filename); // Get a conservative estimate of the total number of allocations so we can // allocate buffers in advance. fseek(file, 0, SEEK_END); size_t length = ftell(file); fseek(file, 0, SEEK_SET); size_t maxTraces = length / sizeof(uint64_t); uint64_t trace; if (!readTrace(file, trace)) die("Empty input file"); if (getTraceEvent(trace) != TraceEventInit) die("Can't parse input file"); if (getTraceExtra(trace) != TraceFormatVersion) die("Unexpected format version %d", getTraceExtra(trace)); for (unsigned kind = 0; kind < AllocKinds; ++kind) thingSizes[kind] = expectTrace(file, TraceEventThingSize); timesliceSize = 1000; while ((maxTraces / timesliceSize ) > 1000) timesliceSize *= 2; size_t maxTimeslices = maxTraces / timesliceSize; gcBytesAllocatedInSlice.reserve(maxTimeslices); gcBytesFreedInSlice.reserve(maxTimeslices); lifetimeBins = getBin(maxTraces) + 1; assert(lifetimeBins <= MaxLifetimeBins); createClassInfo("unknown", 0, NoFinalizer); createTypeInfo(0, 0); types[0].name = "unknown"; State state = StateMutator; while (readTrace(file, trace)) { GCTraceEvent event = getTraceEvent(trace); switch (event) { case TraceEventNurseryAlloc: assert(state == StateMutator); allocNurseryThing(getTracePayload(trace), getTraceExtra(trace)); break; case TraceEventTenuredAlloc: assert(state == StateMutator); allocHeapThing(getTracePayload(trace), getTraceExtra(trace)); break; case TraceEventClassInfo: assert(state == StateMutator); readClassInfo(file, getTracePayload(trace)); break; case TraceEventTypeInfo: assert(state == StateMutator); readTypeInfo(file, getTracePayload(trace)); break; case TraceEventTypeNewScript: assert(state == StateMutator); setTypeName(getTracePayload(trace), expectDataString(file)); break; case TraceEventCreateObject: assert(state == StateMutator); setObjectType(getTracePayload(trace), expectDataAddress(file)); break; case TraceEventMinorGCStart: assert(state == StateMutator); state = StateMinorGC; break; case TraceEventPromoteToTenured: assert(state == StateMinorGC); promoteToTenured(getTracePayload(trace), expectDataAddress(file)); break; case TraceEventMinorGCEnd: assert(state == StateMinorGC); sweepNursery(); state = StateMutator; break; case TraceEventMajorGCStart: assert(state == StateMutator); state = StateMajorGC; break; case TraceEventTenuredFinalize: assert(state == StateMajorGC); finalizeTenuredThing(getTracePayload(trace)); break; case TraceEventMajorGCEnd: assert(state == StateMajorGC); state = StateMutator; break; default: assert(false); die("Unexpected trace event %d", event); break; } } // Correct number of lifetime bins now we know the real allocation count. assert(allocCount < maxTraces); lifetimeBins = getBin(allocCount) + 1; assert(lifetimeBins <= MaxLifetimeBins); fclose(file); } template void withOutputFile(const char* base, const char* name, func f) { const size_t bufSize = 256; char filename[bufSize]; int r = snprintf(filename, bufSize, "%s-%s.csv", base, name); assert(r > 0 && r < bufSize); FILE* file = fopen(filename, "w"); if (!file) die("Can't write to %s", filename); f(file); fclose(file); } int main(int argc, const char* argv[]) { testBinning(); if (argc != 3) die("usage: gctrace INPUT_FILE OUTPUT_BASE"); const char* inputFile = argv[1]; const char* outputBase = argv[2]; processTraceFile(inputFile); using namespace std::placeholders; withOutputFile(outputBase, "bytesAllocatedBySlice", outputGcBytesAllocated); withOutputFile(outputBase, "bytesUsedBySlice", outputGcBytesUsed); withOutputFile(outputBase, "thingCounts", outputThingCounts); withOutputFile(outputBase, "objectCounts", outputObjectCounts); withOutputFile(outputBase, "lifetimeByClassForNursery", std::bind(outputLifetimeByClass, _1, Nursery)); withOutputFile(outputBase, "lifetimeByKindForHeap", std::bind(outputLifetimeByKind, _1, TenuredHeap)); withOutputFile(outputBase, "lifetimeByHeap", outputLifetimeByHeap); withOutputFile(outputBase, "lifetimeByHasFinalizer", outputLifetimeByHasFinalizer); withOutputFile(outputBase, "finalizedHeapObjectlifetimeByClass", outputFinalizedHeapObjectLifetimeByClass); withOutputFile(outputBase, "lifetimeByTypeForNursery", std::bind(outputLifetimeByType, _1, Nursery)); withOutputFile(outputBase, "lifetimeByTypeForHeap", std::bind(outputLifetimeByType, _1, TenuredHeap)); return 0; }