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