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 "cpu_utc_clock.hpp"
9#include "required.hpp"
10#include "tagged_map.hpp"
11#include "wfree_message_queue.hpp"
12#include "fixed_string.hpp"
13#include "statistics.hpp"
14#include <fmt/ostream.h>
15#include <fmt/format.h>
16#include <atomic>
17#include <array>
18#include <utility>
19#include <ostream>
20#include <typeinfo>
21#include <typeindex>
22
23#pragma once
24
25namespace tt {
26
27constexpr int MAX_NR_TRACES = 1024;
28
29
30inline std::atomic<int64_t> trace_id = 0;
31
35 int64_t top_trace_id = 0;
36
39 int8_t depth = 0;
40
43 int8_t record_depth = 0;
44
50 inline int64_t push() noexcept {
51 ttlet parent_id = top_trace_id;
52 top_trace_id = trace_id.fetch_add(1, std::memory_order::relaxed) + 1;
53 depth++;
54 return parent_id;
55 }
56
63 inline std::pair<int64_t, bool> pop(int64_t parent_id) noexcept {
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 {
86 int64_t parent_id;
87
91
92 tagged_map<sdatum, InfoTags...> info;
93
94 trace_data(typename cpu_counter_clock::time_point timestamp) :
95 timestamp(timestamp) {}
96
97 trace_data() = default;
98 ~trace_data() = default;
99 trace_data(trace_data const &other) = default;
100 trace_data &operator=(trace_data const &other) = default;
101 trace_data(trace_data &&other) = default;
102 trace_data &operator=(trace_data &&other) = default;
103
104 template<basic_fixed_string InfoTag>
105 sdatum &get() noexcept {
106 return info.template get<InfoTag>();
107 }
108
109 template<basic_fixed_string InfoTag>
110 sdatum const &get() const noexcept {
111 return info.template get<InfoTag>();
112 }
113};
114
115template<basic_fixed_string Tag, basic_fixed_string... InfoTags>
116std::ostream &operator<<(std::ostream &lhs, trace_data<Tag, InfoTags...> const &rhs) {
117 auto info_string = std::string{};
118
119 auto counter = 0;
120 for (size_t i = 0; i < rhs.info.size(); i++) {
121 if (counter++ > 0) {
122 info_string += ", ";
123 }
124 info_string += rhs.info.get_tag(i);
125 info_string += "=";
126 info_string += static_cast<std::string>(rhs.info[i]);
127 }
128
129 lhs << fmt::format("parent={} tag={} start={} {}",
130 rhs.parent_id,
131 std::type_index(typeid(Tag)).name(),
132 format_iso8601(cpu_utc_clock::convert(rhs.timestamp)),
133 info_string
134 );
135 return lhs;
136}
137
148private:
149 std::atomic<int64_t> count = 0;
153
154 // Variables used by logger.
155 int64_t prev_count = 0;
156 typename cpu_counter_clock::duration prev_duration = {};
157
158public:
163 // In the logging thread we can check if count and version are equal
164 // to read the statistics.
165 ttlet current_count = count.fetch_add(1, std::memory_order::acquire);
166
167 duration.fetch_add(d.count(), std::memory_order::relaxed);
168
169 auto prev_peak = peak_duration.load(std::memory_order::relaxed);
170 decltype(prev_peak) new_peak;
171 do {
172 new_peak = d.count() > prev_peak ? d.count() : prev_peak;
173 } while (!peak_duration.compare_exchange_weak(prev_peak, new_peak, std::memory_order::relaxed));
174
175 version.store(current_count + 1, std::memory_order::release);
176
177 return current_count == 0;
178 }
179
180 struct read_result {
181 int64_t count;
182 int64_t last_count;
183
184 typename cpu_counter_clock::duration duration;
185 typename cpu_counter_clock::duration last_duration;
186 typename cpu_counter_clock::duration peak_duration;
187 };
188
189 read_result read() {
190 read_result r;
191
192 r.peak_duration = {};
193 do {
194 r.count = count.load(std::memory_order::acquire);
195
196 r.duration = decltype(r.duration){duration.load(std::memory_order::relaxed)};
197
198 auto tmp = peak_duration.exchange(0, std::memory_order::relaxed);
199 if (tmp > r.peak_duration.count()) {
200 r.peak_duration = decltype(r.duration){tmp};
201 }
202
203 std::atomic_thread_fence(std::memory_order::release);
204 } while (r.count != version.load(std::memory_order::relaxed));
205
206 r.last_count = r.count - prev_count;
207 r.last_duration = r.duration - prev_duration;
208
209 prev_count = r.count;
210 prev_duration = r.duration;
211 return r;
212 }
213};
214
215template<basic_fixed_string Tag>
216inline trace_statistics_type trace_statistics;
217
218inline wfree_unordered_map<std::string,trace_statistics_type *,MAX_NR_TRACES> trace_statistics_map;
219
220
221template<basic_fixed_string Tag, basic_fixed_string... InfoTags>
222class trace final {
223 // If this pointer is not an volatile, clang will optimize it away and replacing it
224 // with direct access to the trace_stack variable. This trace_stack variable is in local storage,
225 // so a lot of instructions and memory accesses are emitted by the compiler multiple times.
226 trace_stack_type * volatile stack;
227
228 trace_data<Tag, InfoTags...> data;
229
230 tt_no_inline static void add_to_map() {
231 trace_statistics_map.insert(Tag, &trace_statistics<Tag>);
232 statistics_start();
233 }
234
235public:
242 stack(&trace_stack), data(cpu_counter_clock::now())
243 {
244 // We don't need to know our own id, until the destructor is called.
245 // Our id will be at the top of the stack.
246 data.parent_id = stack->push();
247 }
248
249 ~trace() {
250 ttlet end_timestamp = cpu_counter_clock::now();
251
252 if(trace_statistics<Tag>.write(end_timestamp - data.timestamp)) {
253 [[unlikely]] add_to_map();
254 }
255
256 ttlet [id, is_recording] = stack->pop(data.parent_id);
257
258 // Send the log to the log thread.
259 if (is_recording) {
260 [[unlikely]] tt_log_trace("id={} {}", id, std::move(data));
261 }
262 }
263
264 trace(trace const &) = delete;
265 trace(trace &&) = delete;
266 trace &operator=(trace const &) = delete;
267 trace &operator=(trace &&) = delete;
268
269 template<basic_fixed_string InfoTag, typename T>
270 trace &set(T &&value) {
271 data.template get<InfoTag>() = std::forward<T>(value);
272 return *this;
273 }
274};
275
276
277}
Definition cpu_counter_clock.hpp:19
A fixed size (64 bits) class for a generic value type.
Definition datum.hpp:112
A static sized stack.
Definition stack.hpp:22
static time_point convert(typename fast_clock::time_point fast_time) noexcept
Definition sync_clock.hpp:253
Definition tagged_map.hpp:16
Definition trace.hpp:32
int64_t top_trace_id
Definition trace.hpp:35
std::pair< int64_t, bool > pop(int64_t parent_id) noexcept
Definition trace.hpp:63
int8_t depth
Definition trace.hpp:39
int64_t push() noexcept
Definition trace.hpp:50
int8_t record_depth
Definition trace.hpp:43
Definition trace.hpp:82
int64_t parent_id
Definition trace.hpp:86
cpu_counter_clock::time_point timestamp
Definition trace.hpp:90
Definition trace.hpp:147
bool write(cpu_counter_clock::duration const &d)
Definition trace.hpp:162
Definition trace.hpp:222
trace()
Definition trace.hpp:241
Definition version.hpp:12
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 move(T... args)
T name(T... args)