Skip to content

Commit 5a6105a

Browse files
lievenheymilianw
authored andcommitted
feat: correctly parse format string from tracepoint definition
This patch allows hotspot to correctly parse most format string from the tracepoint definition. If that fails every entry in the tracepoint will be printed unformatted. In this case it is possible to add a custom formatter.
1 parent b068879 commit 5a6105a

File tree

8 files changed

+388
-64
lines changed

8 files changed

+388
-64
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@
55
[submodule "3rdparty/PrefixTickLabels"]
66
path = 3rdparty/PrefixTickLabels
77
url = https://github.com/koenpoppe/PrefixTickLabels
8+
[submodule "3rdparty/fmtparser"]
9+
path = 3rdparty/fmtparser
10+
url = https://github.com/fmtparser/fmtparser

3rdparty/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
include(perfparser.cmake)
22
include(PrefixTickLabels.cmake)
3+
add_subdirectory(fmtparser)

3rdparty/fmtparser

Submodule fmtparser added at c10a48c

src/models/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ add_library(
2929

3030
target_link_libraries(
3131
models
32+
fmt_parser
3233
Qt::Core
3334
Qt::Widgets
3435
KF${QT_MAJOR_VERSION}::ItemModels

src/models/timelinedelegate.cpp

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
#include "timelinedelegate.h"
99

1010
#include <QAbstractItemView>
11-
#include <QDebug>
1211
#include <QEvent>
1312
#include <QHelpEvent>
1413
#include <QMenu>
@@ -345,15 +344,12 @@ bool TimeLineDelegate::helpEvent(QHelpEvent* event, QAbstractItemView* view, con
345344
});
346345

347346
const auto format = results.tracePointFormats[tracepoint.tracepointFormat];
348-
qDebug() << format.systemId << format.nameId << format.format;
349-
qDebug() << results.tracePointData[tracepoint.tracepointData];
350347

351-
TracePointFormatter formatter(format.format);
348+
auto tracepointFormatted = format.nameId.isEmpty()
349+
? QStringLiteral("PerfParser does not support tracepoints")
350+
: formatTracepoint(format, results.tracePointData[tracepoint.tracepointData]);
352351

353-
QToolTip::showText(event->globalPos(),
354-
tr("time: %1\n%2:\n%3")
355-
.arg(formattedTime, results.tracepoints[index.row()].name,
356-
formatter.format(results.tracePointData[tracepoint.tracepointData])));
352+
QToolTip::showText(event->globalPos(), tr("time: %1\n%2").arg(formattedTime, tracepointFormatted));
357353
}
358354

