Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qctflib.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3#define BUILD_LIBRARY
4#include <qstring.h>
5#include <qthread.h>
6#include <stdio.h>
7#include <qjsondocument.h>
8#include <qjsonarray.h>
9#include <qjsonobject.h>
10#include <qfileinfo.h>
11#include <qrect.h>
12#include <qsize.h>
13#include <qmetaobject.h>
14#include <qendian.h>
15#include <qplatformdefs.h>
16#include "qctflib_p.h"
17
18#if QT_CONFIG(cxx17_filesystem)
19#include <filesystem>
20#endif
21
23
24using namespace Qt::StringLiterals;
25
26Q_LOGGING_CATEGORY(lcDebugTrace, "qt.core.ctf", QtWarningMsg)
27
28static const size_t packetHeaderSize = 24 + 6 * 8 + 4;
29static const size_t packetSize = 4096;
30
31static const char traceMetadataTemplate[] =
32#include "metadata_template.h"
33;
34static const size_t traceMetadataSize = sizeof(traceMetadataTemplate);
35
36static inline QString allLiteral() { return QStringLiteral("all"); }
37static inline QString defaultLiteral() { return QStringLiteral("default"); }
38
39
40template <typename T>
41static QByteArray &operator<<(QByteArray &arr, T val)
42{
43 static_assert(std::is_arithmetic_v<T>);
44 arr.append(reinterpret_cast<char *>(&val), sizeof(val));
45 return arr;
46}
47
48static FILE *openFile(const QString &filename, const QString &mode)
49{
50#ifdef Q_OS_WINDOWS
51 return _wfopen(qUtf16Printable(filename), qUtf16Printable(mode));
52#else
53 return fopen(qPrintable(filename), qPrintable(mode));
54#endif
55}
56
57QCtfLibImpl *QCtfLibImpl::s_instance = nullptr;
58
59QCtfLib *QCtfLibImpl::instance()
60{
61 if (!s_instance)
62 s_instance = new QCtfLibImpl();
63 return s_instance;
64}
65
67{
68 delete s_instance;
69 s_instance = nullptr;
70}
71
73{
74 m_sessionChanged = true;
75}
76
77void QCtfLibImpl::handleStatusChange(QCtfServer::ServerStatus status)
78{
79 switch (status) {
80 case QCtfServer::Error: {
81 m_serverClosed = true;
82 } break;
83 default:
84 break;
85 }
86}
87
88void QCtfLibImpl::buildMetadata()
89{
90 const QString mhn = QSysInfo::machineHostName();
91 QString metadata = QString::fromUtf8(traceMetadataTemplate, traceMetadataSize);
92 metadata.replace(QStringLiteral("$TRACE_UUID"), s_TraceUuid.toString(QUuid::WithoutBraces));
93 metadata.replace(QStringLiteral("$ARC_BIT_WIDTH"), QString::number(Q_PROCESSOR_WORDSIZE * 8));
94 metadata.replace(QStringLiteral("$SESSION_NAME"), m_session.name);
95 metadata.replace(QStringLiteral("$CREATION_TIME"), m_datetime.toString(Qt::ISODate));
96 metadata.replace(QStringLiteral("$HOST_NAME"), mhn);
97 metadata.replace(QStringLiteral("$CLOCK_FREQUENCY"), QStringLiteral("1000000000"));
98 metadata.replace(QStringLiteral("$CLOCK_NAME"), QStringLiteral("monotonic"));
99 metadata.replace(QStringLiteral("$CLOCK_TYPE"), QStringLiteral("Monotonic clock"));
100 metadata.replace(QStringLiteral("$CLOCK_OFFSET"), QString::number(m_datetime.toMSecsSinceEpoch() * 1000000));
101 metadata.replace(QStringLiteral("$ENDIANNESS"), QSysInfo::ByteOrder == QSysInfo::BigEndian ? u"be"_s : u"le"_s);
102 writeMetadata(metadata, true);
103}
104
106{
107 QString location = qEnvironmentVariable("QTRACE_LOCATION");
108 if (location.isEmpty()) {
109 qCInfo(lcDebugTrace) << "QTRACE_LOCATION not set";
110 return;
111 }
112
113 if (location.startsWith(u"tcp")) {
114 QUrl url(location);
115 m_server.reset(new QCtfServer());
116 m_server->setCallback(this);
117 m_server->setHost(url.host());
118 m_server->setPort(url.port());
119 m_server->startServer();
120 m_streaming = true;
121 m_session.tracepoints.append(allLiteral());
122 m_session.name = defaultLiteral();
123 } else {
124#if !QT_CONFIG(cxx17_filesystem)
125 qCWarning(lcDebugTrace) << "Unable to use filesystem";
126 return;
127#endif
128 // Check if the location is writable
129 if (QT_ACCESS(qPrintable(location), W_OK) != 0) {
130 qCWarning(lcDebugTrace) << "Unable to write to location";
131 return;
132 }
133 const QString filename = location + u"/session.json";
134 FILE *file = openFile(qPrintable(filename), "rb"_L1);
135 if (!file) {
136 qCWarning(lcDebugTrace) << "unable to open session file: "
137 << filename << ", " << qt_error_string();
138 m_location = location;
139 m_session.tracepoints.append(allLiteral());
140 m_session.name = defaultLiteral();
141 } else {
142 QT_STATBUF stat;
143 if (QT_FSTAT(QT_FILENO(file), &stat) != 0) {
144 qCWarning(lcDebugTrace) << "Unable to stat session file, " << qt_error_string();
145 return;
146 }
147 qsizetype filesize = qMin(stat.st_size, std::numeric_limits<qsizetype>::max());
148 QByteArray data(filesize, Qt::Uninitialized);
149 qsizetype size = static_cast<qsizetype>(fread(data.data(), 1, filesize, file));
150 fclose(file);
151 if (size != filesize)
152 return;
153 QJsonDocument json(QJsonDocument::fromJson(data));
154 QJsonObject obj = json.object();
155 bool valid = false;
156 if (!obj.isEmpty()) {
157 const auto it = obj.begin();
158 if (it.value().isArray()) {
159 m_session.name = it.key();
160 for (auto var : it.value().toArray())
161 m_session.tracepoints.append(var.toString());
162 valid = true;
163 }
164 }
165 if (!valid) {
166 qCWarning(lcDebugTrace) << "Session file is not valid";
167 m_session.tracepoints.append(allLiteral());
168 m_session.name = defaultLiteral();
169 }
170 m_location = location + u"/ust";
171#if QT_CONFIG(cxx17_filesystem)
172 std::filesystem::create_directory(qPrintable(m_location), qPrintable(location));
173#endif
174 }
175 clearLocation();
176 }
177
178 m_session.all = m_session.tracepoints.contains(allLiteral());
179 // Get datetime to when the timer was started to store the offset to epoch time for the traces
180 m_datetime = QDateTime::currentDateTime().toUTC();
181 m_timer.start();
182 if (!m_streaming)
183 buildMetadata();
184}
185
186void QCtfLibImpl::clearLocation()
187{
188#if QT_CONFIG(cxx17_filesystem)
189 const std::filesystem::path location{m_location.toStdU16String()};
190 for (auto const& dirEntry : std::filesystem::directory_iterator{location})
191 {
192 const auto path = dirEntry.path();
193#if __cplusplus > 201703L
194 if (dirEntry.is_regular_file()
195 && path.filename().wstring().starts_with(std::wstring_view(L"channel_"))
196 && !path.has_extension()) {
197#else
198 const auto strview = std::wstring_view(L"channel_");
199 const auto sub = path.filename().wstring().substr(0, strview.length());
200 if (dirEntry.is_regular_file() && sub.compare(strview) == 0
201 && !path.has_extension()) {
202#endif
203 if (!std::filesystem::remove(path)) {
204 qCInfo(lcDebugTrace) << "Unable to clear output location.";
205 break;
206 }
207 }
208 }
209#endif
210}
211
212void QCtfLibImpl::writeMetadata(const QString &metadata, bool overwrite)
213{
214 if (m_streaming) {
215 auto mt = metadata.toUtf8();
216 mt.resize(mt.size() - 1);
217 m_server->bufferData(QStringLiteral("metadata"), mt, overwrite);
218 } else {
219 FILE *file = nullptr;
220 file = openFile(qPrintable(m_location + "/metadata"_L1), overwrite ? "w+b"_L1: "ab"_L1);
221 if (!file)
222 return;
223
224 if (!overwrite)
225 fputs("\n", file);
226
227 // data contains zero at the end, hence size - 1.
228 const QByteArray data = metadata.toUtf8();
229 fwrite(data.data(), data.size() - 1, 1, file);
230 fclose(file);
231 }
232}
233
234void QCtfLibImpl::writeCtfPacket(QCtfLibImpl::Channel &ch)
235{
236 FILE *file = nullptr;
237 if (!m_streaming)
238 file = openFile(ch.channelName, "ab"_L1);
239 if (file || m_streaming) {
240 /* Each packet contains header and context, which are defined in the metadata.txt */
241 QByteArray packet;
242 packet << s_CtfHeaderMagic;
243 packet.append(QByteArrayView(s_TraceUuid.toBytes()));
244
245 packet << quint32(0);
246 packet << ch.minTimestamp;
247 packet << ch.maxTimestamp;
248 packet << quint64(ch.data.size() + packetHeaderSize + ch.threadNameLength) * 8u;
249 packet << quint64(packetSize) * 8u;
250 packet << ch.seqnumber++;
251 packet << quint64(0);
252 packet << ch.threadIndex;
253 if (ch.threadName.size())
254 packet.append(ch.threadName);
255 packet << (char)0;
256
257 Q_ASSERT(ch.data.size() + packetHeaderSize + ch.threadNameLength <= packetSize);
258 Q_ASSERT(packet.size() == qsizetype(packetHeaderSize + ch.threadNameLength));
259 if (m_streaming) {
260 ch.data.resize(packetSize - packet.size(), 0);
261 packet += ch.data;
262 m_server->bufferData(QString::fromLatin1(ch.channelName), packet, false);
263 } else {
264 fwrite(packet.data(), packet.size(), 1, file);
265 ch.data.resize(packetSize - packet.size(), 0);
266 fwrite(ch.data.data(), ch.data.size(), 1, file);
267 fclose(file);
268 }
269 }
270}
271
272QCtfLibImpl::Channel::~Channel()
273{
274 impl->writeCtfPacket(*this);
275 impl->removeChannel(this);
276}
277
279{
280 if (!m_server.isNull())
281 m_server->stopServer();
282 qDeleteAll(m_eventPrivs);
283}
284
285void QCtfLibImpl::removeChannel(Channel *ch)
286{
287 const QMutexLocker lock(&m_mutex);
288 m_channels.removeOne(ch);
289}
290
291bool QCtfLibImpl::tracepointEnabled(const QCtfTracePointEvent &point)
292{
293 if (m_sessionChanged) {
294 const QMutexLocker lock(&m_mutex);
295 buildMetadata();
296 m_session.name = m_server->sessionName();
297 m_session.tracepoints = m_server->sessionTracepoints().split(';');
298 m_session.all = m_session.tracepoints.contains(allLiteral());
299 m_sessionChanged = false;
300 for (const auto &meta : m_additionalMetadata)
301 writeMetadata(meta->metadata);
302 for (auto *priv : m_eventPrivs)
303 writeMetadata(priv->metadata);
304 quint64 timestamp = m_timer.nsecsElapsed();
305 for (auto *ch : m_channels) {
306 writeCtfPacket(*ch);
307 ch->data.clear();
308 ch->minTimestamp = ch->maxTimestamp = timestamp;
309 }
310 }
311 if (m_streaming && (m_serverClosed || (!m_server->bufferOnIdle() && m_server->status() == QCtfServer::Idle)))
312 return false;
313 return m_session.all || m_session.tracepoints.contains(point.provider.provider);
314}
315
316static QString toMetadata(const QString &provider, const QString &name, const QString &metadata, quint32 eventId)
317{
318/*
319 generates event structure:
320event {
321 name = provider:tracepoint_name;
322 id = eventId;
323 stream_id = 0;
324 loglevel = 13;
325 fields := struct {
326 metadata
327 };
328};
329*/
330 return QStringView(u"event {\n name = \"") + provider + QLatin1Char(':') + name + u"\";\n"
331 + u" id = " + QString::number(eventId) + u";\n"
332 + u" stream_id = 0;\n loglevel = 13;\n fields := struct {\n "
333 + metadata + u"\n };\n};\n";
334}
335
336QCtfTracePointPrivate *QCtfLibImpl::initializeTracepoint(const QCtfTracePointEvent &point)
337{
338 QMutexLocker lock(&m_mutex);
339 QCtfTracePointPrivate *priv = point.d;
340 if (!point.d) {
341 if (const auto &it = m_eventPrivs.find(point.eventName); it != m_eventPrivs.end()) {
342 priv = *it;
343 } else {
344 priv = new QCtfTracePointPrivate();
345 m_eventPrivs.insert(point.eventName, priv);
346 priv->id = eventId();
347 priv->metadata = toMetadata(point.provider.provider, point.eventName, point.metadata, priv->id);
348 }
349 }
350 return priv;
351}
352
353void QCtfLibImpl::doTracepoint(const QCtfTracePointEvent &point, const QByteArray &arr)
354{
355 QCtfTracePointPrivate *priv = point.d;
356 quint64 timestamp = 0;
357 QThread *thread = nullptr;
358 if (m_streaming && m_serverClosed)
359 return;
360 {
361 QMutexLocker lock(&m_mutex);
362 if (!priv->metadataWritten) {
363 priv->metadataWritten = true;
364 auto providerMetadata = point.provider.metadata;
365 while (providerMetadata) {
366 registerMetadata(*providerMetadata);
367 providerMetadata = providerMetadata->next;
368 }
369 if (m_newAdditionalMetadata.size()) {
370 for (const QString &name : m_newAdditionalMetadata)
371 writeMetadata(m_additionalMetadata[name]->metadata);
372 m_newAdditionalMetadata.clear();
373 }
374 writeMetadata(priv->metadata);
375 }
376 timestamp = m_timer.nsecsElapsed();
377 }
378 if (arr.size() != point.size) {
379 if (arr.size() < point.size)
380 return;
381 if (arr.size() > point.size && !point.variableSize && !point.metadata.isEmpty())
382 return;
383 }
384
385 thread = QThread::currentThread();
386 if (thread == nullptr)
387 return;
388
389 Channel &ch = m_threadData.localData();
390
391 if (ch.channelName[0] == 0) {
392 ch.impl = this;
393 m_channels.append(&ch);
394 m_threadIndices.insert(thread, m_threadIndices.size());
395 sprintf(ch.channelName, "%s/channel_%d", qPrintable(m_location), m_threadIndices[thread]);
396 ch.minTimestamp = ch.maxTimestamp = timestamp;
397 ch.thread = thread;
398 ch.threadIndex = m_threadIndices[thread];
399 ch.threadName = thread->objectName().toUtf8();
400 if (ch.threadName.isEmpty()) {
401 const QMetaObject *obj = thread->metaObject();
402 ch.threadName = QByteArray(obj->className());
403 }
404 ch.threadNameLength = ch.threadName.size() + 1;
405 }
406 if (ch.locked)
407 return;
408 Q_ASSERT(ch.thread == thread);
409 ch.locked = true;
410
411 QByteArray event;
412 event << priv->id << timestamp;
413 if (!point.metadata.isEmpty())
414 event.append(arr);
415
416 if (ch.threadNameLength + ch.data.size() + event.size() + packetHeaderSize >= packetSize) {
417 writeCtfPacket(ch);
418 ch.data = event;
419 ch.minTimestamp = ch.maxTimestamp = timestamp;
420 } else {
421 ch.data.append(event);
422 }
423
424 ch.locked = false;
425 ch.maxTimestamp = timestamp;
426}
427
429{
430 return !m_session.name.isEmpty();
431}
432
434{
435 return m_eventId++;
436}
437
438void QCtfLibImpl::registerMetadata(const QCtfTraceMetadata &metadata)
439{
440 if (m_additionalMetadata.contains(metadata.name))
441 return;
442
443 m_additionalMetadata.insert(metadata.name, &metadata);
444 m_newAdditionalMetadata.insert(metadata.name);
445}
446
447QT_END_NAMESPACE
void handleStatusChange(QCtfServer::ServerStatus status) override
Definition qctflib.cpp:77
int eventId()
Definition qctflib.cpp:433
static void cleanup()
Definition qctflib.cpp:66
bool sessionEnabled() override
Definition qctflib.cpp:428
void registerMetadata(const QCtfTraceMetadata &metadata)
Definition qctflib.cpp:438
bool tracepointEnabled(const QCtfTracePointEvent &point) override
Definition qctflib.cpp:291
QCtfTracePointPrivate * initializeTracepoint(const QCtfTracePointEvent &point) override
Definition qctflib.cpp:336
void handleSessionChange() override
Definition qctflib.cpp:72
void doTracepoint(const QCtfTracePointEvent &point, const QByteArray &arr) override
Definition qctflib.cpp:353
\inmodule QtCore\reentrant
\inmodule QtCore
Definition qmutex.h:332
Combined button and popup list for selecting options.
const QString & asString(const QString &s)
Definition qstring.h:1661
static const size_t traceMetadataSize
Definition qctflib.cpp:34
static QString toMetadata(const QString &provider, const QString &name, const QString &metadata, quint32 eventId)
Definition qctflib.cpp:316
static FILE * openFile(const QString &filename, const QString &mode)
Definition qctflib.cpp:48
static const size_t packetSize
Definition qctflib.cpp:29
static QString defaultLiteral()
Definition qctflib.cpp:37
static QString allLiteral()
Definition qctflib.cpp:36
static const char traceMetadataTemplate[]
Definition qctflib.cpp:31
static const size_t packetHeaderSize
Definition qctflib.cpp:28
#define Q_LOGGING_CATEGORY(name,...)
#define qCInfo(category,...)
#define qCWarning(category,...)
#define qPrintable(string)
Definition qstring.h:1666