Monitoring System 0.1.0
System resource monitoring with pluggable collectors and alerting
Loading...
Searching...
No Matches
test_time_series_buffer.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
5#include <gtest/gtest.h>
7
8#include <chrono>
9#include <cmath>
10#include <thread>
11
12namespace kcenon {
13namespace monitoring {
14namespace {
15
16class TimeSeriesBufferTest : public ::testing::Test {
17 protected:
18 void SetUp() override {
19 time_series_buffer_config config;
20 config.max_samples = 100;
21 buffer_ = std::make_unique<time_series_buffer<double>>(config);
22 }
23
24 std::unique_ptr<time_series_buffer<double>> buffer_;
25};
26
27TEST_F(TimeSeriesBufferTest, InitializesCorrectly) {
28 EXPECT_EQ(buffer_->capacity(), 100);
29 EXPECT_EQ(buffer_->size(), 0);
30 EXPECT_TRUE(buffer_->empty());
31}
32
33TEST_F(TimeSeriesBufferTest, AddSampleIncreasesSize) {
34 buffer_->add_sample(1.0);
35 EXPECT_EQ(buffer_->size(), 1);
36 EXPECT_FALSE(buffer_->empty());
37
38 buffer_->add_sample(2.0);
39 EXPECT_EQ(buffer_->size(), 2);
40}
41
42TEST_F(TimeSeriesBufferTest, GetLatestReturnsLastSample) {
43 buffer_->add_sample(1.0);
44 buffer_->add_sample(2.0);
45 buffer_->add_sample(3.0);
46
47 auto result = buffer_->get_latest();
48 EXPECT_TRUE(result.is_ok());
49 EXPECT_DOUBLE_EQ(result.value(), 3.0);
50}
51
52TEST_F(TimeSeriesBufferTest, GetLatestOnEmptyBufferReturnsError) {
53 auto result = buffer_->get_latest();
54 EXPECT_TRUE(result.is_err());
55}
56
57TEST_F(TimeSeriesBufferTest, RingBufferBehavior) {
58 time_series_buffer_config config;
59 config.max_samples = 5;
60 auto small_buffer = std::make_unique<time_series_buffer<double>>(config);
61
62 for (int i = 1; i <= 10; ++i) {
63 small_buffer->add_sample(static_cast<double>(i));
64 }
65
66 EXPECT_EQ(small_buffer->size(), 5);
67
68 auto samples = small_buffer->get_all_samples();
69 EXPECT_EQ(samples.size(), 5);
70
71 // Last 5 values should be 6, 7, 8, 9, 10
72 std::vector<double> expected = {6.0, 7.0, 8.0, 9.0, 10.0};
73 for (size_t i = 0; i < samples.size(); ++i) {
74 EXPECT_DOUBLE_EQ(samples[i].value, expected[i]);
75 }
76}
77
78TEST_F(TimeSeriesBufferTest, StatisticsCalculation) {
79 for (int i = 1; i <= 10; ++i) {
80 buffer_->add_sample(static_cast<double>(i));
81 }
82
83 auto stats = buffer_->get_statistics();
84
85 EXPECT_EQ(stats.sample_count, 10);
86 EXPECT_DOUBLE_EQ(stats.min_value, 1.0);
87 EXPECT_DOUBLE_EQ(stats.max_value, 10.0);
88 EXPECT_DOUBLE_EQ(stats.avg, 5.5);
89 EXPECT_GT(stats.stddev, 0.0);
90 EXPECT_GT(stats.p95, stats.avg);
91 EXPECT_GT(stats.p99, stats.p95);
92}
93
94TEST_F(TimeSeriesBufferTest, GetSamplesWithDuration) {
95 auto now = std::chrono::system_clock::now();
96
97 buffer_->add_sample(1.0, now - std::chrono::minutes(10));
98 buffer_->add_sample(2.0, now - std::chrono::minutes(5));
99 buffer_->add_sample(3.0, now - std::chrono::minutes(1));
100
101 auto samples = buffer_->get_samples(std::chrono::minutes(3));
102 EXPECT_EQ(samples.size(), 1);
103 EXPECT_DOUBLE_EQ(samples[0].value, 3.0);
104
105 samples = buffer_->get_samples(std::chrono::minutes(7));
106 EXPECT_EQ(samples.size(), 2);
107}
108
109TEST_F(TimeSeriesBufferTest, Clear) {
110 buffer_->add_sample(1.0);
111 buffer_->add_sample(2.0);
112 EXPECT_EQ(buffer_->size(), 2);
113
114 buffer_->clear();
115 EXPECT_EQ(buffer_->size(), 0);
116 EXPECT_TRUE(buffer_->empty());
117}
118
119TEST_F(TimeSeriesBufferTest, MemoryFootprint) {
120 size_t footprint = buffer_->memory_footprint();
121 EXPECT_GT(footprint, 0);
122}
123
124TEST_F(TimeSeriesBufferTest, InvalidConfigThrows) {
125 time_series_buffer_config config;
126 config.max_samples = 0;
127
128 EXPECT_THROW(
129 {
130 time_series_buffer<double> buf(config);
131 },
132 std::invalid_argument);
133}
134
135class LoadAverageHistoryTest : public ::testing::Test {
136 protected:
137 void SetUp() override { history_ = std::make_unique<load_average_history>(100); }
138
139 std::unique_ptr<load_average_history> history_;
140};
141
142TEST_F(LoadAverageHistoryTest, InitializesCorrectly) {
143 EXPECT_EQ(history_->capacity(), 100);
144 EXPECT_EQ(history_->size(), 0);
145 EXPECT_TRUE(history_->empty());
146}
147
148TEST_F(LoadAverageHistoryTest, AddSampleIncreasesSize) {
149 history_->add_sample(1.0, 0.5, 0.3);
150 EXPECT_EQ(history_->size(), 1);
151 EXPECT_FALSE(history_->empty());
152}
153
154TEST_F(LoadAverageHistoryTest, GetLatestReturnsLastSample) {
155 history_->add_sample(1.0, 0.5, 0.3);
156 history_->add_sample(2.0, 1.5, 1.0);
157
158 auto result = history_->get_latest();
159 EXPECT_TRUE(result.is_ok());
160 EXPECT_DOUBLE_EQ(result.value().load_1m, 2.0);
161 EXPECT_DOUBLE_EQ(result.value().load_5m, 1.5);
162 EXPECT_DOUBLE_EQ(result.value().load_15m, 1.0);
163}
164
165TEST_F(LoadAverageHistoryTest, GetLatestOnEmptyHistoryReturnsError) {
166 auto result = history_->get_latest();
167 EXPECT_TRUE(result.is_err());
168}
169
170TEST_F(LoadAverageHistoryTest, RingBufferBehavior) {
171 auto small_history = std::make_unique<load_average_history>(5);
172
173 for (int i = 1; i <= 10; ++i) {
174 small_history->add_sample(static_cast<double>(i), static_cast<double>(i) / 2,
175 static_cast<double>(i) / 4);
176 }
177
178 EXPECT_EQ(small_history->size(), 5);
179
180 auto samples = small_history->get_all_samples();
181 EXPECT_EQ(samples.size(), 5);
182
183 // Last 5 values should be 6, 7, 8, 9, 10
184 for (size_t i = 0; i < samples.size(); ++i) {
185 EXPECT_DOUBLE_EQ(samples[i].load_1m, 6.0 + i);
186 }
187}
188
189TEST_F(LoadAverageHistoryTest, StatisticsCalculation) {
190 for (int i = 1; i <= 10; ++i) {
191 history_->add_sample(static_cast<double>(i), static_cast<double>(i) / 2,
192 static_cast<double>(i) / 4);
193 }
194
195 auto stats = history_->get_statistics();
196
197 EXPECT_EQ(stats.load_1m_stats.sample_count, 10);
198 EXPECT_DOUBLE_EQ(stats.load_1m_stats.min_value, 1.0);
199 EXPECT_DOUBLE_EQ(stats.load_1m_stats.max_value, 10.0);
200 EXPECT_DOUBLE_EQ(stats.load_1m_stats.avg, 5.5);
201
202 EXPECT_EQ(stats.load_5m_stats.sample_count, 10);
203 EXPECT_DOUBLE_EQ(stats.load_5m_stats.min_value, 0.5);
204 EXPECT_DOUBLE_EQ(stats.load_5m_stats.max_value, 5.0);
205
206 EXPECT_EQ(stats.load_15m_stats.sample_count, 10);
207 EXPECT_DOUBLE_EQ(stats.load_15m_stats.min_value, 0.25);
208 EXPECT_DOUBLE_EQ(stats.load_15m_stats.max_value, 2.5);
209}
210
211TEST_F(LoadAverageHistoryTest, GetSamplesWithDuration) {
212 auto now = std::chrono::system_clock::now();
213
214 history_->add_sample(1.0, 0.5, 0.3, now - std::chrono::minutes(10));
215 history_->add_sample(2.0, 1.0, 0.5, now - std::chrono::minutes(5));
216 history_->add_sample(3.0, 1.5, 0.7, now - std::chrono::minutes(1));
217
218 auto samples = history_->get_samples(std::chrono::minutes(3));
219 EXPECT_EQ(samples.size(), 1);
220 EXPECT_DOUBLE_EQ(samples[0].load_1m, 3.0);
221
222 samples = history_->get_samples(std::chrono::minutes(7));
223 EXPECT_EQ(samples.size(), 2);
224}
225
226TEST_F(LoadAverageHistoryTest, Clear) {
227 history_->add_sample(1.0, 0.5, 0.3);
228 history_->add_sample(2.0, 1.0, 0.5);
229 EXPECT_EQ(history_->size(), 2);
230
231 history_->clear();
232 EXPECT_EQ(history_->size(), 0);
233 EXPECT_TRUE(history_->empty());
234}
235
236TEST_F(LoadAverageHistoryTest, MemoryFootprint) {
237 size_t footprint = history_->memory_footprint();
238 EXPECT_GT(footprint, 0);
239}
240
241TEST_F(LoadAverageHistoryTest, InvalidMaxSamplesThrows) {
242 EXPECT_THROW(load_average_history(0), std::invalid_argument);
243}
244
245TEST(TimeSeriesStatisticsTest, PercentileCalculation) {
246 time_series_buffer_config config;
247 config.max_samples = 1000;
248 time_series_buffer<double> buffer(config);
249
250 for (int i = 1; i <= 100; ++i) {
251 buffer.add_sample(static_cast<double>(i));
252 }
253
254 auto stats = buffer.get_statistics();
255
256 EXPECT_NEAR(stats.p95, 95.0, 1.0);
257 EXPECT_NEAR(stats.p99, 99.0, 1.0);
258}
259
260TEST(TimeSeriesStatisticsTest, StandardDeviationCalculation) {
261 time_series_buffer_config config;
262 config.max_samples = 100;
263 time_series_buffer<double> buffer(config);
264
265 buffer.add_sample(2.0);
266 buffer.add_sample(4.0);
267 buffer.add_sample(4.0);
268 buffer.add_sample(4.0);
269 buffer.add_sample(5.0);
270 buffer.add_sample(5.0);
271 buffer.add_sample(7.0);
272 buffer.add_sample(9.0);
273
274 auto stats = buffer.get_statistics();
275
276 EXPECT_DOUBLE_EQ(stats.avg, 5.0);
277 EXPECT_NEAR(stats.stddev, 2.0, 0.01);
278}
279
280TEST(TimeSeriesStatisticsTest, EmptyBufferStatistics) {
281 time_series_buffer_config config;
282 config.max_samples = 100;
283 time_series_buffer<double> buffer(config);
284
285 auto stats = buffer.get_statistics();
286
287 EXPECT_EQ(stats.sample_count, 0);
288 EXPECT_DOUBLE_EQ(stats.min_value, 0.0);
289 EXPECT_DOUBLE_EQ(stats.max_value, 0.0);
290 EXPECT_DOUBLE_EQ(stats.avg, 0.0);
291}
292
293TEST(TimeSeriesStatisticsTest, SingleSampleStatistics) {
294 time_series_buffer_config config;
295 config.max_samples = 100;
296 time_series_buffer<double> buffer(config);
297
298 buffer.add_sample(42.0);
299
300 auto stats = buffer.get_statistics();
301
302 EXPECT_EQ(stats.sample_count, 1);
303 EXPECT_DOUBLE_EQ(stats.min_value, 42.0);
304 EXPECT_DOUBLE_EQ(stats.max_value, 42.0);
305 EXPECT_DOUBLE_EQ(stats.avg, 42.0);
306 EXPECT_DOUBLE_EQ(stats.stddev, 0.0);
307 EXPECT_DOUBLE_EQ(stats.p95, 42.0);
308 EXPECT_DOUBLE_EQ(stats.p99, 42.0);
309}
310
311TEST(TimeSeriesBufferThreadSafety, ConcurrentReadWrite) {
312 time_series_buffer_config config;
313 config.max_samples = 1000;
314 auto buffer = std::make_shared<time_series_buffer<double>>(config);
315
316 std::atomic<bool> stop{false};
317 std::atomic<size_t> write_count{0};
318 std::atomic<size_t> read_count{0};
319
320 auto writer = [&]() {
321 while (!stop) {
322 buffer->add_sample(static_cast<double>(write_count++));
323 std::this_thread::sleep_for(std::chrono::microseconds(100));
324 }
325 };
326
327 auto reader = [&]() {
328 while (!stop) {
329 auto samples = buffer->get_all_samples();
330 read_count++;
331 std::this_thread::sleep_for(std::chrono::microseconds(100));
332 }
333 };
334
335 std::thread writer_thread(writer);
336 std::thread reader_thread(reader);
337
338 std::this_thread::sleep_for(std::chrono::milliseconds(100));
339 stop = true;
340
341 writer_thread.join();
342 reader_thread.join();
343
344 EXPECT_GT(write_count.load(), 0);
345 EXPECT_GT(read_count.load(), 0);
346}
347
348TEST(LoadAverageHistoryThreadSafety, ConcurrentReadWrite) {
349 auto history = std::make_shared<load_average_history>(1000);
350
351 std::atomic<bool> stop{false};
352 std::atomic<size_t> write_count{0};
353 std::atomic<size_t> read_count{0};
354
355 auto writer = [&]() {
356 while (!stop) {
357 double val = static_cast<double>(write_count++);
358 history->add_sample(val, val / 2, val / 4);
359 std::this_thread::sleep_for(std::chrono::microseconds(100));
360 }
361 };
362
363 auto reader = [&]() {
364 while (!stop) {
365 auto samples = history->get_all_samples();
366 read_count++;
367 std::this_thread::sleep_for(std::chrono::microseconds(100));
368 }
369 };
370
371 std::thread writer_thread(writer);
372 std::thread reader_thread(reader);
373
374 std::this_thread::sleep_for(std::chrono::milliseconds(100));
375 stop = true;
376
377 writer_thread.join();
378 reader_thread.join();
379
380 EXPECT_GT(write_count.load(), 0);
381 EXPECT_GT(read_count.load(), 0);
382}
383
384} // namespace
385} // namespace monitoring
386} // namespace kcenon
TEST(AdapterFunctionalityTest, WorksWithoutLogger)
Test Scenario 1: Adapter with NULL logger.
TEST_F(AdaptiveMonitoringTest, AdaptiveConfigDefaults)
std::unique_ptr< load_average_history > history_
std::unique_ptr< time_series_buffer< double > > buffer_
Generic time-series buffer for metric history tracking.