From 0bb88e063abe3812347ccc10ab5cd6152087e0ef Mon Sep 17 00:00:00 2001 From: Lieven Hey Date: Mon, 18 Nov 2024 13:35:56 +0100 Subject: [PATCH] feat: add tracepoint formatter --- src/models/tracepointformat.cpp | 83 ++++++++++++++++ src/models/tracepointformat.h | 35 +++++++ tests/modeltests/tst_tracepointformat.cpp | 109 ++++++++++++++++++++++ 3 files changed, 227 insertions(+) create mode 100644 src/models/tracepointformat.cpp create mode 100644 src/models/tracepointformat.h create mode 100644 tests/modeltests/tst_tracepointformat.cpp diff --git a/src/models/tracepointformat.cpp b/src/models/tracepointformat.cpp new file mode 100644 index 000000000..1c009a888 --- /dev/null +++ b/src/models/tracepointformat.cpp @@ -0,0 +1,83 @@ +#include "tracepointformat.h" + +TracePointFormatter::TracePointFormatter(const QString& format) +{ + // ignore empty format strings + if (format.isEmpty()) { + return; + } + + // the format string are the arguments to a printf call, therefor the format will always be in quotes and then + // follows a list of arguments + auto endOfFormatString = format.indexOf(QLatin1Char('\"'), 1); + + auto lastRec = endOfFormatString; + auto recIndex = lastRec; + + // no quote in format string -> format string is not a string + if (endOfFormatString == -1) { + return; + } + + // check for valid format string + // some format strings contains this tries to filter these out + for (int i = endOfFormatString; i < format.size(); i++) { + auto c = format[i]; + auto nextC = i < format.size() - 1 ? format[i + 1] : QChar {}; + + if ((c == QLatin1Char('>') && nextC == QLatin1Char('>')) + || (c == QLatin1Char('<') && nextC == QLatin1Char('<'))) { + return; + } + } + + // set format string after validating we can print it + m_formatString = format.mid(1, endOfFormatString - 1); + + while ((recIndex = format.indexOf(QStringLiteral("REC->"), lastRec)) != -1) { + auto endOfName = format.indexOf(QLatin1Char(')'), recIndex); + + auto start = recIndex + 5; // 5 because we want the field after REC-> + m_args.push_back(format.mid(start, endOfName - start)); + lastRec = recIndex + 1; + } +} + +QString TracePointFormatter::format(const Data::TracePointData& data) const +{ + QString result; + + // if m_formatString is empty, we couldn't parse it, just dump out the information + if (m_formatString.isEmpty()) { + for (auto it = data.cbegin(), end = data.cend(); it != end; it++) { + result += QLatin1String("%1: %2\n").arg(it.key(), QString::number(it->toULongLong())); + } + return result.trimmed(); + } + + const auto percent = QLatin1Char('%'); + auto currentPercent = 0; + + for (int i = 0; i < m_args.size();) { + auto nextPercent = m_formatString.indexOf(percent, currentPercent + 1); + + auto substring = m_formatString.mid(currentPercent, nextPercent - currentPercent); + if (substring.contains(percent)) { + result += QString::asprintf(qPrintable(substring), data.value(m_args[i]).toULongLong()); + i++; + + currentPercent = nextPercent; + } else { + result += substring; + } + currentPercent = nextPercent; + } + + return result; +} + +QString formatTracepoint(const Data::TracePointFormat& format, const Data::TracePointData& data) +{ + TracePointFormatter formatter(format.format); + return QStringLiteral("%1:%2:\n%3").arg(format.systemId, format.nameId, formatter.format(data)); +} diff --git a/src/models/tracepointformat.h b/src/models/tracepointformat.h new file mode 100644 index 000000000..fb7c71a6c --- /dev/null +++ b/src/models/tracepointformat.h @@ -0,0 +1,35 @@ +/* + SPDX-FileCopyrightText: Lieven Hey + SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include + +#include "data.h" + +class TracePointFormatter +{ +public: + TracePointFormatter(const QString& format); + + QString format(const Data::TracePointData& data) const; + + QString formatString() const + { + return m_formatString; + } + QStringList args() const + { + return m_args; + } + +private: + QString m_formatString; + QStringList m_args; +}; + +QString formatTracepoint(const Data::TracePointFormat& format, const Data::TracePointData& data); diff --git a/tests/modeltests/tst_tracepointformat.cpp b/tests/modeltests/tst_tracepointformat.cpp new file mode 100644 index 000000000..3a8d3c0a3 --- /dev/null +++ b/tests/modeltests/tst_tracepointformat.cpp @@ -0,0 +1,109 @@ +/* + SPDX-FileCopyrightText: Lieven Hey + SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include + +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) +#include +#endif // QT_VERSION < QT_VERSION_CHECK(6, 2, 0) + +#include + +class TestTracepointFormat : public QObject +{ + Q_OBJECT +private slots: + void initTestCase() + { +#if QT_VERSION < QT_VERSION_CHECK(6, 2, 0) + qSetGlobalQHashSeed(0); +#else + QHashSeed::setDeterministicGlobalSeed(); +#endif // QT_VERSION < QT_VERSION_CHECK(6, 2, 0) + } + + void testFormatString() + { + // taken from /sys/kernel/tracing/events/syscalls/sys_enter_openat/format + auto format = QStringLiteral( + "\"dfd: 0x%08lx, filename: 0x%08lx, flags: 0x%08lx, mode: 0x%08lx\", ((unsigned long)(REC->dfd)), " + "((unsigned long)(REC->filename)), ((unsigned long)(REC->flags)), ((unsigned long)(REC->mode))"); + + TracePointFormatter formatter(format); + + QCOMPARE(formatter.formatString(), + QStringLiteral("dfd: 0x%08lx, filename: 0x%08lx, flags: 0x%08lx, mode: 0x%08lx")); + QCOMPARE(formatter.args(), + (QStringList {{QStringLiteral("dfd")}, + {QStringLiteral("filename")}, + {QStringLiteral("flags")}, + {QStringLiteral("mode")}})); + } + + void testSyscallEnterOpenat() + { + Data::TracePointData tracepointData = {{QStringLiteral("filename"), QVariant(140732347873408ull)}, + {QStringLiteral("dfd"), QVariant(4294967196ull)}, + {QStringLiteral("__syscall_nr"), QVariant(257)}, + {QStringLiteral("flags"), QVariant(0ull)}, + {QStringLiteral("mode"), QVariant(0)}}; + + const Data::TracePointFormat format = { + QStringLiteral("syscalls"), QStringLiteral("syscall_enter_openat"), 0, + QStringLiteral( + "\"dfd: 0x%08lx, filename: 0x%08lx, flags: 0x%08lx, mode: 0x%08lx\", ((unsigned long)(REC->dfd)), " + "((unsigned long)(REC->filename)), ((unsigned long)(REC->flags)), ((unsigned long)(REC->mode))")}; + + TracePointFormatter formatter(format.format); + } + + void testInvalidFormatString_data() + { + QTest::addColumn("format"); + QTest::addRow("Too complex format") << QStringLiteral( + "\"%d,%d %s (%s) %llu + %u %s,%u,%u [%d]\", ((unsigned int) ((REC->dev) >> 20)), ((unsigned int) " + "((REC->dev) & ((1U << 20) - 1))), REC->rwbs, __get_str(cmd), (unsigned long long)REC->sector, " + "REC->nr_sector, __print_symbolic((((REC->ioprio) >> 13) & (8 - 1)), { IOPRIO_CLASS_NONE, \"none\" }, " + "{IOPRIO_CLASS_RT, \"rt\"}, {IOPRIO_CLASS_BE, \"be\"}, {IOPRIO_CLASS_IDLE, \"idle\"}, " + "{IOPRIO_CLASS_INVALID, \"invalid\"}), (((REC->ioprio) >> 3) & ((1 << 10) - 1)), ((REC->ioprio) & ((1 << " + "3) - 1)), REC->error "); + + QTest::addRow("Invalid format string") << QStringLiteral("abc123%s"); + QTest::addRow("Emptry format string") << QString {}; + } + void testInvalidFormatString() + { + QFETCH(QString, format); + + Data::TracePointData data = {{QStringLiteral("ioprio"), QVariant(0)}, + {QStringLiteral("sector"), QVariant(18446744073709551615ull)}, + {QStringLiteral("nr_sector"), QVariant(0u)}, + {QStringLiteral("rwbs"), QVariant(QByteArray("N\x00\x00\x00\x00\x00\x00\x00"))}, + {QStringLiteral("dev"), QVariant(8388624u)}, + {QStringLiteral("cmd"), QVariant(65584u)}, + {QStringLiteral("error"), QVariant(-5)}}; + + TracePointFormatter formatter(format); + QVERIFY(formatter.formatString().isEmpty()); + + // if the format string cannot be decoded then for formatter will just concat the tracepoint data + // Qt5 and Qt6 use different hashing functions so we need two different outputs +#if QT_VERSION < QT_VERSION_CHECK(6, 2, 0) + auto output = QLatin1String("dev: 8388624\ncmd: 65584\nnr_sector: 0\nrwbs: 0\nioprio: 0\nerror: " + "18446744073709551611\nsector: 18446744073709551615"); +#else + auto output = QLatin1String("cmd: 65584\nioprio: 0\nnr_sector: 0\nrwbs: 0\nsector: 18446744073709551615\ndev: " + "8388624\nerror: 18446744073709551611"); +#endif // QT_VERSION < QT_VERSION_CHECK(6, 2, 0) + + QCOMPARE(formatter.format(data), output); + } +}; + +QTEST_GUILESS_MAIN(TestTracepointFormat) + +#include "tst_tracepointformat.moc"