Start a simple Qt GUI and share some code with the ASCII printer.
authorMilian Wolff <mail@milianw.de>
Mon, 18 May 2015 14:13:59 +0000 (16:13 +0200)
committerMilian Wolff <mail@milianw.de>
Mon, 18 May 2015 14:13:59 +0000 (16:13 +0200)
CMakeLists.txt
accumulatedtracedata.cpp [new file with mode: 0644]
accumulatedtracedata.h [new file with mode: 0644]
gui/CMakeLists.txt [new file with mode: 0644]
gui/gui.cpp [new file with mode: 0644]
gui/mainwindow.cpp [new file with mode: 0644]
gui/mainwindow.h [new file with mode: 0644]
gui/mainwindow.ui [new file with mode: 0644]
gui/model.cpp [new file with mode: 0644]
gui/model.h [new file with mode: 0644]
heaptrack_print.cpp

index 5cdb6ee..56cc414 100644 (file)
@@ -5,6 +5,10 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wpedantic")
 
 find_package(Boost 1.45.0 REQUIRED COMPONENTS iostreams program_options)
 find_package(Threads REQUIRED)
+find_package(Qt5 NO_MODULE QUIET OPTIONAL_COMPONENTS Widgets)
+if(Qt5_FOUND)
+    add_subdirectory(gui)
+endif()
 
 add_subdirectory(3rdparty)
 
