diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6efec50b4..60cb19a05 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -48,6 +48,7 @@ set(hotspot_SRCS perfoutputwidget.cpp perfoutputwidgettext.cpp perfoutputwidgetkonsole.cpp + diffviewwidget.cpp # ui files: mainwindow.ui @@ -65,6 +66,7 @@ set(hotspot_SRCS settingsdialog.ui flamegraphsettings.ui debuginfoddialog.ui + resultsdiffpage.ui # resources: resources.qrc diff --git a/src/diffviewwidget.cpp b/src/diffviewwidget.cpp new file mode 100644 index 000000000..009fb23d2 --- /dev/null +++ b/src/diffviewwidget.cpp @@ -0,0 +1,132 @@ +/* + diffviewwidget.cpp + + This file is part of Hotspot, the Qt GUI for performance analysis. + + Copyright (C) 2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Author: Lieven Hey + + Licensees holding valid commercial KDAB Hotspot licenses may use this file in + accordance with Hotspot Commercial License Agreement provided with the Software. + + Contact info@kdab.com if any conditions of this licensing are not clear to you. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU 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, see . +*/ + +#include "diffviewwidget.h" + +#include +#include + +#include "dockwidgetsetup.h" +#include "filterandzoomstack.h" +#include "models/treemodel.h" +#include "parsers/perf/perfparser.h" +#include "resultsutil.h" +#include "settings.h" +#include "timelinewidget.h" + +#include "ui_resultsdiffpage.h" + +DiffViewWidget::DiffViewWidget(QWidget* parent) + : QWidget(parent) + , ui(new Ui::ResultsDiffPage) + , m_parserA(new PerfParser(this)) + , m_parserB(new PerfParser(this)) + , m_model(new DiffViewModel(this)) +{ + ui->setupUi(this); + ; + ui->diffTreeView->setModel(m_model); + ui->diffTreeView->sortByColumn(DiffViewModel::InitialSortColumn, Qt::DescendingOrder); + ResultsUtil::setupCostDelegate(m_model, ui->diffTreeView); + + auto repositionFilterBusyIndicator = [this] { + auto geometry = m_filterBusyIndicator->geometry(); + geometry.setWidth(width() / 2); + geometry.moveCenter(rect().center()); + m_filterBusyIndicator->setGeometry(geometry); + }; + + connect(m_parserA, &PerfParser::parsingStarted, this, [this, repositionFilterBusyIndicator] { + repositionFilterBusyIndicator(); + m_filterBusyIndicator->setVisible(true); + }); + + connect(m_parserB, &PerfParser::parsingStarted, this, [this, repositionFilterBusyIndicator] { + repositionFilterBusyIndicator(); + m_filterBusyIndicator->setVisible(true); + }); + + connect(m_parserA, &PerfParser::parsingFinished, this, [this] { + m_aFinished = true; + if (m_bFinished) { + m_filterBusyIndicator->setVisible(false); + } + }); + + connect(m_parserB, &PerfParser::parsingFinished, this, [this] { + m_bFinished = true; + if (m_aFinished) { + m_filterBusyIndicator->setVisible(false); + } + }); + + connect(m_parserA, &PerfParser::topDownDataAvailable, this, [this](const Data::TopDownResults& results) { + m_resultsA = results; + + const auto data = Data::DiffViewResults::fromTopDown(m_resultsA, m_resultsB); + + if (data.costsA.numTypes() > 0 && data.costsB.numTypes() > 0) { + m_model->setData(data); + } + }); + + connect(m_parserB, &PerfParser::topDownDataAvailable, this, [this](const Data::TopDownResults& results) { + m_resultsB = results; + + const auto data = Data::DiffViewResults::fromTopDown(m_resultsA, m_resultsB); + + if (data.costsA.numTypes() > 0 && data.costsB.numTypes() > 0) { + m_model->setData(data); + } + }); + + { + m_filterBusyIndicator = new QWidget(this); + m_filterBusyIndicator->setMinimumHeight(100); + m_filterBusyIndicator->setVisible(false); + m_filterBusyIndicator->setToolTip(tr("Filtering in progress, please wait...")); + auto layout = new QVBoxLayout(m_filterBusyIndicator); + layout->setAlignment(Qt::AlignCenter); + auto progressBar = new QProgressBar(m_filterBusyIndicator); + layout->addWidget(progressBar); + progressBar->setMaximum(0); + auto label = new QLabel(m_filterBusyIndicator->toolTip(), m_filterBusyIndicator); + label->setAlignment(Qt::AlignCenter); + layout->addWidget(label); + } +} + +DiffViewWidget::~DiffViewWidget() = default; + +void DiffViewWidget::open(const QString& a, const QString& b) +{ + auto settings = Settings::instance(); + m_parserA->startParseFile(a, settings->sysroot(), settings->kallsyms(), settings->debugPaths(), + settings->extraLibPaths(), settings->appPath(), settings->arch()); + m_parserB->startParseFile(b, settings->sysroot(), settings->kallsyms(), settings->debugPaths(), + settings->extraLibPaths(), settings->appPath(), settings->arch()); +} diff --git a/src/diffviewwidget.h b/src/diffviewwidget.h new file mode 100644 index 000000000..23ad1d0df --- /dev/null +++ b/src/diffviewwidget.h @@ -0,0 +1,73 @@ +/* + diffviewwidget.h + + This file is part of Hotspot, the Qt GUI for performance analysis. + + Copyright (C) 2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Author: Lieven Hey + + Licensees holding valid commercial KDAB Hotspot licenses may use this file in + accordance with Hotspot Commercial License Agreement provided with the Software. + + Contact info@kdab.com if any conditions of this licensing are not clear to you. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU 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, see . +*/ + +#pragma once + +#include "data.h" +#include + +class PerfParser; +class DiffViewModel; +class TimeLineWidget; +class FilterAndZoomStack; + +namespace KDDockWidgets { +class MainWindow; +class DockWidget; +} + +namespace Ui { +class ResultsDiffPage; +} + +class DiffViewWidget : public QWidget +{ + Q_OBJECT +public: + explicit DiffViewWidget(QWidget* parent = nullptr); + ~DiffViewWidget(); + +public slots: + void open(const QString& a, const QString& b); + +private: + QScopedPointer ui; + PerfParser* m_parserA; + PerfParser* m_parserB; + DiffViewModel* m_model; + KDDockWidgets::MainWindow* m_contents; + KDDockWidgets::DockWidget* m_timelineDockA; + KDDockWidgets::DockWidget* m_timelineDockB; + TimeLineWidget* m_timelineA; + TimeLineWidget* m_timelineB; + QWidget* m_filterBusyIndicator = nullptr; + Data::TopDownResults m_resultsA; + Data::TopDownResults m_resultsB; + + bool m_aFinished = false; + bool m_bFinished = false; +}; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 73432d61b..047952417 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -26,12 +26,13 @@ */ #include "mainwindow.h" +#include "diffviewwidget.h" #include "recordpage.h" #include "resultspage.h" #include "settings.h" +#include "settingsdialog.h" #include "startpage.h" #include "ui_mainwindow.h" -#include "settingsdialog.h" #include "ui_settingsdialog.h" #include @@ -115,12 +116,14 @@ MainWindow::MainWindow(QWidget* parent) , m_recordPage(new RecordPage(this)) , m_resultsPage(new ResultsPage(m_parser, this)) , m_settingsDialog(new SettingsDialog(this)) + , m_diffView(new DiffViewWidget(this)) { ui->setupUi(this); m_pageStack->addWidget(m_startPage); m_pageStack->addWidget(m_resultsPage); m_pageStack->addWidget(m_recordPage); + m_pageStack->addWidget(m_diffView); QVBoxLayout* layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 0); @@ -148,6 +151,7 @@ MainWindow::MainWindow(QWidget* parent) connect(m_startPage, &StartPage::recordButtonClicked, this, &MainWindow::onRecordButtonClicked); connect(m_startPage, &StartPage::stopParseButtonClicked, this, static_cast(&MainWindow::clear)); + connect(m_startPage, &StartPage::diffFilesButtonClicked, this, &MainWindow::onDiffButtonClicked); connect(m_parser, &PerfParser::progress, m_startPage, &StartPage::onParseFileProgress); connect(this, &MainWindow::openFileError, m_startPage, &StartPage::onOpenFileError); connect(m_recordPage, &RecordPage::homeButtonClicked, this, &MainWindow::onHomeButtonClicked); @@ -335,6 +339,16 @@ void MainWindow::onHomeButtonClicked() m_pageStack->setCurrentWidget(m_startPage); } +void MainWindow::onDiffButtonClicked() +{ + const auto file1 = QFileDialog::getOpenFileName(this, tr("Open File"), QDir::currentPath(), + tr("Data Files (perf*.data perf.data.*);;All Files (*)")); + const auto file2 = QFileDialog::getOpenFileName(this, tr("Open File"), QDir::currentPath(), + tr("Data Files (perf*.data perf.data.*);;All Files (*)")); + m_pageStack->setCurrentWidget(m_diffView); + m_diffView->open(file1, file2); +} + void MainWindow::onRecordButtonClicked() { clear(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 3fccb7771..f56dfa6fe 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -46,6 +46,7 @@ class StartPage; class ResultsPage; class RecordPage; class SettingsDialog; +class DiffViewWidget; class MainWindow : public KParts::MainWindow { @@ -65,6 +66,7 @@ public slots: void onOpenFileButtonClicked(); void onRecordButtonClicked(); void onHomeButtonClicked(); + void onDiffButtonClicked(); void aboutKDAB(); void openSettingsDialog(); @@ -90,6 +92,7 @@ public slots: RecordPage* m_recordPage; ResultsPage* m_resultsPage; SettingsDialog* m_settingsDialog; + DiffViewWidget* m_diffView; QString m_lastUsedSettings; KRecentFilesAction* m_recentFilesAction = nullptr; diff --git a/src/models/costdelegate.cpp b/src/models/costdelegate.cpp index 69af56f5e..73ebe6905 100644 --- a/src/models/costdelegate.cpp +++ b/src/models/costdelegate.cpp @@ -32,6 +32,15 @@ #include +float clamp(float value, float low, float high) +{ + if (value < low) + return low; + if (value > high) + return high; + return value; +} + CostDelegate::CostDelegate(quint32 sortRole, quint32 totalCostRole, QObject* parent) : QStyledItemDelegate(parent) , m_sortRole(sortRole) @@ -43,18 +52,18 @@ CostDelegate::~CostDelegate() = default; void CostDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - // TODO: handle negative values - const auto cost = index.data(m_sortRole).toULongLong(); + auto cost = index.data(m_sortRole).toLongLong(); if (cost == 0) { QStyledItemDelegate::paint(painter, option, index); return; } - const auto totalCost = index.data(m_totalCostRole).toULongLong(); - const auto fraction = std::abs(float(cost) / totalCost); + const auto totalCost = index.data(m_totalCostRole).toLongLong(); + const auto fraction = clamp(float(cost) / totalCost, -1, 1); + const auto absFraction = std::abs(fraction); auto rect = option.rect; - rect.setWidth(rect.width() * fraction); + rect.setWidth(rect.width() * absFraction); const auto& brush = painter->brush(); const auto& pen = painter->pen(); @@ -68,7 +77,8 @@ void CostDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, painter->drawRect(option.rect); } - auto color = QColor::fromHsv(120 - fraction * 120, 255, 255, (-((fraction - 1) * (fraction - 1))) * 120 + 120); + auto color = + QColor::fromHsv(120 - fraction * 120, 255, 255, (-((absFraction - 1) * (absFraction - 1))) * 120 + 120); painter->setBrush(color); painter->drawRect(rect); diff --git a/src/models/data.cpp b/src/models/data.cpp index 8d3cf847e..4ba1d68c0 100644 --- a/src/models/data.cpp +++ b/src/models/data.cpp @@ -353,3 +353,33 @@ const Data::ThreadEvents* Data::EventResults::findThread(qint32 pid, qint32 tid) { return const_cast(this)->findThread(pid, tid); } + +DiffViewResults DiffViewResults::fromTopDown(const Data::TopDownResults& a, const Data::TopDownResults& b) +{ + DiffViewResults results; + results.costsA = a.selfCosts; + results.costsB = b.selfCosts; + + std::function iterateModels = [&iterateModels, b](const TopDown& node, DiffView& parent) { + DiffView diffView; + diffView.costIdA = node.id; + diffView.symbol = node.symbol; + const auto sibling = b.root.entryForSymbol(node.symbol); + if (sibling) { + diffView.costIdB = sibling->id; + } + parent.children.push_back(diffView); + + for (const auto& child : node.children) { + iterateModels(child, parent.children.back()); + } + }; + + for (const auto& entry : a.root.children) { + iterateModels(entry, results.root); + } + + Data::DiffView::initializeParents(&results.root); + + return results; +} diff --git a/src/models/data.h b/src/models/data.h index b16640ec7..0df4cd6d7 100644 --- a/src/models/data.h +++ b/src/models/data.h @@ -468,6 +468,21 @@ struct TopDownResults static TopDownResults fromBottomUp(const Data::BottomUpResults& bottomUpData); }; +struct DiffView : SymbolTree +{ + quint32 costIdA = UINT32_MAX; + quint32 costIdB = UINT32_MAX; +}; + +struct DiffViewResults +{ + DiffView root; + Costs costsA; + Costs costsB; + + static DiffViewResults fromTopDown(const Data::TopDownResults& a, const Data::TopDownResults& b); +}; + using SymbolCostMap = QHash; using CalleeMap = SymbolCostMap; using CallerMap = SymbolCostMap; diff --git a/src/models/diffviewproxy.cpp b/src/models/diffviewproxy.cpp new file mode 100644 index 000000000..ce9c03b7d --- /dev/null +++ b/src/models/diffviewproxy.cpp @@ -0,0 +1,56 @@ +/* + diffviewproxy.h + + This file is part of Hotspot, the Qt GUI for performance analysis. + + Copyright (C) 2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + Author: Lieven Hey + + Licensees holding valid commercial KDAB Hotspot licenses may use this file in + accordance with Hotspot Commercial License Agreement provided with the Software. + + Contact info@kdab.com if any conditions of this licensing are not clear to you. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU 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, see . +*/ + +#include "diffviewproxy.h" +#include "treemodel.h" + +DiffViewProxy::DiffViewProxy(QObject* parent) + : QSortFilterProxyModel(parent) +{ + setRecursiveFilteringEnabled(true); +} + +DiffViewProxy::~DiffViewProxy() = default; + +void DiffViewProxy::setThreshold(double threshold) +{ + m_threshold = threshold; + invalidateFilter(); +} + +bool DiffViewProxy::filterAcceptsRow(int source_row, const QModelIndex& parent) const +{ + Q_UNUSED(parent); + + const auto model = qobject_cast(sourceModel()); + + const auto cost = + model->data(model->index(source_row, DiffViewModel::NUM_BASE_COLUMNS), DiffViewModel::SortRole).toLongLong(); + const auto total = model->data(model->index(source_row, DiffViewModel::NUM_BASE_COLUMNS), DiffViewModel::TotalCostRole).toLongLong(); + + return cost * 100 / static_cast(total) > m_threshold; +} diff --git a/src/models/treemodel.cpp b/src/models/treemodel.cpp index b3bbe562a..8daad492c 100644 --- a/src/models/treemodel.cpp +++ b/src/models/treemodel.cpp @@ -224,3 +224,101 @@ int TopDownModel::selfCostColumn(int cost) const Q_ASSERT(cost >= 0 && cost < m_results.selfCosts.numTypes()); return NUM_BASE_COLUMNS + m_results.inclusiveCosts.numTypes() + cost; } + +DiffViewModel::DiffViewModel(QObject* parent) + : CostTreeModel(parent) +{ + auto prettifySymbolsHelper = [this]() { + if (rowCount() == 0) { + return; + } + emit dataChanged(index(0, Symbol), index(rowCount() - 1, Symbol)); + }; + + connect(Settings::instance(), &Settings::prettifySymbolsChanged, this, prettifySymbolsHelper); + + connect(Settings::instance(), &Settings::collapseTemplatesChanged, this, prettifySymbolsHelper); + + connect(Settings::instance(), &Settings::collapseDepthChanged, this, prettifySymbolsHelper); +} + +DiffViewModel::~DiffViewModel() = default; + +QVariant DiffViewModel::headerColumnData(int column, int role) const +{ + if (role == Qt::DisplayRole) { + switch (column) { + case Symbol: + return tr("Symbol"); + case Binary: + return tr("Binary"); + } + column -= NUM_BASE_COLUMNS; + if (column % 2 == 0) { + return tr("%1 (incl.)").arg(m_results.costsA.typeName(column / 2)); + } + return tr("%1 (incl.)").arg(m_results.costsB.typeName(column / 2)); + } else if (role == Qt::ToolTipRole) { + switch (column) { + case Symbol: + return tr("The symbol's function name. May be empty when debug information is missing."); + case Binary: + return tr( + "The name of the executable the symbol resides in. May be empty when debug information is missing."); + } + column -= NUM_BASE_COLUMNS; + if (column % 2 == 0) { + column /= 2; + return tr("The symbol's inclusive cost of type \"%1\", i.e. the aggregated sample costs attributed to this symbol, both directly and indirectly. This includes the costs of all functions called by this symbol plus its self cost.").arg(m_results.costsA.typeName(column)); + } + column /= 2; + return tr("The symbol's inclusive cost of type \"%1\", i.e. the aggregated sample costs attributed to this symbol, both directly and indirectly. This includes the costs of all functions called by this symbol plus its self cost.").arg(m_results.costsB.typeName(column)); + } + return {}; +} + +QVariant DiffViewModel::rowData(const Data::DiffView* row, int column, int role) const +{ + if (role == Qt::DisplayRole || role == SortRole) { + switch (column) { + case Symbol: + return Util::formatSymbol(row->symbol); + case Binary: + return row->symbol.binary; + } + + column -= NUM_BASE_COLUMNS; + if (column % 2 == 0) { + column /= 2; + if (role == SortRole) { + return m_results.costsA.cost(column, row->costIdA); + } + return Util::formatCostRelative(m_results.costsA.cost(column, row->costIdA), m_results.costsA.totalCost(column), true); + } + column /= 2; + if (role == SortRole) { + return m_results.costsB.cost(column, row->costIdB); + } + return Util::formatCostRelative(m_results.costsB.cost(column, row->costIdB), m_results.costsB.totalCost(column), true); + } else if (role == TotalCostRole && column >= NUM_BASE_COLUMNS) { + column -= NUM_BASE_COLUMNS; + column /= 2; + if (column % 2 == 0) { + return m_results.costsA.totalCost(column); + } + return m_results.costsB.totalCost(column); + } else if (role == Qt::ToolTipRole) { + if (column % 2 == 0) { + return Util::formatTooltip(row->costIdA, row->symbol, m_results.costsA); + } + return Util::formatTooltip(row->costIdB, row->symbol, m_results.costsB); + } + else { + return {}; + } +} + +int DiffViewModel::numColumns() const +{ + return NUM_BASE_COLUMNS + m_results.costsA.numTypes() + m_results.costsB.numTypes(); +} diff --git a/src/models/treemodel.h b/src/models/treemodel.h index f9fe0519f..ab4250b30 100644 --- a/src/models/treemodel.h +++ b/src/models/treemodel.h @@ -322,3 +322,26 @@ class TopDownModel : public CostTreeModel int numColumns() const final override; int selfCostColumn(int cost) const; }; + +class DiffViewModel : public CostTreeModel +{ + Q_OBJECT +public: + explicit DiffViewModel(QObject* parent = nullptr); + ~DiffViewModel(); + + enum Columns + { + Symbol = 0, + Binary, + }; + enum + { + NUM_BASE_COLUMNS = Binary + 1, + InitialSortColumn = Binary + 1 // the first cost column + }; + + QVariant headerColumnData(int column, int role) const final override; + QVariant rowData(const Data::DiffView* row, int column, int role) const final override; + int numColumns() const final override; +}; diff --git a/src/resultsdiffpage.ui b/src/resultsdiffpage.ui new file mode 100644 index 000000000..f98fe54db --- /dev/null +++ b/src/resultsdiffpage.ui @@ -0,0 +1,53 @@ + + + ResultsDiffPage + + + + 0 + 0 + 256 + 199 + + + + Inspect the profile data samples in an aggregated view, showing the bottom-up call-graph tree. + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Filter the call graph tree. + + + + + + + true + + + true + + + true + + + + + + + + diff --git a/src/startpage.cpp b/src/startpage.cpp index b00f8a4c6..d143b6330 100644 --- a/src/startpage.cpp +++ b/src/startpage.cpp @@ -42,6 +42,7 @@ StartPage::StartPage(QWidget* parent) connect(ui->recordDataButton, &QAbstractButton::clicked, this, &StartPage::recordButtonClicked); connect(ui->stopParseButton, &QAbstractButton::clicked, this, &StartPage::stopParseButtonClicked); connect(ui->pathSettings, &QAbstractButton::clicked, this, &StartPage::pathSettingsButtonClicked); + connect(ui->diffFilesButton, &QAbstractButton::clicked, this, &StartPage::diffFilesButtonClicked); ui->openFileButton->setFocus(); updateBackground(); diff --git a/src/startpage.h b/src/startpage.h index 58c793c12..2f92e5e02 100644 --- a/src/startpage.h +++ b/src/startpage.h @@ -58,6 +58,7 @@ public slots: void recordButtonClicked(); void stopParseButtonClicked(); void pathSettingsButtonClicked(); + void diffFilesButtonClicked(); private: void updateBackground(); diff --git a/src/startpage.ui b/src/startpage.ui index 59f924c4f..4d64f3256 100644 --- a/src/startpage.ui +++ b/src/startpage.ui @@ -7,7 +7,7 @@ 0 0 1081 - 340 + 346 @@ -134,6 +134,13 @@ + + + + Diff data + + + diff --git a/tests/test-clients/diff-view/fast.cpp b/tests/test-clients/diff-view/fast.cpp new file mode 100644 index 000000000..73bab652d --- /dev/null +++ b/tests/test-clients/diff-view/fast.cpp @@ -0,0 +1,22 @@ +void action1() +{ + for (int i = 0; i < 20'000'000; i++) { } +} + +void action2() +{ + for (int i = 0; i < 20'000'000; i++) { } +} + +void action3() +{ + for (int i = 0; i < 20'000'000; i++) { } +} + +int main() +{ + action1(); + action2(); + action3(); + return 0; +} diff --git a/tests/test-clients/diff-view/slow.cpp b/tests/test-clients/diff-view/slow.cpp new file mode 100644 index 000000000..65dd996d8 --- /dev/null +++ b/tests/test-clients/diff-view/slow.cpp @@ -0,0 +1,22 @@ +void action1() +{ + for (int i = 0; i < 60'000'000; i++) { } +} + +void action2() +{ + for (int i = 0; i < 60'000'000; i++) { } +} + +void action3() +{ + for (int i = 0; i < 60'000'000; i++) { } +} + +int main() +{ + action1(); + action2(); + action3(); + return 0; +}