HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
trace.hpp
1// Copyright Take Vos 2019-2020.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#include "counters.hpp"
6#include "datum.hpp"
7#include "logger.hpp"
8#include "time_stamp_count.hpp"
9#include "hires_utc_clock.hpp"
10#include "required.hpp"
11#include "tagged_map.hpp"
12#include "fixed_string.hpp"
13#include "statistics.hpp"
14#include <format>
15#include <atomic>
16#include <array>
17#include <utility>
18#include <ostream>
19#include <typeinfo>
20#include <typeindex>
21
22#pragma once
23
24namespace tt {
25
26constexpr int MAX_NR_TRACES = 1024;
27
28inline std::atomic<int64_t> trace_id = 0;
29
33 int64_t top_trace_id = 0;
34
37 int8_t depth = 0;
38
41 int8_t record_depth = 0;
42
48 inline int64_t push() noexcept
49 {
50 ttlet parent_id = top_trace_id;
51 top_trace_id = trace_id.fetch_add(1, std::memory_order::relaxed) + 1;
52 depth++;
53 return parent_id;
54 }
55
62 inline std::pair<int64_t, bool> pop(int64_t parent_id) noexcept
63 {
64 bool is_recording = record_depth > --depth;
65 if (is_recording) {
67 }
68
69 ttlet id = top_trace_id;
70 top_trace_id = parent_id;
71 return {id, is_recording};
72 }
73};
74
75inline thread_local trace_stack_type trace_stack;
76
79void trace_record() noexcept;
80
81template<basic_fixed_string Tag, basic_fixed_string... InfoTags>
82struct trace_data {
83 static constexpr basic_fixed_string tag = Tag;
84
88 int64_t parent_id;
89
93
94 tagged_map<sdatum, InfoTags...> info;
95
96 trace_data(time_stamp_count time_stamp) : time_stamp(time_stamp) {}
97
98 trace_data() = default;
99 ~trace_data() = default;
100 trace_data(trace_data const &other) = default;
101 trace_data &operator=(trace_data const &other) = default;
102 trace_data(trace_data &&other) = default;
103 trace_data &operator=(trace_data &&other) = default;
104
105 template<basic_fixed_string InfoTag>
106 sdatum &get() noexcept
107 {
108 return info.template get<InfoTag>();
109 }
110
111 template<basic_fixed_string InfoTag>
112 sdatum const &get() const noexcept
113 {
114 return info.template get<InfoTag>();
115 }
116
117 friend std::string to_string(trace_data const &rhs) noexcept
118 {
119 auto info_string = std::string{};
120
121 auto counter = 0;
122 for (size_t i = 0; i < rhs.info.size(); i++) {
123 if (counter++ > 0) {
124 info_string += ", ";
125 }
126 info_string += rhs.info.get_tag(i);
127 info_string += "=";
128 info_string += static_cast<std::string>(rhs.info[i]);
129 }
130
131 return std::format(
132 "parent={} tag={} start={} {}",
133 rhs.parent_id,
134 std::type_index(typeid(Tag)).name(),
135 format_iso8601(hires_utc_clock::make(rhs.time_stamp)),
136 info_string);
137 }
138};
139
140
151private:
152 std::atomic<long long> count = 0;
153 std::atomic<long long> duration = {};
154 std::atomic<long long> peak_duration = {};
155 std::atomic<long long> version = 0;
156
157 // Variables used by logger.
158 long long prev_count = 0;
159 std::chrono::nanoseconds prev_duration = {};
160
161public:
166 {
167 // In the logging thread we can check if count and version are equal
168 // to read the statistics.
169 ttlet current_count = count.fetch_add(1, std::memory_order::acquire);
170
171 duration.fetch_add(d.count(), std::memory_order::relaxed);
172
173 auto prev_peak = peak_duration.load(std::memory_order::relaxed);
174 decltype(prev_peak) new_peak;
175 do {
176 new_peak = d.count() > prev_peak ? d.count() : prev_peak;
177 } while (!peak_duration.compare_exchange_weak(prev_peak, new_peak, std::memory_order::relaxed));
178
179 version.store(current_count + 1, std::memory_order::release);
180
181 return current_count == 0;
182 }
183
184 struct read_result {
185 long long count;
186 long long last_count;
187
189 std::chrono::nanoseconds last_duration;
190 std::chrono::nanoseconds peak_duration;
191 };
192
193 read_result read()
194 {
195 read_result r;
196
197 r.peak_duration = {};
198 do {
199 r.count = count.load(std::memory_order::acquire);
200
201 r.duration = std::chrono::nanoseconds{duration.load(std::memory_order::relaxed)};
202
203 auto tmp = peak_duration.exchange(0, std::memory_order::relaxed);
204 if (tmp > r.peak_duration.count()) {
205 r.peak_duration = std::chrono::nanoseconds{tmp};
206 }
207
208 std::atomic_thread_fence(std::memory_order::release);
209 } while (r.count != version.load(std::memory_order::relaxed));
210
211 r.last_count = r.count - prev_count;
212 r.last_duration = r.duration - prev_duration;
213
214 prev_count = r.count;
215 prev_duration = r.duration;
216 return r;
217 }
218};
219
220template<basic_fixed_string Tag>
221inline trace_statistics_type trace_statistics;
222
223inline wfree_unordered_map<std::string, trace_statistics_type *, MAX_NR_TRACES> trace_statistics_map;
224
225template<basic_fixed_string Tag, basic_fixed_string... InfoTags>
226class trace final {
227 // If this pointer is not an volatile, clang will optimize it away and replacing it
228 // with direct access to the trace_stack variable. This trace_stack variable is in local storage,
229 // so a lot of instructions and memory accesses are emitted by the compiler multiple times.
230 trace_stack_type *volatile stack;
231
232 trace_data<Tag, InfoTags...> data;
233
234 tt_no_inline static void add_to_map()
235 {
236 trace_statistics_map.insert(Tag, &trace_statistics<Tag>);
237 statistics_start();
238 }
239
240public:
246 trace() : stack(&trace_stack), data(time_stamp_count::now())
247 {
248 // We don't need to know our own id, until the destructor is called.
249 // Our id will be at the top of the stack.
250 data.parent_id = stack->push();
251 }
252
253 ~trace()
254 {
255 ttlet end_time_stamp = time_stamp_count::now();
256
257 if (trace_statistics<Tag>.write(end_time_stamp.time_since_epoch() - data.time_stamp.time_since_epoch())) {
258 [[unlikely]] add_to_map();
259 }
260
261 ttlet[id, is_recording] = stack->pop(data.parent_id);
262
263 // Send the log to the log thread.
264 if (is_recording) {
265 [[unlikely]] tt_log_trace("id={} {}", id, to_string(data));
266 }
267 }
268
269 trace(trace const &) = delete;
270 trace(trace &&) = delete;
271 trace &operator=(trace const &) = delete;
272 trace &operator=(trace &&) = delete;
273
274 template<basic_fixed_string InfoTag, typename T>
275 trace &set(T &&value)
276 {
277 data.template get<InfoTag>() = std::forward<T>(value);
278 return *this;
279 }
280};
281
282} // namespace tt
A fixed size (64 bits) class for a generic value type.
Definition datum.hpp:124
example: ``` template<tt::basic_fixed_string Foo> class A { auto bar() { return std::string{Foo}; } }...
Definition fixed_string.hpp:30
static time_point make(time_stamp_count const &tsc) noexcept
Make a time point from a time stamp count.
A static sized stack.
Definition stack.hpp:23
Definition tagged_map.hpp:16
Since Window's 10 QueryPerformanceCounter() counts at only 10MHz which is too low to measure performa...
Definition time_stamp_count.hpp:29
std::chrono::nanoseconds time_since_epoch() const noexcept
Convert to nanoseconds since epoch.
Definition time_stamp_count.hpp:108
static time_stamp_count now() noexcept
Get the current count from the CPU's time stamp count.
Definition time_stamp_count.hpp:66
Definition trace.hpp:30
int64_t top_trace_id
Definition trace.hpp:33
std::pair< int64_t, bool > pop(int64_t parent_id) noexcept
Definition trace.hpp:62
int8_t depth
Definition trace.hpp:37
int64_t push() noexcept
Definition trace.hpp:48
int8_t record_depth
Definition trace.hpp:41
Definition trace.hpp:82
int64_t parent_id
Definition trace.hpp:88
time_stamp_count time_stamp
Definition trace.hpp:92
Definition trace.hpp:150
bool write(std::chrono::nanoseconds const &d)
Definition trace.hpp:165
Definition trace.hpp:226
trace()
Definition trace.hpp:246
T atomic_thread_fence(T... args)
T compare_exchange_weak(T... args)
T exchange(T... args)
T fetch_add(T... args)
T load(T... args)
T name(T... args)
T store(T... args)
T to_string(T... args)