Prepare for addition of more visualizations.
authorMilian Wolff <mail@milianw.de>
Mon, 15 Jun 2015 22:14:09 +0000 (00:14 +0200)
committerMilian Wolff <mail@milianw.de>
Mon, 15 Jun 2015 22:14:09 +0000 (00:14 +0200)
Refactors loading of files into its own class and cleans up the
implementation of the (bottom-up) model.

12 files changed:
accumulatedtracedata.cpp
accumulatedtracedata.h
gui/CMakeLists.txt
gui/bottomupmodel.cpp [moved from gui/model.cpp with 57% similarity]
gui/bottomupmodel.h [moved from gui/model.h with 81% similarity]
gui/bottomupproxy.cpp [moved from gui/proxy.cpp with 66% similarity]
gui/bottomupproxy.h [moved from gui/proxy.h with 84% similarity]
gui/mainwindow.cpp
gui/mainwindow.h
gui/mainwindow.ui
gui/parser.cpp [new file with mode: 0644]
gui/parser.h [new file with mode: 0644]

index 1788aab..f0a9714 100644 (file)
@@ -69,19 +69,6 @@ void AccumulatedTraceData::clear()
     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()) {
index 1e8eaf2..51b0472 100644 (file)
@@ -132,9 +132,9 @@ struct AccumulatedTraceData
     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;
@@ -155,7 +155,6 @@ struct AccumulatedTraceData
     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
index f2af535..aa24101 100644 (file)
@@ -14,9 +14,10 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
 add_executable(heaptrack_gui
     gui.cpp
     mainwindow.cpp
-    model.cpp
+    bottomupmodel.cpp
+    bottomupproxy.cpp
     modeltest.cpp
-    proxy.cpp
+    parser.cpp
     ${UIFILES}
 )
 
similarity index 57%
rename from gui/model.cpp
rename to gui/bottomupmodel.cpp
index ef6446a..f1884f3 100644 (file)
  * 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 {};
@@ -226,7 +107,7 @@ QVariant Model::headerData(int section, Qt::Orientation orientation, int role) c
     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 {};
@@ -288,7 +169,7 @@ QVariant Model::data(const QModelIndex& index, int role) const
     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();
@@ -296,7 +177,7 @@ QModelIndex Model::index(int row, int column, const QModelIndex& parent) const
     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 {};
@@ -308,7 +189,7 @@ QModelIndex Model::parent(const QModelIndex& child) const
     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();
@@ -320,30 +201,19 @@ int Model::rowCount(const QModelIndex& parent) const
     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;
@@ -355,13 +225,13 @@ const RowData* Model::toRow(const QModelIndex& index) const
     }
 }
 
-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);
similarity index 81%
rename from gui/model.h
rename to gui/bottomupmodel.h
index f9de093..d8da238 100644 (file)
@@ -17,8 +17,8 @@
  * 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>
@@ -63,15 +63,15 @@ struct RowData
 };
 
 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,
@@ -92,18 +92,10 @@ public:
     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
@@ -111,8 +103,8 @@ private:
     /// @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
 
similarity index 66%
rename from gui/proxy.cpp
rename to gui/bottomupproxy.cpp
index 8c8cafb..c6794b1 100644 (file)
  * 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;
         }
similarity index 84%
rename from gui/proxy.h
rename to gui/bottomupproxy.h
index ebc3991..0b8ae63 100644 (file)
  * 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);
@@ -42,4 +42,4 @@ private:
     QString m_moduleFilter;
 };
 
-#endif // PROXY_H
+#endif //BOTTOMUP PROXY_H
index 688da1e..d6a1c1a 100644 (file)
 
 #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);
@@ -71,18 +80,12 @@ MainWindow::~MainWindow()
 {
 }
 
-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()
index 4250a88..b6e1b49 100644 (file)
@@ -26,7 +26,8 @@ namespace Ui {
 class MainWindow;
 }
 
-class Model;
+class BottomUpModel;
+class Parser;
 
 class MainWindow : public QMainWindow
 {
@@ -39,12 +40,10 @@ public slots:
     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
index 32107c6..0bcb26c 100644 (file)
          </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>
diff --git a/gui/parser.cpp b/gui/parser.cpp
new file mode 100644 (file)
index 0000000..541586e
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * 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();
+    });
+}
diff --git a/gui/parser.h b/gui/parser.h
new file mode 100644 (file)
index 0000000..79b4fe9
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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