Add caller/callee table view of data.
authorMilian Wolff <mail@milianw.de>
Thu, 24 Mar 2016 18:21:54 +0000 (19:21 +0100)
committerMilian Wolff <mail@milianw.de>
Thu, 24 Mar 2016 18:21:54 +0000 (19:21 +0100)
12 files changed:
gui/CMakeLists.txt
gui/callercalleemodel.cpp [new file with mode: 0644]
gui/callercalleemodel.h [new file with mode: 0644]
gui/locationdata.h [new file with mode: 0644]
gui/mainwindow.cpp
gui/mainwindow.ui
gui/parser.cpp
gui/parser.h
gui/summarydata.h [new file with mode: 0644]
gui/treemodel.h
gui/treeproxy.cpp
gui/treeproxy.h

index 37c336f..47c60b7 100644 (file)
@@ -27,6 +27,7 @@ add_executable(heaptrack_gui
     flamegraph.cpp
     stacksmodel.cpp
     topproxy.cpp
+    callercalleemodel.cpp
     ${UIFILES}
 )
 
diff --git a/gui/callercalleemodel.cpp b/gui/callercalleemodel.cpp
new file mode 100644 (file)
index 0000000..3dc8f60
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2016 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 "callercalleemodel.h"
+
+#include <KLocalizedString>
+
+#include <QTextStream>
+
+namespace{
+/// TODO: share code
+QString basename(const QString& path)
+{
+    int idx = path.lastIndexOf(QLatin1Char('/'));
+    return path.mid(idx + 1);
+}
+}
+
+CallerCalleeModel::CallerCalleeModel(QObject* parent)
+    : QAbstractTableModel(parent)
+{
+    qRegisterMetaType<CallerCalleeRows>();
+}
+
+CallerCalleeModel::~CallerCalleeModel() = default;
+
+QVariant CallerCalleeModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+    if (orientation != Qt::Horizontal || section < 0 || section >= NUM_COLUMNS) {
+        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)
+        {
+            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;
+        }
+    } 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;
+        }
+    }
+    return {};
+
+}
+
+QVariant CallerCalleeModel::data(const QModelIndex& index, int role) const
+{
+    if (!hasIndex(index.row(), index.column(), index.parent())) {
+        return {};
+    }
+
+    const auto& row = (role == MaxCostRole) ? m_maxCost : m_rows.at(index.row());
+
+    if (role == Qt::DisplayRole || role == SortRole || role == MaxCostRole) {
+        switch (static_cast<Columns>(index.column())) {
+        case SelfAllocatedColumn:
+            if (role == SortRole || role == MaxCostRole) {
+                return static_cast<quint64>(row.selfCost.allocated);
+            } else {
+                return m_format.formatByteSize(row.selfCost.allocated);
+            }
+        case SelfAllocationsColumn:
+            return static_cast<quint64>(row.selfCost.allocations);
+        case SelfTemporaryColumn:
+            return static_cast<quint64>(row.selfCost.temporary);
+        case SelfPeakColumn:
+            if (role == SortRole || role == MaxCostRole) {
+                return static_cast<quint64>(row.selfCost.peak);
+            } else {
+                return m_format.formatByteSize(row.selfCost.peak);
+            }
+        case SelfLeakedColumn:
+            if (role == SortRole || role == MaxCostRole) {
+                return static_cast<quint64>(row.selfCost.leaked);
+            } else {
+                return m_format.formatByteSize(row.selfCost.leaked);
+            }
+        case InclusiveAllocatedColumn:
+            if (role == SortRole || role == MaxCostRole) {
+                return static_cast<quint64>(row.inclusiveCost.allocated);
+            } else {
+                return m_format.formatByteSize(row.inclusiveCost.allocated);
+            }
+        case InclusiveAllocationsColumn:
+            return static_cast<quint64>(row.inclusiveCost.allocations);
+        case InclusiveTemporaryColumn:
+            return static_cast<quint64>(row.inclusiveCost.temporary);
+        case InclusivePeakColumn:
+            if (role == SortRole || role == MaxCostRole) {
+                return static_cast<quint64>(row.inclusiveCost.peak);
+            } else {
+                return m_format.formatByteSize(row.inclusiveCost.peak);
+            }
+        case InclusiveLeakedColumn:
+            if (role == SortRole || role == MaxCostRole) {
+                return static_cast<quint64>(row.inclusiveCost.leaked);
+            } else {
+                return m_format.formatByteSize(row.inclusiveCost.leaked);
+            }
+        case FunctionColumn:
+            return row.location->function;
+        case ModuleColumn:
+            return row.location->module;
+        case FileColumn:
+            return row.location->file;
+        case LineColumn:
+            return row.location->line;
+        case LocationColumn:
+            if (row.location->file.isEmpty()) {
+                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));
+            }
+        case NUM_COLUMNS:
+            break;
+        }
+    } else if (role == Qt::ToolTipRole) {
+        QString tooltip;
+        QTextStream stream(&tooltip);
+        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,
+                            row.location->file, row.location->line, row.location->module);
+        } else {
+            stream << i18nc("1: function, 2: module", "%1\n  in %2",
+                            row.location->function, row.location->module);
+        }
+        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 / 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 / row.selfCost.allocations) / 100.f,
+                       m_format.formatByteSize(row.selfCost.peak), m_format.formatByteSize(row.selfCost.leaked));
+        stream << '\n';
+        stream << "</pre></qt>";
+        return tooltip;
+    }
+    return {};
+}
+
+int CallerCalleeModel::columnCount(const QModelIndex& parent) const
+{
+    return parent.isValid() ? 0 : NUM_COLUMNS;
+}
+
+int CallerCalleeModel::rowCount(const QModelIndex& parent) const
+{
+    return parent.isValid() ? 0 : m_rows.size();
+}
+
+void CallerCalleeModel::resetData(const QVector<CallerCalleeData>& rows)
+{
+    beginResetModel();
+    m_rows = rows;
+    endResetModel();
+}
+
+void CallerCalleeModel::setSummary(const SummaryData& data)
+{
+    beginResetModel();
+    m_maxCost.inclusiveCost = data.cost;
+    m_maxCost.selfCost = data.cost;
+    endResetModel();
+}
diff --git a/gui/callercalleemodel.h b/gui/callercalleemodel.h
new file mode 100644 (file)
index 0000000..993327a
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016 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 CALLERCALLEEMODEL_H
+#define CALLERCALLEEMODEL_H
+
+#include <QVector>
+#include <QAbstractTableModel>
+
+#include <KFormat>
+
+#include "../allocationdata.h"
+#include "locationdata.h"
+#include "summarydata.h"
+
+struct CallerCalleeData
+{
+    AllocationData inclusiveCost;
+    AllocationData selfCost;
+    std::shared_ptr<LocationData> location;
+};
+Q_DECLARE_TYPEINFO(CallerCalleeData, Q_MOVABLE_TYPE);
+
+using CallerCalleeRows = QVector<CallerCalleeData>;
+Q_DECLARE_METATYPE(CallerCalleeRows);
+
+class CallerCalleeModel : public QAbstractTableModel
+{
+    Q_OBJECT
+public:
+    explicit CallerCalleeModel(QObject* parent = nullptr);
+    ~CallerCalleeModel();
+
+    enum Columns {
+        InclusiveAllocationsColumn,
+        InclusiveTemporaryColumn,
+        InclusivePeakColumn,
+        InclusiveLeakedColumn,
+        InclusiveAllocatedColumn,
+        SelfAllocationsColumn,
+        SelfTemporaryColumn,
+        SelfPeakColumn,
+        SelfLeakedColumn,
+        SelfAllocatedColumn,
+        FunctionColumn,
+        FileColumn,
+        LineColumn,
+        ModuleColumn,
+        LocationColumn,
+        NUM_COLUMNS
+    };
+
+    enum Roles {
+        SortRole = Qt::UserRole,
+        MaxCostRole = Qt::UserRole + 1
+    };
+
+    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 columnCount(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;
+    KFormat m_format;
+};
+
+#endif // CALLERCALLEEMODEL_H
diff --git a/gui/locationdata.h b/gui/locationdata.h
new file mode 100644 (file)
index 0000000..385e416
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016 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 LOCATIONDATA_H
+#define LOCATIONDATA_H
+
+#include <QString>
+
+#include <memory>
+
+struct LocationData
+{
+    QString function;
+    QString file;
+    QString module;
+    int line;
+
+    bool operator==(const LocationData& rhs) const
+    {
+        return function == rhs.function
+            && file == rhs.file
+            && module == rhs.module
+            && line == rhs.line;
+    }
+
+    bool operator<(const LocationData& rhs) const
+    {
+        int i = function.compare(rhs.function);
+        if (!i) {
+            i = file.compare(rhs.file);
+        }
+        if (!i) {
+            i = line < rhs.line ? -1 : (line > rhs.line);
+        }
+        if (!i) {
+            i = module.compare(rhs.module);
+        }
+        return i < 0;
+    }
+};
+Q_DECLARE_TYPEINFO(LocationData, Q_MOVABLE_TYPE);
+
+inline bool operator<(const std::shared_ptr<LocationData>& lhs, const LocationData& rhs)
+{
+    return *lhs < rhs;
+}
+
+#endif // LOCATIONDATA_H
index e850faf..22f799a 100644 (file)
@@ -39,6 +39,7 @@
 #include "chartproxy.h"
 #include "histogrammodel.h"
 #include "stacksmodel.h"
+#include "callercalleemodel.h"
 
 using namespace std;
 
@@ -86,6 +87,7 @@ MainWindow::MainWindow(QWidget* parent)
 
     auto bottomUpModel = new TreeModel(this);
     auto topDownModel = new TreeModel(this);
+    auto callerCalleeModel = new CallerCalleeModel(this);
 
     auto consumedModel = new ChartModel(ChartModel::Consumed, this);
     m_ui->consumedTab->setModel(consumedModel);
@@ -98,6 +100,7 @@ MainWindow::MainWindow(QWidget* parent)
     auto sizeHistogramModel = new HistogramModel(this);
     m_ui->sizesTab->setModel(sizeHistogramModel);
 
+    m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->callerCalleeTab), false);
     m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->consumedTab), false);
     m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->allocationsTab), false);
     m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->allocatedTab), false);
