diff options
author | Marco Bubke <marco.bubke@qt.io> | 2023-10-01 00:45:46 +0200 |
---|---|---|
committer | Marco Bubke <marco.bubke@qt.io> | 2023-10-12 11:14:07 +0000 |
commit | 672fc4d2186f224978fa0afc3fef3c3f60b53e5e (patch) | |
tree | a6e61eae1e6f752426842ffecf43e90766890835 | |
parent | 9f6e6131037f6838a1d6bdc970aa0ef1b159db3a (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.txt | 5 | ||||
-rw-r--r-- | src/libs/nanotrace/nanotrace.h | 7 | ||||
-rw-r--r-- | src/libs/nanotrace/nanotraceglobals.h | 16 | ||||
-rw-r--r-- | src/libs/nanotrace/nanotracehr.cpp | 111 | ||||
-rw-r--r-- | src/libs/nanotrace/nanotracehr.h | 373 |
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 |