Files
qt-creator/src/plugins/qmlprofiler/qmlprofilertracefile.cpp
Ulf Hermann 2b75df2139 QmlProfiler: Simplify buffer handling when loading trace
The inserting of events into the buffer is not the bottleneck here.
The book keeping is probably more expensive than just always using
all the events contained in a chuck from the file for one batch.

Change-Id: I75a936fdf9e3a1d9675b44d67b98f14594f87ffd
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2017-02-24 11:55:34 +00:00

865 lines
30 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#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"
};
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(MaximumMessage, MaximumRangeType);
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 == MaximumMessage && ret.second == MaximumRangeType) {
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);
}
QmlProfilerFileReader::QmlProfilerFileReader(QObject *parent) :
QObject(parent),
m_traceStart(-1),
m_traceEnd(-1),
m_future(0),
m_loadedFeatures(0)
{
static int meta[] = {
qRegisterMetaType<QVector<QmlEvent> >(),
qRegisterMetaType<QVector<QmlEventType> >(),
qRegisterMetaType<QVector<QmlNote> >()
};
Q_UNUSED(meta);
}
void QmlProfilerFileReader::setFuture(QFutureInterface<void> *future)
{
m_future = future;
if (m_future) {
m_future->setProgressRange(0, 1000);
m_future->setProgressValue(0);
}
}
void QmlProfilerFileReader::loadQtd(QIODevice *device)
{
QXmlStreamReader stream(device);
bool validVersion = true;
while (validVersion && !stream.atEnd() && !stream.hasError() && !isCanceled()) {
QXmlStreamReader::TokenType token = stream.readNext();
const QStringRef 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")))
m_traceStart = attributes.value(_("traceStart")).toLongLong();
if (attributes.hasAttribute(_("traceEnd")))
m_traceEnd = attributes.value(_("traceEnd")).toLongLong();
}
if (elementName == _("eventData")) {
loadEventTypes(stream);
emit typesLoaded(m_eventTypes);
break;
}
if (elementName == _("profilerDataModel")) {
loadEvents(stream);
break;
}
if (elementName == _("noteData")) {
loadNotes(stream);
emit notesLoaded(m_notes);
break;
}
break;
}
default: break;
}
}
if (isCanceled())
emit canceled();
else if (stream.hasError())
emit error(tr("Error while parsing trace data file: %1").arg(stream.errorString()));
else
emit success();
}
void QmlProfilerFileReader::loadQzt(QIODevice *device)
{
QDataStream stream(device);
stream.setVersion(QDataStream::Qt_5_5);
QByteArray magic;
stream >> magic;
if (magic != QByteArray("QMLPROFILER")) {
emit error(tr("Invalid magic: %1").arg(QLatin1String(magic)));
return;
}
qint32 dataStreamVersion;
stream >> dataStreamVersion;
if (dataStreamVersion > QDataStream::Qt_DefaultCompiledVersion) {
emit error(tr("Unknown data stream version: %1").arg(dataStreamVersion));
return;
}
stream.setVersion(dataStreamVersion);
stream >> m_traceStart >> m_traceEnd;
QBuffer buffer;
QDataStream bufferStream(&buffer);
bufferStream.setVersion(dataStreamVersion);
QByteArray data;
updateProgress(device);
if (!isCanceled()) {
stream >> data;
buffer.setData(qUncompress(data));
buffer.open(QIODevice::ReadOnly);
bufferStream >> m_eventTypes;
buffer.close();
emit typesLoaded(m_eventTypes);
updateProgress(device);
}
if (!isCanceled()) {
stream >> data;
buffer.setData(qUncompress(data));
buffer.open(QIODevice::ReadOnly);
bufferStream >> m_notes;
buffer.close();
emit notesLoaded(m_notes);
updateProgress(device);
}
QVector<QmlEvent> eventBuffer;
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() >= m_eventTypes.length()) {
emit error(tr("Invalid type index %1").arg(event.typeIndex()));
return;
}
m_loadedFeatures |= (1ULL << m_eventTypes[event.typeIndex()].feature());
} 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) {
emit error(tr("Corrupt data before position %1.").arg(device->pos()));
return;
} else {
Q_UNREACHABLE();
}
eventBuffer.append(event);
}
emit qmlEventsLoaded(eventBuffer);
eventBuffer.clear();
buffer.close();
updateProgress(device);
}
if (isCanceled()) {
emit canceled();
} else {
emit qmlEventsLoaded(eventBuffer);
emit success();
}
}
quint64 QmlProfilerFileReader::loadedFeatures() const
{
return m_loadedFeatures;
}
void QmlProfilerFileReader::loadEventTypes(QXmlStreamReader &stream)
{
QTC_ASSERT(stream.name() == _("eventData"), return);
int typeIndex = -1;
QPair<Message, RangeType> messageAndRange(MaximumMessage, MaximumRangeType);
int detailType = -1;
QString displayName;
QString data;
QString filename;
int line = 0, column = 0;
auto clearType = [&](){
messageAndRange = QPair<Message, RangeType>(MaximumMessage, MaximumRangeType);
detailType = -1;
displayName.clear();
data.clear();
filename.clear();
line = column = 0;
};
while (!stream.atEnd() && !stream.hasError() && !isCanceled()) {
QXmlStreamReader::TokenType token = stream.readNext();
const QStringRef elementName = stream.name();
switch (token) {
case QXmlStreamReader::StartElement: {
if (elementName == _("event")) {
updateProgress(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, MaximumRangeType);
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) {
if (typeIndex >= m_eventTypes.size())
m_eventTypes.resize(typeIndex + 1);
QmlEventType type(messageAndRange.first, messageAndRange.second, detailType,
QmlEventLocation(filename, line, column), data, displayName);
m_eventTypes[typeIndex] = type;
ProfileFeature feature = type.feature();
if (feature != MaximumProfileFeature)
m_loadedFeatures |= (1ULL << static_cast<uint>(feature));
}
break;
}
if (elementName == _("eventData")) {
// done reading eventData
return;
}
break;
}
default: break;
} // switch
}
}
void QmlProfilerFileReader::loadEvents(QXmlStreamReader &stream)
{
QTC_ASSERT(stream.name() == _("profilerDataModel"), return);
QVector<QmlEvent> events;
while (!stream.atEnd() && !stream.hasError() && !isCanceled()) {
QXmlStreamReader::TokenType token = stream.readNext();
const QStringRef elementName = stream.name();
switch (token) {
case QXmlStreamReader::StartElement: {
if (elementName == _("range")) {
updateProgress(stream.device());
QmlEvent event;
const QXmlStreamAttributes attributes = stream.attributes();
if (!attributes.hasAttribute(_("startTime"))
|| !attributes.hasAttribute(_("eventIndex"))) {
// ignore incomplete entry
continue;
}
event.setTimestamp(attributes.value(_("startTime")).toLongLong());
event.setTypeIndex(attributes.value(_("eventIndex")).toInt());
if (attributes.hasAttribute(_("duration"))) {
event.setRangeStage(RangeStart);
events.append(event);
QmlEvent rangeEnd(event);
rangeEnd.setRangeStage(RangeEnd);
rangeEnd.setTimestamp(event.timestamp()
+ attributes.value(_("duration")).toLongLong());
events.append(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.append(event);
}
}
break;
}
case QXmlStreamReader::EndElement: {
if (elementName == _("profilerDataModel")) {
// done reading profilerDataModel
std::sort(events.begin(), events.end(), [](const QmlEvent &a, const QmlEvent &b) {
return a.timestamp() < b.timestamp();
});
emit qmlEventsLoaded(events);
return;
}
break;
}
default: break;
} // switch
}
}
void QmlProfilerFileReader::loadNotes(QXmlStreamReader &stream)
{
QmlNote currentNote;
while (!stream.atEnd() && !stream.hasError() && !isCanceled()) {
QXmlStreamReader::TokenType token = stream.readNext();
const QStringRef elementName = stream.name();
switch (token) {
case QXmlStreamReader::StartElement: {
if (elementName == _("note")) {
updateProgress(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")) {
m_notes.append(currentNote);
} else if (elementName == _("noteData")) {
return;
}
break;
}
default:
break;
}
}
}
void QmlProfilerFileReader::updateProgress(QIODevice *device)
{
if (!m_future)
return;
m_future->setProgressValue(device->pos() * 1000 / device->size());
}
bool QmlProfilerFileReader::isCanceled() const
{
return m_future && m_future->isCanceled();
}
QmlProfilerFileWriter::QmlProfilerFileWriter(QObject *parent) :
QObject(parent),
m_startTime(0),
m_endTime(0),
m_measuredTime(0),
m_future(0)
{
}
void QmlProfilerFileWriter::setTraceTime(qint64 startTime, qint64 endTime, qint64 measuredTime)
{
m_startTime = startTime;
m_endTime = endTime;
m_measuredTime = measuredTime;
}
void QmlProfilerFileWriter::setData(const QmlProfilerDataModel *model)
{
m_model = model;
}
void QmlProfilerFileWriter::setNotes(const QVector<QmlNote> &notes)
{
m_notes = notes;
}
void QmlProfilerFileWriter::setFuture(QFutureInterface<void> *future)
{
m_future = future;
if (m_future) {
m_future->setProgressRange(0, ProgressTotal);
m_future->setProgressValue(0);
}
}
void QmlProfilerFileWriter::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(m_startTime));
stream.writeAttribute(_("traceEnd"), QString::number(m_endTime));
stream.writeStartElement(_("eventData"));
stream.writeAttribute(_("totalTime"), QString::number(m_measuredTime));
const QVector<QmlEventType> &eventTypes = m_model->eventTypes();
for (int typeIndex = 0, end = eventTypes.length(); typeIndex < end && !isCanceled();
++typeIndex) {
const QmlEventType &type = eventTypes[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();
}
updateProgress(ProgressTypes);
stream.writeEndElement(); // eventData
if (!isCanceled()) {
stream.writeStartElement(_("profilerDataModel"));
QStack<QmlEvent> stack;
const bool success = m_model->replayEvents(
-1, -1, [this, &stack, &stream](const QmlEvent &event,
const QmlEventType &type) {
if (isCanceled())
return;
if (type.rangeType() != MaximumRangeType && event.rangeStage() == RangeStart) {
stack.push(event);
return;
}
stream.writeStartElement(_("range"));
if (type.rangeType() != MaximumRangeType && 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();
// Update the progress roughly every 4k events. It doesn't have to be precise.
if ((event.timestamp() & 0xfff) == 0)
updateProgress(event.timestamp());
});
if (!success) {
emit error(tr("Could not re-read events from temporary trace file. Saving failed."));
return;
}
stream.writeEndElement(); // profilerDataModel
}
if (!isCanceled()) {
stream.writeStartElement(_("noteData"));
for (int noteIndex = 0; noteIndex < m_notes.size() && !isCanceled(); ++noteIndex) {
const QmlNote &note = m_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
updateProgress(ProgressNotes);
}
stream.writeEndElement(); // trace
stream.writeEndDocument();
if (isCanceled()) {
emit canceled();
} else if (stream.hasError()) {
emit error(tr("Error writing trace file."));
} else {
emit success();
}
}
void QmlProfilerFileWriter::saveQzt(QFile *file)
{
QDataStream stream(file);
stream.setVersion(QDataStream::Qt_5_5);
stream << QByteArray("QMLPROFILER");
stream << static_cast<qint32>(QDataStream::Qt_DefaultCompiledVersion);
stream.setVersion(QDataStream::Qt_DefaultCompiledVersion);
stream << m_startTime << m_endTime;
QBuffer buffer;
QDataStream bufferStream(&buffer);
buffer.open(QIODevice::WriteOnly);
if (!isCanceled()) {
bufferStream << m_model->eventTypes();
stream << qCompress(buffer.data());
buffer.close();
buffer.buffer().clear();
updateProgress(ProgressTypes);
}
if (!isCanceled()) {
buffer.open(QIODevice::WriteOnly);
bufferStream << m_notes;
stream << qCompress(buffer.data());
buffer.close();
buffer.buffer().clear();
updateProgress(ProgressNotes);
}
if (!isCanceled()) {
buffer.open(QIODevice::WriteOnly);
const bool success = m_model->replayEvents(
-1, -1, [this, &stream, &buffer, &bufferStream](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();
if (isCanceled())
return;
buffer.open(QIODevice::WriteOnly);
updateProgress(event.timestamp());
}
});
if (!success) {
emit error(tr("Could not re-read events from temporary trace file. Saving failed."));
return;
}
}
if (isCanceled()) {
emit canceled();
} else {
stream << qCompress(buffer.data());
buffer.close();
buffer.buffer().clear();
updateProgress(m_endTime);
emit success();
}
}
void QmlProfilerFileWriter::updateProgress(qint64 timestamp)
{
if (!m_future)
return;
if (timestamp < 0) {
m_future->setProgressValue(m_future->progressValue() - timestamp);
} else {
m_future->setProgressValue(m_future->progressValue()
+ float(m_endTime - timestamp) / float(m_endTime - m_startTime)
* ProgressEvents);
}
}
bool QmlProfilerFileWriter::isCanceled() const
{
return m_future && m_future->isCanceled();
}
} // namespace Internal
} // namespace QmlProfiler