@@ -115,6 +118,11 @@ MainWindow::MainWindow(QWidget* parent)
         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);
@@ -148,9 +156,10 @@ MainWindow::MainWindow(QWidget* parent)
                 m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->sizesTab), true);
             });
     connect(m_parser, &Parser::summaryAvailable,
-            this, [this, bottomUpModel, topDownModel] (const SummaryData& data) {
+            this, [=] (const SummaryData& data) {
                 bottomUpModel->setSummary(data);
                 topDownModel->setSummary(data);
+                callerCalleeModel->setSummary(data);
                 KFormat format;
                 QString textLeft;
                 QString textCenter;
@@ -208,7 +217,10 @@ MainWindow::MainWindow(QWidget* parent)
     });
     m_ui->messages->hide();
 
-    auto bottomUpProxy = new TreeProxy(bottomUpModel);
+    auto bottomUpProxy = new TreeProxy(TreeModel::FunctionColumn,
+                                       TreeModel::FileColumn,
+                                       TreeModel::ModuleColumn,
+                                       bottomUpModel);
     bottomUpProxy->setSourceModel(bottomUpModel);
     bottomUpProxy->setSortRole(TreeModel::SortRole);
     m_ui->bottomUpResults->setModel(bottomUpProxy);
@@ -229,7 +241,10 @@ MainWindow::MainWindow(QWidget* parent)
     connect(m_ui->bottomUpFilterModule, &QLineEdit::textChanged,
             bottomUpProxy, &TreeProxy::setModuleFilter);
 
-    auto topDownProxy = new TreeProxy(topDownModel);
+    auto topDownProxy = new TreeProxy(TreeModel::FunctionColumn,
+                                      TreeModel::FileColumn,
+                                      TreeModel::ModuleColumn,
+                                      topDownModel);
     topDownProxy->setSourceModel(topDownModel);
     topDownProxy->setSortRole(TreeModel::SortRole);
     m_ui->topDownResults->setModel(topDownProxy);
@@ -249,6 +264,34 @@ MainWindow::MainWindow(QWidget* parent)
     connect(m_ui->topDownFilterModule, &QLineEdit::textChanged,
             bottomUpProxy, &TreeProxy::setModuleFilter);
 
+    auto callerCalleeProxy = new TreeProxy(CallerCalleeModel::FunctionColumn,
+                                           CallerCalleeModel::FileColumn,
+                                           CallerCalleeModel::ModuleColumn,
+                                           callerCalleeModel);
+    callerCalleeProxy->setSourceModel(callerCalleeModel);
+    callerCalleeProxy->setSortRole(CallerCalleeModel::SortRole);
+    m_ui->callerCalleeResults->setModel(callerCalleeProxy);
+    m_ui->callerCalleeResults->setItemDelegateForColumn(CallerCalleeModel::SelfPeakColumn, costDelegate);
+    m_ui->callerCalleeResults->setItemDelegateForColumn(CallerCalleeModel::SelfAllocatedColumn, costDelegate);
+    m_ui->callerCalleeResults->setItemDelegateForColumn(CallerCalleeModel::SelfLeakedColumn, costDelegate);
+    m_ui->callerCalleeResults->setItemDelegateForColumn(CallerCalleeModel::SelfAllocationsColumn, costDelegate);
+    m_ui->callerCalleeResults->setItemDelegateForColumn(CallerCalleeModel::SelfTemporaryColumn, costDelegate);
+    m_ui->callerCalleeResults->setItemDelegateForColumn(CallerCalleeModel::InclusivePeakColumn, costDelegate);
+    m_ui->callerCalleeResults->setItemDelegateForColumn(CallerCalleeModel::InclusiveAllocatedColumn, costDelegate);
+    m_ui->callerCalleeResults->setItemDelegateForColumn(CallerCalleeModel::InclusiveLeakedColumn, costDelegate);
+    m_ui->callerCalleeResults->setItemDelegateForColumn(CallerCalleeModel::InclusiveAllocationsColumn, costDelegate);
+    m_ui->callerCalleeResults->setItemDelegateForColumn(CallerCalleeModel::InclusiveTemporaryColumn, costDelegate);
+    m_ui->callerCalleeResults->hideColumn(CallerCalleeModel::FunctionColumn);
+    m_ui->callerCalleeResults->hideColumn(CallerCalleeModel::FileColumn);
+    m_ui->callerCalleeResults->hideColumn(CallerCalleeModel::LineColumn);
+    m_ui->callerCalleeResults->hideColumn(CallerCalleeModel::ModuleColumn);
+    connect(m_ui->callerCalleeFilterFunction, &QLineEdit::textChanged,
+            bottomUpProxy, &TreeProxy::setFunctionFilter);
+    connect(m_ui->callerCalleeFilterFile, &QLineEdit::textChanged,
+            bottomUpProxy, &TreeProxy::setFileFilter);
+    connect(m_ui->callerCalleeFilterModule, &QLineEdit::textChanged,
+            bottomUpProxy, &TreeProxy::setModuleFilter);
+
     auto openFile = KStandardAction::open(this, SLOT(openFile()), this);
     m_ui->openFile->setDefaultAction(openFile);
 
index b8910f0..fb00ff3 100644 (file)
             </item>
            </layout>
           </widget>
+          <widget class="QWidget" name="callerCalleeTab">
+           <attribute name="title">
+            <string>Caller / Callee</string>
+           </attribute>
+           <layout class="QVBoxLayout" name="verticalLayout_13">
+            <item>
+             <widget class="QWidget" name="widget_11" native="true">
+              <layout class="QHBoxLayout" name="horizontalLayout_7">
+               <property name="spacing">
+                <number>6</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="callerCalleeFilterFunction">
+                 <property name="placeholderText">
+                  <string>filter by function...</string>
+                 </property>
+                </widget>
+               </item>
+               <item>
+                <widget class="QLineEdit" name="callerCalleeFilterFile">
+                 <property name="placeholderText">
+                  <string>filter by file...</string>
+                 </property>
+                </widget>
+               </item>
+               <item>
+                <widget class="QLineEdit" name="callerCalleeFilterModule">
+                 <property name="placeholderText">
+                  <string>filter by module...</string>
+                 </property>
+                </widget>
+               </item>
+              </layout>
+             </widget>
+            </item>
+            <item>
+             <widget class="QTreeView" name="callerCalleeResults">
+              <property name="rootIsDecorated">
+               <bool>false</bool>
+              </property>
+              <property name="uniformRowHeights">
+               <bool>true</bool>
+              </property>
+              <property name="sortingEnabled">
+               <bool>true</bool>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </widget>
           <widget class="QWidget" name="topDownTab">
            <attribute name="title">
             <string>Top-Down</string>
index 8c937a6..11b9a63 100644 (file)
@@ -300,9 +300,11 @@ void setParents(QVector<RowData>& children, const RowData* parent)
     }
 }
 
