FlameGraph: Allow zooming into items

Double clicking an item will now rebuild the flame graph with that item
as root. Double clicking on an empty area will reset the root.

Change-Id: I16dd4b00d0dd09ff922a01acee67f0d553da6323
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
Reviewed-by: Milian Wolff <milian.wolff@kdab.com>
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Ulf Hermann
2018-01-23 18:03:14 +01:00
parent 438db9a488
commit 27c51ed4c8
5 changed files with 66 additions and 10 deletions

View File

@@ -30,6 +30,9 @@ namespace FlameGraph {
FlameGraph::FlameGraph(QQuickItem *parent) : FlameGraph::FlameGraph(QQuickItem *parent) :
QQuickItem(parent) QQuickItem(parent)
{ {
// Queue the rebuild in this case so that a delegate can set the root without getting deleted
// during the call.
connect(this, &FlameGraph::rootChanged, this, &FlameGraph::rebuild, Qt::QueuedConnection);
} }
QQmlComponent *FlameGraph::delegate() const QQmlComponent *FlameGraph::delegate() const
@@ -142,7 +145,7 @@ int FlameGraph::buildNode(const QModelIndex &parentIndex, QObject *parentObject,
for (int row = 0; row < rowCount; ++row) { for (int row = 0; row < rowCount; ++row) {
QModelIndex childIndex = m_model->index(row, 0, parentIndex); QModelIndex childIndex = m_model->index(row, 0, parentIndex);
qreal size = m_model->data(childIndex, m_sizeRole).toReal(); qreal size = m_model->data(childIndex, m_sizeRole).toReal();
if (size / m_model->data(QModelIndex(), m_sizeRole).toReal() < m_sizeThreshold) { if (size / m_model->data(m_root, m_sizeRole).toReal() < m_sizeThreshold) {
skipped += size; skipped += size;
continue; continue;
} }
@@ -155,7 +158,7 @@ int FlameGraph::buildNode(const QModelIndex &parentIndex, QObject *parentObject,
} }
} }
// Root object: attribute all remaining width to "others" // Invisible Root object: attribute all remaining width to "others"
if (!parentIndex.isValid()) if (!parentIndex.isValid())
skipped = parentSize - position; skipped = parentSize - position;
@@ -179,7 +182,13 @@ void FlameGraph::rebuild()
return; return;
} }
m_depth = buildNode(QModelIndex(), this, 0, m_maximumDepth); if (m_root.isValid()) {
QObject *parentObject = appendChild(this, this, qmlContext(this), m_root, 0, 1);
m_depth = buildNode(m_root, parentObject, 1, m_maximumDepth);
} else {
m_depth = buildNode(m_root, this, 0, m_maximumDepth);
}
emit depthChanged(m_depth); emit depthChanged(m_depth);
} }

View File

@@ -44,6 +44,7 @@ class FLAMEGRAPH_EXPORT FlameGraph : public QQuickItem
Q_PROPERTY(int maximumDepth READ maximumDepth WRITE setMaximumDepth Q_PROPERTY(int maximumDepth READ maximumDepth WRITE setMaximumDepth
NOTIFY maximumDepthChanged) NOTIFY maximumDepthChanged)
Q_PROPERTY(int depth READ depth NOTIFY depthChanged) Q_PROPERTY(int depth READ depth NOTIFY depthChanged)
Q_PROPERTY(QPersistentModelIndex root READ root WRITE setRoot NOTIFY rootChanged)
public: public:
FlameGraph(QQuickItem *parent = 0); FlameGraph(QQuickItem *parent = 0);
@@ -75,6 +76,24 @@ public:
} }
} }
QPersistentModelIndex root() const
{
return m_root;
}
void setRoot(const QPersistentModelIndex &root)
{
if (root != m_root) {
m_root = root;
emit rootChanged(root);
}
}
Q_INVOKABLE void resetRoot()
{
setRoot(QModelIndex());
}
static FlameGraphAttached *qmlAttachedProperties(QObject *object); static FlameGraphAttached *qmlAttachedProperties(QObject *object);
signals: signals:
@@ -84,12 +103,14 @@ signals:
void sizeThresholdChanged(qreal threshold); void sizeThresholdChanged(qreal threshold);
void depthChanged(int depth); void depthChanged(int depth);
void maximumDepthChanged(); void maximumDepthChanged();
void rootChanged(const QPersistentModelIndex &root);
private: private:
void rebuild(); void rebuild();
QQmlComponent *m_delegate = nullptr; QQmlComponent *m_delegate = nullptr;
QAbstractItemModel *m_model = nullptr; QAbstractItemModel *m_model = nullptr;
QPersistentModelIndex m_root;
int m_sizeRole = 0; int m_sizeRole = 0;
int m_depth = 0; int m_depth = 0;
qreal m_sizeThreshold = 0; qreal m_sizeThreshold = 0;

View File

