diff options
Diffstat (limited to 'src/plugins')
-rw-r--r-- | src/plugins/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/plugins/tracing/CMakeLists.txt | 23 | ||||
-rw-r--r-- | src/plugins/tracing/metadata_template.txt | 77 | ||||
-rw-r--r-- | src/plugins/tracing/qctflib.cpp | 327 | ||||
-rw-r--r-- | src/plugins/tracing/qctflib_p.h | 108 | ||||
-rw-r--r-- | src/plugins/tracing/qctfplugin.cpp | 58 | ||||
-rw-r--r-- | src/plugins/tracing/qctfplugin_p.h | 28 | ||||
-rw-r--r-- | src/plugins/tracing/trace.json | 3 |
8 files changed, 627 insertions, 0 deletions
diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 308aa800bd..dc7c45c2d8 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -25,3 +25,6 @@ if (TARGET Qt::Network) add_subdirectory(networkinformation) add_subdirectory(tls) endif() +if (QT_FEATURE_ctf AND QT_FEATURE_library) + add_subdirectory(tracing) +endif() diff --git a/src/plugins/tracing/CMakeLists.txt b/src/plugins/tracing/CMakeLists.txt new file mode 100644 index 0000000000..9840b59ecd --- /dev/null +++ b/src/plugins/tracing/CMakeLists.txt @@ -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 Qt6::CorePrivate +) + diff --git a/src/plugins/tracing/metadata_template.txt b/src/plugins/tracing/metadata_template.txt new file mode 100644 index 0000000000..5f27e79da5 --- /dev/null +++ b/src/plugins/tracing/metadata_template.txt @@ -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; +}; diff --git a/src/plugins/tracing/qctflib.cpp b/src/plugins/tracing/qctflib.cpp new file mode 100644 index 0000000000..e0f4db489a --- /dev/null +++ b/src/plugins/tracing/qctflib.cpp @@ -0,0 +1,327 @@ +// 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" +#include <filesystem> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +Q_LOGGING_CATEGORY(lcDebugTrace, "qt.core.ctf"); + +static const size_t packetHeaderSize = 24 + 6 * 8 + 4; +static const size_t packetSize = 4096; + +static const char traceMetadataTemplate[] = +#include "metadata_template.h" +; +static const size_t traceMetadataSize = sizeof(traceMetadataTemplate); + +template <typename T> +QByteArray &operator<<(QByteArray &arr, T val) +{ + static_assert(std::is_arithmetic_v<T>); + 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; +} + +void QCtfLibImpl::cleanup() +{ + if (s_instance) + delete s_instance; +} + +QCtfLibImpl::QCtfLibImpl() +{ + QString location = QString::fromUtf8(qgetenv("QTRACE_LOCATION")); + if (location.isEmpty()) { + qCWarning (lcDebugTrace) << "QTRACE_LOCATION not set"; + return; + } + + // Check if the location is writable + FILE *file = nullptr; + file = fopen(qPrintable(location + "/metadata"_L1), "w+b"); + if (!file) { + qCWarning (lcDebugTrace) << "Unable to write to location"; + return; + } + fclose(file); + + const QString filename = location + QStringLiteral("/session.json"); + file = fopen(qPrintable(filename), "rb"); + if (!file) { + qCWarning (lcDebugTrace) << "unable to open session file: " << filename; + m_location = location; + m_session.tracepoints.append(QStringLiteral("all")); + m_session.name = QStringLiteral("default"); + } else { + fseek(file, 0, SEEK_END); + long pos = ftell(file); + fseek(file, 0, SEEK_SET); + QByteArray data(pos, Qt::Uninitialized); + 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"); + std::filesystem::create_directory(qPrintable(m_location), qPrintable(location)); + } + m_session.all = m_session.tracepoints.contains(QStringLiteral("all")); + + auto datetime = QDateTime::currentDateTime(); + QString mhn = QSysInfo::machineHostName(); + QString metadata = QString::fromUtf8(traceMetadataTemplate, traceMetadataSize); + 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, true); + + m_timer.start(); +} + +void QCtfLibImpl::writeMetadata(const QString &metadata, bool overwrite) +{ + FILE *file = nullptr; + file = fopen(qPrintable(m_location + "/metadata"_L1), overwrite ? "w+b": "ab"); + if (!file) + return; + + if (!overwrite) + fputs("\n", file); + + // data contains zero at the end, hence size - 1. + const QByteArray data = metadata.toUtf8(); + 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() + packetHeaderSize + ch.threadNameLength) * 8u; + packet << quint64(packetSize) * 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() + packetHeaderSize + ch.threadNameLength <= packetSize); + Q_ASSERT(packet.size() == qsizetype(packetHeaderSize + ch.threadNameLength)); + fwrite(packet.data(), packet.size(), 1, file); + ch.data.resize(packetSize - 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); + m_newAdditionalMetadata.clear(); + } + writeMetadata(priv->metadata); + } + 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"); + if (f) + 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() + packetHeaderSize >= packetSize) { + 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 diff --git a/src/plugins/tracing/qctflib_p.h b/src/plugins/tracing/qctflib_p.h new file mode 100644 index 0000000000..081dda1d04 --- /dev/null +++ b/src/plugins/tracing/qctflib_p.h @@ -0,0 +1,108 @@ +// 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 <private/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(); + static void cleanup(); +private: + static QCtfLibImpl *s_instance; + QHash<QString, QCtfTracePointPrivate *> m_eventPrivs; + void updateMetadata(const QCtfTracePointEvent &point); + void writeMetadata(const QString &metadata, 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 diff --git a/src/plugins/tracing/qctfplugin.cpp b/src/plugins/tracing/qctfplugin.cpp new file mode 100644 index 0000000000..8f2245bb28 --- /dev/null +++ b/src/plugins/tracing/qctfplugin.cpp @@ -0,0 +1,58 @@ +// 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 QCtfLib +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QCtfLib" FILE "trace.json") + Q_INTERFACES(QCtfLib) + +public: + QCtfTracePlugin() + { + + } + ~QCtfTracePlugin() + { + m_cleanup = true; + QCtfLibImpl::cleanup(); + } + + bool tracepointEnabled(const QCtfTracePointEvent &point) override + { + if (m_cleanup) + return false; + return QCtfLibImpl::instance()->tracepointEnabled(point); + } + void doTracepoint(const QCtfTracePointEvent &point, const QByteArray &arr) override + { + if (m_cleanup) + return; + QCtfLibImpl::instance()->doTracepoint(point, arr); + } + bool sessionEnabled() override + { + if (m_cleanup) + return false; + return QCtfLibImpl::instance()->sessionEnabled(); + } + QCtfTracePointPrivate *initializeTracepoint(const QCtfTracePointEvent &point) override + { + if (m_cleanup) + return nullptr; + return QCtfLibImpl::instance()->initializeTracepoint(point); + } +private: + bool m_cleanup = false; +}; + +#include "qctfplugin.moc" + +QT_END_NAMESPACE diff --git a/src/plugins/tracing/qctfplugin_p.h b/src/plugins/tracing/qctfplugin_p.h new file mode 100644 index 0000000000..987c4d925f --- /dev/null +++ b/src/plugins/tracing/qctfplugin_p.h @@ -0,0 +1,28 @@ +// 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 <private/qctf_p.h> +#include <qplugin.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_INTERFACE(QCtfLib, "org.qt-project.Qt.QCtfLib") + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/tracing/trace.json b/src/plugins/tracing/trace.json new file mode 100644 index 0000000000..1b991122d4 --- /dev/null +++ b/src/plugins/tracing/trace.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "CTF" ] +} |