forked from qt-creator/qt-creator
Merge QmlProfiler and QmlProfilerExtension plugins
Change-Id: Iaa1de7afda664a7a0779f47d104f863a16a34976 Reviewed-by: Christian Kandeler <christian.kandeler@theqtcompany.com>
This commit is contained in:
140
src/plugins/qmlprofiler/debugmessagesmodel.cpp
Normal file
140
src/plugins/qmlprofiler/debugmessagesmodel.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "debugmessagesmodel.h"
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
bool DebugMessagesModel::accepted(const QmlProfilerDataModel::QmlEventTypeData &event) const
|
||||
{
|
||||
return event.message == QmlDebug::DebugMessage;
|
||||
}
|
||||
|
||||
DebugMessagesModel::DebugMessagesModel(QmlProfilerModelManager *manager, QObject *parent) :
|
||||
QmlProfilerTimelineModel(manager, QmlDebug::DebugMessage, QmlDebug::MaximumRangeType,
|
||||
QmlDebug::ProfileDebugMessages, parent), m_maximumMsgType(-1)
|
||||
{
|
||||
}
|
||||
|
||||
int DebugMessagesModel::typeId(int index) const
|
||||
{
|
||||
return m_data[index].typeId;
|
||||
}
|
||||
|
||||
QColor DebugMessagesModel::color(int index) const
|
||||
{
|
||||
return colorBySelectionId(index);
|
||||
}
|
||||
|
||||
static const char *messageTypes[] = {
|
||||
QT_TRANSLATE_NOOP("DebugMessagesModel", "Debug Message"),
|
||||
QT_TRANSLATE_NOOP("DebugMessagesModel", "Warning Message"),
|
||||
QT_TRANSLATE_NOOP("DebugMessagesModel", "Critical Message"),
|
||||
QT_TRANSLATE_NOOP("DebugMessagesModel", "Fatal Message"),
|
||||
QT_TRANSLATE_NOOP("DebugMessagesModel", "Info Message"),
|
||||
};
|
||||
|
||||
QString DebugMessagesModel::messageType(uint i)
|
||||
{
|
||||
return i < sizeof(messageTypes) / sizeof(char *) ? tr(messageTypes[i]) :
|
||||
tr("Unknown Message %1").arg(i);
|
||||
}
|
||||
|
||||
QVariantList DebugMessagesModel::labels() const
|
||||
{
|
||||
QVariantList result;
|
||||
|
||||
for (int i = 0; i <= m_maximumMsgType; ++i) {
|
||||
QVariantMap element;
|
||||
element.insert(QLatin1String("description"), messageType(i));
|
||||
element.insert(QLatin1String("id"), i);
|
||||
result << element;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QVariantMap DebugMessagesModel::details(int index) const
|
||||
{
|
||||
const QmlProfilerDataModel::QmlEventTypeData &type =
|
||||
modelManager()->qmlModel()->getEventTypes()[m_data[index].typeId];
|
||||
|
||||
QVariantMap result;
|
||||
result.insert(QLatin1String("displayName"), messageType(type.detailType));
|
||||
result.insert(tr("Timestamp"), QmlProfilerDataModel::formatTime(startTime(index)));
|
||||
result.insert(tr("Message"), m_data[index].text);
|
||||
result.insert(tr("Location"), type.displayName);
|
||||
return result;
|
||||
}
|
||||
|
||||
int DebugMessagesModel::expandedRow(int index) const
|
||||
{
|
||||
return selectionId(index) + 1;
|
||||
}
|
||||
|
||||
int DebugMessagesModel::collapsedRow(int index) const
|
||||
{
|
||||
Q_UNUSED(index);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DebugMessagesModel::loadData()
|
||||
{
|
||||
QmlProfilerDataModel *simpleModel = modelManager()->qmlModel();
|
||||
if (simpleModel->isEmpty())
|
||||
return;
|
||||
|
||||
const QVector<QmlProfilerDataModel::QmlEventTypeData> &types = simpleModel->getEventTypes();
|
||||
|
||||
foreach (const QmlProfilerDataModel::QmlEventData &event, simpleModel->getEvents()) {
|
||||
const QmlProfilerDataModel::QmlEventTypeData &type = types[event.typeIndex()];
|
||||
if (!accepted(type) || event.startTime() < 0)
|
||||
continue;
|
||||
|
||||
m_data.insert(insert(event.startTime(), 0, type.detailType),
|
||||
MessageData(event.stringData(), event.typeIndex()));
|
||||
if (type.detailType > m_maximumMsgType)
|
||||
m_maximumMsgType = event.typeIndex();
|
||||
updateProgress(count(), simpleModel->getEvents().count());
|
||||
}
|
||||
setCollapsedRowCount(2);
|
||||
setExpandedRowCount(m_maximumMsgType + 2);
|
||||
updateProgress(1, 1);
|
||||
}
|
||||
|
||||
void DebugMessagesModel::clear()
|
||||
{
|
||||
m_data.clear();
|
||||
m_maximumMsgType = -1;
|
||||
QmlProfilerTimelineModel::clear();
|
||||
}
|
||||
|
||||
QVariantMap DebugMessagesModel::location(int index) const
|
||||
{
|
||||
return locationFromTypeId(index);
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlProfiler
|
||||
68
src/plugins/qmlprofiler/debugmessagesmodel.h
Normal file
68
src/plugins/qmlprofiler/debugmessagesmodel.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "qmlprofilertimelinemodel.h"
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
class DebugMessagesModel : public QmlProfilerTimelineModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
protected:
|
||||
bool accepted(const QmlProfilerDataModel::QmlEventTypeData &event) const override;
|
||||
|
||||
public:
|
||||
DebugMessagesModel(QmlProfilerModelManager *manager, QObject *parent = 0);
|
||||
|
||||
int typeId(int index) const override;
|
||||
QColor color(int index) const override;
|
||||
QVariantList labels() const override;
|
||||
QVariantMap details(int index) const override;
|
||||
int expandedRow(int index) const override;
|
||||
int collapsedRow(int index) const override;
|
||||
void loadData() override;
|
||||
void clear() override;
|
||||
QVariantMap location(int index) const override;
|
||||
|
||||
private:
|
||||
static QString messageType(uint i);
|
||||
|
||||
struct MessageData {
|
||||
MessageData(const QString &text = QString(), int typeId = -1) :
|
||||
text(text), typeId(typeId) {}
|
||||
QString text;
|
||||
int typeId;
|
||||
};
|
||||
|
||||
int m_maximumMsgType;
|
||||
QVector<MessageData> m_data;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Qmlprofiler
|
||||
182
src/plugins/qmlprofiler/flamegraph.cpp
Normal file
182
src/plugins/qmlprofiler/flamegraph.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "flamegraph.h"
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
FlameGraph::FlameGraph(QQuickItem *parent) :
|
||||
QQuickItem(parent)
|
||||
{
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlProfiler
|
||||
157
src/plugins/qmlprofiler/flamegraph.h
Normal file
157
src/plugins/qmlprofiler/flamegraph.h
Normal file
@@ -0,0 +1,157 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QQuickItem>
|
||||
#include <QAbstractItemModel>
|
||||
|
||||
namespace QmlProfiler {
|
||||
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 = nullptr;
|
||||
QAbstractItemModel *m_model = nullptr;
|
||||
int m_sizeRole = 0;
|
||||
int m_depth = 0;
|
||||
qreal m_sizeThreshold = 0;
|
||||
|
||||
int buildNode(const QModelIndex &parentIndex, QObject *parentObject, int depth);
|
||||
QObject *appendChild(QObject *parentObject, QQuickItem *parentItem, QQmlContext *context,
|
||||
const QModelIndex &childIndex, qreal position, qreal size);
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlProfiler
|
||||
|
||||
QML_DECLARE_TYPEINFO(QmlProfiler::Internal::FlameGraph, QML_HAS_ATTACHED_PROPERTIES)
|
||||
309
src/plugins/qmlprofiler/flamegraphmodel.cpp
Normal file
309
src/plugins/qmlprofiler/flamegraphmodel.cpp
Normal file
@@ -0,0 +1,309 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "flamegraphmodel.h"
|
||||
|
||||
#include <qmlprofiler/qmlprofilermodelmanager.h>
|
||||
#include <qmlprofiler/qmlprofilerdatamodel.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QVector>
|
||||
#include <QString>
|
||||
#include <QStack>
|
||||
#include <QQueue>
|
||||
#include <QSet>
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
FlameGraphModel::FlameGraphModel(QmlProfilerModelManager *modelManager,
|
||||
QObject *parent) : QAbstractItemModel(parent)
|
||||
{
|
||||
m_modelManager = modelManager;
|
||||
connect(modelManager->qmlModel(), &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<int> 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)
|
||||
emit dataChanged(QModelIndex(), QModelIndex(), QVector<int>() << Note);
|
||||
}
|
||||
|
||||
void FlameGraphModel::loadData(qint64 rangeStart, qint64 rangeEnd)
|
||||
{
|
||||
const bool checkRanges = (rangeStart != -1) && (rangeEnd != -1);
|
||||
if (m_modelManager->state() == QmlProfilerModelManager::ClearingData) {
|
||||
beginResetModel();
|
||||
clear();
|
||||
endResetModel();
|
||||
return;
|
||||
} else if (m_modelManager->state() != QmlProfilerModelManager::ProcessingData &&
|
||||
m_modelManager->state() != QmlProfilerModelManager::Done) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
clear();
|
||||
|
||||
const QVector<QmlProfilerDataModel::QmlEventData> &eventList
|
||||
= m_modelManager->qmlModel()->getEvents();
|
||||
const QVector<QmlProfilerDataModel::QmlEventTypeData> &typesList
|
||||
= m_modelManager->qmlModel()->getEventTypes();
|
||||
|
||||
// used by binding loop detection
|
||||
QStack<const QmlProfilerDataModel::QmlEventData *> callStack;
|
||||
callStack.append(0);
|
||||
FlameGraphData *stackTop = &m_stackBottom;
|
||||
|
||||
for (int i = 0; i < eventList.size(); ++i) {
|
||||
const QmlProfilerDataModel::QmlEventData *event = &eventList[i];
|
||||
int typeIndex = event->typeIndex();
|
||||
const 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 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;
|
||||
QmlProfilerNotesModel *notes = m_modelManager->notesModel();
|
||||
foreach (const QVariant &item, notes->byTypeId(stats.typeIndex)) {
|
||||
if (ret.isEmpty())
|
||||
ret = notes->text(item.toInt());
|
||||
else
|
||||
ret += QChar::LineFeed + notes->text(item.toInt());
|
||||
}
|
||||
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<QmlProfilerDataModel::QmlEventTypeData> &typeList =
|
||||
m_modelManager->qmlModel()->getEventTypes();
|
||||
const 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 RangeType: return type.rangeType;
|
||||
case Details: return type.data.isEmpty() ?
|
||||
FlameGraphModel::tr("Source code not available") : type.data;
|
||||
case Location: return type.displayName;
|
||||
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 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<FlameGraphData *>(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<FlameGraphData *>(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<FlameGraphData *>(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<FlameGraphData *>(index.internalPointer());
|
||||
return lookup(data ? *data : m_stackBottom, role);
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> FlameGraphModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> 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";
|
||||
names[RangeType] = "rangeType";
|
||||
return names;
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlProfiler
|
||||
111
src/plugins/qmlprofiler/flamegraphmodel.h
Normal file
111
src/plugins/qmlprofiler/flamegraphmodel.h
Normal file
@@ -0,0 +1,111 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <qmlprofiler/qmlprofilerdatamodel.h>
|
||||
#include <qmlprofiler/qmlprofilernotesmodel.h>
|
||||
#include <qmldebug/qmlprofilereventtypes.h>
|
||||
#include <qmldebug/qmlprofilereventlocation.h>
|
||||
|
||||
#include <QSet>
|
||||
#include <QVector>
|
||||
#include <QAbstractItemModel>
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
struct FlameGraphData {
|
||||
FlameGraphData(FlameGraphData *parent = 0, int typeIndex = -1, qint64 duration = 0);
|
||||
~FlameGraphData();
|
||||
|
||||
qint64 duration;
|
||||
qint64 calls;
|
||||
int typeIndex;
|
||||
|
||||
FlameGraphData *parent;
|
||||
QVector<FlameGraphData *> 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,
|
||||
RangeType,
|
||||
Location,
|
||||
MaxRole
|
||||
};
|
||||
|
||||
FlameGraphModel(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;
|
||||
Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QHash<int, QByteArray> 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 QmlProfilerDataModel::QmlEventData *data);
|
||||
|
||||
int m_selectedTypeIndex;
|
||||
FlameGraphData m_stackBottom;
|
||||
|
||||
int m_modelId;
|
||||
QmlProfilerModelManager *m_modelManager;
|
||||
|
||||
QList<QmlDebug::RangeType> m_acceptedTypes;
|
||||
QSet<int> m_typeIdsWithNotes;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlProfiler
|
||||
119
src/plugins/qmlprofiler/flamegraphview.cpp
Normal file
119
src/plugins/qmlprofiler/flamegraphview.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "flamegraphview.h"
|
||||
#include "flamegraph.h"
|
||||
|
||||
#include <qmlprofiler/qmlprofilerconstants.h>
|
||||
#include <qmlprofiler/qmlprofilertool.h>
|
||||
|
||||
#include <QQmlContext>
|
||||
#include <QVBoxLayout>
|
||||
#include <QMenu>
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
FlameGraphView::FlameGraphView(QWidget *parent, QmlProfilerModelManager *manager) :
|
||||
QmlProfilerEventsView(parent), m_content(new QQuickWidget(this)),
|
||||
m_model(new FlameGraphModel(manager, this)), m_isRestrictedToRange(false)
|
||||
{
|
||||
setWindowTitle(QStringLiteral("Flamegraph"));
|
||||
setObjectName(QStringLiteral("QmlProfilerFlamegraph"));
|
||||
|
||||
qmlRegisterType<FlameGraph>("FlameGraph", 1, 0, "FlameGraph");
|
||||
qmlRegisterUncreatableType<FlameGraphModel>("FlameGraphModel", 1, 0, "FlameGraphModel",
|
||||
QLatin1String("use the context property"));
|
||||
qmlRegisterUncreatableType<QAbstractItemModel>("AbstractItemModel", 1, 0, "AbstractItemModel",
|
||||
QLatin1String("only for Qt 5.4"));
|
||||
|
||||
m_content->rootContext()->setContextProperty(QStringLiteral("flameGraphModel"), m_model);
|
||||
m_content->setSource(QUrl(QStringLiteral("qrc:/qmlprofiler/FlameGraphView.qml")));
|
||||
m_content->setClearColor(QColor(0xdc, 0xdc, 0xdc));
|
||||
|
||||
m_content->setResizeMode(QQuickWidget::SizeRootObjectToView);
|
||||
m_content->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
QLayout *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
layout->addWidget(m_content);
|
||||
setLayout(layout);
|
||||
|
||||
connect(m_content->rootObject(), SIGNAL(typeSelected(int)),
|
||||
this, SIGNAL(typeSelected(int)));
|
||||
connect(m_content->rootObject(), SIGNAL(gotoSourceLocation(QString,int,int)),
|
||||
this, SIGNAL(gotoSourceLocation(QString,int,int)));
|
||||
}
|
||||
|
||||
void FlameGraphView::clear()
|
||||
{
|
||||
m_isRestrictedToRange = false;
|
||||
}
|
||||
|
||||
void FlameGraphView::restrictToRange(qint64 rangeStart, qint64 rangeEnd)
|
||||
{
|
||||
m_isRestrictedToRange = (rangeStart != -1 || rangeEnd != -1);
|
||||
m_model->loadData(rangeStart, rangeEnd);
|
||||
}
|
||||
|
||||
bool FlameGraphView::isRestrictedToRange() const
|
||||
{
|
||||
return m_isRestrictedToRange;
|
||||
}
|
||||
|
||||
void FlameGraphView::selectByTypeId(int typeIndex)
|
||||
{
|
||||
m_content->rootObject()->setProperty("selectedTypeId", typeIndex);
|
||||
}
|
||||
|
||||
void FlameGraphView::onVisibleFeaturesChanged(quint64 features)
|
||||
{
|
||||
int rangeTypeMask = 0;
|
||||
for (int rangeType = 0; rangeType < QmlDebug::MaximumRangeType; ++rangeType) {
|
||||
if (features & (1ULL << QmlDebug::featureFromRangeType(QmlDebug::RangeType(rangeType))))
|
||||
rangeTypeMask |= (1 << rangeType);
|
||||
}
|
||||
m_content->rootObject()->setProperty("visibleRangeTypes", rangeTypeMask);
|
||||
}
|
||||
|
||||
void FlameGraphView::contextMenuEvent(QContextMenuEvent *ev)
|
||||
{
|
||||
QMenu menu;
|
||||
QAction *getGlobalStatsAction = 0;
|
||||
|
||||
QPoint position = ev->globalPos();
|
||||
|
||||
menu.addActions(QmlProfilerTool::profilerContextMenuActions());
|
||||
menu.addSeparator();
|
||||
getGlobalStatsAction = menu.addAction(tr("Show Full Range"));
|
||||
if (!isRestrictedToRange())
|
||||
getGlobalStatsAction->setEnabled(false);
|
||||
|
||||
if (menu.exec(position) == getGlobalStatsAction)
|
||||
emit showFullRange();
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlProfiler
|
||||
61
src/plugins/qmlprofiler/flamegraphview.h
Normal file
61
src/plugins/qmlprofiler/flamegraphview.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "flamegraphmodel.h"
|
||||
#include "qmlprofilereventsview.h"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QQuickWidget>
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
class FlameGraphView : public QmlProfilerEventsView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
FlameGraphView(QWidget *parent, QmlProfilerModelManager *manager);
|
||||
|
||||
void clear() override;
|
||||
void restrictToRange(qint64 rangeStart, qint64 rangeEnd) override;
|
||||
bool isRestrictedToRange() const override;
|
||||
|
||||
public slots:
|
||||
void selectByTypeId(int typeIndex) override;
|
||||
void onVisibleFeaturesChanged(quint64 features) override;
|
||||
|
||||
protected:
|
||||
void contextMenuEvent(QContextMenuEvent *ev) override;
|
||||
|
||||
private:
|
||||
QQuickWidget *m_content;
|
||||
FlameGraphModel *m_model;
|
||||
bool m_isRestrictedToRange;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlProfiler
|
||||
192
src/plugins/qmlprofiler/inputeventsmodel.cpp
Normal file
192
src/plugins/qmlprofiler/inputeventsmodel.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "inputeventsmodel.h"
|
||||
#include "qmlprofilermodelmanager.h"
|
||||
#include "qmldebug/qmlprofilereventtypes.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QMetaEnum>
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
InputEventsModel::InputEventsModel(QmlProfilerModelManager *manager, QObject *parent) :
|
||||
QmlProfilerTimelineModel(manager, QmlDebug::Event, QmlDebug::MaximumRangeType,
|
||||
QmlDebug::ProfileInputEvents, parent),
|
||||
m_keyTypeId(-1), m_mouseTypeId(-1)
|
||||
{
|
||||
}
|
||||
|
||||
int InputEventsModel::typeId(int index) const
|
||||
{
|
||||
return selectionId(index) == QmlDebug::Mouse ? m_mouseTypeId : m_keyTypeId;
|
||||
}
|
||||
|
||||
QColor InputEventsModel::color(int index) const
|
||||
{
|
||||
return colorBySelectionId(index);
|
||||
}
|
||||
|
||||
QVariantList InputEventsModel::labels() const
|
||||
{
|
||||
QVariantList result;
|
||||
|
||||
QVariantMap element;
|
||||
element.insert(QLatin1String("description"), QVariant(tr("Mouse Events")));
|
||||
element.insert(QLatin1String("id"), QVariant(QmlDebug::Mouse));
|
||||
result << element;
|
||||
|
||||
element.clear();
|
||||
element.insert(QLatin1String("description"), QVariant(tr("Keyboard Events")));
|
||||
element.insert(QLatin1String("id"), QVariant(QmlDebug::Key));
|
||||
result << element;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QMetaEnum InputEventsModel::metaEnum(const char *name)
|
||||
{
|
||||
return staticQtMetaObject.enumerator(staticQtMetaObject.indexOfEnumerator(name));
|
||||
}
|
||||
|
||||
QVariantMap InputEventsModel::details(int index) const
|
||||
{
|
||||
QVariantMap result;
|
||||
result.insert(tr("Timestamp"), QmlProfilerDataModel::formatTime(startTime(index)));
|
||||
QString type;
|
||||
const InputEvent &event = m_data[index];
|
||||
switch (event.type) {
|
||||
case QmlDebug::InputKeyPress:
|
||||
type = tr("Key Press");
|
||||
case QmlDebug::InputKeyRelease:
|
||||
if (type.isEmpty())
|
||||
type = tr("Key Release");
|
||||
if (event.a != 0) {
|
||||
result.insert(tr("Key"), QLatin1String(metaEnum("Key").valueToKey(event.a)));
|
||||
}
|
||||
if (event.b != 0) {
|
||||
result.insert(tr("Modifiers"),
|
||||
QLatin1String(metaEnum("KeyboardModifiers").valueToKeys(event.b)));
|
||||
}
|
||||
break;
|
||||
case QmlDebug::InputMouseDoubleClick:
|
||||
type = tr("Double Click");
|
||||
case QmlDebug::InputMousePress:
|
||||
if (type.isEmpty())
|
||||
type = tr("Mouse Press");
|
||||
case QmlDebug::InputMouseRelease:
|
||||
if (type.isEmpty())
|
||||
type = tr("Mouse Release");
|
||||
result.insert(tr("Button"), QLatin1String(metaEnum("MouseButtons").valueToKey(event.a)));
|
||||
result.insert(tr("Result"), QLatin1String(metaEnum("MouseButtons").valueToKeys(event.b)));
|
||||
break;
|
||||
case QmlDebug::InputMouseMove:
|
||||
type = tr("Mouse Move");
|
||||
result.insert(tr("X"), QString::number(event.a));
|
||||
result.insert(tr("Y"), QString::number(event.b));
|
||||
break;
|
||||
case QmlDebug::InputMouseWheel:
|
||||
type = tr("Mouse Wheel");
|
||||
result.insert(tr("Angle X"), QString::number(event.a));
|
||||
result.insert(tr("Angle Y"), QString::number(event.b));
|
||||
break;
|
||||
case QmlDebug::InputKeyUnknown:
|
||||
type = tr("Keyboard Event");
|
||||
break;
|
||||
case QmlDebug::InputMouseUnknown:
|
||||
type = tr("Mouse Event");
|
||||
break;
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
|
||||
result.insert(QLatin1String("displayName"), type);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int InputEventsModel::expandedRow(int index) const
|
||||
{
|
||||
return selectionId(index) == QmlDebug::Mouse ? 1 : 2;
|
||||
}
|
||||
|
||||
int InputEventsModel::collapsedRow(int index) const
|
||||
{
|
||||
Q_UNUSED(index)
|
||||
return 1;
|
||||
}
|
||||
|
||||
void InputEventsModel::loadData()
|
||||
{
|
||||
QmlProfilerDataModel *simpleModel = modelManager()->qmlModel();
|
||||
if (simpleModel->isEmpty())
|
||||
return;
|
||||
|
||||
const QVector<QmlProfilerDataModel::QmlEventTypeData> &types = simpleModel->getEventTypes();
|
||||
foreach (const QmlProfilerDataModel::QmlEventData &event, simpleModel->getEvents()) {
|
||||
const QmlProfilerDataModel::QmlEventTypeData &type = types[event.typeIndex()];
|
||||
if (!accepted(type))
|
||||
continue;
|
||||
|
||||
m_data.insert(insert(event.startTime(), 0, type.detailType),
|
||||
InputEvent(static_cast<QmlDebug::InputEventType>(event.numericData(0)),
|
||||
event.numericData(1), event.numericData(2)));
|
||||
|
||||
if (type.detailType == QmlDebug::Mouse) {
|
||||
if (m_mouseTypeId == -1)
|
||||
m_mouseTypeId = event.typeIndex();
|
||||
} else if (m_keyTypeId == -1) {
|
||||
m_keyTypeId = event.typeIndex();
|
||||
}
|
||||
updateProgress(count(), simpleModel->getEvents().count());
|
||||
}
|
||||
setCollapsedRowCount(2);
|
||||
setExpandedRowCount(3);
|
||||
updateProgress(1, 1);
|
||||
}
|
||||
|
||||
void InputEventsModel::clear()
|
||||
{
|
||||
m_keyTypeId = m_mouseTypeId = -1;
|
||||
m_data.clear();
|
||||
QmlProfilerTimelineModel::clear();
|
||||
}
|
||||
|
||||
bool InputEventsModel::accepted(const QmlProfilerDataModel::QmlEventTypeData &event) const
|
||||
{
|
||||
return QmlProfilerTimelineModel::accepted(event) &&
|
||||
(event.detailType == QmlDebug::Mouse || event.detailType == QmlDebug::Key);
|
||||
}
|
||||
|
||||
InputEventsModel::InputEvent::InputEvent(QmlDebug::InputEventType type, int a, int b) :
|
||||
type(type), a(a), b(b)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlProfiler
|
||||
69
src/plugins/qmlprofiler/inputeventsmodel.h
Normal file
69
src/plugins/qmlprofiler/inputeventsmodel.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "qmlprofilertimelinemodel.h"
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
class InputEventsModel : public QmlProfilerTimelineModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
protected:
|
||||
bool accepted(const QmlProfilerDataModel::QmlEventTypeData &event) const;
|
||||
|
||||
public:
|
||||
struct InputEvent {
|
||||
InputEvent(QmlDebug::InputEventType type = QmlDebug::MaximumInputEventType, int a = 0,
|
||||
int b = 0);
|
||||
QmlDebug::InputEventType type;
|
||||
int a;
|
||||
int b;
|
||||
};
|
||||
|
||||
InputEventsModel(QmlProfilerModelManager *manager, QObject *parent = 0);
|
||||
|
||||
int typeId(int index) const;
|
||||
QColor color(int index) const;
|
||||
QVariantList labels() const;
|
||||
QVariantMap details(int index) const;
|
||||
int expandedRow(int index) const;
|
||||
int collapsedRow(int index) const;
|
||||
void loadData();
|
||||
void clear();
|
||||
|
||||
private:
|
||||
static QMetaEnum metaEnum(const char *name);
|
||||
int m_keyTypeId;
|
||||
int m_mouseTypeId;
|
||||
|
||||
QVector<InputEvent> m_data;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlProfiler
|
||||
278
src/plugins/qmlprofiler/memoryusagemodel.cpp
Normal file
278
src/plugins/qmlprofiler/memoryusagemodel.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "memoryusagemodel.h"
|
||||
#include "qmlprofilermodelmanager.h"
|
||||
#include "qmldebug/qmlprofilereventtypes.h"
|
||||
|
||||
#include <QStack>
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
MemoryUsageModel::MemoryUsageModel(QmlProfilerModelManager *manager, QObject *parent) :
|
||||
QmlProfilerTimelineModel(manager, QmlDebug::MemoryAllocation, QmlDebug::MaximumRangeType,
|
||||
QmlDebug::ProfileMemory, parent)
|
||||
{
|
||||
m_maxSize = 1;
|
||||
announceFeatures((1ULL << mainFeature()) | QmlDebug::Constants::QML_JS_RANGE_FEATURES);
|
||||
}
|
||||
|
||||
int MemoryUsageModel::rowMaxValue(int rowNumber) const
|
||||
{
|
||||
Q_UNUSED(rowNumber);
|
||||
return m_maxSize;
|
||||
}
|
||||
|
||||
int MemoryUsageModel::expandedRow(int index) const
|
||||
{
|
||||
int type = selectionId(index);
|
||||
return (type == QmlDebug::HeapPage || type == QmlDebug::LargeItem) ? 1 : 2;
|
||||
}
|
||||
|
||||
int MemoryUsageModel::collapsedRow(int index) const
|
||||
{
|
||||
return expandedRow(index);
|
||||
}
|
||||
|
||||
int MemoryUsageModel::typeId(int index) const
|
||||
{
|
||||
return m_data[index].typeId;
|
||||
}
|
||||
|
||||
QColor MemoryUsageModel::color(int index) const
|
||||
{
|
||||
return colorBySelectionId(index);
|
||||
}
|
||||
|
||||
float MemoryUsageModel::relativeHeight(int index) const
|
||||
{
|
||||
return qMin(1.0f, (float)m_data[index].size / (float)m_maxSize);
|
||||
}
|
||||
|
||||
QVariantMap MemoryUsageModel::location(int index) const
|
||||
{
|
||||
static const QLatin1String file("file");
|
||||
static const QLatin1String line("line");
|
||||
static const QLatin1String column("column");
|
||||
|
||||
QVariantMap result;
|
||||
|
||||
int originType = m_data[index].originTypeIndex;
|
||||
if (originType > -1) {
|
||||
const QmlDebug::QmlEventLocation &location =
|
||||
modelManager()->qmlModel()->getEventTypes().at(originType).location;
|
||||
|
||||
result.insert(file, location.filename);
|
||||
result.insert(line, location.line);
|
||||
result.insert(column, location.column);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QVariantList MemoryUsageModel::labels() const
|
||||
{
|
||||
QVariantList result;
|
||||
|
||||
QVariantMap element;
|
||||
element.insert(QLatin1String("description"), QVariant(tr("Memory Allocation")));
|
||||
element.insert(QLatin1String("id"), QVariant(QmlDebug::HeapPage));
|
||||
result << element;
|
||||
|
||||
element.clear();
|
||||
element.insert(QLatin1String("description"), QVariant(tr("Memory Usage")));
|
||||
element.insert(QLatin1String("id"), QVariant(QmlDebug::SmallItem));
|
||||
result << element;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QVariantMap MemoryUsageModel::details(int index) const
|
||||
{
|
||||
QVariantMap result;
|
||||
const MemoryAllocation *ev = &m_data[index];
|
||||
|
||||
if (ev->allocated >= -ev->deallocated)
|
||||
result.insert(QLatin1String("displayName"), tr("Memory Allocated"));
|
||||
else
|
||||
result.insert(QLatin1String("displayName"), tr("Memory Freed"));
|
||||
|
||||
result.insert(tr("Total"), QString::fromLatin1("%1 bytes").arg(ev->size));
|
||||
if (ev->allocations > 0) {
|
||||
result.insert(tr("Allocated"), QString::fromLatin1("%1 bytes").arg(ev->allocated));
|
||||
result.insert(tr("Allocations"), QString::number(ev->allocations));
|
||||
}
|
||||
if (ev->deallocations > 0) {
|
||||
result.insert(tr("Deallocated"), QString::fromLatin1("%1 bytes").arg(-ev->deallocated));
|
||||
result.insert(tr("Deallocations"), QString::number(ev->deallocations));
|
||||
}
|
||||
result.insert(tr("Type"), QVariant(memoryTypeName(selectionId(index))));
|
||||
|
||||
if (ev->originTypeIndex != -1) {
|
||||
result.insert(tr("Location"),
|
||||
modelManager()->qmlModel()->getEventTypes().at(ev->originTypeIndex).displayName);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
struct RangeStackFrame {
|
||||
RangeStackFrame() : originTypeIndex(-1), startTime(-1), endTime(-1) {}
|
||||
RangeStackFrame(int originTypeIndex, qint64 startTime, qint64 endTime) :
|
||||
originTypeIndex(originTypeIndex), startTime(startTime), endTime(endTime) {}
|
||||
int originTypeIndex;
|
||||
qint64 startTime;
|
||||
qint64 endTime;
|
||||
};
|
||||
|
||||
void MemoryUsageModel::loadData()
|
||||
{
|
||||
QmlProfilerDataModel *simpleModel = modelManager()->qmlModel();
|
||||
if (simpleModel->isEmpty())
|
||||
return;
|
||||
|
||||
qint64 currentSize = 0;
|
||||
qint64 currentUsage = 0;
|
||||
int currentUsageIndex = -1;
|
||||
int currentJSHeapIndex = -1;
|
||||
|
||||
QStack<RangeStackFrame> rangeStack;
|
||||
|
||||
const QVector<QmlProfilerDataModel::QmlEventTypeData> &types = simpleModel->getEventTypes();
|
||||
foreach (const QmlProfilerDataModel::QmlEventData &event, simpleModel->getEvents()) {
|
||||
const QmlProfilerDataModel::QmlEventTypeData &type = types[event.typeIndex()];
|
||||
while (!rangeStack.empty() && rangeStack.top().endTime < event.startTime())
|
||||
rangeStack.pop();
|
||||
if (!accepted(type)) {
|
||||
if (type.rangeType != QmlDebug::MaximumRangeType) {
|
||||
rangeStack.push(RangeStackFrame(event.typeIndex(), event.startTime(),
|
||||
event.startTime() + event.duration()));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type.detailType == QmlDebug::SmallItem || type.detailType == QmlDebug::LargeItem) {
|
||||
if (!rangeStack.empty() && currentUsageIndex > -1 &&
|
||||
type.detailType == selectionId(currentUsageIndex) &&
|
||||
m_data[currentUsageIndex].originTypeIndex == rangeStack.top().originTypeIndex &&
|
||||
rangeStack.top().startTime < startTime(currentUsageIndex)) {
|
||||
m_data[currentUsageIndex].update(event.numericData(0));
|
||||
currentUsage = m_data[currentUsageIndex].size;
|
||||
} else {
|
||||
MemoryAllocation allocation(event.typeIndex(), currentUsage,
|
||||
rangeStack.empty() ? -1 : rangeStack.top().originTypeIndex);
|
||||
allocation.update(event.numericData(0));
|
||||
currentUsage = allocation.size;
|
||||
|
||||
if (currentUsageIndex != -1) {
|
||||
insertEnd(currentUsageIndex,
|
||||
event.startTime() - startTime(currentUsageIndex) - 1);
|
||||
}
|
||||
currentUsageIndex = insertStart(event.startTime(), QmlDebug::SmallItem);
|
||||
m_data.insert(currentUsageIndex, allocation);
|
||||
}
|
||||
}
|
||||
|
||||
if (type.detailType == QmlDebug::HeapPage || type.detailType == QmlDebug::LargeItem) {
|
||||
if (!rangeStack.empty() && currentJSHeapIndex > -1 &&
|
||||
type.detailType == selectionId(currentJSHeapIndex) &&
|
||||
m_data[currentJSHeapIndex].originTypeIndex ==
|
||||
rangeStack.top().originTypeIndex &&
|
||||
rangeStack.top().startTime < startTime(currentJSHeapIndex)) {
|
||||
m_data[currentJSHeapIndex].update(event.numericData(0));
|
||||
currentSize = m_data[currentJSHeapIndex].size;
|
||||
} else {
|
||||
MemoryAllocation allocation(event.typeIndex(), currentSize,
|
||||
rangeStack.empty() ? -1 : rangeStack.top().originTypeIndex);
|
||||
allocation.update(event.numericData(0));
|
||||
currentSize = allocation.size;
|
||||
|
||||
if (currentSize > m_maxSize)
|
||||
m_maxSize = currentSize;
|
||||
if (currentJSHeapIndex != -1)
|
||||
insertEnd(currentJSHeapIndex,
|
||||
event.startTime() - startTime(currentJSHeapIndex) - 1);
|
||||
currentJSHeapIndex = insertStart(event.startTime(), type.detailType);
|
||||
m_data.insert(currentJSHeapIndex, allocation);
|
||||
}
|
||||
}
|
||||
|
||||
updateProgress(count(), simpleModel->getEvents().count());
|
||||
}
|
||||
|
||||
if (currentJSHeapIndex != -1)
|
||||
insertEnd(currentJSHeapIndex, modelManager()->traceTime()->endTime() -
|
||||
startTime(currentJSHeapIndex) - 1);
|
||||
if (currentUsageIndex != -1)
|
||||
insertEnd(currentUsageIndex, modelManager()->traceTime()->endTime() -
|
||||
startTime(currentUsageIndex) - 1);
|
||||
|
||||
|
||||
computeNesting();
|
||||
setExpandedRowCount(3);
|
||||
setCollapsedRowCount(3);
|
||||
updateProgress(1, 1);
|
||||
}
|
||||
|
||||
void MemoryUsageModel::clear()
|
||||
{
|
||||
m_data.clear();
|
||||
m_maxSize = 1;
|
||||
QmlProfilerTimelineModel::clear();
|
||||
}
|
||||
|
||||
QString MemoryUsageModel::memoryTypeName(int type)
|
||||
{
|
||||
switch (type) {
|
||||
case QmlDebug::HeapPage: return tr("Heap Allocation");
|
||||
case QmlDebug::LargeItem: return tr("Large Item Allocation");
|
||||
case QmlDebug::SmallItem: return tr("Heap Usage");
|
||||
case QmlDebug::MaximumMemoryType: return tr("Total");
|
||||
default: return tr("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
MemoryUsageModel::MemoryAllocation::MemoryAllocation(int type, qint64 baseAmount,
|
||||
int originTypeIndex) :
|
||||
typeId(type), size(baseAmount), allocated(0), deallocated(0), allocations(0), deallocations(0),
|
||||
originTypeIndex(originTypeIndex)
|
||||
{
|
||||
}
|
||||
|
||||
void MemoryUsageModel::MemoryAllocation::update(qint64 amount)
|
||||
{
|
||||
size += amount;
|
||||
if (amount < 0) {
|
||||
deallocated += amount;
|
||||
++deallocations;
|
||||
} else {
|
||||
allocated += amount;
|
||||
++allocations;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlProfiler
|
||||
82
src/plugins/qmlprofiler/memoryusagemodel.h
Normal file
82
src/plugins/qmlprofiler/memoryusagemodel.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "qmlprofilertimelinemodel.h"
|
||||
#include "qmlprofilerdatamodel.h"
|
||||
|
||||
#include <QStringList>
|
||||
#include <QColor>
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
class MemoryUsageModel : public QmlProfilerTimelineModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
struct MemoryAllocation {
|
||||
int typeId;
|
||||
qint64 size;
|
||||
qint64 allocated;
|
||||
qint64 deallocated;
|
||||
int allocations;
|
||||
int deallocations;
|
||||
int originTypeIndex;
|
||||
|
||||
MemoryAllocation(int typeId = -1, qint64 baseAmount = 0, int originTypeIndex = -1);
|
||||
void update(qint64 amount);
|
||||
};
|
||||
|
||||
MemoryUsageModel(QmlProfilerModelManager *manager, QObject *parent = 0);
|
||||
|
||||
int rowMaxValue(int rowNumber) const;
|
||||
|
||||
int expandedRow(int index) const;
|
||||
int collapsedRow(int index) const;
|
||||
int typeId(int index) const;
|
||||
QColor color(int index) const;
|
||||
float relativeHeight(int index) const;
|
||||
|
||||
QVariantMap location(int index) const;
|
||||
|
||||
QVariantList labels() const;
|
||||
QVariantMap details(int index) const;
|
||||
|
||||
protected:
|
||||
void loadData();
|
||||
void clear();
|
||||
|
||||
private:
|
||||
static QString memoryTypeName(int type);
|
||||
|
||||
QVector<MemoryAllocation> m_data;
|
||||
qint64 m_maxSize;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlProfiler
|
||||
498
src/plugins/qmlprofiler/pixmapcachemodel.cpp
Normal file
498
src/plugins/qmlprofiler/pixmapcachemodel.cpp
Normal file
@@ -0,0 +1,498 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "pixmapcachemodel.h"
|
||||
#include "qmlprofilermodelmanager.h"
|
||||
#include "qmldebug/qmlprofilereventtypes.h"
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
PixmapCacheModel::PixmapCacheModel(QmlProfilerModelManager *manager, QObject *parent) :
|
||||
QmlProfilerTimelineModel(manager, QmlDebug::PixmapCacheEvent, QmlDebug::MaximumRangeType,
|
||||
QmlDebug::ProfilePixmapCache, parent)
|
||||
{
|
||||
m_maxCacheSize = 1;
|
||||
}
|
||||
|
||||
int PixmapCacheModel::rowMaxValue(int rowNumber) const
|
||||
{
|
||||
if (rowNumber == 1) {
|
||||
return m_maxCacheSize;
|
||||
} else {
|
||||
return QmlProfilerTimelineModel::rowMaxValue(rowNumber);
|
||||
}
|
||||
}
|
||||
|
||||
int PixmapCacheModel::expandedRow(int index) const
|
||||
{
|
||||
return selectionId(index) + 1;
|
||||
}
|
||||
|
||||
int PixmapCacheModel::collapsedRow(int index) const
|
||||
{
|
||||
return m_data[index].rowNumberCollapsed;
|
||||
}
|
||||
|
||||
int PixmapCacheModel::typeId(int index) const
|
||||
{
|
||||
return m_data[index].typeId;
|
||||
}
|
||||
|
||||
QColor PixmapCacheModel::color(int index) const
|
||||
{
|
||||
if (m_data[index].pixmapEventType == PixmapCacheCountChanged)
|
||||
return colorByHue(s_pixmapCacheCountHue);
|
||||
|
||||
return colorBySelectionId(index);
|
||||
}
|
||||
|
||||
float PixmapCacheModel::relativeHeight(int index) const
|
||||
{
|
||||
if (m_data[index].pixmapEventType == PixmapCacheCountChanged)
|
||||
return (float)m_data[index].cacheSize / (float)m_maxCacheSize;
|
||||
else
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
QString getFilenameOnly(QString absUrl)
|
||||
{
|
||||
int characterPos = absUrl.lastIndexOf(QLatin1Char('/'))+1;
|
||||
if (characterPos < absUrl.length())
|
||||
absUrl = absUrl.mid(characterPos);
|
||||
return absUrl;
|
||||
}
|
||||
|
||||
QVariantList PixmapCacheModel::labels() const
|
||||
{
|
||||
QVariantList result;
|
||||
|
||||
// Cache Size
|
||||
QVariantMap element;
|
||||
element.insert(QLatin1String("description"), QVariant(QLatin1String("Cache Size")));
|
||||
|
||||
element.insert(QLatin1String("id"), QVariant(0));
|
||||
result << element;
|
||||
|
||||
for (int i=0; i < m_pixmaps.count(); i++) {
|
||||
// Loading
|
||||
QVariantMap element;
|
||||
element.insert(QLatin1String("displayName"), m_pixmaps[i].url);
|
||||
element.insert(QLatin1String("description"),
|
||||
QVariant(getFilenameOnly(m_pixmaps[i].url)));
|
||||
|
||||
element.insert(QLatin1String("id"), QVariant(i+1));
|
||||
result << element;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QVariantMap PixmapCacheModel::details(int index) const
|
||||
{
|
||||
QVariantMap result;
|
||||
const PixmapCacheEvent *ev = &m_data[index];
|
||||
|
||||
if (ev->pixmapEventType == PixmapCacheCountChanged) {
|
||||
result.insert(QLatin1String("displayName"), tr("Image Cached"));
|
||||
} else {
|
||||
if (ev->pixmapEventType == PixmapLoadingStarted) {
|
||||
result.insert(QLatin1String("displayName"), tr("Image Loaded"));
|
||||
if (m_pixmaps[ev->urlIndex].sizes[ev->sizeIndex].loadState != Finished)
|
||||
result.insert(tr("Result"), tr("Load Error"));
|
||||
}
|
||||
result.insert(tr("Duration"), QmlProfilerDataModel::formatTime(duration(index)));
|
||||
}
|
||||
|
||||
result.insert(tr("Cache Size"), QString::fromLatin1("%1 px").arg(ev->cacheSize));
|
||||
result.insert(tr("File"), getFilenameOnly(m_pixmaps[ev->urlIndex].url));
|
||||
result.insert(tr("Width"), QString::fromLatin1("%1 px")
|
||||
.arg(m_pixmaps[ev->urlIndex].sizes[ev->sizeIndex].size.width()));
|
||||
result.insert(tr("Height"), QString::fromLatin1("%1 px")
|
||||
.arg(m_pixmaps[ev->urlIndex].sizes[ev->sizeIndex].size.height()));
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Ultimately there is no way to know which cache entry a given event refers to as long as we only
|
||||
* receive the pixmap URL from the application. Multiple copies of different sizes may be cached
|
||||
* for each URL. However, we can apply some heuristics to make the result somewhat plausible by
|
||||
* using the following assumptions:
|
||||
*
|
||||
* - PixmapSizeKnown will happen at most once for every cache entry.
|
||||
* - PixmapSizeKnown cannot happen for entries with PixmapLoadingError and vice versa.
|
||||
* - PixmapCacheCountChanged can happen for entries with PixmapLoadingError but doesn't have to.
|
||||
* - Decreasing PixmapCacheCountChanged events can only happen for entries that have seen an
|
||||
* increasing PixmapCacheCountChanged (but that may have happened before the trace).
|
||||
* - PixmapCacheCountChanged can happen before or after PixmapSizeKnown.
|
||||
* - For every PixmapLoadingFinished or PixmapLoadingError there is exactly one
|
||||
* PixmapLoadingStarted event, but it may be before the trace.
|
||||
* - For every PixmapLoadingStarted there is exactly one PixmapLoadingFinished or
|
||||
* PixmapLoadingError, but it may be after the trace.
|
||||
* - Decreasing PixmapCacheCountChanged events in the presence of corrupt cache entries are more
|
||||
* likely to clear those entries than other, correctly loaded ones.
|
||||
* - Increasing PixmapCacheCountChanged events are more likely to refer to correctly loaded entries
|
||||
* than to ones with PixmapLoadingError.
|
||||
* - PixmapLoadingFinished and PixmapLoadingError are more likely to refer to cache entries that
|
||||
* have seen a PixmapLoadingStarted than to ones that haven't.
|
||||
*
|
||||
* For each URL we keep an ordered list of pixmaps possibly being loaded and assign new events to
|
||||
* the first entry that "fits". If multiple sizes of the same pixmap are being loaded concurrently
|
||||
* we generally assume that the PixmapLoadingFinished and PixmapLoadingError events occur in the
|
||||
* order we learn about the existence of these sizes, subject to the above constraints. This is not
|
||||
* necessarily the order the pixmaps are really loaded but it's the best we can do with the given
|
||||
* information. If they're loaded sequentially the representation is correct.
|
||||
*/
|
||||
|
||||
void PixmapCacheModel::loadData()
|
||||
{
|
||||
QmlProfilerDataModel *simpleModel = modelManager()->qmlModel();
|
||||
if (simpleModel->isEmpty())
|
||||
return;
|
||||
|
||||
int lastCacheSizeEvent = -1;
|
||||
int cumulatedCount = 0;
|
||||
|
||||
const QVector<QmlProfilerDataModel::QmlEventTypeData> &types = simpleModel->getEventTypes();
|
||||
foreach (const QmlProfilerDataModel::QmlEventData &event, simpleModel->getEvents()) {
|
||||
const QmlProfilerDataModel::QmlEventTypeData &type = types[event.typeIndex()];
|
||||
if (!accepted(type))
|
||||
continue;
|
||||
|
||||
PixmapCacheEvent newEvent;
|
||||
newEvent.pixmapEventType = static_cast<PixmapEventType>(type.detailType);
|
||||
qint64 pixmapStartTime = event.startTime();
|
||||
|
||||
newEvent.urlIndex = -1;
|
||||
for (QVector<Pixmap>::const_iterator it(m_pixmaps.cend()); it != m_pixmaps.cbegin();) {
|
||||
if ((--it)->url == type.location.filename) {
|
||||
newEvent.urlIndex = it - m_pixmaps.cbegin();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
newEvent.sizeIndex = -1;
|
||||
if (newEvent.urlIndex == -1) {
|
||||
newEvent.urlIndex = m_pixmaps.count();
|
||||
m_pixmaps << Pixmap(type.location.filename);
|
||||
}
|
||||
|
||||
Pixmap &pixmap = m_pixmaps[newEvent.urlIndex];
|
||||
switch (newEvent.pixmapEventType) {
|
||||
case PixmapSizeKnown: {// pixmap size
|
||||
// Look for pixmaps for which we don't know the size, yet and which have actually been
|
||||
// loaded.
|
||||
for (QVector<PixmapState>::iterator i(pixmap.sizes.begin());
|
||||
i != pixmap.sizes.end(); ++i) {
|
||||
if (i->size.isValid() || i->cacheState == Uncacheable || i->cacheState == Corrupt)
|
||||
continue;
|
||||
|
||||
// We can't have cached it before we knew the size
|
||||
Q_ASSERT(i->cacheState != Cached);
|
||||
|
||||
i->size.setWidth(event.numericData(0));
|
||||
i->size.setHeight(event.numericData(1));
|
||||
newEvent.sizeIndex = i - pixmap.sizes.begin();
|
||||
break;
|
||||
}
|
||||
|
||||
if (newEvent.sizeIndex == -1) {
|
||||
newEvent.sizeIndex = pixmap.sizes.length();
|
||||
pixmap.sizes << PixmapState(event.numericData(0), event.numericData(1));
|
||||
}
|
||||
|
||||
PixmapState &state = pixmap.sizes[newEvent.sizeIndex];
|
||||
if (state.cacheState == ToBeCached) {
|
||||
lastCacheSizeEvent = updateCacheCount(lastCacheSizeEvent, pixmapStartTime,
|
||||
state.size.width() * state.size.height(), newEvent,
|
||||
event.typeIndex());
|
||||
state.cacheState = Cached;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PixmapCacheCountChanged: {// Cache Size Changed Event
|
||||
pixmapStartTime = event.startTime() + 1; // delay 1 ns for proper sorting
|
||||
|
||||
bool uncache = cumulatedCount > event.numericData(2);
|
||||
cumulatedCount = event.numericData(2);
|
||||
qint64 pixSize = 0;
|
||||
|
||||
// First try to find a preferred pixmap, which either is Corrupt and will be uncached
|
||||
// or is uncached and will be cached.
|
||||
for (QVector<PixmapState>::iterator i(pixmap.sizes.begin());
|
||||
i != pixmap.sizes.end(); ++i) {
|
||||
if (uncache && i->cacheState == Corrupt) {
|
||||
newEvent.sizeIndex = i - pixmap.sizes.begin();
|
||||
i->cacheState = Uncacheable;
|
||||
break;
|
||||
} else if (!uncache && i->cacheState == Uncached) {
|
||||
newEvent.sizeIndex = i - pixmap.sizes.begin();
|
||||
if (i->size.isValid()) {
|
||||
pixSize = i->size.width() * i->size.height();
|
||||
i->cacheState = Cached;
|
||||
} else {
|
||||
i->cacheState = ToBeCached;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If none found, check for cached or ToBeCached pixmaps that shall be uncached or
|
||||
// Error pixmaps that become corrupt cache entries. We also accept Initial to be
|
||||
// uncached as we may have missed the matching PixmapCacheCountChanged that cached it.
|
||||
if (newEvent.sizeIndex == -1) {
|
||||
for (QVector<PixmapState>::iterator i(pixmap.sizes.begin());
|
||||
i != pixmap.sizes.end(); ++i) {
|
||||
if (uncache && (i->cacheState == Cached || i->cacheState == ToBeCached ||
|
||||
i->cacheState == Uncached)) {
|
||||
newEvent.sizeIndex = i - pixmap.sizes.begin();
|
||||
if (i->size.isValid())
|
||||
pixSize = -i->size.width() * i->size.height();
|
||||
i->cacheState = Uncached;
|
||||
break;
|
||||
} else if (!uncache && i->cacheState == Uncacheable) {
|
||||
newEvent.sizeIndex = i - pixmap.sizes.begin();
|
||||
i->cacheState = Corrupt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If that does't work, create a new entry.
|
||||
if (newEvent.sizeIndex == -1) {
|
||||
newEvent.sizeIndex = pixmap.sizes.length();
|
||||
pixmap.sizes << PixmapState(uncache ? Uncached : ToBeCached);
|
||||
}
|
||||
|
||||
lastCacheSizeEvent = updateCacheCount(lastCacheSizeEvent, pixmapStartTime, pixSize,
|
||||
newEvent, event.typeIndex());
|
||||
break;
|
||||
}
|
||||
case PixmapLoadingStarted: { // Load
|
||||
// Look for a pixmap that hasn't been started, yet. There may have been a refcount
|
||||
// event, which we ignore.
|
||||
for (QVector<PixmapState>::const_iterator i(pixmap.sizes.cbegin());
|
||||
i != pixmap.sizes.cend(); ++i) {
|
||||
if (i->loadState == Initial) {
|
||||
newEvent.sizeIndex = i - pixmap.sizes.cbegin();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (newEvent.sizeIndex == -1) {
|
||||
newEvent.sizeIndex = pixmap.sizes.length();
|
||||
pixmap.sizes << PixmapState();
|
||||
}
|
||||
|
||||
PixmapState &state = pixmap.sizes[newEvent.sizeIndex];
|
||||
state.loadState = Loading;
|
||||
newEvent.typeId = event.typeIndex();
|
||||
state.started = insertStart(pixmapStartTime, newEvent.urlIndex + 1);
|
||||
m_data.insert(state.started, newEvent);
|
||||
break;
|
||||
}
|
||||
case PixmapLoadingFinished:
|
||||
case PixmapLoadingError: {
|
||||
// First try to find one that has already started
|
||||
for (QVector<PixmapState>::const_iterator i(pixmap.sizes.cbegin());
|
||||
i != pixmap.sizes.cend(); ++i) {
|
||||
if (i->loadState != Loading)
|
||||
continue;
|
||||
// Pixmaps with known size cannot be errors and vice versa
|
||||
if (newEvent.pixmapEventType == PixmapLoadingError && i->size.isValid())
|
||||
continue;
|
||||
|
||||
newEvent.sizeIndex = i - pixmap.sizes.cbegin();
|
||||
break;
|
||||
}
|
||||
|
||||
// If none was found use any other compatible one
|
||||
if (newEvent.sizeIndex == -1) {
|
||||
for (QVector<PixmapState>::const_iterator i(pixmap.sizes.cbegin());
|
||||
i != pixmap.sizes.cend(); ++i) {
|
||||
if (i->loadState != Initial)
|
||||
continue;
|
||||
// Pixmaps with known size cannot be errors and vice versa
|
||||
if (newEvent.pixmapEventType == PixmapLoadingError && i->size.isValid())
|
||||
continue;
|
||||
|
||||
newEvent.sizeIndex = i - pixmap.sizes.cbegin();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If again none was found, create one.
|
||||
if (newEvent.sizeIndex == -1) {
|
||||
newEvent.sizeIndex = pixmap.sizes.length();
|
||||
pixmap.sizes << PixmapState();
|
||||
}
|
||||
|
||||
PixmapState &state = pixmap.sizes[newEvent.sizeIndex];
|
||||
// If the pixmap loading wasn't started, start it at traceStartTime()
|
||||
if (state.loadState == Initial) {
|
||||
newEvent.pixmapEventType = PixmapLoadingStarted;
|
||||
newEvent.typeId = event.typeIndex();
|
||||
qint64 traceStart = modelManager()->traceTime()->startTime();
|
||||
state.started = insert(traceStart, pixmapStartTime - traceStart,
|
||||
newEvent.urlIndex + 1);
|
||||
m_data.insert(state.started, newEvent);
|
||||
|
||||
// All other indices are wrong now as we've prepended. Fix them ...
|
||||
if (lastCacheSizeEvent >= state.started)
|
||||
++lastCacheSizeEvent;
|
||||
|
||||
for (int pixmapIndex = 0; pixmapIndex < m_pixmaps.count(); ++pixmapIndex) {
|
||||
Pixmap &brokenPixmap = m_pixmaps[pixmapIndex];
|
||||
for (int sizeIndex = 0; sizeIndex < brokenPixmap.sizes.count(); ++sizeIndex) {
|
||||
PixmapState &brokenSize = brokenPixmap.sizes[sizeIndex];
|
||||
if ((pixmapIndex != newEvent.urlIndex || sizeIndex != newEvent.sizeIndex) &&
|
||||
brokenSize.started >= state.started) {
|
||||
++brokenSize.started;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
insertEnd(state.started, pixmapStartTime - startTime(state.started));
|
||||
if (newEvent.pixmapEventType == PixmapLoadingError) {
|
||||
state.loadState = Error;
|
||||
switch (state.cacheState) {
|
||||
case Uncached:
|
||||
state.cacheState = Uncacheable;
|
||||
break;
|
||||
case ToBeCached:
|
||||
state.cacheState = Corrupt;
|
||||
break;
|
||||
default:
|
||||
// Cached cannot happen as size would have to be known and Corrupt or
|
||||
// Uncacheable cannot happen as we only accept one finish or error event per
|
||||
// pixmap.
|
||||
Q_ASSERT(false);
|
||||
}
|
||||
} else {
|
||||
state.loadState = Finished;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
updateProgress(count(), 2 * simpleModel->getEvents().count());
|
||||
}
|
||||
|
||||
if (lastCacheSizeEvent != -1)
|
||||
insertEnd(lastCacheSizeEvent, modelManager()->traceTime()->endTime() -
|
||||
startTime(lastCacheSizeEvent));
|
||||
|
||||
resizeUnfinishedLoads();
|
||||
|
||||
computeMaxCacheSize();
|
||||
flattenLoads();
|
||||
computeNesting();
|
||||
|
||||
updateProgress(1, 1);
|
||||
}
|
||||
|
||||
void PixmapCacheModel::clear()
|
||||
{
|
||||
m_pixmaps.clear();
|
||||
m_maxCacheSize = 1;
|
||||
m_data.clear();
|
||||
QmlProfilerTimelineModel::clear();
|
||||
}
|
||||
|
||||
void PixmapCacheModel::computeMaxCacheSize()
|
||||
{
|
||||
m_maxCacheSize = 1;
|
||||
foreach (const PixmapCacheModel::PixmapCacheEvent &event, m_data) {
|
||||
if (event.pixmapEventType == PixmapCacheModel::PixmapCacheCountChanged) {
|
||||
if (event.cacheSize > m_maxCacheSize)
|
||||
m_maxCacheSize = event.cacheSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PixmapCacheModel::resizeUnfinishedLoads()
|
||||
{
|
||||
// all the "load start" events with duration 0 continue till the end of the trace
|
||||
for (int i = 0; i < count(); i++) {
|
||||
if (m_data[i].pixmapEventType == PixmapCacheModel::PixmapLoadingStarted &&
|
||||
duration(i) == 0) {
|
||||
insertEnd(i, modelManager()->traceTime()->endTime() - startTime(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PixmapCacheModel::flattenLoads()
|
||||
{
|
||||
int collapsedRowCount = 0;
|
||||
|
||||
// computes "compressed row"
|
||||
QVector <qint64> eventEndTimes;
|
||||
for (int i = 0; i < count(); i++) {
|
||||
PixmapCacheModel::PixmapCacheEvent &event = m_data[i];
|
||||
if (event.pixmapEventType == PixmapCacheModel::PixmapLoadingStarted) {
|
||||
event.rowNumberCollapsed = 0;
|
||||
while (eventEndTimes.count() > event.rowNumberCollapsed &&
|
||||
eventEndTimes[event.rowNumberCollapsed] > startTime(i))
|
||||
event.rowNumberCollapsed++;
|
||||
|
||||
if (eventEndTimes.count() == event.rowNumberCollapsed)
|
||||
eventEndTimes << 0; // increase stack length, proper value added below
|
||||
eventEndTimes[event.rowNumberCollapsed] = endTime(i);
|
||||
|
||||
// readjust to account for category empty row and bargraph
|
||||
event.rowNumberCollapsed += 2;
|
||||
}
|
||||
if (event.rowNumberCollapsed > collapsedRowCount)
|
||||
collapsedRowCount = event.rowNumberCollapsed;
|
||||
}
|
||||
|
||||
// Starting from 0, count is maxIndex+1
|
||||
setCollapsedRowCount(collapsedRowCount + 1);
|
||||
setExpandedRowCount(m_pixmaps.count() + 2);
|
||||
}
|
||||
|
||||
int PixmapCacheModel::updateCacheCount(int lastCacheSizeEvent,
|
||||
qint64 pixmapStartTime, qint64 pixSize, PixmapCacheEvent &newEvent, int typeId)
|
||||
{
|
||||
newEvent.pixmapEventType = PixmapCacheCountChanged;
|
||||
newEvent.rowNumberCollapsed = 1;
|
||||
|
||||
qint64 prevSize = 0;
|
||||
if (lastCacheSizeEvent != -1) {
|
||||
prevSize = m_data[lastCacheSizeEvent].cacheSize;
|
||||
insertEnd(lastCacheSizeEvent, pixmapStartTime - startTime(lastCacheSizeEvent));
|
||||
}
|
||||
|
||||
newEvent.cacheSize = prevSize + pixSize;
|
||||
newEvent.typeId = typeId;
|
||||
int index = insertStart(pixmapStartTime, 0);
|
||||
m_data.insert(index, newEvent);
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlProfiler
|
||||
127
src/plugins/qmlprofiler/pixmapcachemodel.h
Normal file
127
src/plugins/qmlprofiler/pixmapcachemodel.h
Normal file
@@ -0,0 +1,127 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "qmlprofilertimelinemodel.h"
|
||||
#include "qmlprofilerdatamodel.h"
|
||||
|
||||
#include <QStringList>
|
||||
#include <QColor>
|
||||
#include <QSize>
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
class PixmapCacheModel : public QmlProfilerTimelineModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum CacheState {
|
||||
Uncached, // After loading started (or some other proof of existence) or after uncaching
|
||||
ToBeCached, // After determining the pixmap is to be cached but before knowing its size
|
||||
Cached, // After caching a pixmap or determining the size of a ToBeCached pixmap
|
||||
Uncacheable, // If loading failed without ToBeCached or after a corrupt pixmap has been uncached
|
||||
Corrupt // If after ToBeCached we learn that loading failed
|
||||
};
|
||||
|
||||
enum LoadState {
|
||||
Initial,
|
||||
Loading,
|
||||
Finished,
|
||||
Error
|
||||
};
|
||||
|
||||
struct PixmapState {
|
||||
PixmapState(int width, int height, CacheState cache = Uncached) :
|
||||
size(width, height), started(-1), loadState(Initial), cacheState(cache) {}
|
||||
PixmapState(CacheState cache = Uncached) : started(-1), loadState(Initial), cacheState(cache) {}
|
||||
QSize size;
|
||||
int started;
|
||||
LoadState loadState;
|
||||
CacheState cacheState;
|
||||
};
|
||||
|
||||
struct Pixmap {
|
||||
Pixmap() {}
|
||||
Pixmap(const QString &url) : url(url), sizes(1) {}
|
||||
QString url;
|
||||
QVector<PixmapState> sizes;
|
||||
};
|
||||
|
||||
enum PixmapEventType {
|
||||
PixmapSizeKnown,
|
||||
PixmapReferenceCountChanged,
|
||||
PixmapCacheCountChanged,
|
||||
PixmapLoadingStarted,
|
||||
PixmapLoadingFinished,
|
||||
PixmapLoadingError,
|
||||
|
||||
MaximumPixmapEventType
|
||||
};
|
||||
|
||||
struct PixmapCacheEvent {
|
||||
int typeId;
|
||||
PixmapEventType pixmapEventType;
|
||||
int urlIndex;
|
||||
int sizeIndex;
|
||||
int rowNumberCollapsed;
|
||||
qint64 cacheSize;
|
||||
};
|
||||
|
||||
PixmapCacheModel(QmlProfilerModelManager *manager, QObject *parent = 0);
|
||||
|
||||
int rowMaxValue(int rowNumber) const;
|
||||
|
||||
int expandedRow(int index) const;
|
||||
int collapsedRow(int index) const;
|
||||
int typeId(int index) const;
|
||||
QColor color(int index) const;
|
||||
float relativeHeight(int index) const;
|
||||
|
||||
QVariantList labels() const;
|
||||
|
||||
QVariantMap details(int index) const;
|
||||
|
||||
protected:
|
||||
void loadData();
|
||||
void clear();
|
||||
|
||||
private:
|
||||
void computeMaxCacheSize();
|
||||
void resizeUnfinishedLoads();
|
||||
void flattenLoads();
|
||||
int updateCacheCount(int lastCacheSizeEvent, qint64 startTime, qint64 pixSize,
|
||||
PixmapCacheEvent &newEvent, int typeId);
|
||||
|
||||
QVector<PixmapCacheEvent> m_data;
|
||||
QVector<Pixmap> m_pixmaps;
|
||||
qint64 m_maxCacheSize;
|
||||
|
||||
static const int s_pixmapCacheCountHue = 240;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlProfiler
|
||||
211
src/plugins/qmlprofiler/qml/FlameGraphDetails.qml
Normal file
211
src/plugins/qmlprofiler/qml/FlameGraphDetails.qml
Normal file
@@ -0,0 +1,211 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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 QtQuick 2.1
|
||||
|
||||
Item {
|
||||
id: rangeDetails
|
||||
|
||||
property color titleBarColor: "#55a3b8"
|
||||
property color titleBarTextColor: "white"
|
||||
property color contentColor: "white"
|
||||
property color contentTextColor: "black"
|
||||
property color borderColor: "#a0a0a0"
|
||||
property color noteTextColor: "orange"
|
||||
|
||||
property real titleBarHeight: 20
|
||||
property real borderWidth: 1
|
||||
property real outerMargin: 10
|
||||
property real innerMargin: 5
|
||||
property real minimumInnerWidth: 150
|
||||
property real initialWidth: 300
|
||||
|
||||
property real minimumX
|
||||
property real maximumX
|
||||
property real minimumY
|
||||
property real maximumY
|
||||
|
||||
property string dialogTitle
|
||||
property var model
|
||||
property string note
|
||||
|
||||
signal clearSelection
|
||||
|
||||
visible: dialogTitle.length > 0 || model.length > 0
|
||||
|
||||
width: dragHandle.x + dragHandle.width
|
||||
height: contentArea.height + titleBar.height
|
||||
|
||||
onMinimumXChanged: x = Math.max(x, minimumX)
|
||||
onMaximumXChanged: x = Math.min(x, Math.max(minimumX, maximumX - width))
|
||||
onMinimumYChanged: y = Math.max(y, minimumY)
|
||||
onMaximumYChanged: y = Math.min(y, Math.max(minimumY, maximumY - height))
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
drag.target: parent
|
||||
drag.minimumX: parent.minimumX
|
||||
drag.maximumX: parent.maximumX - rangeDetails.width
|
||||
drag.minimumY: parent.minimumY
|
||||
drag.maximumY: parent.maximumY - rangeDetails.height
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: titleBar
|
||||
width: parent.width
|
||||
height: titleBarHeight
|
||||
color: titleBarColor
|
||||
border.width: borderWidth
|
||||
border.color: borderColor
|
||||
|
||||
FlameGraphText {
|
||||
id: typeTitle
|
||||
text: rangeDetails.dialogTitle
|
||||
font.bold: true
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
anchors.left: parent.left
|
||||
anchors.right: closeIcon.left
|
||||
anchors.leftMargin: outerMargin
|
||||
anchors.rightMargin: innerMargin
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
color: titleBarTextColor
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
FlameGraphText {
|
||||
id: closeIcon
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.rightMargin: innerMargin
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: "X"
|
||||
color: titleBarTextColor
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: rangeDetails.clearSelection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: contentArea
|
||||
color: contentColor
|
||||
|
||||
anchors.top: titleBar.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: dragHandle.bottom
|
||||
|
||||
border.width: borderWidth
|
||||
border.color: borderColor
|
||||
}
|
||||
|
||||
Grid {
|
||||
id: col
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.top: titleBar.bottom
|
||||
anchors.topMargin: innerMargin
|
||||
anchors.leftMargin: outerMargin
|
||||
anchors.rightMargin: outerMargin
|
||||
|
||||
spacing: innerMargin
|
||||
columns: 2
|
||||
property int minimumWidth: {
|
||||
var result = minimumInnerWidth;
|
||||
for (var i = 0; i < children.length; ++i)
|
||||
result = Math.max(children[i].x, result);
|
||||
return result + 2 * outerMargin;
|
||||
}
|
||||
|
||||
onMinimumWidthChanged: {
|
||||
if (dragHandle.x < minimumWidth)
|
||||
dragHandle.x = minimumWidth;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: rangeDetails.model
|
||||
FlameGraphText {
|
||||
property bool isLabel: index % 2 === 0
|
||||
font.bold: isLabel
|
||||
elide: Text.ElideRight
|
||||
width: text === "" ? 0 : (isLabel ? implicitWidth :
|
||||
(dragHandle.x - x - innerMargin))
|
||||
text: isLabel ? (modelData + ":") : modelData
|
||||
color: contentTextColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TextEdit {
|
||||
id: noteEdit
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: outerMargin
|
||||
anchors.rightMargin: outerMargin
|
||||
anchors.topMargin: visible ? innerMargin : 0
|
||||
anchors.top: col.bottom
|
||||
height: visible ? implicitHeight : 0
|
||||
|
||||
readOnly: true
|
||||
visible: text.length > 0
|
||||
text: note
|
||||
wrapMode: Text.Wrap
|
||||
color: noteTextColor
|
||||
font.italic: true
|
||||
font.pixelSize: typeTitle.font.pixelSize
|
||||
font.family: typeTitle.font.family
|
||||
renderType: typeTitle.renderType
|
||||
selectByMouse: true
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dragHandle
|
||||
width: outerMargin
|
||||
height: outerMargin
|
||||
x: initialWidth
|
||||
anchors.top: noteEdit.bottom
|
||||
clip: true
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
drag.target: parent
|
||||
drag.minimumX: col.minimumWidth
|
||||
drag.axis: Drag.XAxis
|
||||
cursorShape: Qt.SizeHorCursor
|
||||
}
|
||||
Rectangle {
|
||||
color: titleBarColor
|
||||
rotation: 45
|
||||
width: parent.width * Math.SQRT2
|
||||
height: parent.height * Math.SQRT2
|
||||
x: parent.width - width / 2
|
||||
y: parent.height - height / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/plugins/qmlprofiler/qml/FlameGraphText.qml
Normal file
34
src/plugins/qmlprofiler/qml/FlameGraphText.qml
Normal file
@@ -0,0 +1,34 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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 QtQuick 2.0
|
||||
|
||||
Text {
|
||||
font.pixelSize: 12
|
||||
font.family: "sans-serif"
|
||||
textFormat: Text.PlainText
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
281
src/plugins/qmlprofiler/qml/FlameGraphView.qml
Normal file
281
src/plugins/qmlprofiler/qml/FlameGraphView.qml
Normal file
@@ -0,0 +1,281 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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 QtQuick 2.0
|
||||
import QtQuick.Controls 1.3
|
||||
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
|
||||
|
||||
onSelectedTypeIdChanged: tooltip.hoveredNode = null
|
||||
|
||||
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"
|
||||
property color orange: "orange"
|
||||
|
||||
function checkBindingLoop(otherTypeId) {return false;}
|
||||
|
||||
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 typeId: FlameGraph.data(FlameGraphModel.TypeId) || -1
|
||||
property bool isBindingLoop: parent.checkBindingLoop(typeId)
|
||||
property int level: parent.level + (rangeTypeVisible ? 1 : 0)
|
||||
property bool isSelected: typeId !== -1 && typeId === root.selectedTypeId
|
||||
property bool rangeTypeVisible: root.visibleRangeTypes &
|
||||
(1 << FlameGraph.data(FlameGraphModel.RangeType))
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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(FlameGraphModel.Type) || ""; }
|
||||
function note() { return FlameGraph.data(FlameGraphModel.Note) || ""; }
|
||||
function details() {
|
||||
var model = [];
|
||||
function addDetail(name, index, format) {
|
||||
model.push(name);
|
||||
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 + "%";
|
||||
}
|
||||
|
||||
if (!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);
|
||||
addDetail(qsTr("Location"), FlameGraphModel.Location, noop);
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
border.color: {
|
||||
if (flamegraphItem.isSelected)
|
||||
return flamegraph.blue2;
|
||||
else if (tooltip.hoveredNode === flamegraphItem)
|
||||
return flamegraph.blue1;
|
||||
else if (flamegraphItem.note() !== "" || flamegraphItem.isBindingLoop)
|
||||
return flamegraph.orange;
|
||||
else
|
||||
return flamegraph.grey1;
|
||||
}
|
||||
border.width: {
|
||||
if (tooltip.hoveredNode === flamegraphItem ||
|
||||
tooltip.selectedNode === flamegraphItem) {
|
||||
return 2;
|
||||
} else if (flamegraphItem.note() !== "") {
|
||||
return 3;
|
||||
} else {
|
||||
return 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 || flamegraphItem === tooltip.selectedNode
|
||||
anchors.fill: parent
|
||||
anchors.margins: 5
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: visible ? buildText() : ""
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
font.bold: flamegraphItem === tooltip.selectedNode
|
||||
|
||||
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 {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
onEntered: {
|
||||
tooltip.hoveredNode = flamegraphItem;
|
||||
}
|
||||
|
||||
onExited: {
|
||||
if (tooltip.hoveredNode === flamegraphItem)
|
||||
tooltip.hoveredNode = null;
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if (flamegraphItem.FlameGraph.dataValid) {
|
||||
tooltip.selectedNode = flamegraphItem;
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
FlameGraphDetails {
|
||||
id: tooltip
|
||||
|
||||
minimumX: 0
|
||||
maximumX: flickable.width
|
||||
minimumY: flickable.contentY
|
||||
maximumY: flickable.contentY + flickable.height
|
||||
|
||||
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.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;
|
||||
}
|
||||
onDataChanged: {
|
||||
// refresh to trigger reevaluation of note
|
||||
var selectedNode = tooltip.selectedNode;
|
||||
tooltip.selectedNode = null;
|
||||
tooltip.selectedNode = selectedNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,5 +2,8 @@
|
||||
<qresource prefix="/qmlprofiler">
|
||||
<file>bindingloops.vert</file>
|
||||
<file>bindingloops.frag</file>
|
||||
<file>FlameGraphView.qml</file>
|
||||
<file>FlameGraphText.qml</file>
|
||||
<file>FlameGraphDetails.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -5,7 +5,14 @@ QT += network qml quick quickwidgets
|
||||
include(../../qtcreatorplugin.pri)
|
||||
|
||||
SOURCES += \
|
||||
debugmessagesmodel.cpp \
|
||||
flamegraph.cpp \
|
||||
flamegraphmodel.cpp \
|
||||
flamegraphview.cpp \
|
||||
inputeventsmodel.cpp \
|
||||
localqmlprofilerrunner.cpp \
|
||||
memoryusagemodel.cpp \
|
||||
pixmapcachemodel.cpp \
|
||||
qmlprofileranimationsmodel.cpp \
|
||||
qmlprofilerattachdialog.cpp \
|
||||
qmlprofilerbindingloopsrenderpass.cpp \
|
||||
@@ -31,10 +38,18 @@ SOURCES += \
|
||||
qmlprofilertool.cpp \
|
||||
qmlprofilertracefile.cpp \
|
||||
qmlprofilertraceview.cpp \
|
||||
qmlprofilerviewmanager.cpp
|
||||
qmlprofilerviewmanager.cpp \
|
||||
scenegraphtimelinemodel.cpp
|
||||
|
||||
HEADERS += \
|
||||
debugmessagesmodel.h \
|
||||
flamegraph.h \
|
||||
flamegraphmodel.h \
|
||||
flamegraphview.h \
|
||||
inputeventsmodel.h \
|
||||
localqmlprofilerrunner.h \
|
||||
memoryusagemodel.h \
|
||||
pixmapcachemodel.h \
|
||||
qmlprofiler_global.h \
|
||||
qmlprofileranimationsmodel.h \
|
||||
qmlprofilerattachdialog.h \
|
||||
@@ -63,14 +78,11 @@ HEADERS += \
|
||||
qmlprofilertool.h \
|
||||
qmlprofilertracefile.h \
|
||||
qmlprofilertraceview.h \
|
||||
qmlprofilerviewmanager.h
|
||||
qmlprofilerviewmanager.h \
|
||||
scenegraphtimelinemodel.h \
|
||||
|
||||
RESOURCES += \
|
||||
qml/qmlprofiler.qrc
|
||||
|
||||
DISTFILES += \
|
||||
qml/bindingloops.frag \
|
||||
qml/bindingloops.vert
|
||||
|
||||
FORMS += \
|
||||
qmlprofilerconfigwidget.ui
|
||||
|
||||
@@ -19,7 +19,14 @@ QtcPlugin {
|
||||
Group {
|
||||
name: "General"
|
||||
files: [
|
||||
"debugmessagesmodel.cpp", "debugmessagesmodel.h",
|
||||
"flamegraph.cpp", "flamegraph.h",
|
||||
"flamegraphmodel.cpp", "flamegraphmodel.h",
|
||||
"flamegraphview.cpp", "flamegraphview.h",
|
||||
"inputeventsmodel.cpp", "inputeventsmodel.h",
|
||||
"localqmlprofilerrunner.cpp", "localqmlprofilerrunner.h",
|
||||
"memoryusagemodel.cpp", "memoryusagemodel.h",
|
||||
"pixmapcachemodel.cpp", "pixmapcachemodel.h",
|
||||
"qmlprofiler_global.h",
|
||||
"qmlprofileranimationsmodel.h", "qmlprofileranimationsmodel.cpp",
|
||||
"qmlprofilerattachdialog.cpp", "qmlprofilerattachdialog.h",
|
||||
@@ -49,6 +56,7 @@ QtcPlugin {
|
||||
"qmlprofilertracefile.cpp", "qmlprofilertracefile.h",
|
||||
"qmlprofilertraceview.cpp", "qmlprofilertraceview.h",
|
||||
"qmlprofilerviewmanager.cpp", "qmlprofilerviewmanager.h",
|
||||
"scenegraphtimelinemodel.cpp", "scenegraphtimelinemodel.h",
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -56,8 +56,6 @@ bool QmlProfilerPlugin::initialize(const QStringList &arguments, QString *errorS
|
||||
|
||||
void QmlProfilerPlugin::extensionsInitialized()
|
||||
{
|
||||
factory = ExtensionSystem::PluginManager::getObject<QmlProfilerTimelineModelFactory>();
|
||||
|
||||
(void) new QmlProfilerTool(this);
|
||||
|
||||
addAutoReleasedObject(new QmlProfilerRunControlFactory());
|
||||
@@ -72,14 +70,6 @@ ExtensionSystem::IPlugin::ShutdownFlag QmlProfilerPlugin::aboutToShutdown()
|
||||
return SynchronousShutdown;
|
||||
}
|
||||
|
||||
QList<QmlProfilerTimelineModel *> QmlProfilerPlugin::getModels(QmlProfilerModelManager *manager) const
|
||||
{
|
||||
if (factory)
|
||||
return factory->create(manager);
|
||||
else
|
||||
return QList<QmlProfilerTimelineModel *>();
|
||||
}
|
||||
|
||||
QmlProfilerSettings *QmlProfilerPlugin::globalSettings()
|
||||
{
|
||||
return qmlProfilerGlobalSettings();
|
||||
|
||||
@@ -41,8 +41,6 @@ class QmlProfilerPlugin : public ExtensionSystem::IPlugin
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "QmlProfiler.json")
|
||||
|
||||
public:
|
||||
QmlProfilerPlugin() : factory(0) {}
|
||||
|
||||
bool initialize(const QStringList &arguments, QString *errorString);
|
||||
void extensionsInitialized();
|
||||
ShutdownFlag aboutToShutdown();
|
||||
@@ -50,11 +48,7 @@ public:
|
||||
static bool debugOutput;
|
||||
static QmlProfilerPlugin *instance;
|
||||
|
||||
QList<QmlProfilerTimelineModel *> getModels(QmlProfilerModelManager *manager) const;
|
||||
static QmlProfilerSettings *globalSettings();
|
||||
|
||||
private:
|
||||
QmlProfilerTimelineModelFactory *factory;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
@@ -32,6 +32,13 @@
|
||||
#include "qmlprofilerrangemodel.h"
|
||||
#include "qmlprofilerplugin.h"
|
||||
|
||||
#include "inputeventsmodel.h"
|
||||
#include "pixmapcachemodel.h"
|
||||
#include "debugmessagesmodel.h"
|
||||
#include "flamegraphview.h"
|
||||
#include "memoryusagemodel.h"
|
||||
#include "scenegraphtimelinemodel.h"
|
||||
|
||||
// Communication with the other views (limit events to range)
|
||||
#include "qmlprofilerviewmanager.h"
|
||||
|
||||
@@ -119,12 +126,11 @@ QmlProfilerTraceView::QmlProfilerTraceView(QWidget *parent, QmlProfilerViewManag
|
||||
d->m_modelProxy = new Timeline::TimelineModelAggregator(modelManager->notesModel(), this);
|
||||
d->m_modelManager = modelManager;
|
||||
|
||||
// external models pushed on top
|
||||
foreach (QmlProfilerTimelineModel *timelineModel,
|
||||
QmlProfilerPlugin::instance->getModels(modelManager)) {
|
||||
d->m_modelProxy->addModel(timelineModel);
|
||||
}
|
||||
|
||||
d->m_modelProxy->addModel(new PixmapCacheModel(modelManager, d->m_modelProxy));
|
||||
d->m_modelProxy->addModel(new SceneGraphTimelineModel(modelManager, d->m_modelProxy));
|
||||
d->m_modelProxy->addModel(new MemoryUsageModel(modelManager, d->m_modelProxy));
|
||||
d->m_modelProxy->addModel(new InputEventsModel(modelManager, d->m_modelProxy));
|
||||
d->m_modelProxy->addModel(new DebugMessagesModel(modelManager, d->m_modelProxy));
|
||||
d->m_modelProxy->addModel(new QmlProfilerAnimationsModel(modelManager, d->m_modelProxy));
|
||||
|
||||
for (int i = 0; i < MaximumRangeType; ++i)
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "qmlprofilerstatemanager.h"
|
||||
#include "qmlprofilermodelmanager.h"
|
||||
#include "qmlprofilerstatewidget.h"
|
||||
#include "flamegraphview.h"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/qtcassert.h>
|
||||
@@ -97,8 +98,7 @@ void QmlProfilerViewManager::createViews()
|
||||
Perspective::SplitVertical});
|
||||
|
||||
d->eventsViews << new QmlProfilerStatisticsView(0, d->profilerModelManager);
|
||||
if (d->eventsViewFactory)
|
||||
d->eventsViews.append(d->eventsViewFactory->create(0, d->profilerModelManager));
|
||||
d->eventsViews << new FlameGraphView(0, d->profilerModelManager);
|
||||
|
||||
foreach (QmlProfilerEventsView *view, d->eventsViews) {
|
||||
connect(view, &QmlProfilerEventsView::typeSelected,
|
||||
|
||||
309
src/plugins/qmlprofiler/scenegraphtimelinemodel.cpp
Normal file
309
src/plugins/qmlprofiler/scenegraphtimelinemodel.cpp
Normal file
@@ -0,0 +1,309 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "scenegraphtimelinemodel.h"
|
||||
#include "qmlprofilermodelmanager.h"
|
||||
#include "qmldebug/qmlprofilereventtypes.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
static const char *ThreadLabels[] = {
|
||||
QT_TRANSLATE_NOOP("MainView", "GUI Thread"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Render Thread"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Render Thread Details")
|
||||
};
|
||||
|
||||
static const char *StageLabels[] = {
|
||||
QT_TRANSLATE_NOOP("MainView", "Polish"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Wait"),
|
||||
QT_TRANSLATE_NOOP("MainView", "GUI Thread Sync"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Animations"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Render Thread Sync"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Render"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Swap"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Render Preprocess"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Render Update"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Render Bind"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Render Render"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Material Compile"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Glyph Render"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Glyph Upload"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Texture Bind"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Texture Convert"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Texture Swizzle"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Texture Upload"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Texture Mipmap"),
|
||||
QT_TRANSLATE_NOOP("MainView", "Texture Delete")
|
||||
};
|
||||
|
||||
enum SceneGraphCategoryType {
|
||||
SceneGraphGUIThread,
|
||||
SceneGraphRenderThread,
|
||||
SceneGraphRenderThreadDetails,
|
||||
|
||||
MaximumSceneGraphCategoryType
|
||||
};
|
||||
|
||||
Q_STATIC_ASSERT(sizeof(StageLabels) ==
|
||||
SceneGraphTimelineModel::MaximumSceneGraphStage * sizeof(const char *));
|
||||
|
||||
SceneGraphTimelineModel::SceneGraphTimelineModel(QmlProfilerModelManager *manager,
|
||||
QObject *parent) :
|
||||
QmlProfilerTimelineModel(manager, QmlDebug::SceneGraphFrame, QmlDebug::MaximumRangeType,
|
||||
QmlDebug::ProfileSceneGraph, parent)
|
||||
{
|
||||
}
|
||||
|
||||
int SceneGraphTimelineModel::expandedRow(int index) const
|
||||
{
|
||||
return selectionId(index) + 1;
|
||||
}
|
||||
|
||||
int SceneGraphTimelineModel::collapsedRow(int index) const
|
||||
{
|
||||
return m_data[index].rowNumberCollapsed;
|
||||
}
|
||||
|
||||
int SceneGraphTimelineModel::typeId(int index) const
|
||||
{
|
||||
return m_data[index].typeId;
|
||||
}
|
||||
|
||||
QColor SceneGraphTimelineModel::color(int index) const
|
||||
{
|
||||
return colorBySelectionId(index);
|
||||
}
|
||||
|
||||
QVariantList SceneGraphTimelineModel::labels() const
|
||||
{
|
||||
QVariantList result;
|
||||
|
||||
for (SceneGraphStage i = MinimumSceneGraphStage; i < MaximumSceneGraphStage;
|
||||
i = static_cast<SceneGraphStage>(i + 1)) {
|
||||
QVariantMap element;
|
||||
element.insert(QLatin1String("displayName"), tr(threadLabel(i)));
|
||||
element.insert(QLatin1String("description"), tr(StageLabels[i]));
|
||||
element.insert(QLatin1String("id"), i);
|
||||
result << element;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QVariantMap SceneGraphTimelineModel::details(int index) const
|
||||
{
|
||||
QVariantMap result;
|
||||
const SceneGraphStage stage = static_cast<SceneGraphStage>(selectionId(index));
|
||||
|
||||
result.insert(QLatin1String("displayName"), tr(threadLabel(stage)));
|
||||
result.insert(tr("Stage"), tr(StageLabels[stage]));
|
||||
result.insert(tr("Duration"), QmlProfilerDataModel::formatTime(duration(index)));
|
||||
|
||||
const int glyphCount = m_data[index].glyphCount;
|
||||
if (glyphCount >= 0)
|
||||
result.insert(tr("Glyphs"), QString::number(glyphCount));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void SceneGraphTimelineModel::loadData()
|
||||
{
|
||||
QmlProfilerDataModel *simpleModel = modelManager()->qmlModel();
|
||||
if (simpleModel->isEmpty())
|
||||
return;
|
||||
|
||||
// combine the data of several eventtypes into two rows
|
||||
const QVector<QmlProfilerDataModel::QmlEventTypeData> &types = simpleModel->getEventTypes();
|
||||
foreach (const QmlProfilerDataModel::QmlEventData &event, simpleModel->getEvents()) {
|
||||
const QmlProfilerDataModel::QmlEventTypeData &type = types[event.typeIndex()];
|
||||
if (!accepted(type))
|
||||
continue;
|
||||
|
||||
switch ((QmlDebug::SceneGraphFrameType)type.detailType) {
|
||||
case QmlDebug::SceneGraphRendererFrame: {
|
||||
// Breakdown of render times. We repeat "render" here as "net" render time. It would
|
||||
// look incomplete if that was left out as the printf profiler lists it, too, and people
|
||||
// are apparently comparing that. Unfortunately it is somewhat redundant as the other
|
||||
// parts of the breakdown are usually very short.
|
||||
qint64 startTime = event.startTime() - event.numericData(0) - event.numericData(1) -
|
||||
event.numericData(2) - event.numericData(3);
|
||||
startTime += insert(startTime, event.numericData(0), event.typeIndex(), RenderPreprocess);
|
||||
startTime += insert(startTime, event.numericData(1), event.typeIndex(), RenderUpdate);
|
||||
startTime += insert(startTime, event.numericData(2), event.typeIndex(), RenderBind);
|
||||
insert(startTime, event.numericData(3), event.typeIndex(), RenderRender);
|
||||
break;
|
||||
}
|
||||
case QmlDebug::SceneGraphAdaptationLayerFrame: {
|
||||
qint64 startTime = event.startTime() - event.numericData(1) - event.numericData(2);
|
||||
startTime += insert(startTime, event.numericData(1), event.typeIndex(), GlyphRender,
|
||||
event.numericData(0));
|
||||
insert(startTime, event.numericData(2), event.typeIndex(), GlyphStore, event.numericData(0));
|
||||
break;
|
||||
}
|
||||
case QmlDebug::SceneGraphContextFrame: {
|
||||
insert(event.startTime() - event.numericData(0), event.numericData(0), event.typeIndex(),
|
||||
Material);
|
||||
break;
|
||||
}
|
||||
case QmlDebug::SceneGraphRenderLoopFrame: {
|
||||
qint64 startTime = event.startTime() - event.numericData(0) - event.numericData(1) -
|
||||
event.numericData(2);
|
||||
startTime += insert(startTime, event.numericData(0), event.typeIndex(),
|
||||
RenderThreadSync);
|
||||
startTime += insert(startTime, event.numericData(1), event.typeIndex(),
|
||||
Render);
|
||||
insert(startTime, event.numericData(2), event.typeIndex(), Swap);
|
||||
break;
|
||||
}
|
||||
case QmlDebug::SceneGraphTexturePrepare: {
|
||||
qint64 startTime = event.startTime() - event.numericData(0) - event.numericData(1) -
|
||||
event.numericData(2) - event.numericData(3) - event.numericData(4);
|
||||
startTime += insert(startTime, event.numericData(0), event.typeIndex(), TextureBind);
|
||||
startTime += insert(startTime, event.numericData(1), event.typeIndex(), TextureConvert);
|
||||
startTime += insert(startTime, event.numericData(2), event.typeIndex(), TextureSwizzle);
|
||||
startTime += insert(startTime, event.numericData(3), event.typeIndex(), TextureUpload);
|
||||
insert(startTime, event.numericData(4), event.typeIndex(), TextureMipmap);
|
||||
break;
|
||||
}
|
||||
case QmlDebug::SceneGraphTextureDeletion: {
|
||||
insert(event.startTime() - event.numericData(0), event.numericData(0), event.typeIndex(),
|
||||
TextureDeletion);
|
||||
break;
|
||||
}
|
||||
case QmlDebug::SceneGraphPolishAndSync: {
|
||||
qint64 startTime = event.startTime() - event.numericData(0) - event.numericData(1) -
|
||||
event.numericData(2) - event.numericData(3);
|
||||
|
||||
startTime += insert(startTime, event.numericData(0), event.typeIndex(), Polish);
|
||||
startTime += insert(startTime, event.numericData(1), event.typeIndex(), Wait);
|
||||
startTime += insert(startTime, event.numericData(2), event.typeIndex(), GUIThreadSync);
|
||||
insert(startTime, event.numericData(3), event.typeIndex(), Animations);
|
||||
break;
|
||||
}
|
||||
case QmlDebug::SceneGraphWindowsAnimations: {
|
||||
// GUI thread, separate animations stage
|
||||
insert(event.startTime() - event.numericData(0), event.numericData(0), event.typeIndex(),
|
||||
Animations);
|
||||
break;
|
||||
}
|
||||
case QmlDebug::SceneGraphPolishFrame: {
|
||||
// GUI thread, separate polish stage
|
||||
insert(event.startTime() - event.numericData(0), event.numericData(0), event.typeIndex(),
|
||||
Polish);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
|
||||
updateProgress(count(), simpleModel->getEvents().count());
|
||||
}
|
||||
|
||||
computeNesting();
|
||||
flattenLoads();
|
||||
updateProgress(1, 1);
|
||||
}
|
||||
|
||||
void SceneGraphTimelineModel::flattenLoads()
|
||||
{
|
||||
int collapsedRowCount = 0;
|
||||
|
||||
// computes "compressed row"
|
||||
QVector <qint64> eventEndTimes;
|
||||
|
||||
for (int i = 0; i < count(); i++) {
|
||||
SceneGraphEvent &event = m_data[i];
|
||||
int stage = selectionId(i);
|
||||
// Don't try to put render thread events in GUI row and vice versa.
|
||||
// Rows below those are free for all.
|
||||
if (stage < MaximumGUIThreadStage)
|
||||
event.rowNumberCollapsed = SceneGraphGUIThread;
|
||||
else if (stage < MaximumRenderThreadStage)
|
||||
event.rowNumberCollapsed = SceneGraphRenderThread;
|
||||
else
|
||||
event.rowNumberCollapsed = SceneGraphRenderThreadDetails;
|
||||
|
||||
while (eventEndTimes.count() > event.rowNumberCollapsed &&
|
||||
eventEndTimes[event.rowNumberCollapsed] > startTime(i))
|
||||
++event.rowNumberCollapsed;
|
||||
|
||||
while (eventEndTimes.count() <= event.rowNumberCollapsed)
|
||||
eventEndTimes << 0; // increase stack length, proper value added below
|
||||
eventEndTimes[event.rowNumberCollapsed] = endTime(i);
|
||||
|
||||
// readjust to account for category empty row
|
||||
event.rowNumberCollapsed++;
|
||||
if (event.rowNumberCollapsed > collapsedRowCount)
|
||||
collapsedRowCount = event.rowNumberCollapsed;
|
||||
}
|
||||
|
||||
// Starting from 0, count is maxIndex+1
|
||||
setCollapsedRowCount(collapsedRowCount + 1);
|
||||
setExpandedRowCount(MaximumSceneGraphStage + 1);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Inserts an event characterized by \a start time, \a duration, \a typeIndex, \a stage and possibly
|
||||
* \a glyphCount (if it's a \c GlyphRender or \c GlyphStore event) into the scene graph model if its
|
||||
* \a duration is greater than 0. Returns \a duration in that case; otherwise returns 0.
|
||||
*/
|
||||
qint64 SceneGraphTimelineModel::insert(qint64 start, qint64 duration, int typeIndex,
|
||||
SceneGraphStage stage, int glyphCount)
|
||||
{
|
||||
if (duration <= 0)
|
||||
return 0;
|
||||
|
||||
m_data.insert(QmlProfilerTimelineModel::insert(start, duration, stage),
|
||||
SceneGraphEvent(typeIndex, glyphCount));
|
||||
return duration;
|
||||
}
|
||||
|
||||
const char *SceneGraphTimelineModel::threadLabel(SceneGraphStage stage)
|
||||
{
|
||||
if (stage < MaximumGUIThreadStage)
|
||||
return ThreadLabels[SceneGraphGUIThread];
|
||||
else if (stage < MaximumRenderThreadStage)
|
||||
return ThreadLabels[SceneGraphRenderThread];
|
||||
else
|
||||
return ThreadLabels[SceneGraphRenderThreadDetails];
|
||||
|
||||
}
|
||||
|
||||
void SceneGraphTimelineModel::clear()
|
||||
{
|
||||
m_data.clear();
|
||||
QmlProfilerTimelineModel::clear();
|
||||
}
|
||||
|
||||
SceneGraphTimelineModel::SceneGraphEvent::SceneGraphEvent(int typeId, int glyphCount) :
|
||||
typeId(typeId), rowNumberCollapsed(-1), glyphCount(glyphCount)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlProfiler
|
||||
111
src/plugins/qmlprofiler/scenegraphtimelinemodel.h
Normal file
111
src/plugins/qmlprofiler/scenegraphtimelinemodel.h
Normal file
@@ -0,0 +1,111 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "qmlprofilertimelinemodel.h"
|
||||
#include "qmlprofilermodelmanager.h"
|
||||
#include "qmlprofilerdatamodel.h"
|
||||
|
||||
#include <QStringList>
|
||||
#include <QColor>
|
||||
|
||||
namespace QmlProfiler {
|
||||
namespace Internal {
|
||||
|
||||
class SceneGraphTimelineModel : public QmlProfilerTimelineModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum SceneGraphStage {
|
||||
MinimumSceneGraphStage = 0,
|
||||
Polish = MinimumSceneGraphStage,
|
||||
Wait,
|
||||
GUIThreadSync,
|
||||
Animations,
|
||||
MaximumGUIThreadStage,
|
||||
|
||||
RenderThreadSync = MaximumGUIThreadStage,
|
||||
Render,
|
||||
Swap,
|
||||
MaximumRenderThreadStage,
|
||||
|
||||
RenderPreprocess = MaximumRenderThreadStage,
|
||||
RenderUpdate,
|
||||
RenderBind,
|
||||
RenderRender,
|
||||
MaximumRenderStage,
|
||||
|
||||
Material = MaximumRenderStage,
|
||||
MaximumMaterialStage,
|
||||
|
||||
GlyphRender = MaximumMaterialStage,
|
||||
GlyphStore,
|
||||
MaximumGlyphStage,
|
||||
|
||||
TextureBind = MaximumGlyphStage,
|
||||
TextureConvert,
|
||||
TextureSwizzle,
|
||||
TextureUpload,
|
||||
TextureMipmap,
|
||||
TextureDeletion,
|
||||
MaximumTextureStage,
|
||||
|
||||
MaximumSceneGraphStage = MaximumTextureStage
|
||||
};
|
||||
|
||||
struct SceneGraphEvent {
|
||||
SceneGraphEvent(int typeId = -1, int glyphCount = -1);
|
||||
int typeId;
|
||||
int rowNumberCollapsed;
|
||||
int glyphCount; // only used for one event type
|
||||
};
|
||||
|
||||
SceneGraphTimelineModel(QmlProfilerModelManager *manager, QObject *parent = 0);
|
||||
|
||||
int expandedRow(int index) const;
|
||||
int collapsedRow(int index) const;
|
||||
int typeId(int index) const;
|
||||
QColor color(int index) const;
|
||||
|
||||
QVariantList labels() const;
|
||||
|
||||
QVariantMap details(int index) const;
|
||||
|
||||
protected:
|
||||
void loadData();
|
||||
void clear();
|
||||
|
||||
private:
|
||||
void flattenLoads();
|
||||
qint64 insert(qint64 start, qint64 duration, int typeIndex, SceneGraphStage stage,
|
||||
int glyphCount = -1);
|
||||
static const char *threadLabel(SceneGraphStage stage);
|
||||
|
||||
QVector<SceneGraphEvent> m_data;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace QmlProfiler
|
||||
Reference in New Issue
Block a user