Skip to content

Commit

Permalink
show diff data on flamegraph
Browse files Browse the repository at this point in the history
  • Loading branch information
lievenhey committed Sep 15, 2022
1 parent 319d3bd commit 4639c6f
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 50 deletions.
130 changes: 101 additions & 29 deletions src/flamegraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@ enum SearchMatchType
class FrameGraphicsItem : public QGraphicsRectItem
{
public:
FrameGraphicsItem(const qint64 cost, const Data::Symbol& symbol, FrameGraphicsItem* parent);
FrameGraphicsItem(const qint64 cost, qint64 secondaryCost, const Data::Symbol& symbol, FrameGraphicsItem* parent);

qint64 cost() const;
void setCost(qint64 cost);
qint64 secondaryCost() const;
void setCost(qint64 cost, qint64 secondaryCost);
Data::Symbol symbol() const;

void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override;
Expand All @@ -76,6 +77,7 @@ class FrameGraphicsItem : public QGraphicsRectItem

private:
qint64 m_cost;
qint64 m_secondaryCost = 0;
Data::Symbol m_symbol;
bool m_isHovered;
bool m_isExternallyHovered;
Expand All @@ -87,7 +89,7 @@ class FrameGraphicsRootItem : public FrameGraphicsItem
{
public:
FrameGraphicsRootItem(const qint64 totalCost, Data::Costs::Unit unit, const QString& costName, const QString& label)
: FrameGraphicsItem(totalCost, {label, {}}, nullptr)
: FrameGraphicsItem(totalCost, 0, {label, {}}, nullptr)
, m_costName(costName)
, m_unit(unit)
{
Expand All @@ -109,9 +111,11 @@ class FrameGraphicsRootItem : public FrameGraphicsItem

Q_DECLARE_METATYPE(FrameGraphicsRootItem*)

FrameGraphicsItem::FrameGraphicsItem(const qint64 cost, const Data::Symbol& symbol, FrameGraphicsItem* parent)
FrameGraphicsItem::FrameGraphicsItem(const qint64 cost, const qint64 secondaryCost, const Data::Symbol& symbol,
FrameGraphicsItem* parent)
: QGraphicsRectItem(parent)
, m_cost(cost)
, m_secondaryCost(secondaryCost)
, m_symbol(symbol)
, m_isHovered(false)
, m_isExternallyHovered(false)
Expand All @@ -125,9 +129,15 @@ qint64 FrameGraphicsItem::cost() const
return m_cost;
}

void FrameGraphicsItem::setCost(qint64 cost)
qint64 FrameGraphicsItem::secondaryCost() const
{
return m_secondaryCost;
}

void FrameGraphicsItem::setCost(qint64 cost, qint64 secondaryCost)
{
m_cost = cost;
m_secondaryCost = secondaryCost;
}

Data::Symbol FrameGraphicsItem::symbol() const
Expand All @@ -137,16 +147,48 @@ Data::Symbol FrameGraphicsItem::symbol() const

void FrameGraphicsItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* /*widget*/)
{
auto fillRect = [painter, this](const QColor& color) {
if (secondaryCost() == 0) {
painter->fillRect(rect(), color);
return;
}

const QColor secondaryColor =
QColor::fromHsv((color.hue() + 120) % 360, color.saturation(), color.value(), color.alpha());

if (cost() > secondaryCost()) {
const auto width = rect().width() * static_cast<float>(secondaryCost()) / (cost() + secondaryCost());
auto mainRect = rect();
mainRect.setWidth(rect().width() - width);
painter->fillRect(mainRect, color);
auto secondaryRect = rect();
secondaryRect.setWidth(width);
secondaryRect.moveRight(rect().right());
painter->fillRect(secondaryRect, secondaryColor);
} else {
const auto width = rect().width() * static_cast<float>(cost()) / (secondaryCost() + cost());
auto mainRect = rect();
// mainRect.setWidth(rect().width() - width);
mainRect.moveRight(rect().right());
painter->fillRect(mainRect, color);
auto secondaryRect = rect();
secondaryRect.setWidth(width);
secondaryRect.moveRight(rect().right());

painter->fillRect(secondaryRect, secondaryColor);
}
};

if (isSelected() || m_isHovered || m_isExternallyHovered || m_searchMatch == DirectMatch) {
auto selectedColor = brush().color();
selectedColor.setAlpha(255);
painter->fillRect(rect(), selectedColor);
fillRect(selectedColor);
} else if (m_searchMatch == NoMatch) {
auto noMatchColor = brush().color();
noMatchColor.setAlpha(50);
painter->fillRect(rect(), noMatchColor);
fillRect(noMatchColor);
} else { // default, when no search is running, or a sub-item is matched
painter->fillRect(rect(), brush());
fillRect(brush().color());
}

const QPen oldPen = painter->pen();
Expand Down Expand Up @@ -429,32 +471,42 @@ FrameGraphicsItem* findItemBySymbol(const QList<QGraphicsItem*>& items, const Da
* Convert the top-down graph into a tree of FrameGraphicsItem.
*/
template<typename Tree>
void toGraphicsItems(const Data::Costs& costs, int type, const QVector<Tree>& data, FrameGraphicsItem* parent,
const double costThreshold, const Settings::ColorScheme& colorScheme, bool collapseRecursion)
void toGraphicsItems(const Data::Costs& costs, const Data::Costs& secondaryCosts, int type, const QVector<Tree>& data,
FrameGraphicsItem* parent, const double costThreshold, const Settings::ColorScheme& colorScheme,
bool collapseRecursion)
{
foreach (const auto& row, data) {
if (collapseRecursion && !row.symbol.symbol.isEmpty() && row.symbol == parent->symbol()) {
if (costs.cost(type, row.id) > costThreshold) {
toGraphicsItems(costs, type, row.children, parent, costThreshold, colorScheme, collapseRecursion);
toGraphicsItems(costs, secondaryCosts, type, row.children, parent, costThreshold, colorScheme,
collapseRecursion);
}
continue;
}
auto item = findItemBySymbol(parent->childItems(), row.symbol);

int secondaryCost = 0;
if (secondaryCosts.numTypes() > 0) {
secondaryCost = secondaryCosts.cost(type, row.id);
}

if (!item) {
item = new FrameGraphicsItem(costs.cost(type, row.id), row.symbol, parent);
item = new FrameGraphicsItem(costs.cost(type, row.id), secondaryCost, row.symbol, parent);
item->setPen(parent->pen());
item->setBrush(brush(row.symbol, colorScheme));
} else {
item->setCost(item->cost() + costs.cost(type, row.id));
item->setCost(item->cost() + costs.cost(type, row.id), item->secondaryCost() + secondaryCost);
}
if (item->cost() > costThreshold) {
toGraphicsItems(costs, type, row.children, item, costThreshold, colorScheme, collapseRecursion);
toGraphicsItems(costs, secondaryCosts, type, row.children, item, costThreshold, colorScheme,
collapseRecursion);
}
}
}

template<typename Tree>
FrameGraphicsItem* parseData(const Data::Costs& costs, int type, const QVector<Tree>& topDownData, double costThreshold,
FrameGraphicsItem* parseData(const Data::Costs& costs, const Data::Costs& secondaryCosts, int type,
const QVector<Tree>& topDownData, double costThreshold,
const Settings::ColorScheme& colorScheme, bool collapseRecursion)
{
const auto totalCost = costs.totalCost(type);
Expand All @@ -466,8 +518,8 @@ FrameGraphicsItem* parseData(const Data::Costs& costs, int type, const QVector<T
auto rootItem = new FrameGraphicsRootItem(totalCost, costs.unit(type), costs.typeName(type), label);
rootItem->setBrush(scheme.background());
rootItem->setPen(pen);
toGraphicsItems(costs, type, topDownData, rootItem, static_cast<double>(totalCost) * costThreshold / 100.,
colorScheme, collapseRecursion);
toGraphicsItems(costs, secondaryCosts, type, topDownData, rootItem,
static_cast<double>(totalCost) * costThreshold / 100., colorScheme, collapseRecursion);
return rootItem;
}

Expand Down Expand Up @@ -845,6 +897,16 @@ void FlameGraph::setTopDownData(const Data::TopDownResults& topDownData)
rebuild();
}

void FlameGraph::setSecondaryTopDownData(const Data::TopDownResults& topDownData,
const Data::TopDownResults& secondaryTopDown)
{
m_topDownData = topDownData;
m_secondaryTopDownData = secondaryTopDown;

if (!m_showBottomUpData)
rebuild();
}

void FlameGraph::setBottomUpData(const Data::BottomUpResults& bottomUpData)
{
m_bottomUpData = bottomUpData;
Expand All @@ -859,6 +921,14 @@ void FlameGraph::setBottomUpData(const Data::BottomUpResults& bottomUpData)
rebuild();
}

void FlameGraph::setBottomUpDataDiff(const Data::BottomUpResults& resultsA, const Data::BottomUpResults& resultsB)
{
m_bottomUpData = resultsA;
m_bottomUpDataSecond = resultsB;

rebuild();
}

void FlameGraph::rebuild()
{
if (isVisible()) {
Expand Down Expand Up @@ -931,23 +1001,25 @@ void FlameGraph::showData()
m_buildingScene = true;
using namespace ThreadWeaver;
auto bottomUpData = m_bottomUpData;
auto secondaryBottomUpData = m_bottomUpDataSecond;
auto topDownData = m_topDownData;
auto secondaryTopDownData = m_secondaryTopDownData;
bool collapseRecursion = m_collapseRecursion;
auto type = m_costSource->currentData().value<int>();
auto threshold = m_costThreshold;
const auto colorScheme = Settings::instance()->colorScheme();
stream() << make_job(
[showBottomUpData, bottomUpData, topDownData, type, threshold, colorScheme, collapseRecursion, this]() {
FrameGraphicsItem* parsedData = nullptr;
if (showBottomUpData) {
parsedData = parseData(bottomUpData.costs, type, bottomUpData.root.children, threshold, colorScheme,
collapseRecursion);
} else {
parsedData = parseData(topDownData.inclusiveCosts, type, topDownData.root.children, threshold,
colorScheme, collapseRecursion);
}
QMetaObject::invokeMethod(this, "setData", Qt::QueuedConnection, Q_ARG(FrameGraphicsItem*, parsedData));
});
stream() << make_job([showBottomUpData, bottomUpData, secondaryBottomUpData, topDownData, secondaryTopDownData,
type, threshold, colorScheme, collapseRecursion, this]() {
FrameGraphicsItem* parsedData = nullptr;
if (showBottomUpData) {
parsedData = parseData(bottomUpData.costs, secondaryBottomUpData.costs, type, bottomUpData.root.children,
threshold, colorScheme, collapseRecursion);
} else {
parsedData = parseData(topDownData.inclusiveCosts, secondaryTopDownData.inclusiveCosts, type,
topDownData.root.children, threshold, colorScheme, collapseRecursion);
}
QMetaObject::invokeMethod(this, "setData", Qt::QueuedConnection, Q_ARG(FrameGraphicsItem*, parsedData));
});
updateNavigationActions();
}

Expand Down
4 changes: 4 additions & 0 deletions src/flamegraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ class FlameGraph : public QWidget
void setHoveredStacks(const QVector<QVector<Data::Symbol>>& stacks);
void setFilterStack(FilterAndZoomStack* filterStack);
void setTopDownData(const Data::TopDownResults& topDownData);
void setSecondaryTopDownData(const Data::TopDownResults& topDownData, const Data::TopDownResults& secondaryTopDown);
void setBottomUpData(const Data::BottomUpResults& bottomUpData);
void setBottomUpDataDiff(const Data::BottomUpResults& resultsA, const Data::BottomUpResults& resultsB);
void clear();

QImage toImage() const;
Expand Down Expand Up @@ -67,7 +69,9 @@ private slots:
void rebuild();

Data::TopDownResults m_topDownData;
Data::TopDownResults m_secondaryTopDownData = {};
Data::BottomUpResults m_bottomUpData;
Data::BottomUpResults m_bottomUpDataSecond = {};

FilterAndZoomStack* m_filterStack = nullptr;
QComboBox* m_costSource;
Expand Down
83 changes: 62 additions & 21 deletions src/resultsflamegraphpage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,37 +31,39 @@ QString imageFormatFilter()
filter.chop(1); // remove trailing whitespace
return filter;
}

template<typename ResultType>
void mergeCostIds(const ResultType& a, const ResultType* b, ResultType* result_node, const Data::Costs& costs_a,
const Data::Costs& costs_b, Data::Costs* costs_result)
{
for (const auto& node : a.children) {
const auto sibling = b->entryForSymbol(node.symbol);
if (sibling) {
ResultType diffed;
diffed.id = node.id;
diffed.symbol = node.symbol;

for (int i = 0; i < costs_b.numTypes(); i++) {
costs_result->add(i, diffed.id, costs_b.cost(i, sibling->id));
}

result_node->children.push_back(diffed);
mergeCostIds(node, sibling, &result_node->children.back(), costs_a, costs_b, costs_result);
}
}
}
}

ResultsFlameGraphPage::ResultsFlameGraphPage(FilterAndZoomStack* filterStack, PerfParser* parser, QMenu* exportMenu,
QWidget* parent)
: QWidget(parent)
, ui(new Ui::ResultsFlameGraphPage)
, m_exportMenu(exportMenu)
{
ui->setupUi(this);
ui->flameGraph->setFilterStack(filterStack);

connect(parser, &PerfParser::bottomUpDataAvailable, this, [this, exportMenu](const Data::BottomUpResults& data) {
ui->flameGraph->setBottomUpData(data);
m_exportAction = exportMenu->addAction(QIcon::fromTheme(QStringLiteral("image-x-generic")), tr("Flamegraph"));
connect(m_exportAction, &QAction::triggered, this, [this]() {
const auto filter = tr("Images (%1);;SVG (*.svg)").arg(imageFormatFilter());
QString selectedFilter;
const auto fileName =
QFileDialog::getSaveFileName(this, tr("Export Flamegraph"), {}, filter, &selectedFilter);
if (fileName.isEmpty())
return;
if (selectedFilter.contains(QStringLiteral("svg"))) {
ui->flameGraph->saveSvg(fileName);
} else {
QImageWriter writer(fileName);
if (!writer.write(ui->flameGraph->toImage())) {
QMessageBox::warning(this, tr("Export Failed"),
tr("Failed to export flamegraph: %1").arg(writer.errorString()));
}
}
});
});
connect(parser, &PerfParser::bottomUpDataAvailable, this, &ResultsFlameGraphPage::setBottomUpData);

connect(parser, &PerfParser::topDownDataAvailable, this,
[this](const Data::TopDownResults& data) { ui->flameGraph->setTopDownData(data); });
Expand All @@ -86,3 +88,42 @@ void ResultsFlameGraphPage::setHoveredStacks(const QVector<QVector<Data::Symbol>
{
ui->flameGraph->setHoveredStacks(hoveredStacks);
}

void ResultsFlameGraphPage::setBottomUpData(const Data::BottomUpResults& data)
{
ui->flameGraph->setBottomUpData(data);
m_exportAction = m_exportMenu->addAction(QIcon::fromTheme(QStringLiteral("image-x-generic")), tr("Flamegraph"));
connect(m_exportAction, &QAction::triggered, this, [this]() {
const auto filter = tr("Images (%1);;SVG (*.svg)").arg(imageFormatFilter());
QString selectedFilter;
const auto fileName = QFileDialog::getSaveFileName(this, tr("Export Flamegraph"), {}, filter, &selectedFilter);
if (fileName.isEmpty())
return;
if (selectedFilter.contains(QStringLiteral("svg"))) {
ui->flameGraph->saveSvg(fileName);
} else {
QImageWriter writer(fileName);
if (!writer.write(ui->flameGraph->toImage())) {
QMessageBox::warning(this, tr("Export Failed"),
tr("Failed to export flamegraph: %1").arg(writer.errorString()));
}
}
});
}

void ResultsFlameGraphPage::setBottomUpDataDiff(const Data::BottomUpResults& dataA, const Data::BottomUpResults& dataB)
{
Data::BottomUpResults merged;
merged.costs.initializeCostsFrom(dataA.costs);
mergeCostIds(dataA.root, &dataB.root, &merged.root, dataA.costs, dataB.costs, &merged.costs);
ui->flameGraph->setBottomUpDataDiff(dataA, merged);
}

void ResultsFlameGraphPage::setTopDownDataDiff(const Data::TopDownResults& dataA, const Data::TopDownResults& dataB)
{
Data::TopDownResults merged;
merged.inclusiveCosts.initializeCostsFrom(dataA.inclusiveCosts);
mergeCostIds(dataA.root, &dataB.root, &merged.root, dataA.inclusiveCosts, dataB.inclusiveCosts,
&merged.inclusiveCosts);
ui->flameGraph->setSecondaryTopDownData(dataA, merged);
}
8 changes: 8 additions & 0 deletions src/resultsflamegraphpage.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class ResultsFlameGraphPage;

namespace Data {
struct Symbol;
struct BottomUpResults;
struct TopDownResults;
}

class PerfParser;
Expand All @@ -43,7 +45,13 @@ class ResultsFlameGraphPage : public QWidget
void selectStack(const QVector<Data::Symbol>& stack);
void jumpToDisassembly(const Data::Symbol& symbol);

public slots:
void setBottomUpData(const Data::BottomUpResults& data);
void setBottomUpDataDiff(const Data::BottomUpResults& dataA, const Data::BottomUpResults& dataB);
void setTopDownDataDiff(const Data::TopDownResults& dataA, const Data::TopDownResults& dataB);

private:
QScopedPointer<Ui::ResultsFlameGraphPage> ui;
QMenu* m_exportMenu = nullptr;
QAction* m_exportAction = nullptr;
};
Loading

0 comments on commit 4639c6f

Please sign in to comment.