Add CTF tracing backend

Implement platform independent tracing backend in Common trace format.
This allows tracing in platforms without own/existing backend and
analysing all platforms with the same tooling. The backend is the basis
for further work in application level profiling area.

The backend is implemented as a plugin that is loaded immediately when
the application starts in order to process all trace events. The backend
avoids using Qt classes so that it doesn't generate trace events
itself. Adds plumbing to configure the new backend.

Modifies the tracegen and tracepointgen tools to support the new
backend.

Task-number: QTBUG-106399
Pick-to: 6.5
Change-Id: I80711be52d4d48e1acbc72edffbdf3f379fce52a
Reviewed-by: Tomi Korpipää <tomi.korpipaa@qt.io>
This commit is contained in:
Antti Määttä 2022-10-24 11:30:56 +03:00
parent a37a59eea8
commit e3458aac64
21 changed files with 1291 additions and 12 deletions

View File

@ -709,7 +709,7 @@ function(qt_internal_create_tracepoints name tracepoints_file)
set(header_filename "${provider_name}_tracepoints_p.h")
set(header_path "${CMAKE_CURRENT_BINARY_DIR}/${header_filename}")
if(QT_FEATURE_lttng OR QT_FEATURE_etw)
if(QT_FEATURE_lttng OR QT_FEATURE_etw OR QT_FEATURE_ctf)
set(source_path "${CMAKE_CURRENT_BINARY_DIR}/${provider_name}_tracepoints.cpp")
qt_configure_file(OUTPUT "${source_path}"
CONTENT "#define TRACEPOINT_CREATE_PROBES
@ -723,6 +723,8 @@ function(qt_internal_create_tracepoints name tracepoints_file)
target_link_libraries(${name} PRIVATE LTTng::UST)
elseif(QT_FEATURE_etw)
set(tracegen_arg "etw")
elseif(QT_FEATURE_ctf)
set(tracegen_arg "ctf")
endif()
if(NOT "${QT_HOST_PATH}" STREQUAL "")
@ -754,7 +756,7 @@ function(qt_internal_generate_tracepoints name provider)
set(header_filename "${provider_name}_tracepoints_p.h")
set(header_path "${CMAKE_CURRENT_BINARY_DIR}/${header_filename}")
if(QT_FEATURE_lttng OR QT_FEATURE_etw)
if(QT_FEATURE_lttng OR QT_FEATURE_etw OR QT_FEATURE_ctf)
set(absolute_file_paths "")
foreach(file IN LISTS arg_SOURCES)
@ -791,6 +793,8 @@ function(qt_internal_generate_tracepoints name provider)
target_link_libraries(${name} PRIVATE LTTng::UST)
elseif(QT_FEATURE_etw)
set(tracegen_arg "etw")
elseif(QT_FEATURE_ctf)
set(tracegen_arg "ctf")
endif()
if(NOT "${QT_HOST_PATH}" STREQUAL "")

View File

@ -45,6 +45,9 @@ endif()
qt_install_3rdparty_library_wrap_config_extra_file(BundledZLIB)
add_subdirectory(corelib)
if (QT_FEATURE_ctf AND QT_FEATURE_library)
add_subdirectory(corelib/tracing)
endif()
# Needs to be after corelib, because some of them reference Core.
add_subdirectory(3rdparty)

View File

@ -1320,6 +1320,13 @@ qt_internal_extend_target(Core CONDITION WASM
kernel/qeventdispatcher_wasm.cpp kernel/qeventdispatcher_wasm_p.h
)
qt_internal_extend_target(Core CONDITION QT_FEATURE_ctf AND QT_FEATURE_library
SOURCES
tracing/qctf_p.h tracing/qctf.cpp
PLUGIN_TYPES
tracing
)
set_source_files_properties(
thread/qmutex_mac.cpp
thread/qmutex_unix.cpp

View File

@ -925,6 +925,12 @@ qt_feature("etw" PRIVATE
ENABLE INPUT_trace STREQUAL 'etw' OR ( INPUT_trace STREQUAL 'yes' AND WIN32 )
DISABLE INPUT_trace STREQUAL 'lttng' OR INPUT_trace STREQUAL 'no'
)
qt_feature("ctf" PRIVATE
LABEL "CTF"
AUTODETECT OFF
ENABLE INPUT_trace STREQUAL 'ctf'
DISABLE INPUT_trace STREQUAL 'etw' OR INPUT_trace STREQUAL 'no' OR INPUT_trace STREQUAL 'lttng'
)
qt_feature("forkfd_pidfd" PRIVATE
LABEL "CLONE_PIDFD support in forkfd"
CONDITION LINUX
@ -962,7 +968,7 @@ qt_configure_add_summary_entry(ARGS "mimetype-database")
qt_configure_add_summary_entry(ARGS "cpp-winrt")
qt_configure_add_summary_entry(
TYPE "firstAvailableFeature"
ARGS "etw lttng"
ARGS "etw lttng ctf"
MESSAGE "Tracing backend"
)
qt_configure_add_summary_section(NAME "Logging backends")

View File

@ -57,7 +57,7 @@
* qcoreapplication_qrect(const QRect &rect)
*
* The provider file is then parsed by src/tools/tracegen, which can be
* switched to output either ETW or LTTNG tracepoint definitions. The provider
* switched to output either ETW, CTF or LTTNG tracepoint definitions. The provider
* name is deduced to be basename(provider_file).
*
* To use the above (inside qtcore), you need to include

View File

@ -15,4 +15,4 @@ qt_commandline_option(posix-ipc TYPE boolean NAME ipc_posix)
qt_commandline_option(pps TYPE boolean NAME qqnx_pps)
qt_commandline_option(slog2 TYPE boolean)
qt_commandline_option(syslog TYPE boolean)
qt_commandline_option(trace TYPE optionalString VALUES etw lttng no yes)
qt_commandline_option(trace TYPE optionalString VALUES etw lttng ctf no yes)

View File

@ -0,0 +1,23 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
function(make_includable input_file output_file)
get_filename_component(infile "${CMAKE_CURRENT_SOURCE_DIR}/${input_file}" ABSOLUTE)
set(outfile ${CMAKE_CURRENT_BINARY_DIR}/${output_file})
file(READ ${infile} content)
set(content "R\"(${content})\"")
file(WRITE ${outfile} "${content}")
endfunction(make_includable)
make_includable(metadata_template.txt metadata_template.h)
qt_internal_add_plugin(QCtfTracePlugin
SHARED
CLASS_NAME QCtfTracePlugin
PLUGIN_TYPE tracing
SOURCES
qctflib_p.h qctflib.cpp metadata_template.txt qctfplugin.cpp qctfplugin_p.h
LIBRARIES
Qt6::Core
)

View File

@ -0,0 +1,77 @@
/* CTF 1.8 */
typealias integer { size = 8; align = 8; signed = false; } := uint8_t;
typealias integer { size = 16; align = 8; signed = false; } := uint16_t;
typealias integer { size = 32; align = 8; signed = false; } := uint32_t;
typealias integer { size = 64; align = 8; signed = false; } := uint64_t;
typealias integer { size = 8; align = 8; signed = true; } := int8_t;
typealias integer { size = 16; align = 8; signed = true; } := int16_t;
typealias integer { size = 32; align = 8; signed = true; } := int32_t;
typealias integer { size = 64; align = 8; signed = true; } := int64_t;
typealias integer { size = 32; align = 8; signed = true; base = 16; } := intptr32_t;
typealias integer { size = 64; align = 8; signed = true; base = 16; } := intptr64_t;
typealias floating_point { exp_dig = 8; mant_dig = 24; align = 8; byte_order = native; } := float;
typealias floating_point { exp_dig = 11; mant_dig = 53; align = 8; byte_order = native; } := double;
typealias enum : integer { size = 8; } {
false,
true
} := Boolean;
trace {
major = 1;
minor = 8;
uuid = "$TRACE_UUID";
byte_order = $ENDIANNESS;
packet.header := struct {
uint32_t magic;
uint8_t uuid[16];
uint32_t stream_id;
} align(8);
};
env {
domain = "ust";
tracer_name = "qtctf";
tracer_major = 1;
tracer_minor = 0;
architecture_bit_width = $ARC_BIT_WIDTH;
trace_name = "$SESSION_NAME";
trace_creation_datetime = "$CREATION_TIME";
hostname = "$HOST_NAME";
};
clock {
name = "$CLOCK_NAME";
uuid = "53836526-5a62-4de0-93c8-3a1970afab23";
description = "$CLOCK_TYPE";
freq = $CLOCK_FREQUENCY;
offset = $CLOCK_OFFSET;
};
typealias integer {
size = 64; align = 8; signed = false;
map = clock.monotonic.value;
} := uint64_clock_monotonic_t;
struct packet_context {
uint64_clock_monotonic_t timestamp_begin;
uint64_clock_monotonic_t timestamp_end;
uint64_t content_size;
uint64_t packet_size;
uint64_t packet_seq_num;
uint64_t events_discarded;
uint32_t thread_id;
string thread_name;
} align(8);
struct event_header {
uint32_t id;
uint64_clock_monotonic_t timestamp;
} align(8);
stream {
id = 0;
event.header := struct event_header;
packet.context := struct packet_context;
};

View File

@ -0,0 +1,119 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#define BUILD_LIBRARY
#include <qthread.h>
#include <qpluginloader.h>
#include <qfileinfo.h>
#include <qdir.h>
#include "qctflib_p.h"
QT_BEGIN_NAMESPACE
static bool s_initialized = false;
static bool s_triedLoading = false;
static bool s_prevent_recursion = false;
static QCtfLib* s_plugin = nullptr;
#if defined(Q_OS_ANDROID)
static QString findPlugin(const QString &plugin)
{
QString pluginPath = QString::fromUtf8(qgetenv("QT_PLUGIN_PATH"));
QDir dir(pluginPath);
const QStringList files = dir.entryList(QDir::Files);
for (const QString &file : files) {
if (file.contains(plugin))
return QFileInfo(pluginPath + QLatin1Char('/') + file).absoluteFilePath();
}
return {};
}
#endif
static bool loadPlugin(bool &retry)
{
retry = false;
#ifdef Q_OS_WIN
#ifdef QT_DEBUG
QPluginLoader loader(QStringLiteral("tracing/QCtfTracePlugind.dll"));
#else
QPluginLoader loader(QStringLiteral("tracing/QCtfTracePlugin.dll"));
#endif
#elif defined(Q_OS_ANDROID)
QString plugin = findPlugin(QStringLiteral("QCtfTracePlugin"));
if (plugin.isEmpty()) {
retry = true;
return false;
}
QPluginLoader loader(plugin);
#else
QPluginLoader loader(QStringLiteral("tracing/libQCtfTracePlugin.so"));
#endif
if (!loader.isLoaded()) {
if (!loader.load())
return false;
}
s_plugin = qobject_cast<QCtfLib *>(loader.instance());
if (!s_plugin)
return false;
QObject *obj = loader.instance();
if (obj) {
QObject::connect(obj, &QObject::destroyed, []() {
s_plugin = nullptr;
});
}
return true;
}
static bool initialize()
{
if (s_prevent_recursion)
return false;
if (s_initialized || s_triedLoading)
return s_initialized;
s_prevent_recursion = true;
bool retry = false;
if (!loadPlugin(retry)) {
if (!retry) {
s_triedLoading = true;
s_initialized = false;
}
} else {
bool enabled = s_plugin->sessionEnabled();
if (!enabled) {
s_triedLoading = true;
s_initialized = false;
} else {
s_initialized = true;
}
}
s_prevent_recursion = false;
return s_initialized;
}
bool _tracepoint_enabled(const QCtfTracePointEvent &point)
{
if (!initialize())
return false;
return s_plugin ? s_plugin->tracepointEnabled(point) : false;
}
void _do_tracepoint(const QCtfTracePointEvent &point, const QByteArray &arr)
{
if (!initialize())
return;
if (s_plugin)
s_plugin->doTracepoint(point, arr);
}
QCtfTracePointPrivate *_initialize_tracepoint(const QCtfTracePointEvent &point)
{
if (!initialize())
return nullptr;
return s_plugin ? s_plugin->initializeTracepoint(point) : nullptr;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,225 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef Q_CTF_H
#define Q_CTF_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
//
#include <qtcoreexports.h>
QT_REQUIRE_CONFIG(library);
QT_BEGIN_NAMESPACE
struct QCtfTraceMetadata;
struct Q_CORE_EXPORT QCtfTracePointProvider
{
const QString provider;
QCtfTraceMetadata *metadata;
QCtfTracePointProvider(const QString &provider)
: provider(provider), metadata(nullptr)
{
}
};
struct Q_CORE_EXPORT QCtfTraceMetadata
{
const QString name;
const QString metadata;
QCtfTraceMetadata(QCtfTracePointProvider &provider, const QString &name, const QString &metadata)
: name(name), metadata(metadata)
{
next = provider.metadata;
provider.metadata = this;
}
QCtfTraceMetadata *next = nullptr;
};
struct QCtfTracePointPrivate;
struct Q_CORE_EXPORT QCtfTracePointEvent
{
const QCtfTracePointProvider &provider;
const QString eventName;
const QString metadata;
const int size;
const bool variableSize;
QCtfTracePointEvent(const QCtfTracePointProvider &provider, const QString &name, const QString &metadata, int size, bool variableSize)
: provider(provider), eventName(name), metadata(metadata), size(size), variableSize(variableSize)
{
}
QCtfTracePointPrivate *d = nullptr;
};
Q_CORE_EXPORT bool _tracepoint_enabled(const QCtfTracePointEvent &point);
Q_CORE_EXPORT void _do_tracepoint(const QCtfTracePointEvent &point, const QByteArray &arr);
Q_CORE_EXPORT QCtfTracePointPrivate *_initialize_tracepoint(const QCtfTracePointEvent &point);
#ifndef BUILD_LIBRARY
#include <QtCore/qbytearray.h>
#include <QtCore/qstring.h>
#include <QtCore/qurl.h>
namespace trace {
inline void toByteArray(QByteArray &)
{
}
inline void toByteArray(QByteArray &arr, const QString &value)
{
arr.append(value.toUtf8());
arr.append((char)0);
}
inline void toByteArray(QByteArray &arr, const QUrl &value)
{
arr.append(value.toString().toUtf8());
arr.append((char)0);
}
inline void toByteArray(QByteArray &arr, const QByteArray &data)
{
arr.append(data);
}
template <typename T>
inline void toByteArray(QByteArray &arr, T value)
{
arr.append((char *)&value, sizeof(value));
}
template <typename T, typename... Ts>
inline void toByteArray(QByteArray &arr, T value, Ts... args)
{
toByteArray(arr, value);
toByteArray(arr, args...);
}
inline QByteArray toByteArray()
{
return {};
}
template <typename... Ts>
inline QByteArray toByteArray(Ts... args)
{
QByteArray data;
toByteArray(data, args...);
return data;
}
template <typename T>
inline QByteArray toByteArrayFromArray(const T *values, int arraySize)
{
QByteArray data;
data.append((char *)values, arraySize * sizeof(T));
return data;
}
template <typename IntegerType, typename T>
inline QByteArray toByteArrayFromEnum(T value)
{
IntegerType e = static_cast<IntegerType>(value);
QByteArray data;
data.append((char *)&e, sizeof(e));
return data;
}
inline QByteArray toByteArrayFromCString(const char *str)
{
QByteArray data;
if (str && *str != 0)
data.append(str);
data.append((char)0);
return data;
}
static inline void appendFlags(QByteArray &data, quint8 &count, quint32 value)
{
count = 0;
quint8 d = 1;
while (value) {
if (value&1) {
data.append(d);
count++;
}
d++;
value >>= 1;
}
}
template <typename T>
inline QByteArray toByteArrayFromFlags(QFlags<T> value)
{
quint32 intValue = static_cast<quint32>(value.toInt());
quint8 count;
QByteArray data;
data.append((char)0);
if (intValue == 0) {
data.append((char)0);
data.data()[0] = 1;
} else {
appendFlags(data, count, intValue);
data.data()[0] = count;
}
return data;
}
} // trace
#define _DEFINE_EVENT(provider, event, metadata, size, varSize) \
static QCtfTracePointEvent _ctf_ ## event = QCtfTracePointEvent(_ctf_provider_ ## provider, QStringLiteral(QT_STRINGIFY(event)), QStringLiteral(metadata), size, varSize);
#define _DEFINE_METADATA(provider, name, metadata) \
static QCtfTraceMetadata _ctf_metadata_ ## name = QCtfTraceMetadata(_ctf_provider_ ## provider, QStringLiteral(QT_STRINGIFY(name)), QStringLiteral(metadata));
#define _DEFINE_TRACEPOINT_PROVIDER(provider) \
static QCtfTracePointProvider _ctf_provider_ ## provider = QCtfTracePointProvider(QStringLiteral(QT_STRINGIFY(provider)));
#define TRACEPOINT_EVENT(provider, event, metadata, size, varSize) \
_DEFINE_EVENT(provider, event, metadata, size, varSize)
#define TRACEPOINT_PROVIDER(provider) \
_DEFINE_TRACEPOINT_PROVIDER(provider)
#define TRACEPOINT_METADATA(provider, name, metadata) \
_DEFINE_METADATA(provider, name, metadata)
#define tracepoint_enabled(provider, event) \
_tracepoint_enabled(_ctf_ ## event)
#define do_tracepoint(provider, event, ...) \
{ \
auto &tp = _ctf_ ## event; \
if (!tp.d) \
tp.d = _initialize_tracepoint(tp); \
if (tp.d) { \
QByteArray data(tp.size, 0); \
if (!tp.metadata.isEmpty()) \
data = trace::toByteArray(__VA_ARGS__); \
_do_tracepoint(tp, data); \
} \
}
#define tracepoint(provider, name, ...) \
do { \
if (tracepoint_enabled(provider, name)) \
do_tracepoint(provider, name, __VA_ARGS__); \
} while (0)
#endif
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,303 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#define BUILD_LIBRARY
#include <qstring.h>
#include <qthread.h>
#include <stdio.h>
#include <qjsondocument.h>
#include <qjsonarray.h>
#include <qjsonobject.h>
#include <qfileinfo.h>
#include <qrect.h>
#include <qsize.h>
#include <qmetaobject.h>
#include <qendian.h>
#include "qctflib_p.h"
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcDebugTrace, "qt.core.ctf");
#define PACKET_SIZE (4096)
#define PACKET_HEADER_SIZE (24 + 6 * 8 + 4)
static const char s_trace_metadata_template[] =
#include "metadata_template.h"
;
static const int s_trace_metadata_size = sizeof(s_trace_metadata_template);
template <typename T>
QByteArray &operator << (QByteArray &arr, T val)
{
arr.append((char *)&val, sizeof(val));
return arr;
}
QCtfLibImpl *QCtfLibImpl::s_instance = nullptr;
QCtfLib *QCtfLibImpl::instance()
{
if (!s_instance)
s_instance = new QCtfLibImpl();
return s_instance;
}
QCtfLibImpl::QCtfLibImpl()
{
QString location = QString::fromUtf8(qgetenv("QTRACE_LOCATION"));
if (location.isEmpty()) {
qCWarning (lcDebugTrace) << "QTRACE_LOCATION not set";
return;
}
FILE *file = nullptr;
file = fopen(qPrintable(location + QStringLiteral("/session.json")), "rb");
if (!file) {
qCWarning (lcDebugTrace) << "unable to open session file: " << (location + QStringLiteral("/session.json"));
m_location = location;
m_session.tracepoints.append(QStringLiteral("all"));
m_session.name = QStringLiteral("default");
} else {
QByteArray data;
fseek(file, 0, SEEK_END);
long pos = ftell(file);
fseek(file, 0, SEEK_SET);
data.resize(pos);
long size = (long)fread(data.data(), pos, 1, file);
fclose(file);
if (size != 1)
return;
QJsonDocument json(QJsonDocument::fromJson(data));
QJsonObject obj = json.object();
QJsonValue value = *obj.begin();
if (value.isNull() || !value.isArray())
return;
m_session.name = obj.begin().key();
QJsonArray arr = value.toArray();
for (auto var : arr)
m_session.tracepoints.append(var.toString());
m_location = location + QStringLiteral("/ust");
}
m_session.all = m_session.tracepoints.contains(QStringLiteral("all"));
auto datetime = QDateTime::currentDateTime();
QString mhn = QSysInfo::machineHostName();
QString metadata = QString::fromUtf8(s_trace_metadata_template, s_trace_metadata_size);
metadata.replace(QStringLiteral("$TRACE_UUID"), s_TraceUuid.toString(QUuid::WithoutBraces));
metadata.replace(QStringLiteral("$ARC_BIT_WIDTH"), QString::number(Q_PROCESSOR_WORDSIZE * 8));
metadata.replace(QStringLiteral("$SESSION_NAME"), m_session.name);
metadata.replace(QStringLiteral("$CREATION_TIME"), datetime.toString());
metadata.replace(QStringLiteral("$HOST_NAME"), mhn);
metadata.replace(QStringLiteral("$CLOCK_FREQUENCY"), m_timer.isMonotonic() ? QStringLiteral("1000000000") : QStringLiteral("1000"));
metadata.replace(QStringLiteral("$CLOCK_NAME"), m_timer.isMonotonic() ? QStringLiteral("monotonic") : QStringLiteral("system"));
metadata.replace(QStringLiteral("$CLOCK_TYPE"), m_timer.isMonotonic() ? QStringLiteral("Monotonic clock") : QStringLiteral("System clock"));
metadata.replace(QStringLiteral("$CLOCK_OFFSET"), QString::number(datetime.toMSecsSinceEpoch() * 1000000));
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
metadata.replace(QStringLiteral("$ENDIANNESS"), QStringLiteral("be"));
#else
metadata.replace(QStringLiteral("$ENDIANNESS"), QStringLiteral("le"));
#endif
writeMetadata(metadata.toUtf8(), true);
m_timer.start();
}
void QCtfLibImpl::writeMetadata(const QByteArray &data, bool overwrite)
{
FILE *file = nullptr;
file = fopen(qPrintable(m_location + QStringLiteral("/metadata")), overwrite ? "w+b": "ab");
if (!file)
return;
if (!overwrite)
fputs("\n", file);
fwrite(data.data(), data.size() - 1, 1, file);
fclose(file);
}
void QCtfLibImpl::writeCtfPacket(QCtfLibImpl::Channel &ch)
{
FILE *file = nullptr;
file = fopen(ch.channelName, "ab");
if (file) {
/* Each packet contains header and context, which are defined in the metadata.txt */
QByteArray packet;
packet << s_CtfHeaderMagic;
/* Uuid is array of bytes hence implicitely big endian. */
packet << qToBigEndian(s_TraceUuid.data1);
packet << qToBigEndian(s_TraceUuid.data2);
packet << qToBigEndian(s_TraceUuid.data3);
for (int i = 0; i < 8; i++)
packet << s_TraceUuid.data4[i];
packet << quint32(0);
packet << ch.minTimestamp;
packet << ch.maxTimestamp;
packet << quint64(ch.data.size() + PACKET_HEADER_SIZE + ch.threadNameLength) * 8u;
packet << quint64(PACKET_SIZE) * 8u;
packet << ch.seqnumber++;
packet << quint64(0);
packet << ch.threadIndex;
if (ch.threadName.size())
packet.append(ch.threadName);
packet << (char)0;
Q_ASSERT(ch.data.size() + PACKET_HEADER_SIZE + ch.threadNameLength <= PACKET_SIZE);
Q_ASSERT(packet.size() == PACKET_HEADER_SIZE + ch.threadNameLength);
fwrite(packet.data(), packet.size(), 1, file);
ch.data.resize(PACKET_SIZE - packet.size(), 0);
fwrite(ch.data.data(), ch.data.size(), 1, file);
}
fclose(file);
}
QCtfLibImpl::~QCtfLibImpl()
{
qDeleteAll(m_eventPrivs);
}
bool QCtfLibImpl::tracepointEnabled(const QCtfTracePointEvent &point)
{
return m_session.all || m_session.tracepoints.contains(point.provider.provider);
}
QCtfLibImpl::Channel::~Channel()
{
if (data.size())
QCtfLibImpl::writeCtfPacket(*this);
}
static QString toMetadata(const QString &provider, const QString &name, const QString &metadata, quint32 eventId)
{
/*
generates event structure:
event {
name = provider:tracepoint_name;
id = eventId;
stream_id = 0;
loglevel = 13;
fields := struct {
metadata
};
};
*/
QString ret;
ret = QStringLiteral("event {\n name = \"") + provider + QLatin1Char(':') + name + QStringLiteral("\";\n");
ret += QStringLiteral(" id = ") + QString::number(eventId) + QStringLiteral(";\n");
ret += QStringLiteral(" stream_id = 0;\n loglevel = 13;\n fields := struct {\n ");
ret += metadata + QStringLiteral("\n };\n};\n");
return ret;
}
QCtfTracePointPrivate *QCtfLibImpl::initializeTracepoint(const QCtfTracePointEvent &point)
{
QMutexLocker lock(&m_mutex);
QCtfTracePointPrivate *priv = point.d;
if (!point.d) {
if (const auto &it = m_eventPrivs.find(point.eventName); it != m_eventPrivs.end()) {
priv = *it;
} else {
priv = new QCtfTracePointPrivate();
m_eventPrivs.insert(point.eventName, priv);
priv->id = eventId();
priv->metadata = toMetadata(point.provider.provider, point.eventName, point.metadata, priv->id);
}
}
return priv;
}
void QCtfLibImpl::doTracepoint(const QCtfTracePointEvent &point, const QByteArray &arr)
{
QCtfTracePointPrivate *priv = point.d;
quint64 timestamp = 0;
QThread *thread = nullptr;
{
QMutexLocker lock(&m_mutex);
if (!priv->metadataWritten) {
priv->metadataWritten = true;
auto providerMetadata = point.provider.metadata;
while (providerMetadata) {
registerMetadata(*providerMetadata);
providerMetadata = providerMetadata->next;
}
if (m_newAdditionalMetadata.size()) {
for (const QString &name : m_newAdditionalMetadata)
writeMetadata(m_additionalMetadata[name]->metadata.toUtf8());
m_newAdditionalMetadata.clear();
}
writeMetadata(priv->metadata.toUtf8());
}
timestamp = m_timer.nsecsElapsed();
}
if (arr.size() != point.size) {
if (arr.size() < point.size)
return;
if (arr.size() > point.size && !point.variableSize && !point.metadata.isEmpty())
return;
}
thread = QThread::currentThread();
if (thread == nullptr)
return;
Channel &ch = m_threadData.localData();
if (ch.channelName[0] == 0) {
m_threadIndices.insert(thread, m_threadIndices.size());
sprintf(ch.channelName, "%s/channel_%d", qPrintable(m_location), m_threadIndices[thread]);
FILE *f = nullptr;
f = fopen(ch.channelName, "wb");
fclose(f);
ch.minTimestamp = ch.maxTimestamp = timestamp;
ch.thread = thread;
ch.threadIndex = m_threadIndices[thread];
ch.threadName = thread->objectName().toUtf8();
if (ch.threadName.isEmpty()) {
const QMetaObject *obj = thread->metaObject();
ch.threadName = QByteArray(obj->className());
}
ch.threadNameLength = ch.threadName.size() + 1;
}
if (ch.locked)
return;
Q_ASSERT(ch.thread == thread);
ch.locked = true;
QByteArray event;
event << priv->id << timestamp;
if (!point.metadata.isEmpty())
event.append(arr);
if (ch.threadNameLength + ch.data.size() + event.size() + PACKET_HEADER_SIZE >= PACKET_SIZE) {
writeCtfPacket(ch);
ch.data = event;
ch.minTimestamp = ch.maxTimestamp = timestamp;
} else {
ch.data.append(event);
}
ch.locked = false;
ch.maxTimestamp = timestamp;
}
bool QCtfLibImpl::sessionEnabled()
{
return !m_session.name.isEmpty();
}
int QCtfLibImpl::eventId()
{
return m_eventId++;
}
void QCtfLibImpl::registerMetadata(const QCtfTraceMetadata &metadata)
{
if (m_additionalMetadata.contains(metadata.name))
return;
m_additionalMetadata.insert(metadata.name, &metadata);
m_newAdditionalMetadata.insert(metadata.name);
}
QT_END_NAMESPACE

View File

@ -0,0 +1,107 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QT_CTFLIB_H
#define QT_CTFLIB_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
//
#include "qctf_p.h"
#include "qctfplugin_p.h"
#include <qstring.h>
#include <qmutex.h>
#include <qelapsedtimer.h>
#include <qhash.h>
#include <qset.h>
#include <qthreadstorage.h>
#include <qthread.h>
#include <qmutex.h>
#include <qloggingcategory.h>
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(lcDebugTrace)
struct QCtfTracePointPrivate
{
QString metadata;
quint32 id = 0;
quint32 payloadSize = 0;
bool metadataWritten = false;
};
class QCtfLibImpl : public QCtfLib
{
struct Session
{
QString name;
QStringList tracepoints;
bool all = false;
};
struct Channel
{
char channelName[512];
QByteArray data;
quint64 minTimestamp = 0;
quint64 maxTimestamp = 0;
quint64 seqnumber = 0;
QThread *thread = nullptr;
quint32 threadIndex = 0;
QByteArray threadName;
quint32 threadNameLength = 0;
bool locked = false;
Channel()
{
memset(channelName, 0, sizeof(channelName));
}
~Channel();
};
public:
QCtfLibImpl();
~QCtfLibImpl();
bool tracepointEnabled(const QCtfTracePointEvent &point) override;
void doTracepoint(const QCtfTracePointEvent &point, const QByteArray &arr) override;
bool sessionEnabled() override;
QCtfTracePointPrivate *initializeTracepoint(const QCtfTracePointEvent &point) override;
void registerMetadata(const QCtfTraceMetadata &metadata);
int eventId();
static QCtfLib *instance();
private:
static QCtfLibImpl *s_instance;
QHash<QString, QCtfTracePointPrivate *> m_eventPrivs;
void updateMetadata(const QCtfTracePointEvent &point);
void writeMetadata(const QByteArray &data, bool overwrite = false);
static void writeCtfPacket(Channel &ch);
static constexpr QUuid s_TraceUuid = QUuid(0x3e589c95, 0xed11, 0xc159, 0x42, 0x02, 0x6a, 0x9b, 0x02, 0x00, 0x12, 0xac);
static constexpr quint32 s_CtfHeaderMagic = 0xC1FC1FC1;
QMutex m_mutex;
QElapsedTimer m_timer;
QString m_metadata;
QString m_location;
Session m_session;
QHash<QThread*, quint32> m_threadIndices;
QThreadStorage<Channel> m_threadData;
QHash<QString, const QCtfTraceMetadata *> m_additionalMetadata;
QSet<QString> m_newAdditionalMetadata;
int m_eventId = 0;
};
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,44 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#define BUILD_LIBRARY
#include <qstring.h>
#include "qctfplugin_p.h"
#include "qctflib_p.h"
QT_BEGIN_NAMESPACE
class QCtfTracePlugin : public QObject, public QCtfLib
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QCtfLib" FILE "trace.json")
Q_INTERFACES(QCtfLib)
public:
QCtfTracePlugin()
{
}
~QCtfTracePlugin() = default;
bool tracepointEnabled(const QCtfTracePointEvent &point) override
{
return QCtfLibImpl::instance()->tracepointEnabled(point);
}
void doTracepoint(const QCtfTracePointEvent &point, const QByteArray &arr) override
{
QCtfLibImpl::instance()->doTracepoint(point, arr);
}
bool sessionEnabled() override
{
return QCtfLibImpl::instance()->sessionEnabled();
}
QCtfTracePointPrivate *initializeTracepoint(const QCtfTracePointEvent &point) override
{
return QCtfLibImpl::instance()->initializeTracepoint(point);
}
};
#include "qctfplugin.moc"
QT_END_NAMESPACE

View File

@ -0,0 +1,38 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef Q_CTFPLUGIN_P_H
#define Q_CTFPLUGIN_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
//
#include "qctf_p.h"
#include <qplugin.h>
QT_BEGIN_NAMESPACE
class QCtfLib
{
public:
virtual ~QCtfLib() = default;
virtual bool tracepointEnabled(const QCtfTracePointEvent &point) = 0;
virtual void doTracepoint(const QCtfTracePointEvent &point, const QByteArray &arr) = 0;
virtual bool sessionEnabled() = 0;
virtual QCtfTracePointPrivate *initializeTracepoint(const QCtfTracePointEvent &point) = 0;
};
Q_DECLARE_INTERFACE(QCtfLib, "org.qt-project.Qt.QCtfLib");
QT_END_NAMESPACE
#endif

View File

@ -0,0 +1,3 @@
{
"Keys": [ "CTF" ]
}

View File

@ -15,6 +15,7 @@ qt_internal_add_tool(${target_name}
SOURCES
etw.cpp etw.h
helpers.cpp helpers.h
ctf.cpp ctf.h
lttng.cpp lttng.h
panic.cpp panic.h
provider.cpp provider.h

278
src/tools/tracegen/ctf.cpp Normal file
View File

@ -0,0 +1,278 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "ctf.h"
#include "provider.h"
#include "helpers.h"
#include "panic.h"
#include "qtheaders.h"
#include <qfile.h>
#include <qfileinfo.h>
#include <qtextstream.h>
#include <qdebug.h>
static void writePrologue(QTextStream &stream, const QString &fileName, const Provider &provider)
{
const QString guard = includeGuard(fileName);
// include prefix text or qt headers only once
stream << "#if !defined(" << guard << ")\n";
stream << qtHeaders();
stream << "\n";
if (!provider.prefixText.isEmpty())
stream << provider.prefixText.join(u'\n') << "\n\n";
stream << "#endif\n\n";
/* the first guard is the usual one, the second is required
* by LTTNG to force the re-evaluation of TRACEPOINT_* macros
*/
stream << "#if !defined(" << guard << ") || defined(TRACEPOINT_HEADER_MULTI_READ)\n";
stream << "#define " << guard << "\n\n"
<< "#undef TRACEPOINT_INCLUDE\n"
<< "#define TRACEPOINT_INCLUDE \"" << fileName << "\"\n\n";
stream << "#include <private/qctf_p.h>\n\n";
const QString namespaceGuard = guard + QStringLiteral("_USE_NAMESPACE");
stream << "#if !defined(" << namespaceGuard << ")\n"
<< "#define " << namespaceGuard << "\n"
<< "QT_USE_NAMESPACE\n"
<< "#endif // " << namespaceGuard << "\n\n";
stream << "TRACEPOINT_PROVIDER(" << provider.name << ");\n\n";
}
static void writeEpilogue(QTextStream &stream, const QString &fileName)
{
stream << "\n";
stream << "#endif // " << includeGuard(fileName) << "\n"
<< "#include <private/qtrace_p.h>\n";
}
static void writeWrapper(QTextStream &stream,
const Tracepoint &tracepoint, const Provider &provider)
{
const QString argList = formatFunctionSignature(tracepoint.args);
const QString paramList = formatParameterList(provider, tracepoint.args, tracepoint.fields, CTF);
const QString &name = tracepoint.name;
const QString includeGuard = QStringLiteral("TP_%1_%2").arg(provider.name).arg(name).toUpper();
/* prevents the redefinion of the inline wrapper functions
* once LTTNG recursively includes this header file
*/
stream << "\n"
<< "#ifndef " << includeGuard << "\n"
<< "#define " << includeGuard << "\n"
<< "QT_BEGIN_NAMESPACE\n"
<< "namespace QtPrivate {\n";
stream << "inline void trace_" << name << "(" << argList << ")\n"
<< "{\n"
<< " tracepoint(" << provider.name << ", " << name << paramList << ");\n"
<< "}\n";
stream << "inline void do_trace_" << name << "(" << argList << ")\n"
<< "{\n"
<< " do_tracepoint(" << provider.name << ", " << name << paramList << ");\n"
<< "}\n";
stream << "inline bool trace_" << name << "_enabled()\n"
<< "{\n"
<< " return tracepoint_enabled(" << provider.name << ", " << name << ");\n"
<< "}\n";
stream << "} // namespace QtPrivate\n"
<< "QT_END_NAMESPACE\n"
<< "#endif // " << includeGuard << "\n\n";
}
static void writeTracepoint(QTextStream &stream,
const Tracepoint &tracepoint, const QString &providerName)
{
stream << "TRACEPOINT_EVENT(\n"
<< " " << providerName << ",\n"
<< " " << tracepoint.name << ",\n";
stream << "\"";
const auto checkUnknownArgs = [](const Tracepoint &tracepoint) {
for (auto &field : tracepoint.fields) {
if (field.backendType.backendType == Tracepoint::Field::Unknown)
return true;
}
return false;
};
const auto formatType = [](const QString &type) {
QString ret = type;
if (type.endsWith(QLatin1Char('*')) || type.endsWith(QLatin1Char('&')))
ret = type.left(type.length() - 1).simplified();
if (ret.startsWith(QStringLiteral("const")))
ret = ret.right(ret.length() - 6).simplified();
return typeToName(ret);
};
int eventSize = 0;
bool variableSize = false;
if (!checkUnknownArgs(tracepoint)) {
for (int i = 0; i < tracepoint.args.size(); i++) {
auto &arg = tracepoint.args[i];
auto &field = tracepoint.fields[i];
if (i > 0) {
stream << " \\n\\\n";
stream << " ";
}
const bool array = field.arrayLen > 0;
switch (field.backendType.backendType) {
case Tracepoint::Field::Boolean: {
stream << "Boolean " << arg.name << ";";
eventSize += 8;
} break;
case Tracepoint::Field::Integer: {
if (!field.backendType.isSigned)
stream << "u";
stream << "int" << field.backendType.bits << "_t ";
if (array)
stream << arg.name << "[" << field.arrayLen << "];";
else
stream << arg.name << ";";
eventSize += field.backendType.bits * qMax(1, field.arrayLen);
} break;
case Tracepoint::Field::Pointer: {
if (QT_POINTER_SIZE == 8)
stream << "intptr64_t " << formatType(arg.type) << "_" << arg.name << ";";
else
stream << "intptr32_t " << formatType(arg.type) << "_" << arg.name << ";";
eventSize += QT_POINTER_SIZE * 8;
} break;
case Tracepoint::Field::IntegerHex: {
if (field.backendType.bits == 64)
stream << "intptr64_t " << formatType(arg.name) << ";";
else
stream << "intptr32_t " << formatType(arg.name) << ";";
eventSize += field.backendType.bits;
} break;
case Tracepoint::Field::Float: {
if (field.backendType.bits == 32)
stream << "float " << arg.name;
else
stream << "double " << arg.name;
if (array) {
stream << "[" << field.arrayLen << "];";
} else {
stream << ";";
}
eventSize += field.backendType.bits * qMax(1, field.arrayLen);
} break;
case Tracepoint::Field::String: {
stream << "string " << arg.name << ";";
eventSize += 8;
variableSize = true;
} break;
case Tracepoint::Field::QtString: {
stream << "string " << arg.name << ";";
eventSize += 8;
variableSize = true;
} break;
case Tracepoint::Field::QtByteArray:
break;
case Tracepoint::Field::QtUrl: {
stream << "string " << arg.name << ";";
eventSize += 8;
variableSize = true;
} break;
case Tracepoint::Field::QtRect: {
stream << "int32_t QRect_" << arg.name << "_x;\\n\\\n ";
stream << "int32_t QRect_" << arg.name << "_y;\\n\\\n ";
stream << "int32_t QRect_" << arg.name << "_width;\\n\\\n ";
stream << "int32_t QRect_" << arg.name << "_height;";
eventSize += 32 * 4;
} break;
case Tracepoint::Field::QtSize: {
stream << "int32_t QSize_" << arg.name << "_width;\\n\\\n ";
stream << "int32_t QSize_" << arg.name << "_height;";
eventSize += 32 * 2;
} break;
case Tracepoint::Field::Unknown:
break;
case Tracepoint::Field::EnumeratedType: {
QString type = arg.type;
type.replace(QStringLiteral("::"), QStringLiteral("_"));
stream << type << " " << arg.name << ";";
eventSize += field.backendType.bits;
variableSize = true;
} break;
case Tracepoint::Field::FlagType: {
QString type = arg.type;
type.replace(QStringLiteral("::"), QStringLiteral("_"));
stream << "uint8_t " << arg.name << "_length;\\n\\\n ";
stream << type << " " << arg.name << "[" << arg.name << "_length];";
eventSize += 16;
} break;
case Tracepoint::Field::Sequence:
panic("Unhandled sequence '%s %s", qPrintable(arg.type), qPrintable(arg.name));
break;
}
}
}
stream << "\",\n";
stream << eventSize / 8 << ", \n";
stream << (variableSize ? "true" : "false") << "\n";
stream << ")\n\n";
}
static void writeTracepoints(QTextStream &stream, const Provider &provider)
{
for (const Tracepoint &t : provider.tracepoints) {
writeTracepoint(stream, t, provider.name);
writeWrapper(stream, t, provider);
}
}
static void writeEnums(QTextStream &stream, const Provider &provider)
{
for (const auto &e : provider.enumerations) {
QString name = e.name;
name.replace(QStringLiteral("::"), QStringLiteral("_"));
stream << "TRACEPOINT_METADATA(" << provider.name << ", " << name << ", \n";
stream << "\"typealias enum : integer { size = " << e.valueSize << "; } {\\n\\\n";
for (const auto &v : e.values) {
if (v.range)
stream << v.name << " = " << v.value << " ... " << v.range << ", \\n\\\n";
else
stream << v.name << " = " << v.value << ", \\n\\\n";
}
stream << "} := " << name << ";\\n\\n\");\n\n";
}
stream << "\n";
}
static void writeFlags(QTextStream &stream, const Provider &provider)
{
for (const auto &e : provider.flags) {
QString name = e.name;
name.replace(QStringLiteral("::"), QStringLiteral("_"));
stream << "TRACEPOINT_METADATA(" << provider.name << ", " << name << ", \n";
stream << "\"typealias enum : integer { size = 8; } {\\n\\\n";
for (const auto &v : e.values) {
stream << v.name << " = " << v.value << ", \\n\\\n";
}
stream << "} := " << name << ";\\n\\n\");\n\n";
}
stream << "\n";
}
void writeCtf(QFile &file, const Provider &provider)
{
QTextStream stream(&file);
const QString fileName = QFileInfo(file.fileName()).fileName();
writePrologue(stream, fileName, provider);
writeEnums(stream, provider);
writeFlags(stream, provider);
writeTracepoints(stream, provider);
writeEpilogue(stream, fileName);
}

12
src/tools/tracegen/ctf.h Normal file
View File

@ -0,0 +1,12 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef CTF_H
#define CTF_H
struct Provider;
class QFile;
void writeCtf(QFile &device, const Provider &p);
#endif // CTF_H

View File

@ -72,12 +72,33 @@ QString formatParameterList(const Provider &provider, const QList<Tracepoint::Ar
}
return TraceEnum();
};
if (type == LTTNG) {
if (type == CTF) {
QString ret;
for (const Tracepoint::Argument &arg : args)
ret += ", "_L1 + arg.name;
for (int i = 0; i < args.size(); i++) {
const Tracepoint::Argument &arg = args[i];
const Tracepoint::Field &field = fields[i];
if (arg.arrayLen > 1) {
ret += ", trace::toByteArrayFromArray("_L1 + arg.name + ", "_L1 + QString::number(arg.arrayLen) + ") "_L1;
} else if (field.backendType.backendType == Tracepoint::Field::EnumeratedType) {
const TraceEnum &e = findEnumeration(provider.enumerations, arg.type);
QString integerType;
if (e.valueSize == 8)
integerType = QStringLiteral("quint8");
else if (e.valueSize == 16)
integerType = QStringLiteral("quint16");
else
integerType = QStringLiteral("quint32");
ret += ", trace::toByteArrayFromEnum<"_L1 + integerType + ">("_L1 + arg.name + ")"_L1;
} else if (field.backendType.backendType == Tracepoint::Field::FlagType) {
ret += ", trace::toByteArrayFromFlags("_L1 + arg.name + ")"_L1;
} else if (field.backendType.backendType == Tracepoint::Field::String) {
ret += ", trace::toByteArrayFromCString("_L1 + arg.name + ")"_L1;
} else {
ret += ", "_L1 + arg.name;
}
}
return ret;
}

View File

@ -11,7 +11,8 @@
enum ParamType {
LTTNG,
ETW
ETW,
CTF
};
QString typeToName(const QString &name);

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "provider.h"
#include "ctf.h"
#include "lttng.h"
#include "etw.h"
#include "panic.h"
@ -12,12 +13,13 @@
enum class Target
{
LTTNG,
ETW
ETW,
CTF,
};
static inline void usage(int status)
{
printf("Usage: tracegen <lttng|etw> <input file> <output file>\n");
printf("Usage: tracegen <lttng|etw|ctf> <input file> <output file>\n");
exit(status);
}
@ -34,6 +36,8 @@ static void parseArgs(int argc, char *argv[], Target *target, QString *inFile, Q
*target = Target::LTTNG;
} else if (qstrcmp(targetString, "etw") == 0) {
*target = Target::ETW;
} else if (qstrcmp(targetString, "ctf") == 0) {
*target = Target::CTF;
} else {
fprintf(stderr, "Invalid target: %s\n", targetString);
usage(EXIT_FAILURE);
@ -61,6 +65,9 @@ int main(int argc, char *argv[])
}
switch (target) {
case Target::CTF:
writeCtf(out, p);
break;
case Target::LTTNG:
writeLttng(out, p);
break;