1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include "qmlprofilerdata.h"
30
31#include <QStringList>
32#include <QUrl>
33#include <QHash>
34#include <QFile>
35#include <QXmlStreamReader>
36#include <QRegularExpression>
37#include <QQueue>
38#include <QStack>
39
40#include <limits>
41
42const char PROFILER_FILE_VERSION[] = "1.02";
43
44static const char *RANGE_TYPE_STRINGS[] = {
45 "Painting",
46 "Compiling",
47 "Creating",
48 "Binding",
49 "HandlingSignal",
50 "Javascript"
51};
52
53Q_STATIC_ASSERT(sizeof(RANGE_TYPE_STRINGS) == MaximumRangeType * sizeof(const char *));
54
55static const char *MESSAGE_STRINGS[] = {
56 "Event",
57 "RangeStart",
58 "RangeData",
59 "RangeLocation",
60 "RangeEnd",
61 "Complete",
62 "PixmapCache",
63 "SceneGraph",
64 "MemoryAllocation",
65 "DebugMessage"
66};
67
68Q_STATIC_ASSERT(sizeof(MESSAGE_STRINGS) == MaximumMessage * sizeof(const char *));
69
70/////////////////////////////////////////////////////////////////
71class QmlProfilerDataPrivate
72{
73public:
74 QmlProfilerDataPrivate(QmlProfilerData *qq){ Q_UNUSED(qq); }
75
76 // data storage
77 QVector<QQmlProfilerEventType> eventTypes;
78 QVector<QQmlProfilerEvent> events;
79
80 qint64 traceStartTime;
81 qint64 traceEndTime;
82
83 // internal state while collecting events
84 qint64 qmlMeasuredTime;
85 QmlProfilerData::State state;
86};
87
88/////////////////////////////////////////////////////////////////
89QmlProfilerData::QmlProfilerData(QObject *parent) :
90 QQmlProfilerEventReceiver(parent), d(new QmlProfilerDataPrivate(this))
91{
92 d->state = Empty;
93 clear();
94}
95
96QmlProfilerData::~QmlProfilerData()
97{
98 clear();
99 delete d;
100}
101
102void QmlProfilerData::clear()
103{
104 d->events.clear();
105
106 d->traceEndTime = std::numeric_limits<qint64>::min();
107 d->traceStartTime = std::numeric_limits<qint64>::max();
108 d->qmlMeasuredTime = 0;
109
110 setState(Empty);
111}
112
113QString QmlProfilerData::qmlRangeTypeAsString(RangeType type)
114{
115 if (type * sizeof(char *) < sizeof(RANGE_TYPE_STRINGS))
116 return QLatin1String(RANGE_TYPE_STRINGS[type]);
117 else
118 return QString::number(type);
119}
120
121QString QmlProfilerData::qmlMessageAsString(Message type)
122{
123 if (type * sizeof(char *) < sizeof(MESSAGE_STRINGS))
124 return QLatin1String(MESSAGE_STRINGS[type]);
125 else
126 return QString::number(type);
127}
128
129void QmlProfilerData::setTraceStartTime(qint64 time)
130{
131 if (time < d->traceStartTime)
132 d->traceStartTime = time;
133}
134
135void QmlProfilerData::setTraceEndTime(qint64 time)
136{
137 if (time > d->traceEndTime)
138 d->traceEndTime = time;
139}
140
141qint64 QmlProfilerData::traceStartTime() const
142{
143 return d->traceStartTime;
144}
145
146qint64 QmlProfilerData::traceEndTime() const
147{
148 return d->traceEndTime;
149}
150
151void QmlProfilerData::addEvent(const QQmlProfilerEvent &event)
152{
153 setState(AcquiringData);
154 d->events.append(t: event);
155}
156
157void QmlProfilerData::addEventType(const QQmlProfilerEventType &type)
158{
159 QQmlProfilerEventType newType = type;
160
161 QString details;
162 // generate details string
163 if (!type.data().isEmpty()) {
164 details = type.data().simplified();
165 QRegularExpression rewrite(QStringLiteral("^\\(function \\$(\\w+)\\(\\) \\{ (return |)(.+) \\}\\)$"));
166 QRegularExpressionMatch match = rewrite.match(subject: details);
167 if (match.hasMatch()) {
168 details = match.captured(nth: 1) +QLatin1String(": ") + match.captured(nth: 3);
169 }
170 if (details.startsWith(s: QLatin1String("file://")))
171 details = details.mid(position: details.lastIndexOf(c: QLatin1Char('/')) + 1);
172 }
173
174 newType.setData(details);
175
176 QString displayName;
177 switch (type.message()) {
178 case Event: {
179 switch (type.detailType()) {
180 case Mouse:
181 case Key:
182 displayName = QString::fromLatin1(str: "Input:%1").arg(a: type.detailType());
183 break;
184 case AnimationFrame:
185 displayName = QString::fromLatin1(str: "AnimationFrame");
186 break;
187 default:
188 displayName = QString::fromLatin1(str: "Unknown");
189 }
190 break;
191 }
192 case RangeStart:
193 case RangeData:
194 case RangeLocation:
195 case RangeEnd:
196 case Complete:
197 Q_UNREACHABLE();
198 break;
199 case PixmapCacheEvent: {
200 const QString filePath = QUrl(type.location().filename()).path();
201 displayName = filePath.midRef(position: filePath.lastIndexOf(c: QLatin1Char('/')) + 1)
202 + QLatin1Char(':') + QString::number(type.detailType());
203 break;
204 }
205 case SceneGraphFrame:
206 displayName = QString::fromLatin1(str: "SceneGraph:%1").arg(a: type.detailType());
207 break;
208 case MemoryAllocation:
209 displayName = QString::fromLatin1(str: "MemoryAllocation:%1").arg(a: type.detailType());
210 break;
211 case DebugMessage:
212 displayName = QString::fromLatin1(str: "DebugMessage:%1").arg(a: type.detailType());
213 break;
214 case MaximumMessage: {
215 const QQmlProfilerEventLocation eventLocation = type.location();
216 // generate hash
217 if (eventLocation.filename().isEmpty()) {
218 displayName = QString::fromLatin1(str: "Unknown");
219 } else {
220 const QString filePath = QUrl(eventLocation.filename()).path();
221 displayName = filePath.midRef(
222 position: filePath.lastIndexOf(c: QLatin1Char('/')) + 1) +
223 QLatin1Char(':') + QString::number(eventLocation.line());
224 }
225 break;
226 }
227 }
228
229 newType.setDisplayName(displayName);
230 d->eventTypes.append(t: newType);
231}
232
233void QmlProfilerData::computeQmlTime()
234{
235 // compute levels
236 qint64 level0Start = -1;
237 int level = 0;
238
239 for (const QQmlProfilerEvent &event : qAsConst(t&: d->events)) {
240 const QQmlProfilerEventType &type = d->eventTypes.at(i: event.typeIndex());
241 if (type.message() != MaximumMessage)
242 continue;
243
244 switch (type.rangeType()) {
245 case Compiling:
246 case Creating:
247 case Binding:
248 case HandlingSignal:
249 case Javascript:
250 switch (event.rangeStage()) {
251 case RangeStart:
252 if (level++ == 0)
253 level0Start = event.timestamp();
254 break;
255 case RangeEnd:
256 if (--level == 0)
257 d->qmlMeasuredTime += event.timestamp() - level0Start;
258 break;
259 default:
260 break;
261 }
262 break;
263 default:
264 break;
265 }
266 }
267}
268
269bool compareStartTimes(const QQmlProfilerEvent &t1, const QQmlProfilerEvent &t2)
270{
271 return t1.timestamp() < t2.timestamp();
272}
273
274void QmlProfilerData::sortStartTimes()
275{
276 if (d->events.count() < 2)
277 return;
278
279 // assuming startTimes is partially sorted
280 // identify blocks of events and sort them with quicksort
281 QVector<QQmlProfilerEvent>::iterator itFrom = d->events.end() - 2;
282 QVector<QQmlProfilerEvent>::iterator itTo = d->events.end() - 1;
283
284 while (itFrom != d->events.begin() && itTo != d->events.begin()) {
285 // find block to sort
286 while (itFrom != d->events.begin() && itTo->timestamp() > itFrom->timestamp()) {
287 --itTo;
288 itFrom = itTo - 1;
289 }
290
291 // if we're at the end of the list
292 if (itFrom == d->events.begin())
293 break;
294
295 // find block length
296 while (itFrom != d->events.begin() && itTo->timestamp() <= itFrom->timestamp())
297 --itFrom;
298
299 if (itTo->timestamp() <= itFrom->timestamp())
300 std::sort(first: itFrom, last: itTo + 1, comp: compareStartTimes);
301 else
302 std::sort(first: itFrom + 1, last: itTo + 1, comp: compareStartTimes);
303
304 // move to next block
305 itTo = itFrom;
306 itFrom = itTo - 1;
307 }
308}
309
310void QmlProfilerData::complete()
311{
312 setState(ProcessingData);
313 sortStartTimes();
314 computeQmlTime();
315 setState(Done);
316 emit dataReady();
317}
318
319bool QmlProfilerData::isEmpty() const
320{
321 return d->events.isEmpty();
322}
323
324struct StreamWriter {
325 QString error;
326
327 StreamWriter(const QString &filename)
328 {
329 if (!filename.isEmpty()) {
330 file.setFileName(filename);
331 if (!file.open(flags: QIODevice::WriteOnly)) {
332 error = QmlProfilerData::tr(s: "Could not open %1 for writing").arg(a: filename);
333 return;
334 }
335 } else {
336 if (!file.open(stdout, ioFlags: QIODevice::WriteOnly)) {
337 error = QmlProfilerData::tr(s: "Could not open stdout for writing");
338 return;
339 }
340 }
341
342 stream.setDevice(&file);
343 stream.setAutoFormatting(true);
344 stream.writeStartDocument();
345 writeStartElement(name: "trace");
346 }
347
348 ~StreamWriter() {
349 writeEndElement();
350 stream.writeEndDocument();
351 file.close();
352 }
353
354 template<typename Number>
355 void writeAttribute(const char *name, Number number)
356 {
357 stream.writeAttribute(QLatin1String(name), QString::number(number));
358 }
359
360 void writeAttribute(const char *name, const char *value)
361 {
362 stream.writeAttribute(qualifiedName: QLatin1String(name), value: QLatin1String(value));
363 }
364
365 void writeAttribute(const char *name, const QQmlProfilerEvent &event, int i, bool printZero = true)
366 {
367 const qint64 number = event.number<qint64>(i);
368 if (printZero || number != 0)
369 writeAttribute(name, number);
370 }
371
372 template<typename Number>
373 void writeTextElement(const char *name, Number number)
374 {
375 writeTextElement(name, QString::number(number));
376 }
377
378 void writeTextElement(const char *name, const char *value)
379 {
380 stream.writeTextElement(qualifiedName: QLatin1String(name), text: QLatin1String(value));
381 }
382
383 void writeTextElement(const char *name, const QString &value)
384 {
385 stream.writeTextElement(qualifiedName: QLatin1String(name), text: value);
386 }
387
388 void writeStartElement(const char *name)
389 {
390 stream.writeStartElement(qualifiedName: QLatin1String(name));
391 }
392
393 void writeEndElement()
394 {
395 stream.writeEndElement();
396 }
397
398private:
399 QFile file;
400 QXmlStreamWriter stream;
401};
402
403bool QmlProfilerData::save(const QString &filename)
404{
405 if (isEmpty()) {
406 emit error(tr(s: "No data to save"));
407 return false;
408 }
409
410 StreamWriter stream(filename);
411 if (!stream.error.isEmpty()) {
412 emit error(stream.error);
413 return false;
414 }
415
416 stream.writeAttribute(name: "version", value: PROFILER_FILE_VERSION);
417 stream.writeAttribute(name: "traceStart", number: traceStartTime());
418 stream.writeAttribute(name: "traceEnd", number: traceEndTime());
419
420 stream.writeStartElement(name: "eventData");
421 stream.writeAttribute(name: "totalTime", number: d->qmlMeasuredTime);
422
423 for (int typeIndex = 0, end = d->eventTypes.size(); typeIndex < end; ++typeIndex) {
424 const QQmlProfilerEventType &eventData = d->eventTypes.at(i: typeIndex);
425 stream.writeStartElement(name: "event");
426 stream.writeAttribute(name: "index", number: typeIndex);
427 if (!eventData.displayName().isEmpty())
428 stream.writeTextElement(name: "displayname", value: eventData.displayName());
429
430 stream.writeTextElement(name: "type", value: eventData.rangeType() == MaximumRangeType
431 ? qmlMessageAsString(type: eventData.message())
432 : qmlRangeTypeAsString(type: eventData.rangeType()));
433
434 const QQmlProfilerEventLocation location = eventData.location();
435 if (!location.filename().isEmpty())
436 stream.writeTextElement(name: "filename", value: location.filename());
437 if (location.line() >= 0)
438 stream.writeTextElement(name: "line", number: location.line());
439 if (location.column() >= 0)
440 stream.writeTextElement(name: "column", number: location.column());
441 if (!eventData.data().isEmpty())
442 stream.writeTextElement(name: "details", value: eventData.data());
443 if (eventData.rangeType() == Binding)
444 stream.writeTextElement(name: "bindingType", number: eventData.detailType());
445 else if (eventData.message() == Event) {
446 switch (eventData.detailType()) {
447 case AnimationFrame:
448 stream.writeTextElement(name: "animationFrame", number: eventData.detailType());
449 break;
450 case Key:
451 stream.writeTextElement(name: "keyEvent", number: eventData.detailType());
452 break;
453 case Mouse:
454 stream.writeTextElement(name: "mouseEvent", number: eventData.detailType());
455 break;
456 }
457 } else if (eventData.message() == PixmapCacheEvent)
458 stream.writeTextElement(name: "cacheEventType", number: eventData.detailType());
459 else if (eventData.message() == SceneGraphFrame)
460 stream.writeTextElement(name: "sgEventType", number: eventData.detailType());
461 else if (eventData.message() == MemoryAllocation)
462 stream.writeTextElement(name: "memoryEventType", number: eventData.detailType());
463 stream.writeEndElement();
464 }
465 stream.writeEndElement(); // eventData
466
467 stream.writeStartElement(name: "profilerDataModel");
468
469 auto sendEvent = [&](const QQmlProfilerEvent &event, qint64 duration = 0) {
470 const QQmlProfilerEventType &type = d->eventTypes.at(i: event.typeIndex());
471 stream.writeStartElement(name: "range");
472 stream.writeAttribute(name: "startTime", number: event.timestamp());
473 if (duration != 0)
474 stream.writeAttribute(name: "duration", number: duration);
475 stream.writeAttribute(name: "eventIndex", number: event.typeIndex());
476 if (type.message() == Event) {
477 if (type.detailType() == AnimationFrame) {
478 // special: animation frame
479 stream.writeAttribute(name: "framerate", event, i: 0);
480 stream.writeAttribute(name: "animationcount", event, i: 1);
481 stream.writeAttribute(name: "thread", event, i: 2);
482 } else if (type.detailType() == Key || type.detailType() == Mouse) {
483 // numerical value here, to keep the format a bit more compact
484 stream.writeAttribute(name: "type", event, i: 0);
485 stream.writeAttribute(name: "data1", event, i: 1);
486 stream.writeAttribute(name: "data2", event, i: 2);
487 }
488 } else if (type.message() == PixmapCacheEvent) {
489 // special: pixmap cache event
490 if (type.detailType() == PixmapSizeKnown) {
491 stream.writeAttribute(name: "width", event, i: 0);
492 stream.writeAttribute(name: "height", event, i: 1);
493 } else if (type.detailType() == PixmapReferenceCountChanged
494 || type.detailType() == PixmapCacheCountChanged) {
495 stream.writeAttribute(name: "refCount", event, i: 1);
496 }
497 } else if (type.message() == SceneGraphFrame) {
498 stream.writeAttribute(name: "timing1", event, i: 0, printZero: false);
499 stream.writeAttribute(name: "timing2", event, i: 1, printZero: false);
500 stream.writeAttribute(name: "timing3", event, i: 2, printZero: false);
501 stream.writeAttribute(name: "timing4", event, i: 3, printZero: false);
502 stream.writeAttribute(name: "timing5", event, i: 4, printZero: false);
503 } else if (type.message() == MemoryAllocation) {
504 stream.writeAttribute(name: "amount", event, i: 0);
505 }
506 stream.writeEndElement();
507 };
508
509 QQueue<QQmlProfilerEvent> pointEvents;
510 QQueue<QQmlProfilerEvent> rangeStarts[MaximumRangeType];
511 QStack<qint64> rangeEnds[MaximumRangeType];
512 int level = 0;
513
514 auto sendPending = [&]() {
515 forever {
516 int minimum = MaximumRangeType;
517 qint64 minimumTime = std::numeric_limits<qint64>::max();
518 for (int i = 0; i < MaximumRangeType; ++i) {
519 const QQueue<QQmlProfilerEvent> &starts = rangeStarts[i];
520 if (starts.isEmpty())
521 continue;
522 if (starts.head().timestamp() < minimumTime) {
523 minimumTime = starts.head().timestamp();
524 minimum = i;
525 }
526 }
527 if (minimum == MaximumRangeType)
528 break;
529
530 while (!pointEvents.isEmpty() && pointEvents.front().timestamp() < minimumTime)
531 sendEvent(pointEvents.dequeue());
532
533 sendEvent(rangeStarts[minimum].dequeue(),
534 rangeEnds[minimum].pop() - minimumTime);
535 }
536 };
537
538 for (const QQmlProfilerEvent &event : qAsConst(t&: d->events)) {
539 const QQmlProfilerEventType &type = d->eventTypes.at(i: event.typeIndex());
540
541 if (type.rangeType() != MaximumRangeType) {
542 QQueue<QQmlProfilerEvent> &starts = rangeStarts[type.rangeType()];
543 switch (event.rangeStage()) {
544 case RangeStart: {
545 ++level;
546 starts.enqueue(t: event);
547 break;
548 }
549 case RangeEnd: {
550 QStack<qint64> &ends = rangeEnds[type.rangeType()];
551 if (starts.length() > ends.length()) {
552 ends.push(t: event.timestamp());
553 if (--level == 0)
554 sendPending();
555 }
556 break;
557 }
558 default:
559 break;
560 }
561 } else {
562 if (level == 0)
563 sendEvent(event);
564 else
565 pointEvents.enqueue(t: event);
566 }
567 }
568
569 for (int i = 0; i < MaximumRangeType; ++i) {
570 while (rangeEnds[i].length() < rangeStarts[i].length()) {
571 rangeEnds[i].push(t: d->traceEndTime);
572 --level;
573 }
574 }
575
576 sendPending();
577
578 stream.writeEndElement(); // profilerDataModel
579
580 return true;
581}
582
583void QmlProfilerData::setState(QmlProfilerData::State state)
584{
585 // It's not an error, we are continuously calling "AcquiringData" for example
586 if (d->state == state)
587 return;
588
589 switch (state) {
590 case Empty:
591 // if it's not empty, complain but go on
592 if (!isEmpty())
593 emit error("Invalid qmlprofiler state change (Empty)");
594 break;
595 case AcquiringData:
596 // we're not supposed to receive new data while processing older data
597 if (d->state == ProcessingData)
598 emit error("Invalid qmlprofiler state change (AcquiringData)");
599 break;
600 case ProcessingData:
601 if (d->state != AcquiringData)
602 emit error("Invalid qmlprofiler state change (ProcessingData)");
603 break;
604 case Done:
605 if (d->state != ProcessingData && d->state != Empty)
606 emit error("Invalid qmlprofiler state change (Done)");
607 break;
608 default:
609 emit error("Trying to set unknown state in events list");
610 break;
611 }
612
613 d->state = state;
614 emit stateChanged();
615
616 // special: if we were done with an empty list, clean internal data and go back to empty
617 if (d->state == Done && isEmpty()) {
618 clear();
619 }
620 return;
621}
622
623int QmlProfilerData::numLoadedEventTypes() const
624{
625 return d->eventTypes.length();
626}
627
628#include "moc_qmlprofilerdata.cpp"
629

source code of qtdeclarative/tools/qmlprofiler/qmlprofilerdata.cpp