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 <fabian.kosmale@qt.io>
(cherry picked from commit cc761e9c5a)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit 378de2f412)
(cherry picked from commit 06fc186371)
This commit is contained in:
Ulf Hermann 2024-01-11 16:19:27 +01:00 committed by Qt Cherry-pick Bot
parent 461d31d2d5
commit 2311fc6a82
1 changed files with 134 additions and 76 deletions

View File

@ -3,14 +3,12 @@
#include "qmlprofilerdata.h"
#include <QStringList>
#include <QUrl>
#include <QHash>
#include <QFile>
#include <QXmlStreamReader>
#include <QRegularExpression>
#include <QQueue>
#include <QStack>
#include <QtCore/qfile.h>
#include <QtCore/qqueue.h>
#include <QtCore/qregularexpression.h>
#include <QtCore/qurl.h>
#include <QtCore/qxmlstream.h>
#include <QtCore/qxpfunctional.h>
#include <limits>
@ -375,6 +373,132 @@ private:
QXmlStreamWriter stream;
};
struct DataIterator
{
DataIterator(
const QmlProfilerDataPrivate *d,
qxp::function_ref<void(const QQmlProfilerEvent &, qint64)> &&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<void(const QQmlProfilerEvent &, qint64)> sendEvent;
QQueue<QQmlProfilerEvent> pointEvents;
QList<QQmlProfilerEvent> rangeStarts[MaximumRangeType];
QList<qint64> rangeEnds[MaximumRangeType];
int level = 0;
};
void DataIterator::handleRangeEvent(
const QQmlProfilerEvent &event, const QQmlProfilerEventType &type)
{
QList<QQmlProfilerEvent> &starts = rangeStarts[type.rangeType()];
switch (event.rangeStage()) {
case RangeStart: {
++level;
starts.append(event);
break;
}
case RangeEnd: {
const qint64 invalidTimestamp = -1;
QList<qint64> &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<qint64>::max();
for (qsizetype i = 0; i < MaximumRangeType; ++i) {
const QList<QQmlProfilerEvent> &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<QQmlProfilerEvent> pointEvents;
QQueue<QQmlProfilerEvent> rangeStarts[MaximumRangeType];
QStack<qint64> rangeEnds[MaximumRangeType];
int level = 0;
auto sendPending = [&]() {
forever {
int minimum = MaximumRangeType;
qint64 minimumTime = std::numeric_limits<qint64>::max();
for (int i = 0; i < MaximumRangeType; ++i) {
const QQueue<QQmlProfilerEvent> &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<QQmlProfilerEvent> &starts = rangeStarts[type.rangeType()];
switch (event.rangeStage()) {
case RangeStart: {
++level;
starts.enqueue(event);
break;
}
case RangeEnd: {
QStack<qint64> &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