forked from qt-creator/qt-creator
Add generic FlameGraphView QML component
This allows us to reduce code duplication, but we first have to put timeline and flame graph into the same library, so that we can use the TimelineThere in FlameGraphView. Change-Id: I72b27ffb1fc5aa6baf6a23d85e5ca6c610896b8c Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
This commit is contained in:
@@ -94,6 +94,11 @@ public:
|
|||||||
setRoot(QModelIndex());
|
setRoot(QModelIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Q_INVOKABLE QVariant total(int role) const
|
||||||
|
{
|
||||||
|
return m_model ? m_model->data(m_root, role) : QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
static FlameGraphAttached *qmlAttachedProperties(QObject *object);
|
static FlameGraphAttached *qmlAttachedProperties(QObject *object);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|||||||
344
src/libs/tracing/qml/FlameGraphView.qml
Normal file
344
src/libs/tracing/qml/FlameGraphView.qml
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2018 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
import FlameGraph 1.0
|
||||||
|
import TimelineTheme 1.0
|
||||||
|
|
||||||
|
import QtQml 2.2
|
||||||
|
import QtQuick 2.9
|
||||||
|
import QtQuick.Controls 1.3
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
id: root
|
||||||
|
property int selectedTypeId: -1
|
||||||
|
property int sizeRole: -1
|
||||||
|
property var model: null
|
||||||
|
|
||||||
|
property int typeIdRole: -1
|
||||||
|
property int sourceFileRole: -1
|
||||||
|
property int sourceLineRole: -1
|
||||||
|
property int sourceColumnRole: -1
|
||||||
|
property int detailsTitleRole: -1
|
||||||
|
property int summaryRole: -1
|
||||||
|
property int noteRole: -1
|
||||||
|
|
||||||
|
property var trRoleNames: []
|
||||||
|
|
||||||
|
property var modes: []
|
||||||
|
|
||||||
|
property var details: function(flameGraph) { return []; }
|
||||||
|
property var summary: function(attached) {
|
||||||
|
if (!attached.dataValid)
|
||||||
|
return qsTr("others");
|
||||||
|
|
||||||
|
return attached.data(summaryRole) + " (" + percent(sizeRole, attached) + "%)";
|
||||||
|
}
|
||||||
|
|
||||||
|
property var isHighlighted: function(node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function percent(role, attached) {
|
||||||
|
return Math.round(attached.data(role) / flamegraph.total(role) * 1000) / 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toMap(previousValue, currentValue, currentIndex, array) {
|
||||||
|
if (currentIndex % 2 === 1)
|
||||||
|
previousValue[array[currentIndex - 1]] = array[currentIndex];
|
||||||
|
return previousValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDetail(role, format, model, attached) {
|
||||||
|
model.push(trRoleNames[role]);
|
||||||
|
model.push(format(role, attached)
|
||||||
|
+ (modes.indexOf(role) >= 0 ? " (" + percent(role, attached) + "%)" : ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
property var detailFormats: {
|
||||||
|
"noop": function(role, flameGraph) {
|
||||||
|
return flameGraph.data(role);
|
||||||
|
},
|
||||||
|
"addLine": function(role, flameGraph) {
|
||||||
|
var result = flameGraph.data(role);
|
||||||
|
var line = flameGraph.data(root.sourceLineRole);
|
||||||
|
return line > 0 ? result + ":" + line : result;
|
||||||
|
},
|
||||||
|
"printTime": function(role, flameGraph) {
|
||||||
|
var time = flameGraph.data(role);
|
||||||
|
if (time <= 0)
|
||||||
|
return "0";
|
||||||
|
if (time < 1000)
|
||||||
|
return time + " ns";
|
||||||
|
time = Math.floor(time / 1000);
|
||||||
|
if (time < 1000)
|
||||||
|
return time + " μs";
|
||||||
|
if (time < 1e6)
|
||||||
|
return (time / 1000) + " ms";
|
||||||
|
return (time / 1e6) + " s";
|
||||||
|
},
|
||||||
|
"printMemory": function(role, flameGraph) {
|
||||||
|
var bytes = flameGraph.data(role);
|
||||||
|
if (bytes === 0)
|
||||||
|
return "0b";
|
||||||
|
|
||||||
|
var units = ["b", "kb", "Mb", "Gb"];
|
||||||
|
var div = 1;
|
||||||
|
for (var i = 0; i < units.length; ++i, div *= 1024) {
|
||||||
|
if (bytes > div * 1024)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bytes /= div;
|
||||||
|
var digitsAfterDot = Math.round(3 - Math.log(bytes) / Math.LN10);
|
||||||
|
var multiplier = Math.pow(10, digitsAfterDot);
|
||||||
|
return Math.round(bytes * multiplier) / multiplier + units[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectedTypeIdChanged: tooltip.hoveredNode = null
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
tooltip.selectedNode = null;
|
||||||
|
if (model !== null)
|
||||||
|
model.typeSelected(-1);
|
||||||
|
}
|
||||||
|
onDoubleClicked: {
|
||||||
|
tooltip.selectedNode = null;
|
||||||
|
if (model !== null)
|
||||||
|
model.typeSelected(-1);
|
||||||
|
flamegraph.resetRoot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Flickable {
|
||||||
|
id: flickable
|
||||||
|
contentHeight: flamegraph.height
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
|
||||||
|
FlameGraph {
|
||||||
|
property int delegateHeight: Math.min(60, Math.max(30, flickable.height / depth))
|
||||||
|
property color blue: "blue"
|
||||||
|
property color blue1: Qt.lighter(blue)
|
||||||
|
property color blue2: Qt.rgba(0.375, 0, 1, 1)
|
||||||
|
property color grey1: "#B0B0B0"
|
||||||
|
property color highlight: Theme.color(Theme.Timeline_HighlightColor)
|
||||||
|
|
||||||
|
id: flamegraph
|
||||||
|
width: parent.width
|
||||||
|
height: Math.max(depth * delegateHeight, flickable.height)
|
||||||
|
model: root.model
|
||||||
|
sizeRole: root.sizeRole
|
||||||
|
sizeThreshold: 0.002
|
||||||
|
maximumDepth: 128
|
||||||
|
y: flickable.height > height ? flickable.height - height : 0
|
||||||
|
|
||||||
|
delegate: FlameGraphDelegate {
|
||||||
|
id: flamegraphItem
|
||||||
|
|
||||||
|
property var typeId: FlameGraph.data(root.typeIdRole) || -1
|
||||||
|
property bool isHighlighted: root.isHighlighted(flamegraphItem)
|
||||||
|
|
||||||
|
itemHeight: flamegraph.delegateHeight
|
||||||
|
isSelected: typeId !== -1 && typeId === root.selectedTypeId
|
||||||
|
|
||||||
|
borderColor: {
|
||||||
|
if (isSelected)
|
||||||
|
return flamegraph.blue2;
|
||||||
|
else if (tooltip.hoveredNode === flamegraphItem)
|
||||||
|
return flamegraph.blue1;
|
||||||
|
else if (note() !== "" || isHighlighted)
|
||||||
|
return flamegraph.highlight;
|
||||||
|
else
|
||||||
|
return flamegraph.grey1;
|
||||||
|
}
|
||||||
|
borderWidth: {
|
||||||
|
if (tooltip.hoveredNode === flamegraphItem ||
|
||||||
|
tooltip.selectedNode === flamegraphItem) {
|
||||||
|
return 2;
|
||||||
|
} else if (note() !== "" || isHighlighted) {
|
||||||
|
return 3;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onIsSelectedChanged: {
|
||||||
|
if (isSelected && (tooltip.selectedNode === null ||
|
||||||
|
tooltip.selectedNode.typeId !== root.selectedTypeId)) {
|
||||||
|
tooltip.selectedNode = flamegraphItem;
|
||||||
|
} else if (!isSelected && tooltip.selectedNode === flamegraphItem) {
|
||||||
|
tooltip.selectedNode = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text: textVisible ? root.summary(FlameGraph) : ""
|
||||||
|
FlameGraph.onModelIndexChanged: {
|
||||||
|
if (textVisible)
|
||||||
|
text = root.summary(FlameGraph);
|
||||||
|
|
||||||
|
// refresh to trigger reevaluation
|
||||||
|
if (tooltip.selectedNode == flamegraphItem) {
|
||||||
|
var selectedNode = tooltip.selectedNode;
|
||||||
|
tooltip.selectedNode = null;
|
||||||
|
tooltip.selectedNode = selectedNode;
|
||||||
|
}
|
||||||
|
if (tooltip.hoveredNode == flamegraphItem) {
|
||||||
|
var hoveredNode = tooltip.hoveredNode;
|
||||||
|
tooltip.hoveredNode = null;
|
||||||
|
tooltip.hoveredNode = hoveredNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseEntered: {
|
||||||
|
tooltip.hoveredNode = flamegraphItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseExited: {
|
||||||
|
if (tooltip.hoveredNode === flamegraphItem) {
|
||||||
|
// Keep the window around until something else is hovered or selected.
|
||||||
|
if (tooltip.selectedNode === null
|
||||||
|
|| tooltip.selectedNode.typeId !== root.selectedTypeId) {
|
||||||
|
tooltip.selectedNode = flamegraphItem;
|
||||||
|
}
|
||||||
|
tooltip.hoveredNode = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectClicked() {
|
||||||
|
if (FlameGraph.dataValid) {
|
||||||
|
tooltip.selectedNode = flamegraphItem;
|
||||||
|
selectedTypeId = FlameGraph.data(root.typeIdRole);
|
||||||
|
model.typeSelected(selectedTypeId);
|
||||||
|
model.gotoSourceLocation(
|
||||||
|
FlameGraph.data(root.sourceFileRole),
|
||||||
|
FlameGraph.data(root.sourceLineRole),
|
||||||
|
FlameGraph.data(root.sourceColumnRole));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: selectClicked()
|
||||||
|
onDoubleClicked: {
|
||||||
|
flamegraph.root = FlameGraph.modelIndex;
|
||||||
|
selectClicked();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Functions, not properties to limit the initial overhead when creating the nodes,
|
||||||
|
// and because FlameGraph.data(...) cannot be notified anyway.
|
||||||
|
function title() {
|
||||||
|
return FlameGraph.data(root.detailsTitleRole) || qsTr("unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
function note() {
|
||||||
|
return FlameGraph.data(root.noteRole) || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function details() {
|
||||||
|
return root.details(FlameGraph);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FlameGraphDetails {
|
||||||
|
id: tooltip
|
||||||
|
|
||||||
|
minimumX: 0
|
||||||
|
maximumX: flickable.width
|
||||||
|
minimumY: flickable.contentY
|
||||||
|
maximumY: flickable.contentY + flickable.height
|
||||||
|
|
||||||
|
titleBarColor: Theme.color(Theme.Timeline_PanelHeaderColor)
|
||||||
|
titleBarTextColor: Theme.color(Theme.PanelTextColorLight)
|
||||||
|
contentColor: Theme.color(Theme.Timeline_PanelBackgroundColor)
|
||||||
|
contentTextColor: Theme.color(Theme.Timeline_TextColor)
|
||||||
|
noteTextColor: Theme.color(Theme.Timeline_HighlightColor)
|
||||||
|
buttonHoveredColor: Theme.color(Theme.FancyToolButtonHoverColor)
|
||||||
|
buttonSelectedColor: Theme.color(Theme.FancyToolButtonSelectedColor)
|
||||||
|
borderWidth: 0
|
||||||
|
|
||||||
|
property var hoveredNode: null;
|
||||||
|
property var selectedNode: null;
|
||||||
|
|
||||||
|
property var currentNode: {
|
||||||
|
if (hoveredNode !== null)
|
||||||
|
return hoveredNode;
|
||||||
|
else if (selectedNode !== null)
|
||||||
|
return selectedNode;
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
onClearSelection: {
|
||||||
|
selectedTypeId = -1;
|
||||||
|
selectedNode = null;
|
||||||
|
root.model.typeSelected(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogTitle: {
|
||||||
|
if (currentNode)
|
||||||
|
return currentNode.title();
|
||||||
|
else if (root.model === null || root.model.rowCount() === 0)
|
||||||
|
return qsTr("No data available");
|
||||||
|
else
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
model: currentNode ? currentNode.details() : []
|
||||||
|
note: currentNode ? currentNode.note() : ""
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root.model
|
||||||
|
onModelReset: {
|
||||||
|
tooltip.hoveredNode = null;
|
||||||
|
tooltip.selectedNode = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
x: flickable.width - width
|
||||||
|
y: flickable.contentY
|
||||||
|
|
||||||
|
// It won't listen to anchors.margins and by default it doesn't add any margin. Great.
|
||||||
|
width: implicitWidth + 20
|
||||||
|
|
||||||
|
text: qsTr("Visualize %1").arg(trRoleNames[root.sizeRole])
|
||||||
|
|
||||||
|
menu: Menu {
|
||||||
|
id: modesMenu
|
||||||
|
Instantiator {
|
||||||
|
model: root.modes
|
||||||
|
MenuItem {
|
||||||
|
text: root.trRoleNames[modelData]
|
||||||
|
onTriggered: root.sizeRole = modelData
|
||||||
|
}
|
||||||
|
onObjectAdded: modesMenu.insertItem(index, object)
|
||||||
|
onObjectRemoved: modesMenu.removeItem(object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
<file>FlameGraphDelegate.qml</file>
|
<file>FlameGraphDelegate.qml</file>
|
||||||
<file>FlameGraphDetails.qml</file>
|
<file>FlameGraphDetails.qml</file>
|
||||||
<file>FlameGraphText.qml</file>
|
<file>FlameGraphText.qml</file>
|
||||||
|
<file>FlameGraphView.qml</file>
|
||||||
<file>ico_edit.png</file>
|
<file>ico_edit.png</file>
|
||||||
<file>ico_edit@2x.png</file>
|
<file>ico_edit@2x.png</file>
|
||||||
<file>ico_rangeselected.png</file>
|
<file>ico_rangeselected.png</file>
|
||||||
|
|||||||
@@ -208,7 +208,6 @@ QVariant FlameGraphModel::lookup(const FlameGraphData &stats, int role) const
|
|||||||
case DurationRole: return stats.duration;
|
case DurationRole: return stats.duration;
|
||||||
case CallCountRole: return stats.calls;
|
case CallCountRole: return stats.calls;
|
||||||
case TimePerCallRole: return stats.duration / stats.calls;
|
case TimePerCallRole: return stats.duration / stats.calls;
|
||||||
case TimeInPercentRole: return stats.duration * 100 / m_stackBottom.duration;
|
|
||||||
case AllocationsRole: return stats.allocations;
|
case AllocationsRole: return stats.allocations;
|
||||||
case MemoryRole: return stats.memory;
|
case MemoryRole: return stats.memory;
|
||||||
default: break;
|
default: break;
|
||||||
@@ -321,7 +320,6 @@ QHash<int, QByteArray> FlameGraphModel::roleNames() const
|
|||||||
{ColumnRole, "column"},
|
{ColumnRole, "column"},
|
||||||
{NoteRole, "note"},
|
{NoteRole, "note"},
|
||||||
{TimePerCallRole, "timePerCall"},
|
{TimePerCallRole, "timePerCall"},
|
||||||
{TimeInPercentRole, "timeInPercent"},
|
|
||||||
{RangeTypeRole, "rangeType"},
|
{RangeTypeRole, "rangeType"},
|
||||||
{LocationRole, "location" },
|
{LocationRole, "location" },
|
||||||
{AllocationsRole, "allocations" },
|
{AllocationsRole, "allocations" },
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ public:
|
|||||||
ColumnRole,
|
ColumnRole,
|
||||||
NoteRole,
|
NoteRole,
|
||||||
TimePerCallRole,
|
TimePerCallRole,
|
||||||
TimeInPercentRole,
|
|
||||||
RangeTypeRole,
|
RangeTypeRole,
|
||||||
LocationRole,
|
LocationRole,
|
||||||
AllocationsRole,
|
AllocationsRole,
|
||||||
|
|||||||
@@ -23,20 +23,30 @@
|
|||||||
**
|
**
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
import QtQuick 2.0
|
|
||||||
import QtQuick.Controls 1.3
|
|
||||||
import FlameGraph 1.0
|
|
||||||
import QmlProfilerFlameGraphModel 1.0
|
import QmlProfilerFlameGraphModel 1.0
|
||||||
import TimelineTheme 1.0
|
|
||||||
import "../tracing/"
|
import "../tracing/"
|
||||||
|
|
||||||
ScrollView {
|
FlameGraphView {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property int selectedTypeId: -1
|
model: flameGraphModel
|
||||||
property int sizeRole: QmlProfilerFlameGraphModel.DurationRole
|
sizeRole: QmlProfilerFlameGraphModel.DurationRole
|
||||||
|
|
||||||
readonly property var trRoleNames: [
|
typeIdRole: QmlProfilerFlameGraphModel.TypeIdRole
|
||||||
|
sourceFileRole: QmlProfilerFlameGraphModel.FilenameRole
|
||||||
|
sourceLineRole: QmlProfilerFlameGraphModel.LineRole
|
||||||
|
sourceColumnRole: QmlProfilerFlameGraphModel.ColumnRole
|
||||||
|
detailsTitleRole: QmlProfilerFlameGraphModel.TypeRole
|
||||||
|
summaryRole: QmlProfilerFlameGraphModel.DetailsRole
|
||||||
|
noteRole: QmlProfilerFlameGraphModel.NoteRole
|
||||||
|
|
||||||
|
modes: [
|
||||||
|
QmlProfilerFlameGraphModel.DurationRole,
|
||||||
|
QmlProfilerFlameGraphModel.MemoryRole,
|
||||||
|
QmlProfilerFlameGraphModel.AllocationsRole
|
||||||
|
]
|
||||||
|
|
||||||
|
trRoleNames: [
|
||||||
QmlProfilerFlameGraphModel.DurationRole, qsTr("Total Time"),
|
QmlProfilerFlameGraphModel.DurationRole, qsTr("Total Time"),
|
||||||
QmlProfilerFlameGraphModel.CallCountRole, qsTr("Calls"),
|
QmlProfilerFlameGraphModel.CallCountRole, qsTr("Calls"),
|
||||||
QmlProfilerFlameGraphModel.DetailsRole, qsTr("Details"),
|
QmlProfilerFlameGraphModel.DetailsRole, qsTr("Details"),
|
||||||
@@ -45,311 +55,47 @@ ScrollView {
|
|||||||
QmlProfilerFlameGraphModel.LocationRole, qsTr("Location"),
|
QmlProfilerFlameGraphModel.LocationRole, qsTr("Location"),
|
||||||
QmlProfilerFlameGraphModel.AllocationsRole, qsTr("Allocations"),
|
QmlProfilerFlameGraphModel.AllocationsRole, qsTr("Allocations"),
|
||||||
QmlProfilerFlameGraphModel.MemoryRole, qsTr("Memory")
|
QmlProfilerFlameGraphModel.MemoryRole, qsTr("Memory")
|
||||||
].reduce(function(previousValue, currentValue, currentIndex, array) {
|
].reduce(toMap, {})
|
||||||
if (currentIndex % 2 === 1)
|
|
||||||
previousValue[array[currentIndex - 1]] = array[currentIndex];
|
|
||||||
return previousValue;
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
onSelectedTypeIdChanged: tooltip.hoveredNode = null
|
details: function(flameGraph) {
|
||||||
|
|
||||||
Flickable {
|
|
||||||
id: flickable
|
|
||||||
contentHeight: flamegraph.height
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
tooltip.selectedNode = null;
|
|
||||||
flameGraphModel.typeSelected(-1);
|
|
||||||
}
|
|
||||||
onDoubleClicked: {
|
|
||||||
tooltip.selectedNode = null;
|
|
||||||
flameGraphModel.typeSelected(-1);
|
|
||||||
flamegraph.resetRoot();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FlameGraph {
|
|
||||||
property int delegateHeight: Math.min(60, Math.max(30, flickable.height / depth))
|
|
||||||
property color blue: "blue"
|
|
||||||
property color blue1: Qt.lighter(blue)
|
|
||||||
property color blue2: Qt.rgba(0.375, 0, 1, 1)
|
|
||||||
property color grey1: "#B0B0B0"
|
|
||||||
property color grey2: "#A0A0A0"
|
|
||||||
property color highlight: Theme.color(Theme.Timeline_HighlightColor)
|
|
||||||
|
|
||||||
function checkBindingLoop(otherTypeId) {return false;}
|
|
||||||
|
|
||||||
id: flamegraph
|
|
||||||
width: parent.width
|
|
||||||
height: Math.max(depth * delegateHeight, flickable.height)
|
|
||||||
model: flameGraphModel
|
|
||||||
sizeRole: root.sizeRole
|
|
||||||
sizeThreshold: 0.002
|
|
||||||
maximumDepth: 25
|
|
||||||
y: flickable.height > height ? flickable.height - height : 0
|
|
||||||
|
|
||||||
delegate: FlameGraphDelegate {
|
|
||||||
id: flamegraphItem
|
|
||||||
|
|
||||||
property int typeId: FlameGraph.data(QmlProfilerFlameGraphModel.TypeIdRole) || -1
|
|
||||||
property bool isBindingLoop: parent.checkBindingLoop(typeId)
|
|
||||||
|
|
||||||
itemHeight: flamegraph.delegateHeight
|
|
||||||
isSelected: typeId !== -1 && typeId === root.selectedTypeId
|
|
||||||
|
|
||||||
borderColor: {
|
|
||||||
if (isSelected)
|
|
||||||
return flamegraph.blue2;
|
|
||||||
else if (tooltip.hoveredNode === flamegraphItem)
|
|
||||||
return flamegraph.blue1;
|
|
||||||
else if (note() !== "" || isBindingLoop)
|
|
||||||
return flamegraph.highlight;
|
|
||||||
else
|
|
||||||
return flamegraph.grey1;
|
|
||||||
}
|
|
||||||
borderWidth: {
|
|
||||||
if (tooltip.hoveredNode === flamegraphItem ||
|
|
||||||
tooltip.selectedNode === flamegraphItem) {
|
|
||||||
return 2;
|
|
||||||
} else if (note() !== "") {
|
|
||||||
return 3;
|
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onIsSelectedChanged: {
|
|
||||||
if (isSelected && (tooltip.selectedNode === null ||
|
|
||||||
tooltip.selectedNode.typeId !== root.selectedTypeId)) {
|
|
||||||
tooltip.selectedNode = flamegraphItem;
|
|
||||||
} else if (!isSelected && tooltip.selectedNode === flamegraphItem) {
|
|
||||||
tooltip.selectedNode = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkBindingLoop(otherTypeId) {
|
|
||||||
if (typeId === otherTypeId) {
|
|
||||||
isBindingLoop = true;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return parent.checkBindingLoop(otherTypeId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildText() {
|
|
||||||
if (!FlameGraph.dataValid)
|
|
||||||
return "<others>";
|
|
||||||
|
|
||||||
return FlameGraph.data(QmlProfilerFlameGraphModel.DetailsRole) + " ("
|
|
||||||
+ FlameGraph.data(QmlProfilerFlameGraphModel.TypeRole) + ", "
|
|
||||||
+ Math.floor(width / flamegraph.width * 1000) / 10 + "%)";
|
|
||||||
}
|
|
||||||
text: textVisible ? buildText() : ""
|
|
||||||
FlameGraph.onModelIndexChanged: {
|
|
||||||
if (textVisible)
|
|
||||||
text = buildText();
|
|
||||||
|
|
||||||
// refresh to trigger reevaluation
|
|
||||||
if (tooltip.selectedNode == flamegraphItem) {
|
|
||||||
var selectedNode = tooltip.selectedNode;
|
|
||||||
tooltip.selectedNode = null;
|
|
||||||
tooltip.selectedNode = selectedNode;
|
|
||||||
}
|
|
||||||
if (tooltip.hoveredNode == flamegraphItem) {
|
|
||||||
var hoveredNode = tooltip.hoveredNode;
|
|
||||||
tooltip.hoveredNode = null;
|
|
||||||
tooltip.hoveredNode = hoveredNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseEntered: {
|
|
||||||
tooltip.hoveredNode = flamegraphItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseExited: {
|
|
||||||
if (tooltip.hoveredNode === flamegraphItem) {
|
|
||||||
// Keep the window around until something else is hovered or selected.
|
|
||||||
if (tooltip.selectedNode === null
|
|
||||||
|| tooltip.selectedNode.typeId !== root.selectedTypeId) {
|
|
||||||
tooltip.selectedNode = flamegraphItem;
|
|
||||||
}
|
|
||||||
tooltip.hoveredNode = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectClicked() {
|
|
||||||
if (flamegraphItem.FlameGraph.dataValid) {
|
|
||||||
tooltip.selectedNode = flamegraphItem;
|
|
||||||
flameGraphModel.typeSelected(flamegraphItem.FlameGraph.data(
|
|
||||||
QmlProfilerFlameGraphModel.TypeIdRole));
|
|
||||||
flameGraphModel.gotoSourceLocation(
|
|
||||||
flamegraphItem.FlameGraph.data(
|
|
||||||
QmlProfilerFlameGraphModel.FilenameRole),
|
|
||||||
flamegraphItem.FlameGraph.data(
|
|
||||||
QmlProfilerFlameGraphModel.LineRole),
|
|
||||||
flamegraphItem.FlameGraph.data(
|
|
||||||
QmlProfilerFlameGraphModel.ColumnRole));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: selectClicked()
|
|
||||||
onDoubleClicked: {
|
|
||||||
selectClicked();
|
|
||||||
flamegraph.root = FlameGraph.modelIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Functions, not properties to limit the initial overhead when creating the nodes,
|
|
||||||
// and because FlameGraph.data(...) cannot be notified anyway.
|
|
||||||
function title() { return FlameGraph.data(QmlProfilerFlameGraphModel.TypeRole) || ""; }
|
|
||||||
function note() { return FlameGraph.data(QmlProfilerFlameGraphModel.NoteRole) || ""; }
|
|
||||||
function details() {
|
|
||||||
var model = [];
|
var model = [];
|
||||||
function addDetail(index, format) {
|
if (!flameGraph.dataValid) {
|
||||||
model.push(trRoleNames[index]);
|
model.push(trRoleNames[QmlProfilerFlameGraphModel.DetailsRole]);
|
||||||
model.push(format(FlameGraph.data(index)));
|
|
||||||
}
|
|
||||||
|
|
||||||
function printTime(t)
|
|
||||||
{
|
|
||||||
if (t <= 0)
|
|
||||||
return "0";
|
|
||||||
if (t < 1000)
|
|
||||||
return t + " ns";
|
|
||||||
t = Math.floor(t / 1000);
|
|
||||||
if (t < 1000)
|
|
||||||
return t + " μs";
|
|
||||||
if (t < 1e6)
|
|
||||||
return (t / 1000) + " ms";
|
|
||||||
return (t / 1e6) + " s";
|
|
||||||
}
|
|
||||||
|
|
||||||
function noop(a) {
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addPercent(a) {
|
|
||||||
return a + "%";
|
|
||||||
}
|
|
||||||
|
|
||||||
function printMemory(a) {
|
|
||||||
if (a === 0)
|
|
||||||
return "0b";
|
|
||||||
|
|
||||||
var units = ["b", "kb", "Mb", "Gb"];
|
|
||||||
var div = 1;
|
|
||||||
for (var i = 0; i < units.length; ++i, div *= 1024) {
|
|
||||||
if (a > div * 1024)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
a /= div;
|
|
||||||
var digitsAfterDot = Math.round(3 - Math.log(a) / Math.LN10);
|
|
||||||
var multiplier = Math.pow(10, digitsAfterDot);
|
|
||||||
return Math.round(a * multiplier) / multiplier + units[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!FlameGraph.dataValid) {
|
|
||||||
model.push(qsTr("Details"));
|
|
||||||
model.push(qsTr("Various Events"));
|
model.push(qsTr("Various Events"));
|
||||||
} else {
|
} else {
|
||||||
addDetail(QmlProfilerFlameGraphModel.DetailsRole, noop);
|
function addDetail(role, format) { root.addDetail(role, format, model, flameGraph); }
|
||||||
addDetail(QmlProfilerFlameGraphModel.CallCountRole, noop);
|
|
||||||
addDetail(QmlProfilerFlameGraphModel.DurationRole, printTime);
|
addDetail(QmlProfilerFlameGraphModel.DetailsRole, detailFormats.noop);
|
||||||
addDetail(QmlProfilerFlameGraphModel.TimePerCallRole, printTime);
|
addDetail(QmlProfilerFlameGraphModel.CallCountRole, detailFormats.noop);
|
||||||
addDetail(QmlProfilerFlameGraphModel.TimeInPercentRole, addPercent);
|
addDetail(QmlProfilerFlameGraphModel.DurationRole, detailFormats.printTime);
|
||||||
addDetail(QmlProfilerFlameGraphModel.LocationRole, noop);
|
addDetail(QmlProfilerFlameGraphModel.TimePerCallRole, detailFormats.printTime);
|
||||||
addDetail(QmlProfilerFlameGraphModel.MemoryRole, printMemory);
|
addDetail(QmlProfilerFlameGraphModel.LocationRole, detailFormats.noop);
|
||||||
addDetail(QmlProfilerFlameGraphModel.AllocationsRole, noop);
|
addDetail(QmlProfilerFlameGraphModel.MemoryRole, detailFormats.printMemory);
|
||||||
|
addDetail(QmlProfilerFlameGraphModel.AllocationsRole, detailFormats.noop);
|
||||||
}
|
}
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
summary: function(attached) {
|
||||||
|
if (!attached.dataValid)
|
||||||
|
return qsTr("others");
|
||||||
|
|
||||||
|
return attached.data(QmlProfilerFlameGraphModel.DetailsRole) + " ("
|
||||||
|
+ attached.data(QmlProfilerFlameGraphModel.TypeRole) + ", "
|
||||||
|
+ root.percent(root.sizeRole, attached) + "%)";
|
||||||
}
|
}
|
||||||
|
|
||||||
FlameGraphDetails {
|
isHighlighted: function(node) {
|
||||||
id: tooltip
|
function recurse(parentNode, typeId) {
|
||||||
|
if (!parentNode)
|
||||||
minimumX: 0
|
return false;
|
||||||
maximumX: flickable.width
|
if (parentNode.typeId === typeId) {
|
||||||
minimumY: flickable.contentY
|
parentNode.isHighlighted = true;
|
||||||
maximumY: flickable.contentY + flickable.height
|
return true;
|
||||||
|
}
|
||||||
titleBarColor: Theme.color(Theme.Timeline_PanelHeaderColor)
|
return recurse(parentNode.parent, typeId);
|
||||||
titleBarTextColor: Theme.color(Theme.PanelTextColorLight)
|
|
||||||
contentColor: Theme.color(Theme.Timeline_PanelBackgroundColor)
|
|
||||||
contentTextColor: Theme.color(Theme.Timeline_TextColor)
|
|
||||||
noteTextColor: Theme.color(Theme.Timeline_HighlightColor)
|
|
||||||
buttonHoveredColor: Theme.color(Theme.FancyToolButtonHoverColor)
|
|
||||||
buttonSelectedColor: Theme.color(Theme.FancyToolButtonSelectedColor)
|
|
||||||
borderWidth: 0
|
|
||||||
|
|
||||||
property var hoveredNode: null;
|
|
||||||
property var selectedNode: null;
|
|
||||||
|
|
||||||
property var currentNode: {
|
|
||||||
if (hoveredNode !== null)
|
|
||||||
return hoveredNode;
|
|
||||||
else if (selectedNode !== null)
|
|
||||||
return selectedNode;
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onClearSelection: {
|
return recurse(node.parent, node.typeId);
|
||||||
selectedTypeId = -1;
|
|
||||||
selectedNode = null;
|
|
||||||
flameGraphModel.typeSelected(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
dialogTitle: {
|
|
||||||
if (currentNode)
|
|
||||||
return currentNode.title();
|
|
||||||
else if (flameGraphModel.rowCount() === 0)
|
|
||||||
return qsTr("No data available");
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
model: currentNode ? currentNode.details() : []
|
|
||||||
note: currentNode ? currentNode.note() : ""
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: flameGraphModel
|
|
||||||
onModelReset: {
|
|
||||||
tooltip.hoveredNode = null;
|
|
||||||
tooltip.selectedNode = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
x: flickable.width - width
|
|
||||||
y: flickable.contentY
|
|
||||||
|
|
||||||
// It won't listen to anchors.margins and by default it doesn't add any margin. Great.
|
|
||||||
width: implicitWidth + 20
|
|
||||||
|
|
||||||
text: qsTr("Visualize %1").arg(trRoleNames[root.sizeRole])
|
|
||||||
|
|
||||||
menu: Menu {
|
|
||||||
MenuItem {
|
|
||||||
text: trRoleNames[QmlProfilerFlameGraphModel.DurationRole]
|
|
||||||
onTriggered: root.sizeRole = QmlProfilerFlameGraphModel.DurationRole
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: trRoleNames[QmlProfilerFlameGraphModel.MemoryRole]
|
|
||||||
onTriggered: root.sizeRole = QmlProfilerFlameGraphModel.MemoryRole
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: trRoleNames[QmlProfilerFlameGraphModel.AllocationsRole]
|
|
||||||
onTriggered: root.sizeRole = QmlProfilerFlameGraphModel.AllocationsRole
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,8 +158,6 @@ void FlameGraphModelTest::testData()
|
|||||||
QCOMPARE(model.data(index2, FlameGraphModel::NoteRole).toString(), QString());
|
QCOMPARE(model.data(index2, FlameGraphModel::NoteRole).toString(), QString());
|
||||||
QCOMPARE(model.data(index, FlameGraphModel::TimePerCallRole).toLongLong(), 20);
|
QCOMPARE(model.data(index, FlameGraphModel::TimePerCallRole).toLongLong(), 20);
|
||||||
QCOMPARE(model.data(index2, FlameGraphModel::TimePerCallRole).toLongLong(), 12);
|
QCOMPARE(model.data(index2, FlameGraphModel::TimePerCallRole).toLongLong(), 12);
|
||||||
QCOMPARE(model.data(index, FlameGraphModel::TimeInPercentRole).toInt(), 62);
|
|
||||||
QCOMPARE(model.data(index2, FlameGraphModel::TimeInPercentRole).toInt(), 37);
|
|
||||||
QCOMPARE(model.data(index, FlameGraphModel::RangeTypeRole).toInt(),
|
QCOMPARE(model.data(index, FlameGraphModel::RangeTypeRole).toInt(),
|
||||||
static_cast<int>(Javascript));
|
static_cast<int>(Javascript));
|
||||||
QCOMPARE(model.data(index2, FlameGraphModel::RangeTypeRole).toInt(),
|
QCOMPARE(model.data(index2, FlameGraphModel::RangeTypeRole).toInt(),
|
||||||
@@ -201,7 +199,6 @@ void FlameGraphModelTest::testRoleNames()
|
|||||||
QCOMPARE(names[FlameGraphModel::ColumnRole], QByteArray("column"));
|
QCOMPARE(names[FlameGraphModel::ColumnRole], QByteArray("column"));
|
||||||
QCOMPARE(names[FlameGraphModel::NoteRole], QByteArray("note"));
|
QCOMPARE(names[FlameGraphModel::NoteRole], QByteArray("note"));
|
||||||
QCOMPARE(names[FlameGraphModel::TimePerCallRole], QByteArray("timePerCall"));
|
QCOMPARE(names[FlameGraphModel::TimePerCallRole], QByteArray("timePerCall"));
|
||||||
QCOMPARE(names[FlameGraphModel::TimeInPercentRole], QByteArray("timeInPercent"));
|
|
||||||
QCOMPARE(names[FlameGraphModel::RangeTypeRole], QByteArray("rangeType"));
|
QCOMPARE(names[FlameGraphModel::RangeTypeRole], QByteArray("rangeType"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user