diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2017-09-15 14:20:11 +0200 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2017-11-30 08:49:27 +0000 |
commit | 068a615d253b5c40b686cdec544f001e5baa7be9 (patch) | |
tree | d7943fb0848d05ab8d334222072f78dbee7a5f26 | |
parent | 5f842e0d756bf4d2dbe7b87e7d2e9bc15fadf94e (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.pro | 6 | ||||
-rw-r--r-- | app/app.qbs | 4 | ||||
-rw-r--r-- | app/main.cpp | 1 | ||||
-rw-r--r-- | app/perfdata.cpp | 10 | ||||
-rw-r--r-- | app/perfdata.h | 1 | ||||
-rw-r--r-- | app/perffeatures.cpp | 10 | ||||
-rw-r--r-- | app/perffeatures.h | 3 | ||||
-rw-r--r-- | app/perftracingdata.cpp | 364 | ||||
-rw-r--r-- | app/perftracingdata.h | 108 | ||||
-rw-r--r-- | app/perfunwind.cpp | 131 | ||||
-rw-r--r-- | app/perfunwind.h | 13 | ||||
-rw-r--r-- | tests/auto/perfdata/perfdata.pro | 3 | ||||
-rw-r--r-- | tests/auto/perfdata/perfdata.qbs | 8 | ||||
-rw-r--r-- | tests/auto/perfdata/perfdata.qrc | 1 | ||||
-rw-r--r-- | tests/auto/perfdata/probe.data.file | bin | 0 -> 24628 bytes | |||
-rw-r--r-- | tests/auto/perfdata/tst_perfdata.cpp | 175 | ||||
-rw-r--r-- | tests/auto/shared/perfparsertestclient.cpp | 24 | ||||
-rw-r--r-- | tests/auto/shared/perfparsertestclient.h | 14 |
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 §ion, 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 Binary files differnew file mode 100644 index 0000000..1b1927d --- /dev/null +++ b/tests/auto/perfdata/probe.data.file 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; }; |