@@ -40,11 +40,17 @@ class FLAMEGRAPH_EXPORT FlameGraphAttached : public QObject
Q_PROPERTY(qreal relativePosition READ relativePosition WRITE setRelativePosition Q_PROPERTY(qreal relativePosition READ relativePosition WRITE setRelativePosition
NOTIFY relativePositionChanged) NOTIFY relativePositionChanged)
Q_PROPERTY(bool dataValid READ isDataValid NOTIFY dataValidChanged) Q_PROPERTY(bool dataValid READ isDataValid NOTIFY dataValidChanged)
Q_PROPERTY(QModelIndex modelIndex READ modelIndex WRITE setModelIndex NOTIFY modelIndexChanged)
public: public:
FlameGraphAttached(QObject *parent = 0) : FlameGraphAttached(QObject *parent = 0) :
QObject(parent), m_relativeSize(0), m_relativePosition(0) {} QObject(parent), m_relativeSize(0), m_relativePosition(0) {}
QModelIndex modelIndex() const
{
return m_data;
}
Q_INVOKABLE QVariant data(int role) const Q_INVOKABLE QVariant data(int role) const
{ {
return m_data.isValid() ? m_data.data(role) : QVariant(); return m_data.isValid() ? m_data.data(role) : QVariant();
@@ -88,13 +94,13 @@ public:
m_data = data; m_data = data;
if (validChanged) if (validChanged)
emit dataValidChanged(); emit dataValidChanged();
emit dataChanged(); emit modelIndexChanged();
} }
} }
signals: signals:
void dataChanged();
void dataValidChanged(); void dataValidChanged();
void modelIndexChanged();
void relativeSizeChanged(); void relativeSizeChanged();
void relativePositionChanged(); void relativePositionChanged();

View File

@@ -37,6 +37,7 @@ Item {
signal mouseEntered signal mouseEntered
signal mouseExited signal mouseExited
signal clicked signal clicked
signal doubleClicked
property bool textVisible: width > 20 || isSelected property bool textVisible: width > 20 || isSelected
property int level: parent.level !== undefined ? parent.level + 1 : 0 property int level: parent.level !== undefined ? parent.level + 1 : 0
@@ -76,7 +77,7 @@ Item {
onEntered: flamegraphItem.mouseEntered() onEntered: flamegraphItem.mouseEntered()
onExited: flamegraphItem.mouseExited() onExited: flamegraphItem.mouseExited()
onClicked: flamegraphItem.clicked() onClicked: flamegraphItem.clicked()
onDoubleClicked: flamegraphItem.doubleClicked()
} }
} }
} }

View File

@@ -58,8 +58,21 @@ ScrollView {
contentHeight: flamegraph.height contentHeight: flamegraph.height
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
MouseArea {
anchors.fill: parent
onClicked: {
tooltip.selectedNode = null;
flameGraphModel.typeSelected(-1);
}
onDoubleClicked: {
tooltip.selectedNode = null;
flameGraphModel.typeSelected(-1);
flamegraph.resetRoot();
}
}
FlameGraph { FlameGraph {
property int delegateHeight: Math.max(30, flickable.height / depth) property int delegateHeight: Math.min(60, Math.max(30, flickable.height / depth))
property color blue: "blue" property color blue: "blue"
property color blue1: Qt.lighter(blue) property color blue1: Qt.lighter(blue)
property color blue2: Qt.rgba(0.375, 0, 1, 1) property color blue2: Qt.rgba(0.375, 0, 1, 1)
@@ -71,7 +84,7 @@ ScrollView {
id: flamegraph id: flamegraph
width: parent.width width: parent.width
height: depth * delegateHeight height: Math.max(depth * delegateHeight, flickable.height)
model: flameGraphModel model: flameGraphModel
sizeRole: root.sizeRole sizeRole: root.sizeRole
sizeThreshold: 0.002 sizeThreshold: 0.002
@@ -135,7 +148,7 @@ ScrollView {
+ Math.floor(width / flamegraph.width * 1000) / 10 + "%)"; + Math.floor(width / flamegraph.width * 1000) / 10 + "%)";
} }
text: textVisible ? buildText() : "" text: textVisible ? buildText() : ""
FlameGraph.onDataChanged: if (textVisible) text = buildText(); FlameGraph.onModelIndexChanged: if (textVisible) text = buildText();
onMouseEntered: { onMouseEntered: {
tooltip.hoveredNode = flamegraphItem; tooltip.hoveredNode = flamegraphItem;
@@ -152,7 +165,7 @@ ScrollView {
} }
} }
onClicked: { function selectClicked() {
if (flamegraphItem.FlameGraph.dataValid) { if (flamegraphItem.FlameGraph.dataValid) {
tooltip.selectedNode = flamegraphItem; tooltip.selectedNode = flamegraphItem;
flameGraphModel.typeSelected(flamegraphItem.FlameGraph.data( flameGraphModel.typeSelected(flamegraphItem.FlameGraph.data(
@@ -167,6 +180,12 @@ ScrollView {
} }
} }
onClicked: selectClicked()
onDoubleClicked: {
selectClicked();
flamegraph.root = FlameGraph.modelIndex;
}
// Functions, not properties to limit the initial overhead when creating the nodes, // Functions, not properties to limit the initial overhead when creating the nodes,
// and because FlameGraph.data(...) cannot be notified anyway. // and because FlameGraph.data(...) cannot be notified anyway.
function title() { return FlameGraph.data(QmlProfilerFlameGraphModel.TypeRole) || ""; } function title() { return FlameGraph.data(QmlProfilerFlameGraphModel.TypeRole) || ""; }