From 1f76525c5f701f612b56a49715cda6597e3727b1 Mon Sep 17 00:00:00 2001 From: Christiaan Janssen Date: Thu, 8 Mar 2012 16:50:14 +0100 Subject: QmlProfiler: updated standalone app Using a stripped-down version of the profiler data structure. Change-Id: I93a0b12462edea0ca8a1d0db42aa892aa2afc919 Reviewed-by: Kai Koehne --- tools/qmlprofiler/qmlprofilerdata.cpp | 602 ++++++++++++++++++++++++++++++++++ 1 file changed, 602 insertions(+) create mode 100644 tools/qmlprofiler/qmlprofilerdata.cpp (limited to 'tools/qmlprofiler/qmlprofilerdata.cpp') diff --git a/tools/qmlprofiler/qmlprofilerdata.cpp b/tools/qmlprofiler/qmlprofilerdata.cpp new file mode 100644 index 0000000000..38e64ff46e --- /dev/null +++ b/tools/qmlprofiler/qmlprofilerdata.cpp @@ -0,0 +1,602 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** 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. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmlprofilerdata.h" + +#include +#include +#include +#include +#include + +namespace Constants { + const char TYPE_PAINTING_STR[] = "Painting"; + const char TYPE_COMPILING_STR[] = "Compiling"; + const char TYPE_CREATING_STR[] = "Creating"; + const char TYPE_BINDING_STR[] = "Binding"; + const char TYPE_HANDLINGSIGNAL_STR[] = "HandlingSignal"; + const char PROFILER_FILE_VERSION[] = "1.02"; +} + +struct QmlRangeEventData { + QmlRangeEventData() {} // never called + QmlRangeEventData(const QString &_displayName, + const QString &_eventHashStr, + const QmlEventLocation &_location, + const QString &_details, + const QQmlProfilerService::RangeType &_eventType) + : displayName(_displayName),eventHashStr(_eventHashStr),location(_location), + details(_details),eventType(_eventType) {} + QString displayName; + QString eventHashStr; + QmlEventLocation location; + QString details; + QQmlProfilerService::RangeType eventType; +}; + +struct QmlRangeEventStartInstance { + QmlRangeEventStartInstance() {} // never called + QmlRangeEventStartInstance(qint64 _startTime, qint64 _duration, int _frameRate, + int _animationCount, QmlRangeEventData *_data) + : startTime(_startTime), duration(_duration), frameRate(_frameRate), + animationCount(_animationCount), data(_data) + { } + qint64 startTime; + qint64 duration; + int frameRate; + int animationCount; + QmlRangeEventData *data; +}; + +QT_BEGIN_NAMESPACE +Q_DECLARE_TYPEINFO(QmlRangeEventData, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(QmlRangeEventStartInstance, Q_MOVABLE_TYPE); +QT_END_NAMESPACE + +struct QV8EventInfo { + QString displayName; + QString eventHashStr; + QString functionName; + QString fileName; + int line; + qint64 totalTime; + qint64 selfTime; + + QHash v8children; +}; + +///////////////////////////////////////////////////////////////// +class QmlProfilerDataPrivate +{ +public: + QmlProfilerDataPrivate(QmlProfilerData *qq){ Q_UNUSED(qq); } + + // data storage + QHash eventDescriptions; + QVector startInstanceList; + QHash v8EventHash; + + qint64 traceStartTime; + qint64 traceEndTime; + + // internal state while collecting events + QmlRangeEventStartInstance *lastFrameEvent; + qint64 qmlMeasuredTime; + qint64 v8MeasuredTime; + QHash v8parents; + void clearV8RootEvent(); + QV8EventInfo v8RootEvent; + + QmlProfilerData::State state; +}; + +///////////////////////////////////////////////////////////////// +QmlProfilerData::QmlProfilerData(QObject *parent) : + QObject(parent),d(new QmlProfilerDataPrivate(this)) +{ + d->state = Empty; + clear(); +} + +QmlProfilerData::~QmlProfilerData() +{ + clear(); + delete d; +} + +void QmlProfilerData::clear() +{ + qDeleteAll(d->eventDescriptions.values()); + d->eventDescriptions.clear(); + d->startInstanceList.clear(); + + qDeleteAll(d->v8EventHash.values()); + d->v8EventHash.clear(); + d->v8parents.clear(); + d->clearV8RootEvent(); + d->v8MeasuredTime = 0; + + d->traceEndTime = 0; + d->traceStartTime = -1; + d->qmlMeasuredTime = 0; + + d->lastFrameEvent = 0; + + setState(Empty); +} + +QString QmlProfilerData::getHashStringForQmlEvent(const QmlEventLocation &location, int eventType) +{ + return QString(QStringLiteral("%1:%2:%3:%4")).arg( + location.filename, + QString::number(location.line), + QString::number(location.column), + QString::number(eventType)); +} + +QString QmlProfilerData::getHashStringForV8Event(const QString &displayName, const QString &function) +{ + return QString(QStringLiteral("%1:%2")).arg(displayName, function); +} + +QString QmlProfilerData::qmlRangeTypeAsString(QQmlProfilerService::RangeType typeEnum) +{ + switch (typeEnum) { + case QQmlProfilerService::Painting: + return QLatin1String(Constants::TYPE_PAINTING_STR); + break; + case QQmlProfilerService::Compiling: + return QLatin1String(Constants::TYPE_COMPILING_STR); + break; + case QQmlProfilerService::Creating: + return QLatin1String(Constants::TYPE_CREATING_STR); + break; + case QQmlProfilerService::Binding: + return QLatin1String(Constants::TYPE_BINDING_STR); + break; + case QQmlProfilerService::HandlingSignal: + return QLatin1String(Constants::TYPE_HANDLINGSIGNAL_STR); + break; + default: + return QString::number((int)typeEnum); + } +} + +void QmlProfilerData::setTraceStartTime(qint64 time) +{ + d->traceStartTime = time; +} + +void QmlProfilerData::setTraceEndTime(qint64 time) +{ + d->traceEndTime = time; +} + +qint64 QmlProfilerData::traceStartTime() const +{ + return d->traceStartTime; +} + +qint64 QmlProfilerData::traceEndTime() const +{ + return d->traceEndTime; +} + +void QmlProfilerData::addQmlEvent(QQmlProfilerService::RangeType type, + qint64 startTime, + qint64 duration, + const QStringList &data, + const QmlEventLocation &location) +{ + setState(AcquiringData); + + QString details; + // generate details string + if (data.isEmpty()) + details = tr("Source code not available"); + else { + details = data.join(QStringLiteral(" ")).replace( + QLatin1Char('\n'),QStringLiteral(" ")).simplified(); + QRegExp rewrite(QStringLiteral("\\(function \\$(\\w+)\\(\\) \\{ (return |)(.+) \\}\\)")); + bool match = rewrite.exactMatch(details); + if (match) { + details = rewrite.cap(1) +QLatin1String(": ") + rewrite.cap(3); + } + if (details.startsWith(QLatin1String("file://"))) + details = details.mid(details.lastIndexOf(QLatin1Char('/')) + 1); + } + + QmlEventLocation eventLocation = location; + QString displayName, eventHashStr; + // 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(QLatin1Char('/')) + 1) + + QLatin1Char(':') + QString::number(eventLocation.line); + eventHashStr = getHashStringForQmlEvent(eventLocation, type); + } + + QmlRangeEventData *newEvent; + if (d->eventDescriptions.contains(eventHashStr)) { + newEvent = d->eventDescriptions[eventHashStr]; + } else { + newEvent = new QmlRangeEventData(displayName, eventHashStr, location, details, type); + d->eventDescriptions.insert(eventHashStr, newEvent); + } + + QmlRangeEventStartInstance rangeEventStartInstance(startTime, duration, 1e9/duration, -1, newEvent); + + d->startInstanceList.append(rangeEventStartInstance); +} + +void QmlProfilerData::addFrameEvent(qint64 time, int framerate, int animationcount) +{ + setState(AcquiringData); + + QString details = tr("Animation Timer Update"); + QString displayName = tr(""); + QString eventHashStr = displayName; + + QmlRangeEventData *newEvent; + if (d->eventDescriptions.contains(eventHashStr)) { + newEvent = d->eventDescriptions[eventHashStr]; + } else { + newEvent = new QmlRangeEventData(displayName, eventHashStr, QmlEventLocation(), details, QQmlProfilerService::Painting); + d->eventDescriptions.insert(eventHashStr, newEvent); + } + + qint64 duration = 1e9/framerate; + // avoid overlap + if (d->lastFrameEvent && + d->lastFrameEvent->startTime + d->lastFrameEvent->duration >= time) { + d->lastFrameEvent->duration = time - 1 - d->lastFrameEvent->startTime; + } + + QmlRangeEventStartInstance rangeEventStartInstance(time, duration, framerate, animationcount, newEvent); + + d->startInstanceList.append(rangeEventStartInstance); + + d->lastFrameEvent = &d->startInstanceList.last(); +} + +QString QmlProfilerData::rootEventName() +{ + return tr(""); +} + +QString QmlProfilerData::rootEventDescription() +{ + return tr("Main Program"); +} + +void QmlProfilerDataPrivate::clearV8RootEvent() +{ + v8RootEvent.displayName = QmlProfilerData::rootEventName(); + v8RootEvent.eventHashStr = QmlProfilerData::rootEventName(); + v8RootEvent.functionName = QmlProfilerData::rootEventDescription(); + v8RootEvent.line = -1; + v8RootEvent.totalTime = 0; + v8RootEvent.selfTime = 0; + v8RootEvent.v8children.clear(); +} + +void QmlProfilerData::addV8Event(int depth, const QString &function, const QString &filename, + int lineNumber, double totalTime, double selfTime) +{ + QString displayName = filename.mid(filename.lastIndexOf(QLatin1Char('/')) + 1) + + QLatin1Char(':') + QString::number(lineNumber); + QString hashStr = getHashStringForV8Event(displayName, function); + + setState(AcquiringData); + + // time is given in milliseconds, but internally we store it in microseconds + totalTime *= 1e6; + selfTime *= 1e6; + + // accumulate information + QV8EventInfo *eventData = d->v8EventHash[hashStr]; + if (!eventData) { + eventData = new QV8EventInfo; + eventData->displayName = displayName; + eventData->eventHashStr = hashStr; + eventData->fileName = filename; + eventData->functionName = function; + eventData->line = lineNumber; + eventData->totalTime = totalTime; + eventData->selfTime = selfTime; + d->v8EventHash[hashStr] = eventData; + } else { + eventData->totalTime += totalTime; + eventData->selfTime += selfTime; + } + d->v8parents[depth] = eventData; + + QV8EventInfo *parentEvent = 0; + if (depth == 0) { + parentEvent = &d->v8RootEvent; + d->v8MeasuredTime += totalTime; + } + if (depth > 0 && d->v8parents.contains(depth-1)) { + parentEvent = d->v8parents.value(depth-1); + } + + if (parentEvent != 0) { + if (!parentEvent->v8children.contains(eventData->eventHashStr)) { + parentEvent->v8children[eventData->eventHashStr] = totalTime; + } else { + parentEvent->v8children[eventData->eventHashStr] += totalTime; + } + } +} + +void QmlProfilerData::computeQmlTime() +{ + // compute levels + QHash endtimesPerLevel; + int minimumLevel = 1; + int level = minimumLevel; + + for (int i = 0; i < d->startInstanceList.count(); i++) { + qint64 st = d->startInstanceList[i].startTime; + int type = d->startInstanceList[i].data->eventType; + + if (type == QQmlProfilerService::Painting) { + continue; + } + + // general level + if (endtimesPerLevel[level] > st) { + level++; + } else { + while (level > minimumLevel && endtimesPerLevel[level-1] <= st) + level--; + } + endtimesPerLevel[level] = st + d->startInstanceList[i].duration; + + if (level == minimumLevel) { + d->qmlMeasuredTime += d->startInstanceList[i].duration; + } + } +} + +bool compareStartTimes(const QmlRangeEventStartInstance &t1, const QmlRangeEventStartInstance &t2) +{ + return t1.startTime < t2.startTime; +} + +void QmlProfilerData::sortStartTimes() +{ + if (d->startInstanceList.count() < 2) + return; + + // assuming startTimes is partially sorted + // identify blocks of events and sort them with quicksort + QVector::iterator itFrom = d->startInstanceList.end() - 2; + QVector::iterator itTo = d->startInstanceList.end() - 1; + + while (itFrom != d->startInstanceList.begin() && itTo != d->startInstanceList.begin()) { + // find block to sort + while ( itFrom != d->startInstanceList.begin() + && itTo->startTime > itFrom->startTime ) { + itTo--; + itFrom = itTo - 1; + } + + // if we're at the end of the list + if (itFrom == d->startInstanceList.begin()) + break; + + // find block length + while ( itFrom != d->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; + } +} + +void QmlProfilerData::complete() +{ + setState(ProcessingData); + sortStartTimes(); + computeQmlTime(); + setState(Done); + emit dataReady(); +} + +bool QmlProfilerData::isEmpty() const +{ + return d->startInstanceList.isEmpty() && d->v8EventHash.isEmpty(); +} + +bool QmlProfilerData::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(QStringLiteral("trace")); + stream.writeAttribute(QStringLiteral("version"), QLatin1String(Constants::PROFILER_FILE_VERSION)); + + stream.writeAttribute(QStringLiteral("traceStart"), QString::number(traceStartTime())); + stream.writeAttribute(QStringLiteral("traceEnd"), QString::number(traceEndTime())); + + stream.writeStartElement(QStringLiteral("eventData")); + stream.writeAttribute(QStringLiteral("totalTime"), QString::number(d->qmlMeasuredTime)); + + foreach (const QmlRangeEventData *eventData, d->eventDescriptions.values()) { + stream.writeStartElement(QStringLiteral("event")); + stream.writeAttribute(QStringLiteral("index"), QString::number(d->eventDescriptions.keys().indexOf(eventData->eventHashStr))); + stream.writeTextElement(QStringLiteral("displayname"), eventData->displayName); + stream.writeTextElement(QStringLiteral("type"), qmlRangeTypeAsString(eventData->eventType)); + if (!eventData->location.filename.isEmpty()) { + stream.writeTextElement(QStringLiteral("filename"), eventData->location.filename); + stream.writeTextElement(QStringLiteral("line"), QString::number(eventData->location.line)); + stream.writeTextElement(QStringLiteral("column"), QString::number(eventData->location.column)); + } + stream.writeTextElement(QStringLiteral("details"), eventData->details); + stream.writeEndElement(); + } + stream.writeEndElement(); // eventData + + stream.writeStartElement(QStringLiteral("profilerDataModel")); + foreach (const QmlRangeEventStartInstance &rangedEvent, d->startInstanceList) { + stream.writeStartElement(QStringLiteral("range")); + stream.writeAttribute(QStringLiteral("startTime"), QString::number(rangedEvent.startTime)); + stream.writeAttribute(QStringLiteral("duration"), QString::number(rangedEvent.duration)); + stream.writeAttribute(QStringLiteral("eventIndex"), QString::number(d->eventDescriptions.keys().indexOf(rangedEvent.data->eventHashStr))); + if (rangedEvent.data->eventType == QQmlProfilerService::Painting && rangedEvent.animationCount >= 0) { + // animation frame + stream.writeAttribute(QStringLiteral("framerate"), QString::number(rangedEvent.frameRate)); + stream.writeAttribute(QStringLiteral("animationcount"), QString::number(rangedEvent.animationCount)); + } + stream.writeEndElement(); + } + stream.writeEndElement(); // profilerDataModel + + stream.writeStartElement(QStringLiteral("v8profile")); // v8 profiler output + stream.writeAttribute(QStringLiteral("totalTime"), QString::number(d->v8MeasuredTime)); + foreach (QV8EventInfo *v8event, d->v8EventHash.values()) { + stream.writeStartElement(QStringLiteral("event")); + stream.writeAttribute(QStringLiteral("index"), QString::number(d->v8EventHash.keys().indexOf(v8event->eventHashStr))); + stream.writeTextElement(QStringLiteral("displayname"), v8event->displayName); + stream.writeTextElement(QStringLiteral("functionname"), v8event->functionName); + if (!v8event->fileName.isEmpty()) { + stream.writeTextElement(QStringLiteral("filename"), v8event->fileName); + stream.writeTextElement(QStringLiteral("line"), QString::number(v8event->line)); + } + stream.writeTextElement(QStringLiteral("totalTime"), QString::number(v8event->totalTime)); + stream.writeTextElement(QStringLiteral("selfTime"), QString::number(v8event->selfTime)); + if (!v8event->v8children.isEmpty()) { + stream.writeStartElement(QStringLiteral("childrenEvents")); + QStringList childrenIndexes; + QStringList childrenTimes; + foreach (const QString &childHash, v8event->v8children.keys()) { + childrenIndexes << QString::number(v8EventIndex(childHash)); + childrenTimes << QString::number(v8event->v8children[childHash]); + } + + stream.writeAttribute(QStringLiteral("list"), childrenIndexes.join(QString(", "))); + stream.writeAttribute(QStringLiteral("childrenTimes"), childrenTimes.join(QString(", "))); + stream.writeEndElement(); + } + stream.writeEndElement(); + } + stream.writeEndElement(); // v8 profiler output + + stream.writeEndElement(); // trace + stream.writeEndDocument(); + + file.close(); + return true; +} + +int QmlProfilerData::v8EventIndex(const QString &hashStr) +{ + if (!d->v8EventHash.contains(hashStr)) { + emit error("Trying to index nonexisting v8 event"); + return -1; + } + return d->v8EventHash.keys().indexOf( hashStr ); +} + +void QmlProfilerData::setState(QmlProfilerData::State state) +{ + // It's not an error, we are continuously calling "AcquiringData" for example + if (d->state == state) + return; + + switch (state) { + case Empty: + // if it's not empty, complain but go on + if (!isEmpty()) + emit error("Invalid qmlprofiler state change (Empty)"); + break; + case AcquiringData: + // we're not supposed to receive new data while processing older data + if (d->state == ProcessingData) + emit error("Invalid qmlprofiler state change (AcquiringData)"); + break; + case ProcessingData: + if (d->state != AcquiringData) + emit error("Invalid qmlprofiler state change (ProcessingData)"); + break; + case Done: + if (d->state != ProcessingData && d->state != Empty) + emit error("Invalid qmlprofiler state change (Done)"); + break; + default: + emit error("Trying to set unknown state in events list"); + break; + } + + d->state = state; + emit stateChanged(); + + // special: if we were done with an empty list, clean internal data and go back to empty + if (d->state == Done && isEmpty()) { + clear(); + } + return; +} + -- cgit v1.2.3