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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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