perfc 0.11.0
Loading...
Searching...
No Matches
register.hpp
Go to the documentation of this file.
1/**
2 * @file
3 * @ingroup perfc_register
4 * @copyright (c) Copyright ESO 2024
5 * All Rights Reserved
6 * ESO (eso.org) is an Intergovernmental Organisation, and therefore special legal conditions apply.
7 *
8 * @brief Implementation of the counter register perfc::Register
9 *
10 * @defgroup perfc_register Counter Register
11 * @brief Keep track of counters.
12 * @ingroup perfc
13 *
14 * Main type is @ref perfc::Register.
15 */
16#ifndef PERFC_REGISTER_HPP_
17#define PERFC_REGISTER_HPP_
18#include <algorithm>
19#include <cstdint>
20#include <functional>
21#include <mutex>
22#include <variant>
23#include <vector>
24
25namespace perfc {
26
27/**
28 * Helper used when declaring counter types in perfc::Register.
29 *
30 * Example:
31 * @code
32 * // Specify the counter types that should be supported by our Register
33 * using Counters = perfc::CounterTypes<perfc::CounterDouble,
34 * perfc::CounterU64>;
35 * // Metadata using simple string for this example.
36 * using Metadata = std::string;
37 *
38 * // Specify the counters as first template parameter.
39 * using Register = perfc::Register<Counters, Metadata>;
40 * @endcode
41 *
42 * @ingroup perfc_register
43 * @headerfile <> <perfc/register.hpp>
44 */
45template <class... CounterType>
47 using CounterVariant = std::variant<std::add_pointer_t<CounterType>...>;
48};
49
50/**
51 * Simple type that provide synchronized access by holding a lock to T container.
52 *
53 * Accessing the locked object is peformed using pointer semantics.
54 * @ingroup perfc_register
55 */
56template <class T, class Mutex>
57class Locked {
58public:
59 using PointerType = std::add_pointer_t<T>;
60 using ReferenceType = std::add_lvalue_reference_t<T>;
61
62 /**
63 * Construct by providing reference to object requiring synchronized access and the mutex
64 * protecting it.
65 *
66 * @param t Object to obtain exclusive access to.
67 * @param mutex Mutex protecting @a t.
68 * @throws Exception originating from `Mutex::lock()` (typically `std::system_error`)
69 */
70 Locked(ReferenceType t, Mutex& mutex) : m_lock(mutex), m_ptr(&t) {
71 }
72
73 /**
74 * Move constructs by taking owneship of internal state of @a other.
75 * @param other Object to move from.
76 */
77 Locked(Locked&& other) noexcept : m_lock(std::move(other.m_lock)), m_ptr(other.m_ptr) {
78 if (m_ptr) {
79 other.m_ptr = nullptr;
80 }
81 }
82
83 /**
84 * Move constructs by taking owneship of internal state of @a other.
85 * @param other Object to move from.
86 */
87 Locked& operator=(Locked&& other) noexcept {
88 if (other.m_ptr) {
89 m_ptr = other.m_ptr;
90 m_lock = std::move(other.m_lock);
91 other.m_ptr = nullptr;
92 }
93 }
94
95 /**
96 * @return Reference to underlying object.
97 */
98 PointerType Get() const noexcept {
99 return m_ptr;
100 }
101 /**
102 * @return Reference to underlying object.
103 */
104 ReferenceType operator*() const noexcept {
105 return *m_ptr;
106 }
107 /**
108 * @return Pointer to underlying object.
109 */
110 PointerType operator->() const noexcept {
111 return m_ptr;
112 }
113
114 /**
115 * @returns true if to underlying object is valid, false otherwise.
116 */
117 operator bool() const noexcept {
118 return m_ptr != nullptr;
119 }
120
121 /**
122 * Unlocks lock.
123 *
124 * @note There is no way to re-acquire lock after this.
125 *
126 * @post `Get() == nullptr`
127 */
128 void Unlock() noexcept {
129 if (m_ptr) {
130 m_ptr = nullptr;
131 m_lock.unlock();
132 }
133 }
134
135private:
136 std::unique_lock<Mutex> m_lock;
137 PointerType m_ptr;
138};
139
140template <class CounterType, class MetadataType, class Mutex>
141class Register;
142
143/**
144 * RAII object to manage automatic deregistration.
145 *
146 * @thread_compatible
147 * @ingroup perfc_register
148 */
150public:
151 ScopedRegistration() noexcept = default;
153 ScopedRegistration& operator=(ScopedRegistration&&) noexcept = default;
154 ~ScopedRegistration() noexcept {
155 Reset();
156 }
157 /**
158 * @name Observers
159 */
160 /// @{
161 /**
162 * @returns true if it still manages a deregistration.
163 */
164 bool IsValid() const noexcept {
165 return m_dereg != nullptr;
166 }
167 /**
168 * @returns true if it still manages a deregistration.
169 */
170 operator bool() const noexcept {
171 return m_dereg != nullptr;
172 }
173 /// @}
174 /**
175 * @name Modifiers
176 */
177 /// @{
178 /**
179 * Release ownership such that destroying object will not deregister counter.
180 *
181 * @post `IsValid() == false`
182 */
183 void Release() noexcept {
184 m_dereg = nullptr;
185 }
186
187 /**
188 * Deregisters counter if object is managing a registration.
189 *
190 * @post `IsValid() == false`
191 */
192 void Reset() noexcept {
193 if (m_dereg) {
194 m_dereg();
195 Release();
196 }
197 }
198 /**
199 * Swaps two entries.
200 *
201 * @param other Exchanges ownership between `*this` and @c other.
202 */
203 void Swap(ScopedRegistration& other) noexcept;
204 /// @}
205protected:
206 /**
207 * @note From C++20 `std::function(function&&)` is noexcept
208 */
209 template <class CounterType, class MetadataType, class Mutex>
210 friend class Register;
211 ScopedRegistration(std::function<void()> dereg) : m_dereg(std::move(dereg)){};
212
213private:
214 std::function<void()> m_dereg = nullptr;
215};
216
217/**
218 * Register of counter variants, used for example to facilitate discovery/monitoring.
219 *
220 * @note A counter is uniquely identified by its address. If a counter is moved it must first be
221 * deregistered and then re-registered afterwards.
222 *
223 * To define a register for `double` and `std::uint64_t` and a `std::string` as metadata:
224 *
225 * @code
226 * #include <string>
227 * #include <perfc/perfc.hpp>
228 *
229 * using Counters = perfc::CounterTypes<perfc::CounterDouble,
230 * perfc::CounterU64>;
231 * using Register = perfc::Register<Counters, std::string>;
232 *
233 * Register register;
234 *
235 * perfc::CounterDouble counter;
236 * ScopedRegistration reg = register.Add(&counter, "metadata for counter");
237 *
238 * @endcode
239 *
240 * @thread_safe{All operations take a mutex lock and are thread safe if Mutex type provides mutual
241 * exclusion (i.e. not a null-opt mutex).
242 * Register::Lock() returns a pointer-like object that holds a lock, providing mutual exclusion.
243 * }
244 *
245 * @headerfile <> <perfc/register.hpp>
246 * @tparam CounterTypes Specifies which counter types can be registered using the helper
247 * perfc::CounterTypes.
248 * @tparam TMetadata Specifies the metadata type used to associate each registered counter with.
249 * Type requirements: *CopyConstructible* and *CopyAssignable*
250 *
251 * @tparam TMutex The mutex type to use, defaults to std::recursive_mutex.
252 *
253 * @ingroup perfc_register
254 *
255 */
256template <class CounterTypes, class TMetadata, class TMutex = std::recursive_mutex>
257class Register {
258public:
259 using Mutex = TMutex;
260 using MetadataType = TMetadata;
262
263 /**
264 * Register entry representing the counter and associated metadata.
265 */
270
271 using Container = std::vector<Entry>;
274
275 /**
276 * @name Access container of counters thread safely
277 * Obtains proxy object to counters that holds lock for synchronized access.
278 *
279 * Prefer a new nested scope to minimize lock duration:
280 * @code
281 * // Minimal scope for Lock()
282 * {
283 * auto counters = register.Lock();
284 * for (auto const& counter : *counters) {
285 * ...
286 * }
287 * }
288 * @endcode
289 * @throws Exception originating from `Mutex::lock()`
290 */
291 /// @{
292 /** @returns locked access to container of counters. */
294 return LockedContainer(m_counters, m_mutex);
295 }
296 /** @returns locked access to *const* container of counters. */
298 return LockedContainerConst(m_counters, m_mutex);
299 }
300 /// @}
301
302 /**
303 * @name Counter registration
304 *
305 * @note Manual de-registration with Register::Remove is not necessary unless ScopedRegistration
306 * is released from managing the registration.
307 */
308 /// @{
309 /**
310 * Add a counter, identified by its *address*, together with metadata to the register.
311 *
312 * @pre counter must point to a valid CounterType object.
313 *
314 * @note The caller must ensure that the @a counter is deregistered before the counter is
315 * deleted. The returned object aims to facilitate this.
316 *
317 * @note May invalidate iterators.
318 *
319 * @param counter Pointer to any of the supported counter types.
320 * @param metadata A metadata object to be associated with @a counter.
321 *
322 * @returns Empty ScopedRegistration if counter already has been registered before.
323 * @returns Non-empty RAII registration management object that automatically deregisters the
324 * counter when destroyed (or manually released).
325 *
326 * @throw std::exception-derived exceptions originating from standard library.
327 */
328 [[nodiscard]] ScopedRegistration Add(CounterVariant counter, MetadataType metadata) {
329 std::unique_lock<Mutex> lk(m_mutex);
330 if (auto it = FindCounter(counter); it != std::end(m_counters)) {
331 return ScopedRegistration();
332 }
333 ScopedRegistration entry([counter, this]() { this->Remove(counter); });
334 m_counters.push_back({std::move(counter), std::move(metadata)});
335 return entry;
336 }
337
338 /**
339 * Remove counter from register.
340 *
341 * @note May invalidate iterators.
342 * @return true if counter was found and erased, false otherwise.
343 *
344 * @throw std::exception-derived exceptions originating from standard library.
345 */
346 bool Remove(CounterVariant counter) {
347 std::unique_lock<Mutex> lk(m_mutex);
348
349 if (auto it = FindCounter(counter); it != std::end(m_counters)) {
350 m_counters.erase(it);
351 return true;
352 }
353 return false;
354 }
355 /// @}
356
357private:
358 typename Container::iterator FindCounter(CounterVariant counter) noexcept {
359 return std::find_if(
360 std::begin(m_counters), std::end(m_counters), [&counter](Entry const& entry) -> bool {
361 return entry.counter == counter;
362 });
363 }
364 mutable Mutex m_mutex;
365 Container m_counters;
366 std::uint64_t m_next_id;
367};
368
369} // namespace perfc
370#endif // #ifndef PERFC_REGISTER_HPP_
Simple type that provide synchronized access by holding a lock to T container.
Definition register.hpp:57
void Unlock() noexcept
Unlocks lock.
Definition register.hpp:128
ReferenceType operator*() const noexcept
Definition register.hpp:104
std::add_lvalue_reference_t< T > ReferenceType
Definition register.hpp:60
std::add_pointer_t< T > PointerType
Definition register.hpp:59
PointerType operator->() const noexcept
Definition register.hpp:110
Locked & operator=(Locked &&other) noexcept
Move constructs by taking owneship of internal state of other.
Definition register.hpp:87
Locked(Locked &&other) noexcept
Move constructs by taking owneship of internal state of other.
Definition register.hpp:77
PointerType Get() const noexcept
Definition register.hpp:98
Locked(ReferenceType t, Mutex &mutex)
Construct by providing reference to object requiring synchronized access and the mutex protecting it.
Definition register.hpp:70
Register of counter variants, used for example to facilitate discovery/monitoring.
Definition register.hpp:257
Locked< Container const, Mutex > LockedContainerConst
Definition register.hpp:273
LockedContainerConst Lock() const
Definition register.hpp:297
typename CounterTypes::CounterVariant CounterVariant
Definition register.hpp:261
ScopedRegistration Add(CounterVariant counter, MetadataType metadata)
Add a counter, identified by its address, together with metadata to the register.
Definition register.hpp:328
bool Remove(CounterVariant counter)
Remove counter from register.
Definition register.hpp:346
Locked< Container, Mutex > LockedContainer
Definition register.hpp:272
std::vector< Entry > Container
Definition register.hpp:271
TMetadata MetadataType
Definition register.hpp:260
LockedContainer Lock()
Definition register.hpp:293
RAII object to manage automatic deregistration.
Definition register.hpp:149
ScopedRegistration(std::function< void()> dereg)
Definition register.hpp:211
bool IsValid() const noexcept
Definition register.hpp:164
void Reset() noexcept
Deregisters counter if object is managing a registration.
Definition register.hpp:192
ScopedRegistration() noexcept=default
void Swap(ScopedRegistration &other) noexcept
Swaps two entries.
void Release() noexcept
Release ownership such that destroying object will not deregister counter.
Definition register.hpp:183
Helper used when declaring counter types in perfc::Register.
Definition register.hpp:46
std::variant< std::add_pointer_t< CounterType >... > CounterVariant
Definition register.hpp:47
Register entry representing the counter and associated metadata.
Definition register.hpp:266
CounterVariant counter
Definition register.hpp:267
MetadataType metadata
Definition register.hpp:268