-QVector<RowData> mergeAllocations(const ParserData& data)
+QPair<TreeData, CallerCalleeRows> mergeAllocations(const ParserData& data)
 {
-    QVector<RowData> topRows;
+    TreeData topRows;
+    CallerCalleeRows callerCalleeRows;
+    callerCalleeRows.resize(data.instructionPointers.size());
     // merge allocations, leave parent pointers invalid (their location may change)
     for (const auto& allocation : data.allocations) {
         auto traceIndex = allocation.traceIndex;
@@ -311,6 +313,14 @@ QVector<RowData> mergeAllocations(const ParserData& data)
             const auto& trace = data.findTrace(traceIndex);
             const auto& ip = data.findIp(trace.ipIndex);
             auto location = data.stringCache.location(ip);
+
+            auto& callerCallee = callerCalleeRows[trace.ipIndex.index - 1];
+            callerCallee.inclusiveCost += allocation;
+            if (traceIndex == allocation.traceIndex) {
+                callerCallee.selfCost += allocation;
+            }
+            callerCallee.location = location;
+
             auto it = lower_bound(rows->begin(), rows->end(), location);
             if (it != rows->end() && it->location == location) {
                 it->cost += allocation;
@@ -326,7 +336,10 @@ QVector<RowData> mergeAllocations(const ParserData& data)
     }
     // now set the parents, the data is constant from here on
     setParents(topRows, nullptr);
-    return topRows;
+    callerCalleeRows.erase(std::remove_if(callerCalleeRows.begin(), callerCalleeRows.end(), [] (const CallerCalleeData& data) {
+        return !data.location;
+    }), callerCalleeRows.end());
+    return qMakePair(topRows, callerCalleeRows);
 }
 
 RowData* findByLocation(const RowData& row, QVector<RowData>* data)
@@ -339,7 +352,7 @@ RowData* findByLocation(const RowData& row, QVector<RowData>* data)
     return nullptr;
 }
 
