Commit 8d15633a authored by Ulf Hermann's avatar Ulf Hermann
Browse files

QmlProfiler: Add a QmlTypedEvent and extend QmlEvent



The QmlTypedEvent is mainly useful to read a generic QmlEvent and
QmlEventType from a QPacket. QmlEventType has a stream operator to do
exactly that. QmlEvent also gets further options to store 32-bit data
in addition to 64- and 8-bit data. Also, with the more generic storage
layout we can reduce the memory consumption of range events by 50%.
This comes at the cost of additional memory allocations for non-range
events, but as non-range events are significantly less frequent than
range events, this is a good tradeoff. Finally the new storage layout
lends itself to efficient serialization, which will help when
developing new storage and transfer formats for QML traces.

Change-Id: I420de68b0142f23c8fb2ca8b329d7ffe69c83fe0
Reviewed-by: default avatarJoerg Bornemann <joerg.bornemann@theqtcompany.com>
parent 61d94c5c
......@@ -113,7 +113,7 @@ void DebugMessagesModel::loadData()
continue;
m_data.insert(insert(event.timestamp(), 0, type.detailType),
MessageData(event.stringData(), event.typeIndex()));
MessageData(event.string(), event.typeIndex()));
if (type.detailType > m_maximumMsgType)
m_maximumMsgType = event.typeIndex();
updateProgress(count(), simpleModel->events().count());
......
......@@ -153,8 +153,8 @@ void InputEventsModel::loadData()
continue;
m_data.insert(insert(event.timestamp(), 0, type.detailType),
InputEvent(static_cast<InputEventType>(event.numericData(0)),
event.numericData(1), event.numericData(2)));
InputEvent(static_cast<InputEventType>(event.number<qint32>(0)),
event.number<qint32>(1), event.number<qint32>(2)));
if (type.detailType == Mouse) {
if (m_mouseTypeId == -1)
......
......@@ -177,12 +177,12 @@ void MemoryUsageModel::loadData()
type.detailType == selectionId(currentUsageIndex) &&
m_data[currentUsageIndex].originTypeIndex == rangeStack.top().originTypeIndex &&
rangeStack.top().startTime < startTime(currentUsageIndex)) {
m_data[currentUsageIndex].update(event.numericData(0));
m_data[currentUsageIndex].update(event.number<qint64>(0));
currentUsage = m_data[currentUsageIndex].size;
} else {
MemoryAllocationItem allocation(event.typeIndex(), currentUsage,
rangeStack.empty() ? -1 : rangeStack.top().originTypeIndex);
allocation.update(event.numericData(0));
allocation.update(event.number<qint64>(0));
currentUsage = allocation.size;
if (currentUsageIndex != -1) {
......@@ -200,12 +200,12 @@ void MemoryUsageModel::loadData()
m_data[currentJSHeapIndex].originTypeIndex ==
rangeStack.top().originTypeIndex &&
rangeStack.top().startTime < startTime(currentJSHeapIndex)) {
m_data[currentJSHeapIndex].update(event.numericData(0));
m_data[currentJSHeapIndex].update(event.number<qint64>(0));
currentSize = m_data[currentJSHeapIndex].size;
} else {
MemoryAllocationItem allocation(event.typeIndex(), currentSize,
rangeStack.empty() ? -1 : rangeStack.top().originTypeIndex);
allocation.update(event.numericData(0));
allocation.update(event.number<qint64>(0));
currentSize = allocation.size;
if (currentSize > m_maxSize)
......
......@@ -211,15 +211,15 @@ void PixmapCacheModel::loadData()
// We can't have cached it before we knew the size
Q_ASSERT(i->cacheState != Cached);
i->size.setWidth(event.numericData(0));
i->size.setHeight(event.numericData(1));
i->size.setWidth(event.number<qint32>(0));
i->size.setHeight(event.number<qint32>(1));
newEvent.sizeIndex = i - pixmap.sizes.begin();
break;
}
if (newEvent.sizeIndex == -1) {
newEvent.sizeIndex = pixmap.sizes.length();
pixmap.sizes << PixmapState(event.numericData(0), event.numericData(1));
pixmap.sizes << PixmapState(event.number<qint32>(0), event.number<qint32>(1));
}
PixmapState &state = pixmap.sizes[newEvent.sizeIndex];
......@@ -234,8 +234,8 @@ void PixmapCacheModel::loadData()
case PixmapCacheCountChanged: {// Cache Size Changed Event
pixmapStartTime = event.timestamp() + 1; // delay 1 ns for proper sorting
bool uncache = cumulatedCount > event.numericData(2);
cumulatedCount = event.numericData(2);
bool uncache = cumulatedCount > event.number<qint32>(2);
cumulatedCount = event.number<qint32>(2);
qint64 pixSize = 0;
// First try to find a preferred pixmap, which either is Corrupt and will be uncached
......
......@@ -24,33 +24,45 @@
****************************************************************************/
#pragma once
#include "qmlprofilereventtypes.h"
#include <QString>
#include <QByteArray>
#include <QVarLengthArray>
#include <initializer_list>
#include <type_traits>
namespace QmlProfiler {
struct QmlEvent {
QmlEvent(qint64 timestamp = -1, qint64 duration = -1, int typeIndex = -1,
qint64 num0 = 0, qint64 num1 = 0, qint64 num2 = 0, qint64 num3 = 0,
qint64 num4 = 0) :
m_timestamp(timestamp), m_duration(duration), m_dataType(NumericData),
m_typeIndex(typeIndex)
QmlEvent() : m_timestamp(-1), m_duration(0), m_typeIndex(-1), m_dataType(Inline8Bit),
m_dataLength(0) {}
template<typename Number>
QmlEvent(qint64 timestamp, qint64 duration, int typeIndex, std::initializer_list<Number> list)
: m_timestamp(timestamp), m_duration(duration), m_typeIndex(typeIndex)
{
m_numericData[0] = num0;
m_numericData[1] = num1;
m_numericData[2] = num2;
m_numericData[3] = num3;
m_numericData[4] = num4;
assignNumbers<std::initializer_list<Number>, Number>(list);
}
QmlEvent(qint64 timestamp, qint64 duration, int typeIndex, const QString &data)
: m_timestamp(timestamp), m_duration(duration), m_typeIndex(typeIndex)
{
assignStringData(data);
assignNumbers<QByteArray, qint8>(data.toUtf8());
}
QmlEvent(const QmlEvent &other) :
m_timestamp(other.m_timestamp), m_duration(other.m_duration),
m_dataType(other.m_dataType), m_typeIndex(other.m_typeIndex)
template<typename Number>
QmlEvent(qint64 timestamp, qint64 duration, int typeIndex, const QVector<Number> &data)
: m_timestamp(timestamp), m_duration(duration), m_typeIndex(typeIndex)
{
assignNumbers<QVector<Number>, Number>(data);
}
QmlEvent(const QmlEvent &other)
: m_timestamp(other.m_timestamp), m_duration(other.m_duration),
m_typeIndex(other.m_typeIndex), m_dataType(other.m_dataType),
m_dataLength(other.m_dataLength)
{
assignData(other);
}
......@@ -58,13 +70,12 @@ struct QmlEvent {
QmlEvent &operator=(const QmlEvent &other)
{
if (this != &other) {
if (m_dataType == StringData)
delete m_stringData;
clearPointer();
m_timestamp = other.m_timestamp;
m_duration = other.m_duration;
m_typeIndex = other.m_typeIndex;
m_dataType = other.m_dataType;
m_dataLength = other.m_dataLength;
assignData(other);
}
return *this;
......@@ -72,8 +83,7 @@ struct QmlEvent {
~QmlEvent()
{
if (m_dataType == StringData)
delete m_stringData;
clearPointer();
}
qint64 timestamp() const { return m_timestamp; }
......@@ -85,31 +95,102 @@ struct QmlEvent {
int typeIndex() const { return m_typeIndex; }
void setTypeIndex(int typeIndex) { m_typeIndex = typeIndex; }
qint64 numericData(int i) const { return m_dataType == NumericData ? m_numericData[i] : 0; }
void setNumericData(int i, qint64 data)
template<typename Number>
Number number(int i) const
{
// Trailing zeroes can be omitted, for example for SceneGraph events
if (i >= m_dataLength)
return 0;
switch (m_dataType) {
case Inline8Bit:
return m_data.internal8bit[i];
case Inline16Bit:
return m_data.internal16bit[i];
case Inline32Bit:
return m_data.internal32bit[i];
case Inline64Bit:
return m_data.internal64bit[i];
case External8Bit:
return static_cast<const qint8 *>(m_data.external)[i];
case External16Bit:
return static_cast<const qint16 *>(m_data.external)[i];
case External32Bit:
return static_cast<const qint32 *>(m_data.external)[i];
case External64Bit:
return static_cast<const qint64 *>(m_data.external)[i];
default:
return 0;
}
}
template<typename Number>
void setNumber(int i, Number number)
{
if (m_dataType == StringData)
delete m_stringData;
QVarLengthArray<Number> nums = numbers<QVarLengthArray<Number>, Number>();
int prevSize = nums.size();
if (i >= prevSize) {
nums.resize(i + 1);
// Fill with zeroes. We don't want to accidentally prevent squeezing.
while (prevSize < i)
nums[prevSize++] = 0;
}
nums[i] = number;
setNumbers<QVarLengthArray<Number>, Number>(nums);
}
m_dataType = NumericData;
m_numericData[i] = data;
template<typename Container, typename Number>
void setNumbers(const Container &numbers)
{
clearPointer();
assignNumbers<Container, Number>(numbers);
}
QString stringData() const
template<typename Number>
void setNumbers(std::initializer_list<Number> numbers)
{
setNumbers<std::initializer_list<Number>, Number>(numbers);
}
template<typename Container, typename Number>
Container numbers() const
{
Container container;
for (int i = 0; i < m_dataLength; ++i)
container.append(number<Number>(i));
return container;
}
QString string() const
{
switch (m_dataType) {
case NumericData: return QString();
case StringData: return *m_stringData;
default: return QString::fromUtf8(m_characterData, m_characterDataLength);
case External8Bit:
return QString::fromUtf8(static_cast<const char *>(m_data.external));
case Inline8Bit:
return QString::fromUtf8(m_data.internalChar);
default:
Q_UNREACHABLE();
return QString();
}
}
void setStringData(const QString &data)
void setString(const QString &data)
{
clearPointer();
assignNumbers<QByteArray, char>(data.toUtf8());
}
Message rangeStage() const
{
if (m_dataType == StringData)
delete m_stringData;
Q_ASSERT(m_dataType == Inline8Bit);
return static_cast<Message>(m_data.internal8bit[0]);
}
assignStringData(data);
void setRangeStage(Message stage)
{
clearPointer();
m_dataType = Inline8Bit;
m_dataLength = 1;
m_data.internal8bit[0] = stage;
}
bool isValid() const
......@@ -118,50 +199,96 @@ struct QmlEvent {
}
private:
static const quint8 StringData = 254;
static const quint8 NumericData = 255;
enum Type: quint16 {
External = 1,
Inline8Bit = 8,
External8Bit = Inline8Bit | External,
Inline16Bit = 16,
External16Bit = Inline16Bit | External,
Inline32Bit = 32,
External32Bit = Inline32Bit | External,
Inline64Bit = 64,
External64Bit = Inline64Bit | External
};
qint64 m_timestamp;
qint64 m_duration;
union {
qint64 m_numericData[5];
QString *m_stringData;
char m_characterData[5 * sizeof(qint64) + 3];
};
union {
quint8 m_dataType;
quint8 m_characterDataLength;
};
void *external;
char internalChar [sizeof(external)];
qint8 internal8bit [sizeof(external)];
qint16 internal16bit[sizeof(external) / 2];
qint32 internal32bit[sizeof(external) / 4];
qint64 internal64bit[sizeof(external) / 8];
} m_data;
qint32 m_typeIndex;
Type m_dataType;
quint16 m_dataLength;
void assignData(const QmlEvent &other)
{
switch (m_dataType) {
case StringData:
m_stringData = new QString(*other.m_stringData);
break;
case NumericData:
for (int i = 0; i < 5; ++i)
m_numericData[i] = other.m_numericData[i];
break;
default:
memcpy(m_characterData, other.m_characterData, m_characterDataLength);
break;
if (m_dataType & External) {
int length = m_dataLength * (other.m_dataType / 8);
m_data.external = malloc(length);
memcpy(m_data.external, other.m_data.external, length);
} else {
memcpy(&m_data, &other.m_data, sizeof(m_data));
}
}
void assignStringData(const QString &data)
template<typename Big, typename Small>
bool squeezable(Big source)
{
QByteArray cdata = data.toUtf8();
if (cdata.length() <= (int)sizeof(m_characterData)) {
m_characterDataLength = cdata.length();
memcpy(m_characterData, cdata.constData(), m_characterDataLength);
return static_cast<Small>(source) == source;
}
template<typename Container, typename Number>
typename std::enable_if<(sizeof(Number) > 1), bool>::type
squeeze(const Container &numbers)
{
typedef typename QIntegerForSize<sizeof(Number) / 2>::Signed Small;
foreach (Number item, numbers) {
if (!squeezable<Number, Small>(item))
return false;
}
assignNumbers<Container, Small>(numbers);
return true;
}
template<typename Container, typename Number>
typename std::enable_if<(sizeof(Number) <= 1), bool>::type
squeeze(const Container &)
{
return false;
}
template<typename Container, typename Number>
void assignNumbers(const Container &numbers)
{
Number *data;
m_dataLength = squeezable<size_t, quint16>(numbers.size()) ?
numbers.size() : std::numeric_limits<quint16>::max();
if (m_dataLength > sizeof(m_data) / sizeof(Number)) {
if (squeeze<Container, Number>(numbers))
return;
m_dataType = static_cast<Type>((sizeof(Number) * 8) | External);
m_data.external = malloc(m_dataLength * sizeof(Number));
data = static_cast<Number *>(m_data.external);
} else {
m_dataType = StringData;
m_stringData = new QString(data);
m_dataType = static_cast<Type>(sizeof(Number) * 8);
data = static_cast<Number *>(m_dataType & External ? m_data.external : &m_data);
}
quint16 i = 0; // If you really have more than 64k items, this will wrap. Too bad.
foreach (Number item, numbers)
data[i++] = item;
}
void clearPointer()
{
if (m_dataType & External)
free(m_data.external);
}
};
......
......@@ -39,6 +39,7 @@ SOURCES += \
qmlprofilertracefile.cpp \
qmlprofilertraceview.cpp \
qmlprofilerviewmanager.cpp \
qmltypedevent.cpp \
scenegraphtimelinemodel.cpp
HEADERS += \
......@@ -84,6 +85,7 @@ HEADERS += \
qmlprofilertracefile.h \
qmlprofilertraceview.h \
qmlprofilerviewmanager.h \
qmltypedevent.h \
scenegraphtimelinemodel.h
RESOURCES += \
......
......@@ -59,6 +59,7 @@ QtcPlugin {
"qmlprofilertracefile.cpp", "qmlprofilertracefile.h",
"qmlprofilertraceview.cpp", "qmlprofilertraceview.h",
"qmlprofilerviewmanager.cpp", "qmlprofilerviewmanager.h",
"qmltypedevent.cpp", "qmltypedevent.h",
"scenegraphtimelinemodel.cpp", "scenegraphtimelinemodel.h",
]
}
......
......@@ -78,10 +78,10 @@ void QmlProfilerAnimationsModel::loadData()
if (!accepted(type))
continue;
lastThread = (AnimationThread)event.numericData(2);
lastThread = (AnimationThread)event.number<qint32>(2);
// initial estimation of the event duration: 1/framerate
qint64 estimatedDuration = event.numericData(0) > 0 ? 1e9/event.numericData(0) : 1;
qint64 estimatedDuration = event.number<qint32>(0) > 0 ? 1e9 / event.number<qint32>(0) : 1;
// the profiler registers the animation events at the end of them
qint64 realEndTime = event.timestamp();
......@@ -97,8 +97,8 @@ void QmlProfilerAnimationsModel::loadData()
// Don't "fix" the framerate even if we've fixed the duration.
// The server should know better after all and if it doesn't we want to see that.
lastEvent.typeId = event.typeIndex();
lastEvent.framerate = (int)event.numericData(0);
lastEvent.animationcount = (int)event.numericData(1);
lastEvent.framerate = event.number<qint32>(0);
lastEvent.animationcount = event.number<qint32>(1);
QTC_ASSERT(lastEvent.animationcount > 0, continue);
m_data.insert(insert(realStartTime, realEndTime - realStartTime, lastThread), lastEvent);
......
......@@ -248,7 +248,7 @@ void QmlProfilerDataModel::addEvent(Message message, RangeType rangeType, int de
message == DebugMessage ? QString() : data);
QmlEvent eventData = (message == DebugMessage) ?
QmlEvent(startTime, duration, -1, data) :
QmlEvent(startTime, duration, -1, ndata1, ndata2, ndata3, ndata4, ndata5);
QmlEvent(startTime, duration, -1, {ndata1, ndata2, ndata3, ndata4, ndata5});
QHash<QmlEventType, int>::Iterator it = d->eventTypeIds.find(typeData);
if (it != d->eventTypeIds.end()) {
......
......@@ -360,7 +360,7 @@ void QmlProfilerFileReader::loadEvents(QXmlStreamReader &stream)
case QXmlStreamReader::StartElement: {
if (elementName == _("range")) {
progress(stream.device());
QmlEvent event(0, 0, -1, 0, 0, 0, 0, 0);
QmlEvent event;
const QXmlStreamAttributes attributes = stream.attributes();
if (!attributes.hasAttribute(_("startTime"))
......@@ -375,37 +375,37 @@ void QmlProfilerFileReader::loadEvents(QXmlStreamReader &stream)
// attributes for special events
if (attributes.hasAttribute(_("framerate")))
event.setNumericData(0, attributes.value(_("framerate")).toLongLong());
event.setNumber<qint32>(0, attributes.value(_("framerate")).toInt());
if (attributes.hasAttribute(_("animationcount")))
event.setNumericData(1, attributes.value(_("animationcount")).toLongLong());
event.setNumber<qint32>(1, attributes.value(_("animationcount")).toInt());
if (attributes.hasAttribute(_("thread")))
event.setNumericData(2, attributes.value(_("thread")).toLongLong());
event.setNumber<qint32>(2, attributes.value(_("thread")).toInt());
if (attributes.hasAttribute(_("width")))
event.setNumericData(0, attributes.value(_("width")).toLongLong());
event.setNumber<qint32>(0, attributes.value(_("width")).toInt());
if (attributes.hasAttribute(_("height")))
event.setNumericData(1, attributes.value(_("height")).toLongLong());
event.setNumber<qint32>(1, attributes.value(_("height")).toInt());
if (attributes.hasAttribute(_("refCount")))
event.setNumericData(2, attributes.value(_("refCount")).toLongLong());
event.setNumber<qint32>(2, attributes.value(_("refCount")).toInt());
if (attributes.hasAttribute(_("amount")))
event.setNumericData(0, attributes.value(_("amount")).toLongLong());
event.setNumber<qint64>(0, attributes.value(_("amount")).toLongLong());
if (attributes.hasAttribute(_("timing1")))
event.setNumericData(0, attributes.value(_("timing1")).toLongLong());
event.setNumber<qint64>(0, attributes.value(_("timing1")).toLongLong());
if (attributes.hasAttribute(_("timing2")))
event.setNumericData(1, attributes.value(_("timing2")).toLongLong());
event.setNumber<qint64>(1, attributes.value(_("timing2")).toLongLong());
if (attributes.hasAttribute(_("timing3")))
event.setNumericData(2, attributes.value(_("timing3")).toLongLong());
event.setNumber<qint64>(2, attributes.value(_("timing3")).toLongLong());
if (attributes.hasAttribute(_("timing4")))
event.setNumericData(3, attributes.value(_("timing4")).toLongLong());
event.setNumber<qint64>(3, attributes.value(_("timing4")).toLongLong());
if (attributes.hasAttribute(_("timing5")))
event.setNumericData(4, attributes.value(_("timing5")).toLongLong());
event.setNumber<qint64>(4, attributes.value(_("timing5")).toLongLong());
if (attributes.hasAttribute(_("type")))
event.setNumericData(0, attributes.value(_("type")).toLongLong());
event.setNumber<qint32>(0, attributes.value(_("type")).toInt());
if (attributes.hasAttribute(_("data1")))
event.setNumericData(1, attributes.value(_("data1")).toLongLong());
event.setNumber<qint32>(1, attributes.value(_("data1")).toInt());
if (attributes.hasAttribute(_("data2")))
event.setNumericData(2, attributes.value(_("data2")).toLongLong());
event.setNumber<qint32>(2, attributes.value(_("data2")).toInt());
if (attributes.hasAttribute(_("text")))
event.setStringData(attributes.value(_("text")).toString());
event.setString(attributes.value(_("text")).toString());
event.setTypeIndex(attributes.value(_("eventIndex")).toInt());
......@@ -601,49 +601,47 @@ void QmlProfilerFileWriter::save(QIODevice *device)
if (type.message == Event) {
if (type.detailType == AnimationFrame) {
// special: animation event
stream.writeAttribute(_("framerate"), QString::number(event.numericData(0)));
stream.writeAttribute(_("animationcount"), QString::number(event.numericData(1)));
stream.writeAttribute(_("thread"), QString::number(event.numericData(2)));
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.numericData(0)));
stream.writeAttribute(_("data1"), QString::number(event.numericData(1)));
stream.writeAttribute(_("data2"), QString::number(event.numericData(2)));
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.numericData(0)));
stream.writeAttribute(_("height"), QString::number(event.numericData(1)));
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.numericData(2)));
stream.writeAttribute(_("refCount"), QString::number(event.number<qint32>(2)));
}
if (type.message == SceneGraphFrame) {
// special: scenegraph frame events
if (event.numericData(0) > 0)