1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qmlprofilerdata.h"
44 #include <QStringList>
48 #include <QXmlStreamReader>
51 const char TYPE_PAINTING_STR[] = "Painting";
52 const char TYPE_COMPILING_STR[] = "Compiling";
53 const char TYPE_CREATING_STR[] = "Creating";
54 const char TYPE_BINDING_STR[] = "Binding";
55 const char TYPE_HANDLINGSIGNAL_STR[] = "HandlingSignal";
56 const char PROFILER_FILE_VERSION[] = "1.02";
59 struct QmlRangeEventData {
60 QmlRangeEventData() {} // never called
61 QmlRangeEventData(const QString &_displayName,
62 const QString &_eventHashStr,
63 const QmlEventLocation &_location,
64 const QString &_details,
65 const QQmlProfilerService::RangeType &_eventType)
66 : displayName(_displayName),eventHashStr(_eventHashStr),location(_location),
67 details(_details),eventType(_eventType) {}
70 QmlEventLocation location;
72 QQmlProfilerService::RangeType eventType;
75 struct QmlRangeEventStartInstance {
76 QmlRangeEventStartInstance() {} // never called
77 QmlRangeEventStartInstance(qint64 _startTime, qint64 _duration, int _frameRate,
78 int _animationCount, QmlRangeEventData *_data)
79 : startTime(_startTime), duration(_duration), frameRate(_frameRate),
80 animationCount(_animationCount), data(_data)
86 QmlRangeEventData *data;
90 Q_DECLARE_TYPEINFO(QmlRangeEventData, Q_MOVABLE_TYPE);
91 Q_DECLARE_TYPEINFO(QmlRangeEventStartInstance, Q_MOVABLE_TYPE);
103 QHash<QString, qint64> v8children;
106 /////////////////////////////////////////////////////////////////
107 class QmlProfilerDataPrivate
110 QmlProfilerDataPrivate(QmlProfilerData *qq){ Q_UNUSED(qq); }
113 QHash<QString, QmlRangeEventData *> eventDescriptions;
114 QVector<QmlRangeEventStartInstance> startInstanceList;
115 QHash<QString, QV8EventInfo *> v8EventHash;
117 qint64 traceStartTime;
120 // internal state while collecting events
121 QmlRangeEventStartInstance *lastFrameEvent;
122 qint64 qmlMeasuredTime;
123 qint64 v8MeasuredTime;
124 QHash<int, QV8EventInfo *> v8parents;
125 void clearV8RootEvent();
126 QV8EventInfo v8RootEvent;
128 QmlProfilerData::State state;
131 /////////////////////////////////////////////////////////////////
132 QmlProfilerData::QmlProfilerData(QObject *parent) :
133 QObject(parent),d(new QmlProfilerDataPrivate(this))
139 QmlProfilerData::~QmlProfilerData()
145 void QmlProfilerData::clear()
147 qDeleteAll(d->eventDescriptions.values());
148 d->eventDescriptions.clear();
149 d->startInstanceList.clear();
151 qDeleteAll(d->v8EventHash.values());
152 d->v8EventHash.clear();
153 d->v8parents.clear();
154 d->clearV8RootEvent();
155 d->v8MeasuredTime = 0;
158 d->traceStartTime = -1;
159 d->qmlMeasuredTime = 0;
161 d->lastFrameEvent = 0;
166 QString QmlProfilerData::getHashStringForQmlEvent(const QmlEventLocation &location, int eventType)
168 return QString(QStringLiteral("%1:%2:%3:%4")).arg(
170 QString::number(location.line),
171 QString::number(location.column),
172 QString::number(eventType));
175 QString QmlProfilerData::getHashStringForV8Event(const QString &displayName, const QString &function)
177 return QString(QStringLiteral("%1:%2")).arg(displayName, function);
180 QString QmlProfilerData::qmlRangeTypeAsString(QQmlProfilerService::RangeType typeEnum)
183 case QQmlProfilerService::Painting:
184 return QLatin1String(Constants::TYPE_PAINTING_STR);
186 case QQmlProfilerService::Compiling:
187 return QLatin1String(Constants::TYPE_COMPILING_STR);
189 case QQmlProfilerService::Creating:
190 return QLatin1String(Constants::TYPE_CREATING_STR);
192 case QQmlProfilerService::Binding:
193 return QLatin1String(Constants::TYPE_BINDING_STR);
195 case QQmlProfilerService::HandlingSignal:
196 return QLatin1String(Constants::TYPE_HANDLINGSIGNAL_STR);
199 return QString::number((int)typeEnum);
203 void QmlProfilerData::setTraceStartTime(qint64 time)
205 d->traceStartTime = time;
208 void QmlProfilerData::setTraceEndTime(qint64 time)
210 d->traceEndTime = time;
213 qint64 QmlProfilerData::traceStartTime() const
215 return d->traceStartTime;
218 qint64 QmlProfilerData::traceEndTime() const
220 return d->traceEndTime;
223 void QmlProfilerData::addQmlEvent(QQmlProfilerService::RangeType type,
226 const QStringList &data,
227 const QmlEventLocation &location)
229 setState(AcquiringData);
232 // generate details string
234 details = tr("Source code not available");
236 details = data.join(QStringLiteral(" ")).replace(
237 QLatin1Char('\n'),QStringLiteral(" ")).simplified();
238 QRegExp rewrite(QStringLiteral("\\(function \\$(\\w+)\\(\\) \\{ (return |)(.+) \\}\\)"));
239 bool match = rewrite.exactMatch(details);
241 details = rewrite.cap(1) +QLatin1String(": ") + rewrite.cap(3);
243 if (details.startsWith(QLatin1String("file://")))
244 details = details.mid(details.lastIndexOf(QLatin1Char('/')) + 1);
247 QmlEventLocation eventLocation = location;
248 QString displayName, eventHashStr;
250 if (eventLocation.filename.isEmpty()) {
251 displayName = tr("<bytecode>");
252 eventHashStr = getHashStringForQmlEvent(eventLocation, type);
254 const QString filePath = QUrl(eventLocation.filename).path();
255 displayName = filePath.mid(
256 filePath.lastIndexOf(QLatin1Char('/')) + 1) +
257 QLatin1Char(':') + QString::number(eventLocation.line);
258 eventHashStr = getHashStringForQmlEvent(eventLocation, type);
261 QmlRangeEventData *newEvent;
262 if (d->eventDescriptions.contains(eventHashStr)) {
263 newEvent = d->eventDescriptions[eventHashStr];
265 newEvent = new QmlRangeEventData(displayName, eventHashStr, location, details, type);
266 d->eventDescriptions.insert(eventHashStr, newEvent);
269 QmlRangeEventStartInstance rangeEventStartInstance(startTime, duration, 1e9/duration, -1, newEvent);
271 d->startInstanceList.append(rangeEventStartInstance);
274 void QmlProfilerData::addFrameEvent(qint64 time, int framerate, int animationcount)
276 setState(AcquiringData);
278 QString details = tr("Animation Timer Update");
279 QString displayName = tr("<Animation Update>");
280 QString eventHashStr = displayName;
282 QmlRangeEventData *newEvent;
283 if (d->eventDescriptions.contains(eventHashStr)) {
284 newEvent = d->eventDescriptions[eventHashStr];
286 newEvent = new QmlRangeEventData(displayName, eventHashStr, QmlEventLocation(), details, QQmlProfilerService::Painting);
287 d->eventDescriptions.insert(eventHashStr, newEvent);
290 qint64 duration = 1e9/framerate;
292 if (d->lastFrameEvent &&
293 d->lastFrameEvent->startTime + d->lastFrameEvent->duration >= time) {
294 d->lastFrameEvent->duration = time - 1 - d->lastFrameEvent->startTime;
297 QmlRangeEventStartInstance rangeEventStartInstance(time, duration, framerate, animationcount, newEvent);
299 d->startInstanceList.append(rangeEventStartInstance);
301 d->lastFrameEvent = &d->startInstanceList.last();
304 QString QmlProfilerData::rootEventName()
306 return tr("<program>");
309 QString QmlProfilerData::rootEventDescription()
311 return tr("Main Program");
314 void QmlProfilerDataPrivate::clearV8RootEvent()
316 v8RootEvent.displayName = QmlProfilerData::rootEventName();
317 v8RootEvent.eventHashStr = QmlProfilerData::rootEventName();
318 v8RootEvent.functionName = QmlProfilerData::rootEventDescription();
319 v8RootEvent.line = -1;
320 v8RootEvent.totalTime = 0;
321 v8RootEvent.selfTime = 0;
322 v8RootEvent.v8children.clear();
325 void QmlProfilerData::addV8Event(int depth, const QString &function, const QString &filename,
326 int lineNumber, double totalTime, double selfTime)
328 QString displayName = filename.mid(filename.lastIndexOf(QLatin1Char('/')) + 1) +
329 QLatin1Char(':') + QString::number(lineNumber);
330 QString hashStr = getHashStringForV8Event(displayName, function);
332 setState(AcquiringData);
334 // time is given in milliseconds, but internally we store it in microseconds
338 // accumulate information
339 QV8EventInfo *eventData = d->v8EventHash[hashStr];
341 eventData = new QV8EventInfo;
342 eventData->displayName = displayName;
343 eventData->eventHashStr = hashStr;
344 eventData->fileName = filename;
345 eventData->functionName = function;
346 eventData->line = lineNumber;
347 eventData->totalTime = totalTime;
348 eventData->selfTime = selfTime;
349 d->v8EventHash[hashStr] = eventData;
351 eventData->totalTime += totalTime;
352 eventData->selfTime += selfTime;
354 d->v8parents[depth] = eventData;
356 QV8EventInfo *parentEvent = 0;
358 parentEvent = &d->v8RootEvent;
359 d->v8MeasuredTime += totalTime;
361 if (depth > 0 && d->v8parents.contains(depth-1)) {
362 parentEvent = d->v8parents.value(depth-1);
365 if (parentEvent != 0) {
366 if (!parentEvent->v8children.contains(eventData->eventHashStr)) {
367 parentEvent->v8children[eventData->eventHashStr] = totalTime;
369 parentEvent->v8children[eventData->eventHashStr] += totalTime;
374 void QmlProfilerData::computeQmlTime()
377 QHash<int, qint64> endtimesPerLevel;
378 int minimumLevel = 1;
379 int level = minimumLevel;
381 for (int i = 0; i < d->startInstanceList.count(); i++) {
382 qint64 st = d->startInstanceList[i].startTime;
383 int type = d->startInstanceList[i].data->eventType;
385 if (type == QQmlProfilerService::Painting) {
390 if (endtimesPerLevel[level] > st) {
393 while (level > minimumLevel && endtimesPerLevel[level-1] <= st)
396 endtimesPerLevel[level] = st + d->startInstanceList[i].duration;
398 if (level == minimumLevel) {
399 d->qmlMeasuredTime += d->startInstanceList[i].duration;
404 bool compareStartTimes(const QmlRangeEventStartInstance &t1, const QmlRangeEventStartInstance &t2)
406 return t1.startTime < t2.startTime;
409 void QmlProfilerData::sortStartTimes()
411 if (d->startInstanceList.count() < 2)
414 // assuming startTimes is partially sorted
415 // identify blocks of events and sort them with quicksort
416 QVector<QmlRangeEventStartInstance>::iterator itFrom = d->startInstanceList.end() - 2;
417 QVector<QmlRangeEventStartInstance>::iterator itTo = d->startInstanceList.end() - 1;
419 while (itFrom != d->startInstanceList.begin() && itTo != d->startInstanceList.begin()) {
420 // find block to sort
421 while ( itFrom != d->startInstanceList.begin()
422 && itTo->startTime > itFrom->startTime ) {
427 // if we're at the end of the list
428 if (itFrom == d->startInstanceList.begin())
432 while ( itFrom != d->startInstanceList.begin()
433 && itTo->startTime <= itFrom->startTime )
436 if (itTo->startTime <= itFrom->startTime)
437 qSort(itFrom, itTo + 1, compareStartTimes);
439 qSort(itFrom + 1, itTo + 1, compareStartTimes);
441 // move to next block
447 void QmlProfilerData::complete()
449 setState(ProcessingData);
456 bool QmlProfilerData::isEmpty() const
458 return d->startInstanceList.isEmpty() && d->v8EventHash.isEmpty();
461 bool QmlProfilerData::save(const QString &filename)
464 emit error(tr("No data to save"));
468 QFile file(filename);
469 if (!file.open(QIODevice::WriteOnly)) {
470 emit error(tr("Could not open %1 for writing").arg(filename));
474 QXmlStreamWriter stream(&file);
475 stream.setAutoFormatting(true);
476 stream.writeStartDocument();
478 stream.writeStartElement(QStringLiteral("trace"));
479 stream.writeAttribute(QStringLiteral("version"), QLatin1String(Constants::PROFILER_FILE_VERSION));
481 stream.writeAttribute(QStringLiteral("traceStart"), QString::number(traceStartTime()));
482 stream.writeAttribute(QStringLiteral("traceEnd"), QString::number(traceEndTime()));
484 stream.writeStartElement(QStringLiteral("eventData"));
485 stream.writeAttribute(QStringLiteral("totalTime"), QString::number(d->qmlMeasuredTime));
487 foreach (const QmlRangeEventData *eventData, d->eventDescriptions.values()) {
488 stream.writeStartElement(QStringLiteral("event"));
489 stream.writeAttribute(QStringLiteral("index"), QString::number(d->eventDescriptions.keys().indexOf(eventData->eventHashStr)));
490 stream.writeTextElement(QStringLiteral("displayname"), eventData->displayName);
491 stream.writeTextElement(QStringLiteral("type"), qmlRangeTypeAsString(eventData->eventType));
492 if (!eventData->location.filename.isEmpty()) {
493 stream.writeTextElement(QStringLiteral("filename"), eventData->location.filename);
494 stream.writeTextElement(QStringLiteral("line"), QString::number(eventData->location.line));
495 stream.writeTextElement(QStringLiteral("column"), QString::number(eventData->location.column));
497 stream.writeTextElement(QStringLiteral("details"), eventData->details);
498 stream.writeEndElement();
500 stream.writeEndElement(); // eventData
502 stream.writeStartElement(QStringLiteral("profilerDataModel"));
503 foreach (const QmlRangeEventStartInstance &rangedEvent, d->startInstanceList) {
504 stream.writeStartElement(QStringLiteral("range"));
505 stream.writeAttribute(QStringLiteral("startTime"), QString::number(rangedEvent.startTime));
506 stream.writeAttribute(QStringLiteral("duration"), QString::number(rangedEvent.duration));
507 stream.writeAttribute(QStringLiteral("eventIndex"), QString::number(d->eventDescriptions.keys().indexOf(rangedEvent.data->eventHashStr)));
508 if (rangedEvent.data->eventType == QQmlProfilerService::Painting && rangedEvent.animationCount >= 0) {
510 stream.writeAttribute(QStringLiteral("framerate"), QString::number(rangedEvent.frameRate));
511 stream.writeAttribute(QStringLiteral("animationcount"), QString::number(rangedEvent.animationCount));
513 stream.writeEndElement();
515 stream.writeEndElement(); // profilerDataModel
517 stream.writeStartElement(QStringLiteral("v8profile")); // v8 profiler output
518 stream.writeAttribute(QStringLiteral("totalTime"), QString::number(d->v8MeasuredTime));
519 foreach (QV8EventInfo *v8event, d->v8EventHash.values()) {
520 stream.writeStartElement(QStringLiteral("event"));
521 stream.writeAttribute(QStringLiteral("index"), QString::number(d->v8EventHash.keys().indexOf(v8event->eventHashStr)));
522 stream.writeTextElement(QStringLiteral("displayname"), v8event->displayName);
523 stream.writeTextElement(QStringLiteral("functionname"), v8event->functionName);
524 if (!v8event->fileName.isEmpty()) {
525 stream.writeTextElement(QStringLiteral("filename"), v8event->fileName);
526 stream.writeTextElement(QStringLiteral("line"), QString::number(v8event->line));
528 stream.writeTextElement(QStringLiteral("totalTime"), QString::number(v8event->totalTime));
529 stream.writeTextElement(QStringLiteral("selfTime"), QString::number(v8event->selfTime));
530 if (!v8event->v8children.isEmpty()) {
531 stream.writeStartElement(QStringLiteral("childrenEvents"));
532 QStringList childrenIndexes;
533 QStringList childrenTimes;
534 foreach (const QString &childHash, v8event->v8children.keys()) {
535 childrenIndexes << QString::number(v8EventIndex(childHash));
536 childrenTimes << QString::number(v8event->v8children[childHash]);
539 stream.writeAttribute(QStringLiteral("list"), childrenIndexes.join(QString(", ")));
540 stream.writeAttribute(QStringLiteral("childrenTimes"), childrenTimes.join(QString(", ")));
541 stream.writeEndElement();
543 stream.writeEndElement();
545 stream.writeEndElement(); // v8 profiler output
547 stream.writeEndElement(); // trace
548 stream.writeEndDocument();
554 int QmlProfilerData::v8EventIndex(const QString &hashStr)
556 if (!d->v8EventHash.contains(hashStr)) {
557 emit error("Trying to index nonexisting v8 event");
560 return d->v8EventHash.keys().indexOf( hashStr );
563 void QmlProfilerData::setState(QmlProfilerData::State state)
565 // It's not an error, we are continuously calling "AcquiringData" for example
566 if (d->state == state)
571 // if it's not empty, complain but go on
573 emit error("Invalid qmlprofiler state change (Empty)");
576 // we're not supposed to receive new data while processing older data
577 if (d->state == ProcessingData)
578 emit error("Invalid qmlprofiler state change (AcquiringData)");
581 if (d->state != AcquiringData)
582 emit error("Invalid qmlprofiler state change (ProcessingData)");
585 if (d->state != ProcessingData && d->state != Empty)
586 emit error("Invalid qmlprofiler state change (Done)");
589 emit error("Trying to set unknown state in events list");
596 // special: if we were done with an empty list, clean internal data and go back to empty
597 if (d->state == Done && isEmpty()) {