From 1db57a5bb04112d129f39eebcb7395da6db3ae5f Mon Sep 17 00:00:00 2001 From: Aravind Vijayan Date: Sun, 14 Jan 2024 18:15:34 +0100 Subject: [PATCH 1/2] [Bug 379660] Collapse template arguments in flamegraph using checkbox --- src/analyze/gui/flamegraph.cpp | 44 +++++++++++++++++++---------- src/analyze/gui/flamegraph.h | 1 + src/analyze/gui/util.cpp | 26 +++++++++++++++++ src/analyze/gui/util.h | 1 + tests/auto/CMakeLists.txt | 8 ++++++ tests/auto/tst_template_elision.cpp | 44 +++++++++++++++++++++++++++++ 6 files changed, 109 insertions(+), 15 deletions(-) create mode 100644 tests/auto/tst_template_elision.cpp diff --git a/src/analyze/gui/flamegraph.cpp b/src/analyze/gui/flamegraph.cpp index cf230292..fb108dbd 100644 --- a/src/analyze/gui/flamegraph.cpp +++ b/src/analyze/gui/flamegraph.cpp @@ -59,9 +59,9 @@ enum SearchMatchType class FrameGraphicsItem : public QGraphicsRectItem { public: - FrameGraphicsItem(const qint64 cost, CostType costType, const Symbol& symbol, + FrameGraphicsItem(const qint64 cost, bool templateElision, CostType costType, const Symbol& symbol, std::shared_ptr resultData, FrameGraphicsItem* parent = nullptr); - FrameGraphicsItem(const qint64 cost, const Symbol& symbol, std::shared_ptr resultData, + FrameGraphicsItem(const qint64 cost, bool templateElision, const Symbol& symbol, std::shared_ptr resultData, FrameGraphicsItem* parent); qint64 cost() const; @@ -85,12 +85,13 @@ class FrameGraphicsItem : public QGraphicsRectItem Symbol m_symbol; CostType m_costType; bool m_isHovered; + bool m_templateElision; SearchMatchType m_searchMatch = NoSearch; }; Q_DECLARE_METATYPE(FrameGraphicsItem*) -FrameGraphicsItem::FrameGraphicsItem(const qint64 cost, CostType costType, const Symbol& symbol, +FrameGraphicsItem::FrameGraphicsItem(const qint64 cost, bool templateElision, CostType costType, const Symbol& symbol, std::shared_ptr resultData, FrameGraphicsItem* parent) : QGraphicsRectItem(parent) , m_resultData(std::move(resultData)) @@ -98,14 +99,15 @@ FrameGraphicsItem::FrameGraphicsItem(const qint64 cost, CostType costType, const , m_symbol(symbol) , m_costType(costType) , m_isHovered(false) + , m_templateElision(templateElision) { setFlag(QGraphicsItem::ItemIsSelectable); setAcceptHoverEvents(true); } -FrameGraphicsItem::FrameGraphicsItem(const qint64 cost, const Symbol& symbol, +FrameGraphicsItem::FrameGraphicsItem(const qint64 cost, bool templateElision, const Symbol& symbol, std::shared_ptr resultData, FrameGraphicsItem* parent) - : FrameGraphicsItem(cost, parent->m_costType, symbol, std::move(resultData), parent) + : FrameGraphicsItem(cost, templateElision, parent->m_costType, symbol, std::move(resultData), parent) { } @@ -183,10 +185,11 @@ void FrameGraphicsItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* Q_UNREACHABLE(); }(); + const QString elidedLabel = m_templateElision ? Util::elideTemplateArguments(label) : label; const int height = rect().height(); painter->drawText(margin + rect().x(), rect().y(), width, height, Qt::AlignVCenter | Qt::AlignLeft | Qt::TextSingleLine, - option->fontMetrics.elidedText(label, Qt::ElideRight, width)); + option->fontMetrics.elidedText(elidedLabel, Qt::ElideRight, width)); if (m_searchMatch == NoMatch) { painter->setPen(oldPen); @@ -331,23 +334,23 @@ FrameGraphicsItem* findItemBySymbol(const QList& items, const Sy */ void toGraphicsItems(const std::shared_ptr& resultData, const QVector& data, FrameGraphicsItem* parent, int64_t AllocationData::*member, const double costThreshold, - bool collapseRecursion) + bool collapseRecursion, bool templateElision) { for (const auto& row : data) { if (collapseRecursion && row.symbol.functionId && row.symbol == parent->symbol()) { - toGraphicsItems(resultData, row.children, parent, member, costThreshold, collapseRecursion); + toGraphicsItems(resultData, row.children, parent, member, costThreshold, collapseRecursion, templateElision); continue; } auto item = findItemBySymbol(parent->childItems(), row.symbol); if (!item) { - item = new FrameGraphicsItem(row.cost.*member, row.symbol, resultData, parent); + item = new FrameGraphicsItem(row.cost.*member, templateElision, row.symbol, resultData, parent); item->setPen(parent->pen()); item->setBrush(brush()); } else { item->setCost(item->cost() + row.cost.*member); } if (item->cost() > costThreshold) { - toGraphicsItems(resultData, row.children, item, member, costThreshold, collapseRecursion); + toGraphicsItems(resultData, row.children, item, member, costThreshold, collapseRecursion, templateElision); } } } @@ -367,7 +370,7 @@ int64_t AllocationData::*memberForType(CostType type) Q_UNREACHABLE(); } -FrameGraphicsItem* parseData(const TreeData& data, CostType type, double costThreshold, bool collapseRecursion) +FrameGraphicsItem* parseData(const TreeData& data, CostType type, double costThreshold, bool collapseRecursion, bool templateElision) { auto member = memberForType(type); @@ -376,10 +379,10 @@ FrameGraphicsItem* parseData(const TreeData& data, CostType type, double costThr KColorScheme scheme(QPalette::Active); const QPen pen(scheme.foreground().color()); - auto rootItem = new FrameGraphicsItem(totalCost, type, {}, data.resultData); + auto rootItem = new FrameGraphicsItem(totalCost, templateElision, type, {}, data.resultData); rootItem->setBrush(scheme.background()); rootItem->setPen(pen); - toGraphicsItems(data.resultData, data.rows, rootItem, member, totalCost * costThreshold / 100., collapseRecursion); + toGraphicsItems(data.resultData, data.rows, rootItem, member, totalCost * costThreshold / 100., collapseRecursion, templateElision); return rootItem; } @@ -483,6 +486,15 @@ FlameGraph::FlameGraph(QWidget* parent) showData(); }); + auto templateElisionCheckbox = new QCheckBox(i18n("Collapse Template"), this); + templateElisionCheckbox->setChecked(m_templateElision); + templateElisionCheckbox->setToolTip(i18n("Collapse template arguments for readability. " + "When this is checked, templates will be replaced with <>")); + connect(templateElisionCheckbox, &QCheckBox::toggled, this, [this, templateElisionCheckbox] { + m_templateElision = templateElisionCheckbox->isChecked(); + showData(); + }); + auto costThreshold = new QDoubleSpinBox(this); costThreshold->setDecimals(2); costThreshold->setMinimum(0); @@ -515,6 +527,7 @@ FlameGraph::FlameGraph(QWidget* parent) controls->layout()->addWidget(m_costSource); controls->layout()->addWidget(bottomUpCheckbox); controls->layout()->addWidget(collapseRecursionCheckbox); + controls->layout()->addWidget(templateElisionCheckbox); controls->layout()->addWidget(costThreshold); controls->layout()->addWidget(m_searchInput); @@ -654,10 +667,11 @@ void FlameGraph::showData() m_buildingScene = true; bool collapseRecursion = m_collapseRecursion; + bool templateElision = m_templateElision; auto source = m_costSource->currentData().value(); auto threshold = m_costThreshold; - stream() << make_job([data, source, threshold, collapseRecursion, this]() { - auto parsedData = parseData(data, source, threshold, collapseRecursion); + stream() << make_job([data, source, threshold, collapseRecursion, templateElision, this]() { + auto parsedData = parseData(data, source, threshold, collapseRecursion, templateElision); QMetaObject::invokeMethod(this, "setData", Qt::QueuedConnection, Q_ARG(FrameGraphicsItem*, parsedData)); }); } diff --git a/src/analyze/gui/flamegraph.h b/src/analyze/gui/flamegraph.h index 580f7dab..6090989a 100644 --- a/src/analyze/gui/flamegraph.h +++ b/src/analyze/gui/flamegraph.h @@ -73,6 +73,7 @@ private slots: int m_selectedItem = -1; bool m_showBottomUpData = false; bool m_collapseRecursion = true; + bool m_templateElision = false; bool m_buildingScene = false; // cost threshold in percent, items below that value will not be shown double m_costThreshold = 0.1; diff --git a/src/analyze/gui/util.cpp b/src/analyze/gui/util.cpp index f2d6f37e..6ad955f0 100644 --- a/src/analyze/gui/util.cpp +++ b/src/analyze/gui/util.cpp @@ -27,6 +27,32 @@ QString Util::basename(const QString& path) return path.mid(idx + 1); } +QString Util::elideTemplateArguments(const QString& s) +{ + const auto startBracket = QLatin1Char('<'); + const auto stopBracket = QLatin1Char('>'); + + int level = 0; + QString result; + result.reserve(s.size()); + for (auto currentChar : s) { + if (currentChar == startBracket) { + if (level == 0) { + result += startBracket; + } + ++level; + } else if (currentChar == stopBracket) { + if (level == 1) { + result += stopBracket; + } + --level; + } else if (level == 0) { + result += currentChar; + } + } + return result; +} + QString Util::formatString(const QString& input) { return input.isEmpty() ? i18n("??") : input; diff --git a/src/analyze/gui/util.h b/src/analyze/gui/util.h index c3c7162e..7b5897b6 100644 --- a/src/analyze/gui/util.h +++ b/src/analyze/gui/util.h @@ -18,6 +18,7 @@ class ResultData; namespace Util { QString basename(const QString& path); +QString elideTemplateArguments(const QString& s); QString formatString(const QString& input); QString formatTime(qint64 ms); QString formatBytes(qint64 bytes); diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt index a2b9539f..cbd14ba5 100644 --- a/tests/auto/CMakeLists.txt +++ b/tests/auto/CMakeLists.txt @@ -69,6 +69,14 @@ if ("${Boost_FILESYSTEM_FOUND}" AND "${Boost_SYSTEM_FOUND}") if (TARGET heaptrack_gui_private) find_package(Qt${QT_VERSION_MAJOR} ${QT_MIN_VERSION} CONFIG OPTIONAL_COMPONENTS Test) if (Qt${QT_VERSION_MAJOR}Test_FOUND) + set(CMAKE_AUTOMOC ON) + add_executable(test_template_elision tst_template_elision.cpp) + target_link_libraries(test_template_elision + heaptrack_gui_private + Qt${QT_VERSION_MAJOR}::Test + ) + add_test(NAME test_template_elision COMMAND test_template_elision) + add_executable(tst_parser tst_parser.cpp) set_target_properties(tst_parser PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/${BIN_INSTALL_DIR}") target_link_libraries(tst_parser diff --git a/tests/auto/tst_template_elision.cpp b/tests/auto/tst_template_elision.cpp new file mode 100644 index 00000000..a90cc5d7 --- /dev/null +++ b/tests/auto/tst_template_elision.cpp @@ -0,0 +1,44 @@ +/* + SPDX-FileCopyrightText: 2024 Aravind Vijayan + + SPDX-License-Identifier: LGPL-2.1-or-later +*/ + +#include "analyze/gui/util.h" +#include +#include + +class TestTemplateElision : public QObject +{ + Q_OBJECT + +private slots: + void testTemplateElision(); + void testTemplateElision_data(); +}; + +void TestTemplateElision::testTemplateElision() +{ + QFETCH(QString, test_case); + QFETCH(QString, result); + QCOMPARE(result, Util::elideTemplateArguments(test_case)); +} + +void TestTemplateElision::testTemplateElision_data() +{ + QTest::addColumn("test_case"); + QTest::addColumn("result"); + + QTest::newRow("simple_case") << "MainWindow::onLoadingFinish(unsigned int&)" + << "MainWindow::onLoadingFinish(unsigned int&)"; + QTest::newRow("one_bracket") << "std::vector MainWindow::onLoadingFinish(unsigned int&)" + << "std::vector<> MainWindow::onLoadingFinish(unsigned int&)"; + QTest::newRow("two_brackets") << "std::vector MainWindow::onLoadingFinish(unsigned int&)" + << "std::vector<> MainWindow<>::onLoadingFinish(unsigned int&)"; + QTest::newRow("nested_brackets") << "std::vector in bracket> MainWindow::onLoadingFinish(unsigned int&)" + << "std::vector<> MainWindow::onLoadingFinish(unsigned int&)"; +} + +QTEST_APPLESS_MAIN(TestTemplateElision) + +#include "tst_template_elision.moc" \ No newline at end of file From 5cf23e04109b3ccba6fd001e5df7ec44653a645e Mon Sep 17 00:00:00 2001 From: Aravind Vijayan Date: Sun, 14 Jan 2024 18:17:52 +0100 Subject: [PATCH 2/2] add EOL --- tests/auto/tst_template_elision.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/auto/tst_template_elision.cpp b/tests/auto/tst_template_elision.cpp index a90cc5d7..a635bd89 100644 --- a/tests/auto/tst_template_elision.cpp +++ b/tests/auto/tst_template_elision.cpp @@ -41,4 +41,4 @@ void TestTemplateElision::testTemplateElision_data() QTEST_APPLESS_MAIN(TestTemplateElision) -#include "tst_template_elision.moc" \ No newline at end of file +#include "tst_template_elision.moc"