Skip to content

Commit

Permalink
feat: add tracepoint formatter
Browse files Browse the repository at this point in the history
  • Loading branch information
lievenhey committed Nov 18, 2024
1 parent f1454ef commit 6b7e717
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 0 deletions.
83 changes: 83 additions & 0 deletions src/models/tracepointformat.cpp
Original file line number Diff line number Diff line change
@@ -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));
}
35 changes: 35 additions & 0 deletions src/models/tracepointformat.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
SPDX-FileCopyrightText: Lieven Hey <[email protected]>
SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company, [email protected]
SPDX-License-Identifier: GPL-2.0-or-later
*/

#pragma once

#include <QString>

#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);
109 changes: 109 additions & 0 deletions tests/modeltests/tst_tracepointformat.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
SPDX-FileCopyrightText: Lieven Hey <[email protected]>
SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company, [email protected]
SPDX-License-Identifier: GPL-2.0-or-later
*/

#include <QTest>

#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
#include <QHashSeed>
#endif // QT_VERSION < QT_VERSION_CHECK(6, 2, 0)

#include <tracepointformat.h>

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<QString>("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"

0 comments on commit 6b7e717

Please sign in to comment.