HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
counters.hpp
Go to the documentation of this file.
1// Copyright Take Vos 2019, 2021-2022.
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
8#pragma once
9
10#include "log.hpp"
11#include "../utility/utility.hpp"
12#include "../concurrency/concurrency.hpp"
13#include "../concurrency/unfair_mutex.hpp" // XXX #616
14#include "../concurrency/thread.hpp" // XXX #616
15#include "../time/time.hpp"
16#include "../macros.hpp"
17#include <string>
18#include <atomic>
19#include <map>
20#include <memory>
21#include <mutex>
22#include <chrono>
23#include <limits>
24#include <concepts>
25
26hi_export_module(hikogui.telemetry : counters);
27
28
29hi_export namespace hi::inline v1 {
30namespace detail {
31
32class counter {
33public:
40 [[nodiscard]] static counter *get_if(std::string const& name) noexcept
41 {
42 auto const lock = std::scoped_lock(_mutex);
43 auto const& map_ = _map.get_or_make();
44 auto const it = map_.find(name);
45 if (it == map_.cend()) {
46 return nullptr;
47 } else {
48 hi_assert(it->second);
49 return it->second;
50 }
51 }
52
53 counter(counter const&) = delete;
54 counter(counter&&) = delete;
55 counter& operator=(counter const&) = delete;
56 counter& operator=(counter&&) = delete;
57
58 constexpr counter() noexcept {}
59
60 operator uint64_t() const noexcept
61 {
62 return _total_count.load(std::memory_order::relaxed);
63 }
64
65 static void log() noexcept
66 {
67 auto const lock = std::scoped_lock(_mutex);
68 log_header();
69 for (auto const & [ string, counter ] : _map.get_or_make()) {
70 hi_assert(counter);
71 counter->log(string);
72 }
73 }
74
75 static void log_header() noexcept
76 {
77 hi_log_statistics("");
78 hi_log_statistics("{:>18} {:>9} {:>10} {:>10} {:>10}", "total", "delta", "min", "max", "mean");
79 hi_log_statistics("------------------ --------- ---------- ---------- ----------");
80 }
81
86 void log(std::string const& tag) noexcept
87 {
88 auto const total_count = _total_count.load(std::memory_order::relaxed);
89 auto const prev_count = _prev_count.exchange(_total_count, std::memory_order::relaxed);
90 auto const delta_count = total_count - prev_count;
91 if (delta_count != 0) {
92 auto const duration_max = time_stamp_count::duration_from_count(_duration_max.exchange(0, std::memory_order::relaxed));
93 auto const duration_min = time_stamp_count::duration_from_count(
94 _duration_min.exchange(std::numeric_limits<uint64_t>::max(), std::memory_order::relaxed));
95
96 auto const duration_avg = _duration_avg.exchange(0, std::memory_order::relaxed);
97 if (duration_avg == 0) {
98 hi_log_statistics("{:>18} {:>+9} {:10} {:10} {:10} {}", total_count, delta_count, "", "", "", tag);
99
100 } else {
101 auto const avg_count = duration_avg & 0xffff;
102 auto const avg_sum = duration_avg >> 16;
103 auto const average = time_stamp_count::duration_from_count(avg_sum / avg_count);
104
105 hi_log_statistics(
106 "{:18d} {:+9d} {:>10} {:>10} {:>10} {}",
107 total_count,
108 delta_count,
109 format_engineering(duration_min),
110 format_engineering(duration_max),
111 format_engineering(average),
112 tag);
113 }
114 }
115 }
116
121 counter &operator=(std::integral auto count) noexcept
122 {
123 hi_axiom(count >= 0);
124 _total_count.store(count, std::memory_order::relaxed);
125 return *this;
126 }
127
128 uint64_t operator++() noexcept
129 {
130 return _total_count.fetch_add(1, std::memory_order::relaxed) + 1;
131 }
132
133 uint64_t operator++(int) noexcept
134 {
135 return _total_count.fetch_add(1, std::memory_order::relaxed);
136 }
137
138 uint64_t operator--() noexcept
139 {
140 return _total_count.fetch_sub(1, std::memory_order::relaxed) + 1;
141 }
142
143 uint64_t operator--(int) noexcept
144 {
145 return _total_count.fetch_sub(1, std::memory_order::relaxed);
146 }
147
150 void add_duration(uint64_t duration) noexcept
151 {
152 _total_count.fetch_add(1, std::memory_order::relaxed);
153 fetch_max(_duration_max, duration, std::memory_order::relaxed);
154 fetch_min(_duration_min, duration, std::memory_order::relaxed);
155
156 // Combine duration with count in a single atomic.
157 hi_axiom(duration <= (std::numeric_limits<uint64_t>::max() >> 10));
158 duration <<= 16;
159 ++duration;
160 _duration_avg.fetch_add(duration, std::memory_order::relaxed);
161 }
162
163protected:
164 using map_type = std::map<std::string, counter *>;
165
169 constinit static inline unfair_mutex_impl<false> _mutex;
170 constinit static inline atomic_unique_ptr<map_type> _map;
171
172 std::atomic<uint64_t> _total_count = 0;
173 std::atomic<uint64_t> _prev_count = 0;
174 std::atomic<uint64_t> _duration_max = 0;
176
182 std::atomic<uint64_t> _duration_avg = 0;
183};
184
185template<fixed_string Tag>
186class tagged_counter : public counter {
187public:
188 using counter::operator=;
189
190 tagged_counter() noexcept : counter()
191 {
192 auto const lock = std::scoped_lock(_mutex);
193 _map.get_or_make()[std::string{Tag}] = this;
194 }
195};
196
197} // namespace detail
198
199template<fixed_string Tag>
200inline detail::tagged_counter<Tag> global_counter;
201
202[[nodiscard]] inline detail::counter *get_global_counter_if(std::string const& name)
203{
204 return detail::counter::get_if(name);
205}
206
207inline void log::log_thread_main(std::stop_token stop_token) noexcept
208{
209 using namespace std::chrono_literals;
210
211 set_thread_name("log");
212 hi_log_info("log thread started");
213
214 auto counter_statistics_deadline = std::chrono::utc_clock::now() + 1min;
215
216 while (not stop_token.stop_requested()) {
217 log_global.flush();
218
219 auto const now = std::chrono::utc_clock::now();
220 if (now >= counter_statistics_deadline) {
221 counter_statistics_deadline = now + 1min;
222 detail::counter::log();
223 }
224
226 }
227
228 hi_log_info("log thread finished");
229}
230
231} // namespace hi::inline v1
Functions and types for accessing operating system threads.
Definition of the unfair_mutex.
void set_thread_name(std::string_view name) noexcept
Set the name of the current thread.
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
T fetch_max(std::atomic< T > &lhs, T rhs, std::memory_order order) noexcept
Lock-free fetch-then-max operation on an atomic.
Definition atomic.hpp:25
T fetch_min(std::atomic< T > &lhs, T rhs, std::memory_order order) noexcept
Lock-free fetch-then-min operation on an atomic.
Definition atomic.hpp:39
Definition atomic.hpp:51
Definition counters.hpp:32
static counter * get_if(std::string const &name) noexcept
Get the named counter.
Definition counters.hpp:40
void log(std::string const &tag) noexcept
Log the counter.
Definition counters.hpp:86
void add_duration(uint64_t duration) noexcept
Add a duration.
Definition counters.hpp:150
counter & operator=(std::integral auto count) noexcept
Rest the counter.
Definition counters.hpp:121
Definition counters.hpp:186
hi_no_inline void flush() noexcept
Flush all messages from the log_queue directly from this thread.
Definition log.hpp:147
T max(T... args)
T sleep_for(T... args)