diff --git a/src/flamegraph.cpp b/src/flamegraph.cpp index 1b931398f..d88dcb016 100644 --- a/src/flamegraph.cpp +++ b/src/flamegraph.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -447,6 +448,16 @@ bool FlameGraph::eventFilter(QObject* object, QEvent* event) updateTooltip(); } else if (event->type() == QEvent::Hide) { setData(nullptr); + } else if (event->type() == QEvent::ContextMenu) { + QMenu contextMenu; + auto *viewCallerCallee = contextMenu.addAction(tr("View Caller/Callee")); + + QPoint itemPosition(QCursor::pos().x()-1, QCursor::pos().y()-1); + auto item = static_cast(m_view->itemAt(m_view->mapFromGlobal(itemPosition))); + QAction *action = contextMenu.exec(QCursor::pos()); + if (item && action == viewCallerCallee) { + emit jumpToCallerCallee(item->symbol()); + } } return ret; } diff --git a/src/flamegraph.h b/src/flamegraph.h index f254ef00f..918f7c140 100644 --- a/src/flamegraph.h +++ b/src/flamegraph.h @@ -56,6 +56,9 @@ class FlameGraph : public QWidget private slots: void setData(FrameGraphicsItem* rootItem); +signals: + void jumpToCallerCallee(const Data::Symbol& symbol); + private: void setTooltipItem(const FrameGraphicsItem* item); void updateTooltip(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3e99d14ce..ed909ff52 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -89,6 +89,8 @@ void setupTreeView(QTreeView* view, KFilterProxySearchLine* filter, Model* model view->sortByColumn(Model::InitialSortColumn); view->setModel(proxy); stretchFirstColumn(view); + + view->setContextMenuPolicy(Qt::CustomContextMenu); } template @@ -150,11 +152,13 @@ MainWindow::MainWindow(QWidget *parent) : auto bottomUpCostModel = new BottomUpModel(this); setupTreeView(ui->bottomUpTreeView, ui->bottomUpSearch, bottomUpCostModel); ui->bottomUpTreeView->setItemDelegateForColumn(BottomUpModel::Cost, treeViewCostDelegate); + connect(ui->bottomUpTreeView, &QTreeView::customContextMenuRequested, this, &MainWindow::onBottomUpContextMenu); auto topDownCostModel = new TopDownModel(this); setupTreeView(ui->topDownTreeView, ui->topDownSearch, topDownCostModel); ui->topDownTreeView->setItemDelegateForColumn(TopDownModel::SelfCost, treeViewCostDelegate); ui->topDownTreeView->setItemDelegateForColumn(TopDownModel::InclusiveCost, treeViewCostDelegate); + connect(ui->topDownTreeView, &QTreeView::customContextMenuRequested, this, &MainWindow::onTopDownContextMenu); auto topHotspotsProxy = new TopProxy(this); topHotspotsProxy->setSourceModel(bottomUpCostModel); @@ -163,12 +167,12 @@ MainWindow::MainWindow(QWidget *parent) : ui->topHotspotsTableView->setModel(topHotspotsProxy); stretchFirstColumn(ui->topHotspotsTableView); - auto callerCalleeCostModel = new CallerCalleeModel(this); - auto callerCalleeProxy = new QSortFilterProxyModel(this); - callerCalleeProxy->setSourceModel(callerCalleeCostModel); - ui->callerCalleeFilter->setProxy(callerCalleeProxy); + m_callerCalleeCostModel = new CallerCalleeModel(this); + m_callerCalleeProxy = new QSortFilterProxyModel(this); + m_callerCalleeProxy->setSourceModel(m_callerCalleeCostModel); + ui->callerCalleeFilter->setProxy(m_callerCalleeProxy); ui->callerCalleeTableView->setSortingEnabled(true); - ui->callerCalleeTableView->setModel(callerCalleeProxy); + ui->callerCalleeTableView->setModel(m_callerCalleeProxy); ui->callerCalleeTableView->hideColumn(CallerCalleeModel::Callers); ui->callerCalleeTableView->hideColumn(CallerCalleeModel::Callees); stretchFirstColumn(ui->callerCalleeTableView); @@ -191,8 +195,8 @@ MainWindow::MainWindow(QWidget *parent) : }); connect(m_parser, &PerfParser::callerCalleeDataAvailable, - this, [this, callerCalleeCostModel] (const Data::CallerCalleeEntryMap& data) { - callerCalleeCostModel->setData(data); + this, [this] (const Data::CallerCalleeEntryMap& data) { + m_callerCalleeCostModel->setData(data); auto view = ui->callerCalleeTableView; view->sortByColumn(CallerCalleeModel::InclusiveCost); view->setCurrentIndex(view->model()->index(0, 0, {})); @@ -214,10 +218,10 @@ MainWindow::MainWindow(QWidget *parent) : }); auto calleesModel = setupCallerOrCalleeView(ui->calleesView, ui->callerCalleeTableView, - callerCalleeCostModel, callerCalleeProxy); + m_callerCalleeCostModel, m_callerCalleeProxy); auto callersModel = setupCallerOrCalleeView(ui->callersView, ui->callerCalleeTableView, - callerCalleeCostModel, callerCalleeProxy); + m_callerCalleeCostModel, m_callerCalleeProxy); auto sourceMapModel = setupModelAndProxyForView(ui->sourceMapView); @@ -232,7 +236,7 @@ MainWindow::MainWindow(QWidget *parent) : }); connect(m_parser, &PerfParser::summaryDataAvailable, - this, [this, bottomUpCostModel, topDownCostModel, callerCalleeCostModel, calleesModel, callersModel, sourceMapModel] (const SummaryData& data) { + this, [this, bottomUpCostModel, topDownCostModel, calleesModel, callersModel, sourceMapModel] (const SummaryData& data) { auto formatSummaryText = [] (const QString& description, const QString& value) -> QString { return QString(QLatin1String("") + description + QLatin1String(": ") + value + QLatin1String("")); @@ -274,7 +278,7 @@ MainWindow::MainWindow(QWidget *parent) : bottomUpCostModel->setSampleCount(data.sampleCount); topDownCostModel->setSampleCount(data.sampleCount); - callerCalleeCostModel->setSampleCount(data.sampleCount); + m_callerCalleeCostModel->setSampleCount(data.sampleCount); calleesModel->setSampleCount(data.sampleCount); callersModel->setSampleCount(data.sampleCount); sourceMapModel->setSampleCount(data.sampleCount); @@ -289,6 +293,8 @@ MainWindow::MainWindow(QWidget *parent) : } }); + connect(ui->flameGraph, &FlameGraph::jumpToCallerCallee, this, &MainWindow::jumpToCallerCallee); + for (int i = 0, c = ui->resultsTabWidget->count(); i < c; ++i) { ui->resultsTabWidget->setTabToolTip(i, ui->resultsTabWidget->widget(i)->toolTip()); } @@ -367,3 +373,36 @@ void MainWindow::aboutHotspot() dialog.adjustSize(); dialog.exec(); } + +void MainWindow::customContextMenu(const QPoint &point, QTreeView* view, int symbolRole) +{ + const auto index = view->indexAt(point); + if (!index.isValid()) { + return; + } + + QMenu contextMenu; + auto *viewCallerCallee = contextMenu.addAction(tr("View Caller/Callee")); + auto *action = contextMenu.exec(QCursor::pos()); + if (action == viewCallerCallee) { + const auto symbol = index.data(symbolRole).value(); + jumpToCallerCallee(symbol); + } +} + +void MainWindow::onBottomUpContextMenu(const QPoint &point) +{ + customContextMenu(point, ui->bottomUpTreeView, BottomUpModel::SymbolRole); +} + +void MainWindow::onTopDownContextMenu(const QPoint &point) +{ + customContextMenu(point, ui->topDownTreeView, TopDownModel::SymbolRole); +} + +void MainWindow::jumpToCallerCallee(const Data::Symbol &symbol) +{ + auto callerCalleeIndex = m_callerCalleeProxy->mapFromSource(m_callerCalleeCostModel->indexForSymbol(symbol)); + ui->callerCalleeTableView->setCurrentIndex(callerCalleeIndex); + ui->resultsTabWidget->setCurrentWidget(ui->callerCalleeTab); +} diff --git a/src/mainwindow.h b/src/mainwindow.h index b1e3920c7..dfeaa0b4f 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -35,7 +35,14 @@ namespace Ui { class MainWindow; } +namespace Data { +struct Symbol; +} + class PerfParser; +class CallerCalleeModel; +class QSortFilterProxyModel; +class QTreeView; class MainWindow : public QMainWindow { @@ -55,7 +62,14 @@ public slots: private slots: void on_openFileButton_clicked(); + void customContextMenu(const QPoint &point, QTreeView* view, int symbolRole); + void onBottomUpContextMenu(const QPoint &pos); + void onTopDownContextMenu(const QPoint &pos); + void jumpToCallerCallee(const Data::Symbol &symbol); + private: QScopedPointer ui; PerfParser* m_parser; + CallerCalleeModel* m_callerCalleeCostModel; + QSortFilterProxyModel* m_callerCalleeProxy; }; diff --git a/src/models/hashmodel.h b/src/models/hashmodel.h index 8b0c1c9f2..12a7f0d12 100644 --- a/src/models/hashmodel.h +++ b/src/models/hashmodel.h @@ -30,6 +30,8 @@ #include #include +#include "data.h" + template class HashModel : public QAbstractTableModel { @@ -104,6 +106,16 @@ class HashModel : public QAbstractTableModel return index(row, column); } + QModelIndex indexForSymbol(const Data::Symbol& symbol) + { + auto it = m_rows.find(symbol); + if (it == m_rows.end()) { + return {}; + } + const int row = std::distance(m_rows.begin(), it); + return createIndex(row, 0); + } + private: Rows m_rows; quint64 m_sampleCount = 0; diff --git a/src/models/treemodel.h b/src/models/treemodel.h index 9704d1f11..775ec1c62 100644 --- a/src/models/treemodel.h +++ b/src/models/treemodel.h @@ -41,7 +41,8 @@ class AbstractTreeModel : public QAbstractItemModel enum Roles { SortRole = Qt::UserRole, TotalCostRole, - FilterRole + FilterRole, + SymbolRole }; }; @@ -143,6 +144,8 @@ class TreeModel : public AbstractTreeModel return ModelImpl::displayData(item, static_cast(index.column())); } else if (role == TotalCostRole) { return m_sampleCount; + } else if (role == SymbolRole) { + return QVariant::fromValue(item->symbol); } // TODO: tooltips