summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2017-09-15 14:20:11 +0200
committerUlf Hermann <ulf.hermann@qt.io>2017-11-30 08:49:27 +0000
commit068a615d253b5c40b686cdec544f001e5baa7be9 (patch)
treed7943fb0848d05ab8d334222072f78dbee7a5f26
parent5f842e0d756bf4d2dbe7b87e7d2e9bc15fadf94e (diff)
Parse tracing data and expose it to the client
Change-Id: I8b4f746093d2f531c3967802542417c10dc6ce3d Reviewed-by: Milian Wolff <milian.wolff@kdab.com>
-rw-r--r--app/app.pro6
-rw-r--r--app/app.qbs4
-rw-r--r--app/main.cpp1
-rw-r--r--app/perfdata.cpp10
-rw-r--r--app/perfdata.h1
-rw-r--r--app/perffeatures.cpp10
-rw-r--r--app/perffeatures.h3
-rw-r--r--app/perftracingdata.cpp364
-rw-r--r--app/perftracingdata.h108
-rw-r--r--app/perfunwind.cpp131
-rw-r--r--app/perfunwind.h13
-rw-r--r--tests/auto/perfdata/perfdata.pro3
-rw-r--r--tests/auto/perfdata/perfdata.qbs8
-rw-r--r--tests/auto/perfdata/perfdata.qrc1
-rw-r--r--tests/auto/perfdata/probe.data.filebin0 -> 24628 bytes
-rw-r--r--tests/auto/perfdata/tst_perfdata.cpp175
-rw-r--r--tests/auto/shared/perfparsertestclient.cpp24
-rw-r--r--tests/auto/shared/perfparsertestclient.h14
18 files changed, 826 insertions, 50 deletions
diff --git a/app/app.pro b/app/app.pro
index 384908c..0b7729f 100644
--- a/app/app.pro
+++ b/app/app.pro
@@ -28,7 +28,8 @@ SOURCES += main.cpp \
perfstdin.cpp \
perfsymboltable.cpp \
perfelfmap.cpp \
- perfkallsyms.cpp
+ perfkallsyms.cpp \
+ perftracingdata.cpp
HEADERS += \
perfattributes.h \
@@ -41,6 +42,7 @@ HEADERS += \
perfstdin.h \
perfsymboltable.h \
perfelfmap.h \
- perfkallsyms.h
+ perfkallsyms.h \
+ perftracingdata.h
OTHER_FILES += app.qbs
diff --git a/app/app.qbs b/app/app.qbs
index 470639b..7cb866f 100644
--- a/app/app.qbs
+++ b/app/app.qbs
@@ -35,6 +35,8 @@ QtcTool {
"perfelfmap.cpp",
"perfelfmap.h",
"perfkallsyms.cpp",
- "perfkallsyms.h"
+ "perfkallsyms.h",
+ "perftracingdata.cpp",
+ "perftracingdata.h",
]
}
diff --git a/app/main.cpp b/app/main.cpp
index 26e4183..46948bf 100644
--- a/app/main.cpp
+++ b/app/main.cpp
@@ -251,6 +251,7 @@ int main(int argc, char *argv[])
features.setArchitecture(parser.value(arch).toLatin1());
QObject::connect(&header, &PerfHeader::finished, [&]() {
+ unwind.setByteOrder(static_cast<QSysInfo::Endian>(header.byteOrder()));
if (!header.isPipe()) {
const qint64 filePos = infile->pos();
if (!attributes.read(infile.data(), &header)) {
diff --git a/app/perfdata.cpp b/app/perfdata.cpp
index 91110ed..dcfe431 100644
--- a/app/perfdata.cpp
+++ b/app/perfdata.cpp
@@ -20,6 +20,7 @@
#include "perfdata.h"
#include "perfunwind.h"
+#include "perftracingdata.h"
#include <QDebug>
#include <limits>
@@ -141,9 +142,12 @@ PerfData::ReadStatus PerfData::processEvents(QDataStream &stream)
if (contentSize == 4) {
// The content is actually another 4 byte integer,
// describing the size of the real content that follows.
- quint32 content;
- stream >> content;
- stream.skipRawData(content);
+ PerfTracingData tracing;
+ quint32 size;
+ stream >> size;
+ tracing.setSize(size);
+ stream >> tracing;
+ m_destination->tracing(tracing);
} else {
// Maybe someone with a brain will fix this eventually ...
// then we'll hit this branch.
diff --git a/app/perfdata.h b/app/perfdata.h
index 572d318..b6c1861 100644
--- a/app/perfdata.h
+++ b/app/perfdata.h
@@ -376,6 +376,7 @@ public:
const QList<quint64> &callchain() const { return m_callchain; }
quint64 period() const { return m_period; }
quint64 weight() const { return m_weight; }
+ const QByteArray &rawData() const { return m_rawData; }
private:
struct ReadFormat {
diff --git a/app/perffeatures.cpp b/app/perffeatures.cpp
index 537773c..0d43802 100644
--- a/app/perffeatures.cpp
+++ b/app/perffeatures.cpp
@@ -41,6 +41,16 @@ void PerfFeatures::createFeature(QIODevice *device, QDataStream::ByteOrder byteO
stream.setByteOrder(byteOrder);
switch (featureId) {
+ case PerfHeader::TRACING_DATA:
+ if (section.size > std::numeric_limits<quint32>::max()) {
+ qWarning() << "Excessively large tracing data section" << section.size;
+ } else if (section.size < 0) {
+ qWarning() << "Tracing data section with negative size" << section.size;
+ } else {
+ m_tracingData.setSize(static_cast<quint32>(section.size));
+ stream >> m_tracingData;
+ }
+ break;
case PerfHeader::BUILD_ID:
m_buildId.size = section.size;
stream >> m_buildId;
diff --git a/app/perffeatures.h b/app/perffeatures.h
index da25609..e1563a2 100644
--- a/app/perffeatures.h
+++ b/app/perffeatures.h
@@ -23,6 +23,7 @@
#include "perfheader.h"
#include "perfattributes.h"
+#include "perftracingdata.h"
#include <QHash>
#include <QVector>
@@ -163,6 +164,7 @@ public:
const QByteArray &architecture() const { return m_arch.value; }
void setArchitecture(const QByteArray &arch) { m_arch.value = arch; }
+ PerfTracingData tracingData() const { return m_tracingData; }
PerfBuildId buildId() const { return m_buildId; }
QByteArray hostName() const { return m_hostName.value; }
QByteArray osRelease() const { return m_osRelease.value; }
@@ -183,6 +185,7 @@ private:
void createFeature(QIODevice *device, QDataStream::ByteOrder byteOrder,
const PerfFileSection &section, PerfHeader::Feature featureId);
+ PerfTracingData m_tracingData;
PerfBuildId m_buildId;
PerfStringFeature m_hostName;
PerfStringFeature m_osRelease;
diff --git a/app/perftracingdata.cpp b/app/perftracingdata.cpp
new file mode 100644
index 0000000..180a693
--- /dev/null
+++ b/app/perftracingdata.cpp
@@ -0,0 +1,364 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd
+** All rights reserved.
+** For any questions to The Qt Company, please use contact form at http://www.qt.io/contact-us
+**
+** This file is part of the Qt Enterprise Perf Profiler Add-on.
+**
+** GNU General Public License Usage
+** This file may be used under the terms of the GNU General Public License
+** version 3 as published by the Free Software Foundation and appearing in
+** the file LICENSE.GPLv3 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.html.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://www.qt.io/contact-us
+**
+****************************************************************************/
+
+#include "perftracingdata.h"
+#include "perfattributes.h"
+#include <QDataStream>
+#include <QDebug>
+#include <QBuffer>
+
+#include <functional>
+
+static QByteArray readNullTerminatedString(QDataStream &stream)
+{
+ QByteArray string;
+ qint8 read = 0;
+ while (true) {
+ stream >> read;
+ if (read != 0)
+ string.append(read);
+ else
+ return string;
+ }
+}
+
+static bool checkMagic(QDataStream &stream, const QByteArray &magic)
+{
+ QByteArray read(magic.size(), Qt::Uninitialized);
+ stream.readRawData(read.data(), read.size());
+ if (read != magic) {
+ qWarning() << "Invalid magic in perf tracing data" << read << " - expected" << magic;
+ return false;
+ }
+ return true;
+}
+
+template<typename Number>
+static bool checkSize(Number size)
+{
+ if (size > std::numeric_limits<int>::max()) {
+ qWarning() << "Excessively large section in tracing data" << size;
+ return false;
+ }
+ return true;
+}
+
+const EventFormat &PerfTracingData::eventFormat(qint32 id) const
+{
+ static EventFormat invalid;
+ auto it = m_eventFormats.constFind(id);
+ if (it != m_eventFormats.constEnd())
+ return *it;
+ else
+ return invalid;
+}
+
+bool PerfTracingData::readHeaderFiles(QDataStream &stream)
+{
+ if (!checkMagic(stream, QByteArray("header_page") + '\0'))
+ return false;
+
+ quint64 size;
+ stream >> size;
+
+ if (!checkSize(size))
+ return false;
+
+ QByteArray buffer(static_cast<int>(size), Qt::Uninitialized);
+ stream.readRawData(buffer.data(), buffer.size());
+
+ for (QByteArray line : buffer.split('\n')) {
+ if (!line.isEmpty())
+ m_headerFields << readFormatField(line);
+ }
+
+ if (!checkMagic(stream, QByteArray("header_event") + '\0'))
+ return false;
+
+ stream >> size;
+ if (!checkSize(size))
+ return false;
+
+ stream.skipRawData(static_cast<int>(size));
+
+ return true;
+}
+
+static void processLine(const QByteArray &line,
+ const std::function<void(const QByteArray &, const QByteArray &)> &handler)
+{
+ for (const QByteArray &chunk : line.split('\t')) {
+ QList<QByteArray> segments = chunk.split(':');
+ if (segments.size() != 2)
+ continue;
+
+ QByteArray name = segments[0].toLower();
+ QByteArray value = segments[1].trimmed();
+ if (value.endsWith(';'))
+ value.chop(1);
+ handler(name, value);
+ }
+}
+
+FormatField PerfTracingData::readFormatField(const QByteArray &line)
+{
+ FormatField field;
+ processLine(line, [&](const QByteArray &name, const QByteArray &value) {
+ if (name == "field") {
+ QList<QByteArray> fieldSegments = value.trimmed().split(' ');
+ QByteArray fieldName = fieldSegments.length() > 0 ? fieldSegments.takeLast()
+ : QByteArray();
+ if (fieldName.startsWith('*')) {
+ field.flags |= FIELD_IS_POINTER;
+ fieldName.remove(0, 1);
+ }
+ if (fieldName.endsWith(']')) {
+ const int opening = fieldName.lastIndexOf('[');
+ if (opening >= 0) {
+ field.flags |= FIELD_IS_ARRAY;
+ field.arraylen = fieldName.mid(opening + 1,
+ fieldName.length() - opening - 2).toUInt();
+ fieldName.chop(fieldName.length() - opening);
+ }
+ }
+
+ field.name = fieldName;
+ if (fieldSegments.length() > 0 && fieldSegments.last() == "[]") {
+ fieldSegments.removeLast();
+ field.flags |= FIELD_IS_ARRAY;
+ }
+ field.type = fieldSegments.join(' ');
+ } else if (name == "offset") {
+ field.offset = value.toUInt();
+ } else if (name == "size") {
+ field.size = value.toUInt();
+ } else if (name == "signed") {
+ if (value.toInt() != 0)
+ field.flags |= FIELD_IS_SIGNED;
+ }
+ });
+
+ if (field.type.startsWith("__data_loc"))
+ field.flags |= FIELD_IS_DYNAMIC;
+ if (field.type.contains("long"))
+ field.flags |= FIELD_IS_LONG;
+
+ if (field.flags & FIELD_IS_ARRAY) {
+ if (field.type.contains("char") || field.type.contains("u8") || field.type.contains("s8")) {
+ field.flags |= FIELD_IS_STRING;
+ field.elementsize = 1;
+ } else if (field.arraylen > 0) {
+ field.elementsize = field.size / field.arraylen;
+ } else if (field.type.contains("u16") || field.type.contains("s16")) {
+ field.elementsize = 2;
+ } else if (field.type.contains("u32") || field.type.contains("s32")) {
+ field.elementsize = 4;
+ } else if (field.type.contains("u64") || field.type.contains("s64")) {
+ field.elementsize = 8;
+ } else if (field.flags & FIELD_IS_LONG) {
+ field.elementsize = m_fileLongSize;
+ }
+ } else {
+ field.elementsize = field.size;
+ }
+ return field;
+}
+
+enum FieldStage {
+ BeforeFields,
+ CommonFields,
+ NonCommonFields,
+ AfterFields
+};
+
+bool PerfTracingData::readEventFormats(QDataStream &stream, const QByteArray &system)
+{
+ qint32 count;
+ stream >> count;
+
+ for (qint32 x = 0; x < count; ++x) {
+ qint32 id = -1;
+ bool seenId = false;
+ EventFormat event;
+ quint64 size;
+ stream >> size;
+ if (!checkSize(size))
+ return false;
+
+ event.system = system;
+ if (system == "ftrace")
+ event.flags |= EVENT_FL_ISFTRACE;
+
+ QByteArray buffer(static_cast<int>(size), Qt::Uninitialized);
+ stream.readRawData(buffer.data(), buffer.length());
+
+ FieldStage stage = BeforeFields;
+ for (const QByteArray &line : buffer.split('\n')) {
+ switch (stage) {
+ case CommonFields:
+ if (line.isEmpty())
+ stage = NonCommonFields;
+ else
+ event.commonFields.append(readFormatField(line));
+ break;
+ case NonCommonFields:
+ if (line.isEmpty())
+ stage = AfterFields;
+ else
+ event.fields.append(readFormatField(line));
+ break;
+ case BeforeFields:
+ case AfterFields:
+ processLine(line, [&](const QByteArray &name, const QByteArray &value) {
+ if (name == "name") {
+ event.name = value;
+ if ((event.flags & EVENT_FL_ISFTRACE) && value == "bprint")
+ event.flags |= EVENT_FL_ISBPRINT;
+ } else if (name == "id") {
+ id = value.toInt();
+ seenId = true;
+ } else if (name == "format") {
+ stage = CommonFields;
+ }
+ });
+ }
+ }
+
+ if (!seenId)
+ return false;
+
+ m_eventFormats[id] = event;
+ }
+
+ return true;
+}
+
+bool PerfTracingData::readEventFiles(QDataStream &stream)
+{
+ qint32 systems;
+ stream >> systems;
+ for (qint32 i = 0; i < systems; ++i) {
+ if (!readEventFormats(stream, readNullTerminatedString(stream)))
+ return false;
+ }
+
+ return true;
+}
+
+bool PerfTracingData::readProcKallsyms(QDataStream &stream)
+{
+ quint32 size;
+ stream >> size;
+ if (!checkSize(size))
+ return false;
+ stream.skipRawData(static_cast<int>(size)); // unused, also in perf
+ return true;
+}
+
+bool PerfTracingData::readFtracePrintk(QDataStream &stream)
+{
+ quint32 size;
+ stream >> size;
+
+ if (!checkSize(size))
+ return false;
+
+ QByteArray buffer(static_cast<int>(size), Qt::Uninitialized);
+ stream.readRawData(buffer.data(), buffer.length());
+
+ for (QByteArray line : buffer.split('\n')) {
+ if (!line.isEmpty()) {
+ QList<QByteArray> segments = line.split(':');
+ if (segments.length() == 2) {
+ QByteArray value = segments[1].trimmed();
+ m_ftracePrintk[segments[0].trimmed().toULongLong(nullptr, 0)]
+ = value.mid(1, value.length() - 2);
+ }
+ }
+ }
+
+ return true;
+}
+
+bool PerfTracingData::readSavedCmdline(QDataStream &stream)
+{
+ quint64 size;
+ stream >> size;
+ if (!checkSize(size))
+ return false;
+
+ QByteArray buffer(static_cast<int>(size), Qt::Uninitialized);
+ stream.readRawData(buffer.data(), buffer.length());
+
+ for (const QByteArray &line : buffer.split('\n')) {
+ // Each line is prefixed with the PID it refers to
+ if (!line.isEmpty())
+ m_savedCmdlines.append(line);
+ }
+
+ return true;
+}
+
+QDataStream &operator>>(QDataStream &parentStream, PerfTracingData &record)
+{
+ if (!checkSize(record.m_size)) {
+ parentStream.skipRawData(std::numeric_limits<int>::max());
+ parentStream.skipRawData(static_cast<int>(record.m_size - std::numeric_limits<int>::max()));
+ return parentStream;
+ }
+
+ QByteArray data(static_cast<int>(record.m_size), Qt::Uninitialized);
+ parentStream.readRawData(data.data(), data.size());
+ QDataStream stream(data);
+
+ if (!checkMagic(stream, "\027\bDtracing"))
+ return parentStream;
+
+ record.m_version = readNullTerminatedString(stream);
+
+ qint8 read;
+ stream >> read;
+ record.m_bigEndian = (read != 0);
+ stream.setByteOrder(record.m_bigEndian ? QDataStream::BigEndian : QDataStream::LittleEndian);
+ stream >> read;
+ record.m_fileLongSize = (read != 0);
+ stream >> record.m_filePageSize;
+
+ if (!record.readHeaderFiles(stream))
+ return parentStream;
+ if (!record.readEventFormats(stream, "ftrace"))
+ return parentStream;
+ if (!record.readEventFiles(stream))
+ return parentStream;
+ if (!record.readProcKallsyms(stream))
+ return parentStream;
+ if (!record.readFtracePrintk(stream))
+ return parentStream;
+ if (record.m_version.toFloat() >= 0.6f) {
+ if (!record.readSavedCmdline(stream))
+ return parentStream;
+ }
+
+ const qint64 padding = record.m_size - stream.device()->pos();
+ if (padding >= 8)
+ qWarning() << "More trace data left after parsing:" << padding;
+
+ return parentStream;
+}
diff --git a/app/perftracingdata.h b/app/perftracingdata.h
new file mode 100644
index 0000000..7d0f549
--- /dev/null
+++ b/app/perftracingdata.h
@@ -0,0 +1,108 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd
+** All rights reserved.
+** For any questions to The Qt Company, please use contact form at http://www.qt.io/contact-us
+**
+** This file is part of the Qt Enterprise Perf Profiler Add-on.
+**
+** GNU General Public License Usage
+** This file may be used under the terms of the GNU General Public License
+** version 3 as published by the Free Software Foundation and appearing in
+** the file LICENSE.GPLv3 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.html.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://www.qt.io/contact-us
+**
+****************************************************************************/
+
+#ifndef PERFTRACINGDATA_H
+#define PERFTRACINGDATA_H
+
+#include <QByteArray>
+#include <QVector>
+#include <QHash>
+
+enum FormatFlags
+{
+ FIELD_IS_ARRAY = 1,
+ FIELD_IS_POINTER = 2,
+ FIELD_IS_SIGNED = 4,
+ FIELD_IS_STRING = 8,
+ FIELD_IS_DYNAMIC = 16,
+ FIELD_IS_LONG = 32,
+ FIELD_IS_FLAG = 64,
+ FIELD_IS_SYMBOLIC = 128,
+};
+
+struct FormatField
+{
+ QByteArray type;
+ QByteArray name;
+ quint32 offset = 0;
+ quint32 size = 0;
+ quint32 arraylen = 0;
+ quint32 elementsize = 0;
+ quint32 flags = 0;
+};
+
+enum EventFormatFlags {
+ EVENT_FL_ISFTRACE = 0x01,
+ EVENT_FL_ISPRINT = 0x02,
+ EVENT_FL_ISBPRINT = 0x04,
+ EVENT_FL_ISFUNCENT = 0x10,
+ EVENT_FL_ISFUNCRET = 0x20,
+ EVENT_FL_NOHANDLE = 0x40,
+ EVENT_FL_PRINTRAW = 0x80,
+
+ EVENT_FL_FAILED = 0x80000000
+};
+
+struct EventFormat
+{
+ QByteArray name;
+ QByteArray system;
+ QVector<FormatField> commonFields;
+ QVector<FormatField> fields;
+ quint32 flags = 0;
+};
+
+class PerfTracingData
+{
+public:
+ quint32 size() const { return m_size; }
+ void setSize(quint32 size) { m_size = size; }
+ QByteArray version() const { return m_version; }
+ const EventFormat &eventFormat(qint32 id) const;
+ const QHash<qint32, EventFormat> &eventFormats() const {return m_eventFormats; }
+
+private:
+ bool readHeaderFiles(QDataStream &stream);
+ bool readFtraceFiles(QDataStream &stream);
+ bool readEventFiles(QDataStream &stream);
+ bool readProcKallsyms(QDataStream &stream);
+ bool readFtracePrintk(QDataStream &stream);
+ bool readSavedCmdline(QDataStream &stream);
+ bool readEventFormats(QDataStream &stream, const QByteArray &system);
+
+ FormatField readFormatField(const QByteArray &line);
+
+ quint32 m_size = 0;
+ QByteArray m_version;
+ bool m_bigEndian = false;
+ bool m_fileLongSize = false;
+ qint32 m_filePageSize = false;
+
+ QHash<qint32, EventFormat> m_eventFormats;
+ QVector<FormatField> m_headerFields;
+ QHash<quint64, QByteArray> m_ftracePrintk;
+ QVector<QByteArray> m_savedCmdlines;
+
+ friend QDataStream &operator>>(QDataStream &stream, PerfTracingData &record);
+};
+
+QDataStream &operator>>(QDataStream &stream, PerfTracingData &record);
+
+#endif // PERFTRACINGDATA_H
diff --git a/app/perfunwind.cpp b/app/perfunwind.cpp
index 3cd7414..6be1ffc 100644
--- a/app/perfunwind.cpp
+++ b/app/perfunwind.cpp
@@ -229,6 +229,23 @@ void PerfUnwind::sendAttributes(qint32 id, const PerfEventAttributes &attributes
sendBuffer(buffer);
}
+void PerfUnwind::sendEventFormat(qint32 id, const EventFormat &format)
+{
+ const qint32 systemId = resolveString(format.system);
+ const qint32 nameId = resolveString(format.name);
+
+ for (const FormatField &field : format.commonFields)
+ resolveString(field.name);
+
+ for (const FormatField &field : format.fields)
+ resolveString(field.name);
+
+ QByteArray buffer;
+ QDataStream(&buffer, QIODevice::WriteOnly) << static_cast<quint8>(TracePointFormat) << id
+ << systemId << nameId << format.flags;
+ sendBuffer(buffer);
+}
+
void PerfUnwind::lost(const PerfRecordLost &lost)
{
QByteArray buffer;
@@ -239,6 +256,8 @@ void PerfUnwind::lost(const PerfRecordLost &lost)
void PerfUnwind::features(const PerfFeatures &features)
{
+ tracing(features.tracingData());
+
const auto &eventDescs = features.eventDesc().eventDescs;
for (const auto &desc : eventDescs)
addAttributes(desc.attrs, desc.name, desc.ids);
@@ -273,6 +292,14 @@ void PerfUnwind::features(const PerfFeatures &features)
}
}
+void PerfUnwind::tracing(const PerfTracingData &tracingData)
+{
+ m_tracingData = tracingData;
+ const auto &formats = tracingData.eventFormats();
+ for (auto it = formats.constBegin(), end = formats.constEnd(); it != end; ++it)
+ sendEventFormat(it.key(), it.value());
+}
+
Dwfl_Module *PerfUnwind::reportElf(quint64 ip, qint32 pid)
{
auto symbols = symbolTable(pid);
@@ -411,6 +438,71 @@ void PerfUnwind::sample(const PerfRecordSample &sample)
bufferEvent(sample, &m_sampleBuffer, &m_stats.numSamplesInRound);
}
+template<typename Number>
+Number readFromArray(const QByteArray &data, quint32 offset, bool byteSwap)
+{
+ const Number number = *reinterpret_cast<const Number *>(data.data() + offset);
+ return byteSwap ? qbswap(number) : number;
+}
+
+QVariant readTraceItem(const QByteArray &data, quint32 offset, quint32 size, bool isSigned,
+ bool byteSwap)
+{
+ if (isSigned) {
+ switch (size) {
+ case 1: return readFromArray<qint8>(data, offset, byteSwap);
+ case 2: return readFromArray<qint16>(data, offset, byteSwap);
+ case 4: return readFromArray<qint32>(data, offset, byteSwap);
+ case 8: return readFromArray<qint64>(data, offset, byteSwap);
+ default: return QVariant::Invalid;
+ }
+ } else {
+ switch (size) {
+ case 1: return readFromArray<quint8>(data, offset, byteSwap);
+ case 2: return readFromArray<quint16>(data, offset, byteSwap);
+ case 4: return readFromArray<quint32>(data, offset, byteSwap);
+ case 8: return readFromArray<quint64>(data, offset, byteSwap);
+ default: return QVariant::Invalid;
+ }
+ }
+}
+
+QVariant PerfUnwind::readTraceData(const QByteArray &data, const FormatField &field, bool byteSwap)
+{
+ // TODO: validate that it actually works like this.
+ if (field.offset > std::numeric_limits<int>::max()
+ || field.size > std::numeric_limits<int>::max()
+ || field.offset + field.size > std::numeric_limits<int>::max()
+ || static_cast<int>(field.offset + field.size) > data.length()) {
+ return QVariant::Invalid;
+ }
+
+ if (field.flags & FIELD_IS_ARRAY) {
+ if (field.flags & FIELD_IS_DYNAMIC) {
+ const quint32 dynamicOffsetAndSize = readTraceItem(data, field.offset, field.size,
+ false, byteSwap).toUInt();
+ FormatField newField = field;
+ newField.offset = dynamicOffsetAndSize & 0xffff;
+ newField.size = dynamicOffsetAndSize >> 16;
+ newField.flags = field.flags & (~FIELD_IS_DYNAMIC);
+ return readTraceData(data, newField, byteSwap);
+ }
+ if (field.flags & FIELD_IS_STRING) {
+ return data.mid(static_cast<int>(field.offset), static_cast<int>(field.size));
+ } else {
+ QList<QVariant> result;
+ for (quint32 i = 0; i < field.size; i += field.elementsize) {
+ result.append(readTraceItem(data, field.offset + i, field.elementsize,
+ field.flags & FIELD_IS_SIGNED, byteSwap));
+ }
+ return result;
+ }
+ } else {
+ return readTraceItem(data, field.offset, field.size, field.flags & FIELD_IS_SIGNED,
+ byteSwap);
+ }
+}
+
void PerfUnwind::analyze(const PerfRecordSample &sample)
{
if (m_stats.enabled) // don't do any time intensive work in stats mode
@@ -465,12 +557,36 @@ void PerfUnwind::analyze(const PerfRecordSample &sample)
= static_cast<quint8>(qMin(static_cast<int>(std::numeric_limits<quint8>::max()),
numGuessed));
}
+
+ EventType type = Sample;
+ qint32 eventFormatId = -1;
+ const qint32 attributesId = m_attributeIds.value(sample.id(), -1);
+ if (attributesId != -1) {
+ const auto &attribute = m_attributes.at(attributesId);
+ if (attribute.type() == PerfEventAttributes::TYPE_TRACEPOINT) {
+ type = TracePointSample;
+ eventFormatId = attribute.config();
+ }
+ }
+
QByteArray buffer;
- QDataStream(&buffer, QIODevice::WriteOnly)
- << static_cast<quint8>(Sample) << sample.pid()
- << sample.tid() << sample.time() << m_currentUnwind.frames
- << numGuessedFrames << m_attributeIds.value(sample.id(), -1)
- << sample.period() << sample.weight();
+ QDataStream stream(&buffer, QIODevice::WriteOnly);
+ stream << static_cast<quint8>(type) << sample.pid()
+ << sample.tid() << sample.time() << m_currentUnwind.frames
+ << numGuessedFrames << attributesId
+ << sample.period() << sample.weight();
+
+ if (type == TracePointSample) {
+ QHash<qint32, QVariant> traceData;
+ const QByteArray &data = sample.rawData();
+ const EventFormat &format = m_tracingData.eventFormat(eventFormatId);
+ for (const FormatField &field : format.fields) {
+ traceData[lookupString(field.name)]
+ = readTraceData(data, field, m_byteOrder != QSysInfo::ByteOrder);
+ }
+ stream << traceData;
+ }
+
sendBuffer(buffer);
}
@@ -545,6 +661,11 @@ qint32 PerfUnwind::resolveString(const QByteArray& string)
return stringIt.value();
}
+qint32 PerfUnwind::lookupString(const QByteArray &string)
+{
+ return m_strings.value(string, -1);
+}
+
int PerfUnwind::lookupLocation(const PerfUnwind::Location &location) const
{
return m_locations.value(location, -1);
diff --git a/app/perfunwind.h b/app/perfunwind.h
index b4959f4..a43bbcd 100644
--- a/app/perfunwind.h
+++ b/app/perfunwind.h
@@ -24,6 +24,7 @@
#include "perfdata.h"
#include "perfregisterinfo.h"
#include "perfkallsyms.h"
+#include "perftracingdata.h"
#include <libdwfl.h>
@@ -56,6 +57,8 @@ public:
Error,
Sample,
Progress,
+ TracePointFormat,
+ TracePointSample,
InvalidType
};
@@ -165,11 +168,15 @@ public:
m_architecture = architecture;
}
+ void setByteOrder(QSysInfo::Endian byteOrder) { m_byteOrder = byteOrder; }
+ QSysInfo::Endian byteOrder() const { return m_byteOrder; }
+
void registerElf(const PerfRecordMmap &mmap);
void comm(const PerfRecordComm &comm);
void attr(const PerfRecordAttr &attr);
void lost(const PerfRecordLost &lost);
void features(const PerfFeatures &features);
+ void tracing(const PerfTracingData &tracingData);
void finishedRound();
Dwfl_Module *reportElf(quint64 ip, qint32 pid);
@@ -182,6 +189,7 @@ public:
Dwfl *dwfl(qint32 pid);
qint32 resolveString(const QByteArray &string);
+ qint32 lookupString(const QByteArray &string);
void addAttributes(const PerfEventAttributes &attributes, const QByteArray &name,
const QList<quint64> &ids);
@@ -260,6 +268,7 @@ private:
QList<PerfRecordMmap> m_mmapBuffer;
QHash<qint32, PerfSymbolTable *> m_symbolTables;
PerfKallsyms m_kallsyms;
+ PerfTracingData m_tracingData;
QHash<QByteArray, qint32> m_strings;
QHash<Location, qint32> m_locations;
@@ -271,6 +280,7 @@ private:
uint m_maxEventBufferSize;
uint m_eventBufferSize;
quint64 m_lastFlushMaxTime;
+ QSysInfo::Endian m_byteOrder = QSysInfo::LittleEndian;
Stats m_stats;
@@ -282,10 +292,13 @@ private:
void sendLocation(qint32 id, const Location &location);
void sendSymbol(qint32 id, const Symbol &symbol);
void sendAttributes(qint32 id, const PerfEventAttributes &attributes, const QByteArray &name);
+ void sendEventFormat(qint32 id, const EventFormat &format);
template<typename Event>
void bufferEvent(const Event &event, QList<Event> *buffer, uint *eventCounter);
void flushEventBuffer(uint desiredBufferSize);
+
+ QVariant readTraceData(const QByteArray &data, const FormatField &field, bool byteSwap);
};
uint qHash(const PerfUnwind::Location &location, uint seed = 0);
diff --git a/tests/auto/perfdata/perfdata.pro b/tests/auto/perfdata/perfdata.pro
index f16f379..06cd252 100644
--- a/tests/auto/perfdata/perfdata.pro
+++ b/tests/auto/perfdata/perfdata.pro
@@ -1,4 +1,5 @@
include(../../../elfutils.pri)
+include(../shared/shared.pri)
QT += testlib
QT -= gui
@@ -20,6 +21,7 @@ SOURCES += \
../../../app/perfkallsyms.cpp \
../../../app/perfregisterinfo.cpp \
../../../app/perfsymboltable.cpp \
+ ../../../app/perftracingdata.cpp \
../../../app/perfunwind.cpp
HEADERS += \
@@ -32,6 +34,7 @@ HEADERS += \
../../../app/perfkallsyms.h \
../../../app/perfregisterinfo.h \
../../../app/perfsymboltable.h \
+ ../../../app/perftracingdata.h \
../../../app/perfunwind.h
RESOURCES += \
diff --git a/tests/auto/perfdata/perfdata.qbs b/tests/auto/perfdata/perfdata.qbs
index 8a9eba6..a8f71f8 100644
--- a/tests/auto/perfdata/perfdata.qbs
+++ b/tests/auto/perfdata/perfdata.qbs
@@ -3,12 +3,14 @@ import qbs
QtcAutotest {
name: "PerfData Autotest"
- cpp.includePaths: ["/usr/include/elfutils", "../../../app"]
+ cpp.includePaths: ["/usr/include/elfutils", "../../../app", "../shared"]
cpp.dynamicLibraries: ["dw", "elf"]
files: [
"perfdata.qrc",
"tst_perfdata.cpp",
+ "../shared/perfparsertestclient.cpp",
+ "../shared/perfparsertestclient.h",
"../../../app/perfattributes.cpp",
"../../../app/perfattributes.h",
"../../../app/perfdata.cpp",
@@ -27,7 +29,9 @@ QtcAutotest {
"../../../app/perfregisterinfo.h",
"../../../app/perfsymboltable.cpp",
"../../../app/perfsymboltable.h",
+ "../../../app/perftracingdata.cpp",
+ "../../../app/perftracingdata.h",
"../../../app/perfunwind.cpp",
- "../../../app/perfunwind.h"
+ "../../../app/perfunwind.h",
]
}
diff --git a/tests/auto/perfdata/perfdata.qrc b/tests/auto/perfdata/perfdata.qrc
index 8df083c..48cefab 100644
--- a/tests/auto/perfdata/perfdata.qrc
+++ b/tests/auto/perfdata/perfdata.qrc
@@ -1,6 +1,7 @@
<RCC>
<qresource prefix="/">
<file>probe.data.stream</file>
+ <file>probe.data.file</file>
<file>contentsize.data</file>
</qresource>
</RCC>
diff --git a/tests/auto/perfdata/probe.data.file b/tests/auto/perfdata/probe.data.file
new file mode 100644
index 0000000..1b1927d
--- /dev/null
+++ b/tests/auto/perfdata/probe.data.file
Binary files differ
diff --git a/tests/auto/perfdata/tst_perfdata.cpp b/tests/auto/perfdata/tst_perfdata.cpp
index 22dbcb2..bee0910 100644
--- a/tests/auto/perfdata/tst_perfdata.cpp
+++ b/tests/auto/perfdata/tst_perfdata.cpp
@@ -22,53 +22,92 @@
#include <QBuffer>
#include <QSignalSpy>
#include <QDebug>
+#include <QtEndian>
#include "perfdata.h"
#include "perfunwind.h"
+#include "perfparsertestclient.h"
class TestPerfData : public QObject
{
Q_OBJECT
private slots:
- void testTraceDataHeaderEvent();
+ void testTracingData_data();
+ void testTracingData();
void testContentSize();
};
-void TestPerfData::testTraceDataHeaderEvent()
+static void setupUnwind(PerfUnwind *unwind, PerfHeader *header, QIODevice *input,
+ PerfAttributes *attributes)
{
- QBuffer output;
- QFile input(":/probe.data.stream");
- QVERIFY(input.open(QIODevice::ReadOnly));
- QVERIFY(output.open(QIODevice::WriteOnly));
+ if (!header->isPipe()) {
+ const qint64 filePos = input->pos();
+
+ attributes->read(input, header);
+
+ PerfFeatures features;
+ features.read(input, header);
+
+ PerfTracingData tracingData = features.tracingData();
+ QVERIFY(tracingData.size() > 0);
+ QCOMPARE(tracingData.version(), QByteArray("0.5"));
+
+ unwind->features(features);
+ const auto& attrs = attributes->attributes();
+ for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it)
+ unwind->attr(PerfRecordAttr(it.value(), {it.key()}));
+ const QByteArray &featureArch = features.architecture();
+ for (uint i = 0; i < PerfRegisterInfo::ARCH_INVALID; ++i) {
+ if (featureArch.startsWith(PerfRegisterInfo::s_archNames[i])) {
+ unwind->setArchitecture(static_cast<PerfRegisterInfo::Architecture>(i));
+ break;
+ }
+ }
+
+ input->seek(filePos);
+ }
+}
- PerfUnwind unwind(&output, QDir::rootPath(), PerfUnwind::defaultDebugInfoPath(), QString(),
- QString(), true);
- PerfHeader header(&input);
+static void process(PerfUnwind *unwind, QIODevice *input)
+{
+ PerfHeader header(input);
PerfAttributes attributes;
- PerfData data(&input, &unwind, &header, &attributes);
+ PerfData data(input, unwind, &header, &attributes);
QSignalSpy spy(&data, SIGNAL(finished()));
- connect(&header, &PerfHeader::finished, &data, &PerfData::read);
+ QObject::connect(&header, &PerfHeader::finished, &data, [&](){
+ setupUnwind(unwind, &header, input, &attributes);
+ data.read();
+ });
+
+ QObject::connect(&data, &PerfData::error, []() {
+ QFAIL("PerfData reported an error");
+ });
header.read();
QCOMPARE(spy.count(), 1);
- unwind.finalize();
-
- const PerfUnwind::Stats stats = unwind.stats();
- QCOMPARE(stats.numSamples, 1u);
- QCOMPARE(stats.numMmaps, 120u);
- QCOMPARE(stats.numRounds, 2u);
- QCOMPARE(stats.numBufferFlushes, 2u);
- QCOMPARE(stats.numTimeViolatingSamples, 0u);
- QCOMPARE(stats.numTimeViolatingMmaps, 0u);
- QCOMPARE(stats.maxBufferSize, 15488u);
- QCOMPARE(stats.maxTime, 13780586522722ull);
+ unwind->finalize();
}
-void TestPerfData::testContentSize()
+void TestPerfData::testTracingData_data()
{
- QString file(":/contentsize.data");
+ QTest::addColumn<QString>("file");
+ QTest::addColumn<uint>("flushes");
+ QTest::addColumn<quint64>("maxTime");
+ QTest::addColumn<bool>("stats");
+ QTest::addRow("stream stats") << ":/probe.data.stream" << 2u << 13780586522722ull << true;
+ QTest::addRow("file stats") << ":/probe.data.file" << 3u << 13732862219876ull << true;
+ QTest::addRow("stream data") << ":/probe.data.stream" << 2u << 13780586522722ull << false;
+ QTest::addRow("file data") << ":/probe.data.file" << 3u << 13732862219876ull << false;
+}
+
+void TestPerfData::testTracingData()
+{
+ QFETCH(QString, file);
+ QFETCH(uint, flushes);
+ QFETCH(quint64, maxTime);
+ QFETCH(bool, stats);
QBuffer output;
QFile input(file);
@@ -77,22 +116,86 @@ void TestPerfData::testContentSize()
QVERIFY(output.open(QIODevice::WriteOnly));
// Don't try to load any system files. They are not the same as the ones we use to report.
- PerfUnwind unwind(&output, ":/", QString(), QString(), QString(), true);
- PerfHeader header(&input);
- PerfAttributes attributes;
- PerfData data(&input, &unwind, &header, &attributes);
+ PerfUnwind unwind(&output, ":/", QString(), QString(), QString(), stats);
+ if (!stats) {
+ QTest::ignoreMessage(QtWarningMsg,
+ "PerfUnwind::ErrorCode(MissingElfFile): Could not find ELF file for "
+ "/home/ulf/dev/untitled1-Qt_5_9_1_gcc_64-Profile/untitled1. "
+ "This can break stack unwinding and lead to missing symbols.");
+ QTest::ignoreMessage(QtWarningMsg,
+ "PerfUnwind::ErrorCode(MissingElfFile): Could not find ELF file for "
+ "/lib/x86_64-linux-gnu/ld-2.24.so. "
+ "This can break stack unwinding and lead to missing symbols.");
+ QTest::ignoreMessage(QtWarningMsg,
+ "PerfUnwind::ErrorCode(MissingElfFile): Could not find ELF file for "
+ "/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.22. "
+ "This can break stack unwinding and lead to missing symbols.");
+ QTest::ignoreMessage(QtWarningMsg,
+ "PerfUnwind::ErrorCode(MissingElfFile): Could not find ELF file for "
+ "/lib/x86_64-linux-gnu/libm-2.24.so. "
+ "This can break stack unwinding and lead to missing symbols.");
+ QTest::ignoreMessage(QtWarningMsg,
+ "PerfUnwind::ErrorCode(MissingElfFile): Could not find ELF file for "
+ "/lib/x86_64-linux-gnu/libgcc_s.so.1. "
+ "This can break stack unwinding and lead to missing symbols.");
+ QTest::ignoreMessage(QtWarningMsg,
+ "PerfUnwind::ErrorCode(MissingElfFile): Could not find ELF file for "
+ "/lib/x86_64-linux-gnu/libc-2.24.so. "
+ "This can break stack unwinding and lead to missing symbols.");
+ }
+ process(&unwind, &input);
+
+ if (stats) {
+ const PerfUnwind::Stats stats = unwind.stats();
+ QCOMPARE(stats.numSamples, 1u);
+ QCOMPARE(stats.numMmaps, 120u);
+ QCOMPARE(stats.numRounds, 2u);
+ QCOMPARE(stats.numBufferFlushes, flushes);
+ QCOMPARE(stats.numTimeViolatingSamples, 0u);
+ QCOMPARE(stats.numTimeViolatingMmaps, 0u);
+ QCOMPARE(stats.maxBufferSize, 15488u);
+ QCOMPARE(stats.maxTime, maxTime);
+ return;
+ }
+
+ output.close();
+ output.open(QIODevice::ReadOnly);
+
+ PerfParserTestClient client;
+ client.extractTrace(&output);
+
+ const QVector<PerfParserTestClient::SampleEvent> samples = client.samples();
+ QVERIFY(samples.length() > 0);
+ for (const PerfParserTestClient::SampleEvent &sample : samples) {
+ const PerfParserTestClient::AttributeEvent attribute
+ = client.attribute(sample.attributeId);
+ QCOMPARE(attribute.type, 2u);
+ const PerfParserTestClient::TracePointFormatEvent format
+ = client.tracePointFormat(attribute.config);
+ QCOMPARE(client.string(format.system), QByteArray("probe_untitled1"));
+ QCOMPARE(client.string(format.name), QByteArray("main"));
+ QCOMPARE(format.flags, 0u);
+
+ QCOMPARE(sample.tracePointData.size(), 1);
+ auto it = sample.tracePointData.constBegin();
+ QCOMPARE(client.string(it.key()), QByteArray("__probe_ip"));
+ QCOMPARE(it.value().type(), QVariant::ULongLong);
+ }
+}
- QSignalSpy spy(&data, SIGNAL(finished()));
- connect(&header, &PerfHeader::finished, &data, &PerfData::read);
+void TestPerfData::testContentSize()
+{
+ QString file(":/contentsize.data");
- header.read();
- QCOMPARE(spy.count(), 1);
+ QBuffer output;
+ QFile input(file);
- unwind.finalize();
+ QVERIFY(input.open(QIODevice::ReadOnly));
+ QVERIFY(output.open(QIODevice::WriteOnly));
- QObject::connect(&data, &PerfData::error, []() {
- QFAIL("PerfData reported an error");
- });
+ // Don't try to load any system files. They are not the same as the ones we use to report.
+ PerfUnwind unwind(&output, ":/", QString(), QString(), QString(), true);
+ process(&unwind, &input);
QCOMPARE(unwind.stats().numSamples, 69u);
}
diff --git a/tests/auto/shared/perfparsertestclient.cpp b/tests/auto/shared/perfparsertestclient.cpp
index 2f323b3..a3766a5 100644
--- a/tests/auto/shared/perfparsertestclient.cpp
+++ b/tests/auto/shared/perfparsertestclient.cpp
@@ -134,7 +134,8 @@ void PerfParserTestClient::extractTrace(QIODevice *device)
// Ignore this: We cannot find the elfs of course.
break;
}
- case Sample: {
+ case Sample:
+ case TracePointSample: {
SampleEvent sample;
stream >> sample.pid >> sample.tid >> sample.time >> sample.frames
>> sample.numGuessedFrames >> sample.attributeId >> sample.period
@@ -142,6 +143,16 @@ void PerfParserTestClient::extractTrace(QIODevice *device)
for (qint32 locationId : qAsConst(sample.frames))
checkLocation(locationId);
checkAttribute(sample.attributeId);
+
+ if (eventType == TracePointSample) {
+ stream >> sample.tracePointData;
+ for (auto it = sample.tracePointData.constBegin(),
+ end = sample.tracePointData.constEnd();
+ it != end; ++it) {
+ checkString(it.key());
+ }
+ }
+
m_samples.append(sample);
break;
}
@@ -151,6 +162,17 @@ void PerfParserTestClient::extractTrace(QIODevice *device)
QVERIFY(progress > oldProgress);
break;
}
+ case TracePointFormat: {
+ qint32 id;
+ TracePointFormatEvent tracePointFormat;
+ stream >> id >> tracePointFormat.system >> tracePointFormat.name
+ >> tracePointFormat.flags;
+ checkString(tracePointFormat.system);
+ checkString(tracePointFormat.name);
+ QVERIFY(!m_tracePointFormats.contains(id));
+ m_tracePointFormats.insert(id, tracePointFormat);
+ break;
+ }
default:
stream.skipRawData(size);
qDebug() << size << device->bytesAvailable() << EventType(eventType);
diff --git a/tests/auto/shared/perfparsertestclient.h b/tests/auto/shared/perfparsertestclient.h
index f1c159f..cdc5808 100644
--- a/tests/auto/shared/perfparsertestclient.h
+++ b/tests/auto/shared/perfparsertestclient.h
@@ -22,6 +22,8 @@
#include <QObject>
#include <QIODevice>
#include <QVector>
+#include <QHash>
+#include <QVariant>
class PerfParserTestClient : public QObject
{
@@ -64,12 +66,19 @@ public:
struct SampleEvent : public ThreadEvent {
QVector<qint32> frames;
+ QHash<qint32, QVariant> tracePointData;
quint64 period;
quint64 weight;
qint32 attributeId;
quint8 numGuessedFrames;
};
+ struct TracePointFormatEvent {
+ qint32 system;
+ qint32 name;
+ quint32 flags;
+ };
+
// Repeated here, as we want to check against accidental changes in enum values.
enum EventType {
Sample43,
@@ -85,6 +94,8 @@ public:
Error,
Sample,
Progress,
+ TracePointFormat,
+ TracePointSample,
InvalidType
};
Q_ENUM(EventType)
@@ -97,6 +108,8 @@ public:
AttributeEvent attribute(qint32 id) const { return m_attributes.value(id); }
QVector<SampleEvent> samples() const { return m_samples; }
+ TracePointFormatEvent tracePointFormat(qint32 id) { return m_tracePointFormats.value(id); }
+
private:
QVector<QByteArray> m_strings;
QVector<AttributeEvent> m_attributes;
@@ -105,4 +118,5 @@ private:
QVector<LocationEvent> m_locations;
QVector<SymbolEvent> m_symbols;
QVector<SampleEvent> m_samples;
+ QHash<qint32, TracePointFormatEvent> m_tracePointFormats;
};