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