timemory 3.3.0
Modular C++ Toolkit for Performance Analysis and Logging. Profiling API and Tools for C, C++, CUDA, Fortran, and Python. The C++ template API is essentially a framework to creating tools: it is designed to provide a unifying interface for recording various performance measurements alongside data logging and interfaces to other tools.
ring_buffer.hpp
Go to the documentation of this file.
1// MIT License
2//
3// Copyright (c) 2020, The Regents of the University of California,
4// through Lawrence Berkeley National Laboratory (subject to receipt of any
5// required approvals from the U.S. Dept. of Energy). All rights reserved.
6//
7// Permission is hereby granted, free of charge, to any person obtaining a copy
8// of this software and associated documentation files (the "Software"), to deal
9// in the Software without restriction, including without limitation the rights
10// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11// copies of the Software, and to permit persons to whom the Software is
12// furnished to do so, subject to the following conditions:
13//
14// The above copyright notice and this permission notice shall be included in all
15// copies or substantial portions of the Software.
16//
17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23// SOFTWARE.
24
25#pragma once
26
27#include <algorithm>
28#include <cmath>
29#include <functional>
30#include <iomanip>
31#include <iostream>
32#include <sstream>
33#include <stdexcept>
34#include <utility>
35#include <vector>
36
37namespace tim
38{
39namespace data_storage
40{
41template <typename Tp>
42struct ring_buffer;
43}
44//
45namespace base
46{
47/// \struct tim::base::ring_buffer
48/// \brief Ring buffer implementation, with support for mmap as backend (Linux only).
50{
51 template <typename Tp>
53
54 ring_buffer() = default;
55 explicit ring_buffer(bool _use_mmap) { set_use_mmap(_use_mmap); }
56 explicit ring_buffer(size_t _size) { init(_size); }
57 ring_buffer(size_t _size, bool _use_mmap);
58
60
62 ring_buffer(ring_buffer&&) noexcept = delete;
63
64 ring_buffer& operator=(const ring_buffer&);
65 ring_buffer& operator=(ring_buffer&&) noexcept = delete;
66
67 /// Returns whether the buffer has been allocated
68 bool is_initialized() const { return m_init; }
69
70 /// Get the total number of bytes supported
71 size_t capacity() const { return m_size; }
72
73 /// Creates new ring buffer.
74 void init(size_t size);
75
76 /// Destroy ring buffer.
77 void destroy();
78
79 /// Write class-type data to buffer (uses placement new).
80 template <typename Tp>
81 std::pair<size_t, Tp*> write(Tp* in,
82 std::enable_if_t<std::is_class<Tp>::value, int> = 0);
83
84 /// Write non-class-type data to buffer (uses memcpy).
85 template <typename Tp>
86 std::pair<size_t, Tp*> write(Tp* in,
87 std::enable_if_t<!std::is_class<Tp>::value, int> = 0);
88
89 /// Request a pointer to an allocation. This is similar to a "write" except the
90 /// memory is uninitialized. Typically used by allocators. If Tp is a class type,
91 /// be sure to use a placement new instead of a memcpy.
92 template <typename Tp>
93 Tp* request();
94
95 /// Request a pointer to an allocation for at least \param n bytes.
96 void* request(size_t n);
97
98 /// Read class-type data from buffer (uses placement new).
99 template <typename Tp>
100 std::pair<size_t, Tp*> read(
101 Tp* out, std::enable_if_t<std::is_class<Tp>::value, int> = 0) const;
102
103 /// Read non-class-type data from buffer (uses memcpy).
104 template <typename Tp>
105 std::pair<size_t, Tp*> read(
106 Tp* out, std::enable_if_t<!std::is_class<Tp>::value, int> = 0) const;
107
108 /// Retrieve a pointer to the head allocation (read).
109 template <typename Tp>
110 Tp* retrieve();
111
112 /// Retrieve a pointer to the head allocation of at least \param n bytes (read).
113 void* retrieve(size_t n);
114
115 /// Returns number of bytes currently held by the buffer.
116 size_t count() const { return (m_write_count - m_read_count); }
117
118 /// Returns how many bytes are availiable in the buffer.
119 size_t free() const { return (m_size - count()); }
120
121 /// Returns if the buffer is empty.
122 bool is_empty() const { return (count() == 0); }
123
124 /// Returns if the buffer is full.
125 bool is_full() const { return (count() == m_size); }
126
127 /// Rewind the read position n bytes
128 size_t rewind(size_t n) const;
129
130 /// explicitly configure to use mmap if avail
131 void set_use_mmap(bool);
132
133 /// query whether using mmap
134 bool get_use_mmap() const { return m_use_mmap; }
135
136 std::string as_string() const;
137
138 friend std::ostream& operator<<(std::ostream& os, const ring_buffer& obj)
139 {
140 return os << obj.as_string();
141 }
142
143private:
144 /// Returns the current write pointer.
145 void* write_ptr() const
146 {
147 return static_cast<char*>(m_ptr) + (m_write_count % m_size);
148 }
149
150 /// Returns the current read pointer.
151 void* read_ptr() const { return static_cast<char*>(m_ptr) + (m_read_count % m_size); }
152
153private:
154 bool m_init = false;
155 bool m_use_mmap = true;
156 bool m_use_mmap_explicit = false;
157 int m_fd = 0;
158 void* m_ptr = nullptr;
159 size_t m_size = 0;
160 mutable size_t m_read_count = 0;
161 size_t m_write_count = 0;
162};
163//
164template <typename Tp>
165std::pair<size_t, Tp*>
166ring_buffer::write(Tp* in, std::enable_if_t<std::is_class<Tp>::value, int>)
167{
168 if(in == nullptr || m_ptr == nullptr)
169 return { 0, nullptr };
170
171 auto _length = sizeof(Tp);
172
173 // Make sure we don't put in more than there's room for, by writing no
174 // more than there is free.
175 if(_length > free())
176 throw std::runtime_error("heap-buffer-overflow :: ring buffer is full. read data "
177 "to avoid data corruption");
178
179 // if write count is at the tail of buffer, bump to the end of buffer
180 auto _modulo = m_size - (m_write_count % m_size);
181 if(_modulo < _length)
182 m_write_count += _modulo;
183
184 // pointer in buffer
185 Tp* out = reinterpret_cast<Tp*>(write_ptr());
186
187 // Copy in.
188 new((void*) out) Tp{ std::move(*in) };
189
190 // Update write count
191 m_write_count += _length;
192
193 return { _length, out };
194}
195//
196template <typename Tp>
197std::pair<size_t, Tp*>
198ring_buffer::write(Tp* in, std::enable_if_t<!std::is_class<Tp>::value, int>)
199{
200 if(in == nullptr || m_ptr == nullptr)
201 return { 0, nullptr };
202
203 auto _length = sizeof(Tp);
204
205 // Make sure we don't put in more than there's room for, by writing no
206 // more than there is free.
207 if(_length > free())
208 throw std::runtime_error("heap-buffer-overflow :: ring buffer is full. read data "
209 "to avoid data corruption");
210
211 // if write count is at the tail of buffer, bump to the end of buffer
212 auto _modulo = m_size - (m_write_count % m_size);
213 if(_modulo < _length)
214 m_write_count += _modulo;
215
216 // pointer in buffer
217 Tp* out = reinterpret_cast<Tp*>(write_ptr());
218
219 // Copy in.
220 memcpy((void*) out, in, _length);
221
222 // Update write count
223 m_write_count += _length;
224
225 return { _length, out };
226}
227//
228template <typename Tp>
229Tp*
231{
232 if(m_ptr == nullptr)
233 return nullptr;
234
235 auto _length = sizeof(Tp);
236
237 // Make sure we don't put in more than there's room for, by writing no
238 // more than there is free.
239 if(_length > free())
240 throw std::runtime_error("heap-buffer-overflow :: ring buffer is full. read data "
241 "to avoid data corruption");
242
243 // if write count is at the tail of buffer, bump to the end of buffer
244 auto _modulo = m_size - (m_write_count % m_size);
245 if(_modulo < _length)
246 m_write_count += _modulo;
247
248 // pointer in buffer
249 Tp* _out = reinterpret_cast<Tp*>(write_ptr());
250
251 // Update write count
252 m_write_count += _length;
253
254 return _out;
255}
256//
257template <typename Tp>
258std::pair<size_t, Tp*>
259ring_buffer::read(Tp* out, std::enable_if_t<std::is_class<Tp>::value, int>) const
260{
261 if(is_empty() || out == nullptr)
262 return { 0, nullptr };
263
264 auto _length = sizeof(Tp);
265
266 // Make sure we do not read out more than there is actually in the buffer.
267 if(_length > count())
268 throw std::runtime_error("ring buffer is empty");
269
270 // if read count is at the tail of buffer, bump to the end of buffer
271 auto _modulo = m_size - (m_read_count % m_size);
272 if(_modulo < _length)
273 m_read_count += _modulo;
274
275 // pointer in buffer
276 Tp* in = reinterpret_cast<Tp*>(read_ptr());
277
278 // Copy out for BYTE, nothing magic here.
279 *out = *in;
280
281 // Update read count.
282 m_read_count += _length;
283
284 return { _length, in };
285}
286//
287template <typename Tp>
288std::pair<size_t, Tp*>
289ring_buffer::read(Tp* out, std::enable_if_t<!std::is_class<Tp>::value, int>) const
290{
291 if(is_empty() || out == nullptr)
292 return { 0, nullptr };
293
294 auto _length = sizeof(Tp);
295
296 using Up = typename std::remove_const<Tp>::type;
297
298 // Make sure we do not read out more than there is actually in the buffer.
299 if(_length > count())
300 throw std::runtime_error("ring buffer is empty");
301
302 // if read count is at the tail of buffer, bump to the end of buffer
303 auto _modulo = m_size - (m_read_count % m_size);
304 if(_modulo < _length)
305 m_read_count += _modulo;
306
307 // pointer in buffer
308 Tp* in = reinterpret_cast<Tp*>(read_ptr());
309
310 // Copy out for BYTE, nothing magic here.
311 Up* _out = const_cast<Up*>(out);
312 memcpy(_out, in, _length);
313
314 // Update read count.
315 m_read_count += _length;
316
317 return { _length, in };
318}
319//
320template <typename Tp>
321Tp*
323{
324 if(m_ptr == nullptr)
325 return nullptr;
326
327 auto _length = sizeof(Tp);
328
329 // Make sure we don't put in more than there's room for, by writing no
330 // more than there is free.
331 if(_length > count())
332 throw std::runtime_error("ring buffer is empty");
333
334 // if read count is at the tail of buffer, bump to the end of buffer
335 auto _modulo = m_size - (m_read_count % m_size);
336 if(_modulo < _length)
337 m_read_count += _modulo;
338
339 // pointer in buffer
340 Tp* _out = reinterpret_cast<Tp*>(read_ptr());
341
342 // Update write count
343 m_read_count += _length;
344
345 return _out;
346}
347//
348} // namespace base
349//
350namespace data_storage
351{
352/// \struct tim::data_storage::ring_buffer
353/// \brief Ring buffer wrapper around \ref tim::base::ring_buffer for data of type Tp. If
354/// the data object size is larger than the page size (typically 4KB), behavior is
355/// undefined. During initialization, one requests a minimum number of objects and the
356/// buffer will support that number of object + the remainder of the page, e.g. if a page
357/// is 1000 bytes, the object is 1 byte, and the buffer is requested to support 1500
358/// objects, then an allocation supporting 2000 objects (i.e. 2 pages) will be created.
359template <typename Tp>
361{
363
364 ring_buffer() = default;
365 ~ring_buffer() = default;
366
367 explicit ring_buffer(bool _use_mmap)
368 : base_type{ _use_mmap }
369 {}
370
371 explicit ring_buffer(size_t _size)
372 : base_type{ _size * sizeof(Tp) }
373 {}
374
375 ring_buffer(size_t _size, bool _use_mmap)
376 : base_type{ _size * sizeof(Tp), _use_mmap }
377 {}
378
379 ring_buffer(const ring_buffer&);
380 ring_buffer(ring_buffer&&) noexcept = default;
381
382 ring_buffer& operator=(const ring_buffer&);
383 ring_buffer& operator=(ring_buffer&&) noexcept = default;
384
385 /// Returns whether the buffer has been allocated
386 bool is_initialized() const { return base_type::is_initialized(); }
387
388 /// Get the total number of Tp instances supported
389 size_t capacity() const { return (base_type::capacity()) / sizeof(Tp); }
390
391 /// Creates new ring buffer.
392 void init(size_t _size) { base_type::init(_size * sizeof(Tp)); }
393
394 /// Destroy ring buffer.
396
397 /// Write data to buffer.
398 size_t data_size() const { return sizeof(Tp); }
399
400 /// Write data to buffer. Return pointer to location of write
401 Tp* write(Tp* in) { return add_copy(base_type::write<Tp>(in).second); }
402
403 /// Read data from buffer. Return pointer to location of read
404 Tp* read(Tp* out) const { return remove_copy(base_type::read<Tp>(out).second); }
405
406 /// Get an uninitialized address at tail of buffer.
407 Tp* request() { return add_copy(base_type::request<Tp>()); }
408
409 /// Read data from head of buffer.
410 Tp* retrieve() { return remove_copy(base_type::retrieve<Tp>()); }
411
412 /// Returns number of Tp instances currently held by the buffer.
413 size_t count() const { return (base_type::count()) / sizeof(Tp); }
414
415 /// Returns how many Tp instances are availiable in the buffer.
416 size_t free() const { return (base_type::free()) / sizeof(Tp); }
417
418 /// Returns if the buffer is empty.
419 bool is_empty() const { return base_type::is_empty(); }
420
421 /// Returns if the buffer is full.
422 bool is_full() const { return (base_type::free() < sizeof(Tp)); }
423
424 /// Rewinds the read pointer
425 size_t rewind(size_t n) const { return base_type::rewind(n); }
426
427 template <typename... Args>
428 auto emplace(Args&&... args)
429 {
430 Tp _obj{ std::forward<Args>(args)... };
431 return write(&_obj);
432 }
433
436
438 {
439 std::ostringstream ss{};
440 size_t _w = std::log10(base_type::capacity()) + 1;
441 ss << std::boolalpha << std::right << "data size: " << std::setw(_w)
442 << data_size() << " B, is_initialized: " << std::setw(5) << is_initialized()
443 << ", is_empty: " << std::setw(5) << is_empty()
444 << ", is_full: " << std::setw(5) << is_full()
445 << ", capacity: " << std::setw(_w) << capacity()
446 << ", count: " << std::setw(_w) << count() << ", free: " << std::setw(_w)
447 << free() << ", raw capacity: " << std::setw(_w) << base_type::capacity()
448 << " B, raw count: " << std::setw(_w) << base_type::count()
449 << " B, raw free: " << std::setw(_w) << base_type::free()
450 << " B, pointer: " << std::setw(15) << base_type::m_ptr
451 << ", raw read count: " << std::setw(_w) << base_type::m_read_count
452 << ", raw write count: " << std::setw(_w) << base_type::m_write_count;
453 return ss.str();
454 }
455
456 friend std::ostream& operator<<(std::ostream& os, const ring_buffer& obj)
457 {
458 return os << obj.as_string();
459 }
460
461private:
462 using copy_function_t = std::function<void(ring_buffer&, Tp*)>;
463 using copy_entry_t = std::pair<Tp*, copy_function_t>;
464
465 Tp* add_copy(Tp*) const;
466 Tp* remove_copy(Tp*) const;
467 mutable std::vector<copy_entry_t> m_copy = {};
468};
469//
470template <typename Tp>
472: base_type{ rhs }
473{
474 for(const auto& itr : rhs.m_copy)
475 itr.second(*this, itr.first);
476}
477//
478template <typename Tp>
481{
482 if(this == &rhs)
483 return *this;
484
485 base_type::operator=(rhs);
486 for(const auto& itr : rhs.m_copy)
487 itr.second(*this, itr.first);
488
489 return *this;
490}
491//
492template <typename Tp>
493Tp*
494ring_buffer<Tp>::add_copy(Tp* _v) const
495{
496 auto _copy_func = [](ring_buffer& _rb, Tp* _ptr) { _rb.write(_ptr); };
497 auto itr = m_copy.begin();
498 for(; itr != m_copy.end(); ++itr)
499 {
500 if(itr->first == _v)
501 {
502 itr->second = std::move(_copy_func);
503 break;
504 }
505 }
506 if(itr == m_copy.end())
507 m_copy.emplace_back(_v, std::move(_copy_func));
508 return _v;
509}
510//
511template <typename Tp>
512Tp*
513ring_buffer<Tp>::remove_copy(Tp* _v) const
514{
515 m_copy.erase(
516 std::remove_if(m_copy.begin(), m_copy.end(),
517 [_v](const copy_entry_t& _entry) { return _entry.first == _v; }),
518 m_copy.end());
519 return _v;
520}
521//
522} // namespace data_storage
523//
524} // namespace tim
525
526#if !defined(TIMEMORY_COMMON_SOURCE) && !defined(TIMEMORY_USE_COMMON_EXTERN)
527# if !defined(TIMEMORY_RING_BUFFER_INLINE)
528# define TIMEMORY_RING_BUFFER_INLINE inline
529# endif
531#else
532# if !defined(TIMEMORY_RING_BUFFER_INLINE)
533# define TIMEMORY_RING_BUFFER_INLINE
534# endif
535#endif
typename std::enable_if< B, T >::type enable_if_t
Definition: types.hpp:58
Definition: kokkosp.cpp:39
tim::mpl::apply< std::string > string
Definition: macros.hpp:53
const std::string std::ostream * os
Ring buffer implementation, with support for mmap as backend (Linux only).
Definition: ring_buffer.hpp:50
bool is_full() const
Returns if the buffer is full.
std::string as_string() const
bool get_use_mmap() const
query whether using mmap
friend std::ostream & operator<<(std::ostream &os, const ring_buffer &obj)
void destroy()
Destroy ring buffer.
void init(size_t size)
Creates new ring buffer.
Definition: ring_buffer.cpp:84
std::pair< size_t, Tp * > read(Tp *out, std::enable_if_t< std::is_class< Tp >::value, int >=0) const
Read class-type data from buffer (uses placement new).
Tp * retrieve()
Retrieve a pointer to the head allocation (read).
void set_use_mmap(bool)
explicitly configure to use mmap if avail
bool is_empty() const
Returns if the buffer is empty.
ring_buffer(ring_buffer &&) noexcept=delete
Tp * request()
Request a pointer to an allocation. This is similar to a "write" except the memory is uninitialized....
ring_buffer(bool _use_mmap)
Definition: ring_buffer.hpp:55
size_t free() const
Returns how many bytes are availiable in the buffer.
std::pair< size_t, Tp * > write(Tp *in, std::enable_if_t< std::is_class< Tp >::value, int >=0)
Write class-type data to buffer (uses placement new).
bool is_initialized() const
Returns whether the buffer has been allocated.
Definition: ring_buffer.hpp:68
size_t capacity() const
Get the total number of bytes supported.
Definition: ring_buffer.hpp:71
ring_buffer(size_t _size)
Definition: ring_buffer.hpp:56
size_t rewind(size_t n) const
Rewind the read position n bytes.
size_t count() const
Returns number of bytes currently held by the buffer.
Ring buffer wrapper around tim::base::ring_buffer for data of type Tp. If the data object size is lar...
size_t capacity() const
Get the total number of Tp instances supported.
size_t count() const
Returns number of Tp instances currently held by the buffer.
bool is_initialized() const
Returns whether the buffer has been allocated.
friend std::ostream & operator<<(std::ostream &os, const ring_buffer &obj)
bool is_full() const
Returns if the buffer is full.
auto emplace(Args &&... args)
ring_buffer(ring_buffer &&) noexcept=default
size_t free() const
Returns how many Tp instances are availiable in the buffer.
ring_buffer & operator=(const ring_buffer &)
void init(size_t _size)
Creates new ring buffer.
size_t data_size() const
Write data to buffer.
bool is_empty() const
Returns if the buffer is empty.
ring_buffer(size_t _size, bool _use_mmap)
Tp * request()
Get an uninitialized address at tail of buffer.
size_t rewind(size_t n) const
Rewinds the read pointer.
std::string as_string() const
Tp * write(Tp *in)
Write data to buffer. Return pointer to location of write.
Tp * read(Tp *out) const
Read data from buffer. Return pointer to location of read.
void destroy()
Destroy ring buffer.
Tp * retrieve()
Read data from head of buffer.