Files
qt-creator/src/plugins/qmlprofiler/qmlprofilertracefile.cpp
Kai Köhne 56baf8c058 Remove GPL-3.0+ from license identifiers
Since we also license under GPL-3.0 WITH Qt-GPL-exception-1.0,
this applies only to a hypothetical newer version of GPL, that doesn't
exist yet. If such a version emerges, we can still decide to relicense...

While at it, replace (deprecated) GPL-3.0 with more explicit GPL-3.0-only

Change was done by running

  find . -type f -exec perl -pi -e "s/LicenseRef-Qt-Commercial OR GPL-3.0\+ OR GPL-3.0 WITH Qt-GPL-exception-1.0/LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0/g" {} \;

Change-Id: I5097e6ce8d10233993ee30d7e25120e2659eb10b
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
2023-01-06 11:15:13 +00:00

857 lines
30 KiB
C++

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qmlprofilerconstants.h"
#include "qmlprofilernotesmodel.h"
#include "qmlprofilertr.h"
#include "qmlprofilertracefile.h"
#include <utils/qtcassert.h>
#include <QIODevice>
#include <QStringList>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QStack>
#include <QDebug>
#include <QFile>
#include <QBuffer>
#include <QDataStream>
namespace QmlProfiler {
namespace Internal {
const char PROFILER_FILE_VERSION[] = "1.02";
static const char *RANGE_TYPE_STRINGS[] = {
"Painting",
"Compiling",
"Creating",
"Binding",
"HandlingSignal",
"Javascript"
};
Q_STATIC_ASSERT(sizeof(RANGE_TYPE_STRINGS) == MaximumRangeType * sizeof(const char *));
static const char *MESSAGE_STRINGS[] = {
// So far only pixmap and scenegraph are used. The others are padding.
"Event",
"RangeStart",
"RangeData",
"RangeLocation",
"RangeEnd",
"Complete",
"PixmapCache",
"SceneGraph",
"MemoryAllocation",
"DebugMessage",
"Quick3D"
};
Q_STATIC_ASSERT(sizeof(MESSAGE_STRINGS) == MaximumMessage * sizeof(const char *));
#define _(X) QLatin1String(X)
//
// "be strict in your output but tolerant in your inputs"
//
static QPair<Message, RangeType> qmlTypeAsEnum(const QString &typeString)
{
QPair<Message, RangeType> ret(UndefinedMessage, UndefinedRangeType);
for (int i = 0; i < MaximumMessage; ++i) {
if (typeString == _(MESSAGE_STRINGS[i])) {
ret.first = static_cast<Message>(i);
break;
}
}
for (int i = 0; i < MaximumRangeType; ++i) {
if (typeString == _(RANGE_TYPE_STRINGS[i])) {
ret.second = static_cast<RangeType>(i);
break;
}
}
if (ret.first == UndefinedMessage && ret.second == UndefinedRangeType) {
bool isNumber = false;
int type = typeString.toUInt(&isNumber);
if (isNumber && type < MaximumRangeType)
// Allow saving ranges as numbers, but not messages.
ret.second = static_cast<RangeType>(type);
}
return ret;
}
static QString qmlTypeAsString(Message message, RangeType rangeType)
{
if (rangeType < MaximumRangeType)
return _(RANGE_TYPE_STRINGS[rangeType]);
else if (message < MaximumMessage)
return _(MESSAGE_STRINGS[message]);
else
return QString::number((int)rangeType);
}
QmlProfilerTraceFile::QmlProfilerTraceFile(QObject *parent) : Timeline::TimelineTraceFile(parent)
{
static int meta[] = {
qRegisterMetaType<QVector<QmlEvent> >(),
qRegisterMetaType<QVector<QmlEventType> >(),
qRegisterMetaType<QVector<QmlNote> >()
};
Q_UNUSED(meta)
}
void QmlProfilerTraceFile::load(QIODevice *device)
{
const QFile *file = qobject_cast<QFile *>(device);
if (file && file->fileName().endsWith(Constants::QtdFileExtension))
loadQtd(device);
else
loadQzt(device);
}
void QmlProfilerTraceFile::save(QIODevice *device)
{
const QFile *file = qobject_cast<QFile *>(device);
if (file && file->fileName().endsWith(Constants::QtdFileExtension))
saveQtd(device);
else
saveQzt(device);
}
void QmlProfilerTraceFile::loadQtd(QIODevice *device)
{
QXmlStreamReader stream(device);
bool validVersion = true;
while (validVersion && !stream.atEnd() && !stream.hasError() && !isCanceled()) {
QXmlStreamReader::TokenType token = stream.readNext();
const QStringView elementName = stream.name();
switch (token) {
case QXmlStreamReader::StartDocument : continue;
case QXmlStreamReader::StartElement : {
if (elementName == _("trace")) {
QXmlStreamAttributes attributes = stream.attributes();
if (attributes.hasAttribute(_("version")))
validVersion = attributes.value(_("version")) == _(PROFILER_FILE_VERSION);
else
validVersion = false;
if (attributes.hasAttribute(_("traceStart")))
setTraceStart(attributes.value(_("traceStart")).toLongLong());
if (attributes.hasAttribute(_("traceEnd")))
setTraceEnd(attributes.value(_("traceEnd")).toLongLong());
}
if (elementName == _("eventData")) {
loadEventTypes(stream);
break;
}
if (elementName == _("profilerDataModel")) {
loadEvents(stream);
break;
}
if (elementName == _("noteData")) {
loadNotes(stream);
break;
}
break;
}
default: break;
}
}
if (stream.hasError())
fail(Tr::tr("Error while parsing trace data file: %1").arg(stream.errorString()));
else
finish();
}
void QmlProfilerTraceFile::loadQzt(QIODevice *device)
{
QDataStream stream(device);
stream.setVersion(QDataStream::Qt_5_5);
QByteArray magic;
stream >> magic;
if (magic != QByteArray("QMLPROFILER")) {
fail(Tr::tr("Invalid magic: %1").arg(QLatin1String(magic)));
return;
}
qint32 dataStreamVersion;
stream >> dataStreamVersion;
if (dataStreamVersion > QDataStream::Qt_DefaultCompiledVersion) {
fail(Tr::tr("Unknown data stream version: %1").arg(dataStreamVersion));
return;
}
stream.setVersion(dataStreamVersion);
qint64 traceStart, traceEnd;
stream >> traceStart >> traceEnd;
setTraceStart(traceStart);
setTraceEnd(traceEnd);
QBuffer buffer;
QDataStream bufferStream(&buffer);
bufferStream.setVersion(dataStreamVersion);
QByteArray data;
setDeviceProgress(device);
QmlProfilerModelManager *manager = modelManager();
if (!isCanceled()) {
stream >> data;
buffer.setData(qUncompress(data));
buffer.open(QIODevice::ReadOnly);
quint32 numEventTypes;
bufferStream >> numEventTypes;
if (numEventTypes > quint32(std::numeric_limits<int>::max())) {
fail(Tr::tr("Excessive number of event types: %1").arg(numEventTypes));
return;
}
for (int typeId = 0; typeId < static_cast<int>(numEventTypes); ++typeId) {
QmlEventType type;
bufferStream >> type;
manager->setEventType(typeId, std::move(type));
}
buffer.close();
setDeviceProgress(device);
}
if (!isCanceled()) {
stream >> data;
buffer.setData(qUncompress(data));
buffer.open(QIODevice::ReadOnly);
QVector<QmlNote> notes;
bufferStream >> notes;
buffer.close();
qmlNotes()->setNotes(notes);
setDeviceProgress(device);
}
while (!stream.atEnd() && !isCanceled()) {
stream >> data;
buffer.setData(qUncompress(data));
buffer.open(QIODevice::ReadOnly);
while (!buffer.atEnd() && !isCanceled()) {
QmlEvent event;
bufferStream >> event;
if (bufferStream.status() == QDataStream::Ok) {
if (event.typeIndex() >= traceManager()->numEventTypes()) {
fail(Tr::tr("Invalid type index %1").arg(event.typeIndex()));
return;
}
addFeature(manager->eventType(event.typeIndex()).feature());
if (event.timestamp() < 0)
event.setTimestamp(0);
} else if (bufferStream.status() == QDataStream::ReadPastEnd) {
break; // Apparently EOF is a character so we end up here after the last event.
} else if (bufferStream.status() == QDataStream::ReadCorruptData) {
fail(Tr::tr("Corrupt data before position %1.").arg(device->pos()));
return;
} else {
Q_UNREACHABLE();
}
manager->appendEvent(std::move(event));
}
buffer.close();
setDeviceProgress(device);
}
finish();
}
void QmlProfilerTraceFile::addEventsProgress(qint64 timestamp)
{
addProgressValue(static_cast<float>(timestamp) / static_cast<float>(traceEnd() - traceStart())
* ProgressEvents);
}
void QmlProfilerTraceFile::addStageProgress(QmlProfilerTraceFile::ProgressValues stage)
{
addProgressValue(stage);
}
void QmlProfilerTraceFile::loadEventTypes(QXmlStreamReader &stream)
{
QTC_ASSERT(stream.name() == _("eventData"), return);
int typeIndex = -1;
QPair<Message, RangeType> messageAndRange(UndefinedMessage, UndefinedRangeType);
int detailType = -1;
QString displayName;
QString data;
QString filename;
int line = 0, column = 0;
auto clearType = [&](){
messageAndRange = QPair<Message, RangeType>(UndefinedMessage, UndefinedRangeType);
detailType = -1;
displayName.clear();
data.clear();
filename.clear();
line = column = 0;
};
while (!stream.atEnd() && !stream.hasError() && !isCanceled()) {
QXmlStreamReader::TokenType token = stream.readNext();
const QStringView elementName = stream.name();
switch (token) {
case QXmlStreamReader::StartElement: {
if (elementName == _("event")) {
setDeviceProgress(stream.device());
clearType();
const QXmlStreamAttributes attributes = stream.attributes();
if (attributes.hasAttribute(_("index"))) {
typeIndex = attributes.value(_("index")).toInt();
} else {
// ignore event
typeIndex = -1;
}
break;
}
stream.readNext();
if (stream.tokenType() != QXmlStreamReader::Characters)
break;
const QString readData = stream.text().toString();
if (elementName == _("displayname")) {
displayName = readData;
break;
}
if (elementName == _("type")) {
messageAndRange = qmlTypeAsEnum(readData);
break;
}
if (elementName == _("filename")) {
filename = readData;
break;
}
if (elementName == _("line")) {
line = readData.toInt();
break;
}
if (elementName == _("column")) {
column = readData.toInt();
break;
}
if (elementName == _("details")) {
data = readData;
break;
}
if (elementName == _("animationFrame")) {
detailType = readData.toInt();
// new animation frames used to be saved as ranges of range type Painting with
// binding type 4 (which was called "AnimationFrame" to make everything even more
// confusing), even though they clearly aren't ranges. Convert that to something
// sane here.
if (detailType == 4) {
messageAndRange = QPair<Message, RangeType>(Event, UndefinedRangeType);
detailType = AnimationFrame;
}
}
if (elementName == _("bindingType") ||
elementName == _("cacheEventType") ||
elementName == _("sgEventType") ||
elementName == _("memoryEventType") ||
elementName == _("mouseEvent") ||
elementName == _("keyEvent") ||
elementName == _("level")) {
detailType = readData.toInt();
break;
}
break;
}
case QXmlStreamReader::EndElement: {
if (elementName == _("event")) {
if (typeIndex >= 0) {
QmlEventType type(messageAndRange.first, messageAndRange.second,
detailType, QmlEventLocation(filename, line, column),
data, displayName);
const quint8 feature = type.feature();
modelManager()->setEventType(typeIndex, std::move(type));
if (feature != MaximumProfileFeature)
addFeature(feature);
}
break;
}
if (elementName == _("eventData")) {
// done reading eventData
return;
}
break;
}
default: break;
} // switch
}
}
class EventList
{
public:
void addEvent(const QmlEvent &event);
void addRange(const QmlEvent &start, const QmlEvent &end);
void finalize(QmlProfilerModelManager *modelManager);
private:
struct QmlRange {
QmlEvent begin;
QmlEvent end;
};
QList<QmlRange> ranges; // We are going to do a lot of takeFirst() on this.
};
void EventList::addEvent(const QmlEvent &event)
{
ranges.append({event, QmlEvent()});
}
void EventList::addRange(const QmlEvent &start, const QmlEvent &end)
{
ranges.append({start, end});
}
void EventList::finalize(QmlProfilerModelManager *modelManager)
{
std::sort(ranges.begin(), ranges.end(), [](const QmlRange &a, const QmlRange &b) {
if (a.begin.timestamp() < b.begin.timestamp())
return true;
if (a.begin.timestamp() > b.begin.timestamp())
return false;
// If the start times are equal. Sort the one with the greater end time first, so that
// the nesting is retained.
return (a.end.timestamp() > b.end.timestamp());
});
QList<QmlEvent> ends;
while (!ranges.isEmpty()) {
// This is more expensive than just iterating, but we don't want to double the already
// high memory footprint.
QmlRange range = ranges.takeFirst();
while (!ends.isEmpty() && ends.last().timestamp() <= range.begin.timestamp())
modelManager->appendEvent(ends.takeLast());
if (range.end.isValid()) {
auto it = ends.end();
for (auto begin = ends.begin(); it != begin;) {
if ((--it)->timestamp() >= range.end.timestamp()) {
++it;
break;
}
}
ends.insert(it, range.end);
}
modelManager->appendEvent(std::move(range.begin));
}
while (!ends.isEmpty())
modelManager->appendEvent(ends.takeLast());
}
void QmlProfilerTraceFile::loadEvents(QXmlStreamReader &stream)
{
QTC_ASSERT(stream.name() == _("profilerDataModel"), return);
EventList events;
while (!stream.atEnd() && !stream.hasError() && !isCanceled()) {
QXmlStreamReader::TokenType token = stream.readNext();
const QStringView elementName = stream.name();
switch (token) {
case QXmlStreamReader::StartElement: {
if (elementName == _("range")) {
setDeviceProgress(stream.device());
QmlEvent event;
const QXmlStreamAttributes attributes = stream.attributes();
if (!attributes.hasAttribute(_("startTime"))
|| !attributes.hasAttribute(_("eventIndex"))) {
// ignore incomplete entry
continue;
}
const qint64 timestamp = attributes.value(_("startTime")).toLongLong();
event.setTimestamp(timestamp > 0 ? timestamp : 0);
event.setTypeIndex(attributes.value(_("eventIndex")).toInt());
if (attributes.hasAttribute(_("duration"))) {
event.setRangeStage(RangeStart);
QmlEvent rangeEnd(event);
rangeEnd.setRangeStage(RangeEnd);
const qint64 duration = attributes.value(_("duration")).toLongLong();
rangeEnd.setTimestamp(event.timestamp() + (duration > 0 ? duration : 0));
events.addRange(event, rangeEnd);
} else {
// attributes for special events
if (attributes.hasAttribute(_("framerate")))
event.setNumber<qint32>(0, attributes.value(_("framerate")).toInt());
if (attributes.hasAttribute(_("animationcount")))
event.setNumber<qint32>(1, attributes.value(_("animationcount")).toInt());
if (attributes.hasAttribute(_("thread")))
event.setNumber<qint32>(2, attributes.value(_("thread")).toInt());
if (attributes.hasAttribute(_("width")))
event.setNumber<qint32>(0, attributes.value(_("width")).toInt());
if (attributes.hasAttribute(_("height")))
event.setNumber<qint32>(1, attributes.value(_("height")).toInt());
if (attributes.hasAttribute(_("refCount")))
event.setNumber<qint32>(2, attributes.value(_("refCount")).toInt());
if (attributes.hasAttribute(_("amount")))
event.setNumber<qint64>(0, attributes.value(_("amount")).toLongLong());
if (attributes.hasAttribute(_("timing1")))
event.setNumber<qint64>(0, attributes.value(_("timing1")).toLongLong());
if (attributes.hasAttribute(_("timing2")))
event.setNumber<qint64>(1, attributes.value(_("timing2")).toLongLong());
if (attributes.hasAttribute(_("timing3")))
event.setNumber<qint64>(2, attributes.value(_("timing3")).toLongLong());
if (attributes.hasAttribute(_("timing4")))
event.setNumber<qint64>(3, attributes.value(_("timing4")).toLongLong());
if (attributes.hasAttribute(_("timing5")))
event.setNumber<qint64>(4, attributes.value(_("timing5")).toLongLong());
if (attributes.hasAttribute(_("type")))
event.setNumber<qint32>(0, attributes.value(_("type")).toInt());
if (attributes.hasAttribute(_("data1")))
event.setNumber<qint32>(1, attributes.value(_("data1")).toInt());
if (attributes.hasAttribute(_("data2")))
event.setNumber<qint32>(2, attributes.value(_("data2")).toInt());
if (attributes.hasAttribute(_("text")))
event.setString(attributes.value(_("text")).toString());
events.addEvent(event);
}
}
break;
}
case QXmlStreamReader::EndElement: {
if (elementName == _("profilerDataModel")) {
// done reading profilerDataModel
events.finalize(modelManager());
return;
}
break;
}
default: break;
} // switch
}
}
void QmlProfilerTraceFile::loadNotes(QXmlStreamReader &stream)
{
QmlNote currentNote;
while (!stream.atEnd() && !stream.hasError() && !isCanceled()) {
QXmlStreamReader::TokenType token = stream.readNext();
const QStringView elementName = stream.name();
switch (token) {
case QXmlStreamReader::StartElement: {
if (elementName == _("note")) {
setDeviceProgress(stream.device());
QXmlStreamAttributes attrs = stream.attributes();
int collapsedRow = attrs.hasAttribute(_("collapsedRow")) ?
attrs.value(_("collapsedRow")).toInt() : -1;
currentNote = QmlNote(attrs.value(_("eventIndex")).toInt(),
collapsedRow,
attrs.value(_("startTime")).toLongLong(),
attrs.value(_("duration")).toLongLong());
}
break;
}
case QXmlStreamReader::Characters: {
currentNote.setText(stream.text().toString());
break;
}
case QXmlStreamReader::EndElement: {
if (elementName == _("note")) {
qmlNotes()->addNote(currentNote);
} else if (elementName == _("noteData")) {
return;
}
break;
}
default:
break;
}
}
}
void QmlProfilerTraceFile::saveQtd(QIODevice *device)
{
QXmlStreamWriter stream(device);
stream.setAutoFormatting(true);
stream.writeStartDocument();
stream.writeStartElement(_("trace"));
stream.writeAttribute(_("version"), _(PROFILER_FILE_VERSION));
stream.writeAttribute(_("traceStart"), QString::number(traceStart()));
stream.writeAttribute(_("traceEnd"), QString::number(traceEnd()));
stream.writeStartElement(_("eventData"));
stream.writeAttribute(_("totalTime"), QString::number(measuredTime()));
const QmlProfilerModelManager *manager = modelManager();
for (int typeIndex = 0, end = manager->numEventTypes(); typeIndex < end && !isCanceled();
++typeIndex) {
const QmlEventType &type = manager->eventType(typeIndex);
stream.writeStartElement(_("event"));
stream.writeAttribute(_("index"), QString::number(typeIndex));
stream.writeTextElement(_("displayname"), type.displayName());
stream.writeTextElement(_("type"), qmlTypeAsString(type.message(), type.rangeType()));
const QmlEventLocation location(type.location());
if (!location.filename().isEmpty()) {
stream.writeTextElement(_("filename"), location.filename());
stream.writeTextElement(_("line"), QString::number(location.line()));
stream.writeTextElement(_("column"), QString::number(location.column()));
}
if (!type.data().isEmpty())
stream.writeTextElement(_("details"), type.data());
if (type.rangeType() == Binding) {
stream.writeTextElement(_("bindingType"), QString::number(type.detailType()));
} else if (type.message() == Event) {
switch (type.detailType()) {
case AnimationFrame:
stream.writeTextElement(_("animationFrame"), QString::number(type.detailType()));
break;
case Key:
stream.writeTextElement(_("keyEvent"), QString::number(type.detailType()));
break;
case Mouse:
stream.writeTextElement(_("mouseEvent"), QString::number(type.detailType()));
break;
default:
break;
}
} else if (type.message() == PixmapCacheEvent) {
stream.writeTextElement(_("cacheEventType"), QString::number(type.detailType()));
} else if (type.message() == SceneGraphFrame) {
stream.writeTextElement(_("sgEventType"), QString::number(type.detailType()));
} else if (type.message() == MemoryAllocation) {
stream.writeTextElement(_("memoryEventType"), QString::number(type.detailType()));
} else if (type.message() == DebugMessage) {
stream.writeTextElement(_("level"), QString::number(type.detailType()));
}
stream.writeEndElement();
}
addStageProgress(ProgressTypes);
stream.writeEndElement(); // eventData
if (isCanceled())
return;
QStack<QmlEvent> stack;
qint64 lastProgressTimestamp = traceStart();
modelManager()->replayQmlEvents([&](const QmlEvent &event, const QmlEventType &type) {
if (type.rangeType() != UndefinedRangeType && event.rangeStage() == RangeStart) {
stack.push(event);
return;
}
stream.writeStartElement(_("range"));
if (type.rangeType() != UndefinedRangeType && event.rangeStage() == RangeEnd) {
QmlEvent start = stack.pop();
stream.writeAttribute(_("startTime"), QString::number(start.timestamp()));
stream.writeAttribute(_("duration"),
QString::number(event.timestamp() - start.timestamp()));
} else {
stream.writeAttribute(_("startTime"), QString::number(event.timestamp()));
}
stream.writeAttribute(_("eventIndex"), QString::number(event.typeIndex()));
if (type.message() == Event) {
if (type.detailType() == AnimationFrame) {
// special: animation event
stream.writeAttribute(_("framerate"), QString::number(event.number<qint32>(0)));
stream.writeAttribute(_("animationcount"),
QString::number(event.number<qint32>(1)));
stream.writeAttribute(_("thread"), QString::number(event.number<qint32>(2)));
} else if (type.detailType() == Key || type.detailType() == Mouse) {
// special: input event
stream.writeAttribute(_("type"), QString::number(event.number<qint32>(0)));
stream.writeAttribute(_("data1"), QString::number(event.number<qint32>(1)));
stream.writeAttribute(_("data2"), QString::number(event.number<qint32>(2)));
}
}
// special: pixmap cache event
if (type.message() == PixmapCacheEvent) {
if (type.detailType() == PixmapSizeKnown) {
stream.writeAttribute(_("width"), QString::number(event.number<qint32>(0)));
stream.writeAttribute(_("height"), QString::number(event.number<qint32>(1)));
}
if (type.detailType() == PixmapReferenceCountChanged
|| type.detailType() == PixmapCacheCountChanged)
stream.writeAttribute(_("refCount"), QString::number(event.number<qint32>(2)));
}
if (type.message() == SceneGraphFrame) {
// special: scenegraph frame events
for (int i = 0; i < 5; ++i) {
qint64 number = event.number<qint64>(i);
if (number <= 0)
continue;
stream.writeAttribute(QString::fromLatin1("timing%1").arg(i + 1),
QString::number(number));
}
}
// special: memory allocation event
if (type.message() == MemoryAllocation)
stream.writeAttribute(_("amount"), QString::number(event.number<qint64>(0)));
if (type.message() == DebugMessage)
stream.writeAttribute(_("text"), event.string());
stream.writeEndElement();
if (isProgressUpdateNeeded()) {
addEventsProgress(event.timestamp() - lastProgressTimestamp);
lastProgressTimestamp = event.timestamp();
}
}, [&stream](){
stream.writeStartElement(_("profilerDataModel"));
}, [this, &stream]() {
stream.writeEndElement(); // profilerDataModel
if (!isCanceled()) {
stream.writeStartElement(_("noteData"));
const QVector<QmlNote> &notes = qmlNotes()->notes();
for (int noteIndex = 0; noteIndex < notes.length() && !isCanceled(); ++noteIndex) {
const QmlNote &note = notes[noteIndex];
stream.writeStartElement(_("note"));
stream.writeAttribute(_("startTime"), QString::number(note.startTime()));
stream.writeAttribute(_("duration"), QString::number(note.duration()));
stream.writeAttribute(_("eventIndex"), QString::number(note.typeIndex()));
stream.writeAttribute(_("collapsedRow"), QString::number(note.collapsedRow()));
stream.writeCharacters(note.text());
stream.writeEndElement(); // note
}
stream.writeEndElement(); // noteData
addStageProgress(ProgressNotes);
}
stream.writeEndElement(); // trace
stream.writeEndDocument();
if (stream.hasError())
fail(Tr::tr("Error writing trace file."));
}, [this](const QString &message) {
fail(Tr::tr("Could not re-read events from temporary trace file: %1\nSaving failed.")
.arg(message));
}, future());
}
void QmlProfilerTraceFile::saveQzt(QIODevice *device)
{
QDataStream stream(device);
stream.setVersion(QDataStream::Qt_5_5);
stream << QByteArray("QMLPROFILER");
stream << static_cast<qint32>(QDataStream::Qt_DefaultCompiledVersion);
stream.setVersion(QDataStream::Qt_DefaultCompiledVersion);
stream << traceStart() << traceEnd();
QBuffer buffer;
QDataStream bufferStream(&buffer);
buffer.open(QIODevice::WriteOnly);
if (!isCanceled()) {
const QmlProfilerModelManager *manager = modelManager();
const int numEventTypes = manager->numEventTypes();
bufferStream << static_cast<quint32>(numEventTypes);
for (int typeId = 0; typeId < numEventTypes; ++typeId)
bufferStream << manager->eventType(typeId);
stream << qCompress(buffer.data());
buffer.close();
buffer.buffer().clear();
addStageProgress(ProgressTypes);
}
if (!isCanceled()) {
buffer.open(QIODevice::WriteOnly);
bufferStream << qmlNotes()->notes();
stream << qCompress(buffer.data());
buffer.close();
buffer.buffer().clear();
addStageProgress(ProgressNotes);
}
if (isCanceled())
return;
qint64 lastProgressTimestamp = traceStart();
modelManager()->replayQmlEvents([&](const QmlEvent &event, const QmlEventType &type) {
Q_UNUSED(type)
bufferStream << event;
// 32MB buffer should be plenty for efficient compression
if (buffer.data().length() > (1 << 25)) {
stream << qCompress(buffer.data());
buffer.close();
buffer.buffer().clear();
buffer.open(QIODevice::WriteOnly);
if (isProgressUpdateNeeded()) {
addEventsProgress(event.timestamp() - lastProgressTimestamp);
lastProgressTimestamp = event.timestamp();
}
}
}, [&]() {
buffer.open(QIODevice::WriteOnly);
}, [&]() {
if (!isCanceled()) {
stream << qCompress(buffer.data());
buffer.close();
buffer.buffer().clear();
addEventsProgress(traceEnd() - lastProgressTimestamp);
}
}, [this](const QString &message) {
fail(Tr::tr("Could not re-read events from temporary trace file: %1\nSaving failed.")
.arg(message));
}, future());
}
QmlProfilerModelManager *QmlProfilerTraceFile::modelManager()
{
return static_cast<QmlProfilerModelManager *>(traceManager());
}
QmlProfilerNotesModel *QmlProfilerTraceFile::qmlNotes()
{
return static_cast<QmlProfilerNotesModel *>(notes());
}
} // namespace Internal
} // namespace QmlProfiler