Monitoring System 0.1.0
System resource monitoring with pluggable collectors and alerting
Loading...
Searching...
No Matches
test_monitorable_interface.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
11#include <gtest/gtest.h>
13#include <thread>
14#include <atomic>
15
16using namespace kcenon::monitoring;
17
22private:
23 std::atomic<int> operation_count_{0};
24 std::atomic<double> cpu_usage_{0.0};
25 std::atomic<double> memory_usage_{0.0};
26
27public:
28 explicit test_monitorable_component(const std::string& id)
30
31 kcenon::common::Result<monitoring_data> get_monitoring_data() const override {
32 if (!is_monitoring_enabled()) {
33 return kcenon::common::make_error<monitoring_data>(
34 static_cast<int>(monitoring_error_code::monitoring_disabled),
35 "Monitoring is disabled for this component"
36 );
37 }
38
40
41 // Add metrics
42 data.add_metric("operation_count", operation_count_.load());
43 data.add_metric("cpu_usage", cpu_usage_.load());
44 data.add_metric("memory_usage", memory_usage_.load());
45
46 // Add tags
47 data.add_tag("component_type", "test");
48 data.add_tag("version", "1.0.0");
49 data.add_tag("status", "running");
50
51 return kcenon::common::ok(std::move(data));
52 }
53
54 // Test methods to update internal state
58
59 void set_cpu_usage(double usage) {
60 cpu_usage_ = usage;
61 }
62
63 void set_memory_usage(double usage) {
64 memory_usage_ = usage;
65 }
66
67 int get_operation_count() const {
68 return operation_count_.load();
69 }
70};
71
75class MonitorableInterfaceTest : public ::testing::Test {
76protected:
77 void SetUp() override {
78 // Setup code if needed
79 }
80
81 void TearDown() override {
82 // Cleanup code if needed
83 }
84};
85
89TEST_F(MonitorableInterfaceTest, MonitoringDataBasicOperations) {
90 monitoring_data data("test_component");
91
92 // Test adding metrics
93 data.add_metric("cpu", 75.5);
94 data.add_metric("memory", 1024.0);
95
96 // Test adding tags
97 data.add_tag("host", "localhost");
98 data.add_tag("region", "us-east");
99
100 // Verify metrics
101 auto cpu = data.get_metric("cpu");
102 ASSERT_TRUE(cpu.has_value());
103 EXPECT_DOUBLE_EQ(cpu.value(), 75.5);
104
105 auto memory = data.get_metric("memory");
106 ASSERT_TRUE(memory.has_value());
107 EXPECT_DOUBLE_EQ(memory.value(), 1024.0);
108
109 auto missing = data.get_metric("nonexistent");
110 EXPECT_FALSE(missing.has_value());
111
112 // Verify tags
113 auto host = data.get_tag("host");
114 ASSERT_TRUE(host.has_value());
115 EXPECT_EQ(host.value(), "localhost");
116
117 auto region = data.get_tag("region");
118 ASSERT_TRUE(region.has_value());
119 EXPECT_EQ(region.value(), "us-east");
120
121 // Verify counts
122 EXPECT_EQ(data.metric_count(), 2);
123 EXPECT_EQ(data.tag_count(), 2);
124 EXPECT_FALSE(data.empty());
125
126 // Test component name
127 EXPECT_EQ(data.get_component_name(), "test_component");
128}
129
133TEST_F(MonitorableInterfaceTest, MonitoringDataMerge) {
134 monitoring_data data1("component1");
135 data1.add_metric("metric1", 10.0);
136 data1.add_tag("tag1", "value1");
137
138 monitoring_data data2("component2");
139 data2.add_metric("metric2", 20.0);
140 data2.add_tag("tag2", "value2");
141
142 // Merge without prefix
143 data1.merge(data2);
144
145 EXPECT_EQ(data1.metric_count(), 2);
146 EXPECT_EQ(data1.tag_count(), 2);
147
148 auto metric2 = data1.get_metric("metric2");
149 ASSERT_TRUE(metric2.has_value());
150 EXPECT_DOUBLE_EQ(metric2.value(), 20.0);
151
152 // Merge with prefix
153 monitoring_data data3("component3");
154 data3.add_metric("metric3", 30.0);
155 data3.add_tag("tag3", "value3");
156
157 data1.merge(data3, "prefix");
158
159 auto prefixed_metric = data1.get_metric("prefix.metric3");
160 ASSERT_TRUE(prefixed_metric.has_value());
161 EXPECT_DOUBLE_EQ(prefixed_metric.value(), 30.0);
162
163 auto prefixed_tag = data1.get_tag("prefix.tag3");
164 ASSERT_TRUE(prefixed_tag.has_value());
165 EXPECT_EQ(prefixed_tag.value(), "value3");
166}
167
171TEST_F(MonitorableInterfaceTest, MonitoringDataClearAndEmpty) {
172 monitoring_data data("test");
173
174 // Initially empty
175 EXPECT_TRUE(data.empty());
176 EXPECT_EQ(data.metric_count(), 0);
177 EXPECT_EQ(data.tag_count(), 0);
178
179 // Add data
180 data.add_metric("metric", 1.0);
181 data.add_tag("tag", "value");
182
183 EXPECT_FALSE(data.empty());
184 EXPECT_EQ(data.metric_count(), 1);
185 EXPECT_EQ(data.tag_count(), 1);
186
187 // Clear data
188 data.clear();
189
190 EXPECT_TRUE(data.empty());
191 EXPECT_EQ(data.metric_count(), 0);
192 EXPECT_EQ(data.tag_count(), 0);
193}
194
198TEST_F(MonitorableInterfaceTest, MonitorableComponentBasic) {
199 test_monitorable_component component("test_comp_1");
200
201 // Test initial state
202 EXPECT_EQ(component.get_monitoring_id(), "test_comp_1");
203 EXPECT_TRUE(component.is_monitoring_enabled());
204
205 // Perform operations
206 component.perform_operation();
207 component.perform_operation();
208 component.set_cpu_usage(45.5);
209 component.set_memory_usage(2048.0);
210
211 // Get monitoring data
212 auto result = component.get_monitoring_data();
213 ASSERT_TRUE(result.is_ok());
214
215 auto data = result.value();
216 EXPECT_EQ(data.get_component_name(), "test_comp_1");
217
218 // Verify metrics
219 auto op_count = data.get_metric("operation_count");
220 ASSERT_TRUE(op_count.has_value());
221 EXPECT_DOUBLE_EQ(op_count.value(), 2.0);
222
223 auto cpu = data.get_metric("cpu_usage");
224 ASSERT_TRUE(cpu.has_value());
225 EXPECT_DOUBLE_EQ(cpu.value(), 45.5);
226
227 auto memory = data.get_metric("memory_usage");
228 ASSERT_TRUE(memory.has_value());
229 EXPECT_DOUBLE_EQ(memory.value(), 2048.0);
230
231 // Verify tags
232 auto type = data.get_tag("component_type");
233 ASSERT_TRUE(type.has_value());
234 EXPECT_EQ(type.value(), "test");
235
236 auto version = data.get_tag("version");
237 ASSERT_TRUE(version.has_value());
238 EXPECT_EQ(version.value(), "1.0.0");
239}
240
244TEST_F(MonitorableInterfaceTest, MonitoringEnableDisable) {
245 test_monitorable_component component("test_comp");
246
247 // Initially enabled
248 EXPECT_TRUE(component.is_monitoring_enabled());
249
250 auto result = component.get_monitoring_data();
251 EXPECT_TRUE(result.is_ok());
252
253 // Disable monitoring
254 auto disable_result = component.set_monitoring_enabled(false);
255 EXPECT_TRUE(disable_result.is_ok());
256 EXPECT_FALSE(component.is_monitoring_enabled());
257
258 // Should return error when disabled
259 result = component.get_monitoring_data();
260 EXPECT_TRUE(result.is_err());
261 EXPECT_EQ(static_cast<monitoring_error_code>(result.error().code), monitoring_error_code::monitoring_disabled);
262
263 // Re-enable monitoring
264 auto enable_result = component.set_monitoring_enabled(true);
265 EXPECT_TRUE(enable_result.is_ok());
266 EXPECT_TRUE(component.is_monitoring_enabled());
267
268 // Should work again
269 result = component.get_monitoring_data();
270 EXPECT_TRUE(result.is_ok());
271}
272
277 test_monitorable_component component("test_comp");
278
279 // Set some state
280 component.perform_operation();
281 component.perform_operation();
282 component.perform_operation();
283
284 EXPECT_EQ(component.get_operation_count(), 3);
285
286 // Reset monitoring (note: our test implementation doesn't reset internal counters)
287 auto reset_result = component.reset_monitoring();
288 EXPECT_TRUE(reset_result.is_ok());
289
290 // Internal state remains (as we didn't override reset_monitoring to clear it)
291 EXPECT_EQ(component.get_operation_count(), 3);
292}
293
297TEST_F(MonitorableInterfaceTest, AggregatorBasicOperations) {
298 monitoring_aggregator aggregator("main_aggregator");
299
300 // Create components
301 auto comp1 = std::make_shared<test_monitorable_component>("comp1");
302 auto comp2 = std::make_shared<test_monitorable_component>("comp2");
303 auto comp3 = std::make_shared<test_monitorable_component>("comp3");
304
305 // Set different metrics for each
306 comp1->set_cpu_usage(25.0);
307 comp1->set_memory_usage(1000.0);
308 comp1->perform_operation();
309
310 comp2->set_cpu_usage(50.0);
311 comp2->set_memory_usage(2000.0);
312 comp2->perform_operation();
313 comp2->perform_operation();
314
315 comp3->set_cpu_usage(75.0);
316 comp3->set_memory_usage(3000.0);
317 comp3->perform_operation();
318 comp3->perform_operation();
319 comp3->perform_operation();
320
321 // Add components to aggregator
322 aggregator.add_component(comp1);
323 aggregator.add_component(comp2);
324 aggregator.add_component(comp3);
325
326 EXPECT_EQ(aggregator.size(), 3);
327
328 // Get component IDs
329 auto ids = aggregator.get_component_ids();
330 EXPECT_EQ(ids.size(), 3);
331 EXPECT_TRUE(std::find(ids.begin(), ids.end(), "comp1") != ids.end());
332 EXPECT_TRUE(std::find(ids.begin(), ids.end(), "comp2") != ids.end());
333 EXPECT_TRUE(std::find(ids.begin(), ids.end(), "comp3") != ids.end());
334
335 // Get specific component
336 auto retrieved = aggregator.get_component("comp2");
337 ASSERT_NE(retrieved, nullptr);
338 EXPECT_EQ(retrieved->get_monitoring_id(), "comp2");
339}
340
344TEST_F(MonitorableInterfaceTest, AggregatorDataCollection) {
345 monitoring_aggregator aggregator("test_aggregator");
346
347 // Create and configure components
348 auto comp1 = std::make_shared<test_monitorable_component>("comp1");
349 comp1->set_cpu_usage(30.0);
350 comp1->set_memory_usage(1500.0);
351
352 auto comp2 = std::make_shared<test_monitorable_component>("comp2");
353 comp2->set_cpu_usage(60.0);
354 comp2->set_memory_usage(2500.0);
355
356 aggregator.add_component(comp1);
357 aggregator.add_component(comp2);
358
359 // Collect all data
360 auto result = aggregator.collect_all();
361 ASSERT_TRUE(result.is_ok());
362
363 auto aggregated = result.value();
364 EXPECT_EQ(aggregated.get_component_name(), "test_aggregator");
365
366 // Check that metrics are prefixed
367 auto comp1_cpu = aggregated.get_metric("comp1.cpu_usage");
368 ASSERT_TRUE(comp1_cpu.has_value());
369 EXPECT_DOUBLE_EQ(comp1_cpu.value(), 30.0);
370
371 auto comp2_cpu = aggregated.get_metric("comp2.cpu_usage");
372 ASSERT_TRUE(comp2_cpu.has_value());
373 EXPECT_DOUBLE_EQ(comp2_cpu.value(), 60.0);
374
375 // Check aggregator metadata
376 auto component_count = aggregated.get_metric("aggregator.component_count");
377 ASSERT_TRUE(component_count.has_value());
378 EXPECT_DOUBLE_EQ(component_count.value(), 2.0);
379}
380
384TEST_F(MonitorableInterfaceTest, AggregatorWithDisabledComponents) {
385 monitoring_aggregator aggregator("test_aggregator");
386
387 auto comp1 = std::make_shared<test_monitorable_component>("comp1");
388 auto comp2 = std::make_shared<test_monitorable_component>("comp2");
389
390 comp1->set_cpu_usage(40.0);
391 comp2->set_cpu_usage(80.0);
392
393 // Disable comp2
394 comp2->set_monitoring_enabled(false);
395
396 aggregator.add_component(comp1);
397 aggregator.add_component(comp2);
398
399 // Collect data
400 auto result = aggregator.collect_all();
401 ASSERT_TRUE(result.is_ok());
402
403 auto aggregated = result.value();
404
405 // comp1 data should be present
406 auto comp1_cpu = aggregated.get_metric("comp1.cpu_usage");
407 ASSERT_TRUE(comp1_cpu.has_value());
408
409 // comp2 data should not be present (it's disabled)
410 auto comp2_cpu = aggregated.get_metric("comp2.cpu_usage");
411 EXPECT_FALSE(comp2_cpu.has_value());
412
413 // comp2 is disabled, so it's skipped (no error tag needed)
414 auto comp2_error = aggregated.get_tag("comp2.error");
415 EXPECT_FALSE(comp2_error.has_value());
416}
417
421TEST_F(MonitorableInterfaceTest, AggregatorComponentRemoval) {
422 monitoring_aggregator aggregator("test_aggregator");
423
424 auto comp1 = std::make_shared<test_monitorable_component>("comp1");
425 auto comp2 = std::make_shared<test_monitorable_component>("comp2");
426 auto comp3 = std::make_shared<test_monitorable_component>("comp3");
427
428 aggregator.add_component(comp1);
429 aggregator.add_component(comp2);
430 aggregator.add_component(comp3);
431
432 EXPECT_EQ(aggregator.size(), 3);
433
434 // Remove comp2
435 bool removed = aggregator.remove_component("comp2");
436 EXPECT_TRUE(removed);
437 EXPECT_EQ(aggregator.size(), 2);
438
439 // Try to remove non-existent component
440 removed = aggregator.remove_component("nonexistent");
441 EXPECT_FALSE(removed);
442 EXPECT_EQ(aggregator.size(), 2);
443
444 // Verify comp2 is gone
445 auto ids = aggregator.get_component_ids();
446 EXPECT_TRUE(std::find(ids.begin(), ids.end(), "comp1") != ids.end());
447 EXPECT_FALSE(std::find(ids.begin(), ids.end(), "comp2") != ids.end());
448 EXPECT_TRUE(std::find(ids.begin(), ids.end(), "comp3") != ids.end());
449
450 // Clear all
451 aggregator.clear();
452 EXPECT_EQ(aggregator.size(), 0);
453}
454
458TEST_F(MonitorableInterfaceTest, MonitoringDataTimestamp) {
459 auto start_time = std::chrono::system_clock::now();
460
461 monitoring_data data("test");
462
463 auto timestamp = data.get_timestamp();
464 auto end_time = std::chrono::system_clock::now();
465
466 // Timestamp should be between start and end
467 EXPECT_GE(timestamp, start_time);
468 EXPECT_LE(timestamp, end_time);
469}
470
474TEST_F(MonitorableInterfaceTest, ThreadSafetyMonitorableComponent) {
475 test_monitorable_component component("thread_test");
476
477 const int thread_count = 10;
478 const int operations_per_thread = 1000;
479 std::vector<std::thread> threads;
480
481 // Launch threads that perform operations
482 for (int i = 0; i < thread_count; ++i) {
483 threads.emplace_back([&component]() {
484 for (int j = 0; j < operations_per_thread; ++j) {
485 component.perform_operation();
486
487 // Also get monitoring data periodically
488 if (j % 100 == 0) {
489 auto result = component.get_monitoring_data();
490 EXPECT_TRUE(result.is_ok());
491 }
492 }
493 });
494 }
495
496 // Wait for all threads
497 for (auto& t : threads) {
498 t.join();
499 }
500
501 // Verify final count
502 EXPECT_EQ(component.get_operation_count(), thread_count * operations_per_thread);
503
504 // Get final monitoring data
505 auto result = component.get_monitoring_data();
506 ASSERT_TRUE(result.is_ok());
507
508 auto op_count = result.value().get_metric("operation_count");
509 ASSERT_TRUE(op_count.has_value());
510 EXPECT_DOUBLE_EQ(op_count.value(),
511 static_cast<double>(thread_count * operations_per_thread));
512}
513
514// Main function provided by gtest_main
Base class providing default monitorable implementation.
common::VoidResult reset_monitoring() override
Reset monitoring data.
bool is_monitoring_enabled() const override
Check if monitoring is enabled.
std::string get_monitoring_id() const override
Get monitoring identifier.
common::VoidResult set_monitoring_enabled(bool enable) override
Enable or disable monitoring.
Utility class to aggregate metrics from multiple monitorable components.
std::size_t size() const
Get the number of registered components.
common::Result< monitoring_data > collect_all() const
Collect data from all components.
std::vector< std::string > get_component_ids() const
Get all component IDs.
bool remove_component(const std::string &id)
Remove a component by ID.
std::shared_ptr< monitorable_interface > get_component(const std::string &id) const
Get a specific component by ID.
void add_component(std::shared_ptr< monitorable_interface > component)
Add a component to monitor.
test_monitorable_component(const std::string &id)
kcenon::common::Result< monitoring_data > get_monitoring_data() const override
Get current monitoring data from the component.
Interface for components that expose monitoring metrics.
monitoring_error_code
Comprehensive error codes for monitoring system operations.
Definition error_codes.h:25
@ memory
Memory/DRAM power domain (RAPL)
@ cpu
CPU power domain (RAPL)
Container for monitoring metrics from a component.
void merge(const monitoring_data &other, const std::string &prefix="")
Merge another monitoring_data into this one.
std::size_t metric_count() const
Get the number of metrics.
bool empty() const
Check if data is empty.
std::chrono::system_clock::time_point get_timestamp() const
Get the timestamp.
std::optional< std::string > get_tag(const std::string &key) const
Get a tag value.
std::size_t tag_count() const
Get the number of tags.
void clear()
Clear all metrics and tags.
std::optional< double > get_metric(const std::string &key) const
Get a metric value.
const std::string & get_component_name() const
Get the component name.
void add_metric(const std::string &key, double value)
Add a numeric metric.
void add_tag(const std::string &key, const std::string &value)
Add a tag (string metadata)
TEST_F(MonitorableInterfaceTest, MonitoringDataBasicOperations)