Monitoring System 0.1.0
System resource monitoring with pluggable collectors and alerting
Loading...
Searching...
No Matches
test_buffering_strategies.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>
6#include "utils/buffering_strategy.h"
7#include "utils/buffer_manager.h"
9#include <chrono>
10#include <thread>
11#include <vector>
12#include <random>
13#include <functional>
14
15using namespace kcenon::monitoring;
16
20class BufferingStrategiesTest : public ::testing::Test {
21protected:
22 void SetUp() override {
23 // Common setup for tests
24 }
25
26 void TearDown() override {
27 // Common cleanup for tests
28 }
29
33 buffered_metric create_test_metric(const std::string& name, double value, uint8_t priority = 128) {
34 // Create metadata with hash of name
35 std::hash<std::string> hasher;
36 uint32_t name_hash = static_cast<uint32_t>(hasher(name));
37 metric_metadata metadata(name_hash, metric_type::gauge);
38 compact_metric_value metric(metadata, value);
39 return buffered_metric(std::move(metric), priority);
40 }
41
45 std::vector<buffered_metric> create_test_metrics(size_t count, const std::string& base_name = "test") {
46 std::vector<buffered_metric> metrics;
47 metrics.reserve(count);
48
49 for (size_t i = 0; i < count; ++i) {
50 std::string name = base_name + "_" + std::to_string(i);
51 metrics.push_back(create_test_metric(name, static_cast<double>(i)));
52 }
53
54 return metrics;
55 }
56};
57
58// Configuration Tests
59TEST_F(BufferingStrategiesTest, BufferingConfigValidation) {
60 // Test invalid configuration
61 buffering_config invalid_config;
62 invalid_config.max_buffer_size = 0; // Invalid
63
64 auto validation = invalid_config.validate();
65 EXPECT_FALSE(validation);
66
67 // Test valid configuration
68 buffering_config valid_config;
69 valid_config.max_buffer_size = 1024;
70 valid_config.flush_threshold_size = 512;
71 valid_config.flush_interval = std::chrono::milliseconds(1000);
72
73 validation = valid_config.validate();
74 EXPECT_TRUE(validation);
75
76 // Test invalid threshold
77 buffering_config invalid_threshold;
78 invalid_threshold.max_buffer_size = 100;
79 invalid_threshold.flush_threshold_size = 200; // Exceeds max size
80
81 validation = invalid_threshold.validate();
82 EXPECT_FALSE(validation);
83}
84
85// Immediate Strategy Tests
86TEST_F(BufferingStrategiesTest, ImmediateStrategy) {
87 buffering_config config;
88 config.strategy = buffering_strategy_type::immediate;
89
90 immediate_strategy strategy(config);
91
92 // Test basic properties
93 EXPECT_EQ(strategy.size(), 0);
94 EXPECT_FALSE(strategy.should_flush());
95
96 // Add metric (should be processed immediately)
97 auto metric = create_test_metric("test_metric", 42.0);
98 auto result = strategy.add_metric(std::move(metric));
99 EXPECT_TRUE(result.is_ok());
100
101 // Size should still be 0 (no buffering)
102 EXPECT_EQ(strategy.size(), 0);
103
104 // Flush should return empty vector
105 auto flush_result = strategy.flush();
106 EXPECT_TRUE(flush_result);
107 EXPECT_TRUE(flush_result.value().empty());
108
109 // Check statistics
110 const auto& stats = strategy.get_statistics();
111 EXPECT_EQ(stats.total_items_buffered.load(), 1);
112 EXPECT_EQ(stats.total_items_flushed.load(), 1);
113 EXPECT_EQ(stats.total_flushes.load(), 1);
114}
115
116// Fixed Size Strategy Tests
117TEST_F(BufferingStrategiesTest, FixedSizeStrategy) {
118 buffering_config config;
119 config.strategy = buffering_strategy_type::fixed_size;
120 config.max_buffer_size = 5;
121 config.flush_threshold_size = 3;
122 config.overflow_policy = buffer_overflow_policy::drop_oldest;
123
124 fixed_size_strategy strategy(config);
125
126 // Add metrics up to threshold
127 for (int i = 0; i < 3; ++i) {
128 auto metric = create_test_metric("test_" + std::to_string(i), static_cast<double>(i));
129 auto result = strategy.add_metric(std::move(metric));
130 EXPECT_TRUE(result.is_ok());
131 }
132
133 EXPECT_EQ(strategy.size(), 3);
134 EXPECT_TRUE(strategy.should_flush()); // Should flush at threshold
135
136 // Add more metrics to test overflow
137 for (int i = 3; i < 8; ++i) {
138 auto metric = create_test_metric("test_" + std::to_string(i), static_cast<double>(i));
139 auto result = strategy.add_metric(std::move(metric));
140 EXPECT_TRUE(result.is_ok());
141 }
142
143 EXPECT_EQ(strategy.size(), 5); // Should not exceed max size
144
145 // Flush buffer
146 auto flush_result = strategy.flush();
147 EXPECT_TRUE(flush_result);
148 EXPECT_EQ(flush_result.value().size(), 5);
149
150 // Buffer should be empty after flush
151 EXPECT_EQ(strategy.size(), 0);
152 EXPECT_FALSE(strategy.should_flush());
153
154 // Check statistics
155 const auto& stats = strategy.get_statistics();
156 EXPECT_EQ(stats.total_items_buffered.load(), 8);
157 EXPECT_EQ(stats.total_items_flushed.load(), 5);
158 EXPECT_EQ(stats.items_dropped_overflow.load(), 3);
159}
160
161TEST_F(BufferingStrategiesTest, FixedSizeStrategyDropNewest) {
162 buffering_config config;
163 config.strategy = buffering_strategy_type::fixed_size;
164 config.max_buffer_size = 3;
165 config.overflow_policy = buffer_overflow_policy::drop_newest;
166
167 fixed_size_strategy strategy(config);
168
169 // Fill buffer
170 for (int i = 0; i < 3; ++i) {
171 auto metric = create_test_metric("test_" + std::to_string(i), static_cast<double>(i));
172 strategy.add_metric(std::move(metric));
173 }
174
175 EXPECT_EQ(strategy.size(), 3);
176
177 // Try to add one more (should be dropped)
178 auto metric = create_test_metric("test_overflow", 999.0);
179 strategy.add_metric(std::move(metric));
180
181 EXPECT_EQ(strategy.size(), 3); // Size unchanged
182
183 const auto& stats = strategy.get_statistics();
184 EXPECT_GT(stats.items_dropped_overflow.load(), 0);
185}
186
187// Time Based Strategy Tests
188TEST_F(BufferingStrategiesTest, TimeBasedStrategy) {
189 buffering_config config;
190 config.strategy = buffering_strategy_type::time_based;
191 config.max_buffer_size = 100;
192 config.flush_interval = std::chrono::milliseconds(100);
193
194 time_based_strategy strategy(config);
195
196 // Add some metrics
197 for (int i = 0; i < 5; ++i) {
198 auto metric = create_test_metric("test_" + std::to_string(i), static_cast<double>(i));
199 strategy.add_metric(std::move(metric));
200 }
201
202 EXPECT_EQ(strategy.size(), 5);
203 EXPECT_FALSE(strategy.should_flush()); // Too soon to flush
204
205 // Wait for flush interval
206 std::this_thread::sleep_for(std::chrono::milliseconds(150));
207
208 EXPECT_TRUE(strategy.should_flush()); // Should flush after interval
209
210 // Flush and verify
211 auto flush_result = strategy.flush();
212 EXPECT_TRUE(flush_result);
213 EXPECT_EQ(flush_result.value().size(), 5);
214 EXPECT_EQ(strategy.size(), 0);
215}
216
217TEST_F(BufferingStrategiesTest, TimeBasedStrategyBufferFull) {
218 buffering_config config;
219 config.strategy = buffering_strategy_type::time_based;
220 config.max_buffer_size = 3;
221 config.flush_interval = std::chrono::seconds(10); // Long interval
222
223 time_based_strategy strategy(config);
224
225 // Fill buffer beyond capacity
226 for (int i = 0; i < 5; ++i) {
227 auto metric = create_test_metric("test_" + std::to_string(i), static_cast<double>(i));
228 strategy.add_metric(std::move(metric));
229 }
230
231 // Should trigger flush due to buffer full condition
232 EXPECT_TRUE(strategy.should_flush());
233}
234
235// Priority Based Strategy Tests
236TEST_F(BufferingStrategiesTest, PriorityBasedStrategy) {
237 buffering_config config;
238 config.strategy = buffering_strategy_type::priority_based;
239 config.max_buffer_size = 10;
240 config.flush_priority_threshold = 200;
241
242 priority_based_strategy strategy(config);
243
244 // Add metrics with different priorities
245 std::vector<uint8_t> priorities = {100, 150, 250, 50, 220};
246
247 for (size_t i = 0; i < priorities.size(); ++i) {
248 auto metric = create_test_metric("test_" + std::to_string(i), static_cast<double>(i), priorities[i]);
249 strategy.add_metric(std::move(metric));
250 }
251
252 EXPECT_EQ(strategy.size(), 5);
253 EXPECT_TRUE(strategy.should_flush()); // Should flush due to high priority items
254
255 // Flush and check order (should be sorted by priority)
256 auto flush_result = strategy.flush();
257 EXPECT_TRUE(flush_result);
258
259 const auto& flushed_items = flush_result.value();
260 EXPECT_EQ(flushed_items.size(), 5);
261
262 // Check that items are sorted by priority (highest first)
263 for (size_t i = 1; i < flushed_items.size(); ++i) {
264 EXPECT_GE(flushed_items[i-1].priority, flushed_items[i].priority);
265 }
266}
267
268TEST_F(BufferingStrategiesTest, PriorityBasedStrategyOverflow) {
269 buffering_config config;
270 config.strategy = buffering_strategy_type::priority_based;
271 config.max_buffer_size = 3;
272 config.overflow_policy = buffer_overflow_policy::drop_lowest_priority;
273
274 priority_based_strategy strategy(config);
275
276 // Add metrics with different priorities
277 std::vector<uint8_t> priorities = {100, 200, 150, 250, 50};
278
279 for (size_t i = 0; i < priorities.size(); ++i) {
280 auto metric = create_test_metric("test_" + std::to_string(i), static_cast<double>(i), priorities[i]);
281 strategy.add_metric(std::move(metric));
282 }
283
284 EXPECT_EQ(strategy.size(), 3); // Should maintain max size
285
286 // Flush and verify highest priorities are kept
287 auto flush_result = strategy.flush();
288 EXPECT_TRUE(flush_result);
289
290 const auto& flushed_items = flush_result.value();
291 EXPECT_EQ(flushed_items.size(), 3);
292
293 // Check that lowest priority items were dropped
294 for (const auto& item : flushed_items) {
295 EXPECT_GT(item.priority, 100); // Priority 50 and 100 should be dropped
296 }
297}
298
299// Adaptive Strategy Tests
300TEST_F(BufferingStrategiesTest, AdaptiveStrategy) {
301 buffering_config config;
302 config.strategy = buffering_strategy_type::adaptive;
303 config.max_buffer_size = 100;
304 config.load_factor_threshold = 0.7;
305 config.adaptive_check_interval = std::chrono::milliseconds(50);
306
307 adaptive_strategy strategy(config);
308
309 // Add some metrics
310 for (int i = 0; i < 20; ++i) {
311 auto metric = create_test_metric("test_" + std::to_string(i), static_cast<double>(i));
312 strategy.add_metric(std::move(metric));
313 }
314
315 EXPECT_EQ(strategy.size(), 20);
316
317 // Add more metrics to increase load
318 for (int i = 20; i < 80; ++i) {
319 auto metric = create_test_metric("test_" + std::to_string(i), static_cast<double>(i));
320 strategy.add_metric(std::move(metric));
321 }
322
323 // Should adapt to high load
324 EXPECT_TRUE(strategy.should_flush());
325}
326
327// Factory Function Tests
329 // Test immediate strategy creation
330 buffering_config immediate_config;
331 immediate_config.strategy = buffering_strategy_type::immediate;
332
333 auto immediate_strategy = create_buffering_strategy(immediate_config);
334 EXPECT_NE(immediate_strategy, nullptr);
335 EXPECT_EQ(immediate_strategy->get_config().strategy, buffering_strategy_type::immediate);
336
337 // Test fixed size strategy creation
338 buffering_config fixed_config;
339 fixed_config.strategy = buffering_strategy_type::fixed_size;
340 fixed_config.max_buffer_size = 1024;
341
342 auto fixed_strategy = create_buffering_strategy(fixed_config);
343 EXPECT_NE(fixed_strategy, nullptr);
344 EXPECT_EQ(fixed_strategy->get_config().strategy, buffering_strategy_type::fixed_size);
345
346 // Test invalid configuration
347 buffering_config invalid_config;
348 invalid_config.max_buffer_size = 0; // Invalid
349
350 EXPECT_THROW(create_buffering_strategy(invalid_config), std::invalid_argument);
351}
352
353// Default Configurations Tests
354TEST_F(BufferingStrategiesTest, DefaultConfigurations) {
355 auto configs = create_default_buffering_configs();
356
357 EXPECT_GT(configs.size(), 0);
358
359 // Validate all default configurations
360 for (const auto& config : configs) {
361 auto validation = config.validate();
362 EXPECT_TRUE(validation) << "Default configuration validation failed";
363
364 // Test that we can create strategies with these configurations
365 EXPECT_NO_THROW(create_buffering_strategy(config));
366 }
367}
368
369// Buffer Manager Tests
370TEST_F(BufferingStrategiesTest, BufferManagerBasic) {
371 auto storage = std::make_shared<metric_storage>();
372
373 buffer_manager_config config;
374 config.enable_automatic_flushing = false; // Manual control for testing
375
376 buffer_manager manager(config, storage);
377
378 // Add metrics for different metric names
379 for (int i = 0; i < 5; ++i) {
380 auto metadata = create_metric_metadata("cpu_usage", metric_type::gauge);
381 compact_metric_value metric(metadata, 50.0 + i);
382
383 auto result = manager.add_metric("cpu_usage", std::move(metric));
384 EXPECT_TRUE(result.is_ok());
385 }
386
387 // Check buffer size
388 auto size_result = manager.get_buffer_size("cpu_usage");
389 EXPECT_TRUE(size_result);
390 EXPECT_EQ(size_result.value(), 5);
391
392 // Force flush
393 auto flush_result = manager.force_flush("cpu_usage");
394 EXPECT_TRUE(flush_result);
395
396 // Buffer should be empty after flush
397 size_result = manager.get_buffer_size("cpu_usage");
398 EXPECT_TRUE(size_result);
399 EXPECT_EQ(size_result.value(), 0);
400}
401
402TEST_F(BufferingStrategiesTest, BufferManagerMultipleMetrics) {
403 buffer_manager manager;
404
405 std::vector<std::string> metric_names = {"cpu_usage", "memory_usage", "disk_io"};
406
407 // Add metrics for different metric names
408 for (const auto& metric_name : metric_names) {
409 for (int i = 0; i < 3; ++i) {
410 auto metadata = create_metric_metadata(metric_name, metric_type::gauge);
411 compact_metric_value metric(metadata, static_cast<double>(i));
412
413 manager.add_metric(metric_name, std::move(metric));
414 }
415 }
416
417 // Check that all metrics have buffers
418 auto buffered_metrics = manager.get_buffered_metrics();
419 EXPECT_EQ(buffered_metrics.size(), 3);
420
421 for (const auto& metric_name : metric_names) {
422 EXPECT_NE(std::find(buffered_metrics.begin(), buffered_metrics.end(), metric_name),
423 buffered_metrics.end());
424 }
425
426 // Force flush all
427 manager.force_flush_all();
428
429 // All buffers should be empty
430 for (const auto& metric_name : metric_names) {
431 auto size_result = manager.get_buffer_size(metric_name);
432 EXPECT_TRUE(size_result);
433 EXPECT_EQ(size_result.value(), 0);
434 }
435}
436
437TEST_F(BufferingStrategiesTest, BufferManagerCustomStrategy) {
438 buffer_manager manager;
439
440 // Configure custom buffering strategy
441 buffering_config custom_config;
442 custom_config.strategy = buffering_strategy_type::priority_based;
443 custom_config.max_buffer_size = 10;
444 custom_config.flush_priority_threshold = 200;
445
446 auto result = manager.configure_metric_buffer("high_priority_metric", custom_config);
447 EXPECT_TRUE(result.is_ok());
448
449 // Add metrics with different priorities
450 std::vector<uint8_t> priorities = {100, 250, 150};
451
452 for (size_t i = 0; i < priorities.size(); ++i) {
453 auto metadata = create_metric_metadata("high_priority_metric", metric_type::counter);
454 compact_metric_value metric(metadata, static_cast<double>(i));
455
456 auto add_result = manager.add_metric("high_priority_metric", std::move(metric), priorities[i]);
457 EXPECT_TRUE(add_result);
458 }
459
460 // Get buffer statistics
461 auto stats_result = manager.get_buffer_statistics("high_priority_metric");
462 EXPECT_TRUE(stats_result);
463}
464
465TEST_F(BufferingStrategiesTest, BufferManagerBackgroundProcessing) {
466 auto storage = std::make_shared<metric_storage>();
467
468 buffer_manager_config config;
469 config.background_check_interval = std::chrono::milliseconds(50);
470 config.enable_automatic_flushing = true;
471
472 buffer_manager manager(config, storage);
473
474 // Configure time-based strategy with short interval
475 buffering_config time_config;
476 time_config.strategy = buffering_strategy_type::time_based;
477 time_config.flush_interval = std::chrono::milliseconds(100);
478 time_config.max_buffer_size = 100;
479
480 manager.configure_metric_buffer("timed_metric", time_config);
481
482 // Start background processing
483 auto start_result = manager.start_background_processing();
484 EXPECT_TRUE(start_result);
485
486 // Add metrics
487 for (int i = 0; i < 5; ++i) {
488 auto metadata = create_metric_metadata("timed_metric", metric_type::gauge);
489 compact_metric_value metric(metadata, static_cast<double>(i));
490
491 manager.add_metric("timed_metric", std::move(metric));
492 }
493
494 // Wait for background flush
495 std::this_thread::sleep_for(std::chrono::milliseconds(200));
496
497 // Buffer should be flushed automatically
498 auto size_result = manager.get_buffer_size("timed_metric");
499 EXPECT_TRUE(size_result);
500 EXPECT_EQ(size_result.value(), 0);
501
502 // Stop background processing
503 manager.stop_background_processing();
504}
505
506// Thread Safety Tests
508 buffer_manager manager;
509
510 const int num_threads = 4;
511 const int metrics_per_thread = 100;
512 std::vector<std::thread> threads;
513
514 // Launch multiple threads adding metrics
515 for (int t = 0; t < num_threads; ++t) {
516 threads.emplace_back([&manager, t, metrics_per_thread]() {
517 std::string metric_name = "thread_" + std::to_string(t) + "_metric";
518
519 for (int i = 0; i < metrics_per_thread; ++i) {
520 auto metadata = create_metric_metadata(metric_name, metric_type::counter);
521 compact_metric_value metric(metadata, static_cast<double>(i));
522
523 manager.add_metric(metric_name, std::move(metric));
524
525 // Small delay to increase chance of contention
526 std::this_thread::sleep_for(std::chrono::microseconds(1));
527 }
528 });
529 }
530
531 // Wait for all threads to complete
532 for (auto& thread : threads) {
533 thread.join();
534 }
535
536 // Verify metrics were added
537 auto buffered_metrics = manager.get_buffered_metrics();
538 EXPECT_EQ(buffered_metrics.size(), num_threads);
539
540 // Check each metric buffer
541 for (int t = 0; t < num_threads; ++t) {
542 std::string metric_name = "thread_" + std::to_string(t) + "_metric";
543 auto size_result = manager.get_buffer_size(metric_name);
544 EXPECT_TRUE(size_result);
545 EXPECT_EQ(size_result.value(), metrics_per_thread);
546 }
547}
Test suite for Phase 3 P3: Configurable buffering strategies.
buffered_metric create_test_metric(const std::string &name, double value, uint8_t priority=128)
Create test metric.
std::vector< buffered_metric > create_test_metrics(size_t count, const std::string &base_name="test")
Create multiple test metrics.
Memory-efficient metric storage with ring buffer backend.
metric_metadata create_metric_metadata(const std::string &name, metric_type type, size_t tag_count=0)
Create metric metadata from name and type.
@ storage
Storage device sensor.
Memory-efficient metric value storage.
Compact metadata for metrics.
Basic metric structure for interface compatibility.
TEST_F(BufferingStrategiesTest, BufferingConfigValidation)