activeAllocations.clear();
}
-void AccumulatedTraceData::handleTimeStamp(size_t /*newStamp*/, size_t /*oldStamp*/)
-{
-}
-
-void AccumulatedTraceData::handleAllocation()
-{
-}
-
-void AccumulatedTraceData::handleDebuggee(const char* command)
-{
- debuggee = command;
-}
-
const string& AccumulatedTraceData::stringify(const StringIndex stringId) const
{
if (!stringId || stringId.index > strings.size()) {
AccumulatedTraceData();
virtual ~AccumulatedTraceData() = default;
- virtual void handleTimeStamp(size_t newStamp, size_t oldStamp);
- virtual void handleAllocation();
- virtual void handleDebuggee(const char* command);
+ virtual void handleTimeStamp(size_t newStamp, size_t oldStamp) = 0;
+ virtual void handleAllocation() = 0;
+ virtual void handleDebuggee(const char* command) = 0;
void clear();
const std::string& stringify(const StringIndex stringId) const;
size_t peak = 0;
size_t leaked = 0;
size_t totalTime = 0;
- std::string debuggee;
// our indices are sequentially increasing thus a new allocation can only ever
// occur with an index larger than any other we encountered so far
add_executable(heaptrack_gui
gui.cpp
mainwindow.cpp
- model.cpp
+ bottomupmodel.cpp
+ bottomupproxy.cpp
modeltest.cpp
- proxy.cpp
+ parser.cpp
${UIFILES}
)
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#include "model.h"
+#include "bottomupmodel.h"
#include <QDebug>
#include <QTextStream>
#include <KFormat>
#include <KLocalizedString>
-#include <ThreadWeaver/ThreadWeaver>
-
-#include <sstream>
-#include <cmath>
-
-#include "../accumulatedtracedata.h"
-
-using namespace std;
namespace {
-QString generateSummary(const AccumulatedTraceData& data)
-{
- QString ret;
- KFormat format;
- QTextStream stream(&ret);
- const double totalTimeS = 0.001 * data.totalTime;
- stream << "<qt>"
- << i18n("<strong>debuggee</strong>: <code>%1</code>", QString::fromStdString(data.debuggee)) << "<br/>"
- << i18n("<strong>total runtime</strong>: %1s", totalTimeS) << "<br/>"
- << i18n("<strong>bytes allocated in total</strong> (ignoring deallocations): %1 (%2/s)",
- format.formatByteSize(data.totalAllocated, 2), format.formatByteSize(data.totalAllocated / totalTimeS)) << "<br/>"
- << i18n("<strong>calls to allocation functions</strong>: %1 (%2/s)",
- data.totalAllocations, quint64(data.totalAllocations / totalTimeS)) << "<br/>"
- << i18n("<strong>peak heap memory consumption</strong>: %1", format.formatByteSize(data.peak)) << "<br/>"
- << i18n("<strong>total memory leaked</strong>: %1", format.formatByteSize(data.leaked)) << "<br/>";
- stream << "</qt>";
- return ret;
-}
-int indexOf(const RowData* row, const QVector<RowData>& siblings)
+int indexOf(const RowData* row, const BottomUpData& siblings)
{
Q_ASSERT(siblings.data() <= row);
Q_ASSERT(siblings.data() + siblings.size() > row);
return row - siblings.data();
}
-const RowData* rowAt(const QVector<RowData>& rows, int row)
+const RowData* rowAt(const BottomUpData& rows, int row)
{
Q_ASSERT(rows.size() > row);
return rows.data() + row;
}
-struct StringCache
-{
- StringCache(const AccumulatedTraceData& data)
- {
- m_strings.resize(data.strings.size());
- transform(data.strings.begin(), data.strings.end(),
- m_strings.begin(), [] (const string& str) { return QString::fromStdString(str); });
- }
-
- QString func(const InstructionPointer& ip) const
- {
- if (ip.functionIndex) {
- // TODO: support removal of template arguments
- return stringify(ip.functionIndex);
- } else {
- return static_cast<QString>(QLatin1String("0x") + QString::number(ip.instructionPointer, 16));
- }
- }
-
- QString file(const InstructionPointer& ip) const
- {
- if (ip.fileIndex) {
- auto file = stringify(ip.fileIndex);
- return file + QLatin1Char(':') + QString::number(ip.line);
- } else {
- return {};
- }
- }
-
- QString module(const InstructionPointer& ip) const
- {
- return stringify(ip.moduleIndex);
- }
-
- QString stringify(const StringIndex index) const
- {
- if (!index || index.index > m_strings.size()) {
- return {};
- } else {
- return m_strings.at(index.index - 1);
- }
- }
-
- LocationData location(const InstructionPointer& ip) const
- {
- return {func(ip), file(ip), module(ip)};
- }
-
- vector<QString> m_strings;
-};
-
-void setParents(QVector<RowData>& children, const RowData* parent)
-{
- for (auto& row: children) {
- row.parent = parent;
- setParents(row.children, &row);
- }
}
-QVector<RowData> mergeAllocations(const AccumulatedTraceData& data)
-{
- QVector<RowData> topRows;
- StringCache strings(data);
- // merge allocations, leave parent pointers invalid (their location may change)
- for (const auto& allocation : data.allocations) {
- auto traceIndex = allocation.traceIndex;
- auto rows = &topRows;
- while (traceIndex) {
- const auto& trace = data.findTrace(traceIndex);
- const auto& ip = data.findIp(trace.ipIndex);
- // TODO: only store the IpIndex and use that
- auto location = strings.location(ip);
- auto it = lower_bound(rows->begin(), rows->end(), location);
- if (it != rows->end() && it->location == location) {
- it->allocated += allocation.allocated;
- it->allocations += allocation.allocations;
- it->leaked += allocation.leaked;
- it->peak = max(it->peak, static_cast<quint64>(allocation.peak));
- } else {
- it = rows->insert(it, {allocation.allocations, allocation.allocated, allocation.leaked, allocation.peak,
- location, nullptr, {}});
- }
- traceIndex = trace.parentIndex;
- rows = &it->children;
- }
- }
- // now set the parents, the data is constant from here on
- setParents(topRows, nullptr);
- return topRows;
-}
-
-}
-
-Model::Model(QObject* parent)
+BottomUpModel::BottomUpModel(QObject* parent)
: QAbstractItemModel(parent)
{
- qRegisterMetaType<QVector<RowData>>();
- connect(this, &Model::dataReadyBackground,
- this, &Model::dataReadyForeground);
+ qRegisterMetaType<BottomUpData>();
}
-Model::~Model()
+BottomUpModel::~BottomUpModel()
{
}
-QVariant Model::headerData(int section, Qt::Orientation orientation, int role) const
+QVariant BottomUpModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation != Qt::Horizontal || section < 0 || section >= NUM_COLUMNS) {
return {};
return {};
}
-QVariant Model::data(const QModelIndex& index, int role) const
+QVariant BottomUpModel::data(const QModelIndex& index, int role) const
{
if (index.row() < 0 || index.column() < 0 || index.column() > NUM_COLUMNS) {
return {};
return {};
}
-QModelIndex Model::index(int row, int column, const QModelIndex& parent) const
+QModelIndex BottomUpModel::index(int row, int column, const QModelIndex& parent) const
{
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))));
}
-QModelIndex Model::parent(const QModelIndex& child) const
+QModelIndex BottomUpModel::parent(const QModelIndex& child) const
{
if (!child.isValid()) {
return {};
return createIndex(rowOf(parent), 0, const_cast<void*>(reinterpret_cast<const void*>(parent->parent)));
}
-int Model::rowCount(const QModelIndex& parent) const
+int BottomUpModel::rowCount(const QModelIndex& parent) const
{
if (!parent.isValid()) {
return m_data.size();
return row->children.size();
}
-int Model::columnCount(const QModelIndex& /*parent*/) const
+int BottomUpModel::columnCount(const QModelIndex& /*parent*/) const
{
return NUM_COLUMNS;
}
-void Model::loadFile(const QString& path)
-{
- using namespace ThreadWeaver;
- stream() << make_job([=]() {
- AccumulatedTraceData data;
- data.read(path.toStdString());
- emit dataReadyBackground(mergeAllocations(data), generateSummary(data));
- });
-}
-
-void Model::dataReadyForeground(const QVector<RowData>& data, const QString& summary)
+void BottomUpModel::resetData(const BottomUpData& data)
{
beginResetModel();
m_data = data;
endResetModel();
- emit dataReady(summary);
}
-const RowData* Model::toRow(const QModelIndex& index) const
+const RowData* BottomUpModel::toRow(const QModelIndex& index) const
{
if (!index.isValid()) {
return nullptr;
}
}
-const RowData* Model::toParentRow(const QModelIndex& index) const
+const RowData* BottomUpModel::toParentRow(const QModelIndex& index) const
{
Q_ASSERT(index.isValid());
return static_cast<const RowData*>(index.internalPointer());
}
-int Model::rowOf(const RowData* row) const
+int BottomUpModel::rowOf(const RowData* row) const
{
if (auto parent = row->parent) {
return indexOf(row, parent->children);
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef MODEL_H
-#define MODEL_H
+#ifndef BOTTOMUPMODEL_H
+#define BOTTOMUPMODEL_H
#include <QAbstractItemModel>
#include <QVector>
};
Q_DECLARE_TYPEINFO(RowData, Q_MOVABLE_TYPE);
+using BottomUpData = QVector<RowData>;
+Q_DECLARE_METATYPE(BottomUpData)
-Q_DECLARE_METATYPE(QVector<RowData>)
-
-class Model : public QAbstractItemModel
+class BottomUpModel : public QAbstractItemModel
{
Q_OBJECT
public:
- Model(QObject* parent);
- virtual ~Model();
+ BottomUpModel(QObject* parent);
+ virtual ~BottomUpModel();
enum Columns {
AllocationsColumn,
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
- void loadFile(const QString& path);
-
-signals:
- void dataReady(const QString& summary);
-
- /// emitted from the background for message passing into the foreground
- void dataReadyBackground(const QVector<RowData>& data, const QString& summary);
+public slots:
+ void resetData(const BottomUpData& data);
private:
- /// called in the main thread to actually reset the data of this model and notify views
- void dataReadyForeground(const QVector<RowData>& data, const QString& summary);
-
/// @return the row resembled by @p index
const RowData* toRow(const QModelIndex& index) const;
/// @return the parent row containing @p index
/// @return the row number of @p row in its parent
int rowOf(const RowData* row) const;
- QVector<RowData> m_data;
+ BottomUpData m_data;
};
-#endif // MODEL_H
+#endif // BOTTOMUPMODEL_H
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#include "proxy.h"
+#include "bottomupproxy.h"
-#include "model.h"
+#include "bottomupmodel.h"
-Proxy::Proxy(QObject* parent)
+BottomUpProxy::BottomUpProxy(QObject* parent)
: KRecursiveFilterProxyModel(parent)
{
}
-Proxy::~Proxy() = default;
+BottomUpProxy::~BottomUpProxy() = default;
-void Proxy::setFunctionFilter(const QString& functionFilter)
+void BottomUpProxy::setFunctionFilter(const QString& functionFilter)
{
m_functionFilter = functionFilter;
invalidate();
}
-void Proxy::setFileFilter(const QString& fileFilter)
+void BottomUpProxy::setFileFilter(const QString& fileFilter)
{
m_fileFilter = fileFilter;
invalidate();
}
-void Proxy::setModuleFilter(const QString& moduleFilter)
+void BottomUpProxy::setModuleFilter(const QString& moduleFilter)
{
m_moduleFilter = moduleFilter;
invalidate();
}
-bool Proxy::acceptRow(int sourceRow, const QModelIndex& sourceParent) const
+bool BottomUpProxy::acceptRow(int sourceRow, const QModelIndex& sourceParent) const
{
auto source = sourceModel();
if (!source) {
return false;
}
if (!m_functionFilter.isEmpty()) {
- const auto& function = source->index(sourceRow, Model::FunctionColumn, sourceParent).data().toString();
+ const auto& function = source->index(sourceRow, BottomUpModel::FunctionColumn, sourceParent).data().toString();
if (!function.contains(m_functionFilter, Qt::CaseInsensitive)) {
return false;
}
}
if (!m_fileFilter.isEmpty()) {
- const auto& file = source->index(sourceRow, Model::FileColumn, sourceParent).data().toString();
+ const auto& file = source->index(sourceRow, BottomUpModel::FileColumn, sourceParent).data().toString();
if (!file.contains(m_fileFilter, Qt::CaseInsensitive)) {
return false;
}
}
if (!m_moduleFilter.isEmpty()) {
- const auto& module = source->index(sourceRow, Model::ModuleColumn, sourceParent).data().toString();
+ const auto& module = source->index(sourceRow, BottomUpModel::ModuleColumn, sourceParent).data().toString();
if (!module.contains(m_moduleFilter, Qt::CaseInsensitive)) {
return false;
}
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-#ifndef PROXY_H
-#define PROXY_H
+#ifndef BOTTOMUPPROXY_H
+#define BOTTOMUPPROXY_H
#include <KRecursiveFilterProxyModel>
-class Proxy final : public KRecursiveFilterProxyModel
+class BottomUpProxy final : public KRecursiveFilterProxyModel
{
Q_OBJECT
public:
- explicit Proxy(QObject* parent);
- virtual ~Proxy();
+ explicit BottomUpProxy(QObject* parent = nullptr);
+ virtual ~BottomUpProxy();
public slots:
void setFunctionFilter(const QString& functionFilter);
QString m_moduleFilter;
};
-#endif // PROXY_H
+#endif //BOTTOMUP PROXY_H
#include <QFileDialog>
-#include "model.h"
-#include "proxy.h"
+#include "bottomupmodel.h"
+#include "bottomupproxy.h"
+#include "parser.h"
using namespace std;
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
, m_ui(new Ui::MainWindow)
- , m_model(new Model(this))
+ , m_bottomUpModel(new BottomUpModel(this))
+ , m_parser(new Parser(this))
{
m_ui->setupUi(this);
- auto proxy = new Proxy(m_model);
- proxy->setSourceModel(m_model);
- m_ui->results->setModel(proxy);
+
+ // TODO: renable eventually
+ m_ui->tabWidget->removeTab(1);
m_ui->pages->setCurrentWidget(m_ui->openPage);
// TODO: proper progress report
m_ui->loadingProgress->setMinimum(0);
m_ui->loadingProgress->setMaximum(0);
- connect(m_model, &Model::dataReady,
- this, &MainWindow::dataReady);
+ connect(m_parser, &Parser::bottomUpDataAvailable,
+ m_bottomUpModel, &BottomUpModel::resetData);
+ connect(m_parser, &Parser::summaryAvailable,
+ m_ui->summary, &QLabel::setText);
+ connect(m_parser, &Parser::finished,
+ this, [&] { m_ui->pages->setCurrentWidget(m_ui->resultsPage); });
- m_ui->results->hideColumn(Model::FunctionColumn);
- m_ui->results->hideColumn(Model::FileColumn);
- m_ui->results->hideColumn(Model::ModuleColumn);
+ auto bottomUpProxy = new BottomUpProxy(m_bottomUpModel);
+ bottomUpProxy->setSourceModel(m_bottomUpModel);
+ m_ui->results->setModel(bottomUpProxy);
+ m_ui->results->hideColumn(BottomUpModel::FunctionColumn);
+ m_ui->results->hideColumn(BottomUpModel::FileColumn);
+ m_ui->results->hideColumn(BottomUpModel::ModuleColumn);
connect(m_ui->filterFunction, &QLineEdit::textChanged,
- proxy, &Proxy::setFunctionFilter);
+ bottomUpProxy, &BottomUpProxy::setFunctionFilter);
connect(m_ui->filterFile, &QLineEdit::textChanged,
- proxy, &Proxy::setFileFilter);
+ bottomUpProxy, &BottomUpProxy::setFileFilter);
connect(m_ui->filterModule, &QLineEdit::textChanged,
- proxy, &Proxy::setModuleFilter);
+ bottomUpProxy, &BottomUpProxy::setModuleFilter);
auto openFile = KStandardAction::open(this, SLOT(openFile()), this);
m_ui->openFile->setDefaultAction(openFile);
{
}
-void MainWindow::dataReady(const QString& summary)
-{
- m_ui->summary->setText(summary);
- m_ui->pages->setCurrentWidget(m_ui->resultsPage);
-}
-
void MainWindow::loadFile(const QString& file)
{
m_ui->loadingLabel->setText(i18n("Loading file %1, please wait...", file));
setWindowTitle(i18nc("%1: file name that is open", "Heaptrack - %1", file));
m_ui->pages->setCurrentWidget(m_ui->loadingPage);
- m_model->loadFile(file);
+ m_parser->parse(file);
}
void MainWindow::openFile()
class MainWindow;
}
-class Model;
+class BottomUpModel;
+class Parser;
class MainWindow : public QMainWindow
{
void loadFile(const QString& path);
void openFile();
-private slots:
- void dataReady(const QString& summary);
-
private:
QScopedPointer<Ui::MainWindow> m_ui;
- Model* m_model;
+ BottomUpModel* m_bottomUpModel;
+ Parser* m_parser;
};
#endif // MAINWINDOW_H
</widget>
</item>
<item>
- <widget class="QWidget" name="widget" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout">
- <property name="spacing">
- <number>0</number>
- </property>
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLineEdit" name="filterFunction">
- <property name="placeholderText">
- <string>filter by function...</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="filterFile">
- <property name="placeholderText">
- <string>filter by file...</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="filterModule">
- <property name="placeholderText">
- <string>filter by module...</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QTreeView" name="results">
- <property name="alternatingRowColors">
- <bool>true</bool>
- </property>
- <property name="rootIsDecorated">
- <bool>true</bool>
- </property>
- <property name="uniformRowHeights">
- <bool>true</bool>
- </property>
- <property name="sortingEnabled">
- <bool>true</bool>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
</property>
+ <widget class="QWidget" name="bottomUpTab">
+ <attribute name="title">
+ <string>Bottom-Up</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="filterFunction">
+ <property name="placeholderText">
+ <string>filter by function...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="filterFile">
+ <property name="placeholderText">
+ <string>filter by file...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="filterModule">
+ <property name="placeholderText">
+ <string>filter by module...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="results">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>true</bool>
+ </property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ <zorder>results</zorder>
+ <zorder>filterFile</zorder>
+ <zorder>filterFunction</zorder>
+ <zorder>filterModule</zorder>
+ <zorder>widget</zorder>
+ <zorder>results</zorder>
+ </widget>
+ <widget class="QWidget" name="memoryConsumptionTab">
+ <attribute name="title">
+ <string>Memory Consumption</string>
+ </attribute>
+ </widget>
</widget>
</item>
</layout>
--- /dev/null
+/*
+ * Copyright 2015 Milian Wolff <mail@milianw.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "parser.h"
+
+#include <ThreadWeaver/ThreadWeaver>
+#include <KFormat>
+#include <KLocalizedString>
+
+#include <QTextStream>
+
+#include "../accumulatedtracedata.h"
+
+#include <vector>
+
+using namespace std;
+
+namespace {
+struct ParserData final : public AccumulatedTraceData
+{
+ ParserData()
+ {
+ }
+
+ void handleTimeStamp(size_t /*newStamp*/, size_t /*oldStamp*/)
+ {
+ }
+
+ void handleAllocation()
+ {
+ }
+
+ void handleDebuggee(const char* command)
+ {
+ debuggee = command;
+ }
+
+ string debuggee;
+};
+
+QString generateSummary(const ParserData& data)
+{
+ QString ret;
+ KFormat format;
+ QTextStream stream(&ret);
+ const double totalTimeS = 0.001 * data.totalTime;
+ stream << "<qt>"
+ << i18n("<strong>debuggee</strong>: <code>%1</code>", QString::fromStdString(data.debuggee)) << "<br/>"
+ << i18n("<strong>total runtime</strong>: %1s", totalTimeS) << "<br/>"
+ << i18n("<strong>bytes allocated in total</strong> (ignoring deallocations): %1 (%2/s)",
+ format.formatByteSize(data.totalAllocated, 2), format.formatByteSize(data.totalAllocated / totalTimeS)) << "<br/>"
+ << i18n("<strong>calls to allocation functions</strong>: %1 (%2/s)",
+ data.totalAllocations, quint64(data.totalAllocations / totalTimeS)) << "<br/>"
+ << i18n("<strong>peak heap memory consumption</strong>: %1", format.formatByteSize(data.peak)) << "<br/>"
+ << i18n("<strong>total memory leaked</strong>: %1", format.formatByteSize(data.leaked)) << "<br/>";
+ stream << "</qt>";
+ return ret;
+}
+
+struct StringCache
+{
+ StringCache(const AccumulatedTraceData& data)
+ {
+ m_strings.resize(data.strings.size());
+ transform(data.strings.begin(), data.strings.end(),
+ m_strings.begin(), [] (const string& str) { return QString::fromStdString(str); });
+ }
+
+ QString func(const InstructionPointer& ip) const
+ {
+ if (ip.functionIndex) {
+ // TODO: support removal of template arguments
+ return stringify(ip.functionIndex);
+ } else {
+ return static_cast<QString>(QLatin1String("0x") + QString::number(ip.instructionPointer, 16));
+ }
+ }
+
+ QString file(const InstructionPointer& ip) const
+ {
+ if (ip.fileIndex) {
+ auto file = stringify(ip.fileIndex);
+ return file + QLatin1Char(':') + QString::number(ip.line);
+ } else {
+ return {};
+ }
+ }
+
+ QString module(const InstructionPointer& ip) const
+ {
+ return stringify(ip.moduleIndex);
+ }
+
+ QString stringify(const StringIndex index) const
+ {
+ if (!index || index.index > m_strings.size()) {
+ return {};
+ } else {
+ return m_strings.at(index.index - 1);
+ }
+ }
+
+ LocationData location(const InstructionPointer& ip) const
+ {
+ return {func(ip), file(ip), module(ip)};
+ }
+
+ vector<QString> m_strings;
+};
+
+void setParents(QVector<RowData>& children, const RowData* parent)
+{
+ for (auto& row: children) {
+ row.parent = parent;
+ setParents(row.children, &row);
+ }
+}
+
+QVector<RowData> mergeAllocations(const AccumulatedTraceData& data)
+{
+ QVector<RowData> topRows;
+ StringCache strings(data);
+ // merge allocations, leave parent pointers invalid (their location may change)
+ for (const auto& allocation : data.allocations) {
+ auto traceIndex = allocation.traceIndex;
+ auto rows = &topRows;
+ while (traceIndex) {
+ const auto& trace = data.findTrace(traceIndex);
+ const auto& ip = data.findIp(trace.ipIndex);
+ // TODO: only store the IpIndex and use that
+ auto location = strings.location(ip);
+ auto it = lower_bound(rows->begin(), rows->end(), location);
+ if (it != rows->end() && it->location == location) {
+ it->allocated += allocation.allocated;
+ it->allocations += allocation.allocations;
+ it->leaked += allocation.leaked;
+ it->peak = max(it->peak, static_cast<quint64>(allocation.peak));
+ } else {
+ it = rows->insert(it, {allocation.allocations, allocation.allocated, allocation.leaked, allocation.peak,
+ location, nullptr, {}});
+ }
+ traceIndex = trace.parentIndex;
+ rows = &it->children;
+ }
+ }
+ // now set the parents, the data is constant from here on
+ setParents(topRows, nullptr);
+ return topRows;
+}
+}
+
+Parser::Parser(QObject* parent)
+ : QObject(parent)
+{}
+
+Parser::~Parser() = default;
+
+void Parser::parse(const QString& path)
+{
+ using namespace ThreadWeaver;
+ stream() << make_job([=]() {
+ ParserData data;
+ data.read(path.toStdString());
+ emit summaryAvailable(generateSummary(data));
+ emit bottomUpDataAvailable(mergeAllocations(data));
+ emit finished();
+ });
+}
--- /dev/null
+/*
+ * Copyright 2015 Milian Wolff <mail@milianw.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef PARSER_H
+#define PARSER_H
+
+#include <QObject>
+
+#include "bottomupmodel.h"
+
+class Parser : public QObject
+{
+ Q_OBJECT
+public:
+ explicit Parser(QObject* parent = nullptr);
+ virtual ~Parser();
+
+public slots:
+ void parse(const QString& path);
+
+signals:
+ void summaryAvailable(const QString& summary);
+ void bottomUpDataAvailable(const BottomUpData& data);
+ void finished();
+};
+
+#endif // PARSER_H