MySQL 26.10.0
Source Code Documentation
health_monitor_stat_buffer.h
Go to the documentation of this file.
1/* Copyright (c) 2020, 2026, Oracle and/or its affiliates.
2
3This program is free software; you can redistribute it and/or modify
4it under the terms of the GNU General Public License, version 2.0,
5as published by the Free Software Foundation.
6
7This program is designed to work with certain software (including
8but not limited to OpenSSL) that is licensed under separate terms,
9as designated in a particular file or component or in included license
10documentation. The authors of MySQL hereby grant you an additional
11permission to link the program and your derivative works with the
12separately licensed software that they have either included with
13the program or referenced in the documentation.
14
15This program is distributed in the hope that it will be useful,
16but WITHOUT ANY WARRANTY; without even the implied warranty of
17MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18GNU General Public License, version 2.0, for more details.
19
20You should have received a copy of the GNU General Public License
21along with this program; if not, write to the Free Software
22Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
23
24#pragma once
25
26#include <algorithm>
27#include <memory>
28#include <string>
29#include <utility> // std::cmp_less()
30#include <vector>
31
34
35#include "health_monitor_lock.h"
36
37namespace hm {
38
39/**
40 Sample data buffer.
41*/
42template <typename T>
45
46 public:
47 Stat_Buffer() = default;
48 Stat_Buffer(bool no_lock, PSI_memory_key memory_key)
49 : m_no_lock{no_lock}, m_memory_key{memory_key} {
50 m_stats =
51 std::make_unique<std::vector<T, Allocator>>((Allocator(m_memory_key)));
52 }
53
55 : m_rwlock_key{rwlock_key}, m_memory_key{memory_key} {
57 m_stats =
58 std::make_unique<std::vector<T, Allocator>>((Allocator(m_memory_key)));
59 }
60
61 virtual ~Stat_Buffer() {
62 if (!m_no_lock) {
64 }
65 }
66
67 Stat_Buffer(const Stat_Buffer &) = default;
68 Stat_Buffer &operator=(const Stat_Buffer &) = default;
70 Stat_Buffer &operator=(Stat_Buffer &&) noexcept = default;
71
72 /**
73 Read an element of the stat buffer.
74 @param[in,out] record Element from the buffer to be returned
75 @param[in] row_num Row number, zero-based
76 @return 0 success, 1 failure
77 */
78 int read(T &record, unsigned int row_num) {
79 if (m_no_lock) {
80 return read_impl(record, row_num);
81 }
82 rwlock_guard lock(&m_rwlock, false, __FILE__, __LINE__);
83 return read_impl(record, row_num);
84 }
85
86 /**
87 Read the last element of the stat buffer.
88 @param[in,out] record Element from the buffer to be returned
89 @return 0 success, 1 failure
90 */
92 rwlock_guard lock(&m_rwlock, false, __FILE__, __LINE__);
93 if (m_count == 0) {
94 return 1;
95 }
96 return read_impl(record, m_count - 1);
97 }
98
99 /**
100 Acquire a read lock for a shared buffer.
101 @return 0 success
102 @return 1 fail to set lock
103 */
104 int lock_read() {
105 if (m_no_lock) {
106 return 1;
107 }
108 return rwlock_rdlock(&m_rwlock, __FILE__, __LINE__);
109 }
110
111 /**
112 Unlock a read or write lock for a shared buffer.
113 */
114 void unlock() {
115 if (m_no_lock) {
116 return;
117 }
119 }
120
121 /**
122 @brief Read an element from the circular buffer by logical row index.
123
124 The buffer holds up to m_count valid elements. This function accepts a row
125 non-negative row_num range [0, m_count].
126
127 Thread safety:
128 - If this buffer is shared (m_no_lock == false), the caller should hold a
129 read lock or use read()/read_last() which manage locking.
130 - This method itself does not acquire locks.
131
132 @param[out] record On success, set to element at requested logical row.
133 @param[in] row_num Zero-based logical row index [0, m_count].
134 @return 0 on success.
135 @return 1 if the buffer is empty (no elements to read).
136 @note Logical indexing: When the buffer has wrapped, the oldest element
137 begins at m_index; otherwise it begins at 0.
138 */
139 int read_no_lock(T &record, unsigned int row_num) {
140 return read_impl(record, row_num);
141 }
142
143 int read_impl(T &record, size_t row_num) {
144 if (m_count == 0 || m_stats->empty() || row_num >= m_count) {
145 return 1; // nothing to read
146 }
147 const size_t base = m_wrapped ? m_index : 0; // oldest element
148 const size_t idx = (base + row_num) % m_stats->size();
149 record = (*m_stats)[idx];
150 return 0;
151 }
152
153 /**
154 @brief Write one element into the circular buffer at the current head
155 position.
156
157 If the buffer is not yet full, the element is appended logically and the
158 valid element count (m_count) increases. The head index (m_index) advances
159 modulo the buffer size.
160
161 Once the buffer reaches capacity:
162 - Subsequent writes overwrite the oldest element
163 - m_full remains true
164 - m_wrapped is set when the first overwrite occurs and stays true
165
166 Thread safety:
167 - This method does not acquire locks. For shared buffers (m_no_lock ==
168 false), call write() which manages locking, or ensure the caller holds an
169 exclusive lock.
170
171 @param[in] record Element to write.
172 @return 0 on success.
173 @return 1 if the buffer has zero capacity (size() == 0).
174 @note Uses logical indexing consistent with read_impl(): When not wrapped,
175 the oldest element is at index 0. After wrapping, the oldest begins
176 at m_index.
177 */
178 int write(const T &record) {
179 if (m_no_lock) {
180 return write_impl(record);
181 }
182 rwlock_guard lock(&m_rwlock, true, __FILE__, __LINE__);
183 return write_impl(record);
184 }
185
186 int write_impl(const T &record) {
187 const size_t size = m_stats->size();
188 if (size == 0) {
189 return 1; // cannot write without capacity
190 }
191
192 // Write at current head
193 (*m_stats)[m_index] = record;
195
196 // Update count/full/wrapped
197 if (m_count < size) {
198 ++m_count;
199 if (m_count == size) {
200 m_full = true; // just became full
201 }
202 } else {
203 // Already full, we're overwriting the oldest so mark wrapped.
204 m_wrapped = true;
205 }
206 // Advance head
207 m_index = (m_index + 1) % size; // next write
208
209 return 0;
210 }
211
212 /**
213 @brief Resize the stat buffer, preserve the most recent data.
214
215 Note that m_stats is preallocated to m_max_size elements,
216 so m_stats->size() is always the maximum size.
217 @param[in] new_size Desired size of the buffer, can be either
218 larger or smaller than the current size
219 */
220 void resize(size_t new_size) {
221 if (m_no_lock) {
222 return resize_impl(new_size);
223 }
224 rwlock_guard lock(&m_rwlock, true, __FILE__, __LINE__);
225 return resize_impl(new_size);
226 }
227
228 void resize_impl(size_t new_size) {
229 const size_t old_size = m_stats->size();
230 size_t base = 0; // oldest record to keep
231
232 // Buffer is empty, resize and reset
233 if (m_stats->empty() || m_count == 0 || new_size == 0) {
234 if (new_size != old_size) {
235 m_max_size = new_size;
236 m_stats->resize(m_max_size);
237 }
238 m_index = 0;
239 m_count = 0;
240 m_full = false;
241 m_wrapped = false;
242 return;
243 }
244 // Find the range of data samples to keep. m_index points to
245 // the next element to be written. If the buffer has wrapped,
246 // then m_index points to the oldest element.
247 if (new_size > old_size) {
248 // extend the buffer, no other changes
249 m_full = false;
250 if (!m_wrapped) {
251 m_max_size = new_size;
252 m_stats->resize(m_max_size);
253 return;
254 }
255 // unwrap into sequential order
256 base = m_index;
257 m_index = old_size;
258 } else {
259 // shrink the buffer
260 if (m_index >= new_size) {
261 // sample data is contiguous and exceeds the new buffer size
262 base = m_index - new_size;
263 m_index = 0;
264 m_full = true;
265 } else {
266 if (m_wrapped) {
267 // sample data is split
268 base = old_size - new_size + m_index;
269 m_index = 0;
270 m_full = true;
271 } else {
272 // sample data is contiguous and fits within the new buffer
273 base = 0;
274 m_full = false;
275 }
276 }
277 }
278
279 // Reallocate the buffer with the most recent records
280 std::vector<T, Allocator> vtemp(new_size, (Allocator(m_memory_key)));
281 const size_t new_count = std::min(new_size, m_count);
282 for (size_t i = 0; i < new_count; i++) {
283 const size_t idx = (base + i) % m_stats->size();
284 vtemp.at(i) = m_stats->at(idx);
285 }
286 m_stats->swap(vtemp);
287 m_max_size = new_size;
288 m_count = new_count;
289 m_wrapped = false;
290 }
291
292 /**
293 Reset the state of the buffer. Existing elements will be overwritten.
294 */
295 void reset() {
296 if (m_no_lock) {
297 return reset_impl();
298 }
299 rwlock_guard lock(&m_rwlock, true, __FILE__, __LINE__);
300 return reset_impl();
301 }
302
303 /**
304 Reset the buffer. Assumes an active read_lock() if the buffer is shared.
305 */
306 void reset_no_lock() { return reset_impl(); }
307
308 void reset_impl() {
309 m_index = 0;
310 m_count = 0;
311 m_full = false;
312 m_wrapped = false;
313 }
314
315 /**
316 @brief Map a global logical index to the current ring-buffer row.
317
318 This helper translates a monotonically increasing logical sample index
319 (e.g. a global “next_metric”) into a window-relative row index [0..count-1]
320 for the current contents of the buffer.
321
322 A logical index is considered “available” if it refers to an element that is
323 still retained in the buffer (not yet overwritten.) If the logical index is
324 older than the oldest existing element or newer than the most recent one,
325 the mapping fails and the function returns false.
326
327 Thread safety:
328 - Acquires the buffer’s read lock internally (if the buffer is shared) to
329 provide a coherent snapshot of count() and total_written().
330
331 @param[in] logical_idx Global monotonically increasing sample index.
332 @param[out] row_out On success, the row index in the range [0, count())
333 that corresponds to logical_idx.
334
335 @return true If logical_idx maps to a currently available row.
336 @return false If logical_idx is too old (overwritten) or not yet written.
337
338 @note This function does not perform I/O. Use the returned @p row_out
339 with read(record, row_out) to retrieve the element.
340 */
341 bool map_logical_to_row_no_lock(uint64_t logical_idx, int &row_out) const {
342 return map_logical_to_row_impl(logical_idx, row_out);
343 }
344
345 bool map_logical_to_row(uint64_t logical_idx, int &row_out) const {
346 rwlock_guard lock(&m_rwlock, false, __FILE__, __LINE__);
347 return map_logical_to_row_impl(logical_idx, row_out);
348 }
349
350 bool map_logical_to_row_impl(uint64_t logical_idx, int &row_out) const {
351 if (m_count == 0) {
352 return false;
353 }
354 // The logical index must map to the existing rows [0..count-1]. If the
355 // desired row was overwritten, or if the row does not yet exist (> count),
356 // then the logical index is invalid.
357 const uint64_t oldest_logical =
359 if (logical_idx < oldest_logical || logical_idx >= m_total_written) {
360 return false;
361 }
362 row_out = static_cast<int>(logical_idx - oldest_logical); // 0..count-1
363 return (row_out >= 0 && std::cmp_less(row_out, m_count));
364 }
365
367 // assert(m_initialized); // or some other check
368 return &m_rwlock;
369 }
370
371 size_t max_size() const { return m_max_size; }
372 size_t index() const { return m_index; }
373 size_t count() const { return m_count; }
374 [[nodiscard]] size_t total_written() const { return m_total_written; }
375 bool full() const { return m_full; }
376 bool wrapped() const { return m_wrapped; }
377
378 protected:
379 std::unique_ptr<std::vector<T, Allocator>> m_stats{nullptr};
380 size_t m_max_size{0};
381 size_t m_index{0}; // next element to be written
382 size_t m_count{0}; // non-empty rows
383 size_t m_total_written{0}; // monotonically increasing write count
384 bool m_full{false};
385 bool m_wrapped{false};
386 bool m_no_lock{false}; // for non-shared buffers
388 mutable mysql_rwlock_t m_rwlock; // some const methods use rwlock // NOLINT
390};
391
392} // namespace hm
Component_malloc_allocator is a C++ STL memory allocator based on my_malloc/my_free.
Definition: component_malloc_allocator.h:63
Sample data buffer.
Definition: health_monitor_stat_buffer.h:43
mysql_rwlock_t * get_rwlock()
Definition: health_monitor_stat_buffer.h:366
Stat_Buffer & operator=(const Stat_Buffer &)=default
bool wrapped() const
Definition: health_monitor_stat_buffer.h:376
Stat_Buffer(PSI_rwlock_key rwlock_key, PSI_memory_key memory_key)
Definition: health_monitor_stat_buffer.h:54
bool map_logical_to_row(uint64_t logical_idx, int &row_out) const
Definition: health_monitor_stat_buffer.h:345
Stat_Buffer(Stat_Buffer &&) noexcept=default
void unlock()
Unlock a read or write lock for a shared buffer.
Definition: health_monitor_stat_buffer.h:114
size_t m_index
Definition: health_monitor_stat_buffer.h:381
int write_impl(const T &record)
Definition: health_monitor_stat_buffer.h:186
bool map_logical_to_row_impl(uint64_t logical_idx, int &row_out) const
Definition: health_monitor_stat_buffer.h:350
PSI_rwlock_key m_rwlock_key
Definition: health_monitor_stat_buffer.h:387
mysql_rwlock_t m_rwlock
Definition: health_monitor_stat_buffer.h:388
size_t max_size() const
Definition: health_monitor_stat_buffer.h:371
int read_no_lock(T &record, unsigned int row_num)
Read an element from the circular buffer by logical row index.
Definition: health_monitor_stat_buffer.h:139
Stat_Buffer()=default
void resize_impl(size_t new_size)
Definition: health_monitor_stat_buffer.h:228
size_t m_total_written
Definition: health_monitor_stat_buffer.h:383
bool m_no_lock
Definition: health_monitor_stat_buffer.h:386
size_t count() const
Definition: health_monitor_stat_buffer.h:373
virtual ~Stat_Buffer()
Definition: health_monitor_stat_buffer.h:61
Stat_Buffer(const Stat_Buffer &)=default
bool m_wrapped
Definition: health_monitor_stat_buffer.h:385
size_t m_max_size
Definition: health_monitor_stat_buffer.h:380
void reset()
Reset the state of the buffer.
Definition: health_monitor_stat_buffer.h:295
size_t m_count
Definition: health_monitor_stat_buffer.h:382
size_t index() const
Definition: health_monitor_stat_buffer.h:372
std::unique_ptr< std::vector< T, Allocator > > m_stats
Definition: health_monitor_stat_buffer.h:379
int write(const T &record)
Write one element into the circular buffer at the current head position.
Definition: health_monitor_stat_buffer.h:178
size_t total_written() const
Definition: health_monitor_stat_buffer.h:374
int lock_read()
Acquire a read lock for a shared buffer.
Definition: health_monitor_stat_buffer.h:104
bool map_logical_to_row_no_lock(uint64_t logical_idx, int &row_out) const
Map a global logical index to the current ring-buffer row.
Definition: health_monitor_stat_buffer.h:341
int read(T &record, unsigned int row_num)
Read an element of the stat buffer.
Definition: health_monitor_stat_buffer.h:78
PSI_memory_key m_memory_key
Definition: health_monitor_stat_buffer.h:389
void reset_no_lock()
Reset the buffer.
Definition: health_monitor_stat_buffer.h:306
void reset_impl()
Definition: health_monitor_stat_buffer.h:308
bool m_full
Definition: health_monitor_stat_buffer.h:384
int read_impl(T &record, size_t row_num)
Definition: health_monitor_stat_buffer.h:143
void resize(size_t new_size)
Resize the stat buffer, preserve the most recent data.
Definition: health_monitor_stat_buffer.h:220
Stat_Buffer(bool no_lock, PSI_memory_key memory_key)
Definition: health_monitor_stat_buffer.h:48
int read_last(T &record)
Read the last element of the stat buffer.
Definition: health_monitor_stat_buffer.h:91
Component_malloc_allocator< T > Allocator
Definition: health_monitor_stat_buffer.h:44
bool full() const
Definition: health_monitor_stat_buffer.h:375
Scoped r/w lock.
Definition: health_monitor_lock.h:50
#define mysql_rwlock_init(K, T)
Definition: mysql_rwlock.h:41
#define mysql_rwlock_destroy(T)
Definition: mysql_rwlock.h:51
unsigned int PSI_memory_key
Instrumented memory key.
Definition: psi_memory_bits.h:49
unsigned int PSI_rwlock_key
Instrumented rwlock key.
Definition: psi_rwlock_bits.h:44
#define T
Definition: jit_executor_value.cc:373
static int record
Definition: mysqltest.cc:195
Definition: component_command.h:29
int rwlock_rdlock(mysql_rwlock_t *lock, const char *file, int line)
Acquire a read lock.
Definition: health_monitor_lock.cc:73
void rwlock_unlock(mysql_rwlock_t *lock)
Unlock a read or write lock.
Definition: health_monitor_lock.cc:78
noexcept
The return type for any call_and_catch(f, args...) call where f(args...) returns Type.
Definition: call_and_catch.h:76
size_t size(const char *const c)
Definition: base64.h:46
static std::mutex lock
Definition: net_ns.cc:56
An instrumented rwlock structure.
Definition: mysql_rwlock_bits.h:51