Files
qt-creator/src/plugins/qmlprofiler/qmlprofilereventsmodelproxy.cpp
Ulf Hermann 16bfbceeba QmlProfiler: Allow filtering either JS or QML from the events view
As a lot of events show up as QML bindings and signals and as
JavaScript functions the events view can be hard to navigate. This
change allows the user to either filter out JavaScript events and make
it look like the old QML events view or filter out QML events and make
it look like the old V8 JavaScript view.

Change-Id: I9e0c1184da21263ae174f322b8fcd8ee5ca13f6d
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@digia.com>
Reviewed-by: Kai Koehne <kai.koehne@digia.com>
2014-03-10 12:41:43 +01:00

499 lines
17 KiB
C++

/****************************************************************************
**
** Copyright (C) 2014 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 "qmlprofilerdatamodel.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;
QList<QmlDebug::QmlEventType> acceptedTypes;
QSet<QString> eventsInBindingLoop;
};
QmlProfilerEventsModelProxy::QmlProfilerEventsModelProxy(QmlProfilerModelManager *modelManager, QObject *parent)
: QObject(parent), d(new QmlProfilerEventsModelProxyPrivate(this))
{
d->modelManager = modelManager;
connect(modelManager->qmlModel(), SIGNAL(changed()), this, SLOT(dataChanged()));
d->modelId = modelManager->registerModelProxy();
// We're iterating twice in loadData.
modelManager->setProxyCountWeight(d->modelId, 2);
d->acceptedTypes << QmlDebug::Compiling << QmlDebug::Creating << QmlDebug::Binding << QmlDebug::HandlingSignal << QmlDebug::Javascript;
}
QmlProfilerEventsModelProxy::~QmlProfilerEventsModelProxy()
{
delete d;
}
void QmlProfilerEventsModelProxy::setEventTypeAccepted(QmlDebug::QmlEventType type, bool accepted)
{
if (accepted && !d->acceptedTypes.contains(type))
d->acceptedTypes << type;
else if (!accepted && d->acceptedTypes.contains(type))
d->acceptedTypes.removeOne(type);
}
bool QmlProfilerEventsModelProxy::eventTypeAccepted(QmlDebug::QmlEventType type) const
{
return d->acceptedTypes.contains(type);
}
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();
else 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<QmlProfilerDataModel::QmlEventData> eventList
= d->modelManager->qmlModel()->getEvents();
// used by binding loop detection
typedef QPair<QString, const QmlProfilerDataModel::QmlEventData*> CallStackEntry;
QStack<CallStackEntry> callStack;
callStack.push(CallStackEntry(QString(), 0)); // artificial root
for (int i = 0; i < eventList.size(); ++i) {
const QmlProfilerDataModel::QmlEventData *event = &eventList[i];
if (!d->acceptedTypes.contains((QmlDebug::QmlEventType)event->eventType))
continue;
if (checkRanges) {
if ((event->startTime + event->duration < rangeStart)
|| (event->startTime > rangeEnd))
continue;
}
// put event in hash
QString hash = QmlProfilerDataModel::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 QmlProfilerDataModel::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
int i = d->data.size();
int total = i * 2;
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;
d->modelManager->modelProxyCountUpdated(d->modelId, i++, total);
}
// 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;
QTC_CHECK(eventsModel);
m_eventsModel = eventsModel;
// Load the child models whenever the parent model is done to get the filtering for JS/QML
// right.
connect(m_eventsModel, SIGNAL(dataAvailable()), this, SLOT(dataChanged()));
}
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();
QmlProfilerDataModel *simpleModel = m_modelManager->qmlModel();
if (simpleModel->isEmpty())
return;
QHash<QString, QmlProfilerDataModel::QmlEventData> cachedEvents;
QString rootEventName = tr("<program>");
QmlProfilerDataModel::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<QmlProfilerDataModel::QmlEventData> eventList = simpleModel->getEvents();
foreach (const QmlProfilerDataModel::QmlEventData &event, eventList) {
// whitelist
if (!m_eventsModel->eventTypeAccepted((QmlDebug::QmlEventType)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 = QmlProfilerDataModel::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];
QmlProfilerDataModel::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();
QmlProfilerDataModel *simpleModel = m_modelManager->qmlModel();
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<QmlProfilerDataModel::QmlEventData> eventList = simpleModel->getEvents();
foreach (const QmlProfilerDataModel::QmlEventData &event, eventList) {
// whitelist
if (!m_eventsModel->eventTypeAccepted((QmlDebug::QmlEventType)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 = QmlProfilerDataModel::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;
}
}
}
}