Commit 4e29a946 authored by Ulf Hermann's avatar Ulf Hermann

Add flamegraph view

Change-Id: I001053b63d27adcbbdfd95abb2b45f5f5fa05447
Reviewed-by: default avatarJoerg Bornemann <joerg.bornemann@theqtcompany.com>
parent b85598ec
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd
** All rights reserved.
** For any questions to The Qt Company, please use contact form at http://www.qt.io/contact-us
**
** This file is part of the Qt Enterprise Qt Quick Profiler Add-on.
**
** Licensees holding valid Qt Enterprise licenses may use this file in
** accordance with the Qt Enterprise License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company.
**
** If you have questions regarding the use of this file, please use
** contact form at http://www.qt.io/contact-us
**
****************************************************************************/
import QtQuick 2.0
Text {
font.pixelSize: 12
font.family: "sans-serif"
textFormat: Text.PlainText
renderType: Text.NativeRendering
}
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd
** All rights reserved.
** For any questions to The Qt Company, please use contact form at http://www.qt.io/contact-us
**
** This file is part of the Qt Enterprise Qt Quick Profiler Add-on.
**
** Licensees holding valid Qt Enterprise licenses may use this file in
** accordance with the Qt Enterprise License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company.
**
** If you have questions regarding the use of this file, please use
** contact form at http://www.qt.io/contact-us
**
****************************************************************************/
import QtQuick 2.0
import QtQuick.Controls 1.4
import FlameGraph 1.0
import FlameGraphModel 1.0
ScrollView {
id: root
signal typeSelected(int typeIndex)
signal gotoSourceLocation(string filename, int line, int column)
property int selectedTypeId: -1
property int visibleRangeTypes: -1
Flickable {
id: flickable
contentHeight: flamegraph.height
boundsBehavior: Flickable.StopAtBounds
FlameGraph {
property int itemHeight: Math.max(30, flickable.height / depth)
property int level: -1
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"
id: flamegraph
width: parent.width
height: depth * itemHeight
model: flameGraphModel
sizeRole: FlameGraphModel.Duration
sizeThreshold: 0.002
y: flickable.height > height ? flickable.height - height : 0
delegate: Item {
id: flamegraphItem
property int level: parent.level + (rangeTypeVisible ? 1 : 0)
property bool isSelected: flamegraphItem.FlameGraph.data(FlameGraphModel.TypeId) ===
root.selectedTypeId
property bool rangeTypeVisible: root.visibleRangeTypes &
(1 << FlameGraph.data(FlameGraphModel.RangeType))
Rectangle {
border.color: {
if (flamegraphItem.isSelected)
return flamegraph.blue2;
else if (mouseArea.containsMouse)
return flamegraph.blue1;
else
return flamegraph.grey1
}
border.width: (mouseArea.containsMouse || flamegraphItem.isSelected) ? 2 : 1
color: Qt.hsla((level % 12) / 72, 0.9 + Math.random() / 10,
0.45 + Math.random() / 10, 0.9 + Math.random() / 10);
height: flamegraphItem.rangeTypeVisible ? flamegraph.itemHeight : 0;
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
FlameGraphText {
id: text
visible: width > 20
anchors.fill: parent
anchors.margins: 5
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: visible ? buildText() : ""
elide: Text.ElideRight
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
function buildText() {
if (!flamegraphItem.FlameGraph.dataValid)
return "<others>";
return flamegraphItem.FlameGraph.data(FlameGraphModel.Details) + " (" +
flamegraphItem.FlameGraph.data(FlameGraphModel.Type) + ", " +
flamegraphItem.FlameGraph.data(FlameGraphModel.TimeInPercent) + "%)";
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
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";
}
onEntered: {
var model = [];
function addDetail(name, index, format) {
model.push(name + ":");
model.push(format(flamegraphItem.FlameGraph.data(index)));
}
function noop(a) {
return a;
}
function addPercent(a) {
return a + "%";
}
if (!flamegraphItem.FlameGraph.dataValid) {
model.push(qsTr("Details") + ":");
model.push(qsTr("Various Events"));
} else {
addDetail(qsTr("Details"), FlameGraphModel.Details, noop);
addDetail(qsTr("Type"), FlameGraphModel.Type, noop);
addDetail(qsTr("Calls"), FlameGraphModel.CallCount, noop);
addDetail(qsTr("Total Time"), FlameGraphModel.Duration, printTime);
addDetail(qsTr("Mean Time"), FlameGraphModel.TimePerCall, printTime);
addDetail(qsTr("In Percent"), FlameGraphModel.TimeInPercent,
addPercent);
}
tooltip.model = model;
}
onExited: {
tooltip.model = [];
}
onClicked: {
if (flamegraphItem.FlameGraph.dataValid) {
root.typeSelected(
flamegraphItem.FlameGraph.data(FlameGraphModel.TypeId));
root.gotoSourceLocation(
flamegraphItem.FlameGraph.data(FlameGraphModel.Filename),
flamegraphItem.FlameGraph.data(FlameGraphModel.Line),
flamegraphItem.FlameGraph.data(FlameGraphModel.Column));
}
}
}
}
FlameGraph.onDataChanged: if (text.visible) text.text = text.buildText();
height: flamegraph.height - level * flamegraph.itemHeight;
width: parent === null ? flamegraph.width : parent.width * FlameGraph.relativeSize
x: parent === null ? 0 : parent.width * FlameGraph.relativePosition
}
}
Rectangle {
color: "white"
border.width: 1
border.color: flamegraph.grey2
width: tooltip.model.length > 0 ? tooltip.width + 10 : 0
height: tooltip.model.length > 0 ? tooltip.height + 10 : 0
y: flickable.contentY
x: anchorRight ? parent.width - width : 0
property bool anchorRight: true
Grid {
id: tooltip
anchors.margins: 5
anchors.top: parent.top
anchors.left: parent.left
spacing: 5
columns: 2
property var model: [ qsTr("No data available") ]
Connections {
target: flameGraphModel
onModelReset: {
tooltip.model = (flameGraphModel.rowCount() === 0) ?
[ qsTr("No data available") ] : [];
}
}
Repeater {
model: parent.model
FlameGraphText {
text: modelData
font.bold: (index % 2) === 0
width: Math.min(implicitWidth, 200)
elide: Text.ElideRight
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: parent.anchorRight = !parent.anchorRight
}
}
}
}
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd
** All rights reserved.
** For any questions to The Qt Company, please use contact form at http://www.qt.io/contact-us
**
** This file is part of the Qt Enterprise Qt Quick Profiler Add-on.
**
** Licensees holding valid Qt Enterprise licenses may use this file in
** accordance with the Qt Enterprise License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company.
**
** If you have questions regarding the use of this file, please use
** contact form at http://www.qt.io/contact-us
**
****************************************************************************/
#include "flamegraph.h"
namespace QmlProfilerExtension {
namespace Internal {
FlameGraph::FlameGraph(QQuickItem *parent) :
QQuickItem(parent), m_delegate(0), m_model(0), m_depth(0), m_sizeThreshold(0)
{
}
QQmlComponent *FlameGraph::delegate() const
{
return m_delegate;
}
void FlameGraph::setDelegate(QQmlComponent *delegate)
{
if (delegate != m_delegate) {
m_delegate = delegate;
emit delegateChanged(delegate);
}
}
QAbstractItemModel *FlameGraph::model() const
{
return m_model;
}
void FlameGraph::setModel(QAbstractItemModel *model)
{
if (model != m_model) {
if (m_model)
disconnect(m_model, &QAbstractItemModel::modelReset, this, &FlameGraph::rebuild);
m_model = model;
connect(m_model, &QAbstractItemModel::modelReset, this, &FlameGraph::rebuild);
emit modelChanged(model);
rebuild();
}
}
int FlameGraph::sizeRole() const
{
return m_sizeRole;
}
void FlameGraph::setSizeRole(int sizeRole)
{
if (sizeRole != m_sizeRole) {
m_sizeRole = sizeRole;
emit sizeRoleChanged(sizeRole);
rebuild();
}
}
qreal FlameGraph::sizeThreshold() const
{
return m_sizeThreshold;
}
void FlameGraph::setSizeThreshold(qreal sizeThreshold)
{
if (sizeThreshold != m_sizeThreshold) {
m_sizeThreshold = sizeThreshold;
emit sizeThresholdChanged(sizeThreshold);
rebuild();
}
}
int FlameGraph::depth() const
{
return m_depth;
}
FlameGraphAttached *FlameGraph::qmlAttachedProperties(QObject *object)
{
FlameGraphAttached *attached =
object->findChild<FlameGraphAttached *>(QString(), Qt::FindDirectChildrenOnly);
if (!attached)
attached = new FlameGraphAttached(object);
return attached;
}
QObject *FlameGraph::appendChild(QObject *parentObject, QQuickItem *parentItem,
QQmlContext *context, const QModelIndex &childIndex,
qreal position, qreal size)
{
QObject *childObject = m_delegate->beginCreate(context);
if (parentItem) {
QQuickItem *childItem = qobject_cast<QQuickItem *>(childObject);
if (childItem)
childItem->setParentItem(parentItem);
}
childObject->setParent(parentObject);
FlameGraphAttached *attached = FlameGraph::qmlAttachedProperties(childObject);
attached->setRelativePosition(position);
attached->setRelativeSize(size);
attached->setModelIndex(childIndex);
m_delegate->completeCreate();
return childObject;
}
int FlameGraph::buildNode(const QModelIndex &parentIndex, QObject *parentObject, int depth)
{
qreal position = 0;
qreal skipped = 0;
qreal parentSize = m_model->data(parentIndex, m_sizeRole).toReal();
QQuickItem *parentItem = qobject_cast<QQuickItem *>(parentObject);
QQmlContext *context = qmlContext(this);
int rowCount = m_model->rowCount(parentIndex);
int childrenDepth = depth;
for (int row = 0; row < rowCount; ++row) {
QModelIndex childIndex = m_model->index(row, 0, parentIndex);
qreal size = m_model->data(childIndex, m_sizeRole).toReal();
if (size / m_model->data(QModelIndex(), m_sizeRole).toReal() < m_sizeThreshold) {
skipped += size;
continue;
}
QObject *childObject = appendChild(parentObject, parentItem, context, childIndex,
position / parentSize, size / parentSize);
position += size;
childrenDepth = qMax(childrenDepth, buildNode(childIndex, childObject, depth + 1));
}
if (skipped > 0) {
appendChild(parentObject, parentItem, context, QModelIndex(), position / parentSize,
skipped / parentSize);
childrenDepth = qMax(childrenDepth, depth + 1);
}
return childrenDepth;
}
void FlameGraph::rebuild()
{
qDeleteAll(childItems());
childItems().clear();
m_depth = 0;
if (!m_model) {
emit depthChanged(m_depth);
return;
}
m_depth = buildNode(QModelIndex(), this, 0);
emit depthChanged(m_depth);
}
QVariant FlameGraphAttached::data(int role) const
{
return m_data.isValid() ? m_data.data(role) : QVariant();
}
}
}
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd
** All rights reserved.
** For any questions to The Qt Company, please use contact form at http://www.qt.io/contact-us
**
** This file is part of the Qt Enterprise Qt Quick Profiler Add-on.
**
** Licensees holding valid Qt Enterprise licenses may use this file in
** accordance with the Qt Enterprise License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company.
**
** If you have questions regarding the use of this file, please use
** contact form at http://www.qt.io/contact-us
**
****************************************************************************/
#ifndef FLAMEGRAPH_H
#define FLAMEGRAPH_H
#include <QQuickItem>
#include <QAbstractItemModel>
namespace QmlProfilerExtension {
namespace Internal {
class FlameGraphAttached : public QObject
{
Q_OBJECT
Q_PROPERTY(qreal relativeSize READ relativeSize WRITE setRelativeSize
NOTIFY relativeSizeChanged)
Q_PROPERTY(qreal relativePosition READ relativePosition WRITE setRelativePosition
NOTIFY relativePositionChanged)
Q_PROPERTY(bool dataValid READ isDataValid NOTIFY dataValidChanged)
public:
FlameGraphAttached(QObject *parent = 0) :
QObject(parent), m_relativeSize(0), m_relativePosition(0) {}
Q_INVOKABLE QVariant data(int role) const;
bool isDataValid() const
{
return m_data.isValid();
}
qreal relativeSize() const
{
return m_relativeSize;
}
void setRelativeSize(qreal relativeSize)
{
if (relativeSize != m_relativeSize) {
m_relativeSize = relativeSize;
emit relativeSizeChanged();
}
}
qreal relativePosition() const
{
return m_relativePosition;
}
void setRelativePosition(qreal relativePosition)
{
if (relativePosition != m_relativePosition) {
m_relativePosition = relativePosition;
emit relativePositionChanged();
}
}
void setModelIndex(const QModelIndex &data)
{
if (data != m_data) {
bool validChanged = (data.isValid() != m_data.isValid());
m_data = data;
if (validChanged)
emit dataValidChanged();
emit dataChanged();
}
}
signals:
void dataChanged();
void dataValidChanged();
void relativeSizeChanged();
void relativePositionChanged();
private:
QPersistentModelIndex m_data;
qreal m_relativeSize;
qreal m_relativePosition;
};
class FlameGraph : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
Q_PROPERTY(QAbstractItemModel* model READ model WRITE setModel NOTIFY modelChanged)
Q_PROPERTY(int sizeRole READ sizeRole WRITE setSizeRole NOTIFY sizeRoleChanged)
Q_PROPERTY(qreal sizeThreshold READ sizeThreshold WRITE setSizeThreshold
NOTIFY sizeThresholdChanged)
Q_PROPERTY(int depth READ depth NOTIFY depthChanged)
public:
FlameGraph(QQuickItem *parent = 0);
QQmlComponent *delegate() const;
void setDelegate(QQmlComponent *delegate);
QAbstractItemModel *model() const;
void setModel(QAbstractItemModel *model);
int sizeRole() const;
void setSizeRole(int sizeRole);
qreal sizeThreshold() const;
void setSizeThreshold(qreal sizeThreshold);
int depth() const;
static FlameGraphAttached *qmlAttachedProperties(QObject *object);
signals:
void delegateChanged(QQmlComponent *delegate);
void modelChanged(QAbstractItemModel *model);
void sizeRoleChanged(int role);
void sizeThresholdChanged(qreal threshold);
void depthChanged(int depth);
private slots:
void rebuild();
private:
QQmlComponent *m_delegate;
QAbstractItemModel *m_model;
int m_sizeRole;
int m_depth;
qreal m_sizeThreshold;
int buildNode(const QModelIndex &parentIndex, QObject *parentObject, int depth);
QObject *appendChild(QObject *parentObject, QQuickItem *parentItem, QQmlContext *context,
const QModelIndex &childIndex, qreal position, qreal size);
};
}
}
QML_DECLARE_TYPEINFO(QmlProfilerExtension::Internal::FlameGraph, QML_HAS_ATTACHED_PROPERTIES)
#endif // FLAMEGRAPH_H
<RCC>
<qresource prefix="/">
<file>FlameGraphView.qml</file>
<file>FlameGraphText.qml</file>
</qresource>
</RCC>
......@@ -231,6 +231,7 @@ QVariant FlameGraphModel::lookup(const FlameGraphData &stats, int role) const
case Line: return type.location.line;
case Column: return type.location.column;
case Type: return nameForType(type.rangeType);
case RangeType: return type.rangeType;
case Details: return type.data.isEmpty() ?
FlameGraphModel::tr("Source code not available") : type.data;
default: return QVariant();
......@@ -320,6 +321,7 @@ QHash<int, QByteArray> FlameGraphModel::roleNames() const
names[Note] = "note";
names[TimePerCall] = "timePerCall";
names[TimeInPercent] = "timeInPercent";
names[RangeType] = "rangeType";
return names;
}
......
......@@ -72,6 +72,7 @@ public:
Note,
TimePerCall,
TimeInPercent,
RangeType,
MaxRole
};
......
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd
** All rights reserved.
** For any questions to The Qt Company, please use contact form at http://www.qt.io/contact-us
**
** This file is part of the Qt Enterprise Qt Quick Profiler Add-on.
**
** Licensees holding valid Qt Enterprise licenses may use this file in
** accordance with the Qt Enterprise License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company.
**
** If you have questions regarding the use of this file, please use
** contact form at http://www.qt.io/contact-us
**
****************************************************************************/
#include "flamegraphview.h"
#include "flamegraph.h"