forked from qt-creator/qt-creator
480 lines
16 KiB
C++
480 lines
16 KiB
C++
|
|
/****************************************************************************
|
||
|
|
**
|
||
|
|
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||
|
|
** Contact: http://www.qt-project.org/legal
|
||
|
|
**
|
||
|
|
** 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 Digia. For licensing terms and
|
||
|
|
** conditions see http://qt.digia.com/licensing. For further information
|
||
|
|
** use the contact form at http://qt.digia.com/contact-us.
|
||
|
|
**
|
||
|
|
** GNU Lesser General Public License Usage
|
||
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||
|
|
** General Public License version 2.1 as published by the Free Software
|
||
|
|
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||
|
|
** packaging of this file. Please review the following information to
|
||
|
|
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||
|
|
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||
|
|
**
|
||
|
|
** In addition, as a special exception, Digia gives you certain additional
|
||
|
|
** rights. These rights are described in the Digia Qt LGPL Exception
|
||
|
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||
|
|
**
|
||
|
|
****************************************************************************/
|
||
|
|
|
||
|
|
#include "qmlprofilereventsmodelproxy.h"
|
||
|
|
#include "qmlprofilermodelmanager.h"
|
||
|
|
#include "qmlprofilersimplemodel.h"
|
||
|
|
|
||
|
|
#include <utils/qtcassert.h>
|
||
|
|
|
||
|
|
#include <QVector>
|
||
|
|
#include <QHash>
|
||
|
|
#include <QUrl>
|
||
|
|
#include <QString>
|
||
|
|
#include <QStack>
|
||
|
|
#include <QElapsedTimer>
|
||
|
|
|
||
|
|
#include <QDebug>
|
||
|
|
|
||
|
|
namespace QmlProfiler {
|
||
|
|
namespace Internal {
|
||
|
|
|
||
|
|
class QmlProfilerEventsModelProxy::QmlProfilerEventsModelProxyPrivate
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
QmlProfilerEventsModelProxyPrivate(QmlProfilerEventsModelProxy *qq) : q(qq) {}
|
||
|
|
~QmlProfilerEventsModelProxyPrivate() {}
|
||
|
|
|
||
|
|
QHash<QString, QmlProfilerEventsModelProxy::QmlEventStats> data;
|
||
|
|
|
||
|
|
QmlProfilerModelManager *modelManager;
|
||
|
|
QmlProfilerEventsModelProxy *q;
|
||
|
|
|
||
|
|
int modelId;
|
||
|
|
|
||
|
|
QVector<int> acceptedTypes;
|
||
|
|
QSet<QString> eventsInBindingLoop;
|
||
|
|
};
|
||
|
|
|
||
|
|
QmlProfilerEventsModelProxy::QmlProfilerEventsModelProxy(QmlProfilerModelManager *modelManager, QObject *parent)
|
||
|
|
: QObject(parent), d(new QmlProfilerEventsModelProxyPrivate(this))
|
||
|
|
{
|
||
|
|
d->modelManager = modelManager;
|
||
|
|
connect(modelManager->simpleModel(), SIGNAL(changed()), this, SLOT(dataChanged()));
|
||
|
|
d->modelId = modelManager->registerModelProxy();
|
||
|
|
|
||
|
|
d->acceptedTypes << QmlDebug::Compiling << QmlDebug::Creating << QmlDebug::Binding << QmlDebug::HandlingSignal;
|
||
|
|
}
|
||
|
|
|
||
|
|
QmlProfilerEventsModelProxy::~QmlProfilerEventsModelProxy()
|
||
|
|
{
|
||
|
|
delete d;
|
||
|
|
}
|
||
|
|
|
||
|
|
const QList<QmlProfilerEventsModelProxy::QmlEventStats> QmlProfilerEventsModelProxy::getData() const
|
||
|
|
{
|
||
|
|
return d->data.values();
|
||
|
|
}
|
||
|
|
|
||
|
|
void QmlProfilerEventsModelProxy::clear()
|
||
|
|
{
|
||
|
|
d->modelManager->modelProxyCountUpdated(d->modelId, 0, 1);
|
||
|
|
d->data.clear();
|
||
|
|
d->eventsInBindingLoop.clear();
|
||
|
|
}
|
||
|
|
|
||
|
|
void QmlProfilerEventsModelProxy::limitToRange(qint64 rangeStart, qint64 rangeEnd)
|
||
|
|
{
|
||
|
|
loadData(rangeStart, rangeEnd);
|
||
|
|
}
|
||
|
|
|
||
|
|
void QmlProfilerEventsModelProxy::dataChanged()
|
||
|
|
{
|
||
|
|
if (d->modelManager->state() == QmlProfilerDataState::ProcessingData)
|
||
|
|
loadData();
|
||
|
|
|
||
|
|
if (d->modelManager->state() == QmlProfilerDataState::Empty)
|
||
|
|
clear();
|
||
|
|
}
|
||
|
|
|
||
|
|
QSet<QString> QmlProfilerEventsModelProxy::eventsInBindingLoop() const
|
||
|
|
{
|
||
|
|
return d->eventsInBindingLoop;
|
||
|
|
}
|
||
|
|
|
||
|
|
void QmlProfilerEventsModelProxy::loadData(qint64 rangeStart, qint64 rangeEnd)
|
||
|
|
{
|
||
|
|
clear();
|
||
|
|
|
||
|
|
qint64 qmlTime = 0;
|
||
|
|
qint64 lastEndTime = 0;
|
||
|
|
QHash <QString, QVector<qint64> > durations;
|
||
|
|
|
||
|
|
const bool checkRanges = (rangeStart != -1) && (rangeEnd != -1);
|
||
|
|
|
||
|
|
const QVector<QmlProfilerSimpleModel::QmlEventData> eventList
|
||
|
|
= d->modelManager->simpleModel()->getEvents();
|
||
|
|
|
||
|
|
// used by binding loop detection
|
||
|
|
typedef QPair<QString, const QmlProfilerSimpleModel::QmlEventData*> CallStackEntry;
|
||
|
|
QStack<CallStackEntry> callStack;
|
||
|
|
callStack.push(CallStackEntry(QString(), 0)); // artificial root
|
||
|
|
|
||
|
|
for (int i = 0; i < eventList.size(); ++i) {
|
||
|
|
const QmlProfilerSimpleModel::QmlEventData *event = &eventList[i];
|
||
|
|
|
||
|
|
if (!d->acceptedTypes.contains(event->eventType))
|
||
|
|
continue;
|
||
|
|
|
||
|
|
if (checkRanges) {
|
||
|
|
if ((event->startTime + event->duration < rangeStart)
|
||
|
|
|| (event->startTime > rangeEnd))
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// put event in hash
|
||
|
|
QString hash = QmlProfilerSimpleModel::getHashString(*event);
|
||
|
|
if (!d->data.contains(hash)) {
|
||
|
|
QmlEventStats stats = {
|
||
|
|
event->displayName,
|
||
|
|
hash,
|
||
|
|
event->data.join(QLatin1String(" ")),
|
||
|
|
event->location,
|
||
|
|
event->eventType,
|
||
|
|
event->bindingType,
|
||
|
|
event->duration,
|
||
|
|
1, //calls
|
||
|
|
event->duration, //minTime
|
||
|
|
event->duration, // maxTime
|
||
|
|
0, //timePerCall
|
||
|
|
0, //percentOfTime
|
||
|
|
0, //medianTime
|
||
|
|
false //isBindingLoop
|
||
|
|
};
|
||
|
|
|
||
|
|
d->data.insert(hash, stats);
|
||
|
|
|
||
|
|
// for median computing
|
||
|
|
durations.insert(hash, QVector<qint64>());
|
||
|
|
durations[hash].append(event->duration);
|
||
|
|
} else {
|
||
|
|
// update stats
|
||
|
|
QmlEventStats *stats = &d->data[hash];
|
||
|
|
|
||
|
|
stats->duration += event->duration;
|
||
|
|
if (event->duration < stats->minTime)
|
||
|
|
stats->minTime = event->duration;
|
||
|
|
if (event->duration > stats->maxTime)
|
||
|
|
stats->maxTime = event->duration;
|
||
|
|
stats->calls++;
|
||
|
|
|
||
|
|
// for median computing
|
||
|
|
durations[hash].append(event->duration);
|
||
|
|
}
|
||
|
|
|
||
|
|
// qml time computation
|
||
|
|
if (event->startTime > lastEndTime) { // assume parent event if starts before last end
|
||
|
|
qmlTime += event->duration;
|
||
|
|
lastEndTime = event->startTime + event->duration;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
//
|
||
|
|
// binding loop detection
|
||
|
|
//
|
||
|
|
const QmlProfilerSimpleModel::QmlEventData *potentialParent = callStack.top().second;
|
||
|
|
while (potentialParent
|
||
|
|
&& !(potentialParent->startTime + potentialParent->duration > event->startTime)) {
|
||
|
|
callStack.pop();
|
||
|
|
potentialParent = callStack.top().second;
|
||
|
|
}
|
||
|
|
|
||
|
|
// check whether event is already in stack
|
||
|
|
bool inLoop = false;
|
||
|
|
for (int ii = 1; ii < callStack.size(); ++ii) {
|
||
|
|
if (callStack.at(ii).first == hash)
|
||
|
|
inLoop = true;
|
||
|
|
if (inLoop)
|
||
|
|
d->eventsInBindingLoop.insert(hash);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
CallStackEntry newEntry(hash, event);
|
||
|
|
callStack.push(newEntry);
|
||
|
|
|
||
|
|
d->modelManager->modelProxyCountUpdated(d->modelId, i, eventList.count()*2);
|
||
|
|
}
|
||
|
|
|
||
|
|
// post-process: calc mean time, median time, percentoftime
|
||
|
|
foreach (const QString &hash, d->data.keys()) {
|
||
|
|
QmlEventStats* stats = &d->data[hash];
|
||
|
|
if (stats->calls > 0)
|
||
|
|
stats->timePerCall = stats->duration / (double)stats->calls;
|
||
|
|
|
||
|
|
QVector<qint64> eventDurations = durations.value(hash);
|
||
|
|
if (!eventDurations.isEmpty()) {
|
||
|
|
qSort(eventDurations);
|
||
|
|
stats->medianTime = eventDurations.at(eventDurations.count()/2);
|
||
|
|
}
|
||
|
|
|
||
|
|
stats->percentOfTime = stats->duration * 100.0 / qmlTime;
|
||
|
|
}
|
||
|
|
|
||
|
|
// set binding loop flag
|
||
|
|
foreach (const QString &eventHash, d->eventsInBindingLoop)
|
||
|
|
d->data[eventHash].isBindingLoop = true;
|
||
|
|
|
||
|
|
QString rootEventName = tr("<program>");
|
||
|
|
QmlDebug::QmlEventLocation rootEventLocation(rootEventName, 1, 1);
|
||
|
|
|
||
|
|
// insert root event
|
||
|
|
QmlEventStats rootEvent = {
|
||
|
|
rootEventName, //event.displayName,
|
||
|
|
rootEventName, // hash
|
||
|
|
tr("Main Program"), //event.details,
|
||
|
|
rootEventLocation, // location
|
||
|
|
(int)QmlDebug::Binding, // event type
|
||
|
|
0, // binding type
|
||
|
|
qmlTime + 1,
|
||
|
|
1, //calls
|
||
|
|
qmlTime + 1, //minTime
|
||
|
|
qmlTime + 1, // maxTime
|
||
|
|
qmlTime + 1, //timePerCall
|
||
|
|
100.0, //percentOfTime
|
||
|
|
qmlTime + 1, //medianTime;
|
||
|
|
false
|
||
|
|
};
|
||
|
|
|
||
|
|
d->data.insert(rootEventName, rootEvent);
|
||
|
|
|
||
|
|
d->modelManager->modelProxyCountUpdated(d->modelId, 1, 1);
|
||
|
|
emit dataAvailable();
|
||
|
|
}
|
||
|
|
|
||
|
|
int QmlProfilerEventsModelProxy::count() const
|
||
|
|
{
|
||
|
|
return d->data.count();
|
||
|
|
}
|
||
|
|
|
||
|
|
//////////////////////////////////////////////////////////////////////////////////
|
||
|
|
QmlProfilerEventRelativesModelProxy::QmlProfilerEventRelativesModelProxy(QmlProfilerModelManager *modelManager,
|
||
|
|
QmlProfilerEventsModelProxy *eventsModel,
|
||
|
|
QObject *parent)
|
||
|
|
: QObject(parent)
|
||
|
|
{
|
||
|
|
QTC_CHECK(modelManager);
|
||
|
|
m_modelManager = modelManager;
|
||
|
|
connect(modelManager->simpleModel(), SIGNAL(changed()), this, SLOT(dataChanged()));
|
||
|
|
|
||
|
|
QTC_CHECK(eventsModel);
|
||
|
|
m_eventsModel = eventsModel;
|
||
|
|
|
||
|
|
m_acceptedTypes << QmlDebug::Compiling << QmlDebug::Creating << QmlDebug::Binding << QmlDebug::HandlingSignal;
|
||
|
|
}
|
||
|
|
|
||
|
|
QmlProfilerEventRelativesModelProxy::~QmlProfilerEventRelativesModelProxy()
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
const QmlProfilerEventRelativesModelProxy::QmlEventRelativesMap QmlProfilerEventRelativesModelProxy::getData(const QString &hash) const
|
||
|
|
{
|
||
|
|
if (m_data.contains(hash))
|
||
|
|
return m_data[hash];
|
||
|
|
return QmlEventRelativesMap();
|
||
|
|
}
|
||
|
|
|
||
|
|
int QmlProfilerEventRelativesModelProxy::count() const
|
||
|
|
{
|
||
|
|
return m_data.count();
|
||
|
|
}
|
||
|
|
|
||
|
|
void QmlProfilerEventRelativesModelProxy::clear()
|
||
|
|
{
|
||
|
|
m_data.clear();
|
||
|
|
}
|
||
|
|
|
||
|
|
void QmlProfilerEventRelativesModelProxy::dataChanged()
|
||
|
|
{
|
||
|
|
loadData();
|
||
|
|
|
||
|
|
emit dataAvailable();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
//////////////////////////////////////////////////////////////////////////////////
|
||
|
|
QmlProfilerEventParentsModelProxy::QmlProfilerEventParentsModelProxy(QmlProfilerModelManager *modelManager,
|
||
|
|
QmlProfilerEventsModelProxy *eventsModel,
|
||
|
|
QObject *parent)
|
||
|
|
: QmlProfilerEventRelativesModelProxy(modelManager, eventsModel, parent)
|
||
|
|
{}
|
||
|
|
|
||
|
|
QmlProfilerEventParentsModelProxy::~QmlProfilerEventParentsModelProxy()
|
||
|
|
{}
|
||
|
|
|
||
|
|
void QmlProfilerEventParentsModelProxy::loadData()
|
||
|
|
{
|
||
|
|
clear();
|
||
|
|
QmlProfilerSimpleModel *simpleModel = m_modelManager->simpleModel();
|
||
|
|
if (simpleModel->isEmpty())
|
||
|
|
return;
|
||
|
|
|
||
|
|
QHash<QString, QmlProfilerSimpleModel::QmlEventData> cachedEvents;
|
||
|
|
QString rootEventName = tr("<program>");
|
||
|
|
QmlProfilerSimpleModel::QmlEventData rootEvent = {
|
||
|
|
rootEventName,
|
||
|
|
QmlDebug::Binding,
|
||
|
|
0,
|
||
|
|
0,
|
||
|
|
0,
|
||
|
|
QStringList() << tr("Main Program"),
|
||
|
|
QmlDebug::QmlEventLocation(rootEventName, 0, 0),
|
||
|
|
0,0,0,0,0 // numericData fields
|
||
|
|
};
|
||
|
|
cachedEvents.insert(rootEventName, rootEvent);
|
||
|
|
|
||
|
|
// for level computation
|
||
|
|
QHash<int, qint64> endtimesPerLevel;
|
||
|
|
int level = QmlDebug::Constants::QML_MIN_LEVEL;
|
||
|
|
endtimesPerLevel[0] = 0;
|
||
|
|
|
||
|
|
const QSet<QString> eventsInBindingLoop = m_eventsModel->eventsInBindingLoop();
|
||
|
|
|
||
|
|
// compute parent-child relationship and call count
|
||
|
|
QHash<int, QString> lastParent;
|
||
|
|
//for (int index = fromIndex; index <= toIndex; index++) {
|
||
|
|
const QVector<QmlProfilerSimpleModel::QmlEventData> eventList = simpleModel->getEvents();
|
||
|
|
foreach (const QmlProfilerSimpleModel::QmlEventData &event, eventList) {
|
||
|
|
// whitelist
|
||
|
|
if (!m_acceptedTypes.contains(event.eventType))
|
||
|
|
continue;
|
||
|
|
|
||
|
|
// level computation
|
||
|
|
if (endtimesPerLevel[level] > event.startTime) {
|
||
|
|
level++;
|
||
|
|
} else {
|
||
|
|
while (level > QmlDebug::Constants::QML_MIN_LEVEL && endtimesPerLevel[level-1] <= event.startTime)
|
||
|
|
level--;
|
||
|
|
}
|
||
|
|
endtimesPerLevel[level] = event.startTime + event.duration;
|
||
|
|
|
||
|
|
|
||
|
|
QString parentHash = rootEventName;
|
||
|
|
QString eventHash = QmlProfilerSimpleModel::getHashString(event);
|
||
|
|
|
||
|
|
// save in cache
|
||
|
|
if (!cachedEvents.contains(eventHash))
|
||
|
|
cachedEvents.insert(eventHash, event);
|
||
|
|
|
||
|
|
if (level > QmlDebug::Constants::QML_MIN_LEVEL && lastParent.contains(level-1))
|
||
|
|
parentHash = lastParent[level-1];
|
||
|
|
|
||
|
|
QmlProfilerSimpleModel::QmlEventData *parentEvent = &(cachedEvents[parentHash]);
|
||
|
|
|
||
|
|
// generate placeholder if needed
|
||
|
|
if (!m_data.contains(eventHash))
|
||
|
|
m_data.insert(eventHash, QmlEventRelativesMap());
|
||
|
|
|
||
|
|
if (m_data[eventHash].contains(parentHash)) {
|
||
|
|
QmlEventRelativesData *parent = &(m_data[eventHash][parentHash]);
|
||
|
|
parent->calls++;
|
||
|
|
parent->duration += event.duration;
|
||
|
|
} else {
|
||
|
|
m_data[eventHash].insert(parentHash, QmlEventRelativesData());
|
||
|
|
QmlEventRelativesData *parent = &(m_data[eventHash][parentHash]);
|
||
|
|
parent->displayName = parentEvent->displayName;
|
||
|
|
parent->eventType = parentEvent->eventType;
|
||
|
|
parent->duration = event.duration;
|
||
|
|
parent->calls = 1;
|
||
|
|
parent->details = parentEvent->data.join(QLatin1String(""));
|
||
|
|
parent->isBindingLoop = eventsInBindingLoop.contains(parentHash);
|
||
|
|
}
|
||
|
|
|
||
|
|
// now lastparent is a string with the hash
|
||
|
|
lastParent[level] = eventHash;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//////////////////////////////////////////////////////////////////////////////////
|
||
|
|
QmlProfilerEventChildrenModelProxy::QmlProfilerEventChildrenModelProxy(QmlProfilerModelManager *modelManager,
|
||
|
|
QmlProfilerEventsModelProxy *eventsModel,
|
||
|
|
QObject *parent)
|
||
|
|
: QmlProfilerEventRelativesModelProxy(modelManager, eventsModel, parent)
|
||
|
|
{}
|
||
|
|
|
||
|
|
QmlProfilerEventChildrenModelProxy::~QmlProfilerEventChildrenModelProxy()
|
||
|
|
{}
|
||
|
|
|
||
|
|
void QmlProfilerEventChildrenModelProxy::loadData()
|
||
|
|
{
|
||
|
|
clear();
|
||
|
|
QmlProfilerSimpleModel *simpleModel = m_modelManager->simpleModel();
|
||
|
|
if (simpleModel->isEmpty())
|
||
|
|
return;
|
||
|
|
|
||
|
|
QString rootEventName = tr("<program>");
|
||
|
|
|
||
|
|
// for level computation
|
||
|
|
QHash<int, qint64> endtimesPerLevel;
|
||
|
|
int level = QmlDebug::Constants::QML_MIN_LEVEL;
|
||
|
|
endtimesPerLevel[0] = 0;
|
||
|
|
|
||
|
|
const QSet<QString> eventsInBindingLoop = m_eventsModel->eventsInBindingLoop();
|
||
|
|
|
||
|
|
// compute parent-child relationship and call count
|
||
|
|
QHash<int, QString> lastParent;
|
||
|
|
const QVector<QmlProfilerSimpleModel::QmlEventData> eventList = simpleModel->getEvents();
|
||
|
|
foreach (const QmlProfilerSimpleModel::QmlEventData &event, eventList) {
|
||
|
|
// whitelist
|
||
|
|
if (!m_acceptedTypes.contains(event.eventType))
|
||
|
|
continue;
|
||
|
|
|
||
|
|
// level computation
|
||
|
|
if (endtimesPerLevel[level] > event.startTime) {
|
||
|
|
level++;
|
||
|
|
} else {
|
||
|
|
while (level > QmlDebug::Constants::QML_MIN_LEVEL && endtimesPerLevel[level-1] <= event.startTime)
|
||
|
|
level--;
|
||
|
|
}
|
||
|
|
endtimesPerLevel[level] = event.startTime + event.duration;
|
||
|
|
|
||
|
|
QString parentHash = rootEventName;
|
||
|
|
QString eventHash = QmlProfilerSimpleModel::getHashString(event);
|
||
|
|
|
||
|
|
if (level > QmlDebug::Constants::QML_MIN_LEVEL && lastParent.contains(level-1))
|
||
|
|
parentHash = lastParent[level-1];
|
||
|
|
|
||
|
|
// generate placeholder if needed
|
||
|
|
if (!m_data.contains(parentHash))
|
||
|
|
m_data.insert(parentHash, QmlEventRelativesMap());
|
||
|
|
|
||
|
|
if (m_data[parentHash].contains(eventHash)) {
|
||
|
|
QmlEventRelativesData *child = &(m_data[parentHash][eventHash]);
|
||
|
|
child->calls++;
|
||
|
|
child->duration += event.duration;
|
||
|
|
} else {
|
||
|
|
m_data[parentHash].insert(eventHash, QmlEventRelativesData());
|
||
|
|
QmlEventRelativesData *child = &(m_data[parentHash][eventHash]);
|
||
|
|
child->displayName = event.displayName;
|
||
|
|
child->eventType = event.eventType;
|
||
|
|
child->duration = event.duration;
|
||
|
|
child->calls = 1;
|
||
|
|
child->details = event.data.join(QLatin1String(""));
|
||
|
|
child->isBindingLoop = eventsInBindingLoop.contains(parentHash);
|
||
|
|
}
|
||
|
|
|
||
|
|
// now lastparent is a string with the hash
|
||
|
|
lastParent[level] = eventHash;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|