359355
} else {

src/models/tracepointformat.cpp

Lines changed: 205 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,175 @@
11
#include "tracepointformat.h"
22

3+
#include <QLoggingCategory>
4+
5+
extern "C" {
6+
#include <fmt_parser.h>
7+
#include <fmt_util.h>
8+
}
9+
10+
namespace {
11+
Q_LOGGING_CATEGORY(FormatParser, "hotspot.formatparser");
12+
13+
auto formatUnsignedNumber(const FormatConversion& format, int base, const QVariant& value)
14+
{
15+
switch (format.len) {
16+
case FormatConversion::Length::Char:
17+
return QStringLiteral("%1").arg(static_cast<unsigned char>(value.toULongLong()), format.width, base,
18+
QLatin1Char('0'));
19+
case FormatConversion::Length::Short:
20+
return QStringLiteral("%1").arg(static_cast<unsigned short>(value.toULongLong()), format.width, base,
21+
QLatin1Char('0'));
22+
case FormatConversion::Length::Long:
23+
return QStringLiteral("%1").arg(static_cast<unsigned int>(value.toULongLong()), format.width, base,
24+
QLatin1Char('0'));
25+
case FormatConversion::Length::Size:
26+
case FormatConversion::Length::LongLong:
27+
return QStringLiteral("%1").arg(value.toULongLong(), format.width, base, QLatin1Char('0'));
28+
}
29+
Q_UNREACHABLE();
30+
}
31+
32+
auto formatSignedNumber(const FormatConversion& format, int base, const QVariant& value)
33+
{
34+
switch (format.len) {
35+
case FormatConversion::Length::Char:
36+
return QStringLiteral("%1").arg(static_cast<char>(value.toLongLong()), format.width, base, QLatin1Char('0'));
37+
case FormatConversion::Length::Short:
38+
return QStringLiteral("%1").arg(static_cast<short>(value.toLongLong()), format.width, base, QLatin1Char('0'));
39+
case FormatConversion::Length::Long:
40+
return QStringLiteral("%1").arg(static_cast<int>(value.toLongLong()), format.width, base, QLatin1Char('0'));
41+
case FormatConversion::Length::Size:
42+
case FormatConversion::Length::LongLong:
43+
return QStringLiteral("%1").arg(value.toLongLong(), format.width, base, QLatin1Char('0'));
44+
}
45+
Q_UNREACHABLE();
46+
}
47+
}
48+
49+
FormatData parseFormatString(const QString& format)
50+
{
51+
// try to parse the format string
52+
// if it fails or we encounter unknown flags bail out
53+
auto latin = format.toLatin1().toStdString();
54+
55+
const char* str = latin.c_str();
56+
57+
fmt_status rc;
58+
fmt_spec spec;
59+
60+
QVector<FormatConversion> formats;
61+
QVector<QByteArray> qtFormatString;
62+
int formatCounter = 1;
63+
64+
do {
65+
fmt_spec_init(&spec);
66+
rc = fmt_read_one(&str, &spec);
67+
if (rc == FMT_EOK) {
68+
fmt_spec_print(&spec, stdout);
69+
printf("\n");
70+
FormatConversion format;
71+
72+
if (spec.kind == FMT_SPEC_KIND_STRING) {
73+
qtFormatString.append(QByteArray {spec.str_start, static_cast<int>(spec.str_end - spec.str_start)});
74+
} else {
75+
qtFormatString.append(QStringLiteral("%%1").arg(formatCounter++).toLatin1());
76+
77+
switch (static_cast<fmt_spec_len>(spec.len)) {
78+
case FMT_SPEC_LEN_hh:
79+
format.len = FormatConversion::Length::Char;
80+
break;
81+
case FMT_SPEC_LEN_h:
82+
format.len = FormatConversion::Length::Short;
83+
break;
84+
case FMT_SPEC_LEN_L:
85+
case FMT_SPEC_LEN_l:
86+
format.len = FormatConversion::Length::Long;
87+
break;
88+
case FMT_SPEC_LEN_ll:
89+
format.len = FormatConversion::Length::LongLong;
90+
break;
91+
case FMT_SPEC_LEN_z:
92+
format.len = FormatConversion::Length::Size;
93+
break;
94+
case FMT_SPEC_LEN_UNKNOWN:
95+
// no length given
96+
break;
97+
default:
98+
qCWarning(FormatParser) << "Failed to parse fmt_spec_len" << spec.len;
99+
return {};
100+
}
101+
102+
switch (static_cast<fmt_spec_type>(spec.type)) {
103+
case FMT_SPEC_TYPE_X:
104+
format.format = FormatConversion::Format::UpperHex;
105+
break;
106+
case FMT_SPEC_TYPE_x:
107+
format.format = FormatConversion::Format::Hex;
108+
break;
109+
case FMT_SPEC_TYPE_o:
110+
format.format = FormatConversion::Format::Octal;
111+
break;
112+
case FMT_SPEC_TYPE_d:
113+
case FMT_SPEC_TYPE_i:
114+
format.format = FormatConversion::Format::Signed;
115+
break;
116+
case FMT_SPEC_TYPE_u:
117+
format.format = FormatConversion::Format::Unsigned;
118+
break;
119+
case FMT_SPEC_TYPE_c:
120+
format.format = FormatConversion::Format::Char;
121+
break;
122+
case FMT_SPEC_TYPE_p:
123+
format.format = FormatConversion::Format::Pointer;
124+
break;
125+
case FMT_SPEC_TYPE_s:
126+
format.format = FormatConversion::Format::String;
127+
break;
128+
default:
129+
qCWarning(FormatParser) << "Failed to parse fmt_spec_type" << spec.type;
130+
return {};
131+
}
132+
133+
if (spec.flags.prepend_zero) {
134+
format.padZeros = true;
135+
format.width = spec.width;
136+
137+
if (spec.width == FMT_VALUE_OUT_OF_LINE) {
138+
return {};
139+
}
140+
}
141+
142+
formats.push_back(format);
143+
}
144+
}
145+
} while (fmt_read_is_ok(rc));
146+
147+
return {formats, QString::fromLatin1(qtFormatString.join())};
148+
}
149+
150+
QString format(const FormatConversion& format, const QVariant& value)
151+
{
152+
switch (format.format) {
153+
case FormatConversion::Format::Signed:
154+
return formatSignedNumber(format, 10, value);
155+
case FormatConversion::Format::Unsigned:
156+
return formatUnsignedNumber(format, 10, value);
157+
case FormatConversion::Format::Char:
158+
return value.toChar();
159+
case FormatConversion::Format::String:
160+
return value.toString();
161+
case FormatConversion::Format::Pointer:
162+
return QStringLiteral("0x%1").arg(QString::number(value.toULongLong(), 16));
163+
case FormatConversion::Format::Hex:
164+
return formatUnsignedNumber(format, 16, value);
165+
case FormatConversion::Format::UpperHex:
166+
return formatUnsignedNumber(format, 16, value).toUpper();
167+
case FormatConversion::Format::Octal:
168+
return formatUnsignedNumber(format, 8, value);
169+
}
170+
return {};
171+
}
172+
3173
TracePointFormatter::TracePointFormatter(const QString& format)
4174
{
5175
// ignore empty format strings
@@ -11,36 +181,31 @@ TracePointFormatter::TracePointFormatter(const QString& format)
11181
// follows a list of arguments
12182
auto endOfFormatString = format.indexOf(QLatin1Char('\"'), 1);
13183

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;
184+
auto formatStringExtracted = format.mid(1, endOfFormatString - 1);
185+
auto formats = parseFormatString(formatStringExtracted);
186+
187+
const auto args = format.mid(endOfFormatString + 2).split(QLatin1Char(','));
188+
189+
// we successfully parsed the string
190+
if (formats.format.size() == args.size()) {
191+
m_formatString = formats.formatString;
192+
for (int i = 0; i < formats.format.size(); i++) {
193+
const auto& arg = args[i];
194+
auto rec = arg.indexOf(QLatin1String("REC->"));
195+
196+
if (rec == -1) {
197+
return;
198+
}
199+
200+
auto closingBracket = arg.indexOf(QLatin1Char(')'), rec);
201+
if (closingBracket == -1) {
202+
return;
203+
}
204+
// TODO: safeguard this
205+
rec += 5;
206+
m_args.push_back({formats.format[i], arg.mid(rec, closingBracket - rec)});
31207
}
32208
}
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-
}
44209
}
45210

46211
QString TracePointFormatter::format(const Data::TracePointData& data) const
@@ -55,29 +220,22 @@ QString TracePointFormatter::format(const Data::TracePointData& data) const
55220
return result.trimmed();
56221
}
57222

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;
223+
result = m_formatString;
224+
for (const auto& arg : m_args) {
225+
result = result.arg(::format(arg.format, data.value(arg.name)));
74226
}
75-
76227
return result;
77228
}
78229

79230
QString formatTracepoint(const Data::TracePointFormat& format, const Data::TracePointData& data)
80231
{
81-
TracePointFormatter formatter(format.format);
82-
return QStringLiteral("%1:%2:\n%3").arg(format.systemId, format.nameId, formatter.format(data));
232+
static QHash<QString, TracePointFormatter> formatterCache;
233+
234+
const auto name = QStringLiteral("%1:%2").arg(format.systemId, format.nameId);
235+
auto formatter = formatterCache.find(name);
236+
if (formatter == formatterCache.end()) {
237+
formatter = formatterCache.emplace(name, format.format);
238+
}
239+
240+
return QStringLiteral("%1:\n%2").arg(name, formatter->format(data));
83241
}

0 commit comments

Comments
 (0)