Skip to content

Commit

Permalink
feat: correctly parse format string from tracepoint definition
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lievenhey committed Dec 2, 2024
1 parent 0c2bc35 commit 5bc7da4
Show file tree
Hide file tree
Showing 8 changed files with 388 additions and 66 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@
[submodule "3rdparty/PrefixTickLabels"]
path = 3rdparty/PrefixTickLabels
url = https://github.com/koenpoppe/PrefixTickLabels
[submodule "3rdparty/fmtparser"]
path = 3rdparty/fmtparser
url = https://github.com/fmtparser/fmtparser
1 change: 1 addition & 0 deletions 3rdparty/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
include(perfparser.cmake)
include(PrefixTickLabels.cmake)
add_subdirectory(fmtparser)
1 change: 1 addition & 0 deletions 3rdparty/fmtparser
Submodule fmtparser added at c10a48
1 change: 1 addition & 0 deletions src/models/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ add_library(

target_link_libraries(
models
fmt_parser
Qt::Core
Qt::Widgets
KF${QT_MAJOR_VERSION}::ItemModels
Expand Down
14 changes: 4 additions & 10 deletions src/models/timelinedelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#include "timelinedelegate.h"

#include <QAbstractItemView>
#include <QDebug>
#include <QEvent>
#include <QHelpEvent>
#include <QMenu>
Expand Down Expand Up @@ -329,9 +328,7 @@ bool TimeLineDelegate::helpEvent(QHelpEvent* event, QAbstractItemView* view, con
Util::formatTimeString(found.totalCost),
Util::formatTimeString(found.maxCost)));
} else if (found.numSamples > 0) {
qDebug() << m_eventType;
if (m_eventType == results.tracepointEventCostId) {
qDebug() << "HERE" << found.numSamples;
if (found.numSamples != 1) {
QToolTip::showText(event->globalPos(),
tr("time: %1\n%3 samples: %2")
Expand All @@ -347,15 +344,12 @@ bool TimeLineDelegate::helpEvent(QHelpEvent* event, QAbstractItemView* view, con
});

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

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

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

} else {
Expand Down
252 changes: 205 additions & 47 deletions src/models/tracepointformat.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,175 @@
#include "tracepointformat.h"

#include <QLoggingCategory>

extern "C" {
#include <fmt_parser.h>
#include <fmt_util.h>
}

namespace {
Q_LOGGING_CATEGORY(FormatParser, "hotspot.formatparser");

auto formatUnsignedNumber(const FormatConversion& format, int base, const QVariant& value)
{
switch (format.len) {
case FormatConversion::Length::Char:
return QStringLiteral("%1").arg(static_cast<unsigned char>(value.toULongLong()), format.width, base,
QLatin1Char('0'));
case FormatConversion::Length::Short:
return QStringLiteral("%1").arg(static_cast<unsigned short>(value.toULongLong()), format.width, base,
QLatin1Char('0'));
case FormatConversion::Length::Long:
return QStringLiteral("%1").arg(static_cast<unsigned int>(value.toULongLong()), format.width, base,
QLatin1Char('0'));
case FormatConversion::Length::Size:
case FormatConversion::Length::LongLong:
return QStringLiteral("%1").arg(value.toULongLong(), format.width, base, QLatin1Char('0'));
}
Q_UNREACHABLE();
}

auto formatSignedNumber(const FormatConversion& format, int base, const QVariant& value)
{
switch (format.len) {
case FormatConversion::Length::Char:
return QStringLiteral("%1").arg(static_cast<char>(value.toLongLong()), format.width, base, QLatin1Char('0'));
case FormatConversion::Length::Short:
return QStringLiteral("%1").arg(static_cast<short>(value.toLongLong()), format.width, base, QLatin1Char('0'));
case FormatConversion::Length::Long:
return QStringLiteral("%1").arg(static_cast<int>(value.toLongLong()), format.width, base, QLatin1Char('0'));
case FormatConversion::Length::Size:
case FormatConversion::Length::LongLong:
return QStringLiteral("%1").arg(value.toLongLong(), format.width, base, QLatin1Char('0'));
}
Q_UNREACHABLE();
}
}

FormatData parseFormatString(const QString& format)
{
// try to parse the format string
// if it fails or we encounter unknown flags bail out
auto latin = format.toLatin1().toStdString();

const char* str = latin.c_str();

fmt_status rc;
fmt_spec spec;

QVector<FormatConversion> formats;
QVector<QByteArray> qtFormatString;
int formatCounter = 1;

do {
fmt_spec_init(&spec);
rc = fmt_read_one(&str, &spec);
if (rc == FMT_EOK) {
fmt_spec_print(&spec, stdout);
printf("\n");
FormatConversion format;

if (spec.kind == FMT_SPEC_KIND_STRING) {
qtFormatString.append(QByteArray {spec.str_start, static_cast<int>(spec.str_end - spec.str_start)});
} else {
qtFormatString.append(QStringLiteral("%%1").arg(formatCounter++).toLatin1());

switch (static_cast<fmt_spec_len>(spec.len)) {
case FMT_SPEC_LEN_hh:
format.len = FormatConversion::Length::Char;
break;
case FMT_SPEC_LEN_h:
format.len = FormatConversion::Length::Short;
break;
case FMT_SPEC_LEN_L:
case FMT_SPEC_LEN_l:
format.len = FormatConversion::Length::Long;
break;
case FMT_SPEC_LEN_ll:
format.len = FormatConversion::Length::LongLong;
break;
case FMT_SPEC_LEN_z:
format.len = FormatConversion::Length::Size;
break;
case FMT_SPEC_LEN_UNKNOWN:
// no length given
break;
default:
qCWarning(FormatParser) << "Failed to parse fmt_spec_len" << spec.len;
return {};
}

switch (static_cast<fmt_spec_type>(spec.type)) {
case FMT_SPEC_TYPE_X:
format.format = FormatConversion::Format::UpperHex;
break;
case FMT_SPEC_TYPE_x:
format.format = FormatConversion::Format::Hex;
break;
case FMT_SPEC_TYPE_o:
format.format = FormatConversion::Format::Octal;
break;
case FMT_SPEC_TYPE_d:
case FMT_SPEC_TYPE_i:
format.format = FormatConversion::Format::Signed;
break;
case FMT_SPEC_TYPE_u:
format.format = FormatConversion::Format::Unsigned;
break;
case FMT_SPEC_TYPE_c:
format.format = FormatConversion::Format::Char;
break;
case FMT_SPEC_TYPE_p:
format.format = FormatConversion::Format::Pointer;
break;
case FMT_SPEC_TYPE_s:
format.format = FormatConversion::Format::String;
break;
default:
qCWarning(FormatParser) << "Failed to parse fmt_spec_type" << spec.type;
return {};
}

if (spec.flags.prepend_zero) {
format.padZeros = true;
format.width = spec.width;

if (spec.width == FMT_VALUE_OUT_OF_LINE) {
return {};
}
}

formats.push_back(format);
}
}
} while (fmt_read_is_ok(rc));

return {formats, QString::fromLatin1(qtFormatString.join())};
}

QString format(const FormatConversion& format, const QVariant& value)
{
switch (format.format) {
case FormatConversion::Format::Signed:
return formatSignedNumber(format, 10, value);
case FormatConversion::Format::Unsigned:
return formatUnsignedNumber(format, 10, value);
case FormatConversion::Format::Char:
return value.toChar();
case FormatConversion::Format::String:
return value.toString();
case FormatConversion::Format::Pointer:
return QStringLiteral("0x%1").arg(QString::number(value.toULongLong(), 16));
case FormatConversion::Format::Hex:
return formatUnsignedNumber(format, 16, value);
case FormatConversion::Format::UpperHex:
return formatUnsignedNumber(format, 16, value).toUpper();
case FormatConversion::Format::Octal:
return formatUnsignedNumber(format, 8, value);
}
return {};
}

TracePointFormatter::TracePointFormatter(const QString& format)
{
// ignore empty format strings
Expand All @@ -11,36 +181,31 @@ TracePointFormatter::TracePointFormatter(const QString& format)
// 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;
auto formatStringExtracted = format.mid(1, endOfFormatString - 1);
auto formats = parseFormatString(formatStringExtracted);

const auto args = format.mid(endOfFormatString + 2).split(QLatin1Char(','));

// we successfully parsed the string
if (formats.format.size() == args.size()) {
m_formatString = formats.formatString;
for (int i = 0; i < formats.format.size(); i++) {
const auto& arg = args[i];
auto rec = arg.indexOf(QLatin1String("REC->"));

if (rec == -1) {
return;
}

auto closingBracket = arg.indexOf(QLatin1Char(')'), rec);
if (closingBracket == -1) {
return;
}
// TODO: safeguard this
rec += 5;
m_args.push_back({formats.format[i], arg.mid(rec, closingBracket - rec)});
}
}

// 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
Expand All @@ -55,29 +220,22 @@ QString TracePointFormatter::format(const Data::TracePointData& data) const
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;
result = m_formatString;
for (const auto& arg : m_args) {
result = result.arg(::format(arg.format, data.value(arg.name)));
}

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));
static QHash<QString, TracePointFormatter> formatterCache;

const auto name = QStringLiteral("%1:%2").arg(format.systemId, format.nameId);
auto formatter = formatterCache.find(name);
if (formatter == formatterCache.end()) {
formatter = formatterCache.emplace(name, format.format);
}

return QStringLiteral("%1:\n%2").arg(name, formatter->format(data));
}
Loading

0 comments on commit 5bc7da4

Please sign in to comment.