Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug 379660] Collapse template arguments in flamegraph using checkbox #50

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 29 additions & 15 deletions src/analyze/gui/flamegraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<const ResultData> resultData, FrameGraphicsItem* parent = nullptr);
FrameGraphicsItem(const qint64 cost, const Symbol& symbol, std::shared_ptr<const ResultData> resultData,
FrameGraphicsItem(const qint64 cost, bool templateElision, const Symbol& symbol, std::shared_ptr<const ResultData> resultData,
FrameGraphicsItem* parent);

qint64 cost() const;
Expand All @@ -85,27 +85,29 @@ 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<const ResultData> resultData, FrameGraphicsItem* parent)
: QGraphicsRectItem(parent)
, m_resultData(std::move(resultData))
, m_cost(cost)
, 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<const ResultData> resultData, FrameGraphicsItem* parent)
: FrameGraphicsItem(cost, parent->m_costType, symbol, std::move(resultData), parent)
: FrameGraphicsItem(cost, templateElision, parent->m_costType, symbol, std::move(resultData), parent)
{
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -331,23 +334,23 @@ FrameGraphicsItem* findItemBySymbol(const QList<QGraphicsItem*>& items, const Sy
*/
void toGraphicsItems(const std::shared_ptr<const ResultData>& resultData, const QVector<RowData>& 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);
}
}
}
Expand All @@ -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);

Expand All @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -654,10 +667,11 @@ void FlameGraph::showData()

m_buildingScene = true;
bool collapseRecursion = m_collapseRecursion;
bool templateElision = m_templateElision;
auto source = m_costSource->currentData().value<CostType>();
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));
});
}
Expand Down
1 change: 1 addition & 0 deletions src/analyze/gui/flamegraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
26 changes: 26 additions & 0 deletions src/analyze/gui/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/analyze/gui/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions tests/auto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 44 additions & 0 deletions tests/auto/tst_template_elision.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
SPDX-FileCopyrightText: 2024 Aravind Vijayan <aravindev@live.in>

SPDX-License-Identifier: LGPL-2.1-or-later
*/

#include "analyze/gui/util.h"
#include <QString>
#include <QtTest>

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<QString>("test_case");
QTest::addColumn<QString>("result");

QTest::newRow("simple_case") << "MainWindow::onLoadingFinish(unsigned int&)"
<< "MainWindow::onLoadingFinish(unsigned int&)";
QTest::newRow("one_bracket") << "std::vector<test type in bracket> MainWindow::onLoadingFinish(unsigned int&)"
<< "std::vector<> MainWindow::onLoadingFinish(unsigned int&)";
QTest::newRow("two_brackets") << "std::vector<test type in bracket> MainWindow<vector_a>::onLoadingFinish(unsigned int&)"
<< "std::vector<> MainWindow<>::onLoadingFinish(unsigned int&)";
QTest::newRow("nested_brackets") << "std::vector<test type <int> in bracket> MainWindow::onLoadingFinish(unsigned int&)"
<< "std::vector<> MainWindow::onLoadingFinish(unsigned int&)";
}

QTEST_APPLESS_MAIN(TestTemplateElision)

#include "tst_template_elision.moc"