summaryrefslogtreecommitdiffstats
path: root/js/src/devtools/gctrace
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/devtools/gctrace')
-rw-r--r--js/src/devtools/gctrace/Makefile6
-rw-r--r--js/src/devtools/gctrace/gcstats.cpp873
2 files changed, 879 insertions, 0 deletions
diff --git a/js/src/devtools/gctrace/Makefile b/js/src/devtools/gctrace/Makefile
new file mode 100644
index 000000000..f0985286a
--- /dev/null
+++ b/js/src/devtools/gctrace/Makefile
@@ -0,0 +1,6 @@
+# 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/.
+
+gcstats: gcstats.cpp ../../gc/GCTraceFormat.h Makefile
+ $(CXX) -std=c++11 -g -O2 -I../.. -o $@ $<
diff --git a/js/src/devtools/gctrace/gcstats.cpp b/js/src/devtools/gctrace/gcstats.cpp
new file mode 100644
index 000000000..865e65839
--- /dev/null
+++ b/js/src/devtools/gctrace/gcstats.cpp
@@ -0,0 +1,873 @@
+/* -*- 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
+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<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;
+}