diff options
Diffstat (limited to 'src/plugins/tracing/qctflib.cpp')
-rw-r--r-- | src/plugins/tracing/qctflib.cpp | 300 |
1 files changed, 212 insertions, 88 deletions
diff --git a/src/plugins/tracing/qctflib.cpp b/src/plugins/tracing/qctflib.cpp index cbbefa1511..fe3946d27c 100644 --- a/src/plugins/tracing/qctflib.cpp +++ b/src/plugins/tracing/qctflib.cpp @@ -14,7 +14,10 @@ #include <qendian.h> #include <qplatformdefs.h> #include "qctflib_p.h" + +#if QT_CONFIG(cxx17_filesystem) #include <filesystem> +#endif QT_BEGIN_NAMESPACE @@ -30,14 +33,27 @@ static const char traceMetadataTemplate[] = ; static const size_t traceMetadataSize = sizeof(traceMetadataTemplate); +static inline QString allLiteral() { return QStringLiteral("all"); } +static inline QString defaultLiteral() { return QStringLiteral("default"); } + + template <typename T> -QByteArray &operator<<(QByteArray &arr, T val) +static QByteArray &operator<<(QByteArray &arr, T val) { static_assert(std::is_arithmetic_v<T>); - arr.append((char *)&val, sizeof(val)); + arr.append(reinterpret_cast<char *>(&val), sizeof(val)); return arr; } +static FILE *openFile(const QString &filename, const QString &mode) +{ +#ifdef Q_OS_WINDOWS + return _wfopen(qUtf16Printable(filename), qUtf16Printable(mode)); +#else + return fopen(qPrintable(filename), qPrintable(mode)); +#endif +} + QCtfLibImpl *QCtfLibImpl::s_instance = nullptr; QCtfLib *QCtfLibImpl::instance() @@ -49,102 +65,178 @@ QCtfLib *QCtfLibImpl::instance() void QCtfLibImpl::cleanup() { - if (s_instance) - delete s_instance; + delete s_instance; s_instance = nullptr; } -QCtfLibImpl::QCtfLibImpl() +void QCtfLibImpl::handleSessionChange() { - QString location = QString::fromUtf8(qgetenv("QTRACE_LOCATION")); - if (location.isEmpty()) { - qCInfo (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); - qsizetype pos = ftell(file); - fseek(file, 0, SEEK_SET); - QByteArray data(pos, Qt::Uninitialized); - qsizetype size = (qsizetype)fread(data.data(), 1, pos, file); - fclose(file); - if (size != pos) - 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(); - const QJsonArray arr = value.toArray(); - for (auto var : arr) - m_session.tracepoints.append(var.toString()); + m_sessionChanged = true; +} - m_location = location + QStringLiteral("/ust"); - std::filesystem::create_directory(qPrintable(m_location), qPrintable(location)); +void QCtfLibImpl::handleStatusChange(QCtfServer::ServerStatus status) +{ + switch (status) { + case QCtfServer::Error: { + m_serverClosed = true; + } break; + default: + break; } - m_session.all = m_session.tracepoints.contains(QStringLiteral("all")); +} - auto datetime = QDateTime::currentDateTime().toUTC(); +void QCtfLibImpl::buildMetadata() +{ const 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(Qt::ISODate)); + metadata.replace(QStringLiteral("$CREATION_TIME"), m_datetime.toString(Qt::ISODate)); metadata.replace(QStringLiteral("$HOST_NAME"), mhn); metadata.replace(QStringLiteral("$CLOCK_FREQUENCY"), QStringLiteral("1000000000")); metadata.replace(QStringLiteral("$CLOCK_NAME"), QStringLiteral("monotonic")); metadata.replace(QStringLiteral("$CLOCK_TYPE"), QStringLiteral("Monotonic 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 + metadata.replace(QStringLiteral("$CLOCK_OFFSET"), QString::number(m_datetime.toMSecsSinceEpoch() * 1000000)); + metadata.replace(QStringLiteral("$ENDIANNESS"), QSysInfo::ByteOrder == QSysInfo::BigEndian ? u"be"_s : u"le"_s); writeMetadata(metadata, true); +} + +QCtfLibImpl::QCtfLibImpl() +{ + QString location = qEnvironmentVariable("QTRACE_LOCATION"); + if (location.isEmpty()) { + qCInfo(lcDebugTrace) << "QTRACE_LOCATION not set"; + return; + } + + if (location.startsWith(u"tcp")) { + QUrl url(location); + m_server.reset(new QCtfServer()); + m_server->setCallback(this); + m_server->setHost(url.host()); + m_server->setPort(url.port()); + m_server->startServer(); + m_streaming = true; + m_session.tracepoints.append(allLiteral()); + m_session.name = defaultLiteral(); + } else { +#if !QT_CONFIG(cxx17_filesystem) + qCWarning(lcDebugTrace) << "Unable to use filesystem"; + return; +#endif + // Check if the location is writable + if (QT_ACCESS(qPrintable(location), W_OK) != 0) { + qCWarning(lcDebugTrace) << "Unable to write to location"; + return; + } + const QString filename = location + u"/session.json"; + FILE *file = openFile(qPrintable(filename), "rb"_L1); + if (!file) { + qCWarning(lcDebugTrace) << "unable to open session file: " + << filename << ", " << qt_error_string(); + m_location = location; + m_session.tracepoints.append(allLiteral()); + m_session.name = defaultLiteral(); + } else { + QT_STATBUF stat; + if (QT_FSTAT(QT_FILENO(file), &stat) != 0) { + qCWarning(lcDebugTrace) << "Unable to stat session file, " << qt_error_string(); + return; + } + qsizetype filesize = qMin(stat.st_size, std::numeric_limits<qsizetype>::max()); + QByteArray data(filesize, Qt::Uninitialized); + qsizetype size = static_cast<qsizetype>(fread(data.data(), 1, filesize, file)); + fclose(file); + if (size != filesize) + return; + QJsonDocument json(QJsonDocument::fromJson(data)); + QJsonObject obj = json.object(); + bool valid = false; + if (!obj.isEmpty()) { + const auto it = obj.begin(); + if (it.value().isArray()) { + m_session.name = it.key(); + for (auto var : it.value().toArray()) + m_session.tracepoints.append(var.toString()); + valid = true; + } + } + if (!valid) { + qCWarning(lcDebugTrace) << "Session file is not valid"; + m_session.tracepoints.append(allLiteral()); + m_session.name = defaultLiteral(); + } + m_location = location + u"/ust"; +#if QT_CONFIG(cxx17_filesystem) + std::filesystem::create_directory(qPrintable(m_location), qPrintable(location)); +#endif + } + clearLocation(); + } + + m_session.all = m_session.tracepoints.contains(allLiteral()); + // Get datetime to when the timer was started to store the offset to epoch time for the traces + m_datetime = QDateTime::currentDateTime().toUTC(); m_timer.start(); + if (!m_streaming) + buildMetadata(); +} + +void QCtfLibImpl::clearLocation() +{ +#if QT_CONFIG(cxx17_filesystem) + const std::filesystem::path location{qUtf16Printable(m_location)}; + for (auto const& dirEntry : std::filesystem::directory_iterator{location}) + { + const auto path = dirEntry.path(); +#if __cplusplus > 201703L + if (dirEntry.is_regular_file() + && path.filename().wstring().starts_with(std::wstring_view(L"channel_")) + && !path.has_extension()) { +#else + const auto strview = std::wstring_view(L"channel_"); + const auto sub = path.filename().wstring().substr(0, strview.length()); + if (dirEntry.is_regular_file() && sub.compare(strview) == 0 + && !path.has_extension()) { +#endif + if (!std::filesystem::remove(path)) { + qCInfo(lcDebugTrace) << "Unable to clear output location."; + break; + } + } + } +#endif } 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 (m_streaming) { + auto mt = metadata.toUtf8(); + mt.resize(mt.size() - 1); + m_server->bufferData(QStringLiteral("metadata"), mt, overwrite); + } else { + FILE *file = nullptr; + file = openFile(qPrintable(m_location + "/metadata"_L1), overwrite ? "w+b"_L1: "ab"_L1); + if (!file) + return; - if (!overwrite) - fputs("\n", file); + 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); + // 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) { + if (!m_streaming) + file = openFile(ch.channelName, "ab"_L1); + if (file || m_streaming) { /* Each packet contains header and context, which are defined in the metadata.txt */ QByteArray packet; packet << s_CtfHeaderMagic; @@ -164,27 +256,61 @@ void QCtfLibImpl::writeCtfPacket(QCtfLibImpl::Channel &ch) 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); + if (m_streaming) { + ch.data.resize(packetSize - packet.size(), 0); + packet += ch.data; + m_server->bufferData(QString::fromLatin1(ch.channelName), packet, false); + } else { + 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::Channel::~Channel() +{ + impl->writeCtfPacket(*this); + impl->removeChannel(this); +} + QCtfLibImpl::~QCtfLibImpl() { + if (!m_server.isNull()) + m_server->stopServer(); qDeleteAll(m_eventPrivs); } -bool QCtfLibImpl::tracepointEnabled(const QCtfTracePointEvent &point) +void QCtfLibImpl::removeChannel(Channel *ch) { - return m_session.all || m_session.tracepoints.contains(point.provider.provider); + const QMutexLocker lock(&m_mutex); + m_channels.removeOne(ch); } -QCtfLibImpl::Channel::~Channel() +bool QCtfLibImpl::tracepointEnabled(const QCtfTracePointEvent &point) { - if (data.size()) - QCtfLibImpl::writeCtfPacket(*this); + if (m_sessionChanged) { + const QMutexLocker lock(&m_mutex); + buildMetadata(); + m_session.name = m_server->sessionName(); + m_session.tracepoints = m_server->sessionTracepoints().split(';'); + m_session.all = m_session.tracepoints.contains(allLiteral()); + m_sessionChanged = false; + for (const auto &meta : m_additionalMetadata) + writeMetadata(meta->metadata); + for (auto *priv : m_eventPrivs) + writeMetadata(priv->metadata); + quint64 timestamp = m_timer.nsecsElapsed(); + for (auto *ch : m_channels) { + writeCtfPacket(*ch); + ch->data.clear(); + ch->minTimestamp = ch->maxTimestamp = timestamp; + } + } + if (m_streaming && (m_serverClosed || (!m_server->bufferOnIdle() && m_server->status() == QCtfServer::Idle))) + return false; + return m_session.all || m_session.tracepoints.contains(point.provider.provider); } static QString toMetadata(const QString &provider, const QString &name, const QString &metadata, quint32 eventId) @@ -201,12 +327,10 @@ event { }; }; */ - 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; + return QStringView(u"event {\n name = \"") + provider + QLatin1Char(':') + name + u"\";\n" + + u" id = " + QString::number(eventId) + u";\n" + + u" stream_id = 0;\n loglevel = 13;\n fields := struct {\n " + + metadata + u"\n };\n};\n"; } QCtfTracePointPrivate *QCtfLibImpl::initializeTracepoint(const QCtfTracePointEvent &point) @@ -231,6 +355,8 @@ void QCtfLibImpl::doTracepoint(const QCtfTracePointEvent &point, const QByteArra QCtfTracePointPrivate *priv = point.d; quint64 timestamp = 0; QThread *thread = nullptr; + if (m_streaming && m_serverClosed) + return; { QMutexLocker lock(&m_mutex); if (!priv->metadataWritten) { @@ -263,12 +389,10 @@ void QCtfLibImpl::doTracepoint(const QCtfTracePointEvent &point, const QByteArra Channel &ch = m_threadData.localData(); if (ch.channelName[0] == 0) { + ch.impl = this; + m_channels.append(&ch); 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]; |