From d986ab91d2658fc6f11ed69a307c0f591ff82a23 Mon Sep 17 00:00:00 2001 From: Milian Wolff Date: Fri, 21 Aug 2015 23:49:20 +0200 Subject: [PATCH] Add initial take on a FlameGraph based on QGraphicsView. Many issues still, mostly due to bad scaling, i.e. we'd want constant text size (elided if parent item is too small), and constant item height based on text height. --- gui/CMakeLists.txt | 1 + gui/flamegraph.cpp | 194 +++++++++++++++++++++++++++++++++++++++++++++++++++++ gui/flamegraph.h | 61 +++++++++++++++++ gui/mainwindow.cpp | 2 + gui/mainwindow.ui | 17 +++-- gui/parser.cpp | 23 +++++++ gui/parser.h | 2 + 7 files changed, 294 insertions(+), 6 deletions(-) create mode 100644 gui/flamegraph.cpp create mode 100644 gui/flamegraph.h diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index e0375c2..d0f288e 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -21,6 +21,7 @@ add_executable(heaptrack_gui chartproxy.cpp modeltest.cpp parser.cpp + flamegraph.cpp ${UIFILES} ) diff --git a/gui/flamegraph.cpp b/gui/flamegraph.cpp new file mode 100644 index 0000000..f3f2f07 --- /dev/null +++ b/gui/flamegraph.cpp @@ -0,0 +1,194 @@ +/* + * Copyright 2015 Milian Wolff + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "flamegraph.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace { + +QColor color(quint64 cost, quint64 maxCost) +{ + const double ratio = double(cost) / maxCost; + return QColor::fromHsv(120 - ratio * 120, 255, 255, (-((ratio-1) * (ratio-1))) * 120 + 120); +} + +/* +// TODO: aggregate top-down instead of bottom-up to better resemble +// other flame graphs with the culprits on top instead of on bottom +void aggregateStack(TreeLeafItem* item, StackData* data) +{ + const QByteArray label = isBelowThreshold(item->label()) ? QByteArray() : functionInLabel(item->label()); + + Frame& frame = (*data)[label]; + frame.cost = qMax(item->cost(), frame.cost); + + foreach(TreeLeafItem* child, item->children()) { + aggregateStack(child, &frame.children); + } +}*/ + +class FrameGraphicsItem : public QGraphicsRectItem +{ +public: + FrameGraphicsItem(const QRectF& rect, const quint64 cost, const QByteArray& function) + : QGraphicsRectItem(rect) + { + static const QString emptyLabel = QStringLiteral("???"); + + m_label = i18nc("%1: memory cost, %2: function label", + "%2: %1", + cost, + function.isEmpty() ? emptyLabel : QString::fromUtf8(function)); + setToolTip(m_label); + } + + virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0) + { + QGraphicsRectItem::paint(painter, option, widget); + + // TODO: text should always be displayed in a constant size and not zoomed + // TODO: items should only be scaled horizontally, not vertically + // TODO: items should "fit" into the view width + static QFontMetrics m(QFont(QStringLiteral("monospace"))); + const int margin = 5; + const int width = rect().width() - 2 * margin; + if (width < m.averageCharWidth() * 6) { + return; + } + const int height = rect().height(); + + const QPen oldPen = painter->pen(); + QPen pen = oldPen; + pen.setColor(Qt::white); + painter->setPen(pen); + painter->drawText(margin + rect().x(), rect().y(), width, height, Qt::AlignCenter | Qt::TextSingleLine, m.elidedText(m_label, Qt::ElideRight, width)); + painter->setPen(oldPen); + } + +private: + QString m_label; +}; + +// TODO: what is the right value for maxWidth here? +QVector toGraphicsItems(const FlameGraphData::Stack& data, + const qreal x_0 = 0, const qreal y_0 = 0, + const qreal maxWidth = 800., qreal totalCostForColor = 0) +{ + QVector ret; + ret.reserve(data.size()); + + double totalCost = 0; + foreach(const auto& frame, data) { + totalCost += frame.cost; + } + if (!totalCostForColor) { + totalCostForColor = totalCost; + } + qDebug() << "graphicsitem:" << totalCost << totalCostForColor; + + qreal x = x_0; + const qreal h = 25; + const qreal y = y_0; + + const qreal x_margin = 0; + const qreal y_margin = 2; + + for (auto it = data.constBegin(); it != data.constEnd(); ++it) { + const qreal w = maxWidth * double(it.value().cost) / totalCostForColor; + FrameGraphicsItem* item = new FrameGraphicsItem(QRectF(x, y, w, h), it.value().cost, it.key()); + item->setBrush(color(it.value().cost, totalCostForColor)); + ret += toGraphicsItems(it.value().children, x, y - h - y_margin, w, totalCostForColor); + x += w + x_margin; + ret << item; + } + + return ret; +} + +} + +FlameGraph::FlameGraph(QWidget* parent, Qt::WindowFlags flags) + : QWidget(parent, flags) + , m_scene(new QGraphicsScene(this)) + , m_view(new QGraphicsView(this)) +{ + qRegisterMetaType(); + + setLayout(new QVBoxLayout); + + m_view->setScene(m_scene); + m_view->viewport()->installEventFilter(this); + + layout()->addWidget(m_view); +} + +FlameGraph::~FlameGraph() +{ + +} + +bool FlameGraph::eventFilter(QObject* object, QEvent* event) +{ + if (object == m_view->viewport() && event->type() == QEvent::Wheel) { + QWheelEvent* wheelEvent = static_cast(event); + if (wheelEvent->modifiers() == Qt::ControlModifier) { + // zoom view with Ctrl + mouse wheel + qreal scale = pow(1.1, double(wheelEvent->delta()) / (120.0 * 2.)); + m_view->scale(scale, scale); + return true; + } + + } + return QObject::eventFilter(object, event); +} + +void FlameGraph::setData(const FlameGraphData& data) +{ + m_data = data; + m_scene->clear(); + + qDebug() << "Evaluating flame graph"; + QElapsedTimer t; t.start(); + + foreach(QGraphicsItem* item, toGraphicsItems(data.stack)) { + m_scene->addItem(item); + } + + qDebug() << m_scene->itemsBoundingRect() << m_scene->sceneRect() << m_view->rect() << m_view->contentsRect(); + m_view->fitInView( m_scene->itemsBoundingRect(), Qt::KeepAspectRatio ); + // TODO: what is the correct scale value here?! without it, the contents in the view are teeny tiny! + m_view->scale(5, 5); + + qDebug() << "took me: " << t.elapsed(); +} diff --git a/gui/flamegraph.h b/gui/flamegraph.h new file mode 100644 index 0000000..4ca0fdf --- /dev/null +++ b/gui/flamegraph.h @@ -0,0 +1,61 @@ +/* + * Copyright 2015 Milian Wolff + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef FLAMEGRAPH_H +#define FLAMEGRAPH_H + +#include +#include + +class QGraphicsScene; +class QGraphicsView; + +struct FlameGraphData +{ + struct Frame { + quint64 cost; + QMap children; + }; + + using Stack = QMap; + + Stack stack; +}; + +Q_DECLARE_METATYPE(FlameGraphData); + +class FlameGraph : public QWidget +{ + Q_OBJECT +public: + FlameGraph(QWidget* parent = 0, Qt::WindowFlags flags = 0); + ~FlameGraph(); + + void setData(const FlameGraphData& data); + +protected: + virtual bool eventFilter(QObject* object, QEvent* event); + +private: + QGraphicsScene* m_scene; + QGraphicsView* m_view; + FlameGraphData m_data; +}; + +#endif // FLAMEGRAPH_H diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 228a8b0..6a00f85 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -67,6 +67,8 @@ MainWindow::MainWindow(QWidget* parent) m_chartModel, &ChartModel::resetData); connect(m_parser, &Parser::summaryAvailable, m_ui->summary, &QLabel::setText); + connect(m_parser, &Parser::flameGraphDataAvailable, + m_ui->flameGraphTab, &FlameGraph::setData); connect(m_parser, &Parser::finished, this, [&] { m_ui->pages->setCurrentWidget(m_ui->resultsPage); }); diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui index bee5db5..8245b9f 100644 --- a/gui/mainwindow.ui +++ b/gui/mainwindow.ui @@ -159,12 +159,6 @@ - results - filterFile - filterFunction - filterModule - widget - results @@ -181,6 +175,11 @@ Allocated + + + Flame Graph + + @@ -197,6 +196,12 @@
chartwidget.h
1 + + FlameGraph + QWidget +
flamegraph.h
+ 1 +
diff --git a/gui/parser.cpp b/gui/parser.cpp index 66ce6a7..b822697 100644 --- a/gui/parser.cpp +++ b/gui/parser.cpp @@ -179,6 +179,27 @@ Parser::Parser(QObject* parent) Parser::~Parser() = default; +static FlameGraphData::Stack fakeStack(int children, int recurse, int* id = 0, quint64* parentCost = 0) +{ + int stack_id = 1; + FlameGraphData::Stack data; + if (!id) { + id = &stack_id; + } + for (int i = 0; i < children; ++i) { + FlameGraphData::Frame frame; + frame.cost = (i + 1) * 2; + if (recurse) { + frame.children = fakeStack(children - 1, recurse - 1, id, &frame.cost); + } + if (parentCost) { + parentCost += frame.cost; + } + data[QByteArray::number((*id)++)] = frame; + } + return data; +} + void Parser::parse(const QString& path) { using namespace ThreadWeaver; @@ -188,6 +209,8 @@ void Parser::parse(const QString& path) emit summaryAvailable(generateSummary(data)); emit bottomUpDataAvailable(mergeAllocations(data)); emit chartDataAvailable(data.chartData); + // TODO: implement this + emit flameGraphDataAvailable({fakeStack(4, 4)}); emit finished(); }); } diff --git a/gui/parser.h b/gui/parser.h index 9307c63..3bd4a1f 100644 --- a/gui/parser.h +++ b/gui/parser.h @@ -24,6 +24,7 @@ #include "bottomupmodel.h" #include "chartmodel.h" +#include "flamegraph.h" class Parser : public QObject { @@ -39,6 +40,7 @@ signals: void summaryAvailable(const QString& summary); void bottomUpDataAvailable(const BottomUpData& data); void chartDataAvailable(const ChartData& data); + void flameGraphDataAvailable(const FlameGraphData& data); void finished(); }; -- 2.7.4