forked from qt-creator/qt-creator
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: Joerg Bornemann <joerg.bornemann@theqtcompany.com>
This commit is contained in:
@@ -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)
|
||||
{
|
||||
if (m_dataType == StringData)
|
||||
delete m_stringData;
|
||||
|
||||
m_dataType = NumericData;
|
||||
m_numericData[i] = data;
|
||||
}
|
||||
|
||||
QString stringData() const
|
||||
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 NumericData: return QString();
|
||||
case StringData: return *m_stringData;
|
||||
default: return QString::fromUtf8(m_characterData, m_characterDataLength);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void setStringData(const QString &data)
|
||||
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);
|
||||
}
|
||||
|
||||
assignStringData(data);
|
||||
template<typename Container, typename Number>
|
||||
void setNumbers(const Container &numbers)
|
||||
{
|
||||
clearPointer();
|
||||
assignNumbers<Container, Number>(numbers);
|
||||
}
|
||||
|
||||
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 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 setString(const QString &data)
|
||||
{
|
||||
clearPointer();
|
||||
assignNumbers<QByteArray, char>(data.toUtf8());
|
||||
}
|
||||
|
||||
Message rangeStage() const
|
||||
{
|
||||
Q_ASSERT(m_dataType == Inline8Bit);
|
||||
return static_cast<Message>(m_data.internal8bit[0]);
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
m_dataType = StringData;
|
||||
m_stringData = new QString(data);
|
||||
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 = 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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user