HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
trace.hpp
1// Copyright 2019 Pokitec
2// All rights reserved.
3
4#include "TTauri/Foundation/counters.hpp"
5#include "TTauri/Foundation/datum.hpp"
6#include "TTauri/Foundation/logger.hpp"
7#include "TTauri/Foundation/cpu_utc_clock.hpp"
8#include "TTauri/Foundation/required.hpp"
9#include "TTauri/Foundation/tagged_map.hpp"
10#include "TTauri/Foundation/wfree_message_queue.hpp"
11#include <fmt/ostream.h>
12#include <fmt/format.h>
13#include <atomic>
14#include <array>
15#include <utility>
16#include <ostream>
17#include <typeinfo>
18#include <typeindex>
19
20#pragma once
21
22namespace tt {
23
24constexpr int MAX_NR_TRACES = 1024;
25
26
27inline std::atomic<int64_t> trace_id = 0;
28
32 int64_t top_trace_id = 0;
33
36 int8_t depth = 0;
37
40 int8_t record_depth = 0;
41
47 inline int64_t push() noexcept {
48 ttlet parent_id = top_trace_id;
49 top_trace_id = trace_id.fetch_add(1, std::memory_order_relaxed) + 1;
50 depth++;
51 return parent_id;
52 }
53
60 inline std::pair<int64_t, bool> pop(int64_t parent_id) noexcept {
61 bool is_recording = record_depth > --depth;
62 if (is_recording) {
64 }
65
66 ttlet id = top_trace_id;
67 top_trace_id = parent_id;
68 return {id, is_recording};
69 }
70};
71
72inline thread_local trace_stack_type trace_stack;
73
76void trace_record() noexcept;
77
78template<typename Tag, typename... InfoTags>
79struct trace_data {
83 int64_t parent_id;
84
88
89 tagged_map<sdatum, InfoTags...> info;
90
91 trace_data(typename cpu_counter_clock::time_point timestamp) :
92 timestamp(timestamp) {}
93
94 trace_data() = default;
95 ~trace_data() = default;
96 trace_data(trace_data const &other) = default;
97 trace_data &operator=(trace_data const &other) = default;
98 trace_data(trace_data &&other) = default;
99 trace_data &operator=(trace_data &&other) = default;
100
101 template<typename InfoTag>
102 sdatum &get() noexcept {
103 return info.template get<InfoTag>();
104 }
105
106 template<typename InfoTag>
107 sdatum const &get() const noexcept {
108 return info.template get<InfoTag>();
109 }
110};
111
112template<typename Tag, typename... InfoTags>
113std::ostream &operator<<(std::ostream &lhs, trace_data<Tag, InfoTags...> const &rhs) {
114 auto info_string = std::string{};
115
116 auto counter = 0;
117 for (size_t i = 0; i < rhs.info.size(); i++) {
118 if (counter++ > 0) {
119 info_string += ", ";
120 }
121 info_string += rhs.info.get_tag(i).name();
122 info_string += "=";
123 info_string += static_cast<std::string>(rhs.info[i]);
124 }
125
126 lhs << fmt::format("parent={} tag={} start={} {}",
127 rhs.parent_id,
128 std::type_index(typeid(Tag)).name(),
129 format_iso8601(cpu_utc_clock::convert(rhs.timestamp)),
130 info_string
131 );
132 return lhs;
133}
134
145private:
146 std::atomic<int64_t> count = 0;
150
151 // Variables used by logger.
152 int64_t prev_count = 0;
153 typename cpu_counter_clock::duration prev_duration = {};
154
155public:
160 // In the logging thread we can check if count and version are equal
161 // to read the statistics.
162 ttlet current_count = count.fetch_add(1, std::memory_order_acquire);
163
164 duration.fetch_add(d.count(), std::memory_order_relaxed);
165
166 auto prev_peak = peak_duration.load(std::memory_order_relaxed);
167 decltype(prev_peak) new_peak;
168 do {
169 new_peak = d.count() > prev_peak ? d.count() : prev_peak;
170 } while (!peak_duration.compare_exchange_weak(prev_peak, new_peak, std::memory_order_relaxed));
171
172 version.store(current_count + 1, std::memory_order_release);
173
174 return current_count == 0;
175 }
176
177 struct read_result {
178 int64_t count;
179 int64_t last_count;
180
181 typename cpu_counter_clock::duration duration;
182 typename cpu_counter_clock::duration last_duration;
183 typename cpu_counter_clock::duration peak_duration;
184 };
185
186 read_result read() {
187 read_result r;
188
189 r.peak_duration = {};
190 do {
191 r.count = count.load(std::memory_order_acquire);
192
193 r.duration = decltype(r.duration){duration.load(std::memory_order_relaxed)};
194
195 auto tmp = peak_duration.exchange(0, std::memory_order_relaxed);
196 if (tmp > r.peak_duration.count()) {
197 r.peak_duration = decltype(r.duration){tmp};
198 }
199
200 std::atomic_thread_fence(std::memory_order_release);
201 } while (r.count != version.load(std::memory_order_relaxed));
202
203 r.last_count = r.count - prev_count;
204 r.last_duration = r.duration - prev_duration;
205
206 prev_count = r.count;
207 prev_duration = r.duration;
208 return r;
209 }
210};
211
212template<typename Tag>
213inline trace_statistics_type trace_statistics;
214
215inline wfree_unordered_map<std::type_index,trace_statistics_type *,MAX_NR_TRACES> trace_statistics_map;
216
217
218template<typename Tag, typename... InfoTags>
219class trace final {
220 // If this pointer is not an volatile, clang will optimize it away and replacing it
221 // with direct access to the trace_stack variable. This trace_stack variable is in local storage,
222 // so a lot of instructions and memory accesses are emitted by the compiler multiple times.
223 trace_stack_type * volatile stack;
224
225 trace_data<Tag, InfoTags...> data;
226
227 tt_no_inline static void add_to_map() {
228 trace_statistics_map.insert(std::type_index(typeid(Tag)), &trace_statistics<Tag>);
229 }
230
231public:
238 stack(&trace_stack), data(cpu_counter_clock::now())
239 {
240 // We don't need to know our own id, until the destructor is called.
241 // Our id will be at the top of the stack.
242 data.parent_id = stack->push();
243 }
244
245 tt_force_inline ~trace() {
246 ttlet end_timestamp = cpu_counter_clock::now();
247
248 if(tt_unlikely(trace_statistics<Tag>.write(end_timestamp - data.timestamp))) {
249 add_to_map();
250 }
251
252 ttlet [id, is_recording] = stack->pop(data.parent_id);
253
254 // Send the log to the log thread.
255 if (tt_unlikely(is_recording)) {
256 logger.log<log_level::Trace>(end_timestamp, "id={} {}", id, std::move(data));
257 }
258 }
259
260 trace(trace const &) = delete;
261 trace(trace &&) = delete;
262 trace &operator=(trace const &) = delete;
263 trace &operator=(trace &&) = delete;
264
265 template<typename InfoTag, typename T>
266 trace &set(T &&value) {
267 data.template get<InfoTag>() = std::forward<T>(value);
268 return *this;
269 }
270};
271
272
273}
Definition cpu_counter_clock.hpp:18
A fixed size (64 bits) class for a generic value type.
Definition datum.hpp:106
static time_point convert(typename fast_clock::time_point fast_time) noexcept
Definition sync_clock.hpp:252
Definition tagged_map.hpp:14
Definition trace.hpp:29
int64_t top_trace_id
Definition trace.hpp:32
std::pair< int64_t, bool > pop(int64_t parent_id) noexcept
Definition trace.hpp:60
int8_t depth
Definition trace.hpp:36
int64_t push() noexcept
Definition trace.hpp:47
int8_t record_depth
Definition trace.hpp:40
Definition trace.hpp:79
int64_t parent_id
Definition trace.hpp:83
cpu_counter_clock::time_point timestamp
Definition trace.hpp:87
Definition trace.hpp:144
bool write(cpu_counter_clock::duration const &d)
Definition trace.hpp:159
Definition trace.hpp:219
trace()
Definition trace.hpp:237
Definition version.hpp:10
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)