guanaqo 1.0.0-alpha.28
Utilities for scientific software
Loading...
Searching...
No Matches
trace.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file
4/// @ingroup trace_core
5/// Tracing logger and macros (ITT, Perfetto, or fallback).
6
7#include <guanaqo/export.h>
10#include <guanaqo/stringify.h>
11
12#include <concepts>
13#include <functional>
14#include <memory>
15#include <span>
16#include <utility>
17
18#if GUANAQO_WITH_ITT
19#include <ittnotify.h>
20#include <algorithm>
21#include <iosfwd>
22#else
23#include <chrono>
24#include <cstddef>
25#include <cstdint>
26#include <iomanip>
27#include <ostream>
28#endif
29
30namespace guanaqo {
31
32#if GUANAQO_WITH_ITT
33struct TraceLogger {
34 struct Log {
35 friend std::ostream &operator<<(std::ostream &os, Log) { return os; }
36 };
37
38 __itt_domain *domain = __itt_domain_create("guanaqo");
39 static constexpr int64_t max_instance_num = 255;
40 TraceLogger(size_t = 0) {
41 for (int64_t i = 0; i <= max_instance_num; ++i)
42 __itt_id_create(
43 domain,
44 __itt_id_make(nullptr, static_cast<unsigned long long>(i)));
45 }
46
47 TraceLogger(const TraceLogger &) = delete;
48 TraceLogger &operator=(const TraceLogger &) = delete;
49 TraceLogger(TraceLogger &&other) noexcept
50 : domain{std::exchange(other.domain, nullptr)} {}
51 TraceLogger &operator=(TraceLogger &&) = delete;
52 ~TraceLogger() {
53 if (!domain)
54 return;
55 for (int64_t i = 0; i <= max_instance_num; ++i)
56 __itt_id_destroy(
57 domain,
58 __itt_id_make(nullptr, static_cast<unsigned long long>(i)));
59 }
60
61 struct ScopedLog {
62 __itt_domain *domain;
63 ScopedLog(__itt_domain *domain, __itt_string_handle *name,
64 int64_t instance)
65 : domain{domain} {
66 if (!domain)
67 return;
68 instance = std::clamp<int64_t>(instance, 0, max_instance_num);
69 auto id = __itt_id_make(nullptr,
70 static_cast<unsigned long long>(instance));
71 __itt_task_begin(domain, id, __itt_null, name);
72 }
73 ScopedLog(const ScopedLog &) = delete;
74 ScopedLog &operator=(const ScopedLog &) = delete;
75 ScopedLog(ScopedLog &&other) noexcept
76 : domain{std::exchange(other.domain, nullptr)} {}
77 ScopedLog &operator=(ScopedLog &&) = delete;
78 ~ScopedLog() {
79 if (!domain)
80 return;
81 __itt_task_end(domain);
82 }
83 };
84
85 [[nodiscard]] ScopedLog trace(__itt_string_handle *name,
86 int64_t instance) const {
87 return ScopedLog{domain, name, instance};
88 }
89
90 void trace_instant(__itt_string_handle *name, int64_t instance) const {
91 if (!domain)
92 return;
93 instance = std::clamp<int64_t>(instance, 0, max_instance_num);
94 auto id =
95 __itt_id_make(nullptr, static_cast<unsigned long long>(instance));
96 __itt_marker(domain, id, name, __itt_scope_task);
97 }
98
99 [[nodiscard]] std::span<const Log> get_logs() const { return {}; }
100
101 void reset() {}
102};
103
104GUANAQO_EXPORT TraceLogger &get_trace_logger();
105
106#if !GUANAQO_WITH_PERFETTO
107#define GUANAQO_TRACE_IMPL(var_name, name, instance) \
108 static auto GUANAQO_CAT(var_name, _name) = \
109 __itt_string_handle_create(name); \
110 const auto var_name = ::guanaqo::get_trace_logger().trace( \
111 GUANAQO_CAT(var_name, _name), instance)
112#define GUANAQO_TRACE_INSTANT_IMPL(var_name, name, instance) \
113 do { \
114 static auto GUANAQO_CAT(var_name, _name) = \
115 __itt_string_handle_create(name); \
116 ::guanaqo::get_trace_logger().trace_instant( \
117 GUANAQO_CAT(var_name, _name), instance); \
118 } while (0)
119#define GUANAQO_TRACE(name, instance, ...) \
120 GUANAQO_TRACE_IMPL(GUANAQO_CAT(trace_log_, __COUNTER__), name, instance)
121#define GUANAQO_TRACE_INSTANT(name, instance) \
122 GUANAQO_TRACE_INSTANT_IMPL(GUANAQO_CAT(trace_instant_, __COUNTER__), name, \
123 instance)
124#define GUANAQO_TRACE_LINALG(name, gflops) \
125 GUANAQO_TRACE(name, 0) /* TODO: record gflops? */
126#define GUANAQO_TRACE_REGION(name, instance) GUANAQO_TRACE(name, instance)
127#define GUANAQO_TRACE_STATIC_STR(s) s
128#endif
129
130#else
131
132GUANAQO_EXPORT std::size_t get_thread_id();
133
134/// Class for recording trace logs, used when ITT or Perfetto tracing is not enabled.
135/// @ingroup trace_core
137 struct Log {
138 const char *name = "";
139 int64_t instance{0};
140 std::chrono::nanoseconds start_time{0};
141 std::chrono::nanoseconds duration{0};
142 std::size_t thread_id{0};
143 int64_t flop_count{0};
144
145 friend std::ostream &operator<<(std::ostream &os, const Log &log) {
146 return os << std::quoted(log.name) << ',' << log.instance << ','
147 << log.start_time.count() << ',' << log.duration.count()
148 << ',' << log.thread_id << ',' << log.flop_count;
149 }
150 };
151
152 static std::ostream &write_column_headings(std::ostream &os) {
153 return os << "name" << ',' << "instance" << ',' << "start_time" << ','
154 << "duration" << ',' << "thread_id" << ',' << "flop_count";
155 }
156
157 using clock = std::chrono::steady_clock;
158
159 static clock::time_point t0;
160 std::vector<Log> logs;
161
162 struct ScopedLog {
163 Log *log = nullptr;
164 clock::time_point start_time_point;
165
166 ScopedLog() = default;
169 ScopedLog(const ScopedLog &) = delete;
170 ScopedLog &operator=(const ScopedLog &) = delete;
171 ScopedLog(ScopedLog &&other) noexcept
172 : log{std::exchange(other.log, nullptr)},
173 start_time_point{other.start_time_point} {}
176 if (log)
177 log->duration = clock::now() - start_time_point;
178 }
179 };
180
181 TraceLogger(size_t capacity) { logs.reserve(capacity); }
182
183 [[nodiscard]] ScopedLog trace(const char *name, int64_t instance,
184 int64_t flop_count = -1) {
185 if (logs.size() == logs.capacity())
186 return ScopedLog{nullptr, {}};
187 auto &log = logs.emplace_back();
188 auto t1 = clock::now();
189 log.name = name;
190 log.instance = instance;
191 log.flop_count = flop_count;
192 log.start_time = t1 - t0;
193 log.thread_id = get_thread_id();
194 return ScopedLog{&log, t1}; // Note: assumes stable reference to log
195 }
196
197 void trace_instant(const char *name, int64_t instance,
198 int64_t flop_count = -1) {
199 auto instant_log = trace(name, instance, flop_count);
200 instant_log.log = nullptr;
201 }
202
203 [[nodiscard]] std::span<const Log> get_logs() const {
204 return std::span{logs};
205 }
206
207 /// Set the maximum number of logs that can be recorded. Additional logs are discarded.
208 void reserve(size_t capacity) { logs.reserve(capacity); }
209 /// Clear all recorded logs, but keep the reserved capacity.
210 void reset() { logs.clear(); } // does not change capacity
211 /// Clear all recorded logs and set capacity to 0 (essentially disabling the logger).
212 void clear() { logs = {}; } // set capacity to 0
213};
214
215#if GUANAQO_WITH_TRACING
216/// Get a reference to the global (but thread-local) trace logger instance.
217/// @ingroup trace_core
218GUANAQO_EXPORT TraceLogger &get_trace_logger();
219/// Set the default capacity for trace loggers created by get_trace_logger().
220GUANAQO_EXPORT size_t trace_logger_set_default_size(size_t size);
221/// Call @p callback for each trace logger instance.
222/// Note that the TraceLogger API is not thread-safe, so this function should not be called while
223/// other threads are still logging to their trace loggers. Use a barrier or a critical section to
224/// ensure this is the case.
225GUANAQO_EXPORT void
226for_each_trace_logger(const std::function<void(TraceLogger &)> &callback);
227/// Like @ref for_each_trace_logger, but also forgets about all loggers. Threads that are still
228/// running will still be able to log to their loggers, but they are no longer accessible through
229/// @ref foreach_trace_logger. Loggers for threads that no longer exist will be cleaned up,
230/// unless the callback takes ownership of them.
231GUANAQO_EXPORT void drop_trace_loggers(
232 const std::function<void(std::shared_ptr<TraceLogger>)> &callback =
233 nullptr);
234#endif
235
236#endif
237
238#if !GUANAQO_WITH_PERFETTO && !GUANAQO_WITH_ITT
239#if GUANAQO_WITH_TRACING
240#define GUANAQO_TRACE(name, ...) \
241 const auto GUANAQO_CAT(trace_log_, __COUNTER__) = \
242 ::guanaqo::get_trace_logger().trace(name, __VA_ARGS__)
243#define GUANAQO_TRACE_INSTANT(name, instance) \
244 do { \
245 ::guanaqo::get_trace_logger().trace_instant(name, instance); \
246 } while (0)
247#define GUANAQO_TRACE_LINALG(name, gflops) GUANAQO_TRACE(name, 0, gflops)
248#define GUANAQO_TRACE_REGION(name, instance) GUANAQO_TRACE(name, instance)
249#define GUANAQO_TRACE_STATIC_STR(s) s
250#else
251#define GUANAQO_TRACE(name, ...) GUANAQO_NOOP()
252#define GUANAQO_TRACE_INSTANT(name, instance) GUANAQO_NOOP()
253#define GUANAQO_TRACE_LINALG(name, gflops) GUANAQO_NOOP()
254#define GUANAQO_TRACE_REGION(name, instance) GUANAQO_NOOP()
255#define GUANAQO_TRACE_STATIC_STR(s) s
256#endif
257#endif
258
259#if GUANAQO_WITH_ITT
260#define GUANAQO_IF_ITT(...) __VA_ARGS__
261#else
262#define GUANAQO_IF_ITT(...)
263#endif
264
265} // namespace guanaqo
TraceLogger & get_trace_logger()
Get a reference to the global (but thread-local) trace logger instance.
Definition trace.cpp:59
size_t trace_logger_set_default_size(size_t size)
Set the default capacity for trace loggers created by get_trace_logger().
Definition trace.cpp:55
void drop_trace_loggers(const std::function< void(std::shared_ptr< TraceLogger >)> &callback=nullptr)
Like for_each_trace_logger, but also forgets about all loggers.
Definition trace.cpp:65
void for_each_trace_logger(const std::function< void(TraceLogger &)> &callback)
Call callback for each trace logger instance.
Definition trace.cpp:81
std::size_t get_thread_id()
Definition trace.cpp:29
Perfetto tracing macros and session helpers.
Token concatenation and argument-counting helpers.
Stringify and token concatenation helpers.
std::chrono::nanoseconds start_time
Definition trace.hpp:140
std::chrono::nanoseconds duration
Definition trace.hpp:141
friend std::ostream & operator<<(std::ostream &os, const Log &log)
Definition trace.hpp:145
ScopedLog(Log *log, clock::time_point start_time_point)
Definition trace.hpp:167
ScopedLog(const ScopedLog &)=delete
ScopedLog & operator=(ScopedLog &&)=delete
ScopedLog(ScopedLog &&other) noexcept
Definition trace.hpp:171
clock::time_point start_time_point
Definition trace.hpp:164
ScopedLog & operator=(const ScopedLog &)=delete
Class for recording trace logs, used when ITT or Perfetto tracing is not enabled.
Definition trace.hpp:136
void trace_instant(const char *name, int64_t instance, int64_t flop_count=-1)
Definition trace.hpp:197
std::vector< Log > logs
Definition trace.hpp:160
static clock::time_point t0
Definition trace.hpp:159
TraceLogger(size_t capacity)
Definition trace.hpp:181
std::chrono::steady_clock clock
Definition trace.hpp:157
ScopedLog trace(const char *name, int64_t instance, int64_t flop_count=-1)
Definition trace.hpp:183
void clear()
Clear all recorded logs and set capacity to 0 (essentially disabling the logger).
Definition trace.hpp:212
static std::ostream & write_column_headings(std::ostream &os)
Definition trace.hpp:152
void reserve(size_t capacity)
Set the maximum number of logs that can be recorded. Additional logs are discarded.
Definition trace.hpp:208
void reset()
Clear all recorded logs, but keep the reserved capacity.
Definition trace.hpp:210
std::span< const Log > get_logs() const
Definition trace.hpp:203