Merge master <-> api_changes
[profile/ivi/qtdeclarative.git] / tools / qmlprofiler / profiledata.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
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.
16 **
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.
20 **
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.
28 **
29 ** Other Usage
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.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "profiledata.h"
43 #include "constants.h"
44
45 #include <QtCore/QStringList>
46 #include <QtCore/QRegExp>
47 #include <QtCore/QUrl>
48 #include <QtCore/QFile>
49 #include <QtCore/QXmlStreamReader>
50
51 using namespace Constants;
52
53 QmlEvent::QmlEvent()
54 {
55     eventType = QQmlProfilerService::MaximumRangeType;
56     eventId = -1;
57     duration = 0;
58     calls = 0;
59     minTime = 0;
60     maxTime = 0;
61     timePerCall = 0;
62     percentOfTime = 0;
63     medianTime = 0;
64     isBindingLoop = false;
65 }
66
67 QmlEvent::~QmlEvent()
68 {
69     qDeleteAll(parentHash.values());
70     parentHash.clear();
71     qDeleteAll(childrenHash.values());
72     childrenHash.clear();
73 }
74
75 QmlEvent &QmlEvent::operator=(const QmlEvent &ref)
76 {
77     if (this == &ref)
78         return *this;
79
80     displayname = ref.displayname;
81     location = ref.location;
82     eventHashStr = ref.eventHashStr;
83     details = ref.details;
84     eventType = ref.eventType;
85     duration = ref.duration;
86     calls = ref.calls;
87     minTime = ref.minTime;
88     maxTime = ref.maxTime;
89     timePerCall = ref.timePerCall;
90     percentOfTime = ref.percentOfTime;
91     medianTime = ref.medianTime;
92     eventId = ref.eventId;
93     isBindingLoop = ref.isBindingLoop;
94
95     qDeleteAll(parentHash.values());
96     parentHash.clear();
97     foreach (const QString &key, ref.parentHash.keys()) {
98         parentHash.insert(key,
99                           new QmlEventSub(ref.parentHash.value(key)));
100     }
101
102     qDeleteAll(childrenHash.values());
103     childrenHash.clear();
104     foreach (const QString &key, ref.childrenHash.keys()) {
105         childrenHash.insert(key,
106                             new QmlEventSub(ref.childrenHash.value(key)));
107     }
108
109     return *this;
110 }
111
112 V8Event::V8Event()
113 {
114     line = -1;
115     eventId = -1;
116     totalTime = 0;
117     selfTime = 0;
118     totalPercent = 0;
119     selfPercent = 0;
120 }
121
122 V8Event::~V8Event()
123 {
124     qDeleteAll(parentHash.values());
125     parentHash.clear();
126     qDeleteAll(childrenHash.values());
127     childrenHash.clear();
128 }
129
130 V8Event &V8Event::operator=(const V8Event &ref)
131 {
132     if (this == &ref)
133         return *this;
134
135     displayName = ref.displayName;
136     filename = ref.filename;
137     functionName = ref.functionName;
138     line = ref.line;
139     totalTime = ref.totalTime;
140     totalPercent = ref.totalPercent;
141     selfTime = ref.selfTime;
142     selfPercent = ref.selfPercent;
143     eventId = ref.eventId;
144
145     qDeleteAll(parentHash.values());
146     parentHash.clear();
147     foreach (const QString &key, ref.parentHash.keys()) {
148         parentHash.insert(key, new V8EventSub(ref.parentHash.value(key)));
149     }
150
151     qDeleteAll(childrenHash.values());
152     childrenHash.clear();
153     foreach (const QString &key, ref.childrenHash.keys()) {
154         childrenHash.insert(key, new V8EventSub(ref.childrenHash.value(key)));
155     }
156     return *this;
157 }
158
159 // endtimedata
160 struct QmlEventEndTime {
161     qint64 endTime;
162     int startTimeIndex;
163     QmlEvent *description;
164 };
165
166 // starttimedata
167 struct QmlEventStartTime{
168     qint64 startTime;
169     qint64 length;
170     qint64 level;
171     int endTimeIndex;
172     qint64 nestingLevel;
173     qint64 nestingDepth;
174     QmlEvent *description;
175
176     // animation-related data
177     int frameRate;
178     int animationCount;
179
180     int bindingLoopHead;
181 };
182
183 struct QmlEventTypeCount {
184     QList <int> eventIds;
185     int nestingCount;
186 };
187
188 // used by quicksort
189 bool compareEndTimes(const QmlEventEndTime &t1,
190                      const QmlEventEndTime &t2)
191 {
192     return t1.endTime < t2.endTime;
193 }
194
195 bool compareStartTimes(const QmlEventStartTime &t1,
196                        const QmlEventStartTime &t2)
197 {
198     return t1.startTime < t2.startTime;
199 }
200
201 bool compareStartIndexes(const QmlEventEndTime &t1,
202                          const QmlEventEndTime &t2)
203 {
204     return t1.startTimeIndex < t2.startTimeIndex;
205 }
206
207 QString qmlEventType(QQmlProfilerService::RangeType typeEnum)
208 {
209     switch (typeEnum) {
210     case QQmlProfilerService::Painting:
211         return QLatin1String(TYPE_PAINTING_STR);
212         break;
213     case QQmlProfilerService::Compiling:
214         return QLatin1String(TYPE_COMPILING_STR);
215         break;
216     case QQmlProfilerService::Creating:
217         return QLatin1String(TYPE_CREATING_STR);
218         break;
219     case QQmlProfilerService::Binding:
220         return QLatin1String(TYPE_BINDING_STR);
221         break;
222     case QQmlProfilerService::HandlingSignal:
223         return QLatin1String(TYPE_HANDLINGSIGNAL_STR);
224         break;
225     default:
226         return QString::number((int)typeEnum);
227     }
228 }
229
230 QQmlProfilerService::RangeType qmlEventType(const QString &typeString)
231 {
232     if (typeString == QLatin1String(TYPE_PAINTING_STR)) {
233         return QQmlProfilerService::Painting;
234     } else if (typeString == QLatin1String(TYPE_COMPILING_STR)) {
235         return QQmlProfilerService::Compiling;
236     } else if (typeString == QLatin1String(TYPE_CREATING_STR)) {
237         return QQmlProfilerService::Creating;
238     } else if (typeString == QLatin1String(TYPE_BINDING_STR)) {
239         return QQmlProfilerService::Binding;
240     } else if (typeString == QLatin1String(TYPE_HANDLINGSIGNAL_STR)) {
241         return QQmlProfilerService::HandlingSignal;
242     } else {
243         bool isNumber = false;
244         int type = typeString.toUInt(&isNumber);
245         if (isNumber) {
246             return (QQmlProfilerService::RangeType)type;
247         } else {
248             return QQmlProfilerService::MaximumRangeType;
249         }
250     }
251 }
252
253 QString getHashStringForQmlEvent(
254         EventLocation location,
255         QQmlProfilerService::RangeType eventType)
256 {
257     return QString("%1:%2:%3:%4").arg(location.filename,
258                                       QString::number(location.line),
259                                       QString::number(location.column),
260                                       QString::number(eventType));
261 }
262
263 class ProfileDataPrivate
264 {
265 public:
266
267     // convenience functions
268     void clearQmlRootEvent();
269     void clearV8RootEvent();
270
271     // Stored data
272     QmlEventHash m_eventDescriptions;
273     QList<QmlEventEndTime> m_endTimeSortedList;
274     QList<QmlEventStartTime> m_startTimeSortedList;
275
276     void collectV8Statistics();
277     V8Events m_v8EventList;
278     QHash<int, V8Event *> m_v8parents;
279
280     QmlEvent m_qmlRootEvent;
281     V8Event m_v8RootEvent;
282     QString m_rootEventName;
283     QString m_rootEventDesc;
284
285     QHash<int, QmlEventTypeCount *> m_typeCounts;
286
287     qint64 m_traceEndTime;
288     qint64 m_traceStartTime;
289     qint64 m_qmlMeasuredTime;
290     qint64 m_v8MeasuredTime;
291
292     QmlEventStartTime *m_lastFrameEvent;
293     qint64 m_maximumAnimationCount;
294     qint64 m_minimumAnimationCount;
295
296     // file to load
297     QString m_filename;
298 };
299
300 ProfileData::ProfileData(QObject *parent) :
301     QObject(parent),
302     d(new ProfileDataPrivate)
303 {
304     setObjectName("ProfileData");
305
306     d->m_traceEndTime = 0;
307     d->m_traceStartTime = -1;
308     d->m_qmlMeasuredTime = 0;
309     d->m_v8MeasuredTime = 0;
310     d->m_rootEventName = tr("<program>");
311     d->m_rootEventDesc = tr("Main Program");
312     d->clearQmlRootEvent();
313     d->clearV8RootEvent();
314     d->m_lastFrameEvent = 0;
315     d->m_maximumAnimationCount = 0;
316     d->m_minimumAnimationCount = 0;
317 }
318
319 ProfileData::~ProfileData()
320 {
321     clear();
322 }
323
324 void ProfileData::clear()
325 {
326     qDeleteAll(d->m_eventDescriptions.values());
327     d->m_eventDescriptions.clear();
328
329     qDeleteAll(d->m_v8EventList);
330     d->m_v8EventList.clear();
331
332     d->m_endTimeSortedList.clear();
333     d->m_startTimeSortedList.clear();
334
335     d->m_v8parents.clear();
336
337     d->clearQmlRootEvent();
338     d->clearV8RootEvent();
339
340     foreach (QmlEventTypeCount *typeCount, d->m_typeCounts.values())
341         delete typeCount;
342     d->m_typeCounts.clear();
343
344     d->m_traceEndTime = 0;
345     d->m_traceStartTime = -1;
346     d->m_qmlMeasuredTime = 0;
347     d->m_v8MeasuredTime = 0;
348
349     d->m_lastFrameEvent = 0;
350     d->m_maximumAnimationCount = 0;
351     d->m_minimumAnimationCount = 0;
352
353     emit countChanged();
354     emit dataClear();
355 }
356
357 QmlEvents ProfileData::getQmlEvents() const
358 {
359     return d->m_eventDescriptions.values();
360 }
361
362 QmlEvent *ProfileData::qmlEvent(int eventId) const
363 {
364     foreach (QmlEvent *event, d->m_eventDescriptions.values()) {
365         if (event->eventId == eventId)
366             return event;
367     }
368     return 0;
369 }
370
371 V8Event *ProfileData::v8Event(int eventId) const
372 {
373     foreach (V8Event *event, d->m_v8EventList) {
374         if (event->eventId == eventId)
375             return event;
376     }
377     return 0;
378 }
379
380 const V8Events& ProfileData::getV8Events() const
381 {
382     return d->m_v8EventList;
383 }
384
385 void ProfileData::addQmlEvent(
386         QQmlProfilerService::RangeType type, qint64 startTime, qint64 length,
387         const QStringList &data, const EventLocation &location)
388 {
389     const QChar colon = QLatin1Char(':');
390     QString displayName, eventHashStr, details;
391     EventLocation eventLocation = location;
392
393     emit processingData();
394
395     // generate details string
396     if (data.isEmpty())
397         details = tr("Source code not available");
398     else {
399         details = data.join(" ").replace('\n'," ").simplified();
400         QRegExp rewrite("\\(function \\$(\\w+)\\(\\) \\{ (return |)(.+) \\}\\)");
401         bool match = rewrite.exactMatch(details);
402         if (match) {
403             details = rewrite.cap(1) + ": " + rewrite.cap(3);
404         }
405         if (details.startsWith(QString("file://")))
406             details = details.mid(details.lastIndexOf(QChar('/')) + 1);
407     }
408
409     // backwards compatibility: "compiling" events don't have a proper location in older
410     // version of the protocol, but the filename is passed in the details string
411     if (type == QQmlProfilerService::Compiling && eventLocation.filename.isEmpty()) {
412         eventLocation.filename = details;
413         eventLocation.line = 1;
414         eventLocation.column = 1;
415     }
416
417     // generate hash
418     if (eventLocation.filename.isEmpty()) {
419         displayName = tr("<bytecode>");
420         eventHashStr = getHashStringForQmlEvent(eventLocation, type);
421     } else {
422         const QString filePath = QUrl(eventLocation.filename).path();
423         displayName = filePath.mid(filePath.lastIndexOf(QChar('/')) + 1) + colon + QString::number(eventLocation.line);
424         eventHashStr = getHashStringForQmlEvent(eventLocation, type);
425     }
426
427     QmlEvent *newEvent;
428     if (d->m_eventDescriptions.contains(eventHashStr)) {
429         newEvent = d->m_eventDescriptions[eventHashStr];
430     } else {
431         newEvent = new QmlEvent;
432         newEvent->displayname = displayName;
433         newEvent->location = eventLocation;
434         newEvent->eventHashStr = eventHashStr;
435         newEvent->eventType = type;
436         newEvent->details = details;
437         d->m_eventDescriptions.insert(eventHashStr, newEvent);
438     }
439
440     QmlEventEndTime endTimeData;
441     endTimeData.endTime = startTime + length;
442     endTimeData.description = newEvent;
443     endTimeData.startTimeIndex = d->m_startTimeSortedList.count();
444
445     QmlEventStartTime startTimeData;
446     startTimeData.startTime = startTime;
447     startTimeData.length = length;
448     startTimeData.description = newEvent;
449     startTimeData.endTimeIndex = d->m_endTimeSortedList.count();
450     startTimeData.animationCount = -1;
451     startTimeData.frameRate = 1e9/length;
452
453     d->m_endTimeSortedList << endTimeData;
454     d->m_startTimeSortedList << startTimeData;
455
456     emit countChanged();
457 }
458
459 void ProfileData::addV8Event(int depth, const QString &function,
460                              const QString &filename, int lineNumber,
461                              double totalTime, double selfTime)
462 {
463     QString displayName = filename.mid(
464                 filename.lastIndexOf(QLatin1Char('/')) + 1) + QLatin1Char(':') +
465                 QString::number(lineNumber);
466     V8Event *eventData = 0;
467
468     // time is given in milliseconds, but internally we store it in microseconds
469     totalTime *= 1e6;
470     selfTime *= 1e6;
471
472     // cumulate information
473     foreach (V8Event *v8event, d->m_v8EventList) {
474         if (v8event->displayName == displayName &&
475                 v8event->functionName == function) {
476             eventData = v8event;
477             break;
478         }
479     }
480
481     if (!eventData) {
482         eventData = new V8Event;
483         eventData->displayName = displayName;
484         eventData->filename = filename;
485         eventData->functionName = function;
486         eventData->line = lineNumber;
487         eventData->totalTime = totalTime;
488         eventData->selfTime = selfTime;
489         d->m_v8EventList << eventData;
490     } else {
491         eventData->totalTime += totalTime;
492         eventData->selfTime += selfTime;
493     }
494     d->m_v8parents[depth] = eventData;
495
496     V8Event *parentEvent = 0;
497     if (depth == 0) {
498         parentEvent = &d->m_v8RootEvent;
499         d->m_v8MeasuredTime += totalTime;
500     }
501     if (depth > 0 && d->m_v8parents.contains(depth-1)) {
502         parentEvent = d->m_v8parents.value(depth-1);
503     }
504
505     if (parentEvent != 0) {
506         if (!eventData->parentHash.contains(parentEvent->displayName)) {
507             V8EventSub *newParentSub = new V8EventSub(parentEvent);
508             newParentSub->totalTime = totalTime;
509
510             eventData->parentHash.insert(parentEvent->displayName, newParentSub );
511         } else {
512             V8EventSub *newParentSub =
513                     eventData->parentHash.value(parentEvent->displayName);
514             newParentSub->totalTime += totalTime;
515         }
516
517         if (!parentEvent->childrenHash.contains(eventData->displayName)) {
518             V8EventSub *newChildSub = new V8EventSub(eventData);
519             newChildSub->totalTime = totalTime;
520
521             parentEvent->childrenHash.insert(eventData->displayName, newChildSub);
522         } else {
523             V8EventSub *newChildSub =
524                     parentEvent->childrenHash.value(eventData->displayName);
525             newChildSub->totalTime += totalTime;
526         }
527     }
528 }
529
530 void ProfileData::addFrameEvent(qint64 time, int framerate, int animationcount)
531 {
532     QString displayName, eventHashStr, details;
533
534     emit processingData();
535
536     details = tr("Animation Timer Update");
537     displayName = tr("<Animation Update>");
538     eventHashStr = displayName;
539
540     QmlEvent *newEvent;
541     if (d->m_eventDescriptions.contains(eventHashStr)) {
542         newEvent = d->m_eventDescriptions[eventHashStr];
543     } else {
544         newEvent = new QmlEvent;
545         newEvent->displayname = displayName;
546         newEvent->eventHashStr = eventHashStr;
547         newEvent->eventType = QQmlProfilerService::Painting;
548         newEvent->details = details;
549         d->m_eventDescriptions.insert(eventHashStr, newEvent);
550     }
551
552     qint64 length = 1e9/framerate;
553     // avoid overlap
554     if (d->m_lastFrameEvent &&
555             d->m_lastFrameEvent->startTime + d->m_lastFrameEvent->length >= time) {
556         d->m_lastFrameEvent->length = time - 1 - d->m_lastFrameEvent->startTime;
557         d->m_endTimeSortedList[d->m_lastFrameEvent->endTimeIndex].endTime =
558                 d->m_lastFrameEvent->startTime + d->m_lastFrameEvent->length;
559     }
560
561     QmlEventEndTime endTimeData;
562     endTimeData.endTime = time + length;
563     endTimeData.description = newEvent;
564     endTimeData.startTimeIndex = d->m_startTimeSortedList.count();
565
566     QmlEventStartTime startTimeData;
567     startTimeData.startTime = time;
568     startTimeData.length = length;
569     startTimeData.description = newEvent;
570     startTimeData.endTimeIndex = d->m_endTimeSortedList.count();
571     startTimeData.animationCount = animationcount;
572     startTimeData.frameRate = framerate;
573
574     d->m_endTimeSortedList << endTimeData;
575     d->m_startTimeSortedList << startTimeData;
576
577     d->m_lastFrameEvent = &d->m_startTimeSortedList.last();
578
579     emit countChanged();
580 }
581
582 void ProfileDataPrivate::collectV8Statistics()
583 {
584     if (!m_v8EventList.isEmpty()) {
585         double totalTimes = m_v8MeasuredTime;
586         double selfTimes = 0;
587         foreach (V8Event *v8event, m_v8EventList) {
588             selfTimes += v8event->selfTime;
589         }
590
591         // prevent divisions by 0
592         if (totalTimes == 0)
593             totalTimes = 1;
594         if (selfTimes == 0)
595             selfTimes = 1;
596
597         // insert root event in eventlist
598         // the +1 ns is to get it on top of the sorted list
599         m_v8RootEvent.totalTime = m_v8MeasuredTime + 1;
600         m_v8RootEvent.selfTime = 0;
601
602         int rootEventIndex = -1;
603         for (int ndx = 0; ndx < m_v8EventList.count(); ndx++)
604         {
605             if (m_v8EventList.at(ndx)->displayName == m_rootEventName) {
606                 m_v8RootEvent = *m_v8EventList.at(ndx);
607                 rootEventIndex = ndx;
608                 break;
609             }
610         }
611         if (rootEventIndex == -1) {
612             rootEventIndex = m_v8EventList.count();
613             V8Event *newRootEvent = new V8Event;
614             *newRootEvent = m_v8RootEvent;
615             m_v8EventList << newRootEvent;
616         }
617
618         foreach (V8Event *v8event, m_v8EventList) {
619             v8event->totalPercent = v8event->totalTime * 100.0 / totalTimes;
620             v8event->selfPercent = v8event->selfTime * 100.0 / selfTimes;
621         }
622
623         int index = 0;
624         foreach (V8Event *v8event, m_v8EventList) {
625             v8event->eventId = index++;
626         }
627         m_v8RootEvent.eventId = m_v8EventList[rootEventIndex]->eventId;
628     }
629 }
630
631 void ProfileData::setTraceEndTime( qint64 time )
632 {
633     d->m_traceEndTime = time;
634 }
635
636 void ProfileData::setTraceStartTime( qint64 time )
637 {
638     d->m_traceStartTime = time;
639 }
640
641 void ProfileData::complete()
642 {
643     emit postProcessing();
644     d->collectV8Statistics();
645     postProcess();
646 }
647
648 void ProfileDataPrivate::clearQmlRootEvent()
649 {
650     m_qmlRootEvent.displayname = m_rootEventName;
651     m_qmlRootEvent.location = EventLocation();
652     m_qmlRootEvent.eventHashStr = m_rootEventName;
653     m_qmlRootEvent.details = m_rootEventDesc;
654     m_qmlRootEvent.eventType = QQmlProfilerService::Binding;
655     m_qmlRootEvent.duration = 0;
656     m_qmlRootEvent.calls = 0;
657     m_qmlRootEvent.minTime = 0;
658     m_qmlRootEvent.maxTime = 0;
659     m_qmlRootEvent.timePerCall = 0;
660     m_qmlRootEvent.percentOfTime = 0;
661     m_qmlRootEvent.medianTime = 0;
662     m_qmlRootEvent.eventId = -1;
663
664     qDeleteAll(m_qmlRootEvent.parentHash.values());
665     qDeleteAll(m_qmlRootEvent.childrenHash.values());
666     m_qmlRootEvent.parentHash.clear();
667     m_qmlRootEvent.childrenHash.clear();
668 }
669
670 void ProfileDataPrivate::clearV8RootEvent()
671 {
672     m_v8RootEvent.displayName = m_rootEventName;
673     m_v8RootEvent.functionName = m_rootEventDesc;
674     m_v8RootEvent.line = -1;
675     m_v8RootEvent.totalTime = 0;
676     m_v8RootEvent.totalPercent = 0;
677     m_v8RootEvent.selfTime = 0;
678     m_v8RootEvent.selfPercent = 0;
679     m_v8RootEvent.eventId = -1;
680
681     qDeleteAll(m_v8RootEvent.parentHash.values());
682     qDeleteAll(m_v8RootEvent.childrenHash.values());
683     m_v8RootEvent.parentHash.clear();
684     m_v8RootEvent.childrenHash.clear();
685 }
686
687 void ProfileData::compileStatistics(qint64 startTime, qint64 endTime)
688 {
689     int index;
690     int fromIndex = findFirstIndex(startTime);
691     int toIndex = findLastIndex(endTime);
692     double totalTime = 0;
693
694     // clear existing statistics
695     foreach (QmlEvent *eventDescription,
696              d->m_eventDescriptions.values()) {
697         eventDescription->calls = 0;
698         // maximum possible value
699         eventDescription->minTime = d->m_endTimeSortedList.last().endTime;
700         eventDescription->maxTime = 0;
701         eventDescription->medianTime = 0;
702         eventDescription->duration = 0;
703         qDeleteAll(eventDescription->parentHash);
704         qDeleteAll(eventDescription->childrenHash);
705         eventDescription->parentHash.clear();
706         eventDescription->childrenHash.clear();
707     }
708
709     // create root event for statistics
710     d->clearQmlRootEvent();
711
712     // compute parent-child relationship and call count
713     QHash<int, QmlEvent*> lastParent;
714     for (index = fromIndex; index <= toIndex; index++) {
715         QmlEvent *eventDescription =
716                 d->m_startTimeSortedList[index].description;
717
718         if (d->m_startTimeSortedList[index].startTime > endTime ||
719                 d->m_startTimeSortedList[index].startTime +
720                 d->m_startTimeSortedList[index].length < startTime) {
721             continue;
722         }
723
724         if (eventDescription->eventType == QQmlProfilerService::Painting) {
725             // skip animation/paint events
726             continue;
727         }
728
729         eventDescription->calls++;
730         qint64 duration = d->m_startTimeSortedList[index].length;
731         eventDescription->duration += duration;
732         if (eventDescription->maxTime < duration)
733             eventDescription->maxTime = duration;
734         if (eventDescription->minTime > duration)
735             eventDescription->minTime = duration;
736
737         int level = d->m_startTimeSortedList[index].level;
738
739         QmlEvent *parentEvent = &d->m_qmlRootEvent;
740         if (level > MIN_LEVEL && lastParent.contains(level-1)) {
741             parentEvent = lastParent[level-1];
742         }
743
744         if (!eventDescription->parentHash.contains(parentEvent->eventHashStr)) {
745             QmlEventSub *newParentEvent =
746                     new QmlEventSub(parentEvent);
747             newParentEvent->calls = 1;
748             newParentEvent->duration = duration;
749
750             eventDescription->parentHash.insert(parentEvent->eventHashStr,
751                                                 newParentEvent);
752         } else {
753             QmlEventSub *newParentEvent =
754                     eventDescription->parentHash.value(parentEvent->eventHashStr);
755             newParentEvent->duration += duration;
756             newParentEvent->calls++;
757         }
758
759         if (!parentEvent->childrenHash.contains(eventDescription->eventHashStr)) {
760             QmlEventSub *newChildEvent =
761                     new QmlEventSub(eventDescription);
762             newChildEvent->calls = 1;
763             newChildEvent->duration = duration;
764
765             parentEvent->childrenHash.insert(eventDescription->eventHashStr,
766                                              newChildEvent);
767         } else {
768             QmlEventSub *newChildEvent =
769                     parentEvent->childrenHash.value(eventDescription->eventHashStr);
770             newChildEvent->duration += duration;
771             newChildEvent->calls++;
772         }
773
774         lastParent[level] = eventDescription;
775
776         if (level == MIN_LEVEL) {
777             totalTime += duration;
778         }
779     }
780
781     // fake rootEvent statistics
782     // the +1 nanosecond is to force it to be on top of the sorted list
783     d->m_qmlRootEvent.duration = totalTime+1;
784     d->m_qmlRootEvent.minTime = totalTime+1;
785     d->m_qmlRootEvent.maxTime = totalTime+1;
786     d->m_qmlRootEvent.medianTime = totalTime+1;
787     if (totalTime > 0)
788         d->m_qmlRootEvent.calls = 1;
789
790     // insert into list
791     QmlEvent *listedRootEvent =
792             d->m_eventDescriptions.value(d->m_rootEventName);
793     if (!listedRootEvent) {
794         listedRootEvent = new QmlEvent;
795         d->m_eventDescriptions.insert(d->m_rootEventName, listedRootEvent);
796     }
797     *listedRootEvent = d->m_qmlRootEvent;
798
799     // compute percentages
800     foreach (QmlEvent *binding, d->m_eventDescriptions.values()) {
801         binding->percentOfTime = binding->duration * 100.0 / totalTime;
802         binding->timePerCall = binding->calls > 0 ?
803                     double(binding->duration) / binding->calls : 0;
804     }
805
806     // compute median time
807     QHash < QmlEvent* , QList<qint64> > durationLists;
808     for (index = fromIndex; index <= toIndex; index++) {
809         QmlEvent *desc = d->m_startTimeSortedList[index].description;
810         qint64 len = d->m_startTimeSortedList[index].length;
811         durationLists[desc].append(len);
812     }
813     QMutableHashIterator < QmlEvent* , QList<qint64> > iter(durationLists);
814     while (iter.hasNext()) {
815         iter.next();
816         if (!iter.value().isEmpty()) {
817             qSort(iter.value());
818             iter.key()->medianTime = iter.value().at(iter.value().count()/2);
819         }
820     }
821 }
822
823 void ProfileData::prepareForDisplay()
824 {
825     // generate numeric ids
826     int ndx = 0;
827     foreach (QmlEvent *binding, d->m_eventDescriptions.values()) {
828         binding->eventId = ndx++;
829     }
830
831     // collect type counts
832     foreach (const QmlEventStartTime &eventStartData,
833              d->m_startTimeSortedList) {
834         int typeNumber = eventStartData.description->eventType;
835         if (!d->m_typeCounts.contains(typeNumber)) {
836             d->m_typeCounts[typeNumber] = new QmlEventTypeCount;
837             d->m_typeCounts[typeNumber]->nestingCount = 0;
838         }
839         if (eventStartData.nestingLevel >
840                 d->m_typeCounts[typeNumber]->nestingCount) {
841             d->m_typeCounts[typeNumber]->nestingCount = eventStartData.nestingLevel;
842         }
843         if (!d->m_typeCounts[typeNumber]->eventIds.contains(
844                     eventStartData.description->eventId))
845             d->m_typeCounts[typeNumber]->eventIds << eventStartData.description->eventId;
846     }
847 }
848
849 void ProfileData::sortStartTimes()
850 {
851     if (d->m_startTimeSortedList.count() < 2)
852         return;
853
854     // assuming startTimes is partially sorted
855     // identify blocks of events and sort them with quicksort
856     QList<QmlEventStartTime>::iterator itFrom =
857             d->m_startTimeSortedList.end() - 2;
858     QList<QmlEventStartTime>::iterator itTo =
859             d->m_startTimeSortedList.end() - 1;
860
861     while (itFrom != d->m_startTimeSortedList.begin() &&
862            itTo != d->m_startTimeSortedList.begin()) {
863         // find block to sort
864         while ( itFrom != d->m_startTimeSortedList.begin()
865                 && itTo->startTime > itFrom->startTime ) {
866             itTo--;
867             itFrom = itTo - 1;
868         }
869
870         // if we're at the end of the list
871         if (itFrom == d->m_startTimeSortedList.begin())
872             break;
873
874         // find block length
875         while ( itFrom != d->m_startTimeSortedList.begin()
876                 && itTo->startTime <= itFrom->startTime )
877             itFrom--;
878
879         if (itTo->startTime <= itFrom->startTime)
880             qSort(itFrom, itTo + 1, compareStartTimes);
881         else
882             qSort(itFrom + 1, itTo + 1, compareStartTimes);
883
884         // move to next block
885         itTo = itFrom;
886         itFrom = itTo - 1;
887     }
888
889     // link back the endTimes
890     for (int i = 0; i < d->m_startTimeSortedList.length(); i++)
891         d->m_endTimeSortedList[d->m_startTimeSortedList[i].endTimeIndex].startTimeIndex = i;
892 }
893
894 void ProfileData::sortEndTimes()
895 {
896     // assuming endTimes is partially sorted
897     // identify blocks of events and sort them with quicksort
898
899     if (d->m_endTimeSortedList.count() < 2)
900         return;
901
902     QList<QmlEventEndTime>::iterator itFrom =
903             d->m_endTimeSortedList.begin();
904     QList<QmlEventEndTime>::iterator itTo =
905             d->m_endTimeSortedList.begin() + 1;
906
907     while (itTo != d->m_endTimeSortedList.end() &&
908            itFrom != d->m_endTimeSortedList.end()) {
909         // find block to sort
910         while ( itTo != d->m_endTimeSortedList.end()
911                 && d->m_startTimeSortedList[itTo->startTimeIndex].startTime >
912                 d->m_startTimeSortedList[itFrom->startTimeIndex].startTime +
913                 d->m_startTimeSortedList[itFrom->startTimeIndex].length ) {
914             itFrom++;
915             itTo = itFrom+1;
916         }
917
918         // if we're at the end of the list
919         if (itTo == d->m_endTimeSortedList.end())
920             break;
921
922         // find block length
923         while ( itTo != d->m_endTimeSortedList.end()
924                 && d->m_startTimeSortedList[itTo->startTimeIndex].startTime <=
925                 d->m_startTimeSortedList[itFrom->startTimeIndex].startTime +
926                 d->m_startTimeSortedList[itFrom->startTimeIndex].length )
927             itTo++;
928
929         // sort block
930         qSort(itFrom, itTo, compareEndTimes);
931
932         // move to next block
933         itFrom = itTo;
934         itTo = itFrom+1;
935
936     }
937
938     // link back the startTimes
939     for (int i = 0; i < d->m_endTimeSortedList.length(); i++)
940         d->m_startTimeSortedList[d->m_endTimeSortedList[i].startTimeIndex].endTimeIndex = i;
941 }
942
943 void ProfileData::findAnimationLimits()
944 {
945     d->m_maximumAnimationCount = 0;
946     d->m_minimumAnimationCount = 0;
947     d->m_lastFrameEvent = 0;
948
949     for (int i = 0; i < d->m_startTimeSortedList.count(); i++) {
950         if (d->m_startTimeSortedList[i].description->eventType ==
951                 QQmlProfilerService::Painting &&
952                 d->m_startTimeSortedList[i].animationCount >= 0) {
953             int animationcount = d->m_startTimeSortedList[i].animationCount;
954             if (d->m_lastFrameEvent) {
955                 if (animationcount > d->m_maximumAnimationCount)
956                     d->m_maximumAnimationCount = animationcount;
957                 if (animationcount < d->m_minimumAnimationCount)
958                     d->m_minimumAnimationCount = animationcount;
959             } else {
960                 d->m_maximumAnimationCount = animationcount;
961                 d->m_minimumAnimationCount = animationcount;
962             }
963             d->m_lastFrameEvent = &d->m_startTimeSortedList[i];
964         }
965     }
966 }
967
968 void ProfileData::computeNestingLevels()
969 {
970     // compute levels
971     QHash <int, qint64> endtimesPerLevel;
972     QList <int> nestingLevels;
973     QList < QHash <int, qint64> > endtimesPerNestingLevel;
974     int level = MIN_LEVEL;
975     endtimesPerLevel[MIN_LEVEL] = 0;
976
977     for (int i = 0; i < QQmlProfilerService::MaximumRangeType; i++) {
978         nestingLevels << MIN_LEVEL;
979         QHash <int, qint64> dummyHash;
980         dummyHash[MIN_LEVEL] = 0;
981         endtimesPerNestingLevel << dummyHash;
982     }
983
984     for (int i=0; i<d->m_startTimeSortedList.count(); i++) {
985         qint64 st = d->m_startTimeSortedList[i].startTime;
986         int type = d->m_startTimeSortedList[i].description->eventType;
987
988         if (type == QQmlProfilerService::Painting) {
989             // animation/paint events have level 1 by definition,
990             // but are not considered parents of other events for
991             // statistical purposes
992             d->m_startTimeSortedList[i].level = MIN_LEVEL;
993             d->m_startTimeSortedList[i].nestingLevel = MIN_LEVEL;
994             continue;
995         }
996
997         // general level
998         if (endtimesPerLevel[level] > st) {
999             level++;
1000         } else {
1001             while (level > MIN_LEVEL && endtimesPerLevel[level-1] <= st)
1002                 level--;
1003         }
1004         endtimesPerLevel[level] = st + d->m_startTimeSortedList[i].length;
1005
1006         // per type
1007         if (endtimesPerNestingLevel[type][nestingLevels[type]] > st) {
1008             nestingLevels[type]++;
1009         } else {
1010             while (nestingLevels[type] > MIN_LEVEL &&
1011                    endtimesPerNestingLevel[type][nestingLevels[type]-1] <= st)
1012                 nestingLevels[type]--;
1013         }
1014         endtimesPerNestingLevel[type][nestingLevels[type]] = st +
1015                 d->m_startTimeSortedList[i].length;
1016
1017         d->m_startTimeSortedList[i].level = level;
1018         d->m_startTimeSortedList[i].nestingLevel = nestingLevels[type];
1019
1020         if (level == MIN_LEVEL) {
1021             d->m_qmlMeasuredTime += d->m_startTimeSortedList[i].length;
1022         }
1023     }
1024 }
1025
1026 void ProfileData::computeNestingDepth()
1027 {
1028     QHash <int, int> nestingDepth;
1029     for (int i = 0; i < d->m_endTimeSortedList.count(); i++) {
1030         int type = d->m_endTimeSortedList[i].description->eventType;
1031         int nestingInType = d->m_startTimeSortedList[d->m_endTimeSortedList[i].startTimeIndex].nestingLevel;
1032         if (!nestingDepth.contains(type))
1033             nestingDepth[type] = nestingInType;
1034         else {
1035             int nd = nestingDepth[type];
1036             nestingDepth[type] = nd > nestingInType ? nd : nestingInType;
1037         }
1038
1039         d->m_startTimeSortedList[d->m_endTimeSortedList[i].startTimeIndex].nestingDepth
1040                 = nestingDepth[type];
1041         if (nestingInType == MIN_LEVEL)
1042             nestingDepth[type] = MIN_LEVEL;
1043     }
1044 }
1045
1046 void ProfileData::postProcess()
1047 {
1048     if (count() != 0) {
1049         sortStartTimes();
1050         sortEndTimes();
1051         findAnimationLimits();
1052         computeLevels();
1053         linkEndsToStarts();
1054         reloadDetails();
1055         compileStatistics(traceStartTime(), traceEndTime());
1056         prepareForDisplay();
1057     }
1058     // data is ready even when there's no data
1059     emit dataReady();
1060 }
1061
1062 void ProfileData::linkEndsToStarts()
1063 {
1064     for (int i = 0; i < d->m_startTimeSortedList.count(); i++)
1065         d->m_endTimeSortedList[d->m_startTimeSortedList[i].endTimeIndex].startTimeIndex = i;
1066 }
1067
1068 void ProfileData::computeLevels()
1069 {
1070     computeNestingLevels();
1071     computeNestingDepth();
1072 }
1073
1074 void ProfileData::reloadDetails()
1075 {
1076     // request binding/signal details from the AST
1077     foreach (QmlEvent *event, d->m_eventDescriptions.values()) {
1078         if (event->eventType != QQmlProfilerService::Binding &&
1079                 event->eventType != QQmlProfilerService::HandlingSignal)
1080             continue;
1081
1082         // This skips anonymous bindings in Qt4.8 (we don't have valid location data for them)
1083         if (event->location.filename.isEmpty())
1084             continue;
1085
1086         // Skip non-anonymous bindings from Qt4.8 (we already have correct details for them)
1087         if (event->location.column == -1)
1088             continue;
1089
1090         emit requestDetailsForLocation(event->eventType, event->location);
1091     }
1092     emit reloadDocumentsForDetails();
1093 }
1094
1095 void ProfileData::findBindingLoops(qint64 startTime, qint64 endTime)
1096 {
1097     // first clear existing data
1098     foreach (QmlEvent *event, d->m_eventDescriptions.values()) {
1099         event->isBindingLoop = false;
1100         foreach (QmlEventSub *parentEvent, event->parentHash.values())
1101             parentEvent->inLoopPath = false;
1102         foreach (QmlEventSub *childEvent, event->childrenHash.values())
1103             childEvent->inLoopPath = false;
1104     }
1105
1106     QList <QmlEvent *> stackRefs;
1107     QList <QmlEventStartTime *> stack;
1108     int fromIndex = findFirstIndex(startTime);
1109     int toIndex = findLastIndex(endTime);
1110
1111     for (int i = 0; i < d->m_startTimeSortedList.count(); i++) {
1112         QmlEvent *currentEvent = d->m_startTimeSortedList[i].description;
1113         QmlEventStartTime *inTimeEvent = &d->m_startTimeSortedList[i];
1114         inTimeEvent->bindingLoopHead = -1;
1115
1116         // managing call stack
1117         for (int j = stack.count() - 1; j >= 0; j--) {
1118             if (stack[j]->startTime + stack[j]->length <= inTimeEvent->startTime) {
1119                 stack.removeAt(j);
1120                 stackRefs.removeAt(j);
1121             }
1122         }
1123
1124         bool loopDetected = stackRefs.contains(currentEvent);
1125         stack << inTimeEvent;
1126         stackRefs << currentEvent;
1127
1128         if (loopDetected) {
1129             if (i >= fromIndex && i <= toIndex) {
1130                 // for the statistics
1131                 currentEvent->isBindingLoop = true;
1132                 for (int j = stackRefs.indexOf(currentEvent); j < stackRefs.count()-1; j++) {
1133                     QmlEventSub *nextEventSub = stackRefs[j]->childrenHash.value(stackRefs[j+1]->eventHashStr);
1134                     nextEventSub->inLoopPath = true;
1135                     QmlEventSub *prevEventSub = stackRefs[j+1]->parentHash.value(stackRefs[j]->eventHashStr);
1136                     prevEventSub->inLoopPath = true;
1137                 }
1138             }
1139
1140             // use crossed references to find index in starttimesortedlist
1141             QmlEventStartTime *head = stack[stackRefs.indexOf(currentEvent)];
1142             inTimeEvent->bindingLoopHead = d->m_endTimeSortedList[head->endTimeIndex].startTimeIndex;
1143             d->m_startTimeSortedList[inTimeEvent->bindingLoopHead].bindingLoopHead = i;
1144         }
1145     }
1146 }
1147
1148 void ProfileData::rewriteDetailsString(
1149         QQmlProfilerService::RangeType eventType,
1150         const EventLocation &location, const QString &newString)
1151 {
1152     QString eventHashStr = getHashStringForQmlEvent(location,
1153                                                             eventType);
1154     Q_ASSERT(d->m_eventDescriptions.contains(eventHashStr));
1155     d->m_eventDescriptions.value(eventHashStr)->details = newString;
1156     emit detailsChanged(d->m_eventDescriptions.value(eventHashStr)->eventId,
1157                         newString);
1158 }
1159
1160 void ProfileData::finishedRewritingDetails()
1161 {
1162     emit reloadDetailLabels();
1163 }
1164
1165 // get list of events between A and B:
1166 // find fist event with endtime after A -> aa
1167 // find last event with starttime before B -> bb
1168 // list is from parent of aa with level=0 to bb, in the "sorted by starttime" list
1169 int ProfileData::findFirstIndex(qint64 startTime) const
1170 {
1171     int candidate = -1;
1172     // in the "endtime" list, find the first event that ends after startTime
1173     if (d->m_endTimeSortedList.isEmpty())
1174         return 0; // -1
1175     if (d->m_endTimeSortedList.length() == 1 ||
1176             d->m_endTimeSortedList.first().endTime >= startTime)
1177         candidate = 0;
1178     else
1179         if (d->m_endTimeSortedList.last().endTime <= startTime)
1180             return 0; // -1
1181
1182     if (candidate == -1)
1183     {
1184         int fromIndex = 0;
1185         int toIndex = d->m_endTimeSortedList.count()-1;
1186         while (toIndex - fromIndex > 1) {
1187             int midIndex = (fromIndex + toIndex)/2;
1188             if (d->m_endTimeSortedList[midIndex].endTime < startTime)
1189                 fromIndex = midIndex;
1190             else
1191                 toIndex = midIndex;
1192         }
1193
1194         candidate = toIndex;
1195     }
1196
1197     int ndx = d->m_endTimeSortedList[candidate].startTimeIndex;
1198
1199     // and then go to the parent
1200     while (d->m_startTimeSortedList[ndx].level != MIN_LEVEL && ndx > 0)
1201         ndx--;
1202
1203     return ndx;
1204 }
1205
1206 int ProfileData::findFirstIndexNoParents(qint64 startTime) const
1207 {
1208     int candidate = -1;
1209     // in the "endtime" list, find the first event that ends after startTime
1210     if (d->m_endTimeSortedList.isEmpty())
1211         return 0; // -1
1212     if (d->m_endTimeSortedList.length() == 1 ||
1213             d->m_endTimeSortedList.first().endTime >= startTime)
1214         candidate = 0;
1215     else
1216         if (d->m_endTimeSortedList.last().endTime <= startTime)
1217             return 0; // -1
1218
1219     if (candidate == -1) {
1220         int fromIndex = 0;
1221         int toIndex = d->m_endTimeSortedList.count()-1;
1222         while (toIndex - fromIndex > 1) {
1223             int midIndex = (fromIndex + toIndex)/2;
1224             if (d->m_endTimeSortedList[midIndex].endTime < startTime)
1225                 fromIndex = midIndex;
1226             else
1227                 toIndex = midIndex;
1228         }
1229
1230         candidate = toIndex;
1231     }
1232
1233     int ndx = d->m_endTimeSortedList[candidate].startTimeIndex;
1234
1235     return ndx;
1236 }
1237
1238 int ProfileData::findLastIndex(qint64 endTime) const
1239 {
1240     // in the "starttime" list, find the last event that starts before endtime
1241     if (d->m_startTimeSortedList.isEmpty())
1242         return 0; // -1
1243     if (d->m_startTimeSortedList.first().startTime >= endTime)
1244         return 0; // -1
1245     if (d->m_startTimeSortedList.length() == 1)
1246         return 0;
1247     if (d->m_startTimeSortedList.last().startTime <= endTime)
1248         return d->m_startTimeSortedList.count()-1;
1249
1250     int fromIndex = 0;
1251     int toIndex = d->m_startTimeSortedList.count()-1;
1252     while (toIndex - fromIndex > 1) {
1253         int midIndex = (fromIndex + toIndex)/2;
1254         if (d->m_startTimeSortedList[midIndex].startTime < endTime)
1255             fromIndex = midIndex;
1256         else
1257             toIndex = midIndex;
1258     }
1259
1260     return fromIndex;
1261 }
1262
1263 qint64 ProfileData::firstTimeMark() const
1264 {
1265     if (d->m_startTimeSortedList.isEmpty())
1266         return 0;
1267     else {
1268         return d->m_startTimeSortedList[0].startTime;
1269     }
1270 }
1271
1272 qint64 ProfileData::lastTimeMark() const
1273 {
1274     if (d->m_endTimeSortedList.isEmpty())
1275         return 0;
1276     else {
1277         return d->m_endTimeSortedList.last().endTime;
1278     }
1279 }
1280
1281 qint64 ProfileData::traceStartTime() const
1282 {
1283     return d->m_traceStartTime != -1? d->m_traceStartTime : firstTimeMark();
1284 }
1285
1286 qint64 ProfileData::traceEndTime() const
1287 {
1288     return d->m_traceEndTime ? d->m_traceEndTime : lastTimeMark();
1289 }
1290
1291 qint64 ProfileData::traceDuration() const
1292 {
1293     return traceEndTime() - traceStartTime();
1294 }
1295
1296 qint64 ProfileData::qmlMeasuredTime() const
1297 {
1298     return d->m_qmlMeasuredTime;
1299 }
1300 qint64 ProfileData::v8MeasuredTime() const
1301 {
1302     return d->m_v8MeasuredTime;
1303 }
1304
1305 int ProfileData::count() const
1306 {
1307     return d->m_startTimeSortedList.count();
1308 }
1309
1310 ////////////////////////////////////////////////////////////////////////////////
1311
1312
1313 bool ProfileData::save(const QString &filename)
1314 {
1315     if (count() == 0) {
1316         emit error(tr("No data to save"));
1317         return false;
1318     }
1319
1320     QFile file(filename);
1321     if (!file.open(QIODevice::WriteOnly)) {
1322         emit error(tr("Could not open %1 for writing").arg(filename));
1323         return false;
1324     }
1325
1326     QXmlStreamWriter stream(&file);
1327     stream.setAutoFormatting(true);
1328     stream.writeStartDocument();
1329
1330     stream.writeStartElement("trace");
1331     stream.writeAttribute("version", PROFILER_FILE_VERSION);
1332
1333     stream.writeAttribute("traceStart", QString::number(traceStartTime()));
1334     stream.writeAttribute("traceEnd", QString::number(traceEndTime()));
1335
1336     stream.writeStartElement("eventData");
1337     stream.writeAttribute("totalTime", QString::number(d->m_qmlMeasuredTime));
1338
1339     foreach (const QmlEvent *eventData, d->m_eventDescriptions.values()) {
1340         stream.writeStartElement("event");
1341         stream.writeAttribute("index",
1342                               QString::number(
1343                                   d->m_eventDescriptions.keys().indexOf(
1344                                       eventData->eventHashStr)));
1345         stream.writeTextElement("displayname", eventData->displayname);
1346         stream.writeTextElement("type", qmlEventType(eventData->eventType));
1347         if (!eventData->location.filename.isEmpty()) {
1348             stream.writeTextElement("filename", eventData->location.filename);
1349             stream.writeTextElement("line",
1350                                     QString::number(eventData->location.line));
1351             stream.writeTextElement("column",
1352                                     QString::number(eventData->location.column));
1353         }
1354         stream.writeTextElement("details", eventData->details);
1355         stream.writeEndElement();
1356     }
1357     stream.writeEndElement(); // eventData
1358
1359     stream.writeStartElement("eventList");
1360     foreach (const QmlEventStartTime &rangedEvent,
1361              d->m_startTimeSortedList) {
1362         stream.writeStartElement("range");
1363         stream.writeAttribute("startTime", QString::number(rangedEvent.startTime));
1364         stream.writeAttribute("duration", QString::number(rangedEvent.length));
1365         stream.writeAttribute("eventIndex",
1366                               QString::number(d->m_eventDescriptions.keys().indexOf(
1367                                                   rangedEvent.description->eventHashStr)));
1368         if (rangedEvent.description->eventType ==
1369                 QQmlProfilerService::Painting && rangedEvent.animationCount >= 0) {
1370             // animation frame
1371             stream.writeAttribute("framerate",
1372                                   QString::number(rangedEvent.frameRate));
1373             stream.writeAttribute("animationcount",
1374                                   QString::number(rangedEvent.animationCount));
1375         }
1376         stream.writeEndElement();
1377     }
1378     stream.writeEndElement(); // eventList
1379
1380     stream.writeStartElement("v8profile"); // v8 profiler output
1381     stream.writeAttribute("totalTime", QString::number(d->m_v8MeasuredTime));
1382     foreach (V8Event *v8event, d->m_v8EventList) {
1383         stream.writeStartElement("event");
1384         stream.writeAttribute("index",
1385                               QString::number(d->m_v8EventList.indexOf(v8event)));
1386         stream.writeTextElement("displayname", v8event->displayName);
1387         stream.writeTextElement("functionname", v8event->functionName);
1388         if (!v8event->filename.isEmpty()) {
1389             stream.writeTextElement("filename", v8event->filename);
1390             stream.writeTextElement("line", QString::number(v8event->line));
1391         }
1392         stream.writeTextElement("totalTime",
1393                                 QString::number(v8event->totalTime));
1394         stream.writeTextElement("selfTime", QString::number(v8event->selfTime));
1395         if (!v8event->childrenHash.isEmpty()) {
1396             stream.writeStartElement("childrenEvents");
1397             QStringList childrenIndexes;
1398             QStringList childrenTimes;
1399             QStringList parentTimes;
1400             foreach (V8EventSub *v8child, v8event->childrenHash.values()) {
1401                 childrenIndexes << QString::number(v8child->reference->eventId);
1402                 childrenTimes << QString::number(v8child->totalTime);
1403                 parentTimes << QString::number(
1404                                    d->m_v8EventList[v8child->reference->eventId]->
1405                                    parentHash[v8event->displayName]->totalTime);
1406             }
1407
1408             stream.writeAttribute("list", childrenIndexes.join(QString(", ")));
1409             stream.writeAttribute("childrenTimes",
1410                                   childrenTimes.join(QString(", ")));
1411             stream.writeAttribute("parentTimes",
1412                                   parentTimes.join(QString(", ")));
1413             stream.writeEndElement();
1414         }
1415         stream.writeEndElement();
1416     }
1417     stream.writeEndElement(); // v8 profiler output
1418
1419     stream.writeEndElement(); // trace
1420     stream.writeEndDocument();
1421
1422     file.close();
1423     return true;
1424 }
1425
1426 void ProfileData::setFilename(const QString &filename)
1427 {
1428     d->m_filename = filename;
1429 }
1430
1431 void ProfileData::load(const QString &filename)
1432 {
1433     setFilename(filename);
1434     load();
1435 }
1436
1437 // "be strict in your output but tolerant in your inputs"
1438 void ProfileData::load()
1439 {
1440     QString filename = d->m_filename;
1441
1442     QFile file(filename);
1443
1444     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
1445         emit error(tr("Could not open %1 for reading").arg(filename));
1446         return;
1447     }
1448
1449     emit processingData();
1450
1451     // erase current
1452     clear();
1453
1454     bool readingQmlEvents = false;
1455     bool readingV8Events = false;
1456     QHash <int, QmlEvent *> descriptionBuffer;
1457     QmlEvent *currentEvent = 0;
1458     QHash <int, V8Event *> v8eventBuffer;
1459     QHash <int, QString> childrenIndexes;
1460     QHash <int, QString> childrenTimes;
1461     QHash <int, QString> parentTimes;
1462     V8Event *v8event = 0;
1463     bool startTimesAreSorted = true;
1464     bool validVersion = true;
1465
1466     // time computation
1467     d->m_v8MeasuredTime = 0;
1468     d->m_qmlMeasuredTime = 0;
1469     double cumulatedV8Time = 0;
1470
1471     QXmlStreamReader stream(&file);
1472
1473     while (validVersion && !stream.atEnd() && !stream.hasError()) {
1474         QXmlStreamReader::TokenType token = stream.readNext();
1475         QString elementName = stream.name().toString();
1476         switch (token) {
1477         case QXmlStreamReader::StartDocument :  continue;
1478         case QXmlStreamReader::StartElement : {
1479             if (elementName == "trace") {
1480                 QXmlStreamAttributes attributes = stream.attributes();
1481                 if (attributes.hasAttribute("version"))
1482                     validVersion =
1483                             attributes.value("version").toString() ==
1484                             PROFILER_FILE_VERSION;
1485                 else
1486                     validVersion = false;
1487                 if (attributes.hasAttribute("traceStart"))
1488                     setTraceStartTime(attributes.value("traceStart").
1489                                       toString().toLongLong());
1490                 if (attributes.hasAttribute("traceEnd"))
1491                     setTraceEndTime(attributes.value("traceEnd").
1492                                     toString().toLongLong());
1493             }
1494             if (elementName == "eventData" && !readingV8Events) {
1495                 readingQmlEvents = true;
1496                 QXmlStreamAttributes attributes = stream.attributes();
1497                 if (attributes.hasAttribute("totalTime"))
1498                     d->m_qmlMeasuredTime = attributes.value("totalTime").
1499                             toString().toDouble();
1500                 break;
1501             }
1502             if (elementName == "v8profile" && !readingQmlEvents) {
1503                 readingV8Events = true;
1504                 QXmlStreamAttributes attributes = stream.attributes();
1505                 if (attributes.hasAttribute("totalTime"))
1506                     d->m_v8MeasuredTime = attributes.value("totalTime").
1507                             toString().toDouble();
1508                 break;
1509             }
1510
1511             if (elementName == "trace") {
1512                 QXmlStreamAttributes attributes = stream.attributes();
1513                 if (attributes.hasAttribute("traceStart"))
1514                     setTraceStartTime(attributes.value("traceStart").
1515                                       toString().toLongLong());
1516                 if (attributes.hasAttribute("traceEnd"))
1517                     setTraceEndTime(attributes.value("traceEnd").
1518                                     toString().toLongLong());
1519             }
1520
1521             if (elementName == "range") {
1522                 QmlEventStartTime rangedEvent;
1523                 QXmlStreamAttributes attributes = stream.attributes();
1524                 if (attributes.hasAttribute("startTime"))
1525                     rangedEvent.startTime = attributes.value("startTime").
1526                             toString().toLongLong();
1527                 if (attributes.hasAttribute("duration"))
1528                     rangedEvent.length = attributes.value("duration").
1529                             toString().toLongLong();
1530                 if (attributes.hasAttribute("framerate"))
1531                     rangedEvent.frameRate = attributes.value("framerate").
1532                             toString().toInt();
1533                 if (attributes.hasAttribute("animationcount"))
1534                     rangedEvent.animationCount = attributes.value("animationcount").
1535                             toString().toInt();
1536                 else
1537                     rangedEvent.animationCount = -1;
1538                 if (attributes.hasAttribute("eventIndex")) {
1539                     int ndx = attributes.value("eventIndex").toString().toInt();
1540                     if (!descriptionBuffer.value(ndx))
1541                         descriptionBuffer[ndx] = new QmlEvent;
1542                     rangedEvent.description = descriptionBuffer.value(ndx);
1543                 }
1544                 rangedEvent.endTimeIndex = d->m_endTimeSortedList.length();
1545
1546                 if (!d->m_startTimeSortedList.isEmpty()
1547                         && rangedEvent.startTime <
1548                         d->m_startTimeSortedList.last().startTime)
1549                     startTimesAreSorted = false;
1550                 d->m_startTimeSortedList << rangedEvent;
1551
1552                 QmlEventEndTime endTimeEvent;
1553                 endTimeEvent.endTime = rangedEvent.startTime + rangedEvent.length;
1554                 endTimeEvent.startTimeIndex = d->m_startTimeSortedList.length()-1;
1555                 endTimeEvent.description = rangedEvent.description;
1556                 d->m_endTimeSortedList << endTimeEvent;
1557                 break;
1558             }
1559
1560             if (readingQmlEvents) {
1561                 if (elementName == "event") {
1562                     QXmlStreamAttributes attributes = stream.attributes();
1563                     if (attributes.hasAttribute("index")) {
1564                         int ndx = attributes.value("index").toString().toInt();
1565                         if (!descriptionBuffer.value(ndx))
1566                             descriptionBuffer[ndx] = new QmlEvent;
1567                         currentEvent = descriptionBuffer[ndx];
1568                     } else {
1569                         currentEvent = 0;
1570                     }
1571                     break;
1572                 }
1573
1574                 // the remaining are eventdata or v8eventdata elements
1575                 if (!currentEvent)
1576                     break;
1577
1578                 stream.readNext();
1579                 if (stream.tokenType() != QXmlStreamReader::Characters)
1580                     break;
1581                 QString readData = stream.text().toString();
1582
1583                 if (elementName == "displayname") {
1584                     currentEvent->displayname = readData;
1585                     break;
1586                 }
1587                 if (elementName == "type") {
1588                     currentEvent->eventType = qmlEventType(readData);
1589                     break;
1590                 }
1591                 if (elementName == "filename") {
1592                     currentEvent->location.filename = readData;
1593                     break;
1594                 }
1595                 if (elementName == "line") {
1596                     currentEvent->location.line = readData.toInt();
1597                     break;
1598                 }
1599                 if (elementName == "column") {
1600                     currentEvent->location.column = readData.toInt();
1601                 }
1602                 if (elementName == "details") {
1603                     currentEvent->details = readData;
1604                     break;
1605                 }
1606             }
1607
1608             if (readingV8Events) {
1609                 if (elementName == "event") {
1610                     QXmlStreamAttributes attributes = stream.attributes();
1611                     if (attributes.hasAttribute("index")) {
1612                         int ndx = attributes.value("index").toString().toInt();
1613                         if (!v8eventBuffer.value(ndx))
1614                             v8eventBuffer[ndx] = new V8Event;
1615                         v8event = v8eventBuffer[ndx];
1616                     } else {
1617                         v8event = 0;
1618                     }
1619                     break;
1620                 }
1621
1622                 // the remaining are eventdata or v8eventdata elements
1623                 if (!v8event)
1624                     break;
1625
1626                 if (elementName == "childrenEvents") {
1627                     QXmlStreamAttributes attributes = stream.attributes();
1628                     int eventIndex = v8eventBuffer.key(v8event);
1629                     if (attributes.hasAttribute("list")) {
1630                         // store for later parsing (we haven't read all the events yet)
1631                         childrenIndexes[eventIndex] =
1632                                 attributes.value("list").toString();
1633                     }
1634                     if (attributes.hasAttribute("childrenTimes")) {
1635                         childrenTimes[eventIndex] =
1636                                 attributes.value("childrenTimes").toString();
1637                     }
1638                     if (attributes.hasAttribute("parentTimes")) {
1639                         parentTimes[eventIndex] =
1640                                 attributes.value("parentTimes").toString();
1641                     }
1642                 }
1643
1644                 stream.readNext();
1645                 if (stream.tokenType() != QXmlStreamReader::Characters)
1646                     break;
1647                 QString readData = stream.text().toString();
1648
1649                 if (elementName == "displayname") {
1650                     v8event->displayName = readData;
1651                     break;
1652                 }
1653
1654                 if (elementName == "functionname") {
1655                     v8event->functionName = readData;
1656                     break;
1657                 }
1658
1659                 if (elementName == "filename") {
1660                     v8event->filename = readData;
1661                     break;
1662                 }
1663
1664                 if (elementName == "line") {
1665                     v8event->line = readData.toInt();
1666                     break;
1667                 }
1668
1669                 if (elementName == "totalTime") {
1670                     v8event->totalTime = readData.toDouble();
1671                     cumulatedV8Time += v8event->totalTime;
1672                     break;
1673                 }
1674
1675                 if (elementName == "selfTime") {
1676                     v8event->selfTime = readData.toDouble();
1677                     break;
1678                 }
1679             }
1680
1681             break;
1682         }
1683         case QXmlStreamReader::EndElement : {
1684             if (elementName == "event") {
1685                 currentEvent = 0;
1686                 break;
1687             }
1688             if (elementName == "eventData") {
1689                 readingQmlEvents = false;
1690                 break;
1691             }
1692             if (elementName == "v8profile") {
1693                 readingV8Events = false;
1694             }
1695         }
1696         default: break;
1697         }
1698     }
1699
1700     file.close();
1701
1702     if (stream.hasError()) {
1703         emit error(tr("Error while parsing %1").arg(filename));
1704         clear();
1705         return;
1706     }
1707
1708     stream.clear();
1709
1710     if (!validVersion) {
1711         clear();
1712         emit countChanged();
1713         emit dataReady();
1714         emit error(tr("Invalid version of QML Trace file."));
1715         return;
1716     }
1717
1718     // backwards compatibility
1719     if (d->m_v8MeasuredTime == 0)
1720         d->m_v8MeasuredTime = cumulatedV8Time;
1721
1722     // move the buffered data to the details cache
1723     foreach (QmlEvent *desc, descriptionBuffer.values()) {
1724         desc->eventHashStr = getHashStringForQmlEvent(
1725                     desc->location, desc->eventType);;
1726         d->m_eventDescriptions[desc->eventHashStr] = desc;
1727     }
1728
1729     // sort startTimeSortedList
1730     if (!startTimesAreSorted) {
1731         qSort(d->m_startTimeSortedList.begin(),
1732               d->m_startTimeSortedList.end(), compareStartTimes);
1733         for (int i = 0; i< d->m_startTimeSortedList.length(); i++) {
1734             QmlEventStartTime startTimeData = d->m_startTimeSortedList[i];
1735             d->m_endTimeSortedList[startTimeData.endTimeIndex].startTimeIndex = i;
1736         }
1737         qSort(d->m_endTimeSortedList.begin(),
1738               d->m_endTimeSortedList.end(), compareStartIndexes);
1739     }
1740
1741     // find v8events' children and parents
1742     foreach (int parentIndex, childrenIndexes.keys()) {
1743         QStringList childrenStrings =
1744                 childrenIndexes.value(parentIndex).split(",");
1745         QStringList childrenTimesStrings =
1746                 childrenTimes.value(parentIndex).split(", ");
1747         QStringList parentTimesStrings =
1748                 parentTimes.value(parentIndex).split(", ");
1749         for (int ndx = 0; ndx < childrenStrings.count(); ndx++) {
1750             int childIndex = childrenStrings[ndx].toInt();
1751             if (v8eventBuffer.value(childIndex)) {
1752                 V8EventSub *newChild = new V8EventSub(v8eventBuffer[childIndex]);
1753                 V8EventSub *newParent = new V8EventSub(v8eventBuffer[parentIndex]);
1754                 if (childrenTimesStrings.count() > ndx)
1755                     newChild->totalTime = childrenTimesStrings[ndx].toDouble();
1756                 if (parentTimesStrings.count() > ndx)
1757                     newParent->totalTime = parentTimesStrings[ndx].toDouble();
1758                 v8eventBuffer[parentIndex]->childrenHash.insert(
1759                             newChild->reference->displayName, newChild);
1760                 v8eventBuffer[childIndex]->parentHash.insert(
1761                             newParent->reference->displayName, newParent);
1762             }
1763         }
1764     }
1765     // store v8 events
1766     d->m_v8EventList = v8eventBuffer.values();
1767
1768     emit countChanged();
1769
1770     descriptionBuffer.clear();
1771
1772     emit postProcessing();
1773     d->collectV8Statistics();
1774     postProcess();
1775 }
1776
1777 ///////////////////////////////////////////////
1778 qint64 ProfileData::getStartTime(int index) const
1779 {
1780     return d->m_startTimeSortedList[index].startTime;
1781 }
1782
1783 qint64 ProfileData::getEndTime(int index) const
1784 {
1785     return d->m_startTimeSortedList[index].startTime +
1786             d->m_startTimeSortedList[index].length;
1787 }
1788
1789 qint64 ProfileData::getDuration(int index) const
1790 {
1791     return d->m_startTimeSortedList[index].length;
1792 }
1793
1794 int ProfileData::getType(int index) const
1795 {
1796     return d->m_startTimeSortedList[index].description->eventType;
1797 }
1798
1799 int ProfileData::getNestingLevel(int index) const
1800 {
1801     return d->m_startTimeSortedList[index].nestingLevel;
1802 }
1803
1804 int ProfileData::getNestingDepth(int index) const
1805 {
1806     return d->m_startTimeSortedList[index].nestingDepth;
1807 }
1808
1809 QString ProfileData::getFilename(int index) const
1810 {
1811     return d->m_startTimeSortedList[index].description->location.filename;
1812 }
1813
1814 int ProfileData::getLine(int index) const
1815 {
1816     return d->m_startTimeSortedList[index].description->location.line;
1817 }
1818
1819 int ProfileData::getColumn(int index) const
1820 {
1821     return d->m_startTimeSortedList[index].description->location.column;
1822 }
1823
1824 QString ProfileData::getDetails(int index) const
1825 {
1826     // special: animations
1827     if (d->m_startTimeSortedList[index].description->eventType ==
1828             QQmlProfilerService::Painting &&
1829             d->m_startTimeSortedList[index].animationCount >= 0)
1830         return tr("%1 animations at %2 FPS").arg(
1831                     QString::number(d->m_startTimeSortedList[index].animationCount),
1832                     QString::number(d->m_startTimeSortedList[index].frameRate));
1833     return d->m_startTimeSortedList[index].description->details;
1834 }
1835
1836 int ProfileData::getEventId(int index) const {
1837     return d->m_startTimeSortedList[index].description->eventId;
1838 }
1839
1840 int ProfileData::getFramerate(int index) const
1841 {
1842     return d->m_startTimeSortedList[index].frameRate;
1843 }
1844
1845 int ProfileData::getAnimationCount(int index) const
1846 {
1847     return d->m_startTimeSortedList[index].animationCount;
1848 }
1849
1850 int ProfileData::getMaximumAnimationCount() const
1851 {
1852     return d->m_maximumAnimationCount;
1853 }
1854
1855 int ProfileData::getMinimumAnimationCount() const
1856 {
1857     return d->m_minimumAnimationCount;
1858 }
1859
1860 int ProfileData::uniqueEventsOfType(int type) const
1861 {
1862     if (!d->m_typeCounts.contains(type))
1863         return 0;
1864     return d->m_typeCounts[type]->eventIds.count();
1865 }
1866
1867 int ProfileData::maxNestingForType(int type) const
1868 {
1869     if (!d->m_typeCounts.contains(type))
1870         return 0;
1871     return d->m_typeCounts[type]->nestingCount;
1872 }
1873
1874 QString ProfileData::eventTextForType(int type, int index) const
1875 {
1876     if (!d->m_typeCounts.contains(type))
1877         return QString();
1878     return d->m_eventDescriptions.values().at(
1879                 d->m_typeCounts[type]->eventIds[index])->details;
1880 }
1881
1882 QString ProfileData::eventDisplayNameForType(int type, int index) const
1883 {
1884     if (!d->m_typeCounts.contains(type))
1885         return QString();
1886     return d->m_eventDescriptions.values().at(
1887                 d->m_typeCounts[type]->eventIds[index])->displayname;
1888 }
1889
1890 int ProfileData::eventIdForType(int type, int index) const
1891 {
1892     if (!d->m_typeCounts.contains(type))
1893         return -1;
1894     return d->m_typeCounts[type]->eventIds[index];
1895 }
1896
1897 int ProfileData::eventPosInType(int index) const
1898 {
1899     int eventType = d->m_startTimeSortedList[index].description->eventType;
1900     return d->m_typeCounts[eventType]->eventIds.indexOf(
1901                 d->m_startTimeSortedList[index].description->eventId);
1902 }