Files
qt-creator/src/plugins/qmlprofiler/qmlprofilereventsmodelproxy.cpp
Ulf Hermann 5c7509dd09 QmlProfiler: Add another data state "ClearingData"
This allows us to steer the timeline models in a more fine grained
way and avoid duplicate processing of the same data. We don't need
to rebuild everything on stateChanged of the model manager then.

Task-number: QTCREATORBUG-10950
Change-Id: I2aa9a96a33c602f062fc3b644ac06280ba7cd45a
Reviewed-by: Kai Koehne <kai.koehne@digia.com>
2013-12-11 17:28:30 +01:00

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::ClearingData)
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;
}
}
}
}