@@ -58,8 +62,9 @@ target_link_libraries(heaptrack_inject LINK_PRIVATE ${CMAKE_DL_LIBS} ${CMAKE_THR
 add_executable(heaptrack_interpret heaptrack_interpret.cpp)
 target_link_libraries(heaptrack_interpret backtrace)
 
+add_library(sharedprint STATIC accumulatedtracedata.cpp)
 add_executable(heaptrack_print heaptrack_print.cpp)
-target_link_libraries(heaptrack_print ${Boost_LIBRARIES})
+target_link_libraries(heaptrack_print ${Boost_LIBRARIES} sharedprint)
 
 set(BIN_INSTALL_DIR "bin")
 set(LIB_SUFFIX "" CACHE STRING "Define suffix of directory name (32/64)")
diff --git a/accumulatedtracedata.cpp b/accumulatedtracedata.cpp
new file mode 100644 (file)
index 0000000..ac96142
--- /dev/null
@@ -0,0 +1,446 @@
+/*
+ * Copyright 2015 Milian Wolff <mail@milianw.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "accumulatedtracedata.h"
+
+#include <iostream>
+#include <memory>
+#include <algorithm>
+#include <cassert>
+#include <iomanip>
+
+#include <boost/iostreams/filtering_stream.hpp>
+#include <boost/iostreams/filter/gzip.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include "linereader.h"
+
+using namespace std;
+
+ostream& operator<<(ostream& out, const formatBytes data)
+{
+    if (data.m_bytes < 1000) {
+        // no fancy formatting for plain byte values, esp. no .00 factions
+        return out << data.m_bytes << 'B';
+    }
+
+    static const auto units = {
+        "B",
+        "KB",
+        "MB",
+        "GB",
+        "TB"
+    };
+    auto unit = units.begin();
+    size_t i = 0;
+    double bytes = data.m_bytes;
+    while (i < units.size() - 1 && bytes > 1000.) {
+        bytes /= 1000.;
+        ++i;
+        ++unit;
+    }
+    return out << fixed << setprecision(2) << bytes << *unit;
+}
+
+namespace {
+
+template<typename Base>
+bool operator>>(LineReader& reader, Index<Base> &index)
+{
+    return reader.readHex(index.index);
+}
+
+template<typename Base>
+ostream& operator<<(ostream &out, const Index<Base> index)
+{
+    out << index.index;
+    return out;
+}
+
+}
+
+AccumulatedTraceData::AccumulatedTraceData()
+{
+    instructionPointers.reserve(16384);
+    traces.reserve(65536);
+    strings.reserve(4096);
+    allocations.reserve(16384);
+    activeAllocations.reserve(65536);
+    stopIndices.reserve(4);
+}
+
+void AccumulatedTraceData::clear()
+{
+    stopIndices.clear();
+    instructionPointers.clear();
+    traces.clear();
+    strings.clear();
+    mergedAllocations.clear();
+    allocations.clear();
+    activeAllocations.clear();
+}
+
+void AccumulatedTraceData::handleTimeStamp(size_t /*newStamp*/, size_t /*oldStamp*/)
+{
+}
+
+void AccumulatedTraceData::handleAllocation()
+{
+}
+
+void AccumulatedTraceData::handleDebuggee(const char* command)
+{
+}
+
+const string& AccumulatedTraceData::stringify(const StringIndex stringId) const
+{
+    if (!stringId || stringId.index > strings.size()) {
+        static const string empty;
+        return empty;
+    } else {
+        return strings.at(stringId.index - 1);
+    }
+}
+
+string AccumulatedTraceData::prettyFunction(const string& function) const
+{
+    if (!shortenTemplates) {
+        return function;
+    }
+    string ret;
+    ret.reserve(function.size());
+    int depth = 0;
+    for (size_t i = 0; i < function.size(); ++i) {
+        const auto c = function[i];
+        if ((c == '<' || c == '>') && ret.size() >= 8) {
+            // don't get confused by C++ operators
+            const char* cmp = "operator";
+            if (ret.back() == c) {
+                // skip second angle bracket for operator<< or operator>>
+                if (c == '<') {
+                    cmp = "operator<";
+                } else {
+                    cmp = "operator>";
+                }
+            }
+            if (boost::algorithm::ends_with(ret, cmp)) {
+                ret.push_back(c);
+                continue;
+            }
+        }
+        if (c == '<') {
+            ++depth;
+            if (depth == 1) {
+                ret.push_back(c);
+            }
+        } else if (c == '>') {
+            --depth;
+        }
+        if (depth) {
+            continue;
+        }
+        ret.push_back(c);
+    }
+    return ret;
+}
+
+bool AccumulatedTraceData::read(const string& inputFile)
+{
+    const bool isCompressed = boost::algorithm::ends_with(inputFile, ".gz");
+    ifstream file(inputFile, isCompressed ? ios_base::in | ios_base::binary : ios_base::in);
+
+    if (!file.is_open()) {
+        cerr << "Failed to open heaptrack log file: " << inputFile << endl;
+        return false;
+    }
+
+    boost::iostreams::filtering_istream in;
+    if (isCompressed) {
+        in.push(boost::iostreams::gzip_decompressor());
+    }
+    in.push(file);
+    return read(in);
+}
+
+bool AccumulatedTraceData::read(istream& in)
+{
+    clear();
+
+    LineReader reader;
+    size_t timeStamp = 0;
+
+    vector<StringIndex> opNewStrIndices;
+    opNewStrIndices.reserve(16);
+    vector<IpIndex> opNewIpIndices;
+    opNewIpIndices.reserve(16);
+    vector<string> opNewStrings = {
+        "operator new(unsigned long)",
+        "operator new[](unsigned long)"
+    };
+
+    vector<string> stopStrings = {
+        "main",
+        "__libc_start_main",
+        "__static_initialization_and_destruction_0"
+    };
+
+    while (reader.getLine(in)) {
+        if (reader.mode() == 's') {
+            strings.push_back(reader.line().substr(2));
+            StringIndex index;
+            index.index = strings.size();
+
+            auto opNewIt = find(opNewStrings.begin(), opNewStrings.end(), strings.back());
+            if (opNewIt != opNewStrings.end()) {
+                opNewStrIndices.push_back(index);
+                opNewStrings.erase(opNewIt);
+            } else {
+                auto stopIt = find(stopStrings.begin(), stopStrings.end(), strings.back());
+                if (stopIt != stopStrings.end()) {
+                    stopIndices.push_back(index);
+                    stopStrings.erase(stopIt);
+                }
+            }
+        } else if (reader.mode() == 't') {
+            TraceNode node;
+            reader >> node.ipIndex;
+            reader >> node.parentIndex;
+            // skip operator new and operator new[] at the beginning of traces
+            while (find(opNewIpIndices.begin(), opNewIpIndices.end(), node.ipIndex) != opNewIpIndices.end()) {
+                node = findTrace(node.parentIndex);
+            }
+            traces.push_back(node);
+        } else if (reader.mode() == 'i') {
+            InstructionPointer ip;
+            reader >> ip.instructionPointer;
+            reader >> ip.moduleIndex;
+            reader >> ip.functionIndex;
+            reader >> ip.fileIndex;
+            reader >> ip.line;
+            instructionPointers.push_back(ip);
+            if (find(opNewStrIndices.begin(), opNewStrIndices.end(), ip.functionIndex) != opNewStrIndices.end()) {
+                IpIndex index;
+                index.index = instructionPointers.size();
+                opNewIpIndices.push_back(index);
+            }
+        } else if (reader.mode() == '+') {
+            size_t size = 0;
+            TraceIndex traceId;
+            uintptr_t ptr = 0;
+            if (!(reader >> size) || !(reader >> traceId) || !(reader >> ptr)) {
+                cerr << "failed to parse line: " << reader.line() << endl;
+                continue;
+            }
+
+            activeAllocations[ptr] = {traceId, size};
+
+            auto& allocation = findAllocation(traceId);
+            allocation.leaked += size;
+            allocation.allocated += size;
+            ++allocation.allocations;
+            if (allocation.leaked > allocation.peak) {
+                allocation.peak = allocation.leaked;
+            }
+            totalAllocated += size;
+            ++totalAllocations;
+            leaked += size;
+            if (leaked > peak) {
+                peak = leaked;
+            }
+            handleAllocation();
+            if (printHistogram) {
+                ++sizeHistogram[size];
+            }
+        } else if (reader.mode() == '-') {
+            uintptr_t ptr = 0;
+            if (!(reader >> ptr)) {
+                cerr << "failed to parse line: " << reader.line() << endl;
+                continue;
+            }
+            auto ip = activeAllocations.find(ptr);
+            if (ip == activeAllocations.end()) {
+                if (!fromAttached) {
+                    cerr << "unknown pointer in line: " << reader.line() << endl;
+                }
+                continue;
+            }
+            const auto info = ip->second;
+            activeAllocations.erase(ip);
+
+            auto& allocation = findAllocation(info.traceIndex);
+            if (!allocation.allocations || allocation.leaked < info.size) {
+                if (!fromAttached) {
+                    cerr << "inconsistent allocation info, underflowed allocations of " << info.traceIndex << endl;
+                }
+                allocation.leaked = 0;
+                allocation.allocations = 0;
+            } else {
+                allocation.leaked -= info.size;
+            }
+            leaked -= info.size;
+        } else if (reader.mode() == '#') {
+            // comment or empty line
+            continue;
+        } else if (reader.mode() == 'c') {
+            size_t newStamp = 0;
+            if (!(reader >> newStamp)) {
+                cerr << "Failed to read time stamp: " << reader.line() << endl;
+                continue;
+            }
+            handleTimeStamp(timeStamp, newStamp);
+            timeStamp = newStamp;
+        } else if (reader.mode() == 'X') {
+            cout << "Debuggee command was: " << (reader.line().c_str() + 2) << endl;
+            handleDebuggee(reader.line().c_str() + 2);
+        } else if (reader.mode() == 'A') {
+            leaked = 0;
+            peak = 0;
+            fromAttached = true;
+        } else {
+            cerr << "failed to parse line: " << reader.line() << endl;
+        }
+    }
+
+    /// these are leaks, but we now have the same data in \c allocations as well
+    activeAllocations.clear();
+
+    totalTime = max(timeStamp, size_t(1));
+
+    handleTimeStamp(timeStamp, totalTime);
+
+    filterAllocations();
+    mergedAllocations = mergeAllocations(allocations);
+
+    return true;
+}
+
+Allocation& AccumulatedTraceData::findAllocation(const TraceIndex traceIndex)
+{
+    if (traceIndex < m_maxAllocationTraceIndex) {
+        // only need to search when the trace index is previously known
+        auto it = lower_bound(allocations.begin(), allocations.end(), traceIndex,
+                            [] (const Allocation& allocation, const TraceIndex traceIndex) -> bool {
+                                return allocation.traceIndex < traceIndex;
+                            });
+        assert(it != allocations.end());
+        assert(it->traceIndex == traceIndex);
+        return *it;
+    } else if (traceIndex == m_maxAllocationTraceIndex && !allocations.empty()) {
+        // reuse the last allocation
+        assert(allocations.back().traceIndex == traceIndex);
+    } else {
+        // actually a new allocation
+        Allocation allocation;
+        allocation.traceIndex = traceIndex;
+        allocations.push_back(allocation);
+        m_maxAllocationTraceIndex = traceIndex;
+    }
+    return allocations.back();
+}
+
+void AccumulatedTraceData::mergeAllocation(vector<MergedAllocation>* mergedAllocations, const Allocation& allocation) const
+{
+    const auto trace = findTrace(allocation.traceIndex);
+    const auto traceIp = findIp(trace.ipIndex);
+    auto it = lower_bound(mergedAllocations->begin(), mergedAllocations->end(), traceIp,
+                            [this] (const MergedAllocation& allocation, const InstructionPointer traceIp) -> bool {
+                                // Compare meta data without taking the instruction pointer address into account.
+                                // This is useful since sometimes, esp. when we lack debug symbols, the same function
+                                // allocates memory at different IP addresses which is pretty useless information most of the time
+                                // TODO: make this configurable, but on-by-default
+                                const auto allocationIp = findIp(allocation.ipIndex);
+                                return allocationIp.compareWithoutAddress(traceIp);
+                            });
+    if (it == mergedAllocations->end() || !findIp(it->ipIndex).equalWithoutAddress(traceIp)) {
+        MergedAllocation merged;
+        merged.ipIndex = trace.ipIndex;
+        it = mergedAllocations->insert(it, merged);
+    }
+    it->traces.push_back(allocation);
+}
+
+// merge allocations so that different traces that point to the same
+// instruction pointer at the end where the allocation function is
+// called are combined
+vector<MergedAllocation> AccumulatedTraceData::mergeAllocations(const vector<Allocation>& allocations) const
+{
+    // TODO: merge deeper traces, i.e. A,B,C,D and A,B,C,F
+    //       should be merged to A,B,C: D & F
+    //       currently the below will only merge it to: A: B,C,D & B,C,F
+    vector<MergedAllocation> ret;
+    ret.reserve(allocations.size());
+    for (const Allocation& allocation : allocations) {
+        if (allocation.traceIndex) {
+            mergeAllocation(&ret, allocation);
+        }
+    }
+    for (MergedAllocation& merged : ret) {
+        for (const Allocation& allocation: merged.traces) {
+            merged.allocated += allocation.allocated;
+            merged.allocations += allocation.allocations;
+            merged.leaked += allocation.leaked;
+            merged.peak += allocation.peak;
+        }
+    }
+    return ret;
+}
+
+InstructionPointer AccumulatedTraceData::findIp(const IpIndex ipIndex) const
+{
+    if (!ipIndex || ipIndex.index > instructionPointers.size()) {
+        return {};
+    } else {
+        return instructionPointers[ipIndex.index - 1];
+    }
+}
+
+TraceNode AccumulatedTraceData::findTrace(const TraceIndex traceIndex) const
+{
+    if (!traceIndex || traceIndex.index > traces.size()) {
+        return {};
+    } else {
+        return traces[traceIndex.index - 1];
+    }
+}
+
+bool AccumulatedTraceData::isStopIndex(const StringIndex index) const
+{
+    return find(stopIndices.begin(), stopIndices.end(), index) != stopIndices.end();
+}
+
+void AccumulatedTraceData::filterAllocations()
+{
+    if (filterBtFunction.empty()) {
+        return;
+    }
+    allocations.erase(remove_if(allocations.begin(), allocations.end(), [&] (const Allocation& allocation) -> bool {
+        auto node = findTrace(allocation.traceIndex);
+        while (node.ipIndex) {
+            const auto& ip = findIp(node.ipIndex);
+            if (isStopIndex(ip.functionIndex)) {
+                break;
+            }
+            if (stringify(ip.functionIndex).find(filterBtFunction) != string::npos) {
+                return false;
+            }
+            node = findTrace(node.parentIndex);
+        };
+        return true;
+    }), allocations.end());
+}
diff --git a/accumulatedtracedata.h b/accumulatedtracedata.h
new file mode 100644 (file)
index 0000000..156bad8
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2015 Milian Wolff <mail@milianw.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef ACCUMULATEDTRACEDATA_H
+#define ACCUMULATEDTRACEDATA_H
+
+#include <iosfwd>
+#include <tuple>
+#include <vector>
+
+#include <fstream>
+#include <unordered_map>
+#include <map>
+
+class formatBytes
+{
+public:
+    formatBytes(size_t bytes)
+        : m_bytes(bytes)
+    {
+    }
+
+    friend std::ostream& operator<<(std::ostream& out, const formatBytes data);
+
+private:
+    size_t m_bytes;
+};
+
+// sadly, C++ doesn't yet have opaque typedefs
+template<typename Base>
+struct Index
+{
+    size_t index = 0;
+
+    explicit operator bool() const
+    {
+        return index;
+    }
+
+    bool operator<(Index o) const
+    {
+        return index < o.index;
+    }
+
+    bool operator!=(Index o) const
+    {
+        return index != o.index;
+    }
+
+    bool operator==(Index o) const
+    {
+        return index == o.index;
+    }
+};
+
+struct StringIndex : public Index<StringIndex> {};
+struct ModuleIndex : public StringIndex {};
+struct FunctionIndex : public StringIndex {};
+struct FileIndex : public StringIndex {};
+struct IpIndex : public Index<IpIndex> {};
+struct TraceIndex : public Index<TraceIndex> {};
+
+struct InstructionPointer
+{
+    uintptr_t instructionPointer = 0;
+    ModuleIndex moduleIndex;
+    FunctionIndex functionIndex;
+    FileIndex fileIndex;
+    int line = 0;
+
+    bool compareWithoutAddress(const InstructionPointer &other) const
+    {
+        return std::make_tuple(moduleIndex, functionIndex, fileIndex, line)
+             < std::make_tuple(other.moduleIndex, other.functionIndex, other.fileIndex, other.line);
+    }
+
+    bool equalWithoutAddress(const InstructionPointer &other) const
+    {
+        return std::make_tuple(moduleIndex, functionIndex, fileIndex, line)
+            == std::make_tuple(other.moduleIndex, other.functionIndex, other.fileIndex, other.line);
+    }
+};
+
+struct TraceNode
+{
+    IpIndex ipIndex;
+    TraceIndex parentIndex;
+};
+
+struct AllocationData
+{
+    // number of allocations
+    size_t allocations = 0;
+    // bytes allocated in total
+    size_t allocated = 0;
+    // amount of bytes leaked
+    size_t leaked = 0;
+    // largest amount of bytes allocated
+    size_t peak = 0;
+};
+
+struct Allocation : public AllocationData
+{
+    // backtrace entry point
+    TraceIndex traceIndex;
+};
+
+/**
+ * Merged allocation information by instruction pointer outside of alloc funcs
+ */
+struct MergedAllocation : public AllocationData
+{
+    // individual backtraces
+    std::vector<Allocation> traces;
+    // location
+    IpIndex ipIndex;
+};
+
+/**
+ * Information for a single call to an allocation function
+ */
+struct AllocationInfo
+{
+    TraceIndex traceIndex;
+    size_t size;
+};
+
+struct AccumulatedTraceData
+{
+    AccumulatedTraceData();
+    ~AccumulatedTraceData() = default;
+
+    virtual void handleTimeStamp(size_t newStamp, size_t oldStamp);
+    virtual void handleAllocation();
+    virtual void handleDebuggee(const char* command);
+
+    void clear();
+    const std::string& stringify(const StringIndex stringId) const;
+
+    std::string prettyFunction(const std::string& function) const;
+
+    bool read(const std::string& inputFile);
+    bool read(std::istream& in);
+
+    bool shortenTemplates = false;
+    bool mergeBacktraces = true;
+    bool printHistogram = false;
+    bool fromAttached = false;
+    std::ofstream massifOut;
+    double massifThreshold = 1;
+    size_t massifDetailedFreq = 1;
+    std::string filterBtFunction;
+
+    std::vector<Allocation> allocations;
+    std::vector<MergedAllocation> mergedAllocations;
+    std::map<size_t, size_t> sizeHistogram;
+    size_t totalAllocated = 0;
+    size_t totalAllocations = 0;
+    size_t peak = 0;
+    size_t leaked = 0;
+    size_t totalTime = 0;
+
+    // our indices are sequentially increasing thus a new allocation can only ever
+    // occur with an index larger than any other we encountered so far
+    // this can be used to our advantage in speeding up the findAllocation calls.
+    TraceIndex m_maxAllocationTraceIndex;
+
+    Allocation& findAllocation(const TraceIndex traceIndex);
+
+    void mergeAllocation(std::vector<MergedAllocation>* mergedAllocations, const Allocation& allocation) const;
+
+    // merge allocations so that different traces that point to the same
+    // instruction pointer at the end where the allocation function is
+    // called are combined
+    std::vector<MergedAllocation> mergeAllocations(const std::vector<Allocation>& allocations) const;
+
+    InstructionPointer findIp(const IpIndex ipIndex) const;
+
+    TraceNode findTrace(const TraceIndex traceIndex) const;
+
+    bool isStopIndex(const StringIndex index) const;
+
+    void filterAllocations();
+
+    // indices of functions that should stop the backtrace, e.g. main or static initialization
+    std::vector<StringIndex> stopIndices;
+    std::unordered_map<uintptr_t, AllocationInfo> activeAllocations;
+    std::vector<InstructionPointer> instructionPointers;
+    std::vector<TraceNode> traces;
+    std::vector<std::string> strings;
+};
+
+#endif // ACCUMULATEDTRACEDATA_H
diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt
new file mode 100644 (file)
index 0000000..8c8b7e7
--- /dev/null
@@ -0,0 +1,18 @@
+set(CMAKE_AUTOMOC 1)
+
+qt5_wrap_ui(UIFILES
+    mainwindow.ui
+)
+
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+add_executable(heaptrack_gui
+    gui.cpp
+    mainwindow.cpp
+    model.cpp
+    ${UIFILES}
+)
+
+target_link_libraries(heaptrack_gui Qt5::Widgets ${Boost_LIBRARIES} sharedprint)
+
+install(TARGETS heaptrack_gui RUNTIME DESTINATION "bin")
\ No newline at end of file
diff --git a/gui/gui.cpp b/gui/gui.cpp
new file mode 100644 (file)
index 0000000..1304f9e
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015 Milian Wolff <mail@milianw.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <QApplication>
+
+#include "mainwindow.h"
+
+#include <iostream>
+
+using namespace std;
+
+int main(int argc, char** argv)
+{
+    QApplication app(argc, argv);
+
+    if (app.arguments().size() != 2) {
+        cerr << "Missing path to heaptrack data file." << endl;
+        return 1;
+    }
+
+    MainWindow window;
+    window.loadFile(app.arguments().last());
+    window.show();
+
+    return app.exec();
+}
\ No newline at end of file
diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp
new file mode 100644 (file)
index 0000000..b7b2a5f
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2015 Milian Wolff <mail@milianw.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "mainwindow.h"
+
+#include <ui_mainwindow.h>
+
+#include <iostream>
+#include <sstream>
+
+#include "model.h"
+#include "../accumulatedtracedata.h"
+
+using namespace std;
+
+namespace {
+QString generateSummary(const AccumulatedTraceData& data)
+{
+    stringstream stream;
+    const double totalTimeS = 0.001 * data.totalTime;
+    stream << "<qt>"
+           << "<strong>total runtime</strong>: " << fixed << totalTimeS << "s.<br/>"
+           << "<strong>bytes allocated in total</strong> (ignoring deallocations): " << formatBytes(data.totalAllocated)
+             << " (" << formatBytes(data.totalAllocated / totalTimeS) << "/s)<br/>"
+           << "<strong>calls to allocation functions</strong>: " << data.totalAllocations
+             << " (" << size_t(data.totalAllocations / totalTimeS) << "/s)<br/>"
+           << "<strong>peak heap memory consumption</strong>: " << formatBytes(data.peak) << "<br/>"
+           << "<strong>total memory leaked</strong>: " << formatBytes(data.leaked) << "<br/>";
+    stream << "</qt>";
+    return QString::fromStdString(stream.str());
+}
+}
+
+MainWindow::MainWindow(QWidget* parent)
+    : m_ui(new Ui::MainWindow)
+    , m_model(new Model(this))
+{
+    m_ui->setupUi(this);
+    m_ui->results->setModel(m_model);
+}
+
+MainWindow::~MainWindow()
+{
+}
+
+void MainWindow::loadFile(const QString& file)
+{
+    cout << "Loading file " << qPrintable(file) << ", this might take some time - please wait." << endl;
+    AccumulatedTraceData data;
+    data.read(file.toStdString());
+
+    m_ui->summary->setText(generateSummary(data));
+}
diff --git a/gui/mainwindow.h b/gui/mainwindow.h
new file mode 100644 (file)
index 0000000..f8abf08
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015 Milian Wolff <mail@milianw.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+
+namespace Ui {
+class MainWindow;
+}
+
+class Model;
+
+class MainWindow : public QMainWindow
+{
+public:
+    MainWindow(QWidget* parent = nullptr);
+    virtual ~MainWindow();
+
+    void loadFile(const QString& path);
+
+private:
+    QScopedPointer<Ui::MainWindow> m_ui;
+    Model* m_model;
+};
+
+#endif // MAINWINDOW_H
diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui
new file mode 100644 (file)
index 0000000..1f356e3
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>800</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QVBoxLayout" name="verticalLayout">
+    <item>
+     <widget class="QLabel" name="summary">
+      <property name="text">
+       <string/>
+      </property>
+     </widget>
+    </item>
+    <item>
+     <widget class="QTreeView" name="results"/>
+    </item>
+   </layout>
+  </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/gui/model.cpp b/gui/model.cpp
new file mode 100644 (file)
index 0000000..d85ebf4
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2015 Milian Wolff <mail@milianw.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "model.h"
+
+Model::Model(QObject* parent)
+{
+
+}
+
+Model::~Model()
+{
+}
+
+QVariant Model::headerData(int section, Qt::Orientation orientation, int role) const
+{
+    if (orientation != Qt::Horizontal || role != Qt::DisplayRole || section < 0 || section >= NUM_COLUMNS) {
+        return QVariant();
+    }
+    switch (static_cast<Columns>(section)) {
+        case LocationColumn:
+            return tr("Location");
+        case AllocationsColumn:
+            return tr("Allocations");
+        case PeakColumn:
+            return tr("Peak");
+        case LeakedColumn:
+            return tr("Leaked");
+        case AllocatedColumn:
+            return tr("Allocated");
+        case NUM_COLUMNS:
+            break;
+    }
+    return QVariant();
+}
+
+QVariant Model::data(const QModelIndex& index, int role) const
+{
+    return QVariant();
+}
+
+QModelIndex Model::index(int row, int column, const QModelIndex& parent) const
+{
+    return QModelIndex();
+}
+
+QModelIndex Model::parent(const QModelIndex& child) const
+{
+    return QModelIndex();
+}
+
+int Model::rowCount(const QModelIndex& parent) const
+{
+    return 0;
+}
+
+int Model::columnCount(const QModelIndex& parent) const
+{
+    return NUM_COLUMNS;
+}
diff --git a/gui/model.h b/gui/model.h
new file mode 100644 (file)
index 0000000..d518a2f
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015 Milian Wolff <mail@milianw.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef MODEL_H
+#define MODEL_H
+
+#include <QAbstractItemModel>
+
+class Model : public QAbstractItemModel
+{
+    Q_OBJECT
+public:
+    Model(QObject* parent);
+    virtual ~Model();
+
+    enum Columns {
+        AllocationsColumn,
+        PeakColumn,
+        LeakedColumn,
+        AllocatedColumn,
+        LocationColumn,
+        NUM_COLUMNS
+    };
+
+    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
+    QModelIndex parent(const QModelIndex &child) const override;
+    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+    int columnCount(const QModelIndex &parent = QModelIndex()) const override;
+
+private:
+
+};
+
+#endif // MODEL_H
+
index cc0177a..53cee4a 100644 (file)
  * @brief Evaluate and print the collected heaptrack data.
  */
 
-#include <iostream>
-#include <fstream>
-#include <sstream>
-#include <unordered_map>
-#include <unordered_set>
-#include <map>
-#include <vector>
-#include <memory>
-#include <tuple>
-#include <algorithm>
-#include <cassert>
-#include <iomanip>
-
-#include <boost/iostreams/filtering_stream.hpp>
-#include <boost/iostreams/filter/gzip.hpp>
-#include <boost/algorithm/string/predicate.hpp>
 #include <boost/program_options.hpp>
 
-#include "linereader.h"
+#include "accumulatedtracedata.h"
+
+#include <iostream>
 
 using namespace std;
 namespace po = boost::program_options;
 
-namespace {
-
-class formatBytes
+struct Printer final : public AccumulatedTraceData
 {
-public:
-    formatBytes(size_t bytes)
-        : m_bytes(bytes)
-    {
-    }
-
-    friend ostream& operator<<(ostream& out, const formatBytes data)
-    {
-        if (data.m_bytes < 1000) {
-            // no fancy formatting for plain byte values, esp. no .00 factions
-            return out << data.m_bytes << 'B';
-        }
-
-        static const auto units = {
-            "B",
-            "KB",
-            "MB",
-            "GB",
-            "TB"
-        };
-        auto unit = units.begin();
-        size_t i = 0;
-        double bytes = data.m_bytes;
-        while (i < units.size() - 1 && bytes > 1000.) {
-            bytes /= 1000.;
-            ++i;
-            ++unit;
-        }
-        return out << fixed << setprecision(2) << bytes << *unit;
-    }
-
-private:
-    size_t m_bytes;
-};
-
-// sadly, C++ doesn't yet have opaque typedefs
-template<typename Base>
-struct Index
-{
-    size_t index = 0;
-
-    explicit operator bool() const
-    {
-        return index;
-    }
-
-    bool operator<(Index o) const
-    {
-        return index < o.index;
-    }
-
-    bool operator!=(Index o) const
-    {
-        return index != o.index;
-    }
-
-    bool operator==(Index o) const
-    {
-        return index == o.index;
-    }
-};
-
-template<typename Base>
-bool operator>>(LineReader& reader, Index<Base> &index)
-{
-    return reader.readHex(index.index);
-}
-
-template<typename Base>
-ostream& operator<<(ostream &out, const Index<Base> index)
-{
-    out << index.index;
-    return out;
-}
-
-struct StringIndex : public Index<StringIndex> {};
-struct ModuleIndex : public StringIndex {};
-struct FunctionIndex : public StringIndex {};
-struct FileIndex : public StringIndex {};
-struct IpIndex : public Index<IpIndex> {};
-struct TraceIndex : public Index<TraceIndex> {};
-
-struct InstructionPointer
-{
-    uintptr_t instructionPointer = 0;
-    ModuleIndex moduleIndex;
-    FunctionIndex functionIndex;
-    FileIndex fileIndex;
-    int line = 0;
-
-    bool compareWithoutAddress(const InstructionPointer &other) const
-    {
-        return make_tuple(moduleIndex, functionIndex, fileIndex, line)
-             < make_tuple(other.moduleIndex, other.functionIndex, other.fileIndex, other.line);
-    }
-
-    bool equalWithoutAddress(const InstructionPointer &other) const
-    {
-        return make_tuple(moduleIndex, functionIndex, fileIndex, line)
-            == make_tuple(other.moduleIndex, other.functionIndex, other.fileIndex, other.line);
-    }
-};
-
-struct TraceNode
-{
-    IpIndex ipIndex;
-    TraceIndex parentIndex;
-};
-
-struct AllocationData
-{
-    // number of allocations
-    size_t allocations = 0;
-    // bytes allocated in total
-    size_t allocated = 0;
-    // amount of bytes leaked
-    size_t leaked = 0;
-    // largest amount of bytes allocated
-    size_t peak = 0;
-};
-
-struct Allocation : public AllocationData
-{
-    // backtrace entry point
-    TraceIndex traceIndex;
-};
-
-/**
- * Merged allocation information by instruction pointer outside of alloc funcs
- */
-struct MergedAllocation : public AllocationData
-{
-    // individual backtraces
-    vector<Allocation> traces;
-    // location
-    IpIndex ipIndex;
-};
-
-/**
- * Information for a single call to an allocation function
- */
-struct AllocationInfo
-{
-    TraceIndex traceIndex;
-    size_t size;
-};
-
-struct AccumulatedTraceData
-{
-    AccumulatedTraceData()
-    {
-        instructionPointers.reserve(16384);
-        traces.reserve(65536);
-        strings.reserve(4096);
-        allocations.reserve(16384);
-        activeAllocations.reserve(65536);
-        stopIndices.reserve(4);
-    }
-
-    void clear()
-    {
-        stopIndices.clear();
-        instructionPointers.clear();
-        traces.clear();
-        strings.clear();
-        mergedAllocations.clear();
-        allocations.clear();
-        activeAllocations.clear();
-    }
-
     void printIp(const IpIndex ip, ostream &out, const size_t indent = 0) const
     {
         printIp(findIp(ip), out, indent);
@@ -352,332 +166,6 @@ struct AccumulatedTraceData
         cout << endl;
     }
 
-    const string& stringify(const StringIndex stringId) const
-    {
-        if (!stringId || stringId.index > strings.size()) {
-            static const string empty;
-            return empty;
-        } else {
-            return strings.at(stringId.index - 1);
-        }
-    }
-
-    string prettyFunction(const string& function) const
-    {
-        if (!shortenTemplates) {
-            return function;
-        }
-        string ret;
-        ret.reserve(function.size());
-        int depth = 0;
-        for (size_t i = 0; i < function.size(); ++i) {
-            const auto c = function[i];
-            if ((c == '<' || c == '>') && ret.size() >= 8) {
-                // don't get confused by C++ operators
-                const char* cmp = "operator";
-                if (ret.back() == c) {
-                    // skip second angle bracket for operator<< or operator>>
-                    if (c == '<') {
-                        cmp = "operator<";
-                    } else {
-                        cmp = "operator>";
-                    }
-                }
-                if (boost::algorithm::ends_with(ret, cmp)) {
-                    ret.push_back(c);
-                    continue;
-                }
-            }
-            if (c == '<') {
-                ++depth;
-                if (depth == 1) {
-                    ret.push_back(c);
-                }
-            } else if (c == '>') {
-                --depth;
-            }
-            if (depth) {
-                continue;
-            }
-            ret.push_back(c);
-        }
-        return ret;
-    }
-
-    bool read(istream& in)
-    {
-        clear();
-
-        LineReader reader;
-        size_t timeStamp = 0;
-
-        vector<StringIndex> opNewStrIndices;
-        opNewStrIndices.reserve(16);
-        vector<IpIndex> opNewIpIndices;
-        opNewIpIndices.reserve(16);
-        vector<string> opNewStrings = {
-            "operator new(unsigned long)",
-            "operator new[](unsigned long)"
-        };
-
-        vector<string> stopStrings = {
-            "main",
-            "__libc_start_main",
-            "__static_initialization_and_destruction_0"
-        };
-
-        while (reader.getLine(in)) {
-            if (reader.mode() == 's') {
-                strings.push_back(reader.line().substr(2));
-                StringIndex index;
-                index.index = strings.size();
-
-                auto opNewIt = find(opNewStrings.begin(), opNewStrings.end(), strings.back());
-                if (opNewIt != opNewStrings.end()) {
-                    opNewStrIndices.push_back(index);
-                    opNewStrings.erase(opNewIt);
-                } else {
-                    auto stopIt = find(stopStrings.begin(), stopStrings.end(), strings.back());
-                    if (stopIt != stopStrings.end()) {
-                        stopIndices.push_back(index);
-                        stopStrings.erase(stopIt);
-                    }
-                }
-            } else if (reader.mode() == 't') {
-                TraceNode node;
-                reader >> node.ipIndex;
-                reader >> node.parentIndex;
-                // skip operator new and operator new[] at the beginning of traces
-                while (find(opNewIpIndices.begin(), opNewIpIndices.end(), node.ipIndex) != opNewIpIndices.end()) {
-                    node = findTrace(node.parentIndex);
-                }
-                traces.push_back(node);
-            } else if (reader.mode() == 'i') {
-                InstructionPointer ip;
-                reader >> ip.instructionPointer;
-                reader >> ip.moduleIndex;
-                reader >> ip.functionIndex;
-                reader >> ip.fileIndex;
-                reader >> ip.line;
-                instructionPointers.push_back(ip);
-                if (find(opNewStrIndices.begin(), opNewStrIndices.end(), ip.functionIndex) != opNewStrIndices.end()) {
-                    IpIndex index;
-                    index.index = instructionPointers.size();
-                    opNewIpIndices.push_back(index);
-                }
-            } else if (reader.mode() == '+') {
-                size_t size = 0;
-                TraceIndex traceId;
-                uintptr_t ptr = 0;
-                if (!(reader >> size) || !(reader >> traceId) || !(reader >> ptr)) {
-                    cerr << "failed to parse line: " << reader.line() << endl;
-                    continue;
-                }
-
-                activeAllocations[ptr] = {traceId, size};
-
-                auto& allocation = findAllocation(traceId);
-                allocation.leaked += size;
-                allocation.allocated += size;
-                ++allocation.allocations;
-                if (allocation.leaked > allocation.peak) {
-                    allocation.peak = allocation.leaked;
-                }
-                totalAllocated += size;
-                ++totalAllocations;
-                leaked += size;
-                if (leaked > peak) {
-                    peak = leaked;
-                }
-                if (leaked > lastMassifPeak && massifOut.is_open()) {
-                    massifAllocations = allocations;
-                    lastMassifPeak = leaked;
-                }
-                if (printHistogram) {
-                    ++sizeHistogram[size];
-                }
-            } else if (reader.mode() == '-') {
-                uintptr_t ptr = 0;
-                if (!(reader >> ptr)) {
-                    cerr << "failed to parse line: " << reader.line() << endl;
-                    continue;
-                }
-                auto ip = activeAllocations.find(ptr);
-                if (ip == activeAllocations.end()) {
-                    if (!fromAttached) {
-                        cerr << "unknown pointer in line: " << reader.line() << endl;
-                    }
-                    continue;
-                }
-                const auto info = ip->second;
-                activeAllocations.erase(ip);
-
-                auto& allocation = findAllocation(info.traceIndex);
-                if (!allocation.allocations || allocation.leaked < info.size) {
-                    if (!fromAttached) {
-                        cerr << "inconsistent allocation info, underflowed allocations of " << info.traceIndex << endl;
-                    }
-                    allocation.leaked = 0;
-                    allocation.allocations = 0;
-                } else {
-                    allocation.leaked -= info.size;
-                }
-                leaked -= info.size;
-            } else if (reader.mode() == '#') {
-                // comment or empty line
-                continue;
-            } else if (reader.mode() == 'c') {
-                size_t newStamp = 0;
-                if (!(reader >> newStamp)) {
-                    cerr << "Failed to read time stamp: " << reader.line() << endl;
-                    continue;
-                }
-                if (massifOut.is_open()) {
-                    writeMassifSnapshot(timeStamp, false);
-                }
-                timeStamp = newStamp;
-            } else if (reader.mode() == 'X') {
-                cout << "Debuggee command was: " << (reader.line().c_str() + 2) << endl;
-                if (massifOut.is_open()) {
-                    writeMassifHeader(reader.line().c_str() + 2);
-                }
-            } else if (reader.mode() == 'A') {
-                leaked = 0;
-                peak = 0;
-                fromAttached = true;
-            } else {
-                cerr << "failed to parse line: " << reader.line() << endl;
-            }
-        }
-
-        /// these are leaks, but we now have the same data in \c allocations as well
-        activeAllocations.clear();
-
-        totalTime = max(timeStamp, size_t(1));
-
-        if (massifOut.is_open()) {
-            writeMassifSnapshot(totalTime, true);
-        }
-
-        filterAllocations();
-        mergedAllocations = mergeAllocations(allocations);
-
-        return true;
-    }
-
-    bool shortenTemplates = false;
-    bool mergeBacktraces = true;
-    bool printHistogram = false;
-    bool fromAttached = false;
-    ofstream massifOut;
-    double massifThreshold = 1;
-    size_t massifDetailedFreq = 1;
-    string filterBtFunction;
-
-    vector<Allocation> allocations;
-    vector<MergedAllocation> mergedAllocations;
-    map<size_t, size_t> sizeHistogram;
-    size_t totalAllocated = 0;
-    size_t totalAllocations = 0;
-    size_t peak = 0;
-    size_t leaked = 0;
-    size_t totalTime = 0;
-
-private:
-    // our indices are sequentially increasing thus a new allocation can only ever
-    // occur with an index larger than any other we encountered so far
-    // this can be used to our advantage in speeding up the findAllocation calls.
-    TraceIndex m_maxAllocationTraceIndex;
-
-    Allocation& findAllocation(const TraceIndex traceIndex)
-    {
-        if (traceIndex < m_maxAllocationTraceIndex) {
-            // only need to search when the trace index is previously known
-            auto it = lower_bound(allocations.begin(), allocations.end(), traceIndex,
-                                [] (const Allocation& allocation, const TraceIndex traceIndex) -> bool {
-                                    return allocation.traceIndex < traceIndex;
-                                });
-            assert(it != allocations.end());
-            assert(it->traceIndex == traceIndex);
-            return *it;
-        } else if (traceIndex == m_maxAllocationTraceIndex && !allocations.empty()) {
-            // reuse the last allocation
-            assert(allocations.back().traceIndex == traceIndex);
-        } else {
-            // actually a new allocation
-            Allocation allocation;
-            allocation.traceIndex = traceIndex;
-            allocations.push_back(allocation);
-            m_maxAllocationTraceIndex = traceIndex;
-        }
-        return allocations.back();
-    }
-
-    void mergeAllocation(vector<MergedAllocation>* mergedAllocations, const Allocation& allocation) const
-    {
-        const auto trace = findTrace(allocation.traceIndex);
-        const auto traceIp = findIp(trace.ipIndex);
-        auto it = lower_bound(mergedAllocations->begin(), mergedAllocations->end(), traceIp,
-                                [this] (const MergedAllocation& allocation, const InstructionPointer traceIp) -> bool {
-                                    // Compare meta data without taking the instruction pointer address into account.
-                                    // This is useful since sometimes, esp. when we lack debug symbols, the same function
-                                    // allocates memory at different IP addresses which is pretty useless information most of the time
-                                    // TODO: make this configurable, but on-by-default
-                                    const auto allocationIp = findIp(allocation.ipIndex);
-                                    return allocationIp.compareWithoutAddress(traceIp);
-                                });
-        if (it == mergedAllocations->end() || !findIp(it->ipIndex).equalWithoutAddress(traceIp)) {
-            MergedAllocation merged;
-            merged.ipIndex = trace.ipIndex;
-            it = mergedAllocations->insert(it, merged);
-        }
-        it->traces.push_back(allocation);
-    }
-
-    // merge allocations so that different traces that point to the same
-    // instruction pointer at the end where the allocation function is
-    // called are combined
-    vector<MergedAllocation> mergeAllocations(const vector<Allocation>& allocations) const
-    {
-        // TODO: merge deeper traces, i.e. A,B,C,D and A,B,C,F
-        //       should be merged to A,B,C: D & F
-        //       currently the below will only merge it to: A: B,C,D & B,C,F
-        vector<MergedAllocation> ret;
-        ret.reserve(allocations.size());
-        for (const Allocation& allocation : allocations) {
-            if (allocation.traceIndex) {
-                mergeAllocation(&ret, allocation);
-            }
-        }
-        for (MergedAllocation& merged : ret) {
-            for (const Allocation& allocation: merged.traces) {
-                merged.allocated += allocation.allocated;
-                merged.allocations += allocation.allocations;
-                merged.leaked += allocation.leaked;
-                merged.peak += allocation.peak;
-            }
-        }
-        return ret;
-    }
-
-    InstructionPointer findIp(const IpIndex ipIndex) const
-    {
-        if (!ipIndex || ipIndex.index > instructionPointers.size()) {
-            return {};
-        } else {
-            return instructionPointers[ipIndex.index - 1];
-        }
-    }
-
-    TraceNode findTrace(const TraceIndex traceIndex) const
-    {
-        if (!traceIndex || traceIndex.index > traces.size()) {
-            return {};
-        } else {
-            return traces[traceIndex.index - 1];
-        }
-    }
-
     void writeMassifHeader(const char* command)
     {
         // write massif header
@@ -796,46 +284,33 @@ private:
         }
     }
 
-    bool isStopIndex(const StringIndex index) const
+    void handleAllocation() override
     {
-        return find(stopIndices.begin(), stopIndices.end(), index) != stopIndices.end();
+        if (leaked > lastMassifPeak && massifOut.is_open()) {
+            massifAllocations = allocations;
+            lastMassifPeak = leaked;
+        }
     }
 
-    void filterAllocations()
+    void handleTimeStamp(size_t /*newStamp*/, size_t oldStamp) override
     {
-        if (filterBtFunction.empty()) {
-            return;
+        if (massifOut.is_open()) {
+            writeMassifSnapshot(oldStamp, oldStamp == totalTime);
         }
-        allocations.erase(remove_if(allocations.begin(), allocations.end(), [&] (const Allocation& allocation) -> bool {
-            auto node = findTrace(allocation.traceIndex);
-            while (node.ipIndex) {
-                const auto& ip = findIp(node.ipIndex);
-                if (isStopIndex(ip.functionIndex)) {
-                    break;
-                }
-                if (stringify(ip.functionIndex).find(filterBtFunction) != string::npos) {
-                    return false;
-                }
-                node = findTrace(node.parentIndex);
-            };
-            return true;
-        }), allocations.end());
     }
 
-    // indices of functions that should stop the backtrace, e.g. main or static initialization
-    vector<StringIndex> stopIndices;
-    unordered_map<uintptr_t, AllocationInfo> activeAllocations;
-    vector<InstructionPointer> instructionPointers;
-    vector<TraceNode> traces;
-    vector<string> strings;
+    void handleDebuggee(const char* command) override
+    {
+        if (massifOut.is_open()) {
+            writeMassifHeader(command);
+        }
+    }
 
     size_t massifSnapshotId = 0;
     size_t lastMassifPeak = 0;
     vector<Allocation> massifAllocations;
 };
 
-}
-
 int main(int argc, char** argv)
 {
     po::options_description desc("Options");
@@ -891,7 +366,7 @@ int main(int argc, char** argv)
         return 1;
     }
 
-    AccumulatedTraceData data;
+    Printer data;
 
     const auto inputFile = vm["file"].as<string>();
     data.shortenTemplates = vm["shorten-templates"].as<bool>();
@@ -914,25 +389,8 @@ int main(int argc, char** argv)
     const bool printPeaks = vm["print-peaks"].as<bool>();
     const bool printAllocs = vm["print-allocators"].as<bool>();
 
-    string fileName(inputFile);
-    const bool isCompressed = boost::algorithm::ends_with(fileName, ".gz");
-    ifstream file(fileName, isCompressed ? ios_base::in | ios_base::binary : ios_base::in);
-
-    if (!file.is_open()) {
-        cerr << "Failed to open heaptrack log file: " << inputFile << endl
-             << endl << desc << endl;
-        return 1;
-    }
-
-    boost::iostreams::filtering_istream in;
-    if (isCompressed) {
-        in.push(boost::iostreams::gzip_decompressor());
-    }
-    in.push(file);
-
-    cout << "reading file \"" << fileName << "\" - please wait, this might take some time..." << endl;
-
-    if (!data.read(in)) {
+    cout << "reading file \"" << inputFile << "\" - please wait, this might take some time..." << endl;
+    if (!data.read(inputFile)) {
         return 1;
     }