| /* -*- 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 <assert.h> |
| #include <inttypes.h> |
| #include <math.h> |
| #include <stdarg.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <algorithm> |
| #include <functional> |
| #include <unordered_map> |
| #include <vector> |
| |
| // 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<address, AllocInfo> AllocMap; |
| typedef std::unordered_map<address, ClassId> ClassMap; |
| typedef std::vector<ClassInfo> ClassVector; |
| typedef std::unordered_map<address, TypeId> TypeMap; |
| typedef std::vector<TypeInfo> 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 <typename T, size_t length> |
| 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<uint64_t> gcBytesAllocatedInSlice; |
| std::vector<uint64_t> gcBytesFreedInSlice; |
| |
| Array<Array<uint64_t, AllocKinds>, HeapKinds> allocCountByHeapAndKind; |
| Array<Array<uint64_t, MaxLifetimeBins>, HeapKinds> allocCountByHeapAndLifetime; |
| Array<Array<Array<uint64_t, MaxLifetimeBins>, AllocKinds>, HeapKinds> allocCountByHeapKindAndLifetime; |
| Array<uint64_t, MaxClasses> objectCountByClass; |
| std::vector<uint64_t> objectCountByType; |
| Array<Array<uint64_t, MaxClasses>, HeapKinds> objectCountByHeapAndClass; |
| Array<Array<Array<uint64_t, MaxLifetimeBins>, MaxClasses>, HeapKinds> objectCountByHeapClassAndLifetime; |
| Array<Array<uint64_t, MaxLifetimeBins>, FinalizerKinds> heapObjectCountByFinalizerAndLifetime; |
| Array<Array<uint64_t, MaxLifetimeBins>, MaxClasses> finalizedHeapObjectCountByClassAndLifetime; |
| std::vector<Array<Array<uint64_t, MaxLifetimeBins>, HeapKinds> > objectCountByTypeHeapAndLifetime; |
| |
| static void |
| 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<TypeId> 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<char*>(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<uint64_t>& 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 <class func> |
| 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; |
| } |