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:
parent
a37a59eea8
commit
e3458aac64
@ -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 "")
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
23
src/corelib/tracing/CMakeLists.txt
Normal file
23
src/corelib/tracing/CMakeLists.txt
Normal 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
|
||||
)
|
||||
|
77
src/corelib/tracing/metadata_template.txt
Normal file
77
src/corelib/tracing/metadata_template.txt
Normal 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;
|
||||
};
|
119
src/corelib/tracing/qctf.cpp
Normal file
119
src/corelib/tracing/qctf.cpp
Normal 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
|
225
src/corelib/tracing/qctf_p.h
Normal file
225
src/corelib/tracing/qctf_p.h
Normal 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
|
303
src/corelib/tracing/qctflib.cpp
Normal file
303
src/corelib/tracing/qctflib.cpp
Normal 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
|
107
src/corelib/tracing/qctflib_p.h
Normal file
107
src/corelib/tracing/qctflib_p.h
Normal 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
|
44
src/corelib/tracing/qctfplugin.cpp
Normal file
44
src/corelib/tracing/qctfplugin.cpp
Normal 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
|
38
src/corelib/tracing/qctfplugin_p.h
Normal file
38
src/corelib/tracing/qctfplugin_p.h
Normal 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
|
3
src/corelib/tracing/trace.json
Normal file
3
src/corelib/tracing/trace.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"Keys": [ "CTF" ]
|
||||
}
|
@ -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
278
src/tools/tracegen/ctf.cpp
Normal 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
12
src/tools/tracegen/ctf.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,8 @@
|
||||
|
||||
enum ParamType {
|
||||
LTTNG,
|
||||
ETW
|
||||
ETW,
|
||||
CTF
|
||||
};
|
||||
|
||||
QString typeToName(const QString &name);
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user