aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarco Bubke <marco.bubke@qt.io>2023-10-01 00:45:46 +0200
committerMarco Bubke <marco.bubke@qt.io>2023-10-12 11:14:07 +0000
commit672fc4d2186f224978fa0afc3fef3c3f60b53e5e (patch)
treea6e61eae1e6f752426842ffecf43e90766890835
parent9f6e6131037f6838a1d6bdc970aa0ef1b159db3a (diff)
Nanotrace: Add high resolution low overhead tracer
I want to use the trace in the project storage but I like reduce the overhead. If the constexpr activateTracer is set there is even not overhead at all. Uage would be: foo.h: extern thread_local EventQueue fooEventQueue; extern thread_local Category fooCategory; void foo() { Nanotrace::Tracer t{"Foo", fooCategory}; Nanotrace::Tracer t{"Foo", "fooCategory", fooEventQueue}; Nanotrace::Tracer t{"Foo", "fooCategory", R"xy("First":"Argument")xy", fooEventQueue}; } or you can use the GlobalTracer: void fooWithGlobal() { Nanotrace::GlobalTracer t{"Foo", "Category"}; Nanotrace::GlobalTracer t{"Foo", "Category", R"xy("First":"Argument")xy"}; } foo.cpp: namespace { Nanotrace::TraceFile fooTraceFile{"foo.json"}; thread_local auto fooEventQueueData = Nanotrace::makeEventQueueData<10000>("Foo", fooTraceFile); } // namespace thread_local EventQueue fooEventQueue = fooEventQueueData; thread_local EventQueue fooCategory{"foo"_t, fooEventQueue}; If nano trace is deactivated fooEventQueueData would be a null pointer, fooEventQueue would be disabled and the trace would generate no code. Change-Id: I1cad8570536806c462a61276eb142b8aa4932529 Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: Eike Ziller <eike.ziller@qt.io> Reviewed-by: Tim Jenssen <tim.jenssen@qt.io> Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io>
-rw-r--r--src/libs/nanotrace/CMakeLists.txt5
-rw-r--r--src/libs/nanotrace/nanotrace.h7
-rw-r--r--src/libs/nanotrace/nanotraceglobals.h16
-rw-r--r--src/libs/nanotrace/nanotracehr.cpp111
-rw-r--r--src/libs/nanotrace/nanotracehr.h373
5 files changed, 505 insertions, 7 deletions
diff --git a/src/libs/nanotrace/CMakeLists.txt b/src/libs/nanotrace/CMakeLists.txt
index 57f11af997..fdfb137b68 100644
--- a/src/libs/nanotrace/CMakeLists.txt
+++ b/src/libs/nanotrace/CMakeLists.txt
@@ -2,7 +2,10 @@ add_qtc_library(Nanotrace
BUILD_DEFAULT OFF
DEFINES NANOTRACE_LIBRARY
PUBLIC_DEFINES NANOTRACE_ENABLED
- SOURCES nanotrace.cpp nanotrace.h
+ SOURCES
+ nanotraceglobals.h
+ nanotrace.cpp nanotrace.h
+ nanotracehr.cpp nanotracehr.h
PUBLIC_DEPENDS Qt::Core
PROPERTIES
CXX_VISIBILITY_PRESET default
diff --git a/src/libs/nanotrace/nanotrace.h b/src/libs/nanotrace/nanotrace.h
index ab390519ca..fae1892b80 100644
--- a/src/libs/nanotrace/nanotrace.h
+++ b/src/libs/nanotrace/nanotrace.h
@@ -3,13 +3,8 @@
#pragma once
-#include <QtGlobal>
+#include "nanotraceglobals.h"
-#if defined(NANOTRACE_LIBRARY)
-# define NANOTRACESHARED_EXPORT Q_DECL_EXPORT
-#else
-# define NANOTRACESHARED_EXPORT Q_DECL_IMPORT
-#endif
#include <chrono>
#include <string>
diff --git a/src/libs/nanotrace/nanotraceglobals.h b/src/libs/nanotrace/nanotraceglobals.h
new file mode 100644
index 0000000000..649408d69c
--- /dev/null
+++ b/src/libs/nanotrace/nanotraceglobals.h
@@ -0,0 +1,16 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <QtGlobal>
+
+#if defined(NANOTRACE_LIBRARY)
+#define NANOTRACE_EXPORT Q_DECL_EXPORT
+#elif defined(NANOTRACE_STATIC_LIBRARY)
+#define NANOTRACE_EXPORT
+#else
+#define NANOTRACE_EXPORT Q_DECL_IMPORT
+#endif
+
+#define NANOTRACESHARED_EXPORT NANOTRACE_EXPORT
diff --git a/src/libs/nanotrace/nanotracehr.cpp b/src/libs/nanotrace/nanotracehr.cpp
new file mode 100644
index 0000000000..bd67ae14ea
--- /dev/null
+++ b/src/libs/nanotrace/nanotracehr.cpp
@@ -0,0 +1,111 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "nanotracehr.h"
+
+#include <QCoreApplication>
+
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <limits>
+#include <thread>
+
+namespace NanotraceHR {
+
+namespace {
+
+template<typename TraceEvent>
+void printEvent(std::ostream &out, const TraceEvent &event, qint64 processId, std::thread::id threadId)
+{
+ out << R"({"ph":"X","name":")" << event.name << R"(","cat":")" << event.category
+ << R"(","ts":")" << static_cast<double>(event.start.time_since_epoch().count()) / 1000
+ << R"(","dur":")" << static_cast<double>(event.duration.count()) / 1000 << R"(","pid":")"
+ << processId << R"(","tid":")" << threadId << R"(","args":)" << event.arguments << "}";
+}
+} // namespace
+
+template<typename TraceEvent>
+void flushEvents(const Utils::span<TraceEvent> events,
+ std::thread::id threadId,
+ EventQueue<TraceEvent> &eventQueue)
+{
+ if (events.empty())
+ return;
+
+ std::lock_guard lock{eventQueue.file->fileMutex};
+ auto &out = eventQueue.file->out;
+
+ if (out.is_open()) {
+ auto processId = QCoreApplication::applicationPid();
+ for (const auto &event : events) {
+ printEvent(out, event, processId, threadId);
+ out << ",\n";
+ }
+ }
+}
+
+template void flushEvents(const Utils::span<StringViewTraceEvent> events,
+ std::thread::id threadId,
+ EventQueue<StringViewTraceEvent> &eventQueue);
+template void flushEvents(const Utils::span<StringTraceEvent> events,
+ std::thread::id threadId,
+ EventQueue<StringTraceEvent> &eventQueue);
+
+void openFile(class TraceFile &file)
+{
+ std::lock_guard lock{file.fileMutex};
+
+ if (file.out = std::ofstream{file.filePath, std::ios::trunc}; file.out.good())
+ file.out << std::fixed << std::setprecision(3) << R"({"traceEvents": [)";
+}
+
+void finalizeFile(class TraceFile &file)
+{
+ std::lock_guard lock{file.fileMutex};
+ auto &out = file.out;
+
+ if (out.is_open()) {
+ out.seekp(-2, std::ios_base::cur); // removes last comma and new line
+ out << R"(],"displayTimeUnit":"ns","otherData":{"version": "Qt Creator )";
+ out << QCoreApplication::applicationVersion().toStdString();
+ out << R"("}})";
+ out.close();
+ }
+}
+
+template<typename TraceEvent>
+void flushInThread(EventQueue<TraceEvent> &eventQueue)
+{
+ if (eventQueue.file->processing.valid())
+ eventQueue.file->processing.wait();
+
+ auto flush = [&](const Utils::span<TraceEvent> &events, std::thread::id threadId) {
+ flushEvents(events, threadId, eventQueue);
+ };
+
+ eventQueue.file->processing = std::async(std::launch::async,
+ flush,
+ eventQueue.currentEvents,
+ std::this_thread::get_id());
+ eventQueue.currentEvents = eventQueue.currentEvents.data() == eventQueue.eventsOne.data()
+ ? eventQueue.eventsTwo
+ : eventQueue.eventsOne;
+ eventQueue.eventsIndex = 0;
+}
+
+template void flushInThread(EventQueue<StringViewTraceEvent> &eventQueue);
+template void flushInThread(EventQueue<StringTraceEvent> &eventQueue);
+
+namespace {
+TraceFile globalTraceFile{"global.json"};
+thread_local auto globalEventQueueData = makeEventQueueData<StringTraceEvent, 1000>(globalTraceFile);
+thread_local EventQueue<StringTraceEvent> s_globalEventQueue = globalEventQueueData;
+} // namespace
+
+EventQueue<StringTraceEvent> &globalEventQueue()
+{
+ return s_globalEventQueue;
+}
+
+} // namespace NanotraceHR
diff --git a/src/libs/nanotrace/nanotracehr.h b/src/libs/nanotrace/nanotracehr.h
new file mode 100644
index 0000000000..1adb6d586b
--- /dev/null
+++ b/src/libs/nanotrace/nanotracehr.h
@@ -0,0 +1,373 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "nanotraceglobals.h"
+
+#include <utils/span.h>
+
+#include <array>
+#include <chrono>
+#include <fstream>
+#include <future>
+#include <mutex>
+#include <string>
+#include <string_view>
+
+namespace NanotraceHR {
+using Clock = std::chrono::steady_clock;
+using TimePoint = std::chrono::time_point<Clock>;
+using Duration = std::chrono::nanoseconds;
+
+static_assert(Clock::is_steady, "clock should be steady");
+static_assert(std::is_same_v<Clock::duration, std::chrono::nanoseconds>,
+ "the steady clock should have nano second resolution");
+
+constexpr bool isTracerActive()
+{
+#ifdef NANOTRACE_ENABLED
+ return true;
+#else
+ return false;
+#endif
+}
+
+template<std::size_t size>
+std::string_view toStringView(Utils::span<const char, size> string)
+{
+ return {string.data(), string.size()};
+}
+
+template<typename String>
+struct TraceEvent
+{
+ TraceEvent() = default;
+ TraceEvent(const TraceEvent &) = delete;
+ TraceEvent(TraceEvent &&) = delete;
+ TraceEvent &operator=(const TraceEvent &) = delete;
+ TraceEvent &operator=(TraceEvent &&) = delete;
+ ~TraceEvent() = default;
+
+ String name;
+ String category;
+ String arguments;
+ TimePoint start;
+ Duration duration;
+};
+
+using StringViewTraceEvent = TraceEvent<std::string_view>;
+using StringTraceEvent = TraceEvent<std::string>;
+
+enum class IsEnabled { No, Yes };
+
+template<typename TraceEvent>
+class EventQueue;
+
+template<typename TraceEvent>
+void flushEvents(const Utils::span<TraceEvent> events,
+ std::thread::id threadId,
+ EventQueue<TraceEvent> &eventQueue);
+extern template void flushEvents(const Utils::span<StringViewTraceEvent> events,
+ std::thread::id threadId,
+ EventQueue<StringViewTraceEvent> &eventQueue);
+extern template void flushEvents(const Utils::span<StringTraceEvent> events,
+ std::thread::id threadId,
+ EventQueue<StringTraceEvent> &eventQueue);
+
+template<typename TraceEvent>
+void flushInThread(EventQueue<TraceEvent> &eventQueue);
+extern template void flushInThread(EventQueue<StringViewTraceEvent> &eventQueue);
+extern template void flushInThread(EventQueue<StringTraceEvent> &eventQueue);
+
+void openFile(class TraceFile &file);
+void finalizeFile(class TraceFile &file);
+
+class TraceFile
+{
+public:
+ TraceFile([[maybe_unused]] std::string_view filePath)
+ : filePath{filePath}
+ {
+ openFile(*this);
+ }
+
+ TraceFile(const TraceFile &) = delete;
+ TraceFile(TraceFile &&) = delete;
+ TraceFile &operator=(const TraceFile &) = delete;
+ TraceFile &operator=(TraceFile &&) = delete;
+
+ ~TraceFile() { finalizeFile(*this); }
+ std::string filePath;
+ std::mutex fileMutex;
+ std::future<void> processing;
+ std::ofstream out;
+};
+
+template<typename TraceEvent>
+class EventQueue
+{
+ using TraceEventsSpan = Utils::span<TraceEvent>;
+
+public:
+ EventQueue() = default;
+ ~EventQueue()
+ {
+ if (isEnabled == IsEnabled::Yes)
+ flushEvents(currentEvents, std::this_thread::get_id(), *this);
+ }
+
+ EventQueue(const EventQueue &) = delete;
+ EventQueue(EventQueue &&) = delete;
+ EventQueue &operator=(const EventQueue &) = delete;
+ EventQueue &operator=(EventQueue &&) = delete;
+
+ TraceFile *file = nullptr;
+ TraceEventsSpan eventsOne;
+ TraceEventsSpan eventsTwo;
+ TraceEventsSpan currentEvents;
+ std::size_t eventsIndex = 0;
+ IsEnabled isEnabled = IsEnabled::No;
+};
+
+template<typename TraceEvent, std::size_t eventCount>
+class EventQueueData
+{
+ using TraceEvents = std::array<TraceEvent, eventCount>;
+
+public:
+ EventQueueData(TraceFile &file)
+ : file{file}
+ {}
+
+ TraceFile &file;
+ TraceEvents eventsOne;
+ TraceEvents eventsTwo;
+};
+
+template<typename TraceEvent, std::size_t eventCount>
+struct EventQueueDataPointer
+{
+ operator EventQueue<TraceEvent>() const
+ {
+ if constexpr (isTracerActive()) {
+ return {&data->file, data->eventsOne, data->eventsTwo, data->eventsOne, 0, IsEnabled::Yes};
+ } else {
+ return {};
+ }
+ }
+
+ std::unique_ptr<EventQueueData<TraceEvent, eventCount>> data;
+};
+
+template<typename TraceEvent, std::size_t eventCount>
+EventQueueDataPointer<TraceEvent, eventCount> makeEventQueueData(TraceFile &file)
+{
+ if constexpr (isTracerActive()) {
+ return {std::make_unique<EventQueueData<TraceEvent, eventCount>>(file)};
+ } else {
+ return {};
+ }
+}
+
+EventQueue<StringTraceEvent> &globalEventQueue();
+
+template<typename TraceEvent>
+TraceEvent &getTraceEvent(EventQueue<TraceEvent> &eventQueue)
+{
+ if (eventQueue.eventsIndex == eventQueue.currentEvents.size())
+ flushInThread(eventQueue);
+
+ return eventQueue.currentEvents[eventQueue.eventsIndex++];
+}
+
+namespace Literals {
+struct TracerLiteral
+{
+ friend constexpr TracerLiteral operator""_t(const char *text, size_t size);
+
+ constexpr operator std::string_view() const { return text; }
+
+private:
+ constexpr TracerLiteral(std::string_view text)
+ : text{text}
+ {}
+
+ std::string_view text;
+};
+constexpr TracerLiteral operator""_t(const char *text, size_t size)
+{
+ return {std::string_view{text, size}};
+}
+} // namespace Literals
+
+using namespace Literals;
+
+template<typename TraceEvent>
+class Category
+{
+public:
+ using IsActive = std::true_type;
+ TracerLiteral name;
+ EventQueue<TraceEvent> &eventQueue;
+};
+
+using StringViewCategory = Category<StringViewTraceEvent>;
+using StringCategory = Category<StringTraceEvent>;
+
+class DisabledCategory
+{};
+
+template<typename TraceEvent>
+Category(TracerLiteral name, EventQueue<TraceEvent> &eventQueue) -> Category<TraceEvent>;
+
+template<typename Category>
+class Tracer
+{
+public:
+ constexpr Tracer(TracerLiteral, Category &, TracerLiteral) {}
+ constexpr Tracer(TracerLiteral, Category &) {}
+
+ ~Tracer() {}
+};
+
+template<typename String>
+class BasicTracer
+{};
+
+template<>
+class Tracer<StringViewCategory>
+{
+public:
+ constexpr Tracer(TracerLiteral name, StringViewCategory &category, TracerLiteral arguments)
+ : m_name{name}
+ , m_arguments{arguments}
+ , m_category{category}
+ {
+ if constexpr (isTracerActive()) {
+ if (category.eventQueue.isEnabled == IsEnabled::Yes)
+ m_start = Clock::now();
+ }
+ }
+
+ constexpr Tracer(TracerLiteral name, StringViewCategory &category)
+ : Tracer{name, category, "{}"_t}
+ {
+ if constexpr (isTracerActive()) {
+ if (category.eventQueue.isEnabled == IsEnabled::Yes)
+ m_start = Clock::now();
+ }
+ }
+
+ ~Tracer()
+ {
+ if constexpr (isTracerActive()) {
+ if (m_category.eventQueue.isEnabled == IsEnabled::Yes) {
+ auto duration = Clock::now() - m_start;
+ auto &traceEvent = getTraceEvent(m_category.eventQueue);
+ traceEvent.name = m_name;
+ traceEvent.category = m_category.name;
+ traceEvent.arguments = m_arguments;
+ traceEvent.start = m_start;
+ traceEvent.duration = duration;
+ }
+ }
+ }
+
+private:
+ TimePoint m_start;
+ std::string_view m_name;
+ std::string_view m_arguments;
+ StringViewCategory &m_category;
+};
+
+template<>
+class Tracer<StringCategory>
+{
+public:
+ Tracer(std::string name, StringViewCategory &category, std::string arguments)
+ : m_name{std::move(name)}
+ , m_arguments{arguments}
+ , m_category{category}
+ {
+ if constexpr (isTracerActive()) {
+ if (category.eventQueue.isEnabled == IsEnabled::Yes)
+ m_start = Clock::now();
+ }
+ }
+
+ Tracer(std::string name, StringViewCategory &category)
+ : Tracer{std::move(name), category, "{}"}
+ {
+ if constexpr (isTracerActive()) {
+ if (category.eventQueue.isEnabled == IsEnabled::Yes)
+ m_start = Clock::now();
+ }
+ }
+
+ ~Tracer()
+ {
+ if constexpr (isTracerActive()) {
+ if (m_category.eventQueue.isEnabled == IsEnabled::Yes) {
+ auto duration = Clock::now() - m_start;
+ auto &traceEvent = getTraceEvent(m_category.eventQueue);
+ traceEvent.name = std::move(m_name);
+ traceEvent.category = m_category.name;
+ traceEvent.arguments = std::move(m_arguments);
+ traceEvent.start = m_start;
+ traceEvent.duration = duration;
+ }
+ }
+ }
+
+private:
+ TimePoint m_start;
+ std::string m_name;
+ std::string m_arguments;
+ StringViewCategory &m_category;
+};
+
+template<typename Category>
+Tracer(TracerLiteral name, Category &category) -> Tracer<Category>;
+
+class GlobalTracer
+{
+public:
+ GlobalTracer(std::string name, std::string category, std::string arguments)
+ : m_name{std::move(name)}
+ , m_category{std::move(category)}
+ , m_arguments{std::move(arguments)}
+ {
+ if constexpr (isTracerActive()) {
+ if (globalEventQueue().isEnabled == IsEnabled::Yes)
+ m_start = Clock::now();
+ }
+ }
+
+ GlobalTracer(std::string name, std::string category)
+ : GlobalTracer{std::move(name), std::move(category), "{}"}
+ {}
+
+ ~GlobalTracer()
+ {
+ if constexpr (isTracerActive()) {
+ if (globalEventQueue().isEnabled == IsEnabled::Yes) {
+ auto duration = Clock::now() - m_start;
+ auto &traceEvent = getTraceEvent(globalEventQueue());
+ traceEvent.name = std::move(m_name);
+ traceEvent.category = std::move(m_category);
+ traceEvent.arguments = std::move(m_arguments);
+ traceEvent.start = std::move(m_start);
+ traceEvent.duration = std::move(duration);
+ }
+ }
+ }
+
+private:
+ TimePoint m_start;
+ std::string m_name;
+ std::string m_category;
+ std::string m_arguments;
+};
+
+} // namespace NanotraceHR