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)
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)")
--- /dev/null
+/*
+ * 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());
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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));
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+<?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>
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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
+
* @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);
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
}
}
- 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");
return 1;
}
- AccumulatedTraceData data;
+ Printer data;
const auto inputFile = vm["file"].as<string>();
data.shortenTemplates = vm["shorten-templates"].as<bool>();
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;
}