Skip to content

Commit 6b7e717

Browse files
committed
feat: add tracepoint formatter
1 parent f1454ef commit 6b7e717

File tree

3 files changed

+227
-0
lines changed

3 files changed

+227
-0
lines changed

src/models/tracepointformat.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#include "tracepointformat.h"
2+
3+
TracePointFormatter::TracePointFormatter(const QString& format)
4+
{
5+
// ignore empty format strings
6+
if (format.isEmpty()) {
7+
return;
8+
}
9+
10+
// the format string are the arguments to a printf call, therefor the format will always be in quotes and then
11+
// follows a list of arguments
12+
auto endOfFormatString = format.indexOf(QLatin1Char('\"'), 1);
13+
14+
auto lastRec = endOfFormatString;
15+
auto recIndex = lastRec;
16+
17+
// no quote in format string -> format string is not a string
18+
if (endOfFormatString == -1) {
19+
return;
20+
}
21+
22+
// check for valid format string
23+
// some format strings contains this tries to filter these out
24+
for (int i = endOfFormatString; i < format.size(); i++) {
25+
auto c = format[i];
26+
auto nextC = i < format.size() - 1 ? format[i + 1] : QChar {};
27+
28+
if ((c == QLatin1Char('>') && nextC == QLatin1Char('>'))
29+
|| (c == QLatin1Char('<') && nextC == QLatin1Char('<'))) {
30+
return;
31+
}
32+
}
33+
34+
// set format string after validating we can print it
35+
m_formatString = format.mid(1, endOfFormatString - 1);
36+
37+
while ((recIndex = format.indexOf(QStringLiteral("REC->"), lastRec)) != -1) {
38+
auto endOfName = format.indexOf(QLatin1Char(')'), recIndex);
39+
40+
auto start = recIndex + 5; // 5 because we want the field after REC->
41+
m_args.push_back(format.mid(start, endOfName - start));
42+
lastRec = recIndex + 1;
43+
}
44+
}
45+
46+
QString TracePointFormatter::format(const Data::TracePointData& data) const
47+
{
48+
QString result;
49+
50+
// if m_formatString is empty, we couldn't parse it, just dump out the information
51+
if (m_formatString.isEmpty()) {
52+
for (auto it = data.cbegin(), end = data.cend(); it != end; it++) {
53+
result += QLatin1String("%1: %2\n").arg(it.key(), QString::number(it->toULongLong()));
54+
}
55+
return result.trimmed();
56+
}
57+
58+
const auto percent = QLatin1Char('%');
59+
auto currentPercent = 0;
60+
61+
for (int i = 0; i < m_args.size();) {
62+
auto nextPercent = m_formatString.indexOf(percent, currentPercent + 1);
63+
64+
auto substring = m_formatString.mid(currentPercent, nextPercent - currentPercent);
65+
if (substring.contains(percent)) {
66+
result += QString::asprintf(qPrintable(substring), data.value(m_args[i]).toULongLong());
67+
i++;
68+
69+
currentPercent = nextPercent;
70+
} else {
71+
result += substring;
72+
}
73+
currentPercent = nextPercent;
74+
}
75+
76+
return result;
77+
}
78+
79+
QString formatTracepoint(const Data::TracePointFormat& format, const Data::TracePointData& data)
80+
{
81+
TracePointFormatter formatter(format.format);
82+
return QStringLiteral("%1:%2:\n%3").arg(format.systemId, format.nameId, formatter.format(data));
83+
}

