/* -*- 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;
}
