From 2311fc6a829ba1695f7e60bde09c70f4b717b5ce Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Thu, 11 Jan 2024 16:19:27 +0100 Subject: [PATCH] qmlprofiler: Fix data sorting When analyzing a range with multiple child ranges, qmlprofiler would create a rather random association between the start and end events. Pick-to: 6.2 Change-Id: I564d2c74656dda1cb0963c75cd7b947a7f86d05e Reviewed-by: Fabian Kosmale (cherry picked from commit cc761e9c5a1c5249f4760c8e5e57755a3fe042ca) Reviewed-by: Qt Cherry-pick Bot (cherry picked from commit 378de2f412488b897dd8e6c67f440376d8b95b48) (cherry picked from commit 06fc18637152ad691c6aa5d7dc615c7009576ce8) --- tools/qmlprofiler/qmlprofilerdata.cpp | 210 ++++++++++++++++---------- 1 file changed, 134 insertions(+), 76 deletions(-) diff --git a/tools/qmlprofiler/qmlprofilerdata.cpp b/tools/qmlprofiler/qmlprofilerdata.cpp index 9dbdc45325..f4ba76283e 100644 --- a/tools/qmlprofiler/qmlprofilerdata.cpp +++ b/tools/qmlprofiler/qmlprofilerdata.cpp @@ -3,14 +3,12 @@ #include "qmlprofilerdata.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include @@ -375,6 +373,132 @@ private: QXmlStreamWriter stream; }; +struct DataIterator +{ + DataIterator( + const QmlProfilerDataPrivate *d, + qxp::function_ref &&sendEvent) + : d(d) + , sendEvent(std::move(sendEvent)) + {} + + void run(); + +private: + void handleRangeEvent(const QQmlProfilerEvent &event, const QQmlProfilerEventType &type); + void sendPending(); + void endLevel0(); + + const QmlProfilerDataPrivate *d = nullptr; + const qxp::function_ref sendEvent; + + QQueue pointEvents; + QList rangeStarts[MaximumRangeType]; + QList rangeEnds[MaximumRangeType]; + + int level = 0; +}; + +void DataIterator::handleRangeEvent( + const QQmlProfilerEvent &event, const QQmlProfilerEventType &type) +{ + QList &starts = rangeStarts[type.rangeType()]; + switch (event.rangeStage()) { + case RangeStart: { + ++level; + starts.append(event); + break; + } + case RangeEnd: { + const qint64 invalidTimestamp = -1; + QList &ends = rangeEnds[type.rangeType()]; + + // -1 because all valid timestamps are >= 0. + ends.resize(starts.size(), invalidTimestamp); + + qsizetype i = starts.size(); + while (ends[--i] != invalidTimestamp) {} + + Q_ASSERT(i >= 0); + Q_ASSERT(starts[i].timestamp() <= event.timestamp()); + + ends[i] = event.timestamp(); + if (--level == 0) + endLevel0(); + break; + } + default: + break; + } +} + +void DataIterator::sendPending() +{ + // Send all pending events in the order of their start times. + + qsizetype index[MaximumRangeType] = { 0, 0, 0, 0, 0, 0 }; + while (true) { + + // Find the range type with the minimum start time. + qsizetype minimum = MaximumRangeType; + qint64 minimumTime = std::numeric_limits::max(); + for (qsizetype i = 0; i < MaximumRangeType; ++i) { + const QList &starts = rangeStarts[i]; + if (starts.size() == index[i]) + continue; + const qint64 timestamp = starts[index[i]].timestamp(); + if (timestamp < minimumTime) { + minimumTime = timestamp; + minimum = i; + } + } + if (minimum == MaximumRangeType) + break; + + // Send all point events that happened before the range we've found. + while (!pointEvents.isEmpty() && pointEvents.front().timestamp() < minimumTime) + sendEvent(pointEvents.dequeue(), 0); + + // Send the range itself + sendEvent(rangeStarts[minimum][index[minimum]], + rangeEnds[minimum][index[minimum]] - minimumTime); + + // Bump the index so that we don't send the same range again + ++index[minimum]; + } +} + +void DataIterator::endLevel0() +{ + sendPending(); + for (qsizetype i = 0; i < MaximumRangeType; ++i) { + rangeStarts[i].clear(); + rangeEnds[i].clear(); + } +} + +void DataIterator::run() +{ + for (const QQmlProfilerEvent &event : std::as_const(d->events)) { + const QQmlProfilerEventType &type = d->eventTypes.at(event.typeIndex()); + if (type.rangeType() != MaximumRangeType) + handleRangeEvent(event, type); + else if (level == 0) + sendEvent(event, 0); + else + pointEvents.enqueue(event); + } + + for (qsizetype i = 0; i < MaximumRangeType; ++i) { + while (rangeEnds[i].size() < rangeStarts[i].size()) { + rangeEnds[i].append(d->traceEndTime); + --level; + } + } + + sendPending(); +} + bool QmlProfilerData::save(const QString &filename) { if (isEmpty()) { @@ -442,6 +566,7 @@ bool QmlProfilerData::save(const QString &filename) stream.writeStartElement("profilerDataModel"); auto sendEvent = [&](const QQmlProfilerEvent &event, qint64 duration = 0) { + Q_ASSERT(duration >= 0); const QQmlProfilerEventType &type = d->eventTypes.at(event.typeIndex()); stream.writeStartElement("range"); stream.writeAttribute("startTime", event.timestamp()); @@ -481,74 +606,7 @@ bool QmlProfilerData::save(const QString &filename) stream.writeEndElement(); }; - QQueue pointEvents; - QQueue rangeStarts[MaximumRangeType]; - QStack rangeEnds[MaximumRangeType]; - int level = 0; - - auto sendPending = [&]() { - forever { - int minimum = MaximumRangeType; - qint64 minimumTime = std::numeric_limits::max(); - for (int i = 0; i < MaximumRangeType; ++i) { - const QQueue &starts = rangeStarts[i]; - if (starts.isEmpty()) - continue; - if (starts.head().timestamp() < minimumTime) { - minimumTime = starts.head().timestamp(); - minimum = i; - } - } - if (minimum == MaximumRangeType) - break; - - while (!pointEvents.isEmpty() && pointEvents.front().timestamp() < minimumTime) - sendEvent(pointEvents.dequeue()); - - sendEvent(rangeStarts[minimum].dequeue(), - rangeEnds[minimum].pop() - minimumTime); - } - }; - - for (const QQmlProfilerEvent &event : std::as_const(d->events)) { - const QQmlProfilerEventType &type = d->eventTypes.at(event.typeIndex()); - - if (type.rangeType() != MaximumRangeType) { - QQueue &starts = rangeStarts[type.rangeType()]; - switch (event.rangeStage()) { - case RangeStart: { - ++level; - starts.enqueue(event); - break; - } - case RangeEnd: { - QStack &ends = rangeEnds[type.rangeType()]; - if (starts.size() > ends.size()) { - ends.push(event.timestamp()); - if (--level == 0) - sendPending(); - } - break; - } - default: - break; - } - } else { - if (level == 0) - sendEvent(event); - else - pointEvents.enqueue(event); - } - } - - for (int i = 0; i < MaximumRangeType; ++i) { - while (rangeEnds[i].size() < rangeStarts[i].size()) { - rangeEnds[i].push(d->traceEndTime); - --level; - } - } - - sendPending(); + DataIterator(d, std::move(sendEvent)).run(); stream.writeEndElement(); // profilerDataModel