Monitoring System 0.1.0
System resource monitoring with pluggable collectors and alerting
Loading...
Searching...
No Matches
test_storage_backends.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
5
6#include <gtest/gtest.h>
9#include <filesystem>
10#include <fstream>
11#include <thread>
12#include <atomic>
13
14using namespace kcenon::monitoring;
15
16class StorageBackendsTest : public ::testing::Test {
17protected:
18 void SetUp() override {
19 // Create test snapshots
21
22 // Create test directories
23 test_dir_ = std::filesystem::temp_directory_path() / "monitoring_test";
24 std::filesystem::create_directories(test_dir_);
25 }
26
27 void TearDown() override {
28 // Clean up test files
29 if (std::filesystem::exists(test_dir_)) {
30 std::filesystem::remove_all(test_dir_);
31 }
32 }
33
34 std::vector<metrics_snapshot> create_test_snapshots() {
35 std::vector<metrics_snapshot> snapshots;
36
37 // Create first snapshot
38 metrics_snapshot snap1;
39 snap1.source_id = "web_server";
40 snap1.capture_time = std::chrono::system_clock::now();
41 snap1.add_metric("requests_per_second", 150.0);
42 snap1.add_metric("response_time_ms", 45.2);
43 snap1.add_metric("error_rate", 0.02);
44 snapshots.push_back(snap1);
45
46 // Create second snapshot
47 metrics_snapshot snap2;
48 snap2.source_id = "database";
49 snap2.capture_time = std::chrono::system_clock::now();
50 snap2.add_metric("connections", 25.0);
51 snap2.add_metric("query_time_ms", 12.8);
52 snap2.add_metric("cache_hit_rate", 0.95);
53 snapshots.push_back(snap2);
54
55 // Create third snapshot
56 metrics_snapshot snap3;
57 snap3.source_id = "cache_server";
58 snap3.capture_time = std::chrono::system_clock::now();
59 snap3.add_metric("memory_usage_mb", 512.0);
60 snap3.add_metric("hit_rate", 0.88);
61 snap3.add_metric("evictions_per_sec", 2.1);
62 snapshots.push_back(snap3);
63
64 return snapshots;
65 }
66
67 std::vector<metrics_snapshot> test_snapshots_;
68 std::filesystem::path test_dir_;
69};
70
71TEST_F(StorageBackendsTest, StorageConfigValidation) {
72 // Valid configuration
73 storage_config valid_config;
74 valid_config.path = "/tmp/test.json";
75 valid_config.type = storage_backend_type::file_json;
76 valid_config.max_capacity = 1000;
77 valid_config.batch_size = 100;
78
79 auto validation = valid_config.validate();
80 EXPECT_TRUE(validation.is_ok());
81
82 // Invalid path (for non-memory storage)
83 storage_config invalid_path;
84 invalid_path.type = storage_backend_type::file_json;
85 invalid_path.max_capacity = 1000;
86 invalid_path.batch_size = 100;
87 auto path_validation = invalid_path.validate();
88 EXPECT_FALSE(path_validation.is_ok());
89 EXPECT_EQ(path_validation.error().code, static_cast<int>(monitoring_error_code::invalid_configuration));
90
91 // Valid memory buffer (no path required)
92 storage_config memory_config;
93 memory_config.type = storage_backend_type::memory_buffer;
94 memory_config.max_capacity = 1000;
95 memory_config.batch_size = 100;
96 auto memory_validation = memory_config.validate();
97 EXPECT_TRUE(memory_validation.is_ok());
98
99 // Invalid capacity
101 invalid_capacity.path = "/tmp/test";
102 invalid_capacity.max_capacity = 0;
103 auto capacity_validation = invalid_capacity.validate();
104 EXPECT_FALSE(capacity_validation.is_ok());
105 EXPECT_EQ(capacity_validation.error().code, static_cast<int>(monitoring_error_code::invalid_capacity));
106
107 // Invalid batch size
108 storage_config invalid_batch;
109 invalid_batch.path = "/tmp/test";
110 invalid_batch.max_capacity = 1000;
111 invalid_batch.batch_size = 0;
112 auto batch_validation = invalid_batch.validate();
113 EXPECT_FALSE(batch_validation.is_ok());
114
115 // Batch size larger than capacity
116 storage_config batch_too_large;
117 batch_too_large.path = "/tmp/test";
118 batch_too_large.max_capacity = 100;
119 batch_too_large.batch_size = 200;
120 auto batch_large_validation = batch_too_large.validate();
121 EXPECT_FALSE(batch_large_validation.is_ok());
122}
123
124TEST_F(StorageBackendsTest, FileStorageBackendBasicOperations) {
125 storage_config config;
126 config.type = storage_backend_type::file_json;
127 config.path = (test_dir_ / "test.json").string();
128 config.max_capacity = 10;
129
130 file_storage_backend backend(config);
131
132 // Test initial state
133 EXPECT_EQ(backend.size(), 0);
134 EXPECT_EQ(backend.capacity(), 10);
135
136 // Store snapshots
137 for (const auto& snapshot : test_snapshots_) {
138 auto store_result = backend.store(snapshot);
139 EXPECT_TRUE(store_result.is_ok());
140 }
141
142 EXPECT_EQ(backend.size(), 3);
143
144 // Retrieve snapshot
145 auto retrieve_result = backend.retrieve(0);
146 EXPECT_TRUE(retrieve_result.is_ok());
147
148 // Retrieve range
149 auto range_result = backend.retrieve_range(0, 2);
150 EXPECT_TRUE(range_result.is_ok());
151 EXPECT_EQ(range_result.value().size(), 2);
152
153 // Test flush
154 auto flush_result = backend.flush();
155 EXPECT_TRUE(flush_result.is_ok());
156
157 // Test clear
158 auto clear_result = backend.clear();
159 EXPECT_TRUE(clear_result.is_ok());
160 EXPECT_EQ(backend.size(), 0);
161}
162
163TEST_F(StorageBackendsTest, FileStorageBackendCapacityLimit) {
164 storage_config config;
165 config.type = storage_backend_type::file_json;
166 config.path = (test_dir_ / "capacity_test.json").string();
167 config.max_capacity = 2; // Small capacity for testing
168
169 file_storage_backend backend(config);
170
171 // Store snapshots beyond capacity
172 for (const auto& snapshot : test_snapshots_) {
173 auto store_result = backend.store(snapshot);
174 EXPECT_TRUE(store_result.is_ok());
175 }
176
177 // Should not exceed capacity (oldest removed)
178 EXPECT_EQ(backend.size(), 2);
179
180 // Get statistics
181 auto stats = backend.get_stats();
182 EXPECT_EQ(stats["total_snapshots"], 2);
183 EXPECT_EQ(stats["capacity"], 2);
184}
185
186TEST_F(StorageBackendsTest, FileStorageBackendDifferentFormats) {
187 // Test JSON format
188 {
189 storage_config json_config;
190 json_config.type = storage_backend_type::file_json;
191 json_config.path = (test_dir_ / "test.json").string();
192 json_config.max_capacity = 10;
193
194 file_storage_backend json_backend(json_config);
195 auto store_result = json_backend.store(test_snapshots_[0]);
196 EXPECT_TRUE(store_result.is_ok());
197
198 auto retrieve_result = json_backend.retrieve(0);
199 EXPECT_TRUE(retrieve_result.is_ok());
200 }
201
202 // Test Binary format
203 {
204 storage_config binary_config;
205 binary_config.type = storage_backend_type::file_binary;
206 binary_config.path = (test_dir_ / "test.bin").string();
207 binary_config.max_capacity = 10;
208
209 file_storage_backend binary_backend(binary_config);
210 auto store_result = binary_backend.store(test_snapshots_[0]);
211 EXPECT_TRUE(store_result.is_ok());
212
213 auto retrieve_result = binary_backend.retrieve(0);
214 EXPECT_TRUE(retrieve_result.is_ok());
215 }
216
217 // Test CSV format
218 {
219 storage_config csv_config;
220 csv_config.type = storage_backend_type::file_csv;
221 csv_config.path = (test_dir_ / "test.csv").string();
222 csv_config.max_capacity = 10;
223
224 file_storage_backend csv_backend(csv_config);
225 auto store_result = csv_backend.store(test_snapshots_[0]);
226 EXPECT_TRUE(store_result.is_ok());
227
228 auto retrieve_result = csv_backend.retrieve(0);
229 EXPECT_TRUE(retrieve_result.is_ok());
230 }
231}
232
233TEST_F(StorageBackendsTest, MemoryStorageBackend) {
234 storage_config config;
235 config.type = storage_backend_type::memory_buffer;
236 config.max_capacity = 5;
237
238 file_storage_backend backend(config); // Memory buffer uses file_storage_backend
239
240 // Store snapshots
241 for (const auto& snapshot : test_snapshots_) {
242 auto store_result = backend.store(snapshot);
243 EXPECT_TRUE(store_result.is_ok());
244 }
245
246 EXPECT_EQ(backend.size(), 3);
247
248 // Memory backend should not create files
249 // (Implementation detail - file operations are skipped for memory type)
250
251 // Test all operations work
252 auto retrieve_result = backend.retrieve(0);
253 EXPECT_TRUE(retrieve_result.is_ok());
254
255 auto range_result = backend.retrieve_range(0, 2);
256 EXPECT_TRUE(range_result.is_ok());
257
258 auto clear_result = backend.clear();
259 EXPECT_TRUE(clear_result.is_ok());
260 EXPECT_EQ(backend.size(), 0);
261}
262
263TEST_F(StorageBackendsTest, DatabaseStorageBackendBasicOperations) {
264 storage_config config;
265 config.type = storage_backend_type::database_sqlite;
266 config.path = (test_dir_ / "test.db").string();
267 config.table_name = "test_metrics";
268 config.max_capacity = 100;
269
270 database_storage_backend backend(config);
271
272 // Test initial state
273 EXPECT_EQ(backend.capacity(), 100);
274 EXPECT_GE(backend.size(), 0); // Database starts at 0
275
276 // Store snapshots
277 for (const auto& snapshot : test_snapshots_) {
278 auto store_result = backend.store(snapshot);
279 EXPECT_TRUE(store_result.is_ok());
280 }
281
282 EXPECT_EQ(backend.size(), 3);
283
284 // Retrieve snapshot
285 auto retrieve_result = backend.retrieve(0);
286 EXPECT_TRUE(retrieve_result.is_ok());
287
288 // Retrieve range
289 auto range_result = backend.retrieve_range(0, 2);
290 EXPECT_TRUE(range_result.is_ok());
291
292 // Test flush
293 auto flush_result = backend.flush();
294 EXPECT_TRUE(flush_result.is_ok());
295
296 // Test clear
297 auto clear_result = backend.clear();
298 EXPECT_TRUE(clear_result.is_ok());
299 EXPECT_EQ(backend.size(), 0);
300
301 // Get statistics
302 auto stats = backend.get_stats();
303 EXPECT_EQ(stats["stored_count"], 0); // After clear
304 EXPECT_EQ(stats["capacity"], 100);
305 EXPECT_EQ(stats["connected"], 1);
306}
307
308TEST_F(StorageBackendsTest, DatabaseStorageBackendDifferentTypes) {
309 // Test SQLite
310 {
311 storage_config sqlite_config;
312 sqlite_config.type = storage_backend_type::database_sqlite;
313 sqlite_config.path = (test_dir_ / "sqlite.db").string();
314 sqlite_config.max_capacity = 50;
315
316 database_storage_backend sqlite_backend(sqlite_config);
317 auto store_result = sqlite_backend.store(test_snapshots_[0]);
318 EXPECT_TRUE(store_result.is_ok());
319 }
320
321 // Test PostgreSQL (simulated)
322 {
323 storage_config pg_config;
324 pg_config.type = storage_backend_type::database_postgresql;
325 pg_config.host = "localhost";
326 pg_config.port = 5432;
327 pg_config.database_name = "monitoring_test";
328 pg_config.username = "test_user";
329 pg_config.password = "test_pass";
330 pg_config.max_capacity = 50;
331
332 database_storage_backend pg_backend(pg_config);
333 auto store_result = pg_backend.store(test_snapshots_[0]);
334 EXPECT_TRUE(store_result.is_ok());
335 }
336
337 // Test MySQL (simulated)
338 {
339 storage_config mysql_config;
340 mysql_config.type = storage_backend_type::database_mysql;
341 mysql_config.host = "localhost";
342 mysql_config.port = 3306;
343 mysql_config.database_name = "monitoring_test";
344 mysql_config.username = "test_user";
345 mysql_config.password = "test_pass";
346 mysql_config.max_capacity = 50;
347
348 database_storage_backend mysql_backend(mysql_config);
349 auto store_result = mysql_backend.store(test_snapshots_[0]);
350 EXPECT_TRUE(store_result.is_ok());
351 }
352}
353
354TEST_F(StorageBackendsTest, CloudStorageBackendBasicOperations) {
355 storage_config config;
356 config.type = storage_backend_type::cloud_s3;
357 config.path = "test-monitoring-bucket";
358 config.max_capacity = 1000;
359
360 cloud_storage_backend backend(config);
361
362 // Test initial state
363 EXPECT_EQ(backend.capacity(), 1000);
364 EXPECT_EQ(backend.size(), 0);
365
366 // Store snapshots
367 for (const auto& snapshot : test_snapshots_) {
368 auto store_result = backend.store(snapshot);
369 EXPECT_TRUE(store_result.is_ok());
370 }
371
372 EXPECT_EQ(backend.size(), 3);
373
374 // Retrieve snapshot
375 auto retrieve_result = backend.retrieve(0);
376 EXPECT_TRUE(retrieve_result.is_ok());
377
378 // Retrieve range
379 auto range_result = backend.retrieve_range(0, 2);
380 EXPECT_TRUE(range_result.is_ok());
381 EXPECT_LE(range_result.value().size(), 2); // May be less due to simulated failures
382
383 // Test flush
384 auto flush_result = backend.flush();
385 EXPECT_TRUE(flush_result.is_ok());
386
387 // Test clear
388 auto clear_result = backend.clear();
389 EXPECT_TRUE(clear_result.is_ok());
390 EXPECT_EQ(backend.size(), 0);
391}
392
393TEST_F(StorageBackendsTest, CloudStorageBackendDifferentProviders) {
394 // Test AWS S3
395 {
396 storage_config s3_config;
397 s3_config.type = storage_backend_type::cloud_s3;
398 s3_config.path = "s3-test-bucket";
399 s3_config.max_capacity = 100;
400
401 cloud_storage_backend s3_backend(s3_config);
402 auto store_result = s3_backend.store(test_snapshots_[0]);
403 EXPECT_TRUE(store_result.is_ok());
404 }
405
406 // Test Google Cloud Storage
407 {
408 storage_config gcs_config;
409 gcs_config.type = storage_backend_type::cloud_gcs;
410 gcs_config.path = "gcs-test-bucket";
411 gcs_config.max_capacity = 100;
412
413 cloud_storage_backend gcs_backend(gcs_config);
414 auto store_result = gcs_backend.store(test_snapshots_[0]);
415 EXPECT_TRUE(store_result.is_ok());
416 }
417
418 // Test Azure Blob Storage
419 {
420 storage_config azure_config;
421 azure_config.type = storage_backend_type::cloud_azure_blob;
422 azure_config.path = "azure-test-container";
423 azure_config.max_capacity = 100;
424
425 cloud_storage_backend azure_backend(azure_config);
426 auto store_result = azure_backend.store(test_snapshots_[0]);
427 EXPECT_TRUE(store_result.is_ok());
428 }
429}
430
431TEST_F(StorageBackendsTest, StorageBackendFactory) {
432 // Test file storage creation
433 {
434 storage_config file_config;
435 file_config.type = storage_backend_type::file_json;
436 file_config.path = (test_dir_ / "factory_test.json").string();
437 file_config.max_capacity = 50;
438
439 auto backend = storage_backend_factory::create_backend(file_config);
440 EXPECT_TRUE(backend);
441
442 auto store_result = backend->store(test_snapshots_[0]);
443 EXPECT_TRUE(store_result.is_ok());
444 }
445
446 // Test database storage creation
447 {
448 storage_config db_config;
449 db_config.type = storage_backend_type::database_sqlite;
450 db_config.path = (test_dir_ / "factory_test.db").string();
451 db_config.max_capacity = 50;
452
453 auto backend = storage_backend_factory::create_backend(db_config);
454 EXPECT_TRUE(backend);
455
456 auto store_result = backend->store(test_snapshots_[0]);
457 EXPECT_TRUE(store_result.is_ok());
458 }
459
460 // Test cloud storage creation
461 {
462 storage_config cloud_config;
463 cloud_config.type = storage_backend_type::cloud_s3;
464 cloud_config.path = "factory-test-bucket";
465 cloud_config.max_capacity = 50;
466
467 auto backend = storage_backend_factory::create_backend(cloud_config);
468 EXPECT_TRUE(backend);
469
470 auto store_result = backend->store(test_snapshots_[0]);
471 EXPECT_TRUE(store_result.is_ok());
472 }
473
474 // Test invalid type
475 {
476 storage_config invalid_config;
477 invalid_config.type = static_cast<storage_backend_type>(999);
478
479 auto backend = storage_backend_factory::create_backend(invalid_config);
480 EXPECT_FALSE(backend);
481 }
482}
483
484TEST_F(StorageBackendsTest, SupportedBackendsList) {
486 EXPECT_EQ(supported.size(), 10); // All backend types
487
488 EXPECT_TRUE(std::find(supported.begin(), supported.end(),
489 storage_backend_type::file_json) != supported.end());
490 EXPECT_TRUE(std::find(supported.begin(), supported.end(),
491 storage_backend_type::database_sqlite) != supported.end());
492 EXPECT_TRUE(std::find(supported.begin(), supported.end(),
493 storage_backend_type::cloud_s3) != supported.end());
494 EXPECT_TRUE(std::find(supported.begin(), supported.end(),
495 storage_backend_type::memory_buffer) != supported.end());
496}
497
498TEST_F(StorageBackendsTest, HelperFunctions) {
499 // Test file storage helper
500 {
501 auto backend = create_file_storage(
502 (test_dir_ / "helper_test.json").string(),
503 storage_backend_type::file_json,
504 100);
505 EXPECT_TRUE(backend);
506
507 auto store_result = backend->store(test_snapshots_[0]);
508 EXPECT_TRUE(store_result.is_ok());
509 }
510
511 // Test database storage helper
512 {
513 auto backend = create_database_storage(
514 storage_backend_type::database_sqlite,
515 (test_dir_ / "helper_test.db").string(),
516 "test_table");
517 EXPECT_TRUE(backend);
518
519 auto store_result = backend->store(test_snapshots_[0]);
520 EXPECT_TRUE(store_result.is_ok());
521 }
522
523 // Test cloud storage helper
524 {
525 auto backend = create_cloud_storage(
526 storage_backend_type::cloud_s3,
527 "helper-test-bucket");
528 EXPECT_TRUE(backend);
529
530 auto store_result = backend->store(test_snapshots_[0]);
531 EXPECT_TRUE(store_result.is_ok());
532 }
533}
534
536 // Test file storage with invalid path (skip if filesystem throws)
537 {
538 try {
539 storage_config invalid_config;
540 invalid_config.type = storage_backend_type::file_json;
541 invalid_config.path = "/invalid/path/that/does/not/exist/file.json";
542 invalid_config.max_capacity = 10;
543
544 // Constructor may throw if filesystem operations fail
545 file_storage_backend backend(invalid_config);
546
547 // Store operation may fail due to invalid path
548 auto store_result = backend.store(test_snapshots_[0]);
549 // Don't assert on result as it depends on filesystem permissions
550 } catch (const std::exception&) {
551 // Expected if filesystem doesn't allow directory creation
552 // This is acceptable for this test
553 }
554 }
555
556 // Test retrieval of non-existent snapshot
557 {
558 storage_config config;
559 config.type = storage_backend_type::memory_buffer;
560 config.max_capacity = 10;
561
562 file_storage_backend backend(config);
563
564 auto retrieve_result = backend.retrieve(999);
565 EXPECT_FALSE(retrieve_result.is_ok());
566 EXPECT_EQ(retrieve_result.error().code, static_cast<int>(monitoring_error_code::not_found));
567 }
568}
569
570TEST_F(StorageBackendsTest, ConcurrentOperations) {
571 storage_config config;
572 config.type = storage_backend_type::memory_buffer;
573 config.max_capacity = 100;
574
575 file_storage_backend backend(config);
576
577 // Test concurrent stores
578 std::vector<std::thread> store_threads;
579 std::atomic<int> successful_stores{0};
580
581 for (int i = 0; i < 10; ++i) {
582 store_threads.emplace_back([&, i]() {
583 metrics_snapshot snapshot;
584 snapshot.source_id = "thread_" + std::to_string(i);
585 snapshot.add_metric("value", static_cast<double>(i));
586
587 auto result = backend.store(snapshot);
588 if (result.is_ok()) {
589 successful_stores++;
590 }
591 });
592 }
593
594 for (auto& thread : store_threads) {
595 thread.join();
596 }
597
598 EXPECT_EQ(successful_stores.load(), 10);
599 EXPECT_EQ(backend.size(), 10);
600
601 // Test concurrent retrieval
602 std::vector<std::thread> retrieve_threads;
603 std::atomic<int> successful_retrievals{0};
604
605 for (int i = 0; i < 5; ++i) {
606 retrieve_threads.emplace_back([&, i]() {
607 auto result = backend.retrieve(i);
608 if (result.is_ok()) {
609 successful_retrievals++;
610 }
611 });
612 }
613
614 for (auto& thread : retrieve_threads) {
615 thread.join();
616 }
617
618 EXPECT_EQ(successful_retrievals.load(), 5);
619}
620
621TEST_F(StorageBackendsTest, LargeDatasetHandling) {
622 storage_config config;
623 config.type = storage_backend_type::memory_buffer;
624 config.max_capacity = 50;
625
626 file_storage_backend backend(config);
627
628 // Store more data than capacity
629 for (int i = 0; i < 100; ++i) {
630 metrics_snapshot snapshot;
631 snapshot.source_id = "generator_" + std::to_string(i);
632
633 // Add multiple metrics per snapshot
634 for (int j = 0; j < 10; ++j) {
635 snapshot.add_metric("metric_" + std::to_string(j), static_cast<double>(i * 10 + j));
636 }
637
638 auto store_result = backend.store(snapshot);
639 EXPECT_TRUE(store_result.is_ok());
640 }
641
642 // Should not exceed capacity
643 EXPECT_EQ(backend.size(), 50);
644
645 // Test range retrieval of large dataset
646 auto range_result = backend.retrieve_range(0, 25);
647 EXPECT_TRUE(range_result.is_ok());
648 EXPECT_EQ(range_result.value().size(), 25);
649}
std::vector< metrics_snapshot > create_test_snapshots()
std::vector< metrics_snapshot > test_snapshots_
std::filesystem::path test_dir_
Cloud storage backend (stub implementation)
common::Result< bool > store(const metrics_snapshot &snapshot) override
common::Result< std::vector< metrics_snapshot > > retrieve_range(size_t start, size_t count) override
common::Result< metrics_snapshot > retrieve(size_t index) override
common::Result< bool > clear() override
common::Result< bool > flush() override
Database storage backend (stub implementation)
common::Result< bool > flush() override
common::Result< bool > store(const metrics_snapshot &snapshot) override
std::unordered_map< std::string, size_t > get_stats() const override
common::Result< std::vector< metrics_snapshot > > retrieve_range(size_t start, size_t count) override
common::Result< bool > clear() override
common::Result< metrics_snapshot > retrieve(size_t index) override
File storage backend for metrics snapshots.
common::Result< metrics_snapshot > retrieve(size_t index) override
common::Result< bool > clear() override
common::Result< std::vector< metrics_snapshot > > retrieve_range(size_t start, size_t count) override
common::Result< bool > flush() override
std::unordered_map< std::string, size_t > get_stats() const override
common::Result< bool > store(const metrics_snapshot &snapshot) override
static std::unique_ptr< snapshot_storage_backend > create_backend(const storage_config &config)
Create a storage backend based on configuration.
static std::vector< storage_backend_type > get_supported_backends()
Get list of supported backend types.
Core monitoring system interface definitions.
std::unique_ptr< snapshot_storage_backend > create_database_storage(storage_backend_type type, const std::string &path, const std::string &table)
Create a database storage backend.
std::unique_ptr< snapshot_storage_backend > create_cloud_storage(storage_backend_type type, const std::string &bucket)
Create a cloud storage backend.
std::unique_ptr< snapshot_storage_backend > create_file_storage(const std::string &path, storage_backend_type type, size_t capacity)
Create a file storage backend.
storage_backend_type
Storage backend types.
Storage backend type definitions for metric persistence.
Complete snapshot of metrics at a point in time.
std::chrono::system_clock::time_point capture_time
void add_metric(const std::string &name, double value)
Add a metric to the snapshot.
common::Result< bool > validate() const
Validate configuration.
TEST_F(StorageBackendsTest, StorageConfigValidation)