/************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: http://www.qt-project.org/ ** ** ** 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. ** ** **************************************************************************/ #include "qmlprofilerdatamodel.h" #include #include #include #include #include #include #include #include #include #include using namespace QmlDebug; 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; int baseEventIndex; // animation-related data int frameRate; int animationCount; int bindingLoopHead; }; struct QmlRangeEventTypeCount { QVector 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, QListvisited); // 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 rangeEventDictionary; QVector endInstanceList; QVector startInstanceList; QmlRangeEventData qmlRootEvent; QV8ProfilerDataModel *v8DataModel; QHash typeCounts; qint64 traceEndTime; qint64 traceStartTime; qint64 qmlMeasuredTime; int lastFrameEventIndex; 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->lastFrameEventIndex = -1; d->maxAnimationCount = 0; d->minAnimationCount = 0; d->v8DataModel = new QV8ProfilerDataModel(this, this); } QmlProfilerDataModel::~QmlProfilerDataModel() { clear(); delete d; } //////////////////////////////////////////////////////////////////////////////////// QList 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 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->lastFrameEventIndex = -1; d->maxAnimationCount = 0; d->minAnimationCount = 0; d->v8DataModel->clear(); emit countChanged(); setState(Empty); } void QmlProfilerDataModel::addRangedEvent(int type, int bindingType, qint64 startTime, qint64 length, const QStringList &data, const QmlDebug::QmlEventLocation &location) { const QChar colon = QLatin1Char(':'); QString displayName, eventHashStr, details; QmlDebug::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 == QmlDebug::Compiling && eventLocation.filename.isEmpty()) { eventLocation.filename = details; eventLocation.line = 1; eventLocation.column = 1; } // generate hash if (eventLocation.filename.isEmpty()) { displayName = tr(""); 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 = (QmlDebug::QmlEventType)type; newEvent->details = details; newEvent->bindingType = bindingType; 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; startTimeData.baseEventIndex = d->startInstanceList.count(); // point to itself by default 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(""); 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 = QmlDebug::Painting; newEvent->details = details; d->rangeEventDictionary.insert(eventHashStr, newEvent); } qint64 length = 1e9/framerate; // avoid overlap QmlRangeEventStartInstance *lastFrameEvent = 0; if (d->lastFrameEventIndex > -1) { lastFrameEvent = &d->startInstanceList[d->lastFrameEventIndex]; if (lastFrameEvent->startTime + lastFrameEvent->duration >= time) { lastFrameEvent->duration = time - 1 - lastFrameEvent->startTime; d->endInstanceList[lastFrameEvent->endTimeIndex].endTime = lastFrameEvent->startTime + 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; startTimeData.baseEventIndex = d->startInstanceList.count(); // point to itself by default d->endInstanceList << endTimeData; d->startInstanceList << startTimeData; d->lastFrameEventIndex = d->startInstanceList.count() - 1; emit countChanged(); } void QmlProfilerDataModel::setTraceEndTime(qint64 time) { d->traceEndTime = time; } void QmlProfilerDataModel::setTraceStartTime(qint64 time) { d->traceStartTime = time; } //////////////////////////////////////////////////////////////////////////////////// QString QmlProfilerDataModel::getHashStringForQmlEvent( const QmlDebug::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(""); } 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 eventIndex = d->endInstanceList[candidate].startTimeIndex; return d->startInstanceList[eventIndex].baseEventIndex; } 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 == QmlDebug::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) { d->v8DataModel->collectV8Statistics(); compileStatistics(traceStartTime(), traceEndTime()); 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::iterator itFrom = startInstanceList.end() - 2; QVector::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::iterator itFrom = endInstanceList.begin(); QVector::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; lastFrameEventIndex = -1; for (int i = 0; i < startInstanceList.count(); i++) { if (startInstanceList[i].statsInfo->eventType == QmlDebug::Painting && startInstanceList[i].animationCount >= 0) { int animationcount = startInstanceList[i].animationCount; if (lastFrameEventIndex > -1) { if (animationcount > maxAnimationCount) maxAnimationCount = animationcount; if (animationcount < minAnimationCount) minAnimationCount = animationcount; } else { maxAnimationCount = animationcount; minAnimationCount = animationcount; } lastFrameEventIndex = i; } } } void QmlProfilerDataModel::QmlProfilerDataModelPrivate::computeNestingLevels() { // compute levels QHash endtimesPerLevel; QList nestingLevels; QList< QHash > endtimesPerNestingLevel; int level = Constants::QML_MIN_LEVEL; endtimesPerLevel[Constants::QML_MIN_LEVEL] = 0; int lastBaseEventIndex = 0; qint64 lastBaseEventEndTime = traceStartTime; for (int i = 0; i < QmlDebug::MaximumQmlEventType; i++) { nestingLevels << Constants::QML_MIN_LEVEL; QHash dummyHash; dummyHash[Constants::QML_MIN_LEVEL] = 0; endtimesPerNestingLevel << dummyHash; } for (int i=0; ieventType; if (type == QmlDebug::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; if (lastBaseEventEndTime < startInstanceList[i].startTime) { lastBaseEventIndex = i; lastBaseEventEndTime = startInstanceList[i].startTime + startInstanceList[i].duration; } startInstanceList[i].baseEventIndex = lastBaseEventIndex; 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; if (lastBaseEventEndTime < startInstanceList[i].startTime) { lastBaseEventIndex = i; lastBaseEventEndTime = startInstanceList[i].startTime + startInstanceList[i].duration; } } startInstanceList[i].baseEventIndex = lastBaseEventIndex; } } void QmlProfilerDataModel::QmlProfilerDataModelPrivate::computeNestingDepth() { QHash 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(); if (traceDuration() > 0) { if (count() > 0) { d->redoTree(startTime, endTime); d->computeMedianTime(startTime, endTime); d->findBindingLoops(startTime, endTime); } else { d->insertQmlRootEvent(); QmlRangeEventData *listedRootEvent = d->rangeEventDictionary.value(rootEventName()); listedRootEvent->calls = 1; listedRootEvent->percentOfTime = 100; } } } void QmlProfilerDataModel::QmlProfilerDataModelPrivate::clearStatistics() { // clear existing statistics foreach (QmlRangeEventData *eventDescription, rangeEventDictionary.values()) { eventDescription->calls = 0; // maximum possible value eventDescription->minTime = traceEndTime; 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 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 == QmlDebug::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 > 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 > 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 stackRefs; QList stack; for (int i = 0; i < startInstanceList.count(); i++) { QmlRangeEventData *currentEvent = startInstanceList[i].statsInfo; QmlRangeEventStartInstance *inTimeEvent = &startInstanceList[i]; inTimeEvent->bindingLoopHead = -1; // special: skip animation events (but not old paint events) if (inTimeEvent->statsInfo->eventType == Painting && inTimeEvent->animationCount >= 0) continue; // 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 = QmlDebug::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 QmlDebug::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); if (eventData->eventType == Binding) stream.writeTextElement("bindingType", QString::number((int)eventData->bindingType)); 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 == QmlDebug::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 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]; // backwards compatibility: default bindingType currentEvent->bindingType = QmlBinding; } 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; } if (elementName == "bindingType") { currentEvent->bindingType = readData.toInt(); 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(); 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