forked from qt-creator/qt-creator
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>
499 lines
17 KiB
C++
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;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|