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
334 stream.writeEndDocument();
335 file.close();
336 }
337
338 template<typename Number>
339 void writeAttribute(const char *name, Number number)
340 {
341 stream.writeAttribute(QLatin1String(name), QString::number(number));
342 }
343
344 void writeAttribute(const char *name, const char *value)
345 {
346 stream.writeAttribute(QLatin1String(name), QLatin1String(value));
347 }
348
349 void writeAttribute(const char *name, const QQmlProfilerEvent &event, int i, bool printZero = true)
350 {
351 const qint64 number = event.number<qint64>(i);
352 if (printZero || number != 0)
353 writeAttribute(name, number);
354 }
355
356 template<typename Number>
357 void writeTextElement(const char *name, Number number)
358 {
359 writeTextElement(name, QString::number(number));
360 }
361
362 void writeTextElement(const char *name, const char *value)
363 {
364 stream.writeTextElement(QLatin1String(name), QLatin1String(value));
365 }
366
367 void writeTextElement(const char *name, const QString &value)
368 {
369 stream.writeTextElement(QLatin1String(name), value);
370 }
371
372 void writeStartElement(const char *name)
373 {
374 stream.writeStartElement(QLatin1String(name));
375 }
376
378 {
379 stream.writeEndElement();
380 }
381
382private:
383 QFile file;
384 QXmlStreamWriter stream;
385};
386
388{
391 qxp::function_ref<void(const QQmlProfilerEvent &, qint64)> &&sendEvent)
392 : d(d)
394 {}
395
396 void run();
397
398private:
399 void handleRangeEvent(const QQmlProfilerEvent &event, const QQmlProfilerEventType &type);
400 void sendPending();
401 void endLevel0();
402
403 const QQmlProfilerQtdWriterPrivate *d = nullptr;
404 const qxp::function_ref<void(const QQmlProfilerEvent &, qint64)> sendEvent;
405
406 QQueue<QQmlProfilerEvent> pointEvents;
407 QList<QQmlProfilerEvent> rangeStarts[MaximumRangeType];
408 QList<qint64> rangeEnds[MaximumRangeType];
409
410 int level = 0;
411};
412
413void DataIterator::handleRangeEvent(
414 const QQmlProfilerEvent &event, const QQmlProfilerEventType &type)
415{
416 QList<QQmlProfilerEvent> &starts = rangeStarts[type.rangeType()];
417 switch (event.rangeStage()) {
418 case RangeStart: {
419 ++level;
420 starts.append(event);
421 break;
422 }
423 case RangeEnd: {
424 const qint64 invalidTimestamp = -1;
425 QList<qint64> &ends = rangeEnds[type.rangeType()];
426
427 // -1 because all valid timestamps are >= 0.
428 ends.resize(starts.size(), invalidTimestamp);
429
430 qsizetype i = starts.size();
431 while (ends[--i] != invalidTimestamp) {}
432
433 Q_ASSERT(i >= 0);
434 Q_ASSERT(starts[i].timestamp() <= event.timestamp());
435
436 ends[i] = event.timestamp();
437 if (--level == 0)
438 endLevel0();
439 break;
440 }
441 default:
442 break;
443 }
444}
445
446void DataIterator::sendPending()
447{
448 // Send all pending events in the order of their start times.
449
450 qsizetype index[MaximumRangeType] = { 0, 0, 0, 0, 0, 0 };
451 while (true) {
452
453 // Find the range type with the minimum start time.
454 qsizetype minimum = MaximumRangeType;
455 qint64 minimumTime = std::numeric_limits<qint64>::max();
456 for (qsizetype i = 0; i < MaximumRangeType; ++i) {
457 const QList<QQmlProfilerEvent> &starts = rangeStarts[i];
458 if (starts.size() == index[i])
459 continue;
460 const qint64 timestamp = starts[index[i]].timestamp();
461 if (timestamp < minimumTime) {
462 minimumTime = timestamp;
463 minimum = i;
464 }
465 }
466 if (minimum == MaximumRangeType)
467 break;
468
469 // Send all point events that happened before the range we've found.
470 while (!pointEvents.isEmpty() && pointEvents.front().timestamp() < minimumTime)
471 sendEvent(pointEvents.dequeue(), 0);
472
473 // Send the range itself
474 sendEvent(rangeStarts[minimum][index[minimum]],
475 rangeEnds[minimum][index[minimum]] - minimumTime);
476
477 // Bump the index so that we don't send the same range again
478 ++index[minimum];
479 }
480}
481
482void DataIterator::endLevel0()
483{
484 sendPending();
485 for (qsizetype i = 0; i < MaximumRangeType; ++i) {
486 rangeStarts[i].clear();
487 rangeEnds[i].clear();
488 }
489}
490
492{
493 for (const QQmlProfilerEvent &event : std::as_const(d->events)) {
494 const QQmlProfilerEventType &type = d->eventTypes.at(event.typeIndex());
495 if (type.rangeType() != MaximumRangeType)
496 handleRangeEvent(event, type);
497 else if (level == 0)
498 sendEvent(event, 0);
499 else
500 pointEvents.enqueue(event);
501 }
502
503 for (qsizetype i = 0; i < MaximumRangeType; ++i) {
504 while (rangeEnds[i].size() < rangeStarts[i].size()) {
505 rangeEnds[i].append(d->traceEndTime);
506 --level;
507 }
508 }
509
510 sendPending();
511}
512
513bool QQmlProfilerQtdWriter::save(const QString &filename)
514{
515 Q_D(QQmlProfilerQtdWriter);
516
517 if (isEmpty()) {
518 emit error(tr("No data to save"));
519 return false;
520 }
521
522 StreamWriter stream(filename);
523 if (!stream.error.isEmpty()) {
524 emit error(stream.error);
525 return false;
526 }
527
529 stream.writeAttribute("traceStart", d->traceStartTime);
530 stream.writeAttribute("traceEnd", d->traceEndTime);
531
532 stream.writeStartElement("eventData");
533 stream.writeAttribute("totalTime", d->qmlMeasuredTime);
534
535 for (int typeIndex = 0, end = d->eventTypes.size(); typeIndex < end; ++typeIndex) {
536 const QQmlProfilerEventType &eventData = d->eventTypes.at(typeIndex);
537 stream.writeStartElement("event");
538 stream.writeAttribute("index", typeIndex);
539 if (!eventData.displayName().isEmpty())
540 stream.writeTextElement("displayname", eventData.displayName());
541
542 stream.writeTextElement("type", eventData.rangeType() == MaximumRangeType
543 ? qmlMessageAsString(eventData.message())
544 : qmlRangeTypeAsString(eventData.rangeType()));
545
546 const QQmlProfilerEventLocation location = eventData.location();
547 if (!location.filename().isEmpty())
548 stream.writeTextElement("filename", location.filename());
549 if (location.line() >= 0)
550 stream.writeTextElement("line", location.line());
551 if (location.column() >= 0)
552 stream.writeTextElement("column", location.column());
553 if (!eventData.data().isEmpty())
554 stream.writeTextElement("details", eventData.data());
555 if (eventData.rangeType() == Binding)
556 stream.writeTextElement("bindingType", eventData.detailType());
557 else if (eventData.message() == Event) {
558 switch (eventData.detailType()) {
559 case AnimationFrame:
560 stream.writeTextElement("animationFrame", eventData.detailType());
561 break;
562 case Key:
563 stream.writeTextElement("keyEvent", eventData.detailType());
564 break;
565 case Mouse:
566 stream.writeTextElement("mouseEvent", eventData.detailType());
567 break;
568 }
569 } else if (eventData.message() == PixmapCacheEvent)
570 stream.writeTextElement("cacheEventType", eventData.detailType());
571 else if (eventData.message() == SceneGraphFrame)
572 stream.writeTextElement("sgEventType", eventData.detailType());
573 else if (eventData.message() == MemoryAllocation)
574 stream.writeTextElement("memoryEventType", eventData.detailType());
575 stream.writeEndElement();
576 }
577 stream.writeEndElement(); // eventData
578
579 stream.writeStartElement("profilerDataModel");
580
581 auto sendEvent = [&](const QQmlProfilerEvent &event, qint64 duration = 0) {
582 Q_ASSERT(duration >= 0);
583 const QQmlProfilerEventType &type = d->eventTypes.at(event.typeIndex());
584 stream.writeStartElement("range");
585 stream.writeAttribute("startTime", event.timestamp());
586 if (duration != 0)
587 stream.writeAttribute("duration", duration);
588 stream.writeAttribute("eventIndex", event.typeIndex());
589 if (type.message() == Event) {
590 if (type.detailType() == AnimationFrame) {
591 // special: animation frame
592 stream.writeAttribute("framerate", event, 0);
593 stream.writeAttribute("animationcount", event, 1);
594 stream.writeAttribute("thread", event, 2);
595 } else if (type.detailType() == Key || type.detailType() == Mouse) {
596 // numerical value here, to keep the format a bit more compact
597 stream.writeAttribute("type", event, 0);
598 stream.writeAttribute("data1", event, 1);
599 stream.writeAttribute("data2", event, 2);
600 }
601 } else if (type.message() == PixmapCacheEvent) {
602 // special: pixmap cache event
603 if (type.detailType() == PixmapSizeKnown) {
604 stream.writeAttribute("width", event, 0);
605 stream.writeAttribute("height", event, 1);
606 } else if (type.detailType() == PixmapReferenceCountChanged
607 || type.detailType() == PixmapCacheCountChanged) {
608 stream.writeAttribute("refCount", event, 1);
609 }
610 } else if (type.message() == SceneGraphFrame) {
611 stream.writeAttribute("timing1", event, 0, false);
612 stream.writeAttribute("timing2", event, 1, false);
613 stream.writeAttribute("timing3", event, 2, false);
614 stream.writeAttribute("timing4", event, 3, false);
615 stream.writeAttribute("timing5", event, 4, false);
616 } else if (type.message() == MemoryAllocation) {
617 stream.writeAttribute("amount", event, 0);
618 }
619 stream.writeEndElement();
620 };
621
622 DataIterator(d, std::move(sendEvent)).run();
623
624 stream.writeEndElement(); // profilerDataModel
625
626 return true;
627}
628
630{
631 // It's not an error, we are continuously calling "AcquiringData" for example
632 if (state == newState)
633 return;
634
635 Q_Q(QQmlProfilerQtdWriter);
636 switch (newState) {
637 case Empty:
638 // if it's not empty, complain but go on
639 if (!isEmpty())
640 emit q->error("Invalid qmlprofiler state change (Empty)"_L1);
641 break;
642 case AcquiringData:
643 // we're not supposed to receive new data while processing older data
644 if (state == ProcessingData)
645 emit q->error("Invalid qmlprofiler state change (AcquiringData)"_L1);
646 break;
647 case ProcessingData:
648 if (state != AcquiringData)
649 emit q->error("Invalid qmlprofiler state change (ProcessingData)"_L1);
650 break;
651 case Done:
652 if (state != ProcessingData && state != Empty)
653 emit q->error("Invalid qmlprofiler state change (Done)"_L1);
654 break;
655 default:
656 emit q->error("Trying to set unknown state in events list"_L1);
657 break;
658 }
659
660 state = newState;
661
662 // special: if we were done with an empty list, clean internal data and go back to empty
663 if (state == Done && isEmpty())
664 q->clear();
665 return;
666}
667
669{
670 return d_func()->eventTypes.size();
671}
672
674{
675 return d_func()->events.size();
676}
677
678
679QT_END_NAMESPACE
680
681#include "moc_qqmlprofilerqtdwriter_p.cpp"
QList< QQmlProfilerEventType > eventTypes
QList< QQmlProfilerEvent > events
void addEvent(const QQmlProfilerEvent &event) final
void complete(qint64 maximumTime) final
void endTrace(qint64 time, const QList< int > &engineIds) final
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)