From 1cf5b84ae48a7168aef806f4d1b67dfb75da6494 Mon Sep 17 00:00:00 2001 From: Lieven Hey Date: Fri, 9 Sep 2022 12:00:59 +0200 Subject: [PATCH] diff view for top down and bottom up --- src/models/costdelegate.cpp | 3 +- src/models/costproxy.h | 43 +++++++++++++++++++++++--- src/models/data.cpp | 58 ++++++++++++++++++++++++++++++----- src/models/data.h | 4 ++- src/resultsbottomuppage.cpp | 35 ++++++++++------------ src/resultsbottomuppage.h | 2 +- src/resultspagediff.cpp | 9 +++++- src/resultstopdownpage.cpp | 60 ++++++++++++++++++++----------------- src/resultstopdownpage.h | 6 ++++ 9 files changed, 158 insertions(+), 62 deletions(-) diff --git a/src/models/costdelegate.cpp b/src/models/costdelegate.cpp index 79487f495..78d0eadbc 100644 --- a/src/models/costdelegate.cpp +++ b/src/models/costdelegate.cpp @@ -31,7 +31,8 @@ void CostDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, } const auto totalCost = index.data(m_totalCostRole).toULongLong(); - const auto fraction = std::abs(float(cost) / totalCost); + // TODO C++17: std::clamp + const auto fraction = std::max(0.f, std::min(1.f, std::abs(float(cost) / totalCost))); auto rect = option.rect; rect.setWidth(rect.width() * fraction); diff --git a/src/models/costproxy.h b/src/models/costproxy.h index f19f60a69..6ffdaa85f 100644 --- a/src/models/costproxy.h +++ b/src/models/costproxy.h @@ -42,6 +42,39 @@ class CostProxy : public QSortFilterProxyModel } }; +namespace CostProxyUtil { +inline int cost(const BottomUpModel* model, int column, int nodeid) +{ + return model->results().costs.cost(column, nodeid); +} +inline int cost(const TopDownModel* model, int column, int nodeid) +{ + const auto inclusiveTypes = model->results().inclusiveCosts.numTypes(); + if (column >= inclusiveTypes) { + return model->results().selfCosts.cost(column - inclusiveTypes, nodeid); + } + return model->results().inclusiveCosts.cost(column, nodeid); +} + +inline int totalCost(const BottomUpModel* model, int column) +{ + return model->results().costs.totalCost(column); +} +inline int totalCost(const TopDownModel* model, int column) +{ + const auto inclusiveTypes = model->results().inclusiveCosts.numTypes(); + if (column >= inclusiveTypes) { + return model->results().selfCosts.totalCost(column - inclusiveTypes); + } + return model->results().inclusiveCosts.totalCost(column); +} +} + +// TODO dedicated cost role +// The DiffCostProxy does all the heavy lifting of diffing +// its gets it data from a Model with baseline cost and the file to diff cost in alternating columns +// this proxy return for every even column the base cost and for every uneven the calculated diff cost +// this simplifies the other models, since we don't need to add this logic there template class DiffCostProxy : public CostProxy { @@ -62,15 +95,15 @@ class DiffCostProxy : public CostProxy const auto baseColumn = (index.column() - Model::NUM_BASE_COLUMNS) / 2; const auto column = baseColumn + (index.column() - Model::NUM_BASE_COLUMNS) % 2; - auto cost = [model, node](int column) -> float { return model->results().costs.cost(column, node->id); }; + auto cost = [model, node](int column) { return CostProxyUtil::cost(model, column, node->id); }; - auto totalCost = [model](int column) -> float { return model->results().costs.totalCost(column); }; + auto totalCost = [model](int column) { return CostProxyUtil::totalCost(model, column); }; if (column == baseColumn) { if (role == Model::TotalCostRole) { return totalCost(column); } else if (role == Model::SortRole) { - return cost(column) / totalCost(column); + return cost(column); } else if (role == Qt::DisplayRole) { return Util::formatCostRelative(cost(column), totalCost(column), true); } @@ -78,7 +111,9 @@ class DiffCostProxy : public CostProxy if (role == Model::TotalCostRole) { return cost(baseColumn); } else if (role == Model::SortRole) { - return cost(column) / cost(baseColumn); + if (cost(baseColumn) == 0) + return 0; + return cost(column); } else if (role == Qt::DisplayRole) { return Util::formatCostRelative(cost(column), cost(baseColumn), true); } diff --git a/src/models/data.cpp b/src/models/data.cpp index 9b40074fb..ec81c2b2f 100644 --- a/src/models/data.cpp +++ b/src/models/data.cpp @@ -281,13 +281,14 @@ void buildPerLibrary(const TopDown* node, PerLibraryResults& results, QHash +void diffResults(const ResultType& a, const ResultType* b, ResultType* result_node, const Costs& costs_a, + const Costs& costs_b, Costs* costs_result) { for (const auto& node : a.children) { const auto sibling = b->entryForSymbol(node.symbol); if (sibling) { - BottomUp diffed; + ResultType diffed; diffed.id = node.id; diffed.symbol = node.symbol; @@ -296,8 +297,10 @@ void diffBottomUpResults(const BottomUp& a, const BottomUp* b, BottomUp* result_ costs_result->add(2 * i + 1, diffed.id, costs_b.cost(i, sibling->id)); } - result_node->children.push_back(diffed); - diffBottomUpResults(node, sibling, &result_node->children.back(), costs_a, costs_b, costs_result); + if (addResultNode) { + result_node->children.push_back(diffed); + } + diffResults(node, sibling, &result_node->children.back(), costs_a, costs_b, costs_result); } } } @@ -309,7 +312,7 @@ QString Data::prettifySymbol(const QString& name) return result == name ? name : result; } -TopDownResults TopDownResults::fromBottomUp(const BottomUpResults& bottomUpData, bool skipFirstLevel) +TopDownResults Data::TopDownResults::fromBottomUp(const BottomUpResults& bottomUpData, bool skipFirstLevel) { TopDownResults results; results.selfCosts.initializeCostsFrom(bottomUpData.costs); @@ -443,9 +446,50 @@ Data::BottomUpResults BottomUpResults::diffBottomUpResults(const Data::BottomUpR results.costs.addTotalCost(costBType, b.costs.totalCost(0)); } - ::diffBottomUpResults(a.root, &b.root, &results.root, a.costs, b.costs, &results.costs); + diffResults(a.root, &b.root, &results.root, a.costs, b.costs, &results.costs); BottomUp::initializeParents(&results.root); return results; } + +TopDownResults TopDownResults::diffTopDownResults(const TopDownResults& a, const TopDownResults& b) +{ + if (a.selfCosts.numTypes() != b.selfCosts.numTypes()) { + return {}; + } + + TopDownResults results; + + for (int i = 0; i < a.selfCosts.numTypes(); i++) { + // only diff same type of costs + if (a.selfCosts.typeName(i) != b.selfCosts.typeName(i)) { + return {}; + } + + results.selfCosts.addType(2 * i, QLatin1String("baseline %1").arg(a.selfCosts.typeName(i)), + a.selfCosts.unit(i)); + results.selfCosts.addTotalCost(2 * i, a.selfCosts.totalCost(i)); + + results.inclusiveCosts.addType(2 * i, QLatin1String("baseline %1").arg(a.inclusiveCosts.typeName(i)), + a.inclusiveCosts.unit(i)); + results.inclusiveCosts.addTotalCost(2 * i, a.inclusiveCosts.totalCost(i)); + + const auto costBType = 2 * i + 1; + results.selfCosts.addType(costBType, QLatin1String("ratio of %1").arg(b.selfCosts.typeName(i)), + Costs::Unit::Unknown); + results.selfCosts.addTotalCost(costBType, b.selfCosts.totalCost(0)); + + results.inclusiveCosts.addType(costBType, QLatin1String("ratio of %1").arg(b.inclusiveCosts.typeName(i)), + Costs::Unit::Unknown); + results.inclusiveCosts.addTotalCost(costBType, b.inclusiveCosts.totalCost(0)); + } + + diffResults(a.root, &b.root, &results.root, a.selfCosts, b.selfCosts, &results.selfCosts); + diffResults(a.root, &b.root, &results.root, a.inclusiveCosts, b.inclusiveCosts, + &results.inclusiveCosts); + + Data::TopDown::initializeParents(&results.root); + + return results; +} diff --git a/src/models/data.h b/src/models/data.h index 875678253..3a492cd33 100644 --- a/src/models/data.h +++ b/src/models/data.h @@ -526,7 +526,9 @@ struct TopDownResults TopDown root; Costs selfCosts; Costs inclusiveCosts; - static TopDownResults fromBottomUp(const Data::BottomUpResults& bottomUpData, bool skipFirstLevel); + static TopDownResults fromBottomUp(const Data::BottomUpResults& bottomUpData, bool skipFirestLevel); + + static TopDownResults diffTopDownResults(const Data::TopDownResults& a, const Data::TopDownResults& b); }; struct PerLibrary : SymbolTree diff --git a/src/resultsbottomuppage.cpp b/src/resultsbottomuppage.cpp index c60479a5e..02ae88aff 100644 --- a/src/resultsbottomuppage.cpp +++ b/src/resultsbottomuppage.cpp @@ -85,24 +85,21 @@ void ResultsBottomUpPage::setBottomUpResults(const Data::BottomUpResults& result m_model->setData(results); ResultsUtil::hideEmptyColumns(results.costs, ui->bottomUpTreeView, BottomUpModel::NUM_BASE_COLUMNS); - { - auto stackCollapsed = - m_exportMenu->addMenu(QIcon::fromTheme(QStringLiteral("text-plain")), tr("Stack Collapsed")); - stackCollapsed->setToolTip(tr("Export data in textual form compatible with flamegraph.pl.")); - for (int i = 0; i < results.costs.numTypes(); ++i) { - const auto costName = results.costs.typeName(i); - stackCollapsed->addAction(costName, [this, i, costName]() { - const auto fileName = QFileDialog::getSaveFileName(this, tr("Export %1 Data").arg(costName)); - if (fileName.isEmpty()) - return; - QFile file(fileName); - if (!file.open(QIODevice::Text | QIODevice::WriteOnly)) { - QMessageBox::warning(this, tr("Failed to export data"), - tr("Failed to export stack collapsed data:\n%1").arg(file.errorString())); - return; - } - stackCollapsedExport(file, i, m_model->results()); - }); - } + auto stackCollapsed = m_exportMenu->addMenu(QIcon::fromTheme(QStringLiteral("text-plain")), tr("Stack Collapsed")); + stackCollapsed->setToolTip(tr("Export data in textual form compatible with flamegraph.pl.")); + for (int i = 0; i < results.costs.numTypes(); ++i) { + const auto costName = results.costs.typeName(i); + stackCollapsed->addAction(costName, [this, i, costName]() { + const auto fileName = QFileDialog::getSaveFileName(this, tr("Export %1 Data").arg(costName)); + if (fileName.isEmpty()) + return; + QFile file(fileName); + if (!file.open(QIODevice::Text | QIODevice::WriteOnly)) { + QMessageBox::warning(this, tr("Failed to export data"), + tr("Failed to export stack collapsed data:\n%1").arg(file.errorString())); + return; + } + stackCollapsedExport(file, i, m_model->results()); + }); } } diff --git a/src/resultsbottomuppage.h b/src/resultsbottomuppage.h index 7c67207b2..d94f22d8f 100644 --- a/src/resultsbottomuppage.h +++ b/src/resultsbottomuppage.h @@ -18,7 +18,7 @@ class ResultsBottomUpPage; namespace Data { struct Symbol; -class BottomUpResults; +struct BottomUpResults; } class QTreeView; diff --git a/src/resultspagediff.cpp b/src/resultspagediff.cpp index 753dd2713..c25ce279a 100644 --- a/src/resultspagediff.cpp +++ b/src/resultspagediff.cpp @@ -110,9 +110,16 @@ ResultsPageDiff::ResultsPageDiff(QWidget* parent) } connect(this, &ResultsPageDiff::parsingFinished, this, [this] { - const auto bottomUpData = + auto bottomUpData = Data::BottomUpResults::diffBottomUpResults(m_fileA->bottomUpResults(), m_fileB->bottomUpResults()); m_resultsBottomUpPage->setBottomUpResults(bottomUpData); + + auto skipFirstLevel = Settings::instance()->costAggregation() == Settings::CostAggregation::BySymbol; + + auto topDownData = Data::TopDownResults::diffTopDownResults( + Data::TopDownResults::fromBottomUp(m_fileA->bottomUpResults(), skipFirstLevel), + Data::TopDownResults::fromBottomUp(m_fileB->bottomUpResults(), skipFirstLevel)); + m_resultsTopDownPage->setTopDownResults(topDownData); }); { diff --git a/src/resultstopdownpage.cpp b/src/resultstopdownpage.cpp index e2a5d4318..6008e3e84 100644 --- a/src/resultstopdownpage.cpp +++ b/src/resultstopdownpage.cpp @@ -9,6 +9,7 @@ #include "resultstopdownpage.h" #include "ui_resultstopdownpage.h" +#include "data.h" #include "parsers/perf/perfparser.h" #include "resultsutil.h" @@ -18,38 +19,16 @@ ResultsTopDownPage::ResultsTopDownPage(FilterAndZoomStack* filterStack, PerfParser* parser, CostContextMenu* contextMenu, QWidget* parent) : QWidget(parent) + , m_model(new TopDownModel(this)) , ui(new Ui::ResultsTopDownPage) { ui->setupUi(this); - auto topDownCostModel = new TopDownModel(this); - ResultsUtil::setupTreeView(ui->topDownTreeView, contextMenu, ui->topDownSearch, topDownCostModel); - ResultsUtil::setupCostDelegate(topDownCostModel, ui->topDownTreeView); - ResultsUtil::setupContextMenu(ui->topDownTreeView, contextMenu, topDownCostModel, filterStack, this); - - connect(parser, &PerfParser::topDownDataAvailable, this, - [this, topDownCostModel](const Data::TopDownResults& data) { - topDownCostModel->setData(data); - ResultsUtil::hideEmptyColumns(data.inclusiveCosts, ui->topDownTreeView, TopDownModel::NUM_BASE_COLUMNS); - - ResultsUtil::hideEmptyColumns(data.selfCosts, ui->topDownTreeView, - TopDownModel::NUM_BASE_COLUMNS + data.inclusiveCosts.numTypes()); - ResultsUtil::hideTracepointColumns(data.selfCosts, ui->topDownTreeView, - TopDownModel::NUM_BASE_COLUMNS + data.inclusiveCosts.numTypes()); - - // hide self cost columns for sched:sched_switch and off-CPU - // quasi all rows will have a cost of 0%, and only the leaves will show - // a non-zero value that is equal to the inclusive cost then - const auto costs = data.inclusiveCosts.numTypes(); - const auto schedSwitchName = QLatin1String("sched:sched_switch"); - const auto offCpuName = PerfParser::tr("off-CPU Time"); - for (int i = 0; i < costs; ++i) { - const auto typeName = data.inclusiveCosts.typeName(i); - if (typeName == schedSwitchName || typeName == offCpuName) { - ui->topDownTreeView->hideColumn(topDownCostModel->selfCostColumn(i)); - } - } - }); + ResultsUtil::setupTreeViewDiff(ui->topDownTreeView, contextMenu, ui->topDownSearch, m_model); + ResultsUtil::setupCostDelegate(m_model, ui->topDownTreeView); + ResultsUtil::setupContextMenu(ui->topDownTreeView, contextMenu, m_model, filterStack, this); + + connect(parser, &PerfParser::topDownDataAvailable, this, &ResultsTopDownPage::setTopDownResults); ResultsUtil::setupResultsAggregation(ui->costAggregationComboBox); } @@ -60,3 +39,28 @@ void ResultsTopDownPage::clear() { ui->topDownSearch->setText({}); } + +void ResultsTopDownPage::setTopDownResults(const Data::TopDownResults& data) +{ + m_model->setData(data); + ResultsUtil::hideEmptyColumns(data.inclusiveCosts, ui->topDownTreeView, TopDownModel::NUM_BASE_COLUMNS); + + ResultsUtil::hideEmptyColumns(data.selfCosts, ui->topDownTreeView, + TopDownModel::NUM_BASE_COLUMNS + data.inclusiveCosts.numTypes()); + ResultsUtil::hideTracepointColumns(data.selfCosts, ui->topDownTreeView, + TopDownModel::NUM_BASE_COLUMNS + data.inclusiveCosts.numTypes()); + + // hide self cost columns for sched:sched_switch and off-CPU + // quasi all rows will have a cost of 0%, and only the leaves will show + // a non-zero value that is equal to the inclusive cost then + const auto costs = data.inclusiveCosts.numTypes(); + const auto schedSwitchName = QLatin1String("sched:sched_switch"); + const auto offCpuName = PerfParser::tr("off-CPU Time"); + for (int i = 0; i < costs; ++i) { + const auto typeName = data.inclusiveCosts.typeName(i); + // use contains to also work in diff view + if (typeName.contains(schedSwitchName) || typeName.contains(offCpuName)) { + ui->topDownTreeView->hideColumn(m_model->selfCostColumn(i)); + } + } +} diff --git a/src/resultstopdownpage.h b/src/resultstopdownpage.h index c99c0e426..6b68e87bb 100644 --- a/src/resultstopdownpage.h +++ b/src/resultstopdownpage.h @@ -16,6 +16,7 @@ class ResultsTopDownPage; namespace Data { struct Symbol; +struct TopDownResults; } class QTreeView; @@ -23,6 +24,7 @@ class QTreeView; class PerfParser; class FilterAndZoomStack; class CostContextMenu; +class TopDownModel; class ResultsTopDownPage : public QWidget { @@ -34,6 +36,9 @@ class ResultsTopDownPage : public QWidget void clear(); +public slots: + void setTopDownResults(const Data::TopDownResults& data); + signals: void jumpToCallerCallee(const Data::Symbol& symbol); void openEditor(const Data::Symbol& symbol); @@ -41,5 +46,6 @@ class ResultsTopDownPage : public QWidget void jumpToDisassembly(const Data::Symbol& symbol); private: + TopDownModel* m_model = nullptr; QScopedPointer ui; };