src/models/tracepointformat.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
SPDX-FileCopyrightText: Lieven Hey <[email protected]>
3+
SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company, [email protected]
4+
5+
SPDX-License-Identifier: GPL-2.0-or-later
6+
*/
7+
8+
#pragma once
9+
10+
#include <QString>
11+
12+
#include "data.h"
13+
14+
class TracePointFormatter
15+
{
16+
public:
17+
TracePointFormatter(const QString& format);
18+
19+
QString format(const Data::TracePointData& data) const;
20+
21+
QString formatString() const
22+
{
23+
return m_formatString;
24+
}
25+
QStringList args() const
26+
{
27+
return m_args;
28+
}
29+
30+
private:
31+
QString m_formatString;
32+
QStringList m_args;
33+
};
34+
35+
QString formatTracepoint(const Data::TracePointFormat& format, const Data::TracePointData& data);
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
SPDX-FileCopyrightText: Lieven Hey <[email protected]>
3+
SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company, [email protected]
4+
5+
SPDX-License-Identifier: GPL-2.0-or-later
6+
*/
7+
8+
#include <QTest>
9+
10+
#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
11+
#include <QHashSeed>
12+
#endif // QT_VERSION < QT_VERSION_CHECK(6, 2, 0)
13+
14+
#include <tracepointformat.h>
15+
16+
class TestTracepointFormat : public QObject
17+
{
18+
Q_OBJECT
19+
private slots:
20+
void initTestCase()
21+
{
22+
#if QT_VERSION < QT_VERSION_CHECK(6, 2, 0)
23+
qSetGlobalQHashSeed(0);
24+
#else
25+
QHashSeed::setDeterministicGlobalSeed();
26+
#endif // QT_VERSION < QT_VERSION_CHECK(6, 2, 0)
27+
}
28+
29+
void testFormatString()
30+
{
31+
// taken from /sys/kernel/tracing/events/syscalls/sys_enter_openat/format
32+
auto format = QStringLiteral(
33+
"\"dfd: 0x%08lx, filename: 0x%08lx, flags: 0x%08lx, mode: 0x%08lx\", ((unsigned long)(REC->dfd)), "
34+
"((unsigned long)(REC->filename)), ((unsigned long)(REC->flags)), ((unsigned long)(REC->mode))");
35+
36+
TracePointFormatter formatter(format);
37+
38+
QCOMPARE(formatter.formatString(),
39+
QStringLiteral("dfd: 0x%08lx, filename: 0x%08lx, flags: 0x%08lx, mode: 0x%08lx"));
40+
QCOMPARE(formatter.args(),
41+
(QStringList {{QStringLiteral("dfd")},
42+
{QStringLiteral("filename")},
43+
{QStringLiteral("flags")},
44+
{QStringLiteral("mode")}}));
45+
}
46+
47+
void testSyscallEnterOpenat()
48+
{
49+
Data::TracePointData tracepointData = {{QStringLiteral("filename"), QVariant(140732347873408ull)},
50+
{QStringLiteral("dfd"), QVariant(4294967196ull)},
51+
{QStringLiteral("__syscall_nr"), QVariant(257)},
52+
{QStringLiteral("flags"), QVariant(0ull)},
53+
{QStringLiteral("mode"), QVariant(0)}};
54+
55+
const Data::TracePointFormat format = {
56+
QStringLiteral("syscalls"), QStringLiteral("syscall_enter_openat"), 0,
57+
QStringLiteral(
58+
"\"dfd: 0x%08lx, filename: 0x%08lx, flags: 0x%08lx, mode: 0x%08lx\", ((unsigned long)(REC->dfd)), "
59+
"((unsigned long)(REC->filename)), ((unsigned long)(REC->flags)), ((unsigned long)(REC->mode))")};
60+
61+
TracePointFormatter formatter(format.format);
62+
}
63+
64+
void testInvalidFormatString_data()
65+
{
66+
QTest::addColumn<QString>("format");
67+
QTest::addRow("Too complex format") << QStringLiteral(
68+
"\"%d,%d %s (%s) %llu + %u %s,%u,%u [%d]\", ((unsigned int) ((REC->dev) >> 20)), ((unsigned int) "
69+
"((REC->dev) & ((1U << 20) - 1))), REC->rwbs, __get_str(cmd), (unsigned long long)REC->sector, "
70+
"REC->nr_sector, __print_symbolic((((REC->ioprio) >> 13) & (8 - 1)), { IOPRIO_CLASS_NONE, \"none\" }, "
71+
"{IOPRIO_CLASS_RT, \"rt\"}, {IOPRIO_CLASS_BE, \"be\"}, {IOPRIO_CLASS_IDLE, \"idle\"}, "
72+
"{IOPRIO_CLASS_INVALID, \"invalid\"}), (((REC->ioprio) >> 3) & ((1 << 10) - 1)), ((REC->ioprio) & ((1 << "
73+
"3) - 1)), REC->error ");
74+
75+
QTest::addRow("Invalid format string") << QStringLiteral("abc123%s");
76+
QTest::addRow("Emptry format string") << QString {};
77+
}
78+
void testInvalidFormatString()
79+
{
80+
QFETCH(QString, format);
81+
82+
Data::TracePointData data = {{QStringLiteral("ioprio"), QVariant(0)},
83+
{QStringLiteral("sector"), QVariant(18446744073709551615ull)},
84+
{QStringLiteral("nr_sector"), QVariant(0u)},
85+
{QStringLiteral("rwbs"), QVariant(QByteArray("N\x00\x00\x00\x00\x00\x00\x00"))},
86+
{QStringLiteral("dev"), QVariant(8388624u)},
87+
{QStringLiteral("cmd"), QVariant(65584u)},
88+
{QStringLiteral("error"), QVariant(-5)}};
89+
90+
TracePointFormatter formatter(format);
91+
QVERIFY(formatter.formatString().isEmpty());
92+
93+
// if the format string cannot be decoded then for formatter will just concat the tracepoint data
94+
// Qt5 and Qt6 use different hashing functions so we need two different outputs
95+
#if QT_VERSION < QT_VERSION_CHECK(6, 2, 0)
96+
auto output = QLatin1String("dev: 8388624\ncmd: 65584\nnr_sector: 0\nrwbs: 0\nioprio: 0\nerror: "
97+
"18446744073709551611\nsector: 18446744073709551615");
98+
#else
99+
auto output = QLatin1String("cmd: 65584\nioprio: 0\nnr_sector: 0\nrwbs: 0\nsector: 18446744073709551615\ndev: "
100+
"8388624\nerror: 18446744073709551611");
101+
#endif // QT_VERSION < QT_VERSION_CHECK(6, 2, 0)
102+
103+
QCOMPARE(formatter.format(data), output);
104+
}
105+
};
106+
107+
QTEST_GUILESS_MAIN(TestTracepointFormat)
108+
109+
#include "tst_tracepointformat.moc"

0 commit comments

Comments
 (0)