summaryrefslogtreecommitdiff
path: root/src/plugins/qmlprofiler/qmlprofilerdatamodel.cpp
diff options
context:
space:
mode:
authorChristiaan Janssen <christiaan.janssen@nokia.com>2012-02-24 10:47:17 +0100
committerChristiaan Janssen <christiaan.janssen@nokia.com>2012-03-14 11:38:25 +0100
commitb7304e2f2e533b767bcd1c02d8403e3d5fa63ddd (patch)
tree30bb48a69d7c6bd1c35bcdd359a757486f298368 /src/plugins/qmlprofiler/qmlprofilerdatamodel.cpp
parentd207165f6aa89ee95bd41c41a49d68cfa0b46444 (diff)
downloadqt-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.cpp1652
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