Files
qt-creator/src/plugins/qmlprofiler/qmlprofilertracefile.cpp
Ulf Hermann 94722ec5e7 QmlProfiler: Unify event type definitions
Generally save both the Message and RangeType attributes so that we
avoid clashes between those types. Also keep all the types in one
place and make their names follow qtdeclarative's conventions.

Change-Id: I811bfcc4b72aaa2a0142babc92d96968ed2d4007
Reviewed-by: Kai Koehne <kai.koehne@digia.com>
2014-06-06 15:24:28 +02:00

586 lines
21 KiB
C++

/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include "qmlprofilertracefile.h"
#include <utils/qtcassert.h>
#include <QIODevice>
#include <QStringList>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QDebug>
// import QmlEventType, QmlBindingType enums, QmlEventLocation
using namespace QmlDebug;
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) == QmlDebug::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"
};
Q_STATIC_ASSERT(sizeof(MESSAGE_STRINGS) == QmlDebug::MaximumMessage * sizeof(const char *));
#define _(X) QLatin1String(X)
//
// "be strict in your output but tolerant in your inputs"
//
namespace QmlProfiler {
namespace Internal {
static QPair<QmlDebug::Message, QmlDebug::RangeType> qmlTypeAsEnum(const QString &typeString)
{
QPair<QmlDebug::Message, QmlDebug::RangeType> ret(QmlDebug::MaximumMessage,
QmlDebug::MaximumRangeType);
for (int i = 0; i < QmlDebug::MaximumMessage; ++i) {
if (typeString == _(MESSAGE_STRINGS[i])) {
ret.first = static_cast<QmlDebug::Message>(i);
break;
}
}
for (int i = 0; i < QmlDebug::MaximumRangeType; ++i) {
if (typeString == _(RANGE_TYPE_STRINGS[i])) {
ret.second = static_cast<QmlDebug::RangeType>(i);
break;
}
}
if (ret.first == QmlDebug::MaximumMessage && ret.second == QmlDebug::MaximumRangeType) {
bool isNumber = false;
int type = typeString.toUInt(&isNumber);
if (isNumber && type < QmlDebug::MaximumRangeType)
// Allow saving ranges as numbers, but not messages.
ret.second = static_cast<QmlDebug::RangeType>(type);
}
return ret;
}
static QString qmlTypeAsString(QmlDebug::Message message, QmlDebug::RangeType rangeType)
{
if (rangeType < QmlDebug::MaximumRangeType)
return _(RANGE_TYPE_STRINGS[rangeType]);
else if (message != QmlDebug::MaximumMessage)
return _(MESSAGE_STRINGS[message]);
else
return QString::number((int)rangeType);
}
QmlProfilerFileReader::QmlProfilerFileReader(QObject *parent) :
QObject(parent),
m_v8Model(0)
{
}
void QmlProfilerFileReader::setV8DataModel(QV8ProfilerDataModel *dataModel)
{
m_v8Model = dataModel;
}
bool QmlProfilerFileReader::load(QIODevice *device)
{
QXmlStreamReader stream(device);
bool validVersion = true;
while (validVersion && !stream.atEnd() && !stream.hasError()) {
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")))
emit traceStartTime(attributes.value(_("traceStart")).toString().toLongLong());
if (attributes.hasAttribute(_("traceEnd")))
emit traceEndTime(attributes.value(_("traceEnd")).toString().toLongLong());
}
if (elementName == _("eventData")) {
loadEventData(stream);
break;
}
if (elementName == _("profilerDataModel")) {
loadProfilerDataModel(stream);
break;
}
if (elementName == _("v8profile")) {
if (m_v8Model)
m_v8Model->load(stream);
break;
}
break;
}
default: break;
}
}
if (stream.hasError()) {
emit error(tr("Error while parsing trace data file: %1").arg(stream.errorString()));
return false;
} else {
processQmlEvents();
return true;
}
}
void QmlProfilerFileReader::loadEventData(QXmlStreamReader &stream)
{
QTC_ASSERT(stream.name() == _("eventData"), return);
QXmlStreamAttributes attributes = stream.attributes();
int eventIndex = -1;
QmlEvent event = {
QString(), // displayname
QString(), // filename
QString(), // details
MaximumMessage,
Painting, // type
QmlBinding, // bindingType, set for backwards compatibility
0, // line
0 // column
};
const QmlEvent defaultEvent = event;
while (!stream.atEnd() && !stream.hasError()) {
QXmlStreamReader::TokenType token = stream.readNext();
const QStringRef elementName = stream.name();
switch (token) {
case QXmlStreamReader::StartElement: {
if (elementName == _("event")) {
event = defaultEvent;
const QXmlStreamAttributes attributes = stream.attributes();
if (attributes.hasAttribute(_("index"))) {
eventIndex = attributes.value(_("index")).toString().toInt();
} else {
// ignore event
eventIndex = -1;
}
break;
}
stream.readNext();
if (stream.tokenType() != QXmlStreamReader::Characters)
break;
const QString readData = stream.text().toString();
if (elementName == _("displayname")) {
event.displayName = readData;
break;
}
if (elementName == _("type")) {
QPair<QmlDebug::Message, QmlDebug::RangeType> enums = qmlTypeAsEnum(readData);
event.message = enums.first;
event.rangeType = enums.second;
break;
}
if (elementName == _("filename")) {
event.filename = readData;
break;
}
if (elementName == _("line")) {
event.line = readData.toInt();
break;
}
if (elementName == _("column")) {
event.column = readData.toInt();
break;
}
if (elementName == _("details")) {
event.details = readData;
break;
}
if (elementName == _("animationFrame")) {
event.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 (event.detailType == 4) {
event.message = Event;
event.rangeType = MaximumRangeType;
event.detailType = AnimationFrame;
}
}
if (elementName == _("bindingType") ||
elementName == _("cacheEventType") ||
elementName == _("sgEventType")) {
event.detailType = readData.toInt();
break;
}
break;
}
case QXmlStreamReader::EndElement: {
if (elementName == _("event")) {
if (eventIndex >= 0) {
if (eventIndex >= m_qmlEvents.size())
m_qmlEvents.resize(eventIndex + 1);
m_qmlEvents[eventIndex] = event;
}
break;
}
if (elementName == _("eventData")) {
// done reading eventData
return;
}
break;
}
default: break;
} // switch
}
}
void QmlProfilerFileReader::loadProfilerDataModel(QXmlStreamReader &stream)
{
QTC_ASSERT(stream.name() == _("profilerDataModel"), return);
while (!stream.atEnd() && !stream.hasError()) {
QXmlStreamReader::TokenType token = stream.readNext();
const QStringRef elementName = stream.name();
switch (token) {
case QXmlStreamReader::StartElement: {
if (elementName == _("range")) {
Range range = { 0, 0, 0, 0, 0, 0, 0 };
const QXmlStreamAttributes attributes = stream.attributes();
if (!attributes.hasAttribute(_("startTime"))
|| !attributes.hasAttribute(_("eventIndex"))) {
// ignore incomplete entry
continue;
}
range.startTime = attributes.value(_("startTime")).toString().toLongLong();
if (attributes.hasAttribute(_("duration")))
range.duration = attributes.value(_("duration")).toString().toLongLong();
// attributes for special events
if (attributes.hasAttribute(_("framerate")))
range.numericData1 = attributes.value(_("framerate")).toString().toLongLong();
if (attributes.hasAttribute(_("animationcount")))
range.numericData2 = attributes.value(_("animationcount")).toString().toLongLong();
if (attributes.hasAttribute(_("thread")))
range.numericData3 = attributes.value(_("thread")).toString().toLongLong();
if (attributes.hasAttribute(_("width")))
range.numericData1 = attributes.value(_("width")).toString().toLongLong();
if (attributes.hasAttribute(_("height")))
range.numericData2 = attributes.value(_("height")).toString().toLongLong();
if (attributes.hasAttribute(_("refCount")))
range.numericData3 = attributes.value(_("refCount")).toString().toLongLong();
if (attributes.hasAttribute(_("timing1")))
range.numericData1 = attributes.value(_("timing1")).toString().toLongLong();
if (attributes.hasAttribute(_("timing2")))
range.numericData2 = attributes.value(_("timing2")).toString().toLongLong();
if (attributes.hasAttribute(_("timing3")))
range.numericData3 = attributes.value(_("timing3")).toString().toLongLong();
if (attributes.hasAttribute(_("timing4")))
range.numericData4 = attributes.value(_("timing4")).toString().toLongLong();
if (attributes.hasAttribute(_("timing5")))
range.numericData5 = attributes.value(_("timing5")).toString().toLongLong();
int eventIndex = attributes.value(_("eventIndex")).toString().toInt();
m_ranges.append(QPair<Range,int>(range, eventIndex));
}
break;
}
case QXmlStreamReader::EndElement: {
if (elementName == _("profilerDataModel")) {
// done reading profilerDataModel
return;
}
break;
}
default: break;
} // switch
}
}
void QmlProfilerFileReader::processQmlEvents()
{
for (int i = 0; i < m_ranges.size(); ++i) {
Range range = m_ranges[i].first;
int eventIndex = m_ranges[i].second;
if (eventIndex < 0 || eventIndex >= m_qmlEvents.size()) {
qWarning() << ".qtd file - range index" << eventIndex
<< "is outside of bounds (0, " << m_qmlEvents.size() << ")";
continue;
}
QmlEvent &event = m_qmlEvents[eventIndex];
emit rangedEvent(event.message, event.rangeType, event.detailType, range.startTime,
range.duration, QStringList(event.details),
QmlEventLocation(event.filename, event.line, event.column),
range.numericData1,range.numericData2, range.numericData3,
range.numericData4, range.numericData5);
}
}
QmlProfilerFileWriter::QmlProfilerFileWriter(QObject *parent) :
QObject(parent),
m_startTime(0),
m_endTime(0),
m_measuredTime(0),
m_v8Model(0)
{
m_acceptedRangeTypes << QmlDebug::Compiling << QmlDebug::Creating << QmlDebug::Binding
<< QmlDebug::HandlingSignal << QmlDebug::Javascript;
m_acceptedMessages << QmlDebug::SceneGraphFrame << QmlDebug::PixmapCacheEvent;
}
void QmlProfilerFileWriter::setTraceTime(qint64 startTime, qint64 endTime, qint64 measuredTime)
{
m_startTime = startTime;
m_endTime = endTime;
m_measuredTime = measuredTime;
}
void QmlProfilerFileWriter::setV8DataModel(QV8ProfilerDataModel *dataModel)
{
m_v8Model = dataModel;
}
void QmlProfilerFileWriter::setQmlEvents(const QVector<QmlProfilerDataModel::QmlEventData> &events)
{
foreach (const QmlProfilerDataModel::QmlEventData &event, events) {
const QString hashStr = QmlProfilerDataModel::getHashString(event);
if (!m_qmlEvents.contains(hashStr)) {
QmlEvent e = {
event.displayName,
event.location.filename,
event.data.join(_("")),
event.message,
event.rangeType,
event.detailType,
event.location.line,
event.location.column
};
m_qmlEvents.insert(hashStr, e);
}
Range r = { event.startTime, event.duration, event.numericData1, event.numericData2, event.numericData3, event.numericData4, event.numericData5 };
m_ranges.append(QPair<Range, QString>(r, hashStr));
}
calculateMeasuredTime(events);
}
void QmlProfilerFileWriter::save(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));
QMap<QString, QString> keys;
int i = 0;
foreach (const QString &key, m_qmlEvents.keys())
keys[key] = QString::number(i++);
QHash<QString,QmlEvent>::const_iterator eventIter = m_qmlEvents.constBegin();
for (; eventIter != m_qmlEvents.constEnd(); ++eventIter) {
QmlEvent event = eventIter.value();
stream.writeStartElement(_("event"));
stream.writeAttribute(_("index"), keys[eventIter.key()]);
stream.writeTextElement(_("displayname"), event.displayName);
stream.writeTextElement(_("type"), qmlTypeAsString(event.message, event.rangeType));
if (!event.filename.isEmpty()) {
stream.writeTextElement(_("filename"), event.filename);
stream.writeTextElement(_("line"), QString::number(event.line));
stream.writeTextElement(_("column"), QString::number(event.column));
}
stream.writeTextElement(_("details"), event.details);
if (event.rangeType == Binding)
stream.writeTextElement(_("bindingType"), QString::number(event.detailType));
if (event.message == Event && event.detailType == AnimationFrame)
stream.writeTextElement(_("animationFrame"), QString::number(event.detailType));
if (event.message == PixmapCacheEvent)
stream.writeTextElement(_("cacheEventType"), QString::number(event.detailType));
if (event.message == SceneGraphFrame)
stream.writeTextElement(_("sgEventType"), QString::number(event.detailType));
stream.writeEndElement();
}
stream.writeEndElement(); // eventData
stream.writeStartElement(_("profilerDataModel"));
QVector<QPair<Range, QString> >::const_iterator rangeIter = m_ranges.constBegin();
for (; rangeIter != m_ranges.constEnd(); ++rangeIter) {
Range range = rangeIter->first;
QString eventHash = rangeIter->second;
stream.writeStartElement(_("range"));
stream.writeAttribute(_("startTime"), QString::number(range.startTime));
if (range.duration > 0) // no need to store duration of instantaneous events
stream.writeAttribute(_("duration"), QString::number(range.duration));
stream.writeAttribute(_("eventIndex"), keys[eventHash]);
QmlEvent event = m_qmlEvents.value(eventHash);
// special: animation event
if (event.message == QmlDebug::Event && event.detailType == QmlDebug::AnimationFrame) {
stream.writeAttribute(_("framerate"), QString::number(range.numericData1));
stream.writeAttribute(_("animationcount"), QString::number(range.numericData2));
stream.writeAttribute(_("thread"), QString::number(range.numericData3));
}
// special: pixmap cache event
if (event.message == QmlDebug::PixmapCacheEvent) {
if (event.detailType == PixmapSizeKnown) {
stream.writeAttribute(_("width"), QString::number(range.numericData1));
stream.writeAttribute(_("height"), QString::number(range.numericData2));
}
if (event.detailType == PixmapReferenceCountChanged ||
event.detailType == PixmapCacheCountChanged)
stream.writeAttribute(_("refCount"), QString::number(range.numericData3));
}
if (event.message == QmlDebug::SceneGraphFrame) {
// special: scenegraph frame events
if (range.numericData1 > 0)
stream.writeAttribute(_("timing1"), QString::number(range.numericData1));
if (range.numericData2 > 0)
stream.writeAttribute(_("timing2"), QString::number(range.numericData2));
if (range.numericData3 > 0)
stream.writeAttribute(_("timing3"), QString::number(range.numericData3));
if (range.numericData4 > 0)
stream.writeAttribute(_("timing4"), QString::number(range.numericData4));
if (range.numericData5 > 0)
stream.writeAttribute(_("timing5"), QString::number(range.numericData5));
}
stream.writeEndElement();
}
stream.writeEndElement(); // profilerDataModel
m_v8Model->save(stream);
stream.writeEndElement(); // trace
stream.writeEndDocument();
}
void QmlProfilerFileWriter::calculateMeasuredTime(const QVector<QmlProfilerDataModel::QmlEventData> &events)
{
// measured time isn't used, but old clients might still need it
// -> we calculate it explicitly
qint64 duration = 0;
QHash<int, qint64> endtimesPerLevel;
int level = QmlDebug::Constants::QML_MIN_LEVEL;
endtimesPerLevel[0] = 0;
foreach (const QmlProfilerDataModel::QmlEventData &event, events) {
// whitelist
if (!m_acceptedRangeTypes.contains(event.rangeType) &&
!m_acceptedMessages.contains(event.message))
continue;
// level computation
if (endtimesPerLevel[level] > event.startTime) {
level++;
} else {
while (level > QmlDebug::Constants::QML_MIN_LEVEL && endtimesPerLevel[level-1] <= event.startTime)
level--;
}
endtimesPerLevel[level] = event.startTime + event.duration;
if (level == QmlDebug::Constants::QML_MIN_LEVEL)
duration += event.duration;
}
m_measuredTime = duration;
}
} // namespace Internal
} // namespace QmlProfiler