From 9134f65fc7218605fc60bdb791077deaeab97792 Mon Sep 17 00:00:00 2001 From: Milian Wolff Date: Tue, 15 Dec 2015 17:46:04 +0100 Subject: [PATCH] Add size histogram to heaptrack_gui. This tracks the number of times allocations of a certain size are requested and displays the data in a histogram. Note that the sizes are binned in the following byte ranges: 0-8 9-16 17-32 33-64 65-128 129-256 257-512 513-1024 >1024 --- accumulatedtracedata.cpp | 14 ++--- accumulatedtracedata.h | 4 +- gui/CMakeLists.txt | 2 + gui/chartwidget.cpp | 1 - gui/histogrammodel.cpp | 109 +++++++++++++++++++++++++++++++++ gui/histogrammodel.h | 70 ++++++++++++++++++++++ gui/histogramwidget.cpp | 153 +++++++++++++++++++++++++++++++++++++++++++++++ gui/histogramwidget.h | 48 +++++++++++++++ gui/mainwindow.cpp | 5 ++ gui/mainwindow.ui | 11 ++++ gui/parser.cpp | 97 +++++++++++++++++++++++++++++- gui/parser.h | 2 + heaptrack_print.cpp | 9 ++- 13 files changed, 509 insertions(+), 16 deletions(-) create mode 100644 gui/histogrammodel.cpp create mode 100644 gui/histogrammodel.h create mode 100644 gui/histogramwidget.cpp create mode 100644 gui/histogramwidget.h diff --git a/accumulatedtracedata.cpp b/accumulatedtracedata.cpp index 8829c23..e7ac38d 100644 --- a/accumulatedtracedata.cpp +++ b/accumulatedtracedata.cpp @@ -157,7 +157,6 @@ bool AccumulatedTraceData::read(istream& in) peak = 0; leaked = 0; allocations.clear(); - sizeHistogram.clear(); uint64_t lastAllocationPtr = 0; uint fileVersion = 0; @@ -211,14 +210,15 @@ bool AccumulatedTraceData::read(istream& in) } } else if (reader.mode() == '+') { BigAllocationInfo info; + AllocationIndex allocationIndex; if (fileVersion >= 0x010000) { - uint32_t allocationInfoIndex = 0; - if (!(reader >> allocationInfoIndex)) { + if (!(reader >> allocationIndex.index)) { cerr << "failed to parse line: " << reader.line() << endl; continue; } - info = allocationInfos[allocationInfoIndex]; + info = allocationInfos[allocationIndex.index]; } else { + // TODO: allocationInfoIndex uint64_t ptr = 0; if (!(reader >> info.size) || !(reader >> info.traceIndex) || !(reader >> ptr)) { cerr << "failed to parse line: " << reader.line() << endl; @@ -250,11 +250,7 @@ bool AccumulatedTraceData::read(istream& in) peak = leaked; } - if (printHistogram) { - ++sizeHistogram[info.size]; - } - - handleAllocation(); + handleAllocation(info, allocationIndex); } else if (reader.mode() == '-') { BigAllocationInfo info; bool temporary = false; diff --git a/accumulatedtracedata.h b/accumulatedtracedata.h index 6192f4c..1b34be0 100644 --- a/accumulatedtracedata.h +++ b/accumulatedtracedata.h @@ -125,7 +125,7 @@ struct AccumulatedTraceData virtual ~AccumulatedTraceData() = default; virtual void handleTimeStamp(uint64_t oldStamp, uint64_t newStamp) = 0; - virtual void handleAllocation() = 0; + virtual void handleAllocation(const BigAllocationInfo& info, const AllocationIndex index) = 0; virtual void handleDebuggee(const char* command) = 0; const std::string& stringify(const StringIndex stringId) const; @@ -136,11 +136,9 @@ struct AccumulatedTraceData bool read(std::istream& in); bool shortenTemplates = false; - bool printHistogram = false; bool fromAttached = false; std::vector allocations; - std::map sizeHistogram; uint64_t totalAllocated = 0; uint64_t totalAllocations = 0; uint64_t totalTemporary = 0; diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index 2887bd7..81cd8b3 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -19,6 +19,8 @@ add_executable(heaptrack_gui chartwidget.cpp chartmodel.cpp chartproxy.cpp + histogramwidget.cpp + histogrammodel.cpp modeltest.cpp parser.cpp flamegraph.cpp diff --git a/gui/chartwidget.cpp b/gui/chartwidget.cpp index 46f5ace..ad95f9b 100644 --- a/gui/chartwidget.cpp +++ b/gui/chartwidget.cpp @@ -67,7 +67,6 @@ public: const QString customizedLabel(const QString& label) const override { - // TODO: change distance between labels to 1024 and simply use prettyCost() here KFormat format(QLocale::system()); return format.formatByteSize(label.toDouble(), 1, KFormat::MetricBinaryDialect); } diff --git a/gui/histogrammodel.cpp b/gui/histogrammodel.cpp new file mode 100644 index 0000000..15b5eed --- /dev/null +++ b/gui/histogrammodel.cpp @@ -0,0 +1,109 @@ +/* + * Copyright 2015 Milian Wolff + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "histogrammodel.h" + +#include + +#include +#include + +#include +#include +#include + +#include + +namespace { +QColor colorForColumn(int column, int columnCount) +{ + return QColor::fromHsv((double(column) / columnCount) * 255, 255, 255); +} +} + +HistogramModel::HistogramModel(QObject* parent) + : QAbstractTableModel(parent) +{ + qRegisterMetaType("HistogramData"); +} + +HistogramModel::~HistogramModel() = default; + +QVariant HistogramModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Vertical && role == Qt::DisplayRole && section >= 0 && section < m_data.size()) { + return m_data.at(section).sizeLabel; + } + return {}; +} + +QVariant HistogramModel::data(const QModelIndex& index, int role) const +{ + if (!hasIndex(index.row(), index.column(), index.parent())) { + return {}; + } + if ( role == KChart::DatasetBrushRole ) { + return QVariant::fromValue(QBrush(colorForColumn(index.column(), columnCount()))); + } else if ( role == KChart::DatasetPenRole ) { + return QVariant::fromValue(QPen(Qt::black)); + } + + if (role != Qt::DisplayRole && role != Qt::ToolTipRole) { + return {}; + } + + const auto& row = m_data.at(index.row()); + const auto& column = row.columns[index.column()]; + if (role == Qt::ToolTipRole) { + if (index.column() == 0) { + 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 in %3", column.allocations, + column.location->function, column.location->module); + } + return column.allocations; +} + +int HistogramModel::columnCount(const QModelIndex& parent) const +{ + if (parent.isValid()) { + return 0; + } + return HistogramRow::NUM_COLUMNS; +} + +int HistogramModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) { + return 0; + } + return m_data.size(); +} + +void HistogramModel::resetData(const HistogramData& data) +{ + beginResetModel(); + m_data = data; + endResetModel(); +} diff --git a/gui/histogrammodel.h b/gui/histogrammodel.h new file mode 100644 index 0000000..2ebc64b --- /dev/null +++ b/gui/histogrammodel.h @@ -0,0 +1,70 @@ +/* + * Copyright 2015 Milian Wolff + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef HISTOGRAMMODEL_H +#define HISTOGRAMMODEL_H + +#include + +#include "treemodel.h" + +struct HistogramColumn +{ + quint64 allocations; + std::shared_ptr location; +}; +Q_DECLARE_TYPEINFO(HistogramColumn, Q_MOVABLE_TYPE); + +struct HistogramRow +{ + HistogramRow() + { + columns.fill({0, {}}); + } + enum { + NUM_COLUMNS = 10 + 1 + }; + QString sizeLabel; + quint64 size = 0; + std::array columns; +}; +Q_DECLARE_TYPEINFO(HistogramRow, Q_MOVABLE_TYPE); +Q_DECLARE_METATYPE(HistogramRow); + +using HistogramData = QVector; + +class HistogramModel : public QAbstractTableModel +{ + Q_OBJECT +public: + explicit HistogramModel(QObject* parent = nullptr); + ~HistogramModel() override; + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex& parent = {}) const override; + int columnCount(const QModelIndex& parent = {}) const override; + + void resetData(const HistogramData& data); + +private: + HistogramData m_data; +}; + +#endif // HISTOGRAMMODEL_H diff --git a/gui/histogramwidget.cpp b/gui/histogramwidget.cpp new file mode 100644 index 0000000..64cfc8f --- /dev/null +++ b/gui/histogramwidget.cpp @@ -0,0 +1,153 @@ +/* + * Copyright 2015 Milian Wolff + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "histogramwidget.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "histogrammodel.h" + +using namespace KChart; + +namespace { +class SizeAxis : public CartesianAxis +{ + Q_OBJECT +public: + explicit SizeAxis(AbstractCartesianDiagram* diagram = nullptr) + : CartesianAxis(diagram) + {} + + const QString customizedLabel(const QString& label) const override + { + KFormat format(QLocale::system()); + return format.formatByteSize(label.toDouble(), 1, KFormat::MetricBinaryDialect); + } +}; + +class HistogramProxy : public QSortFilterProxyModel +{ + Q_OBJECT +public: + explicit HistogramProxy(bool showTotal, QObject* parent = nullptr) + : QSortFilterProxyModel(parent) + , m_showTotal(showTotal) + { + } + virtual ~HistogramProxy() = default; + +protected: + bool filterAcceptsColumn(int sourceColumn, const QModelIndex& /*sourceParent*/) const override + { + if (m_showTotal) { + return sourceColumn == 0; + } else { + return sourceColumn != 0; + } + } + +private: + bool m_showTotal; +}; + +} + +HistogramWidget::HistogramWidget(QWidget* parent) + : QWidget(parent) + , m_chart(new KChart::Chart(this)) + , m_total(new BarDiagram(this)) + , m_detailed(new BarDiagram(this)) +{ + auto layout = new QVBoxLayout(this); + layout->addWidget(m_chart); + setLayout(layout); + + auto* coordinatePlane = dynamic_cast(m_chart->coordinatePlane()); + Q_ASSERT(coordinatePlane); + + { + m_total->setAntiAliasing(true); + + KColorScheme scheme(QPalette::Active, KColorScheme::Window); + QPen foreground(scheme.foreground().color()); + auto bottomAxis = new CartesianAxis(m_total); + auto axisTextAttributes = bottomAxis->textAttributes(); + axisTextAttributes.setPen(foreground); + bottomAxis->setTextAttributes(axisTextAttributes); + auto axisTitleTextAttributes = bottomAxis->titleTextAttributes(); + axisTitleTextAttributes.setPen(foreground); + bottomAxis->setTitleTextAttributes(axisTitleTextAttributes); + bottomAxis->setPosition(KChart::CartesianAxis::Bottom); + bottomAxis->setTitleText(i18n("Requested Allocation Size")); + m_total->addAxis(bottomAxis); + + auto* rightAxis = new CartesianAxis(m_total); + rightAxis->setTextAttributes(axisTextAttributes); + rightAxis->setTitleTextAttributes(axisTitleTextAttributes); + rightAxis->setTitleText(i18n("Number of Allocations")); + rightAxis->setPosition(CartesianAxis::Right); + m_total->addAxis(rightAxis); + + coordinatePlane->addDiagram(m_total); + + m_total->setType(BarDiagram::Normal); + } + + { + m_detailed->setAntiAliasing(true); + + coordinatePlane->addDiagram(m_detailed); + + m_detailed->setType(BarDiagram::Stacked); + } +} + +HistogramWidget::~HistogramWidget() = default; + +void HistogramWidget::setModel(QAbstractItemModel* model) +{ + { + auto proxy = new HistogramProxy(true, this); + proxy->setSourceModel(model); + m_total->setModel(proxy); + } + { + auto proxy = new HistogramProxy(false, this); + proxy->setSourceModel(model); + m_detailed->setModel(proxy); + } +} + +#include "histogramwidget.moc" diff --git a/gui/histogramwidget.h b/gui/histogramwidget.h new file mode 100644 index 0000000..28571bf --- /dev/null +++ b/gui/histogramwidget.h @@ -0,0 +1,48 @@ +/* + * Copyright 2015 Milian Wolff + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef HISTOGRAMWIDGET_H +#define HISTOGRAMWIDGET_H + +#include + +namespace KChart { +class Chart; +class BarDiagram; +} + +class QAbstractItemModel; + +class HistogramWidget : public QWidget +{ + Q_OBJECT +public: + explicit HistogramWidget(QWidget* parent = nullptr); + virtual ~HistogramWidget(); + + void setModel(QAbstractItemModel* model); + +private: + KChart::Chart* m_chart; + KChart::BarDiagram* m_total; + KChart::BarDiagram* m_detailed; +}; + +#endif // HISTOGRAMWIDGET_H + diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 3b8b2ef..a33625f 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -32,6 +32,7 @@ #include "parser.h" #include "chartmodel.h" #include "chartproxy.h" +#include "histogrammodel.h" using namespace std; @@ -57,6 +58,8 @@ MainWindow::MainWindow(QWidget* parent) m_ui->allocatedTab->setModel(allocatedModel); auto temporaryModel = new ChartModel(ChartModel::Temporary, this); m_ui->temporaryTab->setModel(temporaryModel); + auto sizeHistogramModel = new HistogramModel(this); + m_ui->sizesTab->setModel(sizeHistogramModel); connect(m_parser, &Parser::bottomUpDataAvailable, m_bottomUpModel, &TreeModel::resetData); @@ -70,6 +73,8 @@ MainWindow::MainWindow(QWidget* parent) allocationsModel, &ChartModel::resetData); connect(m_parser, &Parser::temporaryChartDataAvailable, temporaryModel, &ChartModel::resetData); + connect(m_parser, &Parser::sizeHistogramDataAvailable, + sizeHistogramModel, &HistogramModel::resetData); connect(m_parser, &Parser::summaryAvailable, m_ui->summary, &QLabel::setText); connect(m_parser, &Parser::topDownDataAvailable, diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui index cbbae1d..c58fa1f 100644 --- a/gui/mainwindow.ui +++ b/gui/mainwindow.ui @@ -242,6 +242,11 @@ Allocated + + + Sizes + + Flame Graph @@ -269,6 +274,12 @@
flamegraph.h
1 + + HistogramWidget + QWidget +
histogramwidget.h
+ 1 +
diff --git a/gui/parser.cpp b/gui/parser.cpp index 12fc978..52fdad9 100644 --- a/gui/parser.cpp +++ b/gui/parser.cpp @@ -29,6 +29,7 @@ #include "../accumulatedtracedata.h" #include +#include using namespace std; @@ -239,9 +240,15 @@ struct ParserData final : public AccumulatedTraceData temporaryChartData.rows << temporary; } - void handleAllocation() + void handleAllocation(const BigAllocationInfo& info, const AllocationIndex index) { maxConsumedSinceLastTimeStamp = max(maxConsumedSinceLastTimeStamp, leaked); + + if (index.index == allocationInfoCounter.size()) { + allocationInfoCounter.push_back({info, 1}); + } else { + ++allocationInfoCounter[index.index].allocations; + } } void handleDebuggee(const char* command) @@ -251,6 +258,17 @@ struct ParserData final : public AccumulatedTraceData string debuggee; + struct CountedAllocationInfo + { + BigAllocationInfo info; + uint64_t allocations; + bool operator<(const CountedAllocationInfo& rhs) const + { + return make_tuple(info.size, allocations) < make_tuple(rhs.info.size, rhs.allocations); + } + }; + vector allocationInfoCounter; + ChartData consumedChartData; ChartData allocationsChartData; ChartData allocatedChartData; @@ -390,6 +408,78 @@ QVector toTopDownData(const QVector& bottomUpData) return topRows; } +struct MergedHistogramColumnData +{ + std::shared_ptr location; + uint64_t allocations; + bool operator<(const std::shared_ptr& rhs) const + { + return location < rhs; + } +}; + +HistogramData buildSizeHistogram(ParserData& data) +{ + HistogramData ret; + if (data.allocationInfoCounter.empty()) { + return ret; + } + sort(data.allocationInfoCounter.begin(), data.allocationInfoCounter.end()); + const auto totalLabel = i18n("total"); + HistogramRow row; + const pair 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::max(), i18n("more than 1KB")} + }; + uint bucketIndex = 0; + row.size = buckets[bucketIndex].first; + row.sizeLabel = buckets[bucketIndex].second; + vector columnData; + columnData.reserve(128); + 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 < size_t(HistogramRow::NUM_COLUMNS - 1); ++i) { + const auto& column = columnData[i]; + row.columns[i + 1] = {column.allocations, column.location}; + } + }; + for (const auto& info : data.allocationInfoCounter) { + if (info.info.size > row.size) { + insertColumns(); + columnData.clear(); + ret << row; + ++bucketIndex; + row.size = buckets[bucketIndex].first; + row.sizeLabel = buckets[bucketIndex].second; + row.columns[0] = {info.allocations, {}}; + } else { + row.columns[0].allocations += info.allocations; + } + const auto ipIndex = data.findTrace(info.info.traceIndex).ipIndex; + const auto ip = data.findIp(ipIndex); + const auto location = data.stringCache.location(ip); + auto it = lower_bound(columnData.begin(), columnData.end(), location); + if (it == columnData.end() || it->location != location) { + columnData.insert(it, {location, info.allocations}); + } else { + it->allocations += info.allocations; + } + } + insertColumns(); + ret << row; + return ret; +} + } Parser::Parser(QObject* parent) @@ -411,11 +501,14 @@ void Parser::parse(const QString& path) // merge allocations before modifying the data again const auto mergedAllocations = mergeAllocations(*data); + // also calculate the size histogram + const auto sizeHistogram = buildSizeHistogram(*data); // now data can be modified again for the chart data evaluation auto parallel = new Collection; - *parallel << make_job([this, mergedAllocations]() { + *parallel << make_job([this, mergedAllocations, sizeHistogram]() { emit bottomUpDataAvailable(mergedAllocations); + emit sizeHistogramDataAvailable(sizeHistogram); const auto topDownData = toTopDownData(mergedAllocations); emit topDownDataAvailable(topDownData); }) << make_job([this, data, stdPath]() { diff --git a/gui/parser.h b/gui/parser.h index 88438cd..72e0e46 100644 --- a/gui/parser.h +++ b/gui/parser.h @@ -24,6 +24,7 @@ #include "treemodel.h" #include "chartmodel.h" +#include "histogrammodel.h" class Parser : public QObject { @@ -43,6 +44,7 @@ signals: void allocationsChartDataAvailable(const ChartData& data); void allocatedChartDataAvailable(const ChartData& data); void temporaryChartDataAvailable(const ChartData& data); + void sizeHistogramDataAvailable(const HistogramData& data); void finished(); }; diff --git a/heaptrack_print.cpp b/heaptrack_print.cpp index d544ccd..5e6d0e5 100644 --- a/heaptrack_print.cpp +++ b/heaptrack_print.cpp @@ -430,8 +430,12 @@ struct Printer final : public AccumulatedTraceData } } - void handleAllocation() override + void handleAllocation(const BigAllocationInfo& info, const AllocationIndex /*index*/) override { + if (printHistogram) { + ++sizeHistogram[info.size]; + } + if (leaked > lastMassifPeak && massifOut.is_open()) { massifAllocations = allocations; lastMassifPeak = leaked; @@ -453,10 +457,13 @@ struct Printer final : public AccumulatedTraceData } } + bool printHistogram = false; bool mergeBacktraces = true; vector mergedAllocations; + std::map sizeHistogram; + uint64_t massifSnapshotId = 0; uint64_t lastMassifPeak = 0; vector massifAllocations; -- 2.7.4