--- /dev/null
+---
+BasedOnStyle: WebKit
+Language: Cpp
+AlignAfterOpenBracket: true
+AlwaysBreakTemplateDeclarations: true
+BreakBeforeBraces: Custom
+BreakConstructorInitializersBeforeComma: true
+BreakBeforeBinaryOperators: NonAssignment
+BraceWrapping:
+ AfterClass: true
+ AfterControlStatement: false
+ AfterEnum: true
+ AfterFunction: true
+ AfterNamespace: false
+ AfterObjCDeclaration: false
+ AfterStruct: true
+ AfterUnion: false
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
+ColumnLimit: 120
+Standard: Cpp11
+IndentWidth: 4
+TabWidth: 8
+UseTab: Never
+PointerAlignment: Left
+SpacesInParentheses: false
+SpacesInAngles: false
+SpaceInEmptyParentheses: false
+SpacesInCStyleCastParentheses: false
+SpaceAfterControlStatementKeyword: true
+SpaceBeforeAssignmentOperators: true
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+IncludeCategories:
+ - Regex: '^<boost/'
+ Priority: 1
+ - Regex: '^<Q'
+ Priority: 2
+AllowShortFunctionsOnASingleLine: None
+ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE, TEST_CASE, SECTION ]
+NamespaceIndentation: None
+# Only for newer clang-format versions
+#MacroBlockBegin: BEGINTESTFUNCIMPL
+#MacroBlockEnd: ENDTESTFUNCIMPL
+...
+
#include "accumulatedtracedata.h"
-#include <iostream>
-#include <memory>
#include <algorithm>
#include <cassert>
+#include <iostream>
+#include <memory>
-#include <boost/iostreams/filtering_stream.hpp>
-#include <boost/iostreams/filter/gzip.hpp>
#include <boost/algorithm/string/predicate.hpp>
+#include <boost/iostreams/filter/gzip.hpp>
+#include <boost/iostreams/filtering_stream.hpp>
-#include "util/linereader.h"
#include "util/config.h"
+#include "util/linereader.h"
#include "util/pointermap.h"
#ifdef __GNUC__
-#define POTENTIALLY_UNUSED __attribute__ ((unused))
+#define POTENTIALLY_UNUSED __attribute__((unused))
#else
#define POTENTIALLY_UNUSED
#endif
namespace {
-template<typename Base>
-bool operator>>(LineReader& reader, Index<Base> &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)
+template <typename Base>
+ostream& operator<<(ostream& out, const Index<Base> index)
{
out << index.index;
return out;
}
-
}
AccumulatedTraceData::AccumulatedTraceData()
vector<string> opNewStrings = {
// 64 bit
- "operator new(unsigned long)",
- "operator new[](unsigned long)",
+ "operator new(unsigned long)", "operator new[](unsigned long)",
// 32 bit
- "operator new(unsigned int)",
- "operator new[](unsigned int)",
+ "operator new(unsigned int)", "operator new[](unsigned int)",
};
vector<StringIndex> opNewStrIndices;
opNewStrIndices.reserve(opNewStrings.size());
- vector<string> stopStrings = {
- "main",
- "__libc_start_main",
- "__static_initialization_and_destruction_0"
- };
+ vector<string> stopStrings = {"main", "__libc_start_main", "__static_initialization_and_destruction_0"};
const bool reparsing = totalTime != 0;
m_maxAllocationTraceIndex.index = 0;
cerr << "failed to parse line: " << reader.line() << endl;
continue;
} else if (allocationIndex.index >= allocationInfos.size()) {
- cerr << "allocation index out of bounds: " << allocationIndex.index << ", maximum is: " << allocationInfos.size() << endl;
+ cerr << "allocation index out of bounds: " << allocationIndex.index
+ << ", maximum is: " << allocationInfos.size() << endl;
continue;
}
info = allocationInfos[allocationIndex.index];
fileVersion = 1;
}
if (fileVersion > HEAPTRACK_FILE_FORMAT_VERSION) {
- cerr << "The data file has version " << hex << fileVersion
- << " (written by heaptrack version " << hex << heaptrackVersion << ")\n"
- << "This is not compatible with this build of heaptrack (version " << hex << HEAPTRACK_VERSION << ")"
- << ", which can read file format version " << hex << HEAPTRACK_FILE_FORMAT_VERSION << " and below" << endl;
+ cerr << "The data file has version " << hex << fileVersion << " and was written by heaptrack version "
+ << hex << heaptrackVersion << ")\n"
+ << "This is not compatible with this build of heaptrack (version " << hex << HEAPTRACK_VERSION
+ << "), which can read file format version " << hex << HEAPTRACK_FILE_FORMAT_VERSION << " and below"
+ << endl;
return false;
}
} else if (reader.mode() == 'I') { // system information
namespace { // helpers for diffing
-template<typename IndexT, typename SortF>
+template <typename IndexT, typename SortF>
vector<IndexT> sortedIndices(size_t numIndices, SortF sorter)
{
vector<IndexT> indices;
return map;
}
-template<typename T>
+template <typename T>
inline const T& identity(const T& t)
{
return t;
}
-template<typename IpMapper>
-int compareTraceIndices(const TraceIndex &lhs, const AccumulatedTraceData& lhsData,
- const TraceIndex &rhs, const AccumulatedTraceData& rhsData,
- IpMapper ipMapper)
+template <typename IpMapper>
+int compareTraceIndices(const TraceIndex& lhs, const AccumulatedTraceData& lhsData, const TraceIndex& rhs,
+ const AccumulatedTraceData& rhsData, IpMapper ipMapper)
{
if (!lhs && !rhs) {
return 0;
const auto& lhsTrace = lhsData.findTrace(lhs);
const auto& rhsTrace = rhsData.findTrace(rhs);
- const int parentComparsion = compareTraceIndices(lhsTrace.parentIndex, lhsData, rhsTrace.parentIndex, rhsData, ipMapper);
+ const int parentComparsion =
+ compareTraceIndices(lhsTrace.parentIndex, lhsData, rhsTrace.parentIndex, rhsData, ipMapper);
if (parentComparsion != 0) {
return parentComparsion;
} // else fall-through to below, parents are equal
do {
const auto trace = data.findTrace(index);
const auto& ip = data.findIp(trace.ipIndex);
- cerr << index << " (" << trace.ipIndex << ", " << trace.parentIndex << ")"
- << '\t' << data.stringify(ip.functionIndex)
- << " in " << data.stringify(ip.moduleIndex)
- << " at " << data.stringify(ip.fileIndex) << ':' << ip.line
- << '\n';
+ cerr << index << " (" << trace.ipIndex << ", " << trace.parentIndex << ")" << '\t'
+ << data.stringify(ip.functionIndex) << " in " << data.stringify(ip.moduleIndex) << " at "
+ << data.stringify(ip.fileIndex) << ':' << ip.line << '\n';
index = trace.parentIndex;
} while (index);
cerr << "---\n";
allocationTraceNodes.reserve(allocations.size());
for (auto it = allocations.begin(); it != allocations.end();) {
const auto& allocation = *it;
- auto sortedIt = lower_bound(allocationTraceNodes.begin(), allocationTraceNodes.end(), allocation.traceIndex,
- [this] (const TraceIndex& lhs, const TraceIndex& rhs) -> bool {
- return compareTraceIndices(lhs, *this,
- rhs, *this,
- identity<InstructionPointer>) < 0;
- });
+ auto sortedIt =
+ lower_bound(allocationTraceNodes.begin(), allocationTraceNodes.end(), allocation.traceIndex,
+ [this](const TraceIndex& lhs, const TraceIndex& rhs) -> bool {
+ return compareTraceIndices(lhs, *this, rhs, *this, identity<InstructionPointer>) < 0;
+ });
if (sortedIt == allocationTraceNodes.end()
- || compareTraceIndices(allocation.traceIndex, *this, *sortedIt, *this, identity<InstructionPointer>) != 0)
- {
+ || compareTraceIndices(allocation.traceIndex, *this, *sortedIt, *this, identity<InstructionPointer>) != 0) {
allocationTraceNodes.insert(sortedIt, allocation.traceIndex);
++it;
} else if (*sortedIt != allocation.traceIndex) {
// step 3: map string indices from rhs to lhs data
const auto& stringMap = remapStrings(strings, base.strings);
- auto remapString = [&stringMap] (StringIndex& index) {
+ auto remapString = [&stringMap](StringIndex& index) {
if (index) {
index.index = stringMap[index.index].index;
}
};
- auto remapIp = [&remapString] (InstructionPointer ip) -> InstructionPointer {
+ auto remapIp = [&remapString](InstructionPointer ip) -> InstructionPointer {
remapString(ip.moduleIndex);
remapString(ip.functionIndex);
remapString(ip.fileIndex);
// step 4: iterate over rhs data and find matching traces
// if no match is found, copy the data over
- auto sortedIps = sortedIndices<IpIndex>(instructionPointers.size(),
- [this] (const IpIndex &lhs, const IpIndex &rhs) {
- return findIp(lhs).compareWithoutAddress(findIp(rhs));
- });
+ auto sortedIps = sortedIndices<IpIndex>(instructionPointers.size(), [this](const IpIndex& lhs, const IpIndex& rhs) {
+ return findIp(lhs).compareWithoutAddress(findIp(rhs));
+ });
// map an IpIndex from the rhs data into the lhs data space, or copy the data
// if it does not exist yet
- auto remapIpIndex = [&sortedIps, this, &base, &remapIp] (IpIndex rhsIndex) -> IpIndex {
+ auto remapIpIndex = [&sortedIps, this, &base, &remapIp](IpIndex rhsIndex) -> IpIndex {
if (!rhsIndex) {
return rhsIndex;
}
const auto& lhsIp = remapIp(rhsIp);
auto it = lower_bound(sortedIps.begin(), sortedIps.end(), lhsIp,
- [this] (const IpIndex &lhs, const InstructionPointer &rhs) {
+ [this](const IpIndex& lhs, const InstructionPointer& rhs) {
return findIp(lhs).compareWithoutAddress(rhs);
});
if (it != sortedIps.end() && findIp(*it).equalWithoutAddress(lhsIp)) {
return ret;
};
- // copy the rhs trace index and the data it references into the lhs data, recursively
- function<TraceIndex (TraceIndex)> copyTrace = [this, &base, remapIpIndex, ©Trace] (TraceIndex rhsIndex) -> TraceIndex {
+ // copy the rhs trace index and the data it references into the lhs data,
+ // recursively
+ function<TraceIndex(TraceIndex)> copyTrace = [this, &base, remapIpIndex,
+ ©Trace](TraceIndex rhsIndex) -> TraceIndex {
if (!rhsIndex) {
return rhsIndex;
}
};
// find an equivalent trace or copy the data over if it does not exist yet
- // a trace is equivalent if the complete backtrace has equal InstructionPointer
+ // a trace is equivalent if the complete backtrace has equal
+ // InstructionPointer
// data while ignoring the actual pointer address
- auto remapTrace = [&base, &allocationTraceNodes, this, remapIp, copyTrace] (TraceIndex rhsIndex) -> TraceIndex {
+ auto remapTrace = [&base, &allocationTraceNodes, this, remapIp, copyTrace](TraceIndex rhsIndex) -> TraceIndex {
if (!rhsIndex) {
return rhsIndex;
}
auto it = lower_bound(allocationTraceNodes.begin(), allocationTraceNodes.end(), rhsIndex,
- [&base, this, remapIp] (const TraceIndex& lhs, const TraceIndex& rhs) -> bool {
- return compareTraceIndices(lhs, *this, rhs, base, remapIp) < 0;
- });
+ [&base, this, remapIp](const TraceIndex& lhs, const TraceIndex& rhs) -> bool {
+ return compareTraceIndices(lhs, *this, rhs, base, remapIp) < 0;
+ });
- if (it != allocationTraceNodes.end()
- && compareTraceIndices(*it, *this, rhsIndex, base, remapIp) == 0)
- {
+ if (it != allocationTraceNodes.end() && compareTraceIndices(*it, *this, rhsIndex, base, remapIp) == 0) {
return *it;
}
for (const auto& rhsAllocation : base.allocations) {
const auto lhsTrace = remapTrace(rhsAllocation.traceIndex);
- assert(base.findIp(base.findTrace(rhsAllocation.traceIndex).ipIndex).equalWithoutAddress(findIp(findTrace(lhsTrace).ipIndex)));
+ assert(base.findIp(base.findTrace(rhsAllocation.traceIndex).ipIndex)
+ .equalWithoutAddress(findIp(findTrace(lhsTrace).ipIndex)));
findAllocation(lhsTrace) -= rhsAllocation;
}
// we can still end up with merged backtraces that have a total
// of 0, but different "tails" of different origin with non-zero cost
allocations.erase(remove_if(allocations.begin(), allocations.end(),
- [] (const Allocation& allocation) -> bool {
- return allocation == AllocationData();
- }), allocations.end());
+ [](const Allocation& allocation) -> bool { return allocation == AllocationData(); }),
+ allocations.end());
}
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;
- });
+ [](const Allocation& allocation, const TraceIndex traceIndex) -> bool {
+ return allocation.traceIndex < traceIndex;
+ });
if (it == allocations.end() || it->traceIndex != traceIndex) {
Allocation allocation;
allocation.traceIndex = traceIndex;
#include <vector>
#include <fstream>
+#include <map>
#include <unordered_map>
#include <unordered_set>
-#include <map>
+
#include <boost/functional/hash.hpp>
-#include "util/indices.h"
#include "allocationdata.h"
+#include "util/indices.h"
struct InstructionPointer
{
FileIndex fileIndex;
int line = 0;
- bool compareWithoutAddress(const InstructionPointer &other) const
+ bool compareWithoutAddress(const InstructionPointer& other) const
{
return std::tie(moduleIndex, functionIndex, fileIndex, line)
- < std::tie(other.moduleIndex, other.functionIndex, other.fileIndex, other.line);
+ < std::tie(other.moduleIndex, other.functionIndex, other.fileIndex, other.line);
}
- bool equalWithoutAddress(const InstructionPointer &other) const
+ bool equalWithoutAddress(const InstructionPointer& other) const
{
return std::tie(moduleIndex, functionIndex, fileIndex, line)
== std::tie(other.moduleIndex, other.functionIndex, other.fileIndex, other.line);
int64_t peakTime = 0;
int64_t peakRSS = 0;
- struct SystemInfo {
+ struct SystemInfo
+ {
int64_t pages = 0;
int64_t pageSize = 0;
};
bool isStopIndex(const StringIndex index) const;
- // indices of functions that should stop the backtrace, e.g. main or static initialization
+ // indices of functions that should stop the backtrace, e.g. main or static
+ // initialization
std::vector<StringIndex> stopIndices;
std::vector<InstructionPointer> instructionPointers;
std::vector<TraceNode> traces;
inline bool operator==(const AllocationData& lhs, const AllocationData& rhs)
{
- return lhs.allocations == rhs.allocations
- && lhs.temporary == rhs.temporary
- && lhs.allocated == rhs.allocated
- && lhs.leaked == rhs.leaked
- && lhs.peak == rhs.peak;
+ return lhs.allocations == rhs.allocations && lhs.temporary == rhs.temporary && lhs.allocated == rhs.allocated
+ && lhs.leaked == rhs.leaked && lhs.peak == rhs.peak;
}
inline AllocationData& operator+=(AllocationData& lhs, const AllocationData& rhs)
#include <cmath>
-namespace{
+namespace {
/// TODO: share code
QString basename(const QString& path)
{
return {};
}
if (role == Qt::InitialSortOrderRole) {
- if (section == SelfAllocatedColumn || section == SelfAllocationsColumn
- || section == SelfPeakColumn || section == SelfLeakedColumn
- || section == SelfTemporaryColumn
- || section == InclusiveAllocatedColumn || section == InclusiveAllocationsColumn
- || section == InclusivePeakColumn || section == InclusiveLeakedColumn
- || section == InclusiveTemporaryColumn)
- {
+ if (section == SelfAllocatedColumn || section == SelfAllocationsColumn || section == SelfPeakColumn
+ || section == SelfLeakedColumn || section == SelfTemporaryColumn || section == InclusiveAllocatedColumn
+ || section == InclusiveAllocationsColumn || section == InclusivePeakColumn
+ || section == InclusiveLeakedColumn || section == InclusiveTemporaryColumn) {
return Qt::DescendingOrder;
}
}
if (role == Qt::DisplayRole) {
switch (static_cast<Columns>(section)) {
- case FileColumn:
- return i18n("File");
- case LineColumn:
- return i18n("Line");
- case FunctionColumn:
- return i18n("Function");
- case ModuleColumn:
- return i18n("Module");
- case SelfAllocationsColumn:
- return i18n("Allocations (Self)");
- case SelfTemporaryColumn:
- return i18n("Temporary (Self)");
- case SelfPeakColumn:
- return i18n("Peak (Self)");
- case SelfLeakedColumn:
- return i18n("Leaked (Self)");
- case SelfAllocatedColumn:
- return i18n("Allocated (Self)");
- case InclusiveAllocationsColumn:
- return i18n("Allocations (Incl.)");
- case InclusiveTemporaryColumn:
- return i18n("Temporary (Incl.)");
- case InclusivePeakColumn:
- return i18n("Peak (Incl.)");
- case InclusiveLeakedColumn:
- return i18n("Leaked (Incl.)");
- case InclusiveAllocatedColumn:
- return i18n("Allocated (Incl.)");
- case LocationColumn:
- return i18n("Location");
- case NUM_COLUMNS:
- break;
+ case FileColumn:
+ return i18n("File");
+ case LineColumn:
+ return i18n("Line");
+ case FunctionColumn:
+ return i18n("Function");
+ case ModuleColumn:
+ return i18n("Module");
+ case SelfAllocationsColumn:
+ return i18n("Allocations (Self)");
+ case SelfTemporaryColumn:
+ return i18n("Temporary (Self)");
+ case SelfPeakColumn:
+ return i18n("Peak (Self)");
+ case SelfLeakedColumn:
+ return i18n("Leaked (Self)");
+ case SelfAllocatedColumn:
+ return i18n("Allocated (Self)");
+ case InclusiveAllocationsColumn:
+ return i18n("Allocations (Incl.)");
+ case InclusiveTemporaryColumn:
+ return i18n("Temporary (Incl.)");
+ case InclusivePeakColumn:
+ return i18n("Peak (Incl.)");
+ case InclusiveLeakedColumn:
+ return i18n("Leaked (Incl.)");
+ case InclusiveAllocatedColumn:
+ return i18n("Allocated (Incl.)");
+ case LocationColumn:
+ return i18n("Location");
+ case NUM_COLUMNS:
+ break;
}
} else if (role == Qt::ToolTipRole) {
switch (static_cast<Columns>(section)) {
- case FileColumn:
- return i18n("<qt>The file where the allocation function was called from. "
- "May be empty when debug information is missing.</qt>");
- case LineColumn:
- return i18n("<qt>The line number where the allocation function was called from. "
- "May be empty when debug information is missing.</qt>");
- case FunctionColumn:
- return i18n("<qt>The parent function that called an allocation function. "
- "May be unknown when debug information is missing.</qt>");
- case ModuleColumn:
- return i18n("<qt>The module, i.e. executable or shared library, from which an allocation function was called.</qt>");
- case SelfAllocationsColumn:
- return i18n("<qt>The number of times an allocation function was directly called from this location.</qt>");
- case SelfTemporaryColumn:
- return i18n("<qt>The number of direct temporary allocations. These allocations are directly followed by a free without any other allocations in-between.</qt>");
- case SelfPeakColumn:
- return i18n("<qt>The maximum heap memory in bytes consumed from allocations originating directly at this location. "
- "This takes deallocations into account.</qt>");
- case SelfLeakedColumn:
- return i18n("<qt>The bytes allocated directly at this location that have not been deallocated.</qt>");
- case SelfAllocatedColumn:
- return i18n("<qt>The sum of all bytes directly allocated from this location, ignoring deallocations.</qt>");
- case InclusiveAllocationsColumn:
- return i18n("<qt>The inclusive number of times an allocation function was called from this location or any functions called from here.</qt>");
- case InclusiveTemporaryColumn:
- return i18n("<qt>The number of inclusive temporary allocations. These allocations are directly followed by a free without any other allocations in-between.</qt>");
- case InclusivePeakColumn:
- return i18n("<qt>The inclusive maximum heap memory in bytes consumed from allocations originating at this location or from functions called from here. "
- "This takes deallocations into account.</qt>");
- case InclusiveLeakedColumn:
- return i18n("<qt>The bytes allocated at this location that have not been deallocated.</qt>");
- case InclusiveAllocatedColumn:
- return i18n("<qt>The inclusive sum of all bytes allocated from this location or functions called from here, ignoring deallocations.</qt>");
- case LocationColumn:
- return i18n("<qt>The location from which an allocation function was called. Function symbol and file information "
- "may be unknown when debug information was missing when heaptrack was run.</qt>");
- case NUM_COLUMNS:
- break;
+ case FileColumn:
+ return i18n("<qt>The file where the allocation function was called from. "
+ "May be empty when debug information is missing.</qt>");
+ case LineColumn:
+ return i18n("<qt>The line number where the allocation function was called from. "
+ "May be empty when debug information is missing.</qt>");
+ case FunctionColumn:
+ return i18n("<qt>The parent function that called an allocation function. "
+ "May be unknown when debug information is missing.</qt>");
+ case ModuleColumn:
+ return i18n("<qt>The module, i.e. executable or shared library, from "
+ "which an allocation function was "
+ "called.</qt>");
+ case SelfAllocationsColumn:
+ return i18n("<qt>The number of times an allocation function was directly "
+ "called from this location.</qt>");
+ case SelfTemporaryColumn:
+ return i18n("<qt>The number of direct temporary allocations. These "
+ "allocations are directly followed by a "
+ "free without any other allocations in-between.</qt>");
+ case SelfPeakColumn:
+ return i18n("<qt>The maximum heap memory in bytes consumed from "
+ "allocations originating directly at "
+ "this location. "
+ "This takes deallocations into account.</qt>");
+ case SelfLeakedColumn:
+ return i18n("<qt>The bytes allocated directly at this location that have "
+ "not been deallocated.</qt>");
+ case SelfAllocatedColumn:
+ return i18n("<qt>The sum of all bytes directly allocated from this "
+ "location, ignoring deallocations.</qt>");
+ case InclusiveAllocationsColumn:
+ return i18n("<qt>The inclusive number of times an allocation function "
+ "was called from this location or any "
+ "functions called from here.</qt>");
+ case InclusiveTemporaryColumn:
+ return i18n("<qt>The number of inclusive temporary allocations. These "
+ "allocations are directly followed by "
+ "a free without any other allocations in-between.</qt>");
+ case InclusivePeakColumn:
+ return i18n("<qt>The inclusive maximum heap memory in bytes consumed "
+ "from allocations originating at this "
+ "location or from functions called from here. "
+ "This takes deallocations into account.</qt>");
+ case InclusiveLeakedColumn:
+ return i18n("<qt>The bytes allocated at this location that have not been "
+ "deallocated.</qt>");
+ case InclusiveAllocatedColumn:
+ return i18n("<qt>The inclusive sum of all bytes allocated from this "
+ "location or functions called from "
+ "here, ignoring deallocations.</qt>");
+ case LocationColumn:
+ return i18n("<qt>The location from which an allocation function was "
+ "called. Function symbol and file "
+ "information "
+ "may be unknown when debug information was missing when "
+ "heaptrack was run.</qt>");
+ case NUM_COLUMNS:
+ break;
}
}
return {};
-
}
QVariant CallerCalleeModel::data(const QModelIndex& index, int role) const
return row.location->line;
case LocationColumn:
if (row.location->file.isEmpty()) {
- return i18n("%1 in ?? (%2)",
- basename(row.location->function),
- basename(row.location->module));
+ return i18n("%1 in ?? (%2)", basename(row.location->function), basename(row.location->module));
} else {
- return i18n("%1 in %2:%3 (%4)", row.location->function,
- basename(row.location->file), row.location->line,
- basename(row.location->module));
+ return i18n("%1 in %2:%3 (%4)", row.location->function, basename(row.location->file),
+ row.location->line, basename(row.location->module));
}
case NUM_COLUMNS:
break;
stream << "<qt><pre style='font-family:monospace;'>";
if (row.location->line > 0) {
stream << i18nc("1: function, 2: file, 3: line, 4: module", "%1\n at %2:%3\n in %4",
- row.location->function.toHtmlEscaped(),
- row.location->file.toHtmlEscaped(), row.location->line,
- row.location->module.toHtmlEscaped());
+ row.location->function.toHtmlEscaped(), row.location->file.toHtmlEscaped(),
+ row.location->line, row.location->module.toHtmlEscaped());
} else {
- stream << i18nc("1: function, 2: module", "%1\n in %2",
- row.location->function.toHtmlEscaped(),
+ stream << i18nc("1: function, 2: module", "%1\n in %2", row.location->function.toHtmlEscaped(),
row.location->module.toHtmlEscaped());
}
stream << '\n';
- stream << i18n("inclusive: allocated %1 over %2 calls (%3 temporary, i.e. %4%), peak at %5, leaked %6",
- m_format.formatByteSize(row.inclusiveCost.allocated), row.inclusiveCost.allocations, row.inclusiveCost.temporary,
- round(float(row.inclusiveCost.temporary) * 100.f * 100.f / std::max(int64_t(1), row.inclusiveCost.allocations)) / 100.f,
- m_format.formatByteSize(row.inclusiveCost.peak), m_format.formatByteSize(row.inclusiveCost.leaked));
+ stream << i18n("inclusive: allocated %1 over %2 calls (%3 temporary, i.e. "
+ "%4%), peak at %5, leaked %6",
+ m_format.formatByteSize(row.inclusiveCost.allocated), row.inclusiveCost.allocations,
+ row.inclusiveCost.temporary, round(float(row.inclusiveCost.temporary) * 100.f * 100.f
+ / std::max(int64_t(1), row.inclusiveCost.allocations))
+ / 100.f,
+ m_format.formatByteSize(row.inclusiveCost.peak),
+ m_format.formatByteSize(row.inclusiveCost.leaked));
stream << '\n';
- stream << i18n("self: allocated %1 over %2 calls (%3 temporary, i.e. %4%), peak at %5, leaked %6",
- m_format.formatByteSize(row.selfCost.allocated), row.selfCost.allocations, row.selfCost.temporary,
- round(float(row.selfCost.temporary) * 100.f * 100.f / std::max(int64_t(1), row.selfCost.allocations)) / 100.f,
- m_format.formatByteSize(row.selfCost.peak), m_format.formatByteSize(row.selfCost.leaked));
+ stream << i18n(
+ "self: allocated %1 over %2 calls (%3 temporary, i.e. %4%), "
+ "peak at %5, leaked %6",
+ m_format.formatByteSize(row.selfCost.allocated), row.selfCost.allocations, row.selfCost.temporary,
+ round(float(row.selfCost.temporary) * 100.f * 100.f / std::max(int64_t(1), row.selfCost.allocations))
+ / 100.f,
+ m_format.formatByteSize(row.selfCost.peak), m_format.formatByteSize(row.selfCost.leaked));
stream << '\n';
stream << "</pre></qt>";
return tooltip;
#ifndef CALLERCALLEEMODEL_H
#define CALLERCALLEEMODEL_H
-#include <QVector>
#include <QAbstractTableModel>
+#include <QVector>
#include <KFormat>
explicit CallerCalleeModel(QObject* parent = nullptr);
~CallerCalleeModel();
- enum Columns {
+ enum Columns
+ {
LocationColumn,
FunctionColumn,
FileColumn,
NUM_COLUMNS
};
- enum Roles {
+ enum Roles
+ {
SortRole = Qt::UserRole,
MaxCostRole,
LocationRole
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
int columnCount(const QModelIndex& parent = {}) const override;
- int rowCount(const QModelIndex &parent = {}) const override;
+ int rowCount(const QModelIndex& parent = {}) const override;
void resetData(const QVector<CallerCalleeData>& rows);
void setSummary(const SummaryData& data);
+
private:
QVector<CallerCalleeData> m_rows;
CallerCalleeData m_maxCost;
#include <KChartGlobal>
#include <KChartLineAttributes>
-#include <KLocalizedString>
#include <KFormat>
+#include <KLocalizedString>
-#include <QPen>
#include <QBrush>
#include <QDebug>
+#include <QPen>
namespace {
QColor colorForColumn(int column, int columnCount)
Q_ASSERT(index.column() >= 0 && index.column() < columnCount(index.parent()));
Q_ASSERT(!index.parent().isValid());
- if ( role == KChart::LineAttributesRole ) {
+ if (role == KChart::LineAttributesRole) {
KChart::LineAttributes attributes;
attributes.setDisplayArea(true);
if (index.column() > 1) {
return QVariant::fromValue(attributes);
}
-
- if ( role == KChart::DatasetPenRole ) {
+ if (role == KChart::DatasetPenRole) {
return QVariant::fromValue(m_columnDataSetPens.at(index.column()));
- } else if ( role == KChart::DatasetBrushRole ) {
+ } else if (role == KChart::DatasetBrushRole) {
return QVariant::fromValue(m_columnDataSetBrushes.at(index.column()));
}
- if ( role != Qt::DisplayRole && role != Qt::ToolTipRole ) {
+ if (role != Qt::DisplayRole && role != Qt::ToolTipRole) {
return {};
}
KFormat format;
if (column == 0) {
switch (m_type) {
- case Allocations:
- return i18n("<qt>%1 allocations in total after %2</qt>",
- cost, time);
- case Temporary:
- return i18n("<qt>%1 temporary allocations in total after %2</qt>",
- cost, time);
- case Consumed:
- return i18n("<qt>%1 consumed in total after %2</qt>",
- format.formatByteSize(cost, 1, KFormat::MetricBinaryDialect), time);
- case Allocated:
- return i18n("<qt>%2 allocated in total after %2</qt>",
- format.formatByteSize(cost, 1, KFormat::MetricBinaryDialect), time);
+ case Allocations:
+ return i18n("<qt>%1 allocations in total after %2</qt>", cost, time);
+ case Temporary:
+ return i18n("<qt>%1 temporary allocations in total after %2</qt>", cost, time);
+ case Consumed:
+ return i18n("<qt>%1 consumed in total after %2</qt>",
+ format.formatByteSize(cost, 1, KFormat::MetricBinaryDialect), time);
+ case Allocated:
+ return i18n("<qt>%2 allocated in total after %2</qt>",
+ format.formatByteSize(cost, 1, KFormat::MetricBinaryDialect), time);
}
} else {
const auto label = m_data.labels.value(column).toHtmlEscaped();
switch (m_type) {
- case Allocations:
- return i18n("<qt>%2 allocations after %3 from:<p style='margin-left:10px;'>%1</p></qt>",
- label, cost, time);
- case Temporary:
- return i18n("<qt>%2 temporary allocations after %3 from:<p style='margin-left:10px'>%1</p></qt>",
- label, cost, time);
- case Consumed:
- return i18n("<qt>%2 consumed after %3 from:<p style='margin-left:10px'>%1</p></qt>",
- label, format.formatByteSize(cost, 1, KFormat::MetricBinaryDialect), time);
- case Allocated:
- return i18n("<qt>%2 allocated after %3 from:<p style='margin-left:10px'>%1</p></qt>",
- label, format.formatByteSize(cost, 1, KFormat::MetricBinaryDialect), time);
+ case Allocations:
+ return i18n("<qt>%2 allocations after %3 from:<p "
+ "style='margin-left:10px;'>%1</p></qt>",
+ label, cost, time);
+ case Temporary:
+ return i18n("<qt>%2 temporary allocations after %3 from:<p "
+ "style='margin-left:10px'>%1</p></qt>",
+ label, cost, time);
+ case Consumed:
+ return i18n("<qt>%2 consumed after %3 from:<p "
+ "style='margin-left:10px'>%1</p></qt>",
+ label, format.formatByteSize(cost, 1, KFormat::MetricBinaryDialect), time);
+ case Allocated:
+ return i18n("<qt>%2 allocated after %3 from:<p "
+ "style='margin-left:10px'>%1</p></qt>",
+ label, format.formatByteSize(cost, 1, KFormat::MetricBinaryDialect), time);
}
}
return {};
#ifndef CHARTMODEL_H
#define CHARTMODEL_H
+#include <array>
+
#include <QAbstractTableModel>
#include <QVector>
-#include <array>
struct ChartRows
{
{
cost.fill(0);
}
- enum {
+ enum
+ {
MAX_NUM_COST = 20
};
// time in ms
{
Q_OBJECT
public:
- enum Type {
+ enum Type
+ {
Consumed,
Allocations,
Allocated,
Type type() const;
- QVariant headerData(int section, Qt::Orientation orientation = Qt::Horizontal, int role = Qt::DisplayRole) const override;
+ QVariant headerData(int section, Qt::Orientation orientation = Qt::Horizontal,
+ int role = Qt::DisplayRole) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
private:
ChartData m_data;
Type m_type;
- // we cache the pens and brushes as constructing them requires allocations otherwise
+ // we cache the pens and brushes as constructing them requires allocations
+ // otherwise
QVector<QPen> m_columnDataSetPens;
QVector<QBrush> m_columnDataSetBrushes;
};
#define CHARTPROXY_H
#include <QSortFilterProxyModel>
+
#include "chartmodel.h"
class ChartProxy : public QSortFilterProxyModel
bool m_showTotal;
};
-#endif //CHARTPROXY_H
+#endif // CHARTPROXY_H
#include <KChartChart>
#include <KChartPlotter>
-#include <KChartGridAttributes>
-#include <KChartHeaderFooter>
+#include <KChartBackgroundAttributes>
#include <KChartCartesianCoordinatePlane>
-#include <KChartLegend>
#include <KChartDataValueAttributes>
-#include <KChartBackgroundAttributes>
#include <KChartFrameAttributes.h>
+#include <KChartGridAttributes>
+#include <KChartHeaderFooter>
+#include <KChartLegend>
+#include <KColorScheme>
#include <KFormat>
#include <KLocalizedString>
-#include <KColorScheme>
#include "chartmodel.h"
#include "chartproxy.h"
public:
explicit TimeAxis(AbstractCartesianDiagram* diagram = nullptr)
: CartesianAxis(diagram)
- {}
+ {
+ }
const QString customizedLabel(const QString& label) const override
{
public:
explicit SizeAxis(AbstractCartesianDiagram* diagram = nullptr)
: CartesianAxis(diagram)
- {}
+ {
+ }
const QString customizedLabel(const QString& label) const override
{
}
switch (model->type()) {
- case ChartModel::Consumed:
- setToolTip(i18n("<qt>Shows the heap memory consumption over time.</qt>"));
- break;
- case ChartModel::Allocated:
- setToolTip(i18n("<qt>Displays total memory allocated over time. "
- "This value ignores deallocations and just measures heap allocation throughput.</qt>"));
- break;
- case ChartModel::Allocations:
- setToolTip(i18n("<qt>Shows number of memory allocations over time.</qt>"));
- break;
- case ChartModel::Temporary:
- setToolTip(i18n("<qt>Shows number of temporary memory allocations over time. "
- "A temporary allocation is one that is followed immediately by its "
- "corresponding deallocation, without other allocations happening in-between.</qt>"));
- break;
+ case ChartModel::Consumed:
+ setToolTip(i18n("<qt>Shows the heap memory consumption over time.</qt>"));
+ break;
+ case ChartModel::Allocated:
+ setToolTip(i18n("<qt>Displays total memory allocated over time. "
+ "This value ignores deallocations and just measures heap "
+ "allocation throughput.</qt>"));
+ break;
+ case ChartModel::Allocations:
+ setToolTip(i18n("<qt>Shows number of memory allocations over time.</qt>"));
+ break;
+ case ChartModel::Temporary:
+ setToolTip(i18n("<qt>Shows number of temporary memory allocations over time. "
+ "A temporary allocation is one that is followed immediately by its "
+ "corresponding deallocation, without other allocations happening "
+ "in-between.</qt>"));
+ break;
}
{
totalPlotter->addAxis(bottomAxis);
CartesianAxis* rightAxis = model->type() == ChartModel::Allocations || model->type() == ChartModel::Temporary
- ? new CartesianAxis(totalPlotter) : new SizeAxis(totalPlotter);
+ ? new CartesianAxis(totalPlotter)
+ : new SizeAxis(totalPlotter);
rightAxis->setTextAttributes(axisTextAttributes);
rightAxis->setTitleTextAttributes(axisTitleTextAttributes);
rightAxis->setTitleText(model->headerData(1).toString());
painter->drawRect(option.rect);
}
- auto color = QColor::fromHsv(120 - fraction * 120, 255, 255,
- (-((fraction-1) * (fraction-1))) * 120 + 120);
+ auto color = QColor::fromHsv(120 - fraction * 120, 255, 255, (-((fraction - 1) * (fraction - 1))) * 120 + 120);
painter->setBrush(color);
painter->drawRect(rect);
QStyledItemDelegate::paint(painter, option, index);
}
}
-
{
Q_OBJECT
public:
- explicit CostDelegate(QObject *parent = nullptr);
+ explicit CostDelegate(QObject* parent = nullptr);
~CostDelegate();
- void paint(QPainter *painter, const QStyleOptionViewItem &option,
- const QModelIndex &index) const override;
+ void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
};
#endif // COSTDELEGATE_H
#include <cmath>
-#include <QVBoxLayout>
+#include <QAction>
+#include <QCheckBox>
+#include <QComboBox>
+#include <QCursor>
+#include <QDebug>
+#include <QDoubleSpinBox>
+#include <QEvent>
+#include <QGraphicsRectItem>
#include <QGraphicsScene>
-#include <QStyleOption>
#include <QGraphicsView>
#include <QLabel>
-#include <QGraphicsRectItem>
-#include <QWheelEvent>
-#include <QEvent>
+#include <QStyleOption>
#include <QToolTip>
-#include <QDebug>
-#include <QAction>
-#include <QComboBox>
-#include <QCheckBox>
-#include <QDoubleSpinBox>
-#include <QCursor>
+#include <QVBoxLayout>
+#include <QWheelEvent>
-#include <ThreadWeaver/ThreadWeaver>
-#include <KLocalizedString>
#include <KColorScheme>
+#include <KLocalizedString>
+#include <ThreadWeaver/ThreadWeaver>
enum CostType
{
class FrameGraphicsItem : public QGraphicsRectItem
{
public:
- FrameGraphicsItem(const qint64 cost, CostType costType, const QString& function, FrameGraphicsItem* parent = nullptr);
+ FrameGraphicsItem(const qint64 cost, CostType costType, const QString& function,
+ FrameGraphicsItem* parent = nullptr);
FrameGraphicsItem(const qint64 cost, const QString& function, FrameGraphicsItem* parent);
qint64 cost() const;
QString description() const;
protected:
- void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
- void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
+ void hoverEnterEvent(QGraphicsSceneHoverEvent* event) override;
+ void hoverLeaveEvent(QGraphicsSceneHoverEvent* event) override;
private:
qint64 m_cost;
Q_DECLARE_METATYPE(FrameGraphicsItem*)
-FrameGraphicsItem::FrameGraphicsItem(const qint64 cost, CostType costType, const QString& function, FrameGraphicsItem* parent)
+FrameGraphicsItem::FrameGraphicsItem(const qint64 cost, CostType costType, const QString& function,
+ FrameGraphicsItem* parent)
: QGraphicsRectItem(parent)
, m_cost(cost)
, m_function(function)
const int height = rect().height();
- painter->drawText(margin + rect().x(), rect().y(), width, height, Qt::AlignVCenter | Qt::AlignLeft | Qt::TextSingleLine,
+ painter->drawText(margin + rect().x(), rect().y(), width, height,
+ Qt::AlignVCenter | Qt::AlignLeft | Qt::TextSingleLine,
option->fontMetrics.elidedText(m_function, Qt::ElideRight, width));
}
-void FrameGraphicsItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
+void FrameGraphicsItem::hoverEnterEvent(QGraphicsSceneHoverEvent* event)
{
QGraphicsRectItem::hoverEnterEvent(event);
m_isHovered = true;
QString FrameGraphicsItem::description() const
{
- // we build the tooltip text on demand, which is much faster than doing that for potentially thousands of items when we load the data
+ // we build the tooltip text on demand, which is much faster than doing that
+ // for potentially thousands of items when we load the data
QString tooltip;
KFormat format;
qint64 totalCost = 0;
}
totalCost = item->cost();
}
- const auto fraction = QString::number(double(m_cost) * 100. / totalCost, 'g', 3);
- const auto function = QString(QLatin1String("<span style='font-family:monospace'>") + m_function.toHtmlEscaped() + QLatin1String("</span>"));
+ const auto fraction = QString::number(double(m_cost) * 100. / totalCost, 'g', 3);
+ const auto function = QString(QLatin1String("<span style='font-family:monospace'>") + m_function.toHtmlEscaped()
+ + QLatin1String("</span>"));
if (!parentItem()) {
return function;
}
"%1 (%2%) allocations in %3 and below.", m_cost, fraction, function);
break;
case Temporary:
- tooltip = i18nc("%1: number of temporary allocations, %2: relative number, %3 function label",
+ tooltip = i18nc("%1: number of temporary allocations, %2: relative number, "
+ "%3 function label",
"%1 (%2%) temporary allocations in %3 and below.", m_cost, fraction, function);
break;
case Peak:
- tooltip = i18nc("%1: peak consumption in bytes, %2: relative number, %3: function label",
- "%1 (%2%) peak consumption in %3 and below.", format.formatByteSize(m_cost), fraction, function);
+ tooltip =
+ i18nc("%1: peak consumption in bytes, %2: relative number, %3: "
+ "function label",
+ "%1 (%2%) peak consumption in %3 and below.", format.formatByteSize(m_cost), fraction, function);
break;
case Leaked:
- tooltip = i18nc("%1: leaked bytes, %2: relative number, %3: function label",
- "%1 (%2%) leaked in %3 and below.", format.formatByteSize(m_cost), fraction, function);
+ tooltip = i18nc("%1: leaked bytes, %2: relative number, %3: function label", "%1 (%2%) leaked in %3 and below.",
+ format.formatByteSize(m_cost), fraction, function);
break;
case Allocated:
tooltip = i18nc("%1: allocated bytes, %2: relative number, %3: function label",
return tooltip;
}
-void FrameGraphicsItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
+void FrameGraphicsItem::hoverLeaveEvent(QGraphicsSceneHoverEvent* event)
{
QGraphicsRectItem::hoverLeaveEvent(event);
m_isHovered = false;
QBrush brush()
{
// intern the brushes, to reuse them across items which can be thousands
- // otherwise we'd end up with dozens of allocations and higher memory consumption
+ // otherwise we'd end up with dozens of allocations and higher memory
+ // consumption
static QVector<QBrush> brushes;
if (brushes.isEmpty()) {
- std::generate_n(std::back_inserter(brushes), 100, [] () {
+ std::generate_n(std::back_inserter(brushes), 100, []() {
return QColor(0, 190 + 50 * qreal(rand()) / RAND_MAX, 210 * qreal(rand()) / RAND_MAX, 125);
});
}
/**
* Layout the flame graph and hide tiny items.
*/
-void layoutItems(FrameGraphicsItem *parent)
+void layoutItems(FrameGraphicsItem* parent)
{
const auto& parentRect = parent->rect();
const auto pos = parentRect.topLeft();
/**
* Convert the top-down graph into a tree of FrameGraphicsItem.
*/
-void toGraphicsItems(const QVector<RowData>& data, FrameGraphicsItem *parent, int64_t AllocationData::* member,
+void toGraphicsItems(const QVector<RowData>& data, FrameGraphicsItem* parent, int64_t AllocationData::*member,
const double costThreshold, bool collapseRecursion)
{
foreach (const auto& row, data) {
item->setCost(item->cost() + row.cost.*member);
}
if (item->cost() > costThreshold) {
- toGraphicsItems(row.children, item, member,
- costThreshold, collapseRecursion);
+ toGraphicsItems(row.children, item, member, costThreshold, collapseRecursion);
}
}
}
-int64_t AllocationData::* memberForType(CostType type)
+int64_t AllocationData::*memberForType(CostType type)
{
switch (type) {
case Allocations:
Q_UNREACHABLE();
}
-FrameGraphicsItem* parseData(const QVector<RowData>& topDownData, CostType type,
- double costThreshold, bool collapseRecursion)
+FrameGraphicsItem* parseData(const QVector<RowData>& topDownData, CostType type, double costThreshold,
+ bool collapseRecursion)
{
auto member = memberForType(type);
double totalCost = 0;
- foreach(const auto& frame, topDownData) {
+ foreach (const auto& frame, topDownData) {
totalCost += frame.cost.*member;
}
auto rootItem = new FrameGraphicsItem(totalCost, type, label);
rootItem->setBrush(scheme.background());
rootItem->setPen(pen);
- toGraphicsItems(topDownData, rootItem, member,
- totalCost * costThreshold / 100., collapseRecursion);
+ toGraphicsItems(topDownData, rootItem, member, totalCost * costThreshold / 100., collapseRecursion);
return rootItem;
}
-
}
FlameGraph::FlameGraph(QWidget* parent, Qt::WindowFlags flags)
qRegisterMetaType<FrameGraphicsItem*>();
m_costSource->addItem(i18n("Allocations"), QVariant::fromValue(Allocations));
- m_costSource->setItemData(0, i18n("Show a flame graph over the number of allocations triggered by functions in your code."), Qt::ToolTipRole);
+ m_costSource->setItemData(0, i18n("Show a flame graph over the number of allocations triggered by "
+ "functions in your code."),
+ Qt::ToolTipRole);
m_costSource->addItem(i18n("Temporary Allocations"), QVariant::fromValue(Temporary));
- m_costSource->setItemData(1, i18n("Show a flame graph over the number of temporary allocations triggered by functions in your code. "
- "Allocations are marked as temporary when they are immediately followed by their deallocation."), Qt::ToolTipRole);
+ m_costSource->setItemData(1, i18n("Show a flame graph over the number of temporary allocations "
+ "triggered by functions in your code. "
+ "Allocations are marked as temporary when they are immediately "
+ "followed by their deallocation."),
+ Qt::ToolTipRole);
m_costSource->addItem(i18n("Peak Consumption"), QVariant::fromValue(Peak));
- m_costSource->setItemData(2, i18n("Show a flame graph over the peak heap memory consumption of your application."), Qt::ToolTipRole);
+ m_costSource->setItemData(2, i18n("Show a flame graph over the peak heap "
+ "memory consumption of your application."),
+ Qt::ToolTipRole);
m_costSource->addItem(i18n("Leaked"), QVariant::fromValue(Leaked));
m_costSource->setItemData(3, i18n("Show a flame graph over the leaked heap memory of your application. "
- "Memory is considered to be leaked when it never got deallocated. "), Qt::ToolTipRole);
+ "Memory is considered to be leaked when it never got deallocated. "),
+ Qt::ToolTipRole);
m_costSource->addItem(i18n("Allocated"), QVariant::fromValue(Allocated));
- m_costSource->setItemData(4, i18n("Show a flame graph over the total memory allocated by functions in your code. "
- "This aggregates all memory allocations and ignores deallocations."), Qt::ToolTipRole);
- connect(m_costSource, static_cast<void (QComboBox::*) (int)>(&QComboBox::currentIndexChanged),
- this, &FlameGraph::showData);
+ m_costSource->setItemData(4, i18n("Show a flame graph over the total memory allocated by functions in "
+ "your code. "
+ "This aggregates all memory allocations and ignores deallocations."),
+ Qt::ToolTipRole);
+ connect(m_costSource, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
+ &FlameGraph::showData);
m_costSource->setToolTip(i18n("Select the data source that should be visualized in the flame graph."));
m_scene->setItemIndexMethod(QGraphicsScene::NoIndex);
m_view->setContextMenuPolicy(Qt::ActionsContextMenu);
auto bottomUpCheckbox = new QCheckBox(i18n("Bottom-Down View"), this);
- bottomUpCheckbox->setToolTip(i18n("Enable the bottom-down flame graph view. When this is unchecked, the top-down view is enabled by default."));
+ bottomUpCheckbox->setToolTip(i18n("Enable the bottom-down flame graph view. When this is unchecked, "
+ "the top-down view is enabled by default."));
connect(bottomUpCheckbox, &QCheckBox::toggled, this, [this, bottomUpCheckbox] {
m_showBottomUpData = bottomUpCheckbox->isChecked();
showData();
auto collapseRecursionCheckbox = new QCheckBox(i18n("Collapse Recursion"), this);
collapseRecursionCheckbox->setChecked(m_collapseRecursion);
collapseRecursionCheckbox->setToolTip(i18n("Collapse stack frames for functions calling themselves. "
- "When this is unchecked, recursive frames will be visualized separately."));
+ "When this is unchecked, recursive frames will be visualized "
+ "separately."));
connect(collapseRecursionCheckbox, &QCheckBox::toggled, this, [this, collapseRecursionCheckbox] {
m_collapseRecursion = collapseRecursionCheckbox->isChecked();
showData();
costThreshold->setValue(m_costThreshold);
costThreshold->setSingleStep(0.01);
costThreshold->setToolTip(i18n("<qt>The cost threshold defines a fractional cut-off value. "
- "Items with a relative cost below this value will not be shown in the flame graph. "
- "This is done as an optimization to quickly generate graphs for large data sets with "
- "low memory overhead. If you need more details, decrease the threshold value, or set it to zero.</qt>"));
- connect(costThreshold, static_cast<void (QDoubleSpinBox::*) (double)>(&QDoubleSpinBox::valueChanged),
- this, [this] (double threshold) {
+ "Items with a relative cost below this value will not be shown in "
+ "the flame graph. This is done as an optimization to quickly generate "
+ "graphs for large data sets with low memory overhead. If you need more "
+ "details, decrease the threshold value, or set it to zero.</qt>"));
+ connect(costThreshold, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this,
+ [this](double threshold) {
m_costThreshold = threshold;
showData();
});
{
auto action = new QAction(tr("back"), this);
action->setShortcuts({QKeySequence::Back, Qt::Key_Backspace});
- connect(action, &QAction::triggered,
- this, &FlameGraph::navigateBack);
+ connect(action, &QAction::triggered, this, &FlameGraph::navigateBack);
addAction(action);
}
{
auto action = new QAction(tr("forward"), this);
action->setShortcuts(QKeySequence::Forward);
- connect(action, &QAction::triggered,
- this, &FlameGraph::navigateForward);
+ connect(action, &QAction::triggered, this, &FlameGraph::navigateForward);
addAction(action);
}
}
auto threshold = m_costThreshold;
stream() << make_job([data, source, threshold, collapseRecursion, this]() {
auto parsedData = parseData(data, source, threshold, collapseRecursion);
- QMetaObject::invokeMethod(this, "setData", Qt::QueuedConnection,
- Q_ARG(FrameGraphicsItem*, parsedData));
+ QMetaObject::invokeMethod(this, "setData", Qt::QueuedConnection, Q_ARG(FrameGraphicsItem*, parsedData));
});
}
}
m_view->setCursor(Qt::ArrowCursor);
- // layouting needs a root item with a given height, the rest will be overwritten later
+ // layouting needs a root item with a given height, the rest will be
+ // overwritten later
rootItem->setRect(0, 0, 800, m_view->fontMetrics().height() + 4);
m_scene->addItem(rootItem);
#ifndef FLAMEGRAPH_H
#define FLAMEGRAPH_H
-#include <QWidget>
#include <QVector>
+#include <QWidget>
#include "treemodel.h"
KAboutData aboutData(QStringLiteral("heaptrack_gui"), i18n("Heaptrack GUI"), QStringLiteral("0.1"),
i18n("A visualizer for heaptrack data files."), KAboutLicense::LGPL,
- i18n("Copyright 2015, Milian Wolff <mail@milianw.de>"), QString(), QStringLiteral("mail@milianw.de"));
+ i18n("Copyright 2015, Milian Wolff <mail@milianw.de>"), QString(),
+ QStringLiteral("mail@milianw.de"));
- aboutData.addAuthor(i18n("Milian Wolff"), i18n("Original author, maintainer"),
- QStringLiteral("mail@milianw.de"), QStringLiteral("http://milianw.de"));
+ aboutData.addAuthor(i18n("Milian Wolff"), i18n("Original author, maintainer"), QStringLiteral("mail@milianw.de"),
+ QStringLiteral("http://milianw.de"));
aboutData.setOrganizationDomain("kde.org");
KAboutData::setApplicationData(aboutData);
parser.addHelpOption();
aboutData.setupCommandLine(&parser);
- QCommandLineOption diffOption {
- { QStringLiteral("d"), QStringLiteral("diff")},
- i18n("Base profile data to compare other files to."),
- QStringLiteral("<file>")
- };
+ QCommandLineOption diffOption{{QStringLiteral("d"), QStringLiteral("diff")},
+ i18n("Base profile data to compare other files to."),
+ QStringLiteral("<file>")};
parser.addOption(diffOption);
- parser.addPositionalArgument(QStringLiteral("files"), i18n( "Files to load" ), i18n("[FILE...]"));
+ parser.addPositionalArgument(QStringLiteral("files"), i18n("Files to load"), i18n("[FILE...]"));
parser.process(app);
aboutData.processCommandLine(&parser);
- auto createWindow = [] () -> MainWindow* {
+ auto createWindow = []() -> MainWindow* {
auto window = new MainWindow;
window->setAttribute(Qt::WA_DeleteOnClose);
window->show();
return window;
};
- foreach (const QString &file, parser.positionalArguments()) {
+ foreach (const QString& file, parser.positionalArguments()) {
createWindow()->loadFile(file, parser.value(diffOption));
}
#include <KFormat>
#include <KLocalizedString>
-#include <QColor>
#include <QBrush>
+#include <QColor>
#include <QPen>
#include <limits>
if (!hasIndex(index.row(), index.column(), index.parent())) {
return {};
}
- if ( role == KChart::DatasetBrushRole ) {
+ if (role == KChart::DatasetBrushRole) {
return QVariant::fromValue(QBrush(colorForColumn(index.column(), columnCount())));
- } else if ( role == KChart::DatasetPenRole ) {
+ } else if (role == KChart::DatasetPenRole) {
return QVariant::fromValue(QPen(Qt::black));
}
return i18n("%1 allocations in total", column.allocations);
}
if (!column.location->file.isEmpty()) {
- return i18n("%1 allocations from %2 at %3:%4 in %5", column.allocations,
- column.location->function, column.location->file, column.location->line,
- column.location->module);
+ return i18n("%1 allocations from %2 at %3:%4 in %5", column.allocations, column.location->function,
+ column.location->file, column.location->line, column.location->module);
}
- return i18n("%1 allocations from %2 in %3", column.allocations,
- column.location->function, column.location->module);
+ return i18n("%1 allocations from %2 in %3", column.allocations, column.location->function,
+ column.location->module);
}
return column.allocations;
}
{
columns.fill({0, {}});
}
- enum {
+ enum
+ {
NUM_COLUMNS = 10 + 1
};
QString sizeLabel;
#include "histogramwidget.h"
-#include <QVBoxLayout>
#include <QSortFilterProxyModel>
+#include <QVBoxLayout>
-#include <KChartChart>
#include <KChartBarDiagram>
+#include <KChartChart>
-#include <KChartGridAttributes>
-#include <KChartHeaderFooter>
+#include <KChartBackgroundAttributes>
#include <KChartCartesianCoordinatePlane>
-#include <KChartLegend>
#include <KChartDataValueAttributes>
-#include <KChartBackgroundAttributes>
#include <KChartFrameAttributes.h>
+#include <KChartGridAttributes>
+#include <KChartHeaderFooter>
+#include <KChartLegend>
-#include <KLocalizedString>
#include <KColorScheme>
#include <KFormat>
+#include <KLocalizedString>
#include "histogrammodel.h"
public:
explicit SizeAxis(AbstractCartesianDiagram* diagram = nullptr)
: CartesianAxis(diagram)
- {}
+ {
+ }
const QString customizedLabel(const QString& label) const override
{
private:
bool m_showTotal;
};
-
}
HistogramWidget::HistogramWidget(QWidget* parent)
};
#endif // HISTOGRAMWIDGET_H
-
bool operator==(const LocationData& rhs) const
{
- return function == rhs.function
- && file == rhs.file
- && module == rhs.module
- && line == rhs.line;
+ return function == rhs.function && file == rhs.file && module == rhs.module && line == rhs.line;
}
bool operator<(const LocationData& rhs) const
#include <cmath>
-#include <KRecursiveFilterProxyModel>
-#include <KLocalizedString>
#include <KConfigGroup>
+#include <KLocalizedString>
+#include <KRecursiveFilterProxyModel>
-#include <QFileDialog>
-#include <QStatusBar>
-#include <QDebug>
-#include <QMenu>
#include <QAction>
+#include <QDebug>
#include <QDesktopServices>
+#include <QFileDialog>
+#include <QMenu>
+#include <QStatusBar>
-#include "treemodel.h"
-#include "treeproxy.h"
-#include "topproxy.h"
+#include "callercalleemodel.h"
#include "costdelegate.h"
#include "parser.h"
#include "stacksmodel.h"
-#include "callercalleemodel.h"
+#include "topproxy.h"
+#include "treemodel.h"
+#include "treeproxy.h"
#include "gui_config.h"
#if KChart_FOUND
-#include "chartwidget.h"
#include "chartmodel.h"
#include "chartproxy.h"
-#include "histogramwidget.h"
+#include "chartwidget.h"
#include "histogrammodel.h"
+#include "histogramwidget.h"
#endif
using namespace std;
void addContextMenu(QTreeView* treeView, int role)
{
treeView->setContextMenuPolicy(Qt::CustomContextMenu);
- QObject::connect(treeView, &QTreeView::customContextMenuRequested,
- treeView, [treeView, role] (const QPoint& pos) {
- auto index = treeView->indexAt(pos);
- if (!index.isValid()) {
- return;
- }
- const auto location = index.data(role).value<LocationData::Ptr>();
- if (!location || !QFile::exists(location->file)) {
- return;
- }
- auto menu = new QMenu(treeView);
- auto openFile = new QAction(QIcon::fromTheme(QStringLiteral("document-open")),
- QObject::tr("Open file in editor"));
- QObject::connect(openFile, &QAction::triggered,
- openFile, [location] {
- /// FIXME: add settings to let user configure this
- auto url = QUrl::fromLocalFile(location->file);
- url.setFragment(QString::number(location->line));
- QDesktopServices::openUrl(url);
- });
- menu->addAction(openFile);
- menu->popup(treeView->mapToGlobal(pos));
+ QObject::connect(treeView, &QTreeView::customContextMenuRequested, treeView, [treeView, role](const QPoint& pos) {
+ auto index = treeView->indexAt(pos);
+ if (!index.isValid()) {
+ return;
+ }
+ const auto location = index.data(role).value<LocationData::Ptr>();
+ if (!location || !QFile::exists(location->file)) {
+ return;
+ }
+ auto menu = new QMenu(treeView);
+ auto openFile =
+ new QAction(QIcon::fromTheme(QStringLiteral("document-open")), QObject::tr("Open file in editor"));
+ QObject::connect(openFile, &QAction::triggered, openFile, [location] {
+ /// FIXME: add settings to let user configure this
+ auto url = QUrl::fromLocalFile(location->file);
+ url.setFragment(QString::number(location->line));
+ QDesktopServices::openUrl(url);
});
+ menu->addAction(openFile);
+ menu->popup(treeView->mapToGlobal(pos));
+ });
}
void setupTopView(TreeModel* source, QTreeView* view, TopProxy::Type type)
}
#if KChart_FOUND
-void addChartTab(QTabWidget* tabWidget, const QString& title, ChartModel::Type type,
- const Parser* parser, void (Parser::*dataReady)(const ChartData&))
+void addChartTab(QTabWidget* tabWidget, const QString& title, ChartModel::Type type, const Parser* parser,
+ void (Parser::*dataReady)(const ChartData&))
{
auto tab = new ChartWidget(tabWidget->parentWidget());
tabWidget->addTab(tab, title);
tabWidget->setTabEnabled(tabWidget->indexOf(tab), false);
auto model = new ChartModel(type, tab);
tab->setModel(model);
- QObject::connect(parser, dataReady,
- tab, [=] (const ChartData& data) {
- model->resetData(data);
- tabWidget->setTabEnabled(tabWidget->indexOf(tab), true);
- });
+ QObject::connect(parser, dataReady, tab, [=](const ChartData& data) {
+ model->resetData(data);
+ tabWidget->setTabEnabled(tabWidget->indexOf(tab), true);
+ });
}
#endif
-void setupTreeModel(TreeModel* model, QTreeView* view,
- CostDelegate* costDelegate, QLineEdit* filterFunction,
+void setupTreeModel(TreeModel* model, QTreeView* view, CostDelegate* costDelegate, QLineEdit* filterFunction,
QLineEdit* filterFile, QLineEdit* filterModule)
{
- auto proxy = new TreeProxy(TreeModel::FunctionColumn,
- TreeModel::FileColumn,
- TreeModel::ModuleColumn,
- model);
+ auto proxy = new TreeProxy(TreeModel::FunctionColumn, TreeModel::FileColumn, TreeModel::ModuleColumn, model);
proxy->setSourceModel(model);
proxy->setSortRole(TreeModel::SortRole);
view->hideColumn(TreeModel::LineColumn);
view->hideColumn(TreeModel::ModuleColumn);
- QObject::connect(filterFunction, &QLineEdit::textChanged,
- proxy, &TreeProxy::setFunctionFilter);
- QObject::connect(filterFile, &QLineEdit::textChanged,
- proxy, &TreeProxy::setFileFilter);
- QObject::connect(filterModule, &QLineEdit::textChanged,
- proxy, &TreeProxy::setModuleFilter);
+ QObject::connect(filterFunction, &QLineEdit::textChanged, proxy, &TreeProxy::setFunctionFilter);
+ QObject::connect(filterFile, &QLineEdit::textChanged, proxy, &TreeProxy::setFileFilter);
+ QObject::connect(filterModule, &QLineEdit::textChanged, proxy, &TreeProxy::setModuleFilter);
addContextMenu(view, TreeModel::LocationRole);
}
-void setupCallerCalle(CallerCalleeModel* model, QTreeView* view,
- CostDelegate* costDelegate, QLineEdit* filterFunction,
+void setupCallerCalle(CallerCalleeModel* model, QTreeView* view, CostDelegate* costDelegate, QLineEdit* filterFunction,
QLineEdit* filterFile, QLineEdit* filterModule)
{
- auto callerCalleeProxy = new TreeProxy(CallerCalleeModel::FunctionColumn,
- CallerCalleeModel::FileColumn,
- CallerCalleeModel::ModuleColumn,
- model);
+ auto callerCalleeProxy = new TreeProxy(CallerCalleeModel::FunctionColumn, CallerCalleeModel::FileColumn,
+ CallerCalleeModel::ModuleColumn, model);
callerCalleeProxy->setSourceModel(model);
callerCalleeProxy->setSortRole(CallerCalleeModel::SortRole);
view->setModel(callerCalleeProxy);
view->hideColumn(CallerCalleeModel::FileColumn);
view->hideColumn(CallerCalleeModel::LineColumn);
view->hideColumn(CallerCalleeModel::ModuleColumn);
- QObject::connect(filterFunction, &QLineEdit::textChanged,
- callerCalleeProxy, &TreeProxy::setFunctionFilter);
- QObject::connect(filterFile, &QLineEdit::textChanged,
- callerCalleeProxy, &TreeProxy::setFileFilter);
- QObject::connect(filterModule, &QLineEdit::textChanged,
- callerCalleeProxy, &TreeProxy::setModuleFilter);
+ QObject::connect(filterFunction, &QLineEdit::textChanged, callerCalleeProxy, &TreeProxy::setFunctionFilter);
+ QObject::connect(filterFile, &QLineEdit::textChanged, callerCalleeProxy, &TreeProxy::setFileFilter);
+ QObject::connect(filterModule, &QLineEdit::textChanged, callerCalleeProxy, &TreeProxy::setModuleFilter);
addContextMenu(view, CallerCalleeModel::LocationRole);
}
-
}
MainWindow::MainWindow(QWidget* parent)
m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->topDownTab), false);
m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->flameGraphTab), false);
- connect(m_parser, &Parser::bottomUpDataAvailable,
- this, [=] (const TreeData& data) {
+ connect(m_parser, &Parser::bottomUpDataAvailable, this, [=](const TreeData& data) {
bottomUpModel->resetData(data);
if (!m_diffMode) {
m_ui->flameGraphTab->setBottomUpData(data);
statusBar()->addWidget(m_ui->loadingProgress);
m_ui->pages->setCurrentWidget(m_ui->resultsPage);
});
- connect(m_parser, &Parser::callerCalleeDataAvailable,
- this, [=] (const CallerCalleeRows& data) {
- callerCalleeModel->resetData(data);
- m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->callerCalleeTab), true);
- });
- connect(m_parser, &Parser::topDownDataAvailable,
- this, [=] (const TreeData& data) {
- topDownModel->resetData(data);
- m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->topDownTab), true);
- if (!m_diffMode) {
- m_ui->flameGraphTab->setTopDownData(data);
- }
- m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->flameGraphTab), !m_diffMode);
- });
- connect(m_parser, &Parser::summaryAvailable,
- this, [=] (const SummaryData& data) {
- bottomUpModel->setSummary(data);
- topDownModel->setSummary(data);
- callerCalleeModel->setSummary(data);
- KFormat format;
- QString textLeft;
- QString textCenter;
- QString textRight;
- const double totalTimeS = 0.001 * data.totalTime;
- const double peakTimeS = 0.001 * data.peakTime;
- {
- QTextStream stream(&textLeft);
- stream << "<qt><dl>"
- << i18n("<dt><b>debuggee</b>:</dt><dd style='font-family:monospace;'>%1</dd>", data.debuggee)
- // xgettext:no-c-format
- << i18n("<dt><b>total runtime</b>:</dt><dd>%1s</dd>", totalTimeS)
- << i18n("<dt><b>total system memory</b>:</dt><dd>%1</dd>", format.formatByteSize(data.totalSystemMemory))
- << "</dl></qt>";
- }
- {
- QTextStream stream(&textCenter);
- stream << "<qt><dl>"
- << i18n("<dt><b>calls to allocation functions</b>:</dt><dd>%1 (%2/s)</dd>",
- data.cost.allocations, qint64(data.cost.allocations / totalTimeS))
- << i18n("<dt><b>temporary allocations</b>:</dt><dd>%1 (%2%, %3/s)</dd>",
- data.cost.temporary, std::round(float(data.cost.temporary) * 100.f * 100.f / data.cost.allocations) / 100.f,
- qint64(data.cost.temporary / totalTimeS))
- << i18n("<dt><b>bytes allocated in total</b> (ignoring deallocations):</dt><dd>%1 (%2/s)</dd>",
- format.formatByteSize(data.cost.allocated, 2), format.formatByteSize(data.cost.allocated / totalTimeS))
- << "</dl></qt>";
- }
- {
- QTextStream stream(&textRight);
- stream << "<qt><dl>"
- << i18n("<dt><b>peak heap memory consumption</b>:</dt><dd>%1 after %2s</dd>", format.formatByteSize(data.cost.peak), peakTimeS)
- << i18n("<dt><b>peak RSS</b> (including heaptrack overhead):</dt><dd>%1</dd>", format.formatByteSize(data.peakRSS))
- << i18n("<dt><b>total memory leaked</b>:</dt><dd>%1</dd>", format.formatByteSize(data.cost.leaked))
- << "</dl></qt>";
- }
-
- m_ui->summaryLeft->setText(textLeft);
- m_ui->summaryCenter->setText(textCenter);
- m_ui->summaryRight->setText(textRight);
- });
- connect(m_parser, &Parser::progressMessageAvailable,
- m_ui->progressLabel, &QLabel::setText);
+ connect(m_parser, &Parser::callerCalleeDataAvailable, this, [=](const CallerCalleeRows& data) {
+ callerCalleeModel->resetData(data);
+ m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->callerCalleeTab), true);
+ });
+ connect(m_parser, &Parser::topDownDataAvailable, this, [=](const TreeData& data) {
+ topDownModel->resetData(data);
+ m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->topDownTab), true);
+ if (!m_diffMode) {
+ m_ui->flameGraphTab->setTopDownData(data);
+ }
+ m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->flameGraphTab), !m_diffMode);
+ });
+ connect(m_parser, &Parser::summaryAvailable, this, [=](const SummaryData& data) {
+ bottomUpModel->setSummary(data);
+ topDownModel->setSummary(data);
+ callerCalleeModel->setSummary(data);
+ KFormat format;
+ QString textLeft;
+ QString textCenter;
+ QString textRight;
+ const double totalTimeS = 0.001 * data.totalTime;
+ const double peakTimeS = 0.001 * data.peakTime;
+ {
+ QTextStream stream(&textLeft);
+ stream << "<qt><dl>" << i18n("<dt><b>debuggee</b>:</dt><dd "
+ "style='font-family:monospace;'>%1</dd>",
+ data.debuggee)
+ // xgettext:no-c-format
+ << i18n("<dt><b>total runtime</b>:</dt><dd>%1s</dd>", totalTimeS)
+ << i18n("<dt><b>total system memory</b>:</dt><dd>%1</dd>",
+ format.formatByteSize(data.totalSystemMemory))
+ << "</dl></qt>";
+ }
+ {
+ QTextStream stream(&textCenter);
+ stream << "<qt><dl>" << i18n("<dt><b>calls to allocation functions</b>:</dt><dd>%1 "
+ "(%2/s)</dd>",
+ data.cost.allocations, qint64(data.cost.allocations / totalTimeS))
+ << i18n("<dt><b>temporary allocations</b>:</dt><dd>%1 (%2%, "
+ "%3/s)</dd>",
+ data.cost.temporary,
+ std::round(float(data.cost.temporary) * 100.f * 100.f / data.cost.allocations) / 100.f,
+ qint64(data.cost.temporary / totalTimeS))
+ << i18n("<dt><b>bytes allocated in total</b> (ignoring "
+ "deallocations):</dt><dd>%1 (%2/s)</dd>",
+ format.formatByteSize(data.cost.allocated, 2),
+ format.formatByteSize(data.cost.allocated / totalTimeS))
+ << "</dl></qt>";
+ }
+ {
+ QTextStream stream(&textRight);
+ stream << "<qt><dl>" << i18n("<dt><b>peak heap memory consumption</b>:</dt><dd>%1 "
+ "after %2s</dd>",
+ format.formatByteSize(data.cost.peak), peakTimeS)
+ << i18n("<dt><b>peak RSS</b> (including heaptrack "
+ "overhead):</dt><dd>%1</dd>",
+ format.formatByteSize(data.peakRSS))
+ << i18n("<dt><b>total memory leaked</b>:</dt><dd>%1</dd>", format.formatByteSize(data.cost.leaked))
+ << "</dl></qt>";
+ }
+
+ m_ui->summaryLeft->setText(textLeft);
+ m_ui->summaryCenter->setText(textCenter);
+ m_ui->summaryRight->setText(textRight);
+ });
+ connect(m_parser, &Parser::progressMessageAvailable, m_ui->progressLabel, &QLabel::setText);
auto removeProgress = [this] {
statusBar()->removeWidget(m_ui->progressLabel);
statusBar()->removeWidget(m_ui->loadingProgress);
};
- connect(m_parser, &Parser::finished,
- this, removeProgress);
- connect(m_parser, &Parser::failedToOpen,
- this, [this, removeProgress] (const QString& failedFile) {
+ connect(m_parser, &Parser::finished, this, removeProgress);
+ connect(m_parser, &Parser::failedToOpen, this, [this, removeProgress](const QString& failedFile) {
removeProgress();
m_ui->pages->setCurrentWidget(m_ui->openPage);
showError(i18n("Failed to parse file %1.", failedFile));
m_ui->messages->hide();
#if KChart_FOUND
- addChartTab(m_ui->tabWidget, tr("Consumed"), ChartModel::Consumed,
- m_parser, &Parser::consumedChartDataAvailable);
- addChartTab(m_ui->tabWidget, tr("Allocations"), ChartModel::Allocations,
- m_parser, &Parser::allocationsChartDataAvailable);
- addChartTab(m_ui->tabWidget, tr("Temporary Allocations"), ChartModel::Temporary,
- m_parser, &Parser::temporaryChartDataAvailable);
- addChartTab(m_ui->tabWidget, tr("Allocated"), ChartModel::Allocated,
- m_parser, &Parser::allocatedChartDataAvailable);
+ addChartTab(m_ui->tabWidget, tr("Consumed"), ChartModel::Consumed, m_parser, &Parser::consumedChartDataAvailable);
+ addChartTab(m_ui->tabWidget, tr("Allocations"), ChartModel::Allocations, m_parser,
+ &Parser::allocationsChartDataAvailable);
+ addChartTab(m_ui->tabWidget, tr("Temporary Allocations"), ChartModel::Temporary, m_parser,
+ &Parser::temporaryChartDataAvailable);
+ addChartTab(m_ui->tabWidget, tr("Allocated"), ChartModel::Allocated, m_parser,
+ &Parser::allocatedChartDataAvailable);
auto sizesTab = new HistogramWidget(this);
m_ui->tabWidget->addTab(sizesTab, tr("Sizes"));
auto sizeHistogramModel = new HistogramModel(this);
sizesTab->setModel(sizeHistogramModel);
- connect(m_parser, &Parser::sizeHistogramDataAvailable,
- this, [=] (const HistogramData& data) {
- sizeHistogramModel->resetData(data);
- m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(sizesTab), true);
- });
+ connect(m_parser, &Parser::sizeHistogramDataAvailable, this, [=](const HistogramData& data) {
+ sizeHistogramModel->resetData(data);
+ m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(sizesTab), true);
+ });
#endif
auto costDelegate = new CostDelegate(this);
- setupTreeModel(bottomUpModel, m_ui->bottomUpResults, costDelegate,
- m_ui->bottomUpFilterFunction, m_ui->bottomUpFilterFile,
- m_ui->bottomUpFilterModule);
+ setupTreeModel(bottomUpModel, m_ui->bottomUpResults, costDelegate, m_ui->bottomUpFilterFunction,
+ m_ui->bottomUpFilterFile, m_ui->bottomUpFilterModule);
- setupTreeModel(topDownModel, m_ui->topDownResults, costDelegate,
- m_ui->topDownFilterFunction, m_ui->topDownFilterFile,
- m_ui->topDownFilterModule);
+ setupTreeModel(topDownModel, m_ui->topDownResults, costDelegate, m_ui->topDownFilterFunction,
+ m_ui->topDownFilterFile, m_ui->topDownFilterModule);
- setupCallerCalle(callerCalleeModel, m_ui->callerCalleeResults, costDelegate,
- m_ui->callerCalleeFilterFunction,
- m_ui->callerCalleeFilterFile,
- m_ui->callerCalleeFilterModule);
+ setupCallerCalle(callerCalleeModel, m_ui->callerCalleeResults, costDelegate, m_ui->callerCalleeFilterFunction,
+ m_ui->callerCalleeFilterFile, m_ui->callerCalleeFilterModule);
- auto validateInputFile = [this] (const QString &path, bool allowEmpty) -> bool {
+ auto validateInputFile = [this](const QString& path, bool allowEmpty) -> bool {
if (path.isEmpty()) {
return allowEmpty;
}
return false;
};
- auto validateInput = [this, validateInputFile] () {
+ auto validateInput = [this, validateInputFile]() {
m_ui->messages->hide();
- m_ui->buttonBox->setEnabled(
- validateInputFile(m_ui->openFile->url().toLocalFile(), false)
- && validateInputFile(m_ui->compareTo->url().toLocalFile(), true)
- );
+ m_ui->buttonBox->setEnabled(validateInputFile(m_ui->openFile->url().toLocalFile(), false)
+ && validateInputFile(m_ui->compareTo->url().toLocalFile(), true));
};
- connect(m_ui->openFile, &KUrlRequester::textChanged,
- this, validateInput);
- connect(m_ui->compareTo, &KUrlRequester::textChanged,
- this, validateInput);
- connect(m_ui->buttonBox, &QDialogButtonBox::accepted,
- this, [this] () {
- const auto path = m_ui->openFile->url().toLocalFile();
- Q_ASSERT(!path.isEmpty());
- const auto base = m_ui->compareTo->url().toLocalFile();
- loadFile(path, base);
- });
+ connect(m_ui->openFile, &KUrlRequester::textChanged, this, validateInput);
+ connect(m_ui->compareTo, &KUrlRequester::textChanged, this, validateInput);
+ connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, [this]() {
+ const auto path = m_ui->openFile->url().toLocalFile();
+ Q_ASSERT(!path.isEmpty());
+ const auto base = m_ui->compareTo->url().toLocalFile();
+ loadFile(path, base);
+ });
setupStacks();
{
m_ui->loadingLabel->setText(i18n("Loading file %1, please wait...", file));
if (diffBase.isEmpty()) {
- setWindowTitle(i18nc("%1: file name that is open", "Heaptrack - %1",
- QFileInfo(file).fileName()));
+ setWindowTitle(i18nc("%1: file name that is open", "Heaptrack - %1", QFileInfo(file).fileName()));
m_diffMode = false;
} else {
setWindowTitle(i18nc("%1, %2: file names that are open", "Heaptrack - %1 compared to %2",
void MainWindow::openFile()
{
- auto dialog = new QFileDialog(this, i18n("Open Heaptrack Output File"), {}, i18n("Heaptrack data files (heaptrack.*)"));
+ auto dialog =
+ new QFileDialog(this, i18n("Open Heaptrack Output File"), {}, i18n("Heaptrack data files (heaptrack.*)"));
dialog->setAttribute(Qt::WA_DeleteOnClose, true);
dialog->setFileMode(QFileDialog::ExistingFile);
- connect(dialog, &QFileDialog::fileSelected,
- this, [this] (const QString& file) {
- loadFile(file);
- });
+ connect(dialog, &QFileDialog::fileSelected, this, [this](const QString& file) { loadFile(file); });
dialog->show();
}
-void MainWindow::showError(const QString &message)
+void MainWindow::showError(const QString& message)
{
m_ui->messages->setText(message);
m_ui->messages->show();
m_ui->stacksTree->setModel(stacksModel);
m_ui->stacksTree->setRootIsDecorated(false);
- auto updateStackSpinner = [this] (int stacks) {
+ auto updateStackSpinner = [this](int stacks) {
m_ui->stackSpinner->setMinimum(min(stacks, 1));
m_ui->stackSpinner->setSuffix(i18n(" / %1", stacks));
m_ui->stackSpinner->setMaximum(stacks);
};
updateStackSpinner(0);
- connect(stacksModel, &StacksModel::stacksFound,
- this, updateStackSpinner);
- connect(m_ui->stackSpinner, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
- stacksModel, &StacksModel::setStackIndex);
+ connect(stacksModel, &StacksModel::stacksFound, this, updateStackSpinner);
+ connect(m_ui->stackSpinner, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), stacksModel,
+ &StacksModel::setStackIndex);
- auto fillFromIndex = [stacksModel] (const QModelIndex& current) {
+ auto fillFromIndex = [stacksModel](const QModelIndex& current) {
if (!current.isValid()) {
stacksModel->clear();
} else {
stacksModel->fillFromIndex(leaf);
}
};
- connect(m_ui->bottomUpResults->selectionModel(), &QItemSelectionModel::currentChanged,
- this, fillFromIndex);
- connect(m_ui->topDownResults->selectionModel(), &QItemSelectionModel::currentChanged,
- this, fillFromIndex);
+ connect(m_ui->bottomUpResults->selectionModel(), &QItemSelectionModel::currentChanged, this, fillFromIndex);
+ connect(m_ui->topDownResults->selectionModel(), &QItemSelectionModel::currentChanged, this, fillFromIndex);
- auto tabChanged = [this, fillFromIndex] (int tabIndex) {
+ auto tabChanged = [this, fillFromIndex](int tabIndex) {
const auto widget = m_ui->tabWidget->widget(tabIndex);
const bool showDocks = (widget == m_ui->topDownTab || widget == m_ui->bottomUpTab);
m_ui->stacksDock->setVisible(showDocks);
fillFromIndex(tree->selectionModel()->currentIndex());
}
};
- connect(m_ui->tabWidget, &QTabWidget::currentChanged,
- this, tabChanged);
- connect(m_parser, &Parser::bottomUpDataAvailable,
- this, [tabChanged] () { tabChanged(0); });
+ connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, tabChanged);
+ connect(m_parser, &Parser::bottomUpDataAvailable, this, [tabChanged]() { tabChanged(0); });
m_ui->stacksDock->setVisible(false);
}
#define MAINWINDOW_H
#include <QMainWindow>
+
#include <KSharedConfig>
namespace Ui {
void openFile();
private:
- void showError(const QString &message);
+ void showError(const QString& message);
void setupStacks();
QScopedPointer<Ui::MainWindow> m_ui;
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/
+// clang-format off
#include "modeltest.h"
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/
+// clang-format off
#ifndef KDEVPLATFORM_MODELTEST_H
#define KDEVPLATFORM_MODELTEST_H
#include "parser.h"
-#include <ThreadWeaver/ThreadWeaver>
#include <KLocalizedString>
+#include <ThreadWeaver/ThreadWeaver>
#include <QDebug>
#include "analyze/accumulatedtracedata.h"
-#include <vector>
-#include <tuple>
#include <future>
+#include <tuple>
+#include <vector>
using namespace std;
void update(const vector<string>& strings)
{
- transform(strings.begin() + m_strings.size(), strings.end(),
- back_inserter(m_strings), [] (const string& str) { return QString::fromStdString(str); });
+ transform(strings.begin() + m_strings.size(), strings.end(), back_inserter(m_strings),
+ [](const string& str) { return QString::fromStdString(str); });
}
vector<QString> m_strings;
it->allocations += alloc.allocations;
it->temporary += alloc.temporary;
}
- // find the top hot spots for the individual data members and remember their IP and store the label
- auto findTopChartEntries = [&] (qint64 ChartMergeData::* member, int LabelIds::* label, ChartData* data) {
- sort(merged.begin(), merged.end(), [=] (const ChartMergeData& left, const ChartMergeData& right) {
- return left.*member > right.*member;
- });
+ // find the top hot spots for the individual data members and remember their
+ // IP and store the label
+ auto findTopChartEntries = [&](qint64 ChartMergeData::*member, int LabelIds::*label, ChartData* data) {
+ sort(merged.begin(), merged.end(),
+ [=](const ChartMergeData& left, const ChartMergeData& right) { return left.*member > right.*member; });
for (size_t i = 0; i < min(size_t(ChartRows::MAX_NUM_COST - 1), merged.size()); ++i) {
const auto& alloc = merged[i];
if (!(alloc.*member)) {
lastTimeStamp = newStamp;
// create the rows
- auto createRow = [] (int64_t timeStamp, int64_t totalCost) {
+ auto createRow = [](int64_t timeStamp, int64_t totalCost) {
ChartRows row;
row.timeStamp = timeStamp;
row.cost[0] = totalCost;
auto allocs = createRow(newStamp, totalCost.allocations);
auto temporary = createRow(newStamp, totalCost.temporary);
- // if the cost is non-zero and the ip corresponds to a hotspot function selected in the labels,
+ // if the cost is non-zero and the ip corresponds to a hotspot function
+ // selected in the labels,
// we add the cost to the rows column
- auto addDataToRow = [] (int64_t cost, int labelId, ChartRows* rows) {
+ auto addDataToRow = [](int64_t cost, int labelId, ChartRows* rows) {
if (!cost || labelId == -1) {
return;
}
int64_t allocations;
bool operator<(const CountedAllocationInfo& rhs) const
{
- return tie(info.size, allocations)
- < tie(rhs.info.size, rhs.allocations);
+ return tie(info.size, allocations) < tie(rhs.info.size, rhs.allocations);
}
};
vector<CountedAllocationInfo> allocationInfoCounter;
void setParents(QVector<RowData>& children, const RowData* parent)
{
- for (auto& row: children) {
+ for (auto& row : children) {
row.parent = parent;
setParents(row.children, &row);
}
TreeData topRows;
CallerCalleeRows callerCalleeRows;
callerCalleeRows.reserve(data.instructionPointers.size());
- // merge allocations, leave parent pointers invalid (their location may change)
+ // merge allocations, leave parent pointers invalid (their location may
+ // change)
for (const auto& allocation : data.allocations) {
auto traceIndex = allocation.traceIndex;
auto rows = &topRows;
auto location = data.stringCache.location(trace.ipIndex, ip);
if (!recursionGuard.contains(trace.ipIndex)) { // aggregate caller-callee data
- auto it = lower_bound(callerCalleeRows.begin(), callerCalleeRows.end(), location,
- [] (const CallerCalleeData& lhs, const LocationData::Ptr& rhs) {
- return lhs.location < rhs;
- });
+ auto it = lower_bound(
+ callerCalleeRows.begin(), callerCalleeRows.end(), location,
+ [](const CallerCalleeData& lhs, const LocationData::Ptr& rhs) { return lhs.location < rhs; });
if (it == callerCalleeRows.end() || it->location != location) {
it = callerCalleeRows.insert(it, {{}, {}, location});
}
if (data.stringCache.diffMode) {
// remove rows without cost
callerCalleeRows.erase(remove_if(callerCalleeRows.begin(), callerCalleeRows.end(),
- [] (const CallerCalleeData& data) -> bool {
- return data.inclusiveCost == AllocationData()
- && data.selfCost == AllocationData();
- }), callerCalleeRows.end());
+ [](const CallerCalleeData& data) -> bool {
+ return data.inclusiveCost == AllocationData()
+ && data.selfCost == AllocationData();
+ }),
+ callerCalleeRows.end());
}
return qMakePair(topRows, callerCalleeRows);
sort(data.allocationInfoCounter.begin(), data.allocationInfoCounter.end());
const auto totalLabel = i18n("total");
HistogramRow row;
- const pair<uint64_t, QString> buckets[] = {
- {8, i18n("0B to 8B")},
- {16, i18n("9B to 16B")},
- {32, i18n("17B to 32B")},
- {64, i18n("33B to 64B")},
- {128, i18n("65B to 128B")},
- {256, i18n("129B to 256B")},
- {512, i18n("257B to 512B")},
- {1024, i18n("512B to 1KB")},
- {numeric_limits<uint64_t>::max(), i18n("more than 1KB")}
- };
+ const pair<uint64_t, QString> buckets[] = {{8, i18n("0B to 8B")},
+ {16, i18n("9B to 16B")},
+ {32, i18n("17B to 32B")},
+ {64, i18n("33B to 64B")},
+ {128, i18n("65B to 128B")},
+ {256, i18n("129B to 256B")},
+ {512, i18n("257B to 512B")},
+ {1024, i18n("512B to 1KB")},
+ {numeric_limits<uint64_t>::max(), i18n("more than 1KB")}};
uint bucketIndex = 0;
row.size = buckets[bucketIndex].first;
row.sizeLabel = buckets[bucketIndex].second;
vector<MergedHistogramColumnData> columnData;
columnData.reserve(128);
- auto insertColumns = [&] () {
- sort(columnData.begin(), columnData.end(), [] (const MergedHistogramColumnData& lhs, const MergedHistogramColumnData& rhs) {
- return lhs.allocations > rhs.allocations;
- });
+ auto insertColumns = [&]() {
+ sort(columnData.begin(), columnData.end(),
+ [](const MergedHistogramColumnData& lhs, const MergedHistogramColumnData& rhs) {
+ return lhs.allocations > rhs.allocations;
+ });
// -1 to account for total row
for (size_t i = 0; i < min(columnData.size(), size_t(HistogramRow::NUM_COLUMNS - 1)); ++i) {
const auto& column = columnData[i];
ret << row;
return ret;
}
-
}
Parser::Parser(QObject* parent)
if (!diffBase.isEmpty()) {
ParserData diffData;
- auto readBase = async(launch::async, [&diffData, diffBase] () {
- return diffData.read(diffBase.toStdString());
- });
+ auto readBase =
+ async(launch::async, [&diffData, diffBase]() { return diffData.read(diffBase.toStdString()); });
if (!data->read(stdPath)) {
emit failedToOpen(path);
return;
data->updateStringCache();
- emit summaryAvailable({
- QString::fromStdString(data->debuggee),
- data->totalCost,
- data->totalTime,
- data->peakTime,
- data->peakRSS * data->systemInfo.pageSize,
- data->systemInfo.pages * data->systemInfo.pageSize
- });
+ emit summaryAvailable({QString::fromStdString(data->debuggee), data->totalCost, data->totalTime, data->peakTime,
+ data->peakRSS * data->systemInfo.pageSize,
+ data->systemInfo.pages * data->systemInfo.pageSize});
emit progressMessageAvailable(i18n("merging allocations..."));
// merge allocations before modifying the data again
}
auto sequential = new Sequence;
- *sequential << parallel << make_job([this]() {
- emit finished();
- });
+ *sequential << parallel << make_job([this]() { emit finished(); });
stream() << sequential;
});
#include <QObject>
-#include "treemodel.h"
+#include "callercalleemodel.h"
#include "chartmodel.h"
#include "histogrammodel.h"
-#include "callercalleemodel.h"
+#include "treemodel.h"
class Parser : public QObject
{
#ifndef SUMMARYDATA_H
#define SUMMARYDATA_H
-#include <QString>
-#include <QMetaType>
#include "../allocationdata.h"
+#include <QMetaType>
+#include <QString>
struct SummaryData
{
case TopProxy::Leaked:
return TreeModel::LeakedColumn;
case TopProxy::Allocations:
- return TreeModel::AllocationsColumn;;
+ return TreeModel::AllocationsColumn;
case TopProxy::Temporary:
return TreeModel::TemporaryColumn;
case TopProxy::Allocated:
return false;
}
if (!sourceModel()->index(source_row, toSource(m_type)).data(TreeModel::SortRole).toULongLong()) {
- // don't show rows that didn't leak anything, or didn't trigger any temporary allocations
+ // don't show rows that didn't leak anything, or didn't trigger any
+ // temporary allocations
return false;
}
return true;
{
Q_OBJECT
public:
- enum Type {
+ enum Type
+ {
Peak,
Leaked,
Allocations,
int idx = path.lastIndexOf(QLatin1Char('/'));
return path.mid(idx + 1);
}
-
}
TreeModel::TreeModel(QObject* parent)
return {};
}
if (role == Qt::InitialSortOrderRole) {
- if (section == AllocatedColumn || section == AllocationsColumn
- || section == PeakColumn || section == LeakedColumn
- || section == TemporaryColumn)
- {
+ if (section == AllocatedColumn || section == AllocationsColumn || section == PeakColumn
+ || section == LeakedColumn || section == TemporaryColumn) {
return Qt::DescendingOrder;
}
}
if (role == Qt::DisplayRole) {
switch (static_cast<Columns>(section)) {
- case FileColumn:
- return i18n("File");
- case LineColumn:
- return i18n("Line");
- case FunctionColumn:
- return i18n("Function");
- case ModuleColumn:
- return i18n("Module");
- case AllocationsColumn:
- return i18n("Allocations");
- case TemporaryColumn:
- return i18n("Temporary");
- case PeakColumn:
- return i18n("Peak");
- case LeakedColumn:
- return i18n("Leaked");
- case AllocatedColumn:
- return i18n("Allocated");
- case LocationColumn:
- return i18n("Location");
- case NUM_COLUMNS:
- break;
+ case FileColumn:
+ return i18n("File");
+ case LineColumn:
+ return i18n("Line");
+ case FunctionColumn:
+ return i18n("Function");
+ case ModuleColumn:
+ return i18n("Module");
+ case AllocationsColumn:
+ return i18n("Allocations");
+ case TemporaryColumn:
+ return i18n("Temporary");
+ case PeakColumn:
+ return i18n("Peak");
+ case LeakedColumn:
+ return i18n("Leaked");
+ case AllocatedColumn:
+ return i18n("Allocated");
+ case LocationColumn:
+ return i18n("Location");
+ case NUM_COLUMNS:
+ break;
}
} else if (role == Qt::ToolTipRole) {
switch (static_cast<Columns>(section)) {
- case FileColumn:
- return i18n("<qt>The file where the allocation function was called from. "
- "May be empty when debug information is missing.</qt>");
- case LineColumn:
- return i18n("<qt>The line number where the allocation function was called from. "
- "May be empty when debug information is missing.</qt>");
- case FunctionColumn:
- return i18n("<qt>The parent function that called an allocation function. "
- "May be unknown when debug information is missing.</qt>");
- case ModuleColumn:
- return i18n("<qt>The module, i.e. executable or shared library, from which an allocation function was called.</qt>");
- case AllocationsColumn:
- return i18n("<qt>The number of times an allocation function was called from this location.</qt>");
- case TemporaryColumn:
- return i18n("<qt>The number of temporary allocations. These allocations are directly followed by a free without any other allocations in-between.</qt>");
- case PeakColumn:
- return i18n("<qt>The maximum heap memory in bytes consumed from allocations originating at this location. "
- "This takes deallocations into account.</qt>");
- case LeakedColumn:
- return i18n("<qt>The bytes allocated at this location that have not been deallocated.</qt>");
- case AllocatedColumn:
- return i18n("<qt>The sum of all bytes allocated from this location, ignoring deallocations.</qt>");
- case LocationColumn:
- return i18n("<qt>The location from which an allocation function was called. Function symbol and file information "
- "may be unknown when debug information was missing when heaptrack was run.</qt>");
- case NUM_COLUMNS:
- break;
+ case FileColumn:
+ return i18n("<qt>The file where the allocation function was called from. "
+ "May be empty when debug information is missing.</qt>");
+ case LineColumn:
+ return i18n("<qt>The line number where the allocation function was called from. "
+ "May be empty when debug information is missing.</qt>");
+ case FunctionColumn:
+ return i18n("<qt>The parent function that called an allocation function. "
+ "May be unknown when debug information is missing.</qt>");
+ case ModuleColumn:
+ return i18n("<qt>The module, i.e. executable or shared library, from "
+ "which an allocation function was "
+ "called.</qt>");
+ case AllocationsColumn:
+ return i18n("<qt>The number of times an allocation function was called "
+ "from this location.</qt>");
+ case TemporaryColumn:
+ return i18n("<qt>The number of temporary allocations. These allocations "
+ "are directly followed by a free "
+ "without any other allocations in-between.</qt>");
+ case PeakColumn:
+ return i18n("<qt>The maximum heap memory in bytes consumed from "
+ "allocations originating at this location. "
+ "This takes deallocations into account.</qt>");
+ case LeakedColumn:
+ return i18n("<qt>The bytes allocated at this location that have not been "
+ "deallocated.</qt>");
+ case AllocatedColumn:
+ return i18n("<qt>The sum of all bytes allocated from this location, "
+ "ignoring deallocations.</qt>");
+ case LocationColumn:
+ return i18n("<qt>The location from which an allocation function was "
+ "called. Function symbol and file "
+ "information "
+ "may be unknown when debug information was missing when "
+ "heaptrack was run.</qt>");
+ case NUM_COLUMNS:
+ break;
}
}
return {};
return row->location->line;
case LocationColumn:
if (row->location->file.isEmpty()) {
- return i18n("%1 in ?? (%2)",
- basename(row->location->function),
- basename(row->location->module));
+ return i18n("%1 in ?? (%2)", basename(row->location->function), basename(row->location->module));
} else {
- return i18n("%1 in %2:%3 (%4)", row->location->function,
- basename(row->location->file), row->location->line,
- basename(row->location->module));
+ return i18n("%1 in %2:%3 (%4)", row->location->function, basename(row->location->file),
+ row->location->line, basename(row->location->module));
}
case NUM_COLUMNS:
break;
stream << "<qt><pre style='font-family:monospace;'>";
if (row->location->line > 0) {
stream << i18nc("1: function, 2: file, 3: line, 4: module", "%1\n at %2:%3\n in %4",
- row->location->function.toHtmlEscaped(),
- row->location->file.toHtmlEscaped(), row->location->line, row->location->module.toHtmlEscaped());
+ row->location->function.toHtmlEscaped(), row->location->file.toHtmlEscaped(),
+ row->location->line, row->location->module.toHtmlEscaped());
} else {
- stream << i18nc("1: function, 2: module", "%1\n in %2",
- row->location->function.toHtmlEscaped(), row->location->module.toHtmlEscaped());
+ stream << i18nc("1: function, 2: module", "%1\n in %2", row->location->function.toHtmlEscaped(),
+ row->location->module.toHtmlEscaped());
}
stream << '\n';
stream << '\n';
KFormat format;
- const auto allocatedFraction = QString::number(double(row->cost.allocated) * 100. / m_maxCost.cost.allocated, 'g', 3);
- const auto peakFraction = QString::number(double(row->cost.peak) * 100. / m_maxCost.cost.peak, 'g', 3);
- const auto leakedFraction = QString::number(double(row->cost.leaked) * 100. / m_maxCost.cost.leaked, 'g', 3);
- const auto allocationsFraction = QString::number(double(row->cost.allocations) * 100. / m_maxCost.cost.allocations, 'g', 3);
- const auto temporaryFraction = QString::number(double(row->cost.temporary) * 100. / row->cost.allocations, 'g', 3);
- const auto temporaryFractionTotal = QString::number(double(row->cost.temporary) * 100. / m_maxCost.cost.temporary, 'g', 3);
- stream << i18n("allocated: %1 (%2% of total)\n",
- format.formatByteSize(row->cost.allocated), allocatedFraction);
+ const auto allocatedFraction =
+ QString::number(double(row->cost.allocated) * 100. / m_maxCost.cost.allocated, 'g', 3);
+ const auto peakFraction = QString::number(double(row->cost.peak) * 100. / m_maxCost.cost.peak, 'g', 3);
+ const auto leakedFraction = QString::number(double(row->cost.leaked) * 100. / m_maxCost.cost.leaked, 'g', 3);
+ const auto allocationsFraction =
+ QString::number(double(row->cost.allocations) * 100. / m_maxCost.cost.allocations, 'g', 3);
+ const auto temporaryFraction =
+ QString::number(double(row->cost.temporary) * 100. / row->cost.allocations, 'g', 3);
+ const auto temporaryFractionTotal =
+ QString::number(double(row->cost.temporary) * 100. / m_maxCost.cost.temporary, 'g', 3);
+ stream << i18n("allocated: %1 (%2% of total)\n", format.formatByteSize(row->cost.allocated), allocatedFraction);
stream << i18n("peak: %1 (%2% of total)\n", format.formatByteSize(row->cost.peak), peakFraction);
stream << i18n("leaked: %1 (%2% of total)\n", format.formatByteSize(row->cost.leaked), leakedFraction);
stream << i18n("allocations: %1 (%2% of total)\n", row->cost.allocations, allocationsFraction);
- stream << i18n("temporary: %1 (%2% of allocations, %3% of total)\n",
- row->cost.temporary, temporaryFraction, temporaryFractionTotal);
+ stream << i18n("temporary: %1 (%2% of allocations, %3% of total)\n", row->cost.temporary, temporaryFraction,
+ temporaryFractionTotal);
if (!row->children.isEmpty()) {
auto child = row;
int max = 5;
stream << "\n";
if (child->location->line > 0) {
stream << i18nc("1: function, 2: file, 3: line, 4: module", "%1\n at %2:%3\n in %4",
- child->location->function.toHtmlEscaped(),
- child->location->file.toHtmlEscaped(), child->location->line,
- child->location->module.toHtmlEscaped());
+ child->location->function.toHtmlEscaped(), child->location->file.toHtmlEscaped(),
+ child->location->line, child->location->module.toHtmlEscaped());
} else {
- stream << i18nc("1: function, 2: module", "%1\n in %2",
- child->location->function.toHtmlEscaped(), child->location->module.toHtmlEscaped());
+ stream << i18nc("1: function, 2: module", "%1\n in %2", child->location->function.toHtmlEscaped(),
+ child->location->module.toHtmlEscaped());
}
child = child->children.data();
}
QModelIndex TreeModel::index(int row, int column, const QModelIndex& parent) const
{
- if (row < 0 || column < 0 || column >= NUM_COLUMNS || row >= rowCount(parent)) {
+ if (row < 0 || column < 0 || column >= NUM_COLUMNS || row >= rowCount(parent)) {
return QModelIndex();
}
return createIndex(row, column, const_cast<void*>(reinterpret_cast<const void*>(toRow(parent))));
#include <KFormat>
+#include "../allocationdata.h"
#include "locationdata.h"
#include "summarydata.h"
-#include "../allocationdata.h"
struct RowData
{
TreeModel(QObject* parent);
virtual ~TreeModel();
- enum Columns {
+ enum Columns
+ {
AllocationsColumn,
TemporaryColumn,
PeakColumn,
NUM_COLUMNS
};
- enum Roles {
+ enum Roles
+ {
SortRole = Qt::UserRole,
MaxCostRole,
LocationRole
};
#endif // TREEMODEL_H
-
QString m_moduleFilter;
};
-#endif //TREEPROXY_H
+#endif // TREEPROXY_H
#include "analyze/accumulatedtracedata.h"
-#include <iostream>
-#include <iomanip>
#include <future>
+#include <iomanip>
+#include <iostream>
#include "util/config.h"
return out << data.m_bytes << 'B';
}
- static const auto units = {
- "B",
- "KB",
- "MB",
- "GB",
- "TB"
- };
+ static const auto units = {"B", "KB", "MB", "GB", "TB"};
auto unit = units.begin();
size_t i = 0;
double bytes = data.m_bytes;
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);
- });
+ [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;
}
}
for (MergedAllocation& merged : ret) {
- for (const Allocation& allocation: merged.traces) {
+ for (const Allocation& allocation : merged.traces) {
merged.allocated += allocation.allocated;
merged.allocations += allocation.allocations;
merged.leaked += allocation.leaked;
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());
+ 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());
}
void printIndent(ostream& out, size_t indent, const char* indentString = " ") const
}
}
- void printIp(const IpIndex ip, ostream &out, const size_t indent = 0) const
+ void printIp(const IpIndex ip, ostream& out, const size_t indent = 0) const
{
printIp(findIp(ip), out, indent);
}
out << '\n';
}
- void printBacktrace(const TraceIndex traceIndex, ostream& out, const size_t indent = 0, bool skipFirst = false) const
+ void printBacktrace(const TraceIndex traceIndex, ostream& out, const size_t indent = 0,
+ bool skipFirst = false) const
{
if (!traceIndex) {
out << " ??";
printIp(ip, out, 0, true);
}
- template<typename T, typename LabelPrinter, typename SubLabelPrinter>
- void printAllocations(T AllocationData::* member, LabelPrinter label, SubLabelPrinter sublabel)
+ template <typename T, typename LabelPrinter, typename SubLabelPrinter>
+ void printAllocations(T AllocationData::*member, LabelPrinter label, SubLabelPrinter sublabel)
{
if (mergeBacktraces) {
printMerged(member, label, sublabel);
}
}
- template<typename T, typename LabelPrinter, typename SubLabelPrinter>
- void printMerged(T AllocationData::* member, LabelPrinter label, SubLabelPrinter sublabel)
+ template <typename T, typename LabelPrinter, typename SubLabelPrinter>
+ void printMerged(T AllocationData::*member, LabelPrinter label, SubLabelPrinter sublabel)
{
- auto sortOrder = [member] (const AllocationData& l, const AllocationData& r) {
+ auto sortOrder = [member](const AllocationData& l, const AllocationData& r) {
return std::abs(l.*member) > std::abs(r.*member);
};
sort(mergedAllocations.begin(), mergedAllocations.end(), sortOrder);
}
}
- template<typename T, typename LabelPrinter>
- void printUnmerged(T AllocationData::* member, LabelPrinter label)
+ template <typename T, typename LabelPrinter>
+ void printUnmerged(T AllocationData::*member, LabelPrinter label)
{
sort(allocations.begin(), allocations.end(),
- [member] (const Allocation& l, const Allocation &r) {
- return std::abs(l.*member) > std::abs(r.*member);
- });
+ [member](const Allocation& l, const Allocation& r) { return std::abs(l.*member) > std::abs(r.*member); });
for (size_t i = 0; i < min(peakLimit, allocations.size()); ++i) {
const auto& allocation = allocations[i];
if (!(allocation.*member)) {
lastMassifPeak = totalCost.leaked;
massifAllocations = allocations;
}
- massifOut
- << "#-----------\n"
- << "snapshot=" << massifSnapshotId << '\n'
- << "#-----------\n"
- << "time=" << (0.001 * timeStamp) << '\n'
- << "mem_heap_B=" << lastMassifPeak << '\n'
- << "mem_heap_extra_B=0\n"
- << "mem_stacks_B=0\n";
+ massifOut << "#-----------\n"
+ << "snapshot=" << massifSnapshotId << '\n'
+ << "#-----------\n"
+ << "time=" << (0.001 * timeStamp) << '\n'
+ << "mem_heap_B=" << lastMassifPeak << '\n'
+ << "mem_heap_extra_B=0\n"
+ << "mem_stacks_B=0\n";
if (massifDetailedFreq && (isLast || !(massifSnapshotId % massifDetailedFreq))) {
massifOut << "heap_tree=detailed\n";
size_t numAllocs = 0;
size_t skipped = 0;
auto mergedAllocations = mergeAllocations(allocations);
- sort(mergedAllocations.begin(), mergedAllocations.end(), [] (const MergedAllocation& l, const MergedAllocation& r) {
- return l.leaked > r.leaked;
- });
+ sort(mergedAllocations.begin(), mergedAllocations.end(),
+ [](const MergedAllocation& l, const MergedAllocation& r) { return l.leaked > r.leaked; });
const auto ip = findIp(location);
if (!shouldStop) {
for (auto& merged : mergedAllocations) {
if (merged.leaked < 0) {
- // list is sorted, so we can bail out now - these entries are uninteresting for massif
+ // list is sorted, so we can bail out now - these entries are
+ // uninteresting for massif
break;
}
// skip items below threshold
if (static_cast<size_t>(merged.leaked) >= threshold) {
++numAllocs;
- // skip the first level of the backtrace, otherwise we'd endlessly recurse
+ // skip the first level of the backtrace, otherwise we'd endlessly
+ // recurse
for (auto& alloc : merged.traces) {
alloc.traceIndex = findTrace(alloc.traceIndex).parentIndex;
}
printIndent(massifOut, depth, " ");
massifOut << 'n' << (numAllocs + (skipped ? 1 : 0)) << ": " << heapSize;
if (!depth) {
- massifOut << " (heap allocation functions) malloc/new/new[], --alloc-fns, etc.\n";
+ massifOut << " (heap allocation functions) malloc/new/new[], "
+ "--alloc-fns, etc.\n";
} else {
- massifOut << " 0x" << hex << ip.instructionPointer << dec
- << ": ";
+ massifOut << " 0x" << hex << ip.instructionPointer << dec << ": ";
if (ip.functionIndex) {
massifOut << stringify(ip.functionIndex);
} else {
auto writeSkipped = [&] {
if (skipped) {
printIndent(massifOut, depth, " ");
- massifOut << " n0: " << skippedLeaked << " in " << skipped
- << " places, all below massif's threshold (" << massifThreshold << ")\n";
+ massifOut << " n0: " << skippedLeaked << " in " << skipped << " places, all below massif's threshold ("
+ << massifThreshold << ")\n";
skipped = 0;
}
};
int main(int argc, char** argv)
{
po::options_description desc("Options", 120, 60);
- desc.add_options()
- ("file,f", po::value<string>(),
- "The heaptrack data file to print.")
- ("diff,d", po::value<string>()->default_value({}),
- "Find the differences to this file.")
- ("shorten-templates,t", po::value<bool>()->default_value(true)->implicit_value(true),
- "Shorten template identifiers.")
- ("merge-backtraces,m", po::value<bool>()->default_value(true)->implicit_value(true),
- "Merge backtraces.\nNOTE: the merged peak consumption is not correct.")
- ("print-peaks,p", po::value<bool>()->default_value(true)->implicit_value(true),
- "Print backtraces to top allocators, sorted by peak consumption.")
- ("print-allocators,a", po::value<bool>()->default_value(true)->implicit_value(true),
- "Print backtraces to top allocators, sorted by number of calls to allocation functions.")
- ("print-temporary,T", po::value<bool>()->default_value(true)->implicit_value(true),
- "Print backtraces to top allocators, sorted by number of temporary allocations.")
- ("print-leaks,l", po::value<bool>()->default_value(false)->implicit_value(true),
- "Print backtraces to leaked memory allocations.")
- ("print-overall-allocated,o", po::value<bool>()->default_value(false)->implicit_value(true),
- "Print top overall allocators, ignoring memory frees.")
- ("peak-limit,n", po::value<size_t>()->default_value(10)->implicit_value(10),
- "Limit the number of reported peaks.")
- ("sub-peak-limit,s", po::value<size_t>()->default_value(5)->implicit_value(5),
- "Limit the number of reported backtraces of merged peak locations.")
- ("print-histogram,H", po::value<string>()->default_value(string()),
- "Path to output file where an allocation size histogram will be written to.")
- ("print-flamegraph,F", po::value<string>()->default_value(string()),
- "Path to output file where a flame-graph compatible stack file will be written to.\n"
- "To visualize the resulting file, use flamegraph.pl from https://github.com/brendangregg/FlameGraph:\n"
- " heaptrack_print heaptrack.someapp.PID.gz -F stacks.txt\n"
- " # optionally pass --reverse to flamegraph.pl\n"
- " flamegraph.pl --title \"heaptrack: allocations\" --colors mem \\\n"
- " --countname allocations < stacks.txt > heaptrack.someapp.PID.svg\n"
- " [firefox|chromium] heaptrack.someapp.PID.svg\n")
- ("print-massif,M", po::value<string>()->default_value(string()),
- "Path to output file where a massif compatible data file will be written to.")
- ("massif-threshold", po::value<double>()->default_value(1.),
- "Percentage of current memory usage, below which allocations are aggregated into a 'below threshold' entry.\n"
- "This is only used in the massif output file so far.\n")
- ("massif-detailed-freq", po::value<size_t>()->default_value(2),
- "Frequency of detailed snapshots in the massif output file. Increase this to reduce the file size.\n"
- "You can set the value to zero to disable detailed snapshots.\n")
- ("filter-bt-function", po::value<string>()->default_value(string()),
- "Only print allocations where the backtrace contains the given function.")
- ("help,h",
- "Show this help message.")
- ("version,v",
- "Displays version information.");
+ desc.add_options()("file,f", po::value<string>(), "The heaptrack data file to print.")(
+ "diff,d", po::value<string>()->default_value({}), "Find the differences to this file.")(
+ "shorten-templates,t", po::value<bool>()->default_value(true)->implicit_value(true),
+ "Shorten template identifiers.")("merge-backtraces,m",
+ po::value<bool>()->default_value(true)->implicit_value(true),
+ "Merge backtraces.\nNOTE: the merged peak consumption is not correct.")(
+ "print-peaks,p", po::value<bool>()->default_value(true)->implicit_value(true),
+ "Print backtraces to top allocators, sorted by peak consumption.")(
+ "print-allocators,a", po::value<bool>()->default_value(true)->implicit_value(true),
+ "Print backtraces to top allocators, sorted by number of calls to "
+ "allocation functions.")("print-temporary,T", po::value<bool>()->default_value(true)->implicit_value(true),
+ "Print backtraces to top allocators, sorted by number of temporary "
+ "allocations.")("print-leaks,l",
+ po::value<bool>()->default_value(false)->implicit_value(true),
+ "Print backtraces to leaked memory allocations.")(
+ "print-overall-allocated,o", po::value<bool>()->default_value(false)->implicit_value(true),
+ "Print top overall allocators, ignoring memory frees.")(
+ "peak-limit,n", po::value<size_t>()->default_value(10)->implicit_value(10),
+ "Limit the number of reported peaks.")("sub-peak-limit,s",
+ po::value<size_t>()->default_value(5)->implicit_value(5),
+ "Limit the number of reported backtraces of merged peak locations.")(
+ "print-histogram,H", po::value<string>()->default_value(string()),
+ "Path to output file where an allocation size histogram will be written "
+ "to.")("print-flamegraph,F", po::value<string>()->default_value(string()),
+ "Path to output file where a flame-graph compatible stack file will be "
+ "written to.\n"
+ "To visualize the resulting file, use flamegraph.pl from "
+ "https://github.com/brendangregg/FlameGraph:\n"
+ " heaptrack_print heaptrack.someapp.PID.gz -F stacks.txt\n"
+ " # optionally pass --reverse to flamegraph.pl\n"
+ " flamegraph.pl --title \"heaptrack: allocations\" --colors mem \\\n"
+ " --countname allocations < stacks.txt > heaptrack.someapp.PID.svg\n"
+ " [firefox|chromium] heaptrack.someapp.PID.svg\n")(
+ "print-massif,M", po::value<string>()->default_value(string()),
+ "Path to output file where a massif compatible data file will be written "
+ "to.")("massif-threshold", po::value<double>()->default_value(1.),
+ "Percentage of current memory usage, below which allocations are "
+ "aggregated into a 'below threshold' entry.\n"
+ "This is only used in the massif output file so far.\n")(
+ "massif-detailed-freq", po::value<size_t>()->default_value(2),
+ "Frequency of detailed snapshots in the massif output file. Increase "
+ "this to reduce the file size.\n"
+ "You can set the value to zero to disable detailed snapshots.\n")(
+ "filter-bt-function", po::value<string>()->default_value(string()),
+ "Only print allocations where the backtrace contains the given "
+ "function.")("help,h", "Show this help message.")("version,v", "Displays version information.");
po::positional_options_description p;
p.add("file", -1);
po::variables_map vm;
try {
- po::store(po::command_line_parser(argc, argv)
- .options(desc).positional(p).run(), vm);
+ po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), vm);
if (vm.count("help")) {
cout << "heaptrack_print - analyze heaptrack data files.\n"
- << "\n"
- << "heaptrack is a heap memory profiler which records information\n"
- << "about calls to heap allocation functions such as malloc, operator new etc. pp.\n"
- << "This print utility can then be used to analyze the generated data files.\n\n"
- << desc << endl;
+ << "\n"
+ << "heaptrack is a heap memory profiler which records information\n"
+ << "about calls to heap allocation functions such as malloc, "
+ "operator new etc. pp.\n"
+ << "This print utility can then be used to analyze the generated "
+ "data files.\n\n"
+ << desc << endl;
return 0;
} else if (vm.count("version")) {
cout << "heaptrack_print " << HEAPTRACK_VERSION_STRING << endl;
}
po::notify(vm);
} catch (const po::error& error) {
- cerr << "ERROR: " << error.what() << endl
- << endl << desc << endl;
+ cerr << "ERROR: " << error.what() << endl << endl << desc << endl;
return 1;
}
const string printMassif = vm["print-massif"].as<string>();
if (!printMassif.empty()) {
data.massifOut.open(printMassif, ios_base::out);
- if (!data.massifOut.is_open()) {
+ if (!data.massifOut.is_open()) {
cerr << "Failed to open massif output file \"" << printMassif << "\"." << endl;
return 1;
}
if (!diffFile.empty()) {
cout << "reading diff file \"" << diffFile << "\" - please wait, this might take some time..." << endl;
Printer diffData;
- auto diffRead = async(launch::async, [&diffData, diffFile] () {
- return diffData.read(diffFile);
- });
+ auto diffRead = async(launch::async, [&diffData, diffFile]() { return diffData.read(diffFile); });
if (!data.read(inputFile) || !diffRead.get()) {
return 1;
if (printAllocs) {
// sort by amount of allocations
cout << "MOST CALLS TO ALLOCATION FUNCTIONS\n";
- data.printAllocations(&AllocationData::allocations, [] (const AllocationData& data) {
- cout << data.allocations << " calls to allocation functions with " << formatBytes(data.peak) << " peak consumption from\n";
- }, [] (const AllocationData& data) {
- cout << data.allocations << " calls with " << formatBytes(data.peak) << " peak consumption from:\n";
- });
+ data.printAllocations(&AllocationData::allocations,
+ [](const AllocationData& data) {
+ cout << data.allocations << " calls to allocation functions with "
+ << formatBytes(data.peak) << " peak consumption from\n";
+ },
+ [](const AllocationData& data) {
+ cout << data.allocations << " calls with " << formatBytes(data.peak)
+ << " peak consumption from:\n";
+ });
cout << endl;
}
if (printOverallAlloc) {
cout << "MOST BYTES ALLOCATED OVER TIME (ignoring deallocations)\n";
- data.printAllocations(&AllocationData::allocated, [] (const AllocationData& data) {
- cout << formatBytes(data.allocated) << " allocated over " << data.allocations << " calls from\n";
- }, [] (const AllocationData& data) {
- cout << formatBytes(data.allocated) << " allocated over " << data.allocations << " calls from:\n";
- });
+ data.printAllocations(&AllocationData::allocated,
+ [](const AllocationData& data) {
+ cout << formatBytes(data.allocated) << " allocated over " << data.allocations
+ << " calls from\n";
+ },
+ [](const AllocationData& data) {
+ cout << formatBytes(data.allocated) << " allocated over " << data.allocations
+ << " calls from:\n";
+ });
cout << endl;
}
if (printPeaks) {
- ///FIXME: find a way to merge this without breaking temporal dependency.
+ /// FIXME: find a way to merge this without breaking temporal dependency.
/// I.e. a given function could be called N times from different places
/// and allocate M bytes each, but free it thereafter.
/// Then the below would give a wrong total peak size of N * M instead
" For an accurate overview, disable backtrace merging.\n";
}
- data.printAllocations(&AllocationData::peak, [] (const AllocationData& data) {
- cout << formatBytes(data.peak) << " peak memory consumed over " << data.allocations << " calls from\n";
- }, [] (const AllocationData& data) {
- cout << formatBytes(data.peak) << " consumed over " << data.allocations << " calls from:\n";
- });
+ data.printAllocations(&AllocationData::peak,
+ [](const AllocationData& data) {
+ cout << formatBytes(data.peak) << " peak memory consumed over " << data.allocations
+ << " calls from\n";
+ },
+ [](const AllocationData& data) {
+ cout << formatBytes(data.peak) << " consumed over " << data.allocations
+ << " calls from:\n";
+ });
}
if (printLeaks) {
// sort by amount of leaks
cout << "MEMORY LEAKS\n";
- data.printAllocations(&AllocationData::leaked, [] (const AllocationData& data) {
- cout << formatBytes(data.leaked) << " leaked over " << data.allocations << " calls from\n";
- }, [] (const AllocationData& data) {
- cout << formatBytes(data.leaked) << " leaked over " << data.allocations << " calls from:\n";
- });
+ data.printAllocations(&AllocationData::leaked,
+ [](const AllocationData& data) {
+ cout << formatBytes(data.leaked) << " leaked over " << data.allocations
+ << " calls from\n";
+ },
+ [](const AllocationData& data) {
+ cout << formatBytes(data.leaked) << " leaked over " << data.allocations
+ << " calls from:\n";
+ });
cout << endl;
}
if (printTemporary) {
// sort by amount of temporary allocations
cout << "MOST TEMPORARY ALLOCATIONS\n";
- data.printAllocations(&AllocationData::temporary, [] (const AllocationData& data) {
- cout << data.temporary << " temporary allocations of " << data.allocations << " allocations in total ("
- << setprecision(2) << (float(data.temporary) * 100.f / data.allocations) << "%) from\n";
- }, [] (const AllocationData& data) {
- cout << data.temporary << " temporary allocations of " << data.allocations << " allocations in total ("
- << setprecision(2) << (float(data.temporary) * 100.f / data.allocations) << "%) from:\n";
- });
+ data.printAllocations(&AllocationData::temporary,
+ [](const AllocationData& data) {
+ cout << data.temporary << " temporary allocations of " << data.allocations
+ << " allocations in total (" << setprecision(2)
+ << (float(data.temporary) * 100.f / data.allocations) << "%) from\n";
+ },
+ [](const AllocationData& data) {
+ cout << data.temporary << " temporary allocations of " << data.allocations
+ << " allocations in total (" << setprecision(2)
+ << (float(data.temporary) * 100.f / data.allocations) << "%) from:\n";
+ });
cout << endl;
}
const double totalTimeS = 0.001 * data.totalTime;
cout << "total runtime: " << fixed << totalTimeS << "s.\n"
- << "bytes allocated in total (ignoring deallocations): " << formatBytes(data.totalCost.allocated)
- << " (" << formatBytes(data.totalCost.allocated / totalTimeS) << "/s)" << '\n'
- << "calls to allocation functions: " << data.totalCost.allocations
- << " (" << int64_t(data.totalCost.allocations / totalTimeS) << "/s)\n"
- << "temporary memory allocations: " << data.totalCost.temporary
- << " (" << int64_t(data.totalCost.temporary / totalTimeS) << "/s)\n"
+ << "bytes allocated in total (ignoring deallocations): " << formatBytes(data.totalCost.allocated) << " ("
+ << formatBytes(data.totalCost.allocated / totalTimeS) << "/s)" << '\n'
+ << "calls to allocation functions: " << data.totalCost.allocations << " ("
+ << int64_t(data.totalCost.allocations / totalTimeS) << "/s)\n"
+ << "temporary memory allocations: " << data.totalCost.temporary << " ("
+ << int64_t(data.totalCost.temporary / totalTimeS) << "/s)\n"
<< "peak heap memory consumption: " << formatBytes(data.totalCost.peak) << '\n'
<< "peak RSS (including heaptrack overhead): " << formatBytes(data.peakRSS * data.systemInfo.pageSize) << '\n'
<< "total memory leaked: " << formatBytes(data.totalCost.leaked) << '\n';
* @brief Interpret raw heaptrack data and add Dwarf based debug information.
*/
+#include <algorithm>
+#include <cinttypes>
#include <iostream>
#include <sstream>
+#include <stdio_ext.h>
+#include <tuple>
#include <unordered_map>
#include <vector>
-#include <tuple>
-#include <algorithm>
-#include <stdio_ext.h>
-#include <cinttypes>
#include <cxxabi.h>
}
backtrace_pcinfo(backtraceState, address,
- [] (void *data, uintptr_t /*addr*/, const char *file, int line, const char *function) -> int {
- auto info = reinterpret_cast<AddressInformation*>(data);
- info->function = demangle(function);
- info->file = file ? file : "";
- info->line = line;
- return 0;
- }, [] (void */*data*/, const char */*msg*/, int /*errnum*/) {}, &info);
+ [](void* data, uintptr_t /*addr*/, const char* file, int line, const char* function) -> int {
+ auto info = reinterpret_cast<AddressInformation*>(data);
+ info->function = demangle(function);
+ info->file = file ? file : "";
+ info->line = line;
+ return 0;
+ },
+ [](void* /*data*/, const char* /*msg*/, int /*errnum*/) {}, &info);
if (info.function.empty()) {
- backtrace_syminfo(backtraceState, address,
- [] (void *data, uintptr_t /*pc*/, const char *symname, uintptr_t /*symval*/, uintptr_t /*symsize*/) {
- if (symname) {
- reinterpret_cast<AddressInformation*>(data)->function = demangle(symname);
- }
- }, [] (void */*data*/, const char *msg, int errnum) {
- cerr << "Module backtrace error (code " << errnum << "): " << msg << endl;
- }, &info);
+ backtrace_syminfo(
+ backtraceState, address,
+ [](void* data, uintptr_t /*pc*/, const char* symname, uintptr_t /*symval*/, uintptr_t /*symsize*/) {
+ if (symname) {
+ reinterpret_cast<AddressInformation*>(data)->function = demangle(symname);
+ }
+ },
+ [](void* /*data*/, const char* msg, int errnum) {
+ cerr << "Module backtrace error (code " << errnum << "): " << msg << endl;
+ },
+ &info);
}
return info;
bool operator<(const Module& module) const
{
return tie(addressStart, addressEnd, moduleIndex)
- < tie(module.addressStart, module.addressEnd, module.moduleIndex);
+ < tie(module.addressStart, module.addressEnd, module.moduleIndex);
}
bool operator!=(const Module& module) const
~AccumulatedTraceData()
{
- fprintf(stdout, "# strings: %zu\n# ips: %zu\n",
- m_internedData.size(), m_encounteredIps.size());
+ fprintf(stdout, "# strings: %zu\n# ips: %zu\n", m_internedData.size(), m_encounteredIps.size());
}
ResolvedIP resolve(const uintptr_t ip)
continue;
}
const auto& m2 = m_modules[j];
- if ((m1.addressStart <= m2.addressStart && m1.addressEnd > m2.addressStart) ||
- (m1.addressStart < m2.addressEnd && m1.addressEnd >= m2.addressEnd))
- {
- cerr << "OVERLAPPING MODULES: " << hex
- << m1.moduleIndex << " (" << m1.addressStart << " to " << m1.addressEnd << ") and "
- << m1.moduleIndex << " (" << m2.addressStart << " to " << m2.addressEnd << ")\n"
+ if ((m1.addressStart <= m2.addressStart && m1.addressEnd > m2.addressStart)
+ || (m1.addressStart < m2.addressEnd && m1.addressEnd >= m2.addressEnd)) {
+ cerr << "OVERLAPPING MODULES: " << hex << m1.moduleIndex << " (" << m1.addressStart << " to "
+ << m1.addressEnd << ") and " << m1.moduleIndex << " (" << m2.addressStart << " to "
+ << m2.addressEnd << ")\n"
<< dec;
} else if (m2.addressStart >= m1.addressEnd) {
break;
ResolvedIP data;
// find module for this instruction pointer
- auto module = lower_bound(m_modules.begin(), m_modules.end(), ip,
- [] (const Module& module, const uintptr_t ip) -> bool {
- return module.addressEnd < ip;
- });
+ auto module =
+ lower_bound(m_modules.begin(), m_modules.end(), ip,
+ [](const Module& module, const uintptr_t ip) -> bool { return module.addressEnd < ip; });
if (module != m_modules.end() && module->addressStart <= ip && module->addressEnd >= ip) {
data.moduleIndex = module->moduleIndex;
const auto info = module->resolveAddress(ip);
return id;
}
- void addModule(backtrace_state* backtraceState, const size_t moduleIndex,
- const uintptr_t addressStart, const uintptr_t addressEnd)
+ void addModule(backtrace_state* backtraceState, const size_t moduleIndex, const uintptr_t addressStart,
+ const uintptr_t addressEnd)
{
m_modules.emplace_back(addressStart, addressEnd, backtraceState, moduleIndex);
m_modulesDirty = true;
return it->second;
}
- struct CallbackData {
+ struct CallbackData
+ {
const char* fileName;
};
CallbackData data = {fileName};
- auto errorHandler = [] (void *rawData, const char *msg, int errnum) {
+ auto errorHandler = [](void* rawData, const char* msg, int errnum) {
auto data = reinterpret_cast<const CallbackData*>(rawData);
- cerr << "Failed to create backtrace state for module " << data->fileName << ": "
- << msg << " / " << strerror(errnum) << " (error code " << errnum << ")" << endl;
+ cerr << "Failed to create backtrace state for module " << data->fileName << ": " << msg << " / "
+ << strerror(errnum) << " (error code " << errnum << ")" << endl;
};
auto state = backtrace_create_state(fileName, /* we are single threaded, so: not thread safe */ false,
if (descriptor >= 1) {
int foundSym = 0;
int foundDwarf = 0;
- auto ret = elf_add(state, descriptor, addressStart, errorHandler, &data,
- &state->fileline_fn, &foundSym, &foundDwarf, false);
+ auto ret = elf_add(state, descriptor, addressStart, errorHandler, &data, &state->fileline_fn, &foundSym,
+ &foundDwarf, false);
if (ret && foundSym) {
state->syminfo_fn = &elf_syminfo;
}
unordered_map<string, size_t> m_internedData;
unordered_map<uintptr_t, size_t> m_encounteredIps;
};
-
}
int main(int /*argc*/, char** /*argv*/)
if (fileName == "x") {
fileName = exe;
}
- const char *internedString = nullptr;
+ const char* internedString = nullptr;
const auto moduleIndex = data.intern(fileName, &internedString);
uintptr_t addressStart = 0;
if (!(reader >> addressStart)) {
uintptr_t vAddr = 0;
uintptr_t memSize = 0;
while ((reader >> vAddr) && (reader >> memSize)) {
- data.addModule(state, moduleIndex,
- addressStart + vAddr,
- addressStart + vAddr + memSize);
+ data.addModule(state, moduleIndex, addressStart + vAddr, addressStart + vAddr + memSize);
}
}
} else if (reader.mode() == 't') {
}
fprintf(stderr, "heaptrack stats:\n"
- "\tallocations: \t%" PRIu64 "\n"
- "\tleaked allocations: \t%" PRIu64 "\n"
- "\ttemporary allocations:\t%" PRIu64 "\n",
- allocations, leakedAllocations, temporaryAllocations);
+ "\tallocations: \t%" PRIu64 "\n"
+ "\tleaked allocations: \t%" PRIu64 "\n"
+ "\ttemporary allocations:\t%" PRIu64 "\n",
+ allocations, leakedAllocations, temporaryAllocations);
return 0;
}
}
#endif
-#define heaptrack_report_alloc(ptr, size) if (heaptrack_malloc) heaptrack_malloc(ptr, size)
+#define heaptrack_report_alloc(ptr, size) \
+ if (heaptrack_malloc) \
+ heaptrack_malloc(ptr, size)
-#define heaptrack_report_realloc(ptr_in, size, ptr_out) if (heaptrack_realloc) heaptrack_realloc(ptr_in, size, ptr_out)
+#define heaptrack_report_realloc(ptr_in, size, ptr_out) \
+ if (heaptrack_realloc) \
+ heaptrack_realloc(ptr_in, size, ptr_out)
-#define heaptrack_report_free(ptr) if (heaptrack_free) heaptrack_free(ptr)
+#define heaptrack_report_free(ptr) \
+ if (heaptrack_free) \
+ heaptrack_free(ptr)
#else // HEAPTRACK_API_DLSYM
struct heaptrack_api_t
{
- void (*malloc) (void *, size_t);
- void (*free) (void *);
- void (*realloc) (void *, size_t, void *);
+ void (*malloc)(void*, size_t);
+ void (*free)(void*);
+ void (*realloc)(void*, size_t, void*);
};
static struct heaptrack_api_t heaptrack_api = {0, 0, 0};
if (!initialized) {
void* sym = dlsym(RTLD_NEXT, "heaptrack_malloc");
if (sym)
- heaptrack_api.malloc = (void (*) (void *, size_t)) sym;
+ heaptrack_api.malloc = (void (*)(void*, size_t))sym;
sym = dlsym(RTLD_NEXT, "heaptrack_realloc");
if (sym)
- heaptrack_api.realloc = (void (*) (void *, size_t, void *)) sym;
+ heaptrack_api.realloc = (void (*)(void*, size_t, void*))sym;
sym = dlsym(RTLD_NEXT, "heaptrack_free");
if (sym)
- heaptrack_api.free = (void (*) (void *)) sym;
+ heaptrack_api.free = (void (*)(void*))sym;
initialized = 1;
}
}
-#define heaptrack_report_alloc(ptr, size) \
- do { \
- heaptrack_init_api(); \
- if (heaptrack_api.malloc) heaptrack_api.malloc(ptr, size); \
- } while(0)
-
-#define heaptrack_report_realloc(ptr_in, size, ptr_out) \
- do { \
- heaptrack_init_api(); \
- if (heaptrack_api.realloc) heaptrack_api.realloc(ptr_in, size, ptr_out); \
- } while(0)
-
-#define heaptrack_report_free(ptr) \
- do { \
- heaptrack_init_api(); \
- if (heaptrack_api.free) heaptrack_api.free(ptr); \
- } while(0)
+#define heaptrack_report_alloc(ptr, size) \
+ do { \
+ heaptrack_init_api(); \
+ if (heaptrack_api.malloc) \
+ heaptrack_api.malloc(ptr, size); \
+ } while (0)
+
+#define heaptrack_report_realloc(ptr_in, size, ptr_out) \
+ do { \
+ heaptrack_init_api(); \
+ if (heaptrack_api.realloc) \
+ heaptrack_api.realloc(ptr_in, size, ptr_out); \
+ } while (0)
+
+#define heaptrack_report_free(ptr) \
+ do { \
+ heaptrack_init_api(); \
+ if (heaptrack_api.free) \
+ heaptrack_api.free(ptr); \
+ } while (0)
#endif // HEAPTRACK_API_DLSYM
#include "libheaptrack.h"
-#include <link.h>
-#include <cstring>
#include <cstdlib>
-#include <sys/mman.h>
+#include <cstring>
+
+#include <link.h>
#include <malloc.h>
+#include <sys/mman.h>
+
#include <type_traits>
/**
static constexpr auto name = "free";
static constexpr auto original = &::free;
- static void hook(void *ptr) noexcept
+ static void hook(void* ptr) noexcept
{
heaptrack_free(ptr);
original(ptr);
static constexpr auto name = "realloc";
static constexpr auto original = &::realloc;
- static void* hook(void *ptr, size_t size) noexcept
+ static void* hook(void* ptr, size_t size) noexcept
{
auto ret = original(ptr, size);
heaptrack_realloc(ptr, size, ret);
static constexpr auto name = "cfree";
static constexpr auto original = &::cfree;
- static void hook(void *ptr) noexcept
+ static void hook(void* ptr) noexcept
{
heaptrack_free(ptr);
original(ptr);
static constexpr auto name = "dlopen";
static constexpr auto original = &::dlopen;
- static void* hook(const char *filename, int flag) noexcept
+ static void* hook(const char* filename, int flag) noexcept
{
auto ret = original(filename, flag);
if (ret) {
static constexpr auto name = "dlclose";
static constexpr auto original = &::dlclose;
- static int hook(void *handle) noexcept
+ static int hook(void* handle) noexcept
{
auto ret = original(handle);
if (!ret) {
static constexpr auto name = "posix_memalign";
static constexpr auto original = &::posix_memalign;
- static int hook(void **memptr, size_t alignment, size_t size) noexcept
+ static int hook(void** memptr, size_t alignment, size_t size) noexcept
{
auto ret = original(memptr, alignment, size);
if (!ret) {
}
};
-template<typename Hook>
-bool hook(const char *symname, ElfW(Addr) addr, bool restore)
+template <typename Hook>
+bool hook(const char* symname, ElfW(Addr) addr, bool restore)
{
static_assert(std::is_convertible<decltype(&Hook::hook), decltype(Hook::original)>::value,
"hook is not compatible to original function");
// try to make the page read/write accessible, which is hackish
// but apparently required for some shared libraries
- auto page = reinterpret_cast<void *>(addr & ~(0x1000 - 1));
+ auto page = reinterpret_cast<void*>(addr & ~(0x1000 - 1));
mprotect(page, 0x1000, PROT_READ | PROT_WRITE);
// now write to the address
return true;
}
-void apply(const char *symname, ElfW(Addr) addr, bool restore)
+void apply(const char* symname, ElfW(Addr) addr, bool restore)
{
// TODO: use std::apply once we can rely on C++17
- hook<malloc>(symname, addr, restore)
- || hook<free>(symname, addr, restore)
- || hook<realloc>(symname, addr, restore)
+ hook<malloc>(symname, addr, restore) || hook<free>(symname, addr, restore) || hook<realloc>(symname, addr, restore)
|| hook<calloc>(symname, addr, restore)
#if HAVE_CFREE
|| hook<cfree>(symname, addr, restore)
#endif
- || hook<posix_memalign>(symname, addr, restore)
- || hook<dlopen>(symname, addr, restore)
+ || hook<posix_memalign>(symname, addr, restore) || hook<dlopen>(symname, addr, restore)
|| hook<dlclose>(symname, addr, restore);
}
-
}
-template<typename T, ElfW(Sxword) AddrTag, ElfW(Sxword) SizeTag>
+template <typename T, ElfW(Sxword) AddrTag, ElfW(Sxword) SizeTag>
struct elftable
{
T* table = nullptr;
ElfW(Xword) size = ElfW(Xword)();
- bool consume(const ElfW(Dyn) *dyn) noexcept
+ bool consume(const ElfW(Dyn) * dyn) noexcept
{
if (dyn->d_tag == AddrTag) {
table = reinterpret_cast<T*>(dyn->d_un.d_ptr);
using elf_jmprel_table = elftable<ElfW(Rela), DT_JMPREL, DT_PLTRELSZ>;
using elf_symbol_table = elftable<ElfW(Sym), DT_SYMTAB, DT_SYMENT>;
-void try_overwrite_symbols(const ElfW(Dyn) *dyn, const ElfW(Addr) base, const bool restore) noexcept
+void try_overwrite_symbols(const ElfW(Dyn) * dyn, const ElfW(Addr) base, const bool restore) noexcept
{
elf_symbol_table symbols;
elf_jmprel_table jmprels;
}
// find symbols to overwrite
- const auto rela_end = reinterpret_cast<ElfW(Rela) *>(reinterpret_cast<char *>(jmprels.table) + jmprels.size);
+ const auto rela_end = reinterpret_cast<ElfW(Rela)*>(reinterpret_cast<char*>(jmprels.table) + jmprels.size);
for (auto rela = jmprels.table; rela < rela_end; rela++) {
const auto index = ELF_R_SYM(rela->r_info);
- const char *symname = strings.table + symbols.table[index].st_name;
+ const char* symname = strings.table + symbols.table[index].st_name;
auto addr = rela->r_offset + base;
hooks::apply(symname, addr, restore);
}
}
-int iterate_phdrs(dl_phdr_info *info, size_t /*size*/, void *data) noexcept
+int iterate_phdrs(dl_phdr_info* info, size_t /*size*/, void* data) noexcept
{
if (strstr(info->dlpi_name, "/libheaptrack_inject.so")) {
// prevent infinite recursion: do not overwrite our own symbols
for (auto phdr = info->dlpi_phdr, end = phdr + info->dlpi_phnum; phdr != end; ++phdr) {
if (phdr->p_type == PT_DYNAMIC) {
- try_overwrite_symbols(reinterpret_cast<const ElfW(Dyn) *>(phdr->p_vaddr + info->dlpi_addr),
- info->dlpi_addr, data != nullptr);
+ try_overwrite_symbols(reinterpret_cast<const ElfW(Dyn)*>(phdr->p_vaddr + info->dlpi_addr), info->dlpi_addr,
+ data != nullptr);
}
}
return 0;
{
dl_iterate_phdr(&iterate_phdrs, nullptr);
}
-
}
extern "C" {
-void heaptrack_inject(const char *outputFileName) noexcept
+void heaptrack_inject(const char* outputFileName) noexcept
{
- heaptrack_init(outputFileName, [] () {
- overwrite_symbols();
- }, [] (FILE* out) {
- fprintf(out, "A\n");
- }, [] () {
- bool do_shutdown = true;
- dl_iterate_phdr(&iterate_phdrs, &do_shutdown);
- });
+ heaptrack_init(outputFileName, []() { overwrite_symbols(); }, [](FILE* out) { fprintf(out, "A\n"); },
+ []() {
+ bool do_shutdown = true;
+ dl_iterate_phdr(&iterate_phdrs, &do_shutdown);
+ });
}
-
}
#include "libheaptrack.h"
-#include <dlfcn.h>
#include <cstdio>
-#include <cstring>
#include <cstdlib>
+#include <cstring>
+#include <dlfcn.h>
-#include <type_traits>
#include <atomic>
+#include <type_traits>
using namespace std;
namespace hooks {
-template<typename Signature, typename Base>
+template <typename Signature, typename Base>
struct hook
{
Signature original = nullptr;
original = reinterpret_cast<Signature>(ret);
}
- template<typename... Args>
- auto operator() (Args... args) const noexcept -> decltype(original(args...))
+ template <typename... Args>
+ auto operator()(Args... args) const noexcept -> decltype(original(args...))
{
return original(args...);
}
- explicit operator bool () const noexcept
+ explicit operator bool() const noexcept
{
return original;
}
};
-#define HOOK(name) struct name ## _t : public hook<decltype(&::name), name ## _t> { \
- static constexpr const char* identifier = #name; } name
+#define HOOK(name) \
+ struct name##_t : public hook<decltype(&::name), name##_t> \
+ { \
+ static constexpr const char* identifier = #name; \
+ } name
HOOK(malloc);
HOOK(free);
HOOK(dlclose);
/**
- * Dummy implementation, since the call to dlsym from findReal triggers a call to calloc.
+ * Dummy implementation, since the call to dlsym from findReal triggers a call
+ * to calloc.
*
- * This is only called at startup and will eventually be replaced by the "proper" calloc implementation.
+ * This is only called at startup and will eventually be replaced by the
+ * "proper" calloc implementation.
*/
void* dummy_calloc(size_t num, size_t size) noexcept
{
size_t oldOffset = offset;
offset += num * size;
if (offset >= MAX_SIZE) {
- fprintf(stderr, "failed to initialize, dummy calloc buf size exhausted: %zu requested, %zu available\n", offset, MAX_SIZE);
+ fprintf(stderr, "failed to initialize, dummy calloc buf size exhausted: "
+ "%zu requested, %zu available\n",
+ offset, MAX_SIZE);
abort();
}
return buf + oldOffset;
void init()
{
- heaptrack_init(getenv("DUMP_HEAPTRACK_OUTPUT"), [] {
- hooks::calloc.original = &dummy_calloc;
- hooks::calloc.init();
- hooks::dlopen.init();
- hooks::dlclose.init();
- hooks::malloc.init();
- hooks::free.init();
- hooks::calloc.init();
+ heaptrack_init(getenv("DUMP_HEAPTRACK_OUTPUT"),
+ [] {
+ hooks::calloc.original = &dummy_calloc;
+ hooks::calloc.init();
+ hooks::dlopen.init();
+ hooks::dlclose.init();
+ hooks::malloc.init();
+ hooks::free.init();
+ hooks::calloc.init();
#if HAVE_CFREE
- hooks::cfree.init();
+ hooks::cfree.init();
#endif
- hooks::realloc.init();
- hooks::posix_memalign.init();
- hooks::valloc.init();
+ hooks::realloc.init();
+ hooks::posix_memalign.init();
+ hooks::valloc.init();
#if HAVE_ALIGNED_ALLOC
- hooks::aligned_alloc.init();
+ hooks::aligned_alloc.init();
#endif
- // cleanup environment to prevent tracing of child apps
- unsetenv("LD_PRELOAD");
- unsetenv("DUMP_HEAPTRACK_OUTPUT");
- }, nullptr, nullptr);
+ // cleanup environment to prevent tracing of child apps
+ unsetenv("LD_PRELOAD");
+ unsetenv("DUMP_HEAPTRACK_OUTPUT");
+ },
+ nullptr, nullptr);
}
-
}
-
}
extern "C" {
}
#endif
-int posix_memalign(void **memptr, size_t alignment, size_t size) noexcept
+int posix_memalign(void** memptr, size_t alignment, size_t size) noexcept
{
if (!hooks::posix_memalign) {
hooks::init();
return ret;
}
-void *dlopen(const char *filename, int flag) noexcept
+void* dlopen(const char* filename, int flag) noexcept
{
if (!hooks::dlopen) {
hooks::init();
return ret;
}
-int dlclose(void *handle) noexcept
+int dlclose(void* handle) noexcept
{
if (!hooks::dlclose) {
hooks::init();
return ret;
}
-
}
#include <cstdio>
#include <cstdlib>
-#include <stdio_ext.h>
#include <fcntl.h>
#include <link.h>
+#include <stdio_ext.h>
#include <atomic>
-#include <string>
+#include <cinttypes>
#include <memory>
-#include <unordered_set>
#include <mutex>
+#include <string>
#include <thread>
-#include <cinttypes>
+#include <unordered_set>
#include <boost/algorithm/string/replace.hpp>
* Call this to optionally show debug information but give the compiler
* a hand in removing it all if debug output is disabled.
*/
-template<DebugVerbosity debugLevel, typename... Args>
+template <DebugVerbosity debugLevel, typename... Args>
inline void debugLog(const char fmt[], Args... args)
{
if (debugLevel <= s_debugVerbosity) {
char buf[BUF_SIZE + 1];
auto fd = open("/proc/self/cmdline", O_RDONLY);
int bytesRead = read(fd, buf, BUF_SIZE);
- char *end = buf + bytesRead;
- for (char *p = buf; p < end;) {
+ char* end = buf + bytesRead;
+ for (char* p = buf; p < end;) {
fputc(' ', out);
fputs(p, out);
- while (*p++); // skip until start of next 0-terminated section
+ while (*p++)
+ ; // skip until start of next 0-terminated section
}
close(fd);
void writeSystemInfo(FILE* out)
{
- fprintf(out, "I %lx %lx\n",
- sysconf(_SC_PAGESIZE),
- sysconf(_SC_PHYS_PAGES));
+ fprintf(out, "I %lx %lx\n", sysconf(_SC_PAGESIZE), sysconf(_SC_PHYS_PAGES));
}
FILE* createFile(const char* fileName)
/**
* Thread-Safe heaptrack API
*
- * The only critical section in libheaptrack is the output of the data, dl_iterate_phdr
+ * The only critical section in libheaptrack is the output of the data,
+ * dl_iterate_phdr
* calls, as well as initialization and shutdown.
*
- * This uses a spinlock, instead of a std::mutex, as the latter can lead to deadlocks
- * on destruction. The spinlock is "simple", and OK to only guard the small sections.
+ * This uses a spinlock, instead of a std::mutex, as the latter can lead to
+ * deadlocks
+ * on destruction. The spinlock is "simple", and OK to only guard the small
+ * sections.
*/
class HeapTrack
{
s_locked.store(false, memory_order_release);
}
- void initialize(const char* fileName,
- heaptrack_callback_t initBeforeCallback,
- heaptrack_callback_initialized_t initAfterCallback,
- heaptrack_callback_t stopCallback)
+ void initialize(const char* fileName, heaptrack_callback_t initBeforeCallback,
+ heaptrack_callback_initialized_t initAfterCallback, heaptrack_callback_t stopCallback)
{
debugLog<MinimalOutput>("initializing: %s", fileName);
if (s_data) {
if (unw_set_caching_policy(unw_local_addr_space, UNW_CACHE_PER_THREAD)) {
fprintf(stderr, "WARNING: Failed to enable per-thread libunwind caching.\n");
}
- #ifdef unw_set_cache_size
+#ifdef unw_set_cache_size
if (unw_set_cache_size(unw_local_addr_space, 1024, 0)) {
fprintf(stderr, "WARNING: Failed to set libunwind cache size.\n");
}
- #endif
+#endif
// do not trace forked child processes
// TODO: make this configurable
pthread_atfork(&prepare_fork, &parent_fork, &child_fork);
- atexit([] () {
+ atexit([]() {
debugLog<MinimalOutput>("%s", "atexit()");
s_atexit.store(true);
heaptrack_stop();
}
}
- void handleMalloc(void* ptr, size_t size, const Trace &trace)
+ void handleMalloc(void* ptr, size_t size, const Trace& trace)
{
if (!s_data || !s_data->out) {
return;
}
private:
- static int dl_iterate_phdr_callback(struct dl_phdr_info *info, size_t /*size*/, void *data)
+ static int dl_iterate_phdr_callback(struct dl_phdr_info* info, size_t /*size*/, void* data)
{
auto heaptrack = reinterpret_cast<HeapTrack*>(data);
- const char *fileName = info->dlpi_name;
+ const char* fileName = info->dlpi_name;
if (!fileName || !fileName[0]) {
fileName = "x";
}
shutdown();
}
- template<typename AdditionalLockCheck>
+ template <typename AdditionalLockCheck>
HeapTrack(AdditionalLockCheck lockCheck)
{
debugLog<VeryVerboseOutput>("%s", "acquiring lock");
if (!procStatm) {
fprintf(stderr, "WARNING: Failed to open /proc/self/statm for reading.\n");
}
- timerThread = thread([&] () {
+ timerThread = thread([&]() {
RecursionGuard::isActive = true;
debugLog<MinimalOutput>("%s", "timer thread started");
while (!stopTimerThread) {
if (timerThread.joinable()) {
try {
timerThread.join();
- } catch(std::system_error) {}
+ } catch (std::system_error) {
+ }
}
if (out) {
heaptrack_callback_t stopCallback = nullptr;
- #ifdef DEBUG_MALLOC_PTRS
+#ifdef DEBUG_MALLOC_PTRS
unordered_set<void*> known;
- #endif
+#endif
};
static atomic<bool> s_locked;
atomic<bool> HeapTrack::s_locked{false};
HeapTrack::LockedData* HeapTrack::s_data{nullptr};
-
}
extern "C" {
-void heaptrack_init(const char *outputFileName,
- heaptrack_callback_t initBeforeCallback,
- heaptrack_callback_initialized_t initAfterCallback,
- heaptrack_callback_t stopCallback)
+void heaptrack_init(const char* outputFileName, heaptrack_callback_t initBeforeCallback,
+ heaptrack_callback_initialized_t initAfterCallback, heaptrack_callback_t stopCallback)
{
RecursionGuard guard;
debugLog<MinimalOutput>("heaptrack_init(%s)", outputFileName);
HeapTrack heaptrack(guard);
- heaptrack.initialize(outputFileName,
- initBeforeCallback, initAfterCallback,
- stopCallback);
+ heaptrack.initialize(outputFileName, initBeforeCallback, initAfterCallback, stopCallback);
}
void heaptrack_stop()
HeapTrack heaptrack(guard);
heaptrack.invalidateModuleCache();
}
-
}
extern "C" {
#endif
-typedef void (*heaptrack_callback_t) ();
-typedef void (*heaptrack_callback_initialized_t) (FILE*);
+typedef void (*heaptrack_callback_t)();
+typedef void (*heaptrack_callback_initialized_t)(FILE*);
-void heaptrack_init(const char *outputFileName,
- heaptrack_callback_t initCallbackBefore,
- heaptrack_callback_initialized_t initCallbackAfter,
- heaptrack_callback_t stopCallback);
+void heaptrack_init(const char* outputFileName, heaptrack_callback_t initCallbackBefore,
+ heaptrack_callback_initialized_t initCallbackAfter, heaptrack_callback_t stopCallback);
void heaptrack_stop();
-void heaptrack_malloc(void *ptr, size_t size);
+void heaptrack_malloc(void* ptr, size_t size);
-void heaptrack_free(void *ptr);
+void heaptrack_free(void* ptr);
-void heaptrack_realloc(void *ptr_in, size_t size, void *ptr_out);
+void heaptrack_realloc(void* ptr_in, size_t size, void* ptr_out);
void heaptrack_invalidate_module_cache();
{
using ip_t = void*;
- enum : int {
+ enum : int
+ {
MAX_SIZE = 64
};
return begin() + m_size;
}
- ip_t operator[] (int i) const
+ ip_t operator[](int i) const
{
return m_data[m_skip + i];
}
* @brief Efficiently combine and store the data of multiple Traces.
*/
-#include <vector>
#include <algorithm>
+#include <vector>
#include "trace.h"
}
/**
- * Index the data in @p trace and return the index of the last instruction pointer.
+ * Index the data in @p trace and return the index of the last instruction
+ * pointer.
*
* Unknown instruction pointers will be printed to @p out.
*/
if (!ip) {
continue;
}
- auto it = std::lower_bound(parent->children.begin(), parent->children.end(), ip, [] (const TraceEdge& l, const Trace::ip_t ip) {
- return l.instructionPointer < ip;
- });
+ auto it =
+ std::lower_bound(parent->children.begin(), parent->children.end(), ip,
+ [](const TraceEdge& l, const Trace::ip_t ip) { return l.instructionPointer < ip; });
if (it == parent->children.end() || it->instructionPointer != ip) {
index = m_index++;
it = parent->children.insert(it, {ip, index, {}});
#include <unordered_map>
// sadly, C++ doesn't yet have opaque typedefs
-template<typename Base>
+template <typename Base>
struct Index
{
uint32_t index = 0;
}
};
-template<typename Base>
+template <typename Base>
uint qHash(const Index<Base> index, uint seed = 0) noexcept
{
return qHash(index.index, seed);
}
namespace std {
-template<typename Base>
-struct hash<Index<Base>> {
+template <typename Base>
+struct hash<Index<Base>>
+{
std::size_t operator()(const Index<Base> index) const
{
return std::hash<uint32_t>()(index.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 AllocationIndex : public Index<AllocationIndex> {};
+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 AllocationIndex : public Index<AllocationIndex>
+{
+};
#endif // INDICES_H
return m_line;
}
- template<typename T>
+ template <typename T>
bool readHex(T& in)
{
auto it = m_it;
#ifndef POINTERMAP_H
#define POINTERMAP_H
-#include <vector>
+#include <algorithm>
+#include <iostream>
+#include <limits>
+#include <map>
#include <unordered_map>
#include <unordered_set>
-#include <map>
-#include <limits>
-#include <iostream>
-#include <algorithm>
+#include <vector>
#include <boost/functional/hash.hpp>
AllocationIndex allocationIndex;
bool operator==(const IndexedAllocationInfo& rhs) const
{
- return rhs.traceIndex == traceIndex
- && rhs.size == size;
- // allocationInfoIndex not compared to allow to look it up
+ return rhs.traceIndex == traceIndex && rhs.size == size;
+ // allocationInfoIndex not compared to allow to look it up
}
};
namespace std {
-template<>
-struct hash<IndexedAllocationInfo> {
- size_t operator()(const IndexedAllocationInfo &info) const
+template <>
+struct hash<IndexedAllocationInfo>
+{
+ size_t operator()(const IndexedAllocationInfo& info) const
{
size_t seed = 0;
boost::hash_combine(seed, info.size);
};
/**
- * A low-memory-overhead map of 64bit pointer addresses to 32bit allocation indices.
+ * A low-memory-overhead map of 64bit pointer addresses to 32bit allocation
+ * indices.
*
* We leverage the fact that pointers are allocated in pages, i.e. close to each
* other. We split the 64bit address into a common large part and an individual
*
* The big part of the address is used for a hash map to lookup the Indices
* structure where we aggregate common pointers in two memory-efficient vectors,
- * one for the 16bit small pointer pairs, and one for the 32bit allocation indices.
+ * one for the 16bit small pointer pairs, and one for the 32bit allocation
+ * indices.
*/
class PointerMap
{
struct SplitPointer
{
- enum {
+ enum
+ {
PageSize = std::numeric_limits<uint16_t>::max() / 4
};
SplitPointer(uint64_t ptr)
: big(ptr / PageSize)
, small(ptr % PageSize)
- {}
+ {
+ }
uint64_t big;
uint16_t small;
};
auto& indices = mapIt->second;
auto pageIt = std::lower_bound(indices.smallPtrParts.begin(), indices.smallPtrParts.end(), pointer.small);
if (pageIt == indices.smallPtrParts.end() || *pageIt != pointer.small) {
- return {{}, false};
+ return {{}, false};
}
auto allocationIt = indices.allocationIndices.begin() + distance(indices.smallPtrParts.begin(), pageIt);
auto index = *allocationIt;
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#include "src/track/trace.h"
#include "3rdparty/catch.hpp"
+#include "src/track/trace.h"
#include <algorithm>
void validateTrace(const Trace& trace, int expectedSize)
{
- SECTION("validate the trace size") {
+ SECTION ("validate the trace size") {
REQUIRE(trace.size() == expectedSize);
REQUIRE(distance(trace.begin(), trace.end()) == trace.size());
}
- SECTION("validate trace contents") {
+ SECTION ("validate trace contents") {
REQUIRE(find(trace.begin(), trace.end(), Trace::ip_t(0)) == trace.end());
}
}
}
-TEST_CASE("getting backtrace traces", "[trace]") {
+TEST_CASE ("getting backtrace traces", "[trace]") {
Trace trace;
validateTrace(trace, 0);
- SECTION("fill without skipping") {
+ SECTION ("fill without skipping") {
REQUIRE(trace.fill(0));
const auto offset = trace.size();
REQUIRE(offset > 1);
validateTrace(trace, offset);
- SECTION("fill with skipping") {
+ SECTION ("fill with skipping") {
for (auto skip : {0, 1, 2}) {
for (int i = 0; i < 2 * Trace::MAX_SIZE; ++i) {
REQUIRE(fill(trace, i, skip));
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#include "src/util/pointermap.h"
#include "bench_pointers.h"
+#include "src/util/pointermap.h"
int main()
{
#ifndef BENCH_POINTERS
#define BENCH_POINTERS
-#include <cstdint>
-#include <vector>
#include <algorithm>
+#include <cstdint>
#include <iostream>
+#include <vector>
#include <malloc.h>
#include "src/util/indices.h"
-template<typename Map>
+template <typename Map>
void benchPointers()
{
uint32_t matches = 0;
}
const auto added = mallinfo().uordblks - baseline;
- std::cerr << "pointers added: \t" << added << " (" << (float(added) * 100.f / allocated) << "% overhead)" << std::endl;
+ std::cerr << "pointers added: \t" << added << " (" << (float(added) * 100.f / allocated)
+ << "% overhead)" << std::endl;
std::random_shuffle(pointers.begin(), pointers.end());
for (auto ptr : pointers) {
*/
#include <sparse_hash_map>
+
#include "bench_pointers.h"
#include "src/util/indices.h"
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+#include <iostream>
#include <malloc.h>
#include <unistd.h>
-#include <iostream>
using namespace std;
Foo::Foo()
: d(new Private)
{
-
}
Foo::~Foo()
-#include <cstdlib>
#include <cstdio>
+#include <cstdlib>
#define HAVE_ALIGNED_ALLOC defined(_ISOC11_SOURCE)
-struct Foo {
+struct Foo
+{
Foo()
: i(new int)
{
-#include <thread>
#include <future>
+#include <thread>
#include <vector>
using namespace std;