-void buildTopDown(const QVector<RowData>& bottomUpData, QVector<RowData>* topDownData)
+void buildTopDown(const TreeData& bottomUpData, TreeData* topDownData)
 {
     foreach (const auto& row, bottomUpData) {
         if (row.children.isEmpty()) {
@@ -483,7 +496,8 @@ void Parser::parse(const QString& path)
         emit progressMessageAvailable(i18n("merging allocations..."));
         // merge allocations before modifying the data again
         const auto mergedAllocations = mergeAllocations(*data);
-        emit bottomUpDataAvailable(mergedAllocations);
+        emit bottomUpDataAvailable(mergedAllocations.first);
+        emit callerCalleeDataAvailable(mergedAllocations.second);
 
         // also calculate the size histogram
         emit progressMessageAvailable(i18n("building size histogram..."));
@@ -494,7 +508,7 @@ void Parser::parse(const QString& path)
         emit progressMessageAvailable(i18n("building charts..."));
         auto parallel = new Collection;
         *parallel << make_job([this, mergedAllocations, sizeHistogram]() {
-            const auto topDownData = toTopDownData(mergedAllocations);
+            const auto topDownData = toTopDownData(mergedAllocations.first);
             emit topDownDataAvailable(topDownData);
         }) << make_job([this, data, stdPath]() {
             // this mutates data, and thus anything running in parallel must
index 113eadf..eec9d29 100644 (file)
@@ -25,6 +25,7 @@
 #include "treemodel.h"
 #include "chartmodel.h"
 #include "histogrammodel.h"
+#include "callercalleemodel.h"
 
 class Parser : public QObject
 {
@@ -41,6 +42,7 @@ signals:
     void summaryAvailable(const SummaryData& summary);
     void bottomUpDataAvailable(const TreeData& data);
     void topDownDataAvailable(const TreeData& data);
+    void callerCalleeDataAvailable(const CallerCalleeRows& data);
     void consumedChartDataAvailable(const ChartData& data);
     void allocationsChartDataAvailable(const ChartData& data);
     void allocatedChartDataAvailable(const ChartData& data);
diff --git a/gui/summarydata.h b/gui/summarydata.h
new file mode 100644 (file)
index 0000000..6f861c7
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 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 SUMMARYDATA_H
+#define SUMMARYDATA_H
+
+#include <QString>
+#include <QMetaType>
+#include "../allocationdata.h"
+
+struct SummaryData
+{
+    QString debuggee;
+    AllocationData cost;
+    uint64_t totalTime;
+    uint64_t peakTime;
+    uint64_t peakRSS;
+    uint64_t totalSystemMemory;
+};
+Q_DECLARE_METATYPE(SummaryData);
+
+#endif // SUMMARYDATA_H
index 22e258f..667c1b4 100644 (file)
 
 #include <KFormat>
 
-#include <memory>
-
+#include "locationdata.h"
+#include "summarydata.h"
 #include "../allocationdata.h"
 
-struct SummaryData
-{
-    QString debuggee;
-    AllocationData cost;
-    uint64_t totalTime;
-    uint64_t peakTime;
-    uint64_t peakRSS;
-    uint64_t totalSystemMemory;
-};
-Q_DECLARE_METATYPE(SummaryData);
-
-struct LocationData
-{
-    QString function;
-    QString file;
-    QString module;
-    int line;
-
-    bool operator==(const LocationData& rhs) const
-    {
-        return function == rhs.function
-            && file == rhs.file
-            && module == rhs.module
-            && line == rhs.line;
-    }
-
-    bool operator<(const LocationData& rhs) const
-    {
-        int i = function.compare(rhs.function);
-        if (!i) {
-            i = file.compare(rhs.file);
-        }
-        if (!i) {
-            i = line < rhs.line ? -1 : (line > rhs.line);
-        }
-        if (!i) {
-            i = module.compare(rhs.module);
-        }
-        return i < 0;
-    }
-};
-Q_DECLARE_TYPEINFO(LocationData, Q_MOVABLE_TYPE);
-
-inline bool operator<(const std::shared_ptr<LocationData>& lhs, const LocationData& rhs)
-{
-    return *lhs < rhs;
-}
-
 struct RowData
 {
     AllocationData cost;
index ea2faca..bc5baeb 100644 (file)
 
 #include "treeproxy.h"
 
-#include "treemodel.h"
-
-TreeProxy::TreeProxy(QObject* parent)
+TreeProxy::TreeProxy(int functionColumn, int fileColumn, int moduleColumn, QObject* parent)
     : KRecursiveFilterProxyModel(parent)
+    , m_functionColumn(functionColumn)
+    , m_fileColumn(fileColumn)
+    , m_moduleColumn(moduleColumn)
 {
 }
 
@@ -53,19 +54,19 @@ bool TreeProxy::acceptRow(int sourceRow, const QModelIndex& sourceParent) const
         return false;
     }
     if (!m_functionFilter.isEmpty()) {
-        const auto& function = source->index(sourceRow, TreeModel::FunctionColumn, sourceParent).data().toString();
+        const auto& function = source->index(sourceRow, m_functionColumn, sourceParent).data().toString();
         if (!function.contains(m_functionFilter, Qt::CaseInsensitive)) {
             return false;
         }
     }
     if (!m_fileFilter.isEmpty()) {
-        const auto& file = source->index(sourceRow, TreeModel::FileColumn, sourceParent).data().toString();
+        const auto& file = source->index(sourceRow, m_fileColumn, sourceParent).data().toString();
         if (!file.contains(m_fileFilter, Qt::CaseInsensitive)) {
             return false;
         }
     }
     if (!m_moduleFilter.isEmpty()) {
-        const auto& module = source->index(sourceRow, TreeModel::ModuleColumn, sourceParent).data().toString();
+        const auto& module = source->index(sourceRow, m_moduleColumn, sourceParent).data().toString();
         if (!module.contains(m_moduleFilter, Qt::CaseInsensitive)) {
             return false;
         }
index 646af5f..7154c90 100644 (file)
@@ -26,7 +26,7 @@ class TreeProxy final : public KRecursiveFilterProxyModel
 {
     Q_OBJECT
 public:
-    explicit TreeProxy(QObject* parent = nullptr);
+    explicit TreeProxy(int functionColumn, int fileColumn, int moduleColumn, QObject* parent = nullptr);
     virtual ~TreeProxy();
 
 public slots:
@@ -37,6 +37,10 @@ public slots:
 private:
     bool acceptRow(int source_row, const QModelIndex& source_parent) const override;
 
+    int m_functionColumn;
+    int m_fileColumn;
+    int m_moduleColumn;
+
     QString m_functionFilter;
     QString m_fileFilter;
     QString m_moduleFilter;