diff options
author | Christiaan Janssen <christiaan.janssen@nokia.com> | 2012-02-24 10:47:17 +0100 |
---|---|---|
committer | Christiaan Janssen <christiaan.janssen@nokia.com> | 2012-03-14 11:38:25 +0100 |
commit | b7304e2f2e533b767bcd1c02d8403e3d5fa63ddd (patch) | |
tree | 30bb48a69d7c6bd1c35bcdd359a757486f298368 /src/plugins/qmlprofiler/qmlprofilerdatamodel.cpp | |
parent | d207165f6aa89ee95bd41c41a49d68cfa0b46444 (diff) | |
download | qt-creator-b7304e2f2e533b767bcd1c02d8403e3d5fa63ddd.tar.gz |
QmlProfiler: Refactor
The code of the qmlprofiler client has become a bit too complex,
this patch reorganizes the modules in a more sensible way,
having the modules communicate with each other through a
state machine instead of the excess of signals and slots
from before.
Change-Id: I76f7313779888a1bd07a1cdb1acbf2e47aacf42a
Reviewed-by: Kai Koehne <kai.koehne@nokia.com>
Diffstat (limited to 'src/plugins/qmlprofiler/qmlprofilerdatamodel.cpp')
-rw-r--r-- | src/plugins/qmlprofiler/qmlprofilerdatamodel.cpp | 1652 |
1 files changed, 1652 insertions, 0 deletions
diff --git a/src/plugins/qmlprofiler/qmlprofilerdatamodel.cpp b/src/plugins/qmlprofiler/qmlprofilerdatamodel.cpp new file mode 100644 index 0000000000..750bbb0f0d --- /dev/null +++ b/src/plugins/qmlprofiler/qmlprofilerdatamodel.cpp @@ -0,0 +1,1652 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "qmlprofilerdatamodel.h" + +#include <QUrl> +#include <QHash> +#include <QtAlgorithms> +#include <QString> +#include <QStringList> + +#include <QFile> +#include <QXmlStreamReader> +#include <QXmlStreamWriter> + +#include <QTimer> +#include <utils/qtcassert.h> + +using namespace QmlJsDebugClient; + +namespace QmlProfiler { +namespace Internal { + +/////////////////////////////////////////////////////////// +QmlRangeEventData::QmlRangeEventData() +{ + eventType = MaximumQmlEventType; + eventId = -1; + duration = 0; + calls = 0; + minTime = 0; + maxTime = 0; + timePerCall = 0; + percentOfTime = 0; + medianTime = 0; + isBindingLoop = false; +} + +QmlRangeEventData::~QmlRangeEventData() +{ + qDeleteAll(parentHash.values()); + parentHash.clear(); + qDeleteAll(childrenHash.values()); + childrenHash.clear(); +} + +QmlRangeEventData &QmlRangeEventData::operator=(const QmlRangeEventData &ref) +{ + if (this == &ref) + return *this; + + displayName = ref.displayName; + location = ref.location; + eventHashStr = ref.eventHashStr; + details = ref.details; + eventType = ref.eventType; + duration = ref.duration; + calls = ref.calls; + minTime = ref.minTime; + maxTime = ref.maxTime; + timePerCall = ref.timePerCall; + percentOfTime = ref.percentOfTime; + medianTime = ref.medianTime; + eventId = ref.eventId; + isBindingLoop = ref.isBindingLoop; + + qDeleteAll(parentHash.values()); + parentHash.clear(); + foreach (const QString &key, ref.parentHash.keys()) { + parentHash.insert(key, new QmlRangeEventRelative(ref.parentHash.value(key))); + } + + qDeleteAll(childrenHash.values()); + childrenHash.clear(); + foreach (const QString &key, ref.childrenHash.keys()) { + childrenHash.insert(key, new QmlRangeEventRelative(ref.childrenHash.value(key))); + } + + return *this; +} + +/////////////////////////////////////////////////////////// + +// endtimedata +struct QmlRangeEventEndInstance { + qint64 endTime; + int startTimeIndex; + QmlRangeEventData *description; +}; + +// starttimedata +struct QmlRangeEventStartInstance { + qint64 startTime; + qint64 duration; + qint64 level; + int endTimeIndex; + qint64 nestingLevel; + qint64 nestingDepth; + QmlRangeEventData *statsInfo; + + // animation-related data + int frameRate; + int animationCount; + + int bindingLoopHead; +}; + +struct QmlRangeEventTypeCount { + QVector<int> eventIds; + int nestingCount; +}; + +// used by quicksort +bool compareEndTimes(const QmlRangeEventEndInstance &t1, const QmlRangeEventEndInstance &t2) +{ + return t1.endTime < t2.endTime; +} + +bool compareStartTimes(const QmlRangeEventStartInstance &t1, const QmlRangeEventStartInstance &t2) +{ + return t1.startTime < t2.startTime; +} + +bool compareStartIndexes(const QmlRangeEventEndInstance &t1, const QmlRangeEventEndInstance &t2) +{ + return t1.startTimeIndex < t2.startTimeIndex; +} + +////////////////////////////////////////////////////////////// +class QmlProfilerDataModel::QmlProfilerDataModelPrivate +{ +public: + QmlProfilerDataModelPrivate(QmlProfilerDataModel *qq) : q(qq) {} + + QmlProfilerDataModel *q; + + // convenience functions + void clearQmlRootEvent(); + void insertQmlRootEvent(); + void postProcess(); + void sortEndTimes(); + void findAnimationLimits(); + void sortStartTimes(); + void computeNestingLevels(); + void computeNestingDepth(); + void prepareForDisplay(); + void linkStartsToEnds(); + void linkEndsToStarts(); + bool checkBindingLoop(QmlRangeEventData *from, QmlRangeEventData *current, QList<QmlRangeEventData *>visited); + + + // stats + void clearStatistics(); + void redoTree(qint64 fromTime, qint64 toTime); + void computeMedianTime(qint64 fromTime, qint64 toTime); + void findBindingLoops(qint64 fromTime, qint64 toTime); + + QmlProfilerDataModel::State listState; + + // Stored data + QHash<QString, QmlRangeEventData *> rangeEventDictionary; + QVector<QmlRangeEventEndInstance> endInstanceList; + QVector<QmlRangeEventStartInstance> startInstanceList; + + QmlRangeEventData qmlRootEvent; + + QV8ProfilerDataModel *v8DataModel; + + QHash<int, QmlRangeEventTypeCount *> typeCounts; + + qint64 traceEndTime; + qint64 traceStartTime; + qint64 qmlMeasuredTime; + + QmlRangeEventStartInstance *lastFrameEvent; + qint64 maxAnimationCount; + qint64 minAnimationCount; + + // file to load + QString fileName; +}; + + +//////////////////////////////////////////////////////////////////////////////////// + + +QmlProfilerDataModel::QmlProfilerDataModel(QObject *parent) : + QObject(parent), d(new QmlProfilerDataModelPrivate(this)) +{ + setObjectName("QmlProfilerDataModel"); + + d->listState = Empty; + + d->traceEndTime = 0; + d->traceStartTime = -1; + d->qmlMeasuredTime = 0; + d->clearQmlRootEvent(); + d->lastFrameEvent = 0; + d->maxAnimationCount = 0; + d->minAnimationCount = 0; + d->v8DataModel = new QV8ProfilerDataModel(this, this); +} + +QmlProfilerDataModel::~QmlProfilerDataModel() +{ + clear(); + delete d; +} + +//////////////////////////////////////////////////////////////////////////////////// +QList<QmlRangeEventData *> QmlProfilerDataModel::getEventDescriptions() const +{ + return d->rangeEventDictionary.values(); +} + +QmlRangeEventData *QmlProfilerDataModel::eventDescription(int eventId) const +{ + foreach (QmlRangeEventData *event, d->rangeEventDictionary.values()) { + if (event->eventId == eventId) + return event; + } + return 0; +} + +QList<QV8EventData *> QmlProfilerDataModel::getV8Events() const +{ + return d->v8DataModel->getV8Events(); +} + +QV8EventData *QmlProfilerDataModel::v8EventDescription(int eventId) const +{ + return d->v8DataModel->v8EventDescription(eventId); +} + +//////////////////////////////////////////////////////////////////////////////////// + +void QmlProfilerDataModel::clear() +{ + qDeleteAll(d->rangeEventDictionary.values()); + d->rangeEventDictionary.clear(); + + d->endInstanceList.clear(); + d->startInstanceList.clear(); + + d->clearQmlRootEvent(); + + foreach (QmlRangeEventTypeCount *typeCount, d->typeCounts.values()) + delete typeCount; + d->typeCounts.clear(); + + d->traceEndTime = 0; + d->traceStartTime = -1; + d->qmlMeasuredTime = 0; + + d->lastFrameEvent = 0; + d->maxAnimationCount = 0; + d->minAnimationCount = 0; + + d->v8DataModel->clear(); + + emit countChanged(); + setState(Empty); +} + +void QmlProfilerDataModel::addRangedEvent(int type, qint64 startTime, qint64 length, + const QStringList &data, + const QmlJsDebugClient::QmlEventLocation &location) +{ + const QChar colon = QLatin1Char(':'); + QString displayName, eventHashStr, details; + QmlJsDebugClient::QmlEventLocation eventLocation = location; + + setState(AcquiringData); + + // generate details string + if (data.isEmpty()) + details = tr("Source code not available"); + else { + details = data.join(" ").replace('\n'," ").simplified(); + QRegExp rewrite("\\(function \\$(\\w+)\\(\\) \\{ (return |)(.+) \\}\\)"); + bool match = rewrite.exactMatch(details); + if (match) { + details = rewrite.cap(1) + ": " + rewrite.cap(3); + } + if (details.startsWith(QString("file://"))) + details = details.mid(details.lastIndexOf(QChar('/')) + 1); + } + + // backwards compatibility: "compiling" events don't have a proper location in older + // version of the protocol, but the filename is passed in the details string + if (type == QmlJsDebugClient::Compiling && eventLocation.filename.isEmpty()) { + eventLocation.filename = details; + eventLocation.line = 1; + eventLocation.column = 1; + } + + // generate hash + if (eventLocation.filename.isEmpty()) { + displayName = tr("<bytecode>"); + eventHashStr = getHashStringForQmlEvent(eventLocation, type); + } else { + const QString filePath = QUrl(eventLocation.filename).path(); + displayName = filePath.mid(filePath.lastIndexOf(QChar('/')) + 1) + colon + + QString::number(eventLocation.line); + eventHashStr = getHashStringForQmlEvent(eventLocation, type); + } + + QmlRangeEventData *newEvent; + if (d->rangeEventDictionary.contains(eventHashStr)) { + newEvent = d->rangeEventDictionary[eventHashStr]; + } else { + newEvent = new QmlRangeEventData; + newEvent->displayName = displayName; + newEvent->location = eventLocation; + newEvent->eventHashStr = eventHashStr; + newEvent->eventType = (QmlJsDebugClient::QmlEventType)type; + newEvent->details = details; + d->rangeEventDictionary.insert(eventHashStr, newEvent); + } + + QmlRangeEventEndInstance endTimeData; + endTimeData.endTime = startTime + length; + endTimeData.description = newEvent; + endTimeData.startTimeIndex = d->startInstanceList.count(); + + QmlRangeEventStartInstance startTimeData; + startTimeData.startTime = startTime; + startTimeData.duration = length; + startTimeData.statsInfo = newEvent; + startTimeData.endTimeIndex = d->endInstanceList.count(); + startTimeData.animationCount = -1; + startTimeData.frameRate = 1e9/length; + + d->endInstanceList << endTimeData; + d->startInstanceList << startTimeData; + + emit countChanged(); +} + +void QmlProfilerDataModel::addV8Event(int depth, const QString &function, + const QString &filename, int lineNumber, + double totalTime, double selfTime) +{ + d->v8DataModel->addV8Event(depth, function, filename, lineNumber, totalTime, selfTime); +} + +void QmlProfilerDataModel::addFrameEvent(qint64 time, int framerate, int animationcount) +{ + QString displayName, eventHashStr, details; + + setState(AcquiringData); + + details = tr("Animation Timer Update"); + displayName = tr("<Animation Update>"); + eventHashStr = displayName; + + QmlRangeEventData *newEvent; + if (d->rangeEventDictionary.contains(eventHashStr)) { + newEvent = d->rangeEventDictionary[eventHashStr]; + } else { + newEvent = new QmlRangeEventData; + newEvent->displayName = displayName; + newEvent->eventHashStr = eventHashStr; + newEvent->eventType = QmlJsDebugClient::Painting; + newEvent->details = details; + d->rangeEventDictionary.insert(eventHashStr, newEvent); + } + + qint64 length = 1e9/framerate; + // avoid overlap + if (d->lastFrameEvent && + d->lastFrameEvent->startTime + d->lastFrameEvent->duration >= time) { + d->lastFrameEvent->duration = time - 1 - d->lastFrameEvent->startTime; + d->endInstanceList[d->lastFrameEvent->endTimeIndex].endTime = + d->lastFrameEvent->startTime + d->lastFrameEvent->duration; + } + + QmlRangeEventEndInstance endTimeData; + endTimeData.endTime = time + length; + endTimeData.description = newEvent; + endTimeData.startTimeIndex = d->startInstanceList.count(); + + QmlRangeEventStartInstance startTimeData; + startTimeData.startTime = time; + startTimeData.duration = length; + startTimeData.statsInfo = newEvent; + startTimeData.endTimeIndex = d->endInstanceList.count(); + startTimeData.animationCount = animationcount; + startTimeData.frameRate = framerate; + + d->endInstanceList << endTimeData; + d->startInstanceList << startTimeData; + + d->lastFrameEvent = &d->startInstanceList.last(); + + emit countChanged(); +} + +void QmlProfilerDataModel::setTraceEndTime(qint64 time) +{ + d->traceEndTime = time; +} + +void QmlProfilerDataModel::setTraceStartTime(qint64 time) +{ + d->traceStartTime = time; +} + +//////////////////////////////////////////////////////////////////////////////////// + +QString QmlProfilerDataModel::getHashStringForQmlEvent( + const QmlJsDebugClient::QmlEventLocation &location, int eventType) +{ + return QString("%1:%2:%3:%4").arg(location.filename, + QString::number(location.line), + QString::number(location.column), + QString::number(eventType)); +} + +QString QmlProfilerDataModel::getHashStringForV8Event(const QString &displayName, + const QString &function) +{ + return QString("%1:%2").arg(displayName, function); +} + +QString QmlProfilerDataModel::rootEventName() +{ + return tr("<program>"); +} + +QString QmlProfilerDataModel::rootEventDescription() +{ + return tr("Main Program"); +} + +QString QmlProfilerDataModel::qmlEventTypeAsString(QmlEventType typeEnum) +{ + switch (typeEnum) { + case Painting: + return QLatin1String(Constants::TYPE_PAINTING_STR); + break; + case Compiling: + return QLatin1String(Constants::TYPE_COMPILING_STR); + break; + case Creating: + return QLatin1String(Constants::TYPE_CREATING_STR); + break; + case Binding: + return QLatin1String(Constants::TYPE_BINDING_STR); + break; + case HandlingSignal: + return QLatin1String(Constants::TYPE_HANDLINGSIGNAL_STR); + break; + default: + return QString::number((int)typeEnum); + } +} + +QmlEventType QmlProfilerDataModel::qmlEventTypeAsEnum(const QString &typeString) +{ + if (typeString == QLatin1String(Constants::TYPE_PAINTING_STR)) { + return Painting; + } else if (typeString == QLatin1String(Constants::TYPE_COMPILING_STR)) { + return Compiling; + } else if (typeString == QLatin1String(Constants::TYPE_CREATING_STR)) { + return Creating; + } else if (typeString == QLatin1String(Constants::TYPE_BINDING_STR)) { + return Binding; + } else if (typeString == QLatin1String(Constants::TYPE_HANDLINGSIGNAL_STR)) { + return HandlingSignal; + } else { + bool isNumber = false; + int type = typeString.toUInt(&isNumber); + if (isNumber) { + return (QmlEventType)type; + } else { + return MaximumQmlEventType; + } + } +} + +//////////////////////////////////////////////////////////////////////////////////// + +int QmlProfilerDataModel::findFirstIndex(qint64 startTime) const +{ + int candidate = -1; + // in the "endtime" list, find the first event that ends after startTime + if (d->endInstanceList.isEmpty()) + return 0; // -1 + if (d->endInstanceList.count() == 1 || d->endInstanceList.first().endTime >= startTime) + candidate = 0; + else + if (d->endInstanceList.last().endTime <= startTime) + return 0; // -1 + + if (candidate == -1) + { + int fromIndex = 0; + int toIndex = d->endInstanceList.count()-1; + while (toIndex - fromIndex > 1) { + int midIndex = (fromIndex + toIndex)/2; + if (d->endInstanceList[midIndex].endTime < startTime) + fromIndex = midIndex; + else + toIndex = midIndex; + } + + candidate = toIndex; + } + + int ndx = d->endInstanceList[candidate].startTimeIndex; + + // and then go to the parent + while (ndx > 0 && d->startInstanceList[ndx].level > d->startInstanceList[ndx-1].level ) + ndx--; + + return ndx; +} + +int QmlProfilerDataModel::findFirstIndexNoParents(qint64 startTime) const +{ + int candidate = -1; + // in the "endtime" list, find the first event that ends after startTime + if (d->endInstanceList.isEmpty()) + return 0; // -1 + if (d->endInstanceList.count() == 1 || d->endInstanceList.first().endTime >= startTime) + candidate = 0; + else + if (d->endInstanceList.last().endTime <= startTime) + return 0; // -1 + + if (candidate == -1) { + int fromIndex = 0; + int toIndex = d->endInstanceList.count()-1; + while (toIndex - fromIndex > 1) { + int midIndex = (fromIndex + toIndex)/2; + if (d->endInstanceList[midIndex].endTime < startTime) + fromIndex = midIndex; + else + toIndex = midIndex; + } + + candidate = toIndex; + } + + int ndx = d->endInstanceList[candidate].startTimeIndex; + + return ndx; +} + +int QmlProfilerDataModel::findLastIndex(qint64 endTime) const +{ + // in the "starttime" list, find the last event that starts before endtime + if (d->startInstanceList.isEmpty()) + return 0; // -1 + if (d->startInstanceList.first().startTime >= endTime) + return 0; // -1 + if (d->startInstanceList.count() == 1) + return 0; + if (d->startInstanceList.last().startTime <= endTime) + return d->startInstanceList.count()-1; + + int fromIndex = 0; + int toIndex = d->startInstanceList.count()-1; + while (toIndex - fromIndex > 1) { + int midIndex = (fromIndex + toIndex)/2; + if (d->startInstanceList[midIndex].startTime < endTime) + fromIndex = midIndex; + else + toIndex = midIndex; + } + + return fromIndex; +} + +qint64 QmlProfilerDataModel::firstTimeMark() const +{ + if (d->startInstanceList.isEmpty()) + return 0; + else { + return d->startInstanceList[0].startTime; + } +} + +qint64 QmlProfilerDataModel::lastTimeMark() const +{ + if (d->endInstanceList.isEmpty()) + return 0; + else { + return d->endInstanceList.last().endTime; + } +} + +//////////////////////////////////////////////////////////////////////////////////// + +int QmlProfilerDataModel::count() const +{ + return d->startInstanceList.count(); +} + +bool QmlProfilerDataModel::isEmpty() const +{ + return d->startInstanceList.isEmpty() && d->v8DataModel->isEmpty(); +} + +qint64 QmlProfilerDataModel::getStartTime(int index) const +{ + return d->startInstanceList[index].startTime; +} + +qint64 QmlProfilerDataModel::getEndTime(int index) const +{ + return d->startInstanceList[index].startTime + d->startInstanceList[index].duration; +} + +qint64 QmlProfilerDataModel::getDuration(int index) const +{ + return d->startInstanceList[index].duration; +} + +int QmlProfilerDataModel::getType(int index) const +{ + return d->startInstanceList[index].statsInfo->eventType; +} + +int QmlProfilerDataModel::getNestingLevel(int index) const +{ + return d->startInstanceList[index].nestingLevel; +} + +int QmlProfilerDataModel::getNestingDepth(int index) const +{ + return d->startInstanceList[index].nestingDepth; +} + +QString QmlProfilerDataModel::getFilename(int index) const +{ + return d->startInstanceList[index].statsInfo->location.filename; +} + +int QmlProfilerDataModel::getLine(int index) const +{ + return d->startInstanceList[index].statsInfo->location.line; +} + +int QmlProfilerDataModel::getColumn(int index) const +{ + return d->startInstanceList[index].statsInfo->location.column; +} + +QString QmlProfilerDataModel::getDetails(int index) const +{ + // special: animations + if (d->startInstanceList[index].statsInfo->eventType == QmlJsDebugClient::Painting && + d->startInstanceList[index].animationCount >= 0) + return tr("%1 animations at %2 FPS").arg( + QString::number(d->startInstanceList[index].animationCount), + QString::number(d->startInstanceList[index].frameRate)); + return d->startInstanceList[index].statsInfo->details; +} + +int QmlProfilerDataModel::getEventId(int index) const +{ + return d->startInstanceList[index].statsInfo->eventId; +} + +int QmlProfilerDataModel::getBindingLoopDest(int index) const +{ + return d->startInstanceList[index].bindingLoopHead; +} + +int QmlProfilerDataModel::getFramerate(int index) const +{ + return d->startInstanceList[index].frameRate; +} + +int QmlProfilerDataModel::getAnimationCount(int index) const +{ + return d->startInstanceList[index].animationCount; +} + +int QmlProfilerDataModel::getMaximumAnimationCount() const +{ + return d->maxAnimationCount; +} + +int QmlProfilerDataModel::getMinimumAnimationCount() const +{ + return d->minAnimationCount; +} + +///////////////////////////////////////// + +int QmlProfilerDataModel::uniqueEventsOfType(int type) const +{ + if (!d->typeCounts.contains(type)) + return 0; + return d->typeCounts[type]->eventIds.count(); +} + +int QmlProfilerDataModel::maxNestingForType(int type) const +{ + if (!d->typeCounts.contains(type)) + return 0; + return d->typeCounts[type]->nestingCount; +} + +QString QmlProfilerDataModel::eventTextForType(int type, int index) const +{ + if (!d->typeCounts.contains(type)) + return QString(); + return d->rangeEventDictionary.values().at(d->typeCounts[type]->eventIds[index])->details; +} + +QString QmlProfilerDataModel::eventDisplayNameForType(int type, int index) const +{ + if (!d->typeCounts.contains(type)) + return QString(); + return d->rangeEventDictionary.values().at(d->typeCounts[type]->eventIds[index])->displayName; +} + +int QmlProfilerDataModel::eventIdForType(int type, int index) const +{ + if (!d->typeCounts.contains(type)) + return -1; + return d->typeCounts[type]->eventIds[index]; +} + +int QmlProfilerDataModel::eventPosInType(int index) const +{ + int eventType = d->startInstanceList[index].statsInfo->eventType; + return d->typeCounts[eventType]->eventIds.indexOf(d->startInstanceList[index].statsInfo->eventId); +} + +///////////////////////////////////////// + +qint64 QmlProfilerDataModel::traceStartTime() const +{ + return d->traceStartTime != -1? d->traceStartTime : firstTimeMark(); +} + +qint64 QmlProfilerDataModel::traceEndTime() const +{ + return d->traceEndTime ? d->traceEndTime : lastTimeMark(); +} + +qint64 QmlProfilerDataModel::traceDuration() const +{ + return traceEndTime() - traceStartTime(); +} + +qint64 QmlProfilerDataModel::qmlMeasuredTime() const +{ + return d->qmlMeasuredTime; +} +qint64 QmlProfilerDataModel::v8MeasuredTime() const +{ + return d->v8DataModel->v8MeasuredTime(); +} + +//////////////////////////////////////////////////////////////////////////////////// + +void QmlProfilerDataModel::complete() +{ + if (currentState() == AcquiringData) { + setState(ProcessingData); + d->v8DataModel->collectV8Statistics(); + d->postProcess(); + } else + if (currentState() == Empty) { + setState(Done); + } else { + emit error("Unexpected complete signal in data model"); + } +} + +void QmlProfilerDataModel::QmlProfilerDataModelPrivate::postProcess() +{ + if (q->count() != 0) { + sortStartTimes(); + sortEndTimes(); + findAnimationLimits(); + computeNestingLevels(); + computeNestingDepth(); + linkEndsToStarts(); + insertQmlRootEvent(); + q->reloadDetails(); + prepareForDisplay(); + q->compileStatistics(q->traceStartTime(), q->traceEndTime()); + + } + q->setState(Done); +} + + +void QmlProfilerDataModel::QmlProfilerDataModelPrivate::prepareForDisplay() +{ + // generate numeric ids + int ndx = 0; + foreach (QmlRangeEventData *binding, rangeEventDictionary.values()) { + binding->eventId = ndx++; + } + + // collect type counts + foreach (const QmlRangeEventStartInstance &eventStartData, startInstanceList) { + int typeNumber = eventStartData.statsInfo->eventType; + if (!typeCounts.contains(typeNumber)) { + typeCounts[typeNumber] = new QmlRangeEventTypeCount; + typeCounts[typeNumber]->nestingCount = 0; + } + if (eventStartData.nestingLevel > typeCounts[typeNumber]->nestingCount) { + typeCounts[typeNumber]->nestingCount = eventStartData.nestingLevel; + } + if (!typeCounts[typeNumber]->eventIds.contains(eventStartData.statsInfo->eventId)) + typeCounts[typeNumber]->eventIds << eventStartData.statsInfo->eventId; + } +} + +void QmlProfilerDataModel::QmlProfilerDataModelPrivate::sortStartTimes() +{ + if (startInstanceList.count() < 2) + return; + + // assuming startTimes is partially sorted + // identify blocks of events and sort them with quicksort + QVector<QmlRangeEventStartInstance>::iterator itFrom = startInstanceList.end() - 2; + QVector<QmlRangeEventStartInstance>::iterator itTo = startInstanceList.end() - 1; + + while (itFrom != startInstanceList.begin() && itTo != startInstanceList.begin()) { + // find block to sort + while (itFrom != startInstanceList.begin() + && itTo->startTime > itFrom->startTime) { + itTo--; + itFrom = itTo - 1; + } + + // if we're at the end of the list + if (itFrom == startInstanceList.begin()) + break; + + // find block length + while (itFrom != startInstanceList.begin() + && itTo->startTime <= itFrom->startTime) + itFrom--; + + if (itTo->startTime <= itFrom->startTime) + qSort(itFrom, itTo + 1, compareStartTimes); + else + qSort(itFrom + 1, itTo + 1, compareStartTimes); + + // move to next block + itTo = itFrom; + itFrom = itTo - 1; + } + + // link back the endTimes + linkEndsToStarts(); +} + +void QmlProfilerDataModel::QmlProfilerDataModelPrivate::sortEndTimes() +{ + // assuming endTimes is partially sorted + // identify blocks of events and sort them with quicksort + + if (endInstanceList.count() < 2) + return; + + QVector<QmlRangeEventEndInstance>::iterator itFrom = endInstanceList.begin(); + QVector<QmlRangeEventEndInstance>::iterator itTo = endInstanceList.begin() + 1; + + while (itTo != endInstanceList.end() && itFrom != endInstanceList.end()) { + // find block to sort + while (itTo != endInstanceList.end() + && startInstanceList[itTo->startTimeIndex].startTime > + startInstanceList[itFrom->startTimeIndex].startTime + + startInstanceList[itFrom->startTimeIndex].duration) { + itFrom++; + itTo = itFrom+1; + } + + // if we're at the end of the list + if (itTo == endInstanceList.end()) + break; + + // find block length + while (itTo != endInstanceList.end() + && startInstanceList[itTo->startTimeIndex].startTime <= + startInstanceList[itFrom->startTimeIndex].startTime + + startInstanceList[itFrom->startTimeIndex].duration) + itTo++; + + // sort block + qSort(itFrom, itTo, compareEndTimes); + + // move to next block + itFrom = itTo; + itTo = itFrom+1; + + } + + // link back the startTimes + linkStartsToEnds(); +} + +void QmlProfilerDataModel::QmlProfilerDataModelPrivate::linkStartsToEnds() +{ + for (int i = 0; i < endInstanceList.count(); i++) + startInstanceList[endInstanceList[i].startTimeIndex].endTimeIndex = i; +} + +void QmlProfilerDataModel::QmlProfilerDataModelPrivate::findAnimationLimits() +{ + maxAnimationCount = 0; + minAnimationCount = 0; + lastFrameEvent = 0; + + for (int i = 0; i < startInstanceList.count(); i++) { + if (startInstanceList[i].statsInfo->eventType == QmlJsDebugClient::Painting && + startInstanceList[i].animationCount >= 0) { + int animationcount = startInstanceList[i].animationCount; + if (lastFrameEvent) { + if (animationcount > maxAnimationCount) + maxAnimationCount = animationcount; + if (animationcount < minAnimationCount) + minAnimationCount = animationcount; + } else { + maxAnimationCount = animationcount; + minAnimationCount = animationcount; + } + lastFrameEvent = &startInstanceList[i]; + } + } +} + +void QmlProfilerDataModel::QmlProfilerDataModelPrivate::computeNestingLevels() +{ + // compute levels + QHash<int, qint64> endtimesPerLevel; + QList<int> nestingLevels; + QList< QHash<int, qint64> > endtimesPerNestingLevel; + int level = Constants::QML_MIN_LEVEL; + endtimesPerLevel[Constants::QML_MIN_LEVEL] = 0; + + for (int i = 0; i < QmlJsDebugClient::MaximumQmlEventType; i++) { + nestingLevels << Constants::QML_MIN_LEVEL; + QHash<int, qint64> dummyHash; + dummyHash[Constants::QML_MIN_LEVEL] = 0; + endtimesPerNestingLevel << dummyHash; + } + + for (int i=0; i<startInstanceList.count(); i++) { + qint64 st = startInstanceList[i].startTime; + int type = startInstanceList[i].statsInfo->eventType; + + if (type == QmlJsDebugClient::Painting) { + // animation/paint events have level 0 by definition (same as "mainprogram"), + // but are not considered parents of other events for statistical purposes + startInstanceList[i].level = Constants::QML_MIN_LEVEL - 1; + startInstanceList[i].nestingLevel = Constants::QML_MIN_LEVEL; + continue; + } + + // general level + if (endtimesPerLevel[level] > st) { + level++; + } else { + while (level > Constants::QML_MIN_LEVEL && endtimesPerLevel[level-1] <= st) + level--; + } + endtimesPerLevel[level] = st + startInstanceList[i].duration; + + // per type + if (endtimesPerNestingLevel[type][nestingLevels[type]] > st) { + nestingLevels[type]++; + } else { + while (nestingLevels[type] > Constants::QML_MIN_LEVEL && + endtimesPerNestingLevel[type][nestingLevels[type]-1] <= st) + nestingLevels[type]--; + } + endtimesPerNestingLevel[type][nestingLevels[type]] = + st + startInstanceList[i].duration; + + startInstanceList[i].level = level; + startInstanceList[i].nestingLevel = nestingLevels[type]; + + if (level == Constants::QML_MIN_LEVEL) { + qmlMeasuredTime += startInstanceList[i].duration; + } + } +} + +void QmlProfilerDataModel::QmlProfilerDataModelPrivate::computeNestingDepth() +{ + QHash<int, int> nestingDepth; + for (int i = 0; i < endInstanceList.count(); i++) { + int type = endInstanceList[i].description->eventType; + int nestingInType = startInstanceList[endInstanceList[i].startTimeIndex].nestingLevel; + if (!nestingDepth.contains(type)) + nestingDepth[type] = nestingInType; + else { + int nd = nestingDepth[type]; + nestingDepth[type] = nd > nestingInType ? nd : nestingInType; + } + + startInstanceList[endInstanceList[i].startTimeIndex].nestingDepth = nestingDepth[type]; + if (nestingInType == Constants::QML_MIN_LEVEL) + nestingDepth[type] = Constants::QML_MIN_LEVEL; + } +} + +void QmlProfilerDataModel::QmlProfilerDataModelPrivate::linkEndsToStarts() +{ + for (int i = 0; i < startInstanceList.count(); i++) + endInstanceList[startInstanceList[i].endTimeIndex].startTimeIndex = i; +} + +//////////////////////////////////////////////////////////////////////////////////// + +void QmlProfilerDataModel::compileStatistics(qint64 startTime, qint64 endTime) +{ + d->clearStatistics(); + d->redoTree(startTime, endTime); + d->computeMedianTime(startTime, endTime); + d->findBindingLoops(startTime, endTime); +} + +void QmlProfilerDataModel::QmlProfilerDataModelPrivate::clearStatistics() +{ + // clear existing statistics + foreach (QmlRangeEventData *eventDescription, rangeEventDictionary.values()) { + eventDescription->calls = 0; + // maximum possible value + eventDescription->minTime = endInstanceList.last().endTime; + eventDescription->maxTime = 0; + eventDescription->medianTime = 0; + eventDescription->duration = 0; + qDeleteAll(eventDescription->parentHash); + qDeleteAll(eventDescription->childrenHash); + eventDescription->parentHash.clear(); + eventDescription->childrenHash.clear(); + } +} + +void QmlProfilerDataModel::QmlProfilerDataModelPrivate::redoTree(qint64 fromTime, + qint64 toTime) +{ + double totalTime = 0; + int fromIndex = q->findFirstIndex(fromTime); + int toIndex = q->findLastIndex(toTime); + + QmlRangeEventData *listedRootEvent = rangeEventDictionary.value(rootEventName()); + QTC_ASSERT(listedRootEvent, /**/); + + // compute parent-child relationship and call count + QHash<int, QmlRangeEventData*> lastParent; + for (int index = fromIndex; index <= toIndex; index++) { + QmlRangeEventData *eventDescription = startInstanceList[index].statsInfo; + + if (startInstanceList[index].startTime > toTime || + startInstanceList[index].startTime+startInstanceList[index].duration < fromTime) { + continue; + } + + if (eventDescription->eventType == QmlJsDebugClient::Painting) { + // skip animation/paint events + continue; + } + + eventDescription->calls++; + qint64 duration = startInstanceList[index].duration; + eventDescription->duration += duration; + if (eventDescription->maxTime < duration) + eventDescription->maxTime = duration; + if (eventDescription->minTime > duration) + eventDescription->minTime = duration; + + int level = startInstanceList[index].level; + + QmlRangeEventData *parentEvent = listedRootEvent; + if (level > Constants::QML_MIN_LEVEL && lastParent.contains(level-1)) { + parentEvent = lastParent[level-1]; + } + + if (!eventDescription->parentHash.contains(parentEvent->eventHashStr)) { + QmlRangeEventRelative *newParentEvent = new QmlRangeEventRelative(parentEvent); + newParentEvent->calls = 1; + newParentEvent->duration = duration; + + eventDescription->parentHash.insert(parentEvent->eventHashStr, newParentEvent); + } else { + QmlRangeEventRelative *newParentEvent = + eventDescription->parentHash.value(parentEvent->eventHashStr); + newParentEvent->duration += duration; + newParentEvent->calls++; + } + + if (!parentEvent->childrenHash.contains(eventDescription->eventHashStr)) { + QmlRangeEventRelative *newChildEvent = new QmlRangeEventRelative(eventDescription); + newChildEvent->calls = 1; + newChildEvent->duration = duration; + + parentEvent->childrenHash.insert(eventDescription->eventHashStr, newChildEvent); + } else { + QmlRangeEventRelative *newChildEvent = + parentEvent->childrenHash.value(eventDescription->eventHashStr); + newChildEvent->duration += duration; + newChildEvent->calls++; + } + + lastParent[level] = eventDescription; + + if (level == Constants::QML_MIN_LEVEL) { + totalTime += duration; + } + } + + // fake rootEvent statistics + // the +1 nanosecond is to force it to be on top of the sorted list + listedRootEvent->duration = totalTime+1; + listedRootEvent->minTime = totalTime+1; + listedRootEvent->maxTime = totalTime+1; + listedRootEvent->medianTime = totalTime+1; + if (totalTime > 0) + listedRootEvent->calls = 1; + + // copy to the global root reference + qmlRootEvent = *listedRootEvent; + + // compute percentages + foreach (QmlRangeEventData *binding, rangeEventDictionary.values()) { + binding->percentOfTime = binding->duration * 100.0 / totalTime; + binding->timePerCall = binding->calls > 0 ? double(binding->duration) / binding->calls : 0; + } +} + +void QmlProfilerDataModel::QmlProfilerDataModelPrivate::computeMedianTime(qint64 fromTime, + qint64 toTime) +{ + int fromIndex = q->findFirstIndex(fromTime); + int toIndex = q->findLastIndex(toTime); + + // compute median time + QHash< QmlRangeEventData* , QList<qint64> > durationLists; + for (int index = fromIndex; index <= toIndex; index++) { + QmlRangeEventData *desc = startInstanceList[index].statsInfo; + qint64 len = startInstanceList[index].duration; + durationLists[desc].append(len); + } + QMutableHashIterator < QmlRangeEventData* , QList<qint64> > iter(durationLists); + while (iter.hasNext()) { + iter.next(); + if (!iter.value().isEmpty()) { + qSort(iter.value()); + iter.key()->medianTime = iter.value().at(iter.value().count()/2); + } + } +} + +void QmlProfilerDataModel::QmlProfilerDataModelPrivate::findBindingLoops(qint64 fromTime, + qint64 toTime) +{ + int fromIndex = q->findFirstIndex(fromTime); + int toIndex = q->findLastIndex(toTime); + + // first clear existing data + foreach (QmlRangeEventData *event, rangeEventDictionary.values()) { + event->isBindingLoop = false; + foreach (QmlRangeEventRelative *parentEvent, event->parentHash.values()) + parentEvent->inLoopPath = false; + foreach (QmlRangeEventRelative *childEvent, event->childrenHash.values()) + childEvent->inLoopPath = false; + } + + QList<QmlRangeEventData *> stackRefs; + QList<QmlRangeEventStartInstance *> stack; + + for (int i = 0; i < startInstanceList.count(); i++) { + QmlRangeEventData *currentEvent = startInstanceList[i].statsInfo; + QmlRangeEventStartInstance *inTimeEvent = &startInstanceList[i]; + inTimeEvent->bindingLoopHead = -1; + + // managing call stack + for (int j = stack.count() - 1; j >= 0; j--) { + if (stack[j]->startTime + stack[j]->duration <= inTimeEvent->startTime) { + stack.removeAt(j); + stackRefs.removeAt(j); + } + } + + bool loopDetected = stackRefs.contains(currentEvent); + stack << inTimeEvent; + stackRefs << currentEvent; + + if (loopDetected) { + if (i >= fromIndex && i <= toIndex) { + // for the statistics + currentEvent->isBindingLoop = true; + for (int j = stackRefs.indexOf(currentEvent); j < stackRefs.count()-1; j++) { + QmlRangeEventRelative *nextEventSub = + stackRefs[j]->childrenHash.value(stackRefs[j+1]->eventHashStr); + nextEventSub->inLoopPath = true; + QmlRangeEventRelative *prevEventSub = + stackRefs[j+1]->parentHash.value(stackRefs[j]->eventHashStr); + prevEventSub->inLoopPath = true; + } + } + + // use crossed references to find index in starttimesortedlist + QmlRangeEventStartInstance *head = stack[stackRefs.indexOf(currentEvent)]; + inTimeEvent->bindingLoopHead = endInstanceList[head->endTimeIndex].startTimeIndex; + startInstanceList[inTimeEvent->bindingLoopHead].bindingLoopHead = i; + } + } +} + +//////////////////////////////////////////////////////////////////////////////////// + +void QmlProfilerDataModel::QmlProfilerDataModelPrivate::clearQmlRootEvent() +{ + qmlRootEvent.displayName = rootEventName(); + qmlRootEvent.location = QmlEventLocation(); + qmlRootEvent.eventHashStr = rootEventName(); + qmlRootEvent.details = rootEventDescription(); + qmlRootEvent.eventType = QmlJsDebugClient::Binding; + qmlRootEvent.duration = 0; + qmlRootEvent.calls = 0; + qmlRootEvent.minTime = 0; + qmlRootEvent.maxTime = 0; + qmlRootEvent.timePerCall = 0; + qmlRootEvent.percentOfTime = 0; + qmlRootEvent.medianTime = 0; + qmlRootEvent.eventId = -1; + + qDeleteAll(qmlRootEvent.parentHash.values()); + qDeleteAll(qmlRootEvent.childrenHash.values()); + qmlRootEvent.parentHash.clear(); + qmlRootEvent.childrenHash.clear(); +} + +void QmlProfilerDataModel::QmlProfilerDataModelPrivate::insertQmlRootEvent() +{ + // create root event for statistics & insert into list + clearQmlRootEvent(); + QmlRangeEventData *listedRootEvent = rangeEventDictionary.value(rootEventName()); + if (!listedRootEvent) { + listedRootEvent = new QmlRangeEventData; + rangeEventDictionary.insert(rootEventName(), listedRootEvent); + } + *listedRootEvent = qmlRootEvent; +} + +void QmlProfilerDataModel::reloadDetails() +{ + // request binding/signal details from the AST + foreach (QmlRangeEventData *event, d->rangeEventDictionary.values()) { + if (event->eventType != Binding && event->eventType != HandlingSignal) + continue; + + // This skips anonymous bindings in Qt4.8 (we don't have valid location data for them) + if (event->location.filename.isEmpty()) + continue; + + // Skip non-anonymous bindings from Qt4.8 (we already have correct details for them) + if (event->location.column == -1) + continue; + + emit requestDetailsForLocation(event->eventType, event->location); + } + emit reloadDocumentsForDetails(); +} + +void QmlProfilerDataModel::rewriteDetailsString(int eventType, + const QmlJsDebugClient::QmlEventLocation &location, + const QString &newString) +{ + QString eventHashStr = getHashStringForQmlEvent(location, eventType); + QTC_ASSERT(d->rangeEventDictionary.contains(eventHashStr), return); + d->rangeEventDictionary.value(eventHashStr)->details = newString; + emit detailsChanged(d->rangeEventDictionary.value(eventHashStr)->eventId, newString); +} + +void QmlProfilerDataModel::finishedRewritingDetails() +{ + emit reloadDetailLabels(); +} + +//////////////////////////////////////////////////////////////////////////////////// + +bool QmlProfilerDataModel::save(const QString &filename) +{ + if (isEmpty()) { + emit error(tr("No data to save")); + return false; + } + + QFile file(filename); + if (!file.open(QIODevice::WriteOnly)) { + emit error(tr("Could not open %1 for writing").arg(filename)); + return false; + } + + QXmlStreamWriter stream(&file); + stream.setAutoFormatting(true); + stream.writeStartDocument(); + + stream.writeStartElement("trace"); + stream.writeAttribute("version", Constants::PROFILER_FILE_VERSION); + + stream.writeAttribute("traceStart", QString::number(traceStartTime())); + stream.writeAttribute("traceEnd", QString::number(traceEndTime())); + + stream.writeStartElement("eventData"); + stream.writeAttribute("totalTime", QString::number(d->qmlMeasuredTime)); + + foreach (const QmlRangeEventData *eventData, d->rangeEventDictionary.values()) { + stream.writeStartElement("event"); + stream.writeAttribute("index", QString::number(d->rangeEventDictionary.keys().indexOf(eventData->eventHashStr))); + stream.writeTextElement("displayname", eventData->displayName); + stream.writeTextElement("type", qmlEventTypeAsString(eventData->eventType)); + if (!eventData->location.filename.isEmpty()) { + stream.writeTextElement("filename", eventData->location.filename); + stream.writeTextElement("line", QString::number(eventData->location.line)); + stream.writeTextElement("column", QString::number(eventData->location.column)); + } + stream.writeTextElement("details", eventData->details); + stream.writeEndElement(); + } + stream.writeEndElement(); // eventData + + stream.writeStartElement("profilerDataModel"); + foreach (const QmlRangeEventStartInstance &rangedEvent, d->startInstanceList) { + stream.writeStartElement("range"); + stream.writeAttribute("startTime", QString::number(rangedEvent.startTime)); + stream.writeAttribute("duration", QString::number(rangedEvent.duration)); + stream.writeAttribute("eventIndex", QString::number(d->rangeEventDictionary.keys().indexOf(rangedEvent.statsInfo->eventHashStr))); + if (rangedEvent.statsInfo->eventType == QmlJsDebugClient::Painting && rangedEvent.animationCount >= 0) { + // animation frame + stream.writeAttribute("framerate", QString::number(rangedEvent.frameRate)); + stream.writeAttribute("animationcount", QString::number(rangedEvent.animationCount)); + } + stream.writeEndElement(); + } + stream.writeEndElement(); // profilerDataModel + + d->v8DataModel->save(stream); + + stream.writeEndElement(); // trace + stream.writeEndDocument(); + + file.close(); + return true; +} + +void QmlProfilerDataModel::setFilename(const QString &filename) +{ + d->fileName = filename; +} + +void QmlProfilerDataModel::load(const QString &filename) +{ + setFilename(filename); + load(); +} + +// "be strict in your output but tolerant in your inputs" +void QmlProfilerDataModel::load() +{ + QString filename = d->fileName; + + QFile file(filename); + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + emit error(tr("Could not open %1 for reading").arg(filename)); + return; + } + + // erase current + clear(); + + setState(AcquiringData); + + bool readingQmlEvents = false; + QHash<int, QmlRangeEventData *> descriptionBuffer; + QmlRangeEventData *currentEvent = 0; + bool startTimesAreSorted = true; + bool validVersion = true; + + // time computation + d->qmlMeasuredTime = 0; + + QXmlStreamReader stream(&file); + + while (validVersion && !stream.atEnd() && !stream.hasError()) { + QXmlStreamReader::TokenType token = stream.readNext(); + QString elementName = stream.name().toString(); + switch (token) { + case QXmlStreamReader::StartDocument : continue; + case QXmlStreamReader::StartElement : { + if (elementName == "trace") { + QXmlStreamAttributes attributes = stream.attributes(); + if (attributes.hasAttribute("version")) + validVersion = attributes.value("version").toString() == Constants::PROFILER_FILE_VERSION; + else + validVersion = false; + if (attributes.hasAttribute("traceStart")) + setTraceStartTime(attributes.value("traceStart").toString().toLongLong()); + if (attributes.hasAttribute("traceEnd")) + setTraceEndTime(attributes.value("traceEnd").toString().toLongLong()); + } + if (elementName == "eventData") { + readingQmlEvents = true; + QXmlStreamAttributes attributes = stream.attributes(); + if (attributes.hasAttribute("totalTime")) + d->qmlMeasuredTime = attributes.value("totalTime").toString().toDouble(); + break; + } + if (elementName == "v8profile" && !readingQmlEvents) { + d->v8DataModel->load(stream); + break; + } + + if (elementName == "trace") { + QXmlStreamAttributes attributes = stream.attributes(); + if (attributes.hasAttribute("traceStart")) + setTraceStartTime(attributes.value("traceStart").toString().toLongLong()); + if (attributes.hasAttribute("traceEnd")) + setTraceEndTime(attributes.value("traceEnd").toString().toLongLong()); + } + + if (elementName == "range") { + QmlRangeEventStartInstance rangedEvent; + QXmlStreamAttributes attributes = stream.attributes(); + if (attributes.hasAttribute("startTime")) + rangedEvent.startTime = attributes.value("startTime").toString().toLongLong(); + if (attributes.hasAttribute("duration")) + rangedEvent.duration = attributes.value("duration").toString().toLongLong(); + if (attributes.hasAttribute("framerate")) + rangedEvent.frameRate = attributes.value("framerate").toString().toInt(); + if (attributes.hasAttribute("animationcount")) + rangedEvent.animationCount = attributes.value("animationcount").toString().toInt(); + else + rangedEvent.animationCount = -1; + if (attributes.hasAttribute("eventIndex")) { + int ndx = attributes.value("eventIndex").toString().toInt(); + if (!descriptionBuffer.value(ndx)) + descriptionBuffer[ndx] = new QmlRangeEventData; + rangedEvent.statsInfo = descriptionBuffer.value(ndx); + } + rangedEvent.endTimeIndex = d->endInstanceList.count(); + + if (!d->startInstanceList.isEmpty() + && rangedEvent.startTime < d->startInstanceList.last().startTime) + startTimesAreSorted = false; + d->startInstanceList << rangedEvent; + + QmlRangeEventEndInstance endTimeEvent; + endTimeEvent.endTime = rangedEvent.startTime + rangedEvent.duration; + endTimeEvent.startTimeIndex = d->startInstanceList.count()-1; + endTimeEvent.description = rangedEvent.statsInfo; + d->endInstanceList << endTimeEvent; + break; + } + + if (readingQmlEvents) { + if (elementName == "event") { + QXmlStreamAttributes attributes = stream.attributes(); + if (attributes.hasAttribute("index")) { + int ndx = attributes.value("index").toString().toInt(); + if (!descriptionBuffer.value(ndx)) + descriptionBuffer[ndx] = new QmlRangeEventData; + currentEvent = descriptionBuffer[ndx]; + } else { + currentEvent = 0; + } + break; + } + + // the remaining are eventdata or v8eventdata elements + if (!currentEvent) + break; + + stream.readNext(); + if (stream.tokenType() != QXmlStreamReader::Characters) + break; + QString readData = stream.text().toString(); + + if (elementName == "displayname") { + currentEvent->displayName = readData; + break; + } + if (elementName == "type") { + currentEvent->eventType = qmlEventTypeAsEnum(readData); + break; + } + if (elementName == "filename") { + currentEvent->location.filename = readData; + break; + } + if (elementName == "line") { + currentEvent->location.line = readData.toInt(); + break; + } + if (elementName == "column") { + currentEvent->location.column = readData.toInt(); + } + if (elementName == "details") { + currentEvent->details = readData; + break; + } + } + break; + } + case QXmlStreamReader::EndElement : { + if (elementName == "event") { + currentEvent = 0; + break; + } + if (elementName == "eventData") { + readingQmlEvents = false; + break; + } + } + default: break; + } + } + + file.close(); + + if (stream.hasError()) { + emit error(tr("Error while parsing %1").arg(filename)); + clear(); + return; + } + + stream.clear(); + + if (!validVersion) { + clear(); + emit error(tr("Invalid version of QML Trace file.")); + return; + } + + // move the buffered data to the details cache + foreach (QmlRangeEventData *desc, descriptionBuffer.values()) { + desc->eventHashStr = getHashStringForQmlEvent(desc->location, desc->eventType);; + d->rangeEventDictionary[desc->eventHashStr] = desc; + } + + // sort startTimeSortedList + if (!startTimesAreSorted) { + qSort(d->startInstanceList.begin(), d->startInstanceList.end(), compareStartTimes); + for (int i = 0; i< d->startInstanceList.count(); i++) { + QmlRangeEventStartInstance startTimeData = d->startInstanceList[i]; + d->endInstanceList[startTimeData.endTimeIndex].startTimeIndex = i; + } + qSort(d->endInstanceList.begin(), d->endInstanceList.end(), compareStartIndexes); + } + + emit countChanged(); + + descriptionBuffer.clear(); + + setState(ProcessingData); + d->postProcess(); +} + +//////////////////////////////////////////////////////////////////////////////////// + +QmlProfilerDataModel::State QmlProfilerDataModel::currentState() const +{ + return d->listState; +} + +int QmlProfilerDataModel::getCurrentStateFromQml() const +{ + return (int)d->listState; +} + +void QmlProfilerDataModel::setState(QmlProfilerDataModel::State state) +{ + // It's not an error, we are continuously calling "AcquiringData" for example + if (d->listState == state) + return; + + switch (state) { + case Empty: + // if it's not empty, complain but go on + QTC_ASSERT(isEmpty(), /**/); + break; + case AcquiringData: + // we're not supposed to receive new data while processing older data + QTC_ASSERT(d->listState != ProcessingData, return); + break; + case ProcessingData: + QTC_ASSERT(d->listState == AcquiringData, return); + break; + case Done: + QTC_ASSERT(d->listState == ProcessingData || d->listState == Empty, return); + break; + default: + emit error("Trying to set unknown state in events list"); + break; + } + + d->listState = state; + emit stateChanged(); + + // special: if we were done with an empty list, clean internal data and go back to empty + if (d->listState == Done && isEmpty()) { + clear(); + } + return; +} + +} // namespace Internal +} // namespace QmlProfiler + +QT_BEGIN_NAMESPACE +Q_DECLARE_TYPEINFO(QmlProfiler::Internal::QmlRangeEventData, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(QmlProfiler::Internal::QmlRangeEventStartInstance, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(QmlProfiler::Internal::QmlRangeEventEndInstance, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(QmlProfiler::Internal::QmlRangeEventRelative, Q_MOVABLE_TYPE); +QT_END_NAMESPACE |