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.cpp
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
26
29#include "timemory/units.hpp"
30
31#include <cerrno>
32#include <cstdio>
33#include <cstdlib>
34#include <cstring>
35
36#if defined(TIMEMORY_LINUX)
37# include <sys/mman.h>
38# include <sys/stat.h>
39# include <sys/types.h>
40# include <unistd.h>
41#endif
42
43#if !defined(TIMEMORY_RING_BUFFER_INLINE)
44# define TIMEMORY_RING_BUFFER_INLINE
45#endif
46
47namespace tim
48{
49namespace base
50{
52ring_buffer::ring_buffer(size_t _size, bool _use_mmap)
53{
54 set_use_mmap(_use_mmap);
55 init(_size);
56}
57
60
63: m_use_mmap{ rhs.m_use_mmap }
64, m_use_mmap_explicit{ rhs.m_use_mmap_explicit }
65{
66 init(rhs.m_size);
67}
68
72{
73 if(this == &rhs)
74 return *this;
75 destroy();
76 m_use_mmap = rhs.m_use_mmap;
77 m_use_mmap_explicit = rhs.m_use_mmap_explicit;
78 init(rhs.m_size);
79 return *this;
80}
81
83void
84ring_buffer::init(size_t _size)
85{
86 if(m_init)
87 throw std::runtime_error(
88 "tim::base::ring_buffer::init(size_t) :: already initialized");
89
90 m_init = true;
91
92 // Round up to multiple of page size.
93 _size += units::get_page_size() - ((_size % units::get_page_size() > 0)
94 ? (_size % units::get_page_size())
95 : units::get_page_size());
96
97 if((_size % units::get_page_size()) > 0)
98 {
99 std::ostringstream _oss{};
100 _oss << "Error! size is not a multiple of page size: " << _size << " % "
101 << units::get_page_size() << " = " << (_size % units::get_page_size());
102 throw std::runtime_error(_oss.str());
103 }
104
105 m_size = _size;
106 m_read_count = 0;
107 m_write_count = 0;
108
109 if(!m_use_mmap_explicit)
110 m_use_mmap = get_env("TIMEMORY_USE_MMAP", m_use_mmap);
111
112#if defined(TIMEMORY_LINUX)
113 if(!m_use_mmap)
114 {
115 m_ptr = malloc(m_size * sizeof(char));
116 return;
117 }
118 // Set file path depending on whether shared memory is compiled in or not.
119# ifdef SHM
120 char path[] = "/dev/shm/rb-XXXXXX";
121# else
122 char path[] = "/tmp/rb-XXXXXX";
123# endif /* SHM */
124
125 // Create a temporary file for mmap backing.
126 if((m_fd = mkstemp(path)) < 0)
127 destroy();
128
129 // Remove file from filesystem. Note the file is still open by the
130 // process.
131 // XXX there might be a security problem with this, if so, use umaks 0600.
132 if(unlink(path))
133 destroy();
134
135 // Resize file to buffer size.
136 if(ftruncate(m_fd, m_size) < 0)
137 destroy();
138
139 // Map twice the buffer size.
140 if((m_ptr = mmap(NULL, 2 * m_size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)) ==
141 MAP_FAILED)
142 destroy();
143
144 // Map the temporary file into the first half of the above mapped space.
145 if(mmap(m_ptr, m_size, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, m_fd, 0) ==
146 MAP_FAILED)
147 destroy();
148
149 // Map the temporary file into the second half of the mapped space.
150 // This creates two consecutive copies of the same physical memory, thus
151 // allowing contiues reads and writes of the buffer.
152 if(mmap(static_cast<char*>(m_ptr) + m_size, m_size, PROT_READ | PROT_WRITE,
153 MAP_FIXED | MAP_SHARED, m_fd, 0) == MAP_FAILED)
154 destroy();
155#else
156 m_use_mmap = false;
157 m_ptr = malloc(m_size * sizeof(char));
158 (void) m_fd;
159#endif
160}
161
163void
165{
166 if(m_ptr && m_init)
167 {
168 m_init = false;
169#if defined(TIMEMORY_LINUX)
170 if(!m_use_mmap)
171 {
172 ::free(m_ptr);
173 }
174 else
175 {
176 // Truncate file to zero, to avoid writing back memory to file, on munmap.
177 if(ftruncate(m_fd, 0) < 0)
178 {
179 bool _cond = settings::verbose() > 0 || settings::debug();
181 "Ring buffer failed to truncate the file "
182 "descriptor %i\n",
183 m_fd);
184 }
185 // Unmap the mapped virtual memmory.
186 auto ret = munmap(m_ptr, m_size * 2);
187 // Close the backing file.
188 close(m_fd);
189 if(ret)
190 perror("munmap");
191 }
192#else
193 ::free(m_ptr);
194#endif
195 }
196 m_init = false;
197 m_size = 0;
198 m_read_count = 0;
199 m_write_count = 0;
200 m_ptr = nullptr;
201}
202
204void
206{
207 if(m_init)
208 throw std::runtime_error("tim::base::ring_buffer::set_use_mmap(bool) cannot be "
209 "called after initialization");
210 m_use_mmap = _v;
211 m_use_mmap_explicit = true;
212}
213
217{
218 std::ostringstream ss{};
219 ss << std::boolalpha << "is_initialized: " << is_initialized()
220 << ", capacity: " << capacity() << ", count: " << count() << ", free: " << free()
221 << ", is_empty: " << is_empty() << ", is_full: " << is_full()
222 << ", pointer: " << m_ptr << ", read count: " << m_read_count
223 << ", write count: " << m_write_count;
224 return ss.str();
225}
226//
228void*
229ring_buffer::request(size_t _length)
230{
231 if(m_ptr == nullptr)
232 return nullptr;
233
234 // Make sure we don't put in more than there's room for, by writing no
235 // more than there is free.
236 if(_length > free())
237 throw std::runtime_error("heap-buffer-overflow :: ring buffer is full. read data "
238 "to avoid data corruption");
239
240 // if write count is at the tail of buffer, bump to the end of buffer
241 auto _modulo = m_size - (m_write_count % m_size);
242 if(_modulo < _length)
243 m_write_count += _modulo;
244
245 // pointer in buffer
246 void* _out = write_ptr();
247
248 // Update write count
249 m_write_count += _length;
250
251 return _out;
252}
253//
255void*
257{
258 if(m_ptr == nullptr)
259 return nullptr;
260
261 // Make sure we don't put in more than there's room for, by writing no
262 // more than there is free.
263 if(_length > count())
264 throw std::runtime_error("ring buffer is empty");
265
266 // if read count is at the tail of buffer, bump to the end of buffer
267 auto _modulo = m_size - (m_read_count % m_size);
268 if(_modulo < _length)
269 m_read_count += _modulo;
270
271 // pointer in buffer
272 void* _out = read_ptr();
273
274 // Update write count
275 m_read_count += _length;
276
277 return _out;
278}
279//
281size_t
282ring_buffer::rewind(size_t n) const
283{
284 if(n > m_read_count)
285 n = m_read_count;
286 m_read_count -= n;
287 return n;
288}
289} // namespace base
290} // namespace tim
Definition: kokkosp.cpp:39
Tp get_env(const std::string &env_id, Tp _default, bool _store)
tim::mpl::apply< std::string > string
Definition: macros.hpp:53
#define TIMEMORY_RING_BUFFER_INLINE
Definition: ring_buffer.cpp:44
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
void destroy()
Destroy ring buffer.
void init(size_t size)
Creates new ring buffer.
Definition: ring_buffer.cpp:84
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.
Tp * request()
Request a pointer to an allocation. This is similar to a "write" except the memory is uninitialized....
size_t free() const
Returns how many bytes are availiable in the buffer.
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 & operator=(const ring_buffer &)
Definition: ring_buffer.cpp:71
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.
#define CONDITIONAL_PRINT_HERE(CONDITION,...)
Definition: macros.hpp:183