From 93bcc0d4a5fd76c72df81f2af231909254e28b8e Mon Sep 17 00:00:00 2001 From: Milian Wolff Date: Mon, 18 May 2015 16:13:59 +0200 Subject: [PATCH] Start a simple Qt GUI and share some code with the ASCII printer. --- CMakeLists.txt | 7 +- accumulatedtracedata.cpp | 446 ++++++++++++++++++++++++++++++++++++ accumulatedtracedata.h | 209 +++++++++++++++++ gui/CMakeLists.txt | 18 ++ gui/gui.cpp | 42 ++++ gui/mainwindow.cpp | 69 ++++++ gui/mainwindow.h | 44 ++++ gui/mainwindow.ui | 33 +++ gui/model.cpp | 76 ++++++ gui/model.h | 53 +++++ heaptrack_print.cpp | 584 ++--------------------------------------------- 11 files changed, 1017 insertions(+), 564 deletions(-) create mode 100644 accumulatedtracedata.cpp create mode 100644 accumulatedtracedata.h create mode 100644 gui/CMakeLists.txt create mode 100644 gui/gui.cpp create mode 100644 gui/mainwindow.cpp create mode 100644 gui/mainwindow.h create mode 100644 gui/mainwindow.ui create mode 100644 gui/model.cpp create mode 100644 gui/model.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5cdb6ee..56cc414 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 index 0000000..ac96142 --- /dev/null +++ b/accumulatedtracedata.cpp @@ -0,0 +1,446 @@ +/* + * Copyright 2015 Milian Wolff + * + * 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 +#include +#include +#include +#include + +#include +#include +#include + +#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 +bool operator>>(LineReader& reader, Index &index) +{ + return reader.readHex(index.index); +} + +template +ostream& operator<<(ostream &out, const Index 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 opNewStrIndices; + opNewStrIndices.reserve(16); + vector opNewIpIndices; + opNewIpIndices.reserve(16); + vector opNewStrings = { + "operator new(unsigned long)", + "operator new[](unsigned long)" + }; + + vector 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* 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 AccumulatedTraceData::mergeAllocations(const vector& 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 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 index 0000000..156bad8 --- /dev/null +++ b/accumulatedtracedata.h @@ -0,0 +1,209 @@ +/* + * Copyright 2015 Milian Wolff + * + * 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 +#include +#include + +#include +#include +#include + +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 +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 {}; +struct ModuleIndex : public StringIndex {}; +struct FunctionIndex : public StringIndex {}; +struct FileIndex : public StringIndex {}; +struct IpIndex : public Index {}; +struct TraceIndex : public Index {}; + +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 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 allocations; + std::vector mergedAllocations; + std::map 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* 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 mergeAllocations(const std::vector& 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 stopIndices; + std::unordered_map activeAllocations; + std::vector instructionPointers; + std::vector traces; + std::vector strings; +}; + +#endif // ACCUMULATEDTRACEDATA_H diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt new file mode 100644 index 0000000..8c8b7e7 --- /dev/null +++ b/gui/CMakeLists.txt @@ -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 index 0000000..1304f9e --- /dev/null +++ b/gui/gui.cpp @@ -0,0 +1,42 @@ +/* + * Copyright 2015 Milian Wolff + * + * 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 + +#include "mainwindow.h" + +#include + +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 index 0000000..b7b2a5f --- /dev/null +++ b/gui/mainwindow.cpp @@ -0,0 +1,69 @@ +/* + * Copyright 2015 Milian Wolff + * + * 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 + +#include +#include + +#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 << "" + << "total runtime: " << fixed << totalTimeS << "s.
" + << "bytes allocated in total (ignoring deallocations): " << formatBytes(data.totalAllocated) + << " (" << formatBytes(data.totalAllocated / totalTimeS) << "/s)
" + << "calls to allocation functions: " << data.totalAllocations + << " (" << size_t(data.totalAllocations / totalTimeS) << "/s)
" + << "peak heap memory consumption: " << formatBytes(data.peak) << "
" + << "total memory leaked: " << formatBytes(data.leaked) << "
"; + stream << "
"; + 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 index 0000000..f8abf08 --- /dev/null +++ b/gui/mainwindow.h @@ -0,0 +1,44 @@ +/* + * Copyright 2015 Milian Wolff + * + * 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 + +namespace Ui { +class MainWindow; +} + +class Model; + +class MainWindow : public QMainWindow +{ +public: + MainWindow(QWidget* parent = nullptr); + virtual ~MainWindow(); + + void loadFile(const QString& path); + +private: + QScopedPointer m_ui; + Model* m_model; +}; + +#endif // MAINWINDOW_H diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui new file mode 100644 index 0000000..1f356e3 --- /dev/null +++ b/gui/mainwindow.ui @@ -0,0 +1,33 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + + + + + + + + + + + + + + diff --git a/gui/model.cpp b/gui/model.cpp new file mode 100644 index 0000000..d85ebf4 --- /dev/null +++ b/gui/model.cpp @@ -0,0 +1,76 @@ +/* + * Copyright 2015 Milian Wolff + * + * 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(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 index 0000000..d518a2f --- /dev/null +++ b/gui/model.h @@ -0,0 +1,53 @@ +/* + * Copyright 2015 Milian Wolff + * + * 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 + +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 + diff --git a/heaptrack_print.cpp b/heaptrack_print.cpp index cc0177a..53cee4a 100644 --- a/heaptrack_print.cpp +++ b/heaptrack_print.cpp @@ -23,203 +23,17 @@ * @brief Evaluate and print the collected heaptrack data. */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include #include -#include "linereader.h" +#include "accumulatedtracedata.h" + +#include 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 -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 -bool operator>>(LineReader& reader, Index &index) -{ - return reader.readHex(index.index); -} - -template -ostream& operator<<(ostream &out, const Index index) -{ - out << index.index; - return out; -} - -struct StringIndex : public Index {}; -struct ModuleIndex : public StringIndex {}; -struct FunctionIndex : public StringIndex {}; -struct FileIndex : public StringIndex {}; -struct IpIndex : public Index {}; -struct TraceIndex : public Index {}; - -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 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 opNewStrIndices; - opNewStrIndices.reserve(16); - vector opNewIpIndices; - opNewIpIndices.reserve(16); - vector opNewStrings = { - "operator new(unsigned long)", - "operator new[](unsigned long)" - }; - - vector 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 allocations; - vector mergedAllocations; - map 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* 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 mergeAllocations(const vector& 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 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 stopIndices; - unordered_map activeAllocations; - vector instructionPointers; - vector traces; - vector strings; + void handleDebuggee(const char* command) override + { + if (massifOut.is_open()) { + writeMassifHeader(command); + } + } size_t massifSnapshotId = 0; size_t lastMassifPeak = 0; vector 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(); data.shortenTemplates = vm["shorten-templates"].as(); @@ -914,25 +389,8 @@ int main(int argc, char** argv) const bool printPeaks = vm["print-peaks"].as(); const bool printAllocs = vm["print-allocators"].as(); - 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; } -- 2.7.4