From b85598ecd3c2d39e6a0f4bdf88eb6a1a186a19de Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Fri, 11 Dec 2015 11:09:10 +0100 Subject: Add flamegraph model Change-Id: I0c5b837dd649f95495ef2ef36523d455a59103f4 Reviewed-by: Joerg Bornemann --- plugins/qmlprofiler/flamegraphmodel.cpp | 327 ++++++++++++++++++++++++++++++++ plugins/qmlprofiler/flamegraphmodel.h | 117 ++++++++++++ plugins/qmlprofiler/qmlprofiler.pro | 6 +- plugins/qmlprofiler/qmlprofiler.qbs | 2 + 4 files changed, 450 insertions(+), 2 deletions(-) create mode 100644 plugins/qmlprofiler/flamegraphmodel.cpp create mode 100644 plugins/qmlprofiler/flamegraphmodel.h diff --git a/plugins/qmlprofiler/flamegraphmodel.cpp b/plugins/qmlprofiler/flamegraphmodel.cpp new file mode 100644 index 0000000000..16783913e7 --- /dev/null +++ b/plugins/qmlprofiler/flamegraphmodel.cpp @@ -0,0 +1,327 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://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 http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "flamegraphmodel.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace QmlProfilerExtension { +namespace Internal { + +FlameGraphModel::FlameGraphModel(QmlProfiler::QmlProfilerModelManager *modelManager, + QObject *parent) : QAbstractItemModel(parent) +{ + m_modelManager = modelManager; + connect(modelManager->qmlModel(), &QmlProfiler::QmlProfilerDataModel::changed, + this, [this](){loadData();}); + connect(modelManager->notesModel(), &Timeline::TimelineNotesModel::changed, + this, [this](int typeId, int, int){loadNotes(typeId, true);}); + m_modelId = modelManager->registerModelProxy(); + + // We're iterating twice in loadData. + modelManager->setProxyCountWeight(m_modelId, 2); + + m_acceptedTypes << QmlDebug::Compiling << QmlDebug::Creating << QmlDebug::Binding + << QmlDebug::HandlingSignal << QmlDebug::Javascript; + modelManager->announceFeatures(m_modelId, QmlDebug::Constants::QML_JS_RANGE_FEATURES); +} + +void FlameGraphModel::setEventTypeAccepted(QmlDebug::RangeType type, bool accepted) +{ + if (accepted && !m_acceptedTypes.contains(type)) + m_acceptedTypes << type; + else if (!accepted && m_acceptedTypes.contains(type)) + m_acceptedTypes.removeOne(type); +} + +bool FlameGraphModel::eventTypeAccepted(QmlDebug::RangeType type) const +{ + return m_acceptedTypes.contains(type); +} + +void FlameGraphModel::clear() +{ + m_modelManager->modelProxyCountUpdated(m_modelId, 0, 1); + m_stackBottom = FlameGraphData(); + m_typeIdsWithNotes.clear(); +} + +void FlameGraphModel::loadNotes(int typeIndex, bool emitSignal) +{ + QSet changedNotes; + Timeline::TimelineNotesModel *notes = m_modelManager->notesModel(); + if (typeIndex == -1) { + changedNotes = m_typeIdsWithNotes; + m_typeIdsWithNotes.clear(); + for (int i = 0; i < notes->count(); ++i) + m_typeIdsWithNotes.insert(notes->typeId(i)); + changedNotes += m_typeIdsWithNotes; + } else { + changedNotes.insert(typeIndex); + if (notes->byTypeId(typeIndex).isEmpty()) + m_typeIdsWithNotes.remove(typeIndex); + else + m_typeIdsWithNotes.insert(typeIndex); + } + + if (!emitSignal) + return; + + QQueue queue; + queue.append(QModelIndex()); + + QVector roles = {Note}; + while (!queue.isEmpty()) { + QModelIndex index = queue.takeFirst(); + if (index.isValid()) { + FlameGraphData *data = static_cast(index.internalPointer()); + if (changedNotes.contains(data->typeIndex)) + emit dataChanged(index, index, roles); + for (int row = 0; row < rowCount(index); ++row) + queue.append(index.child(row, 0)); + } + + } +} + +void FlameGraphModel::loadData(qint64 rangeStart, qint64 rangeEnd) +{ + const bool checkRanges = (rangeStart != -1) && (rangeEnd != -1); + if (m_modelManager->state() == QmlProfiler::QmlProfilerModelManager::ClearingData) { + beginResetModel(); + clear(); + endResetModel(); + return; + } else if (m_modelManager->state() != QmlProfiler::QmlProfilerModelManager::ProcessingData && + m_modelManager->state() != QmlProfiler::QmlProfilerModelManager::Done) { + return; + } + + beginResetModel(); + clear(); + + const QVector &eventList + = m_modelManager->qmlModel()->getEvents(); + const QVector &typesList + = m_modelManager->qmlModel()->getEventTypes(); + + // used by binding loop detection + QStack callStack; + callStack.append(0); + FlameGraphData *stackTop = &m_stackBottom; + + for (int i = 0; i < eventList.size(); ++i) { + const QmlProfiler::QmlProfilerDataModel::QmlEventData *event = &eventList[i]; + int typeIndex = event->typeIndex(); + const QmlProfiler::QmlProfilerDataModel::QmlEventTypeData *type = &typesList[typeIndex]; + + if (!m_acceptedTypes.contains(type->rangeType)) + continue; + + if (checkRanges) { + if ((event->startTime() + event->duration() < rangeStart) + || (event->startTime() > rangeEnd)) + continue; + } + + const QmlProfiler::QmlProfilerDataModel::QmlEventData *potentialParent = callStack.top(); + while (potentialParent && + potentialParent->startTime() + potentialParent->duration() <= event->startTime()) { + callStack.pop(); + stackTop = stackTop->parent; + potentialParent = callStack.top(); + } + + callStack.push(event); + stackTop = pushChild(stackTop, event); + + m_modelManager->modelProxyCountUpdated(m_modelId, i, eventList.count()); + } + + foreach (FlameGraphData *child, m_stackBottom.children) + m_stackBottom.duration += child->duration; + + loadNotes(-1, false); + + m_modelManager->modelProxyCountUpdated(m_modelId, 1, 1); + endResetModel(); +} + +static QString nameForType(QmlDebug::RangeType typeNumber) +{ + switch (typeNumber) { + case QmlDebug::Painting: return FlameGraphModel::tr("Paint"); + case QmlDebug::Compiling: return FlameGraphModel::tr("Compile"); + case QmlDebug::Creating: return FlameGraphModel::tr("Create"); + case QmlDebug::Binding: return FlameGraphModel::tr("Binding"); + case QmlDebug::HandlingSignal: return FlameGraphModel::tr("Signal"); + case QmlDebug::Javascript: return FlameGraphModel::tr("JavaScript"); + default: return QString(); + } +} + +QVariant FlameGraphModel::lookup(const FlameGraphData &stats, int role) const +{ + switch (role) { + case TypeId: return stats.typeIndex; + case Note: { + QString ret; + if (!m_typeIdsWithNotes.contains(stats.typeIndex)) + return ret; + QmlProfiler::QmlProfilerNotesModel *notes = m_modelManager->notesModel(); + foreach (const QVariant &item, notes->byTypeId(stats.typeIndex)) { + if (ret.isEmpty()) + ret = item.toString(); + else + ret += QChar::LineFeed + item.toString(); + } + return ret; + } + case Duration: return stats.duration; + case CallCount: return stats.calls; + case TimePerCall: return stats.duration / stats.calls; + case TimeInPercent: return stats.duration * 100 / m_stackBottom.duration; + default: break; + } + + if (stats.typeIndex != -1) { + const QVector &typeList = + m_modelManager->qmlModel()->getEventTypes(); + const QmlProfiler::QmlProfilerDataModel::QmlEventTypeData &type = typeList[stats.typeIndex]; + + switch (role) { + case Filename: return type.location.filename; + case Line: return type.location.line; + case Column: return type.location.column; + case Type: return nameForType(type.rangeType); + case Details: return type.data.isEmpty() ? + FlameGraphModel::tr("Source code not available") : type.data; + default: return QVariant(); + } + } else { + return QVariant(); + } +} + +FlameGraphData::FlameGraphData(FlameGraphData *parent, int typeIndex, qint64 duration) : + duration(duration), calls(1), typeIndex(typeIndex), parent(parent) {} + +FlameGraphData::~FlameGraphData() +{ + qDeleteAll(children); +} + +FlameGraphData *FlameGraphModel::pushChild( + FlameGraphData *parent, const QmlProfiler::QmlProfilerDataModel::QmlEventData *data) +{ + foreach (FlameGraphData *child, parent->children) { + if (child->typeIndex == data->typeIndex()) { + ++child->calls; + child->duration += data->duration(); + return child; + } + } + + FlameGraphData *child = new FlameGraphData(parent, data->typeIndex(), data->duration()); + parent->children.append(child); + return child; +} + +QModelIndex FlameGraphModel::index(int row, int column, const QModelIndex &parent) const +{ + if (parent.isValid()) { + FlameGraphData *parentData = static_cast(parent.internalPointer()); + return createIndex(row, column, parentData->children[row]); + } else { + return createIndex(row, column, row >= 0 ? m_stackBottom.children[row] : 0); + } +} + +QModelIndex FlameGraphModel::parent(const QModelIndex &child) const +{ + if (child.isValid()) { + FlameGraphData *childData = static_cast(child.internalPointer()); + return createIndex(0, 0, childData->parent); + } else { + return QModelIndex(); + } +} + +int FlameGraphModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + FlameGraphData *parentData = static_cast(parent.internalPointer()); + return parentData->children.count(); + } else { + return m_stackBottom.children.count(); + } +} + +int FlameGraphModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +QVariant FlameGraphModel::data(const QModelIndex &index, int role) const +{ + FlameGraphData *data = static_cast(index.internalPointer()); + return lookup(data ? *data : m_stackBottom, role); +} + +QHash FlameGraphModel::roleNames() const +{ + QHash names = QAbstractItemModel::roleNames(); + names[TypeId] = "typeId"; + names[Type] = "type"; + names[Duration] = "duration"; + names[CallCount] = "callCount"; + names[Details] = "details"; + names[Filename] = "filename"; + names[Line] = "line"; + names[Column] = "column"; + names[Note] = "note"; + names[TimePerCall] = "timePerCall"; + names[TimeInPercent] = "timeInPercent"; + return names; +} + +} // namespace Internal +} // namespace QmlProfilerExtension diff --git a/plugins/qmlprofiler/flamegraphmodel.h b/plugins/qmlprofiler/flamegraphmodel.h new file mode 100644 index 0000000000..6f41bf21d1 --- /dev/null +++ b/plugins/qmlprofiler/flamegraphmodel.h @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://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 http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef QMLPROFILEREVENTSMODEL_H +#define QMLPROFILEREVENTSMODEL_H + +#include +#include +#include +#include + +#include +#include +#include + +namespace QmlProfilerExtension { +namespace Internal { + +struct FlameGraphData { + FlameGraphData(FlameGraphData *parent = 0, int typeIndex = -1, qint64 duration = 0); + ~FlameGraphData(); + + qint64 duration; + qint64 calls; + int typeIndex; + + FlameGraphData *parent; + QVector children; +}; + +class FlameGraphModel : public QAbstractItemModel +{ + Q_OBJECT + Q_ENUMS(Role) +public: + enum Role { + TypeId = Qt::UserRole + 1, // Sort by data, not by displayed string + Type, + Duration, + CallCount, + Details, + Filename, + Line, + Column, + Note, + TimePerCall, + TimeInPercent, + MaxRole + }; + + FlameGraphModel(QmlProfiler::QmlProfilerModelManager *modelManager, QObject *parent = 0); + + void setEventTypeAccepted(QmlDebug::RangeType type, bool accepted); + bool eventTypeAccepted(QmlDebug::RangeType) const; + + QModelIndex index(int row, int column, const QModelIndex &parent) const override; + QModelIndex parent(const QModelIndex &child) const override; + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + +public slots: + void loadData(qint64 rangeStart = -1, qint64 rangeEnd = -1); + void loadNotes(int typeId, bool emitSignal); + +private: + friend class FlameGraphRelativesModel; + friend class FlameGraphParentsModel; + friend class FlameGraphChildrenModel; + + QVariant lookup(const FlameGraphData &data, int role) const; + void clear(); + FlameGraphData *pushChild(FlameGraphData *parent, + const QmlProfiler::QmlProfilerDataModel::QmlEventData *data); + + int m_selectedTypeIndex; + FlameGraphData m_stackBottom; + + int m_modelId; + QmlProfiler::QmlProfilerModelManager *m_modelManager; + + QList m_acceptedTypes; + QSet m_typeIdsWithNotes; +}; + +} // namespace Internal +} // namespace QmlprofilerExtension + +#endif // QMLPROFILEREVENTSMODEL_H diff --git a/plugins/qmlprofiler/qmlprofiler.pro b/plugins/qmlprofiler/qmlprofiler.pro index 7fa6b03163..ec7591fbee 100644 --- a/plugins/qmlprofiler/qmlprofiler.pro +++ b/plugins/qmlprofiler/qmlprofiler.pro @@ -14,7 +14,8 @@ SOURCES += qmlprofilerextensionplugin.cpp \ pixmapcachemodel.cpp \ memoryusagemodel.cpp \ inputeventsmodel.cpp \ - debugmessagesmodel.cpp + debugmessagesmodel.cpp \ + flamegraphmodel.cpp \ HEADERS += qmlprofilerextensionplugin.h \ qmlprofilerextension_global.h \ @@ -23,7 +24,8 @@ HEADERS += qmlprofilerextensionplugin.h \ pixmapcachemodel.h \ memoryusagemodel.h \ inputeventsmodel.h \ - debugmessagesmodel.h + debugmessagesmodel.h \ + flamegraphmodel.h \ OTHER_FILES += \ QmlProfilerExtension.json.in diff --git a/plugins/qmlprofiler/qmlprofiler.qbs b/plugins/qmlprofiler/qmlprofiler.qbs index 8c16895a72..223698bbf3 100644 --- a/plugins/qmlprofiler/qmlprofiler.qbs +++ b/plugins/qmlprofiler/qmlprofiler.qbs @@ -12,6 +12,8 @@ QtcCommercialPlugin { files: [ "debugmessagesmodel.cpp", "debugmessagesmodel.h", + "flamegraphmodel.cpp", + "flamegraphmodel.h", "inputeventsmodel.cpp", "inputeventsmodel.h", "memoryusagemodel.cpp", -- cgit v1.2.3