PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
pacs_monitor.h
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
19#pragma once
20
21#include <chrono>
22#include <functional>
23#include <memory>
24#include <mutex>
25#include <shared_mutex>
26#include <string>
27#include <string_view>
28#include <unordered_map>
29#include <vector>
30
35#include "health_checker.h"
36#include "pacs_metrics.h"
37
39
40// ─────────────────────────────────────────────────────────────────────────────
41// Metric Types (compatible with common_system/monitoring_system)
42// ─────────────────────────────────────────────────────────────────────────────
43
48enum class metric_type {
49 gauge,
50 counter,
51 histogram,
52 summary
53};
54
58[[nodiscard]] inline std::string to_string(metric_type type) {
59 switch (type) {
61 return "gauge";
63 return "counter";
65 return "histogram";
67 return "summary";
68 default:
69 return "unknown";
70 }
71}
72
78 std::string name;
79 double value;
81 std::chrono::system_clock::time_point timestamp;
82 std::unordered_map<std::string, std::string> tags;
83
84 metric_value() = default;
85
86 metric_value(std::string n,
87 double v,
89 std::unordered_map<std::string, std::string> tg = {})
90 : name(std::move(n))
91 , value(v)
92 , type(t)
93 , timestamp(std::chrono::system_clock::now())
94 , tags(std::move(tg)) {}
95};
96
102 std::vector<metric_value> metrics;
103 std::chrono::system_clock::time_point capture_time;
104 std::string source_id;
105
107 : capture_time(std::chrono::system_clock::now()) {}
108
109 void add_metric(const std::string& name,
110 double value,
112 metrics.emplace_back(name, value, type);
113 }
114};
115
121 healthy = 0,
122 degraded = 1,
123 unhealthy = 2,
124 unknown = 3
125};
126
133 std::string message;
134 std::chrono::system_clock::time_point timestamp;
135 std::chrono::milliseconds check_duration{0};
136 std::unordered_map<std::string, std::string> metadata;
137
139 : timestamp(std::chrono::system_clock::now()) {}
140
141 [[nodiscard]] bool is_healthy() const {
143 }
144
145 [[nodiscard]] bool is_operational() const {
148 }
149};
150
151// ─────────────────────────────────────────────────────────────────────────────
152// PACS Monitor Class
153// ─────────────────────────────────────────────────────────────────────────────
154
161 std::string ae_title{"PACS_SCP"};
162
165
168
171
174
177
179 std::string metric_prefix{"pacs"};
180};
181
224public:
225 // =========================================================================
226 // Construction and Singleton Access
227 // =========================================================================
228
233 explicit pacs_monitor(const pacs_monitor_config& config = {});
234
238 ~pacs_monitor() = default;
239
246 [[nodiscard]] static pacs_monitor& global_monitor() noexcept {
247 static pacs_monitor instance;
248 return instance;
249 }
250
251 // Non-copyable, non-movable
252 pacs_monitor(const pacs_monitor&) = delete;
256
257 // =========================================================================
258 // IMonitor Interface Methods
259 // =========================================================================
260
266 void record_metric(std::string_view name, double value);
267
274 void record_metric(std::string_view name,
275 double value,
276 const std::unordered_map<std::string, std::string>& tags);
277
282 [[nodiscard]] metrics_snapshot get_metrics();
283
288 [[nodiscard]] health_check_result check_health();
289
293 void reset();
294
295 // =========================================================================
296 // Prometheus Export
297 // =========================================================================
298
303 [[nodiscard]] std::string to_prometheus() const;
304
309 [[nodiscard]] std::string to_json() const;
310
311 // =========================================================================
312 // Health Check Registration
313 // =========================================================================
314
320 void register_health_check(std::string_view component,
321 std::function<bool()> check);
322
327 void unregister_health_check(std::string_view component);
328
329 // =========================================================================
330 // Configuration
331 // =========================================================================
332
337 [[nodiscard]] const pacs_monitor_config& get_config() const;
338
343 void update_config(const pacs_monitor_config& config);
344
345 // =========================================================================
346 // Collector Access
347 // =========================================================================
348
354
360
366
373
378 [[nodiscard]] dicom_metrics_snapshot get_unified_snapshot() const;
379
380private:
381 // Configuration
383 mutable std::shared_mutex config_mutex_;
384
385 // Collectors
386 std::unique_ptr<dicom_association_collector> association_collector_;
387 std::unique_ptr<dicom_service_collector> service_collector_;
388 std::unique_ptr<dicom_storage_collector> storage_collector_;
389 std::unique_ptr<dicom_metrics_collector> unified_collector_;
390
391 // Custom metrics
392 mutable std::mutex custom_metrics_mutex_;
393 std::vector<metric_value> custom_metrics_;
394
395 // Health checks
396 mutable std::mutex health_checks_mutex_;
397 std::unordered_map<std::string, std::function<bool()>> health_checks_;
398
399 // Helper methods
402};
403
404// ─────────────────────────────────────────────────────────────────────────────
405// Inline Implementation
406// ─────────────────────────────────────────────────────────────────────────────
407
409 : config_(config) {
411}
412
414 association_collector_ = std::make_unique<dicom_association_collector>(config_.ae_title);
415 service_collector_ = std::make_unique<dicom_service_collector>(config_.ae_title);
416 storage_collector_ = std::make_unique<dicom_storage_collector>(config_.ae_title);
417 unified_collector_ = std::make_unique<dicom_metrics_collector>(config_.ae_title);
418
419 // Initialize all collectors
420 std::unordered_map<std::string, std::string> collector_config;
421 collector_config["ae_title"] = config_.ae_title;
422
423 (void)association_collector_->initialize(collector_config);
424 (void)service_collector_->initialize(collector_config);
425 (void)storage_collector_->initialize(collector_config);
426 storage_collector_->set_pool_metrics_enabled(config_.enable_pool_metrics);
427
428 // Initialize unified CRTP-based collector
429 config_map unified_config;
430 unified_config["ae_title"] = config_.ae_title;
431 unified_config["collect_associations"] = config_.enable_association_metrics ? "true" : "false";
432 unified_config["collect_transfers"] = config_.enable_storage_metrics ? "true" : "false";
433 unified_config["collect_storage"] = config_.enable_storage_metrics ? "true" : "false";
434 unified_config["collect_queries"] = config_.enable_service_metrics ? "true" : "false";
435 unified_config["collect_pools"] = config_.enable_pool_metrics ? "true" : "false";
436 (void)unified_collector_->initialize(unified_config);
437}
438
439inline void pacs_monitor::record_metric(std::string_view name, double value) {
440 std::lock_guard<std::mutex> lock(custom_metrics_mutex_);
441 custom_metrics_.emplace_back(std::string(name), value);
442}
443
445 std::string_view name,
446 double value,
447 const std::unordered_map<std::string, std::string>& tags) {
448 std::lock_guard<std::mutex> lock(custom_metrics_mutex_);
449 custom_metrics_.emplace_back(std::string(name), value, metric_type::gauge, tags);
450}
451
453 metrics_snapshot snapshot;
454 snapshot.source_id = config_.ae_title;
455 collect_all_metrics(snapshot);
456 return snapshot;
457}
458
460 std::shared_lock<std::shared_mutex> config_lock(config_mutex_);
461
462 // Collect association metrics
464 for (const auto& m : association_collector_->collect()) {
465 snapshot.add_metric(
466 m.name,
467 m.value,
468 m.type == "counter" ? metric_type::counter : metric_type::gauge);
469 }
470 }
471
472 // Collect service metrics
474 for (const auto& m : service_collector_->collect()) {
475 snapshot.add_metric(
476 m.name,
477 m.value,
478 m.type == "counter" ? metric_type::counter : metric_type::gauge);
479 }
480 }
481
482 // Collect storage metrics
484 for (const auto& m : storage_collector_->collect()) {
485 snapshot.add_metric(
486 m.name,
487 m.value,
488 m.type == "counter" ? metric_type::counter : metric_type::gauge);
489 }
490 }
491
492 // Add custom metrics
493 {
494 std::lock_guard<std::mutex> lock(custom_metrics_mutex_);
495 for (const auto& m : custom_metrics_) {
496 snapshot.metrics.push_back(m);
497 }
498 }
499}
500
502 const auto start = std::chrono::steady_clock::now();
503 health_check_result result;
505
506 std::lock_guard<std::mutex> lock(health_checks_mutex_);
507
508 bool all_healthy = true;
509 bool any_check_failed = false;
510
511 for (const auto& [component, check] : health_checks_) {
512 try {
513 if (!check()) {
514 all_healthy = false;
515 result.metadata[component] = "unhealthy";
516 } else {
517 result.metadata[component] = "healthy";
518 }
519 } catch (const std::exception& e) {
520 any_check_failed = true;
521 result.metadata[component] = std::string("error: ") + e.what();
522 }
523 }
524
525 // Check collectors health
526 if (association_collector_ && !association_collector_->is_healthy()) {
527 all_healthy = false;
528 result.metadata["association_collector"] = "unhealthy";
529 }
530 if (service_collector_ && !service_collector_->is_healthy()) {
531 all_healthy = false;
532 result.metadata["service_collector"] = "unhealthy";
533 }
534 if (storage_collector_ && !storage_collector_->is_healthy()) {
535 all_healthy = false;
536 result.metadata["storage_collector"] = "unhealthy";
537 }
538 if (unified_collector_ && !unified_collector_->is_healthy()) {
539 all_healthy = false;
540 result.metadata["unified_collector"] = "unhealthy";
541 }
542
543 if (any_check_failed) {
545 result.message = "Some health checks threw exceptions";
546 } else if (!all_healthy) {
548 result.message = "Some components are unhealthy";
549 } else {
550 result.message = "All components healthy";
551 }
552
553 const auto end = std::chrono::steady_clock::now();
554 result.check_duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
555
556 return result;
557}
558
559inline void pacs_monitor::reset() {
560 // Reset underlying metrics
562
563 // Clear custom metrics
564 {
565 std::lock_guard<std::mutex> lock(custom_metrics_mutex_);
566 custom_metrics_.clear();
567 }
568}
569
570inline std::string pacs_monitor::to_prometheus() const {
571 // Use existing pacs_metrics Prometheus export
573}
574
575inline std::string pacs_monitor::to_json() const {
576 // Use existing pacs_metrics JSON export
578}
579
581 std::string_view component,
582 std::function<bool()> check) {
583 std::lock_guard<std::mutex> lock(health_checks_mutex_);
584 health_checks_[std::string(component)] = std::move(check);
585}
586
587inline void pacs_monitor::unregister_health_check(std::string_view component) {
588 std::lock_guard<std::mutex> lock(health_checks_mutex_);
589 health_checks_.erase(std::string(component));
590}
591
593 std::shared_lock<std::shared_mutex> lock(config_mutex_);
594 return config_;
595}
596
598 std::unique_lock<std::shared_mutex> lock(config_mutex_);
599 config_ = config;
600
601 // Update collector configurations
604 }
605 if (service_collector_) {
606 service_collector_->set_ae_title(config_.ae_title);
607 }
608 if (storage_collector_) {
609 storage_collector_->set_ae_title(config_.ae_title);
610 storage_collector_->set_pool_metrics_enabled(config_.enable_pool_metrics);
611 }
612 if (unified_collector_) {
613 unified_collector_->set_ae_title(config_.ae_title);
614 unified_collector_->set_collect_associations(config_.enable_association_metrics);
615 unified_collector_->set_collect_transfers(config_.enable_storage_metrics);
619 }
620}
621
625
629
633
637
639 if (unified_collector_) {
640 return unified_collector_->get_snapshot();
641 }
642 return dicom_metrics_snapshot{};
643}
644
645} // namespace kcenon::pacs::monitoring
Collector for DICOM association lifecycle metrics.
CRTP-based unified DICOM metrics collector.
Collector for DICOM DIMSE service operation metrics.
Collector for DICOM storage and data transfer metrics.
std::string to_prometheus(std::string_view prefix="pacs") const
Export metrics in Prometheus text format.
void reset() noexcept
Reset all metrics to zero.
static pacs_metrics & global_metrics() noexcept
Get the global singleton instance.
std::string to_json() const
Export metrics as JSON string.
Unified PACS monitoring implementing IMonitor interface.
void update_config(const pacs_monitor_config &config)
Update configuration.
pacs_monitor(pacs_monitor &&)=delete
void unregister_health_check(std::string_view component)
Unregister a health check.
dicom_storage_collector & storage_collector()
Get the storage collector.
std::vector< metric_value > custom_metrics_
void register_health_check(std::string_view component, std::function< bool()> check)
Register a health check for a component.
std::string to_json() const
Export all metrics as JSON.
std::unique_ptr< dicom_storage_collector > storage_collector_
dicom_service_collector & service_collector()
Get the service collector.
std::unordered_map< std::string, std::function< bool()> > health_checks_
void record_metric(std::string_view name, double value)
Record a metric value.
static pacs_monitor & global_monitor() noexcept
Get the global singleton instance.
dicom_metrics_collector & unified_collector()
Get the unified CRTP-based metrics collector.
dicom_association_collector & association_collector()
Get the association collector.
pacs_monitor(const pacs_monitor &)=delete
pacs_monitor(const pacs_monitor_config &config={})
Construct a new PACS monitor.
const pacs_monitor_config & get_config() const
Get current configuration.
health_check_result check_health()
Perform health check.
std::unique_ptr< dicom_metrics_collector > unified_collector_
std::string to_prometheus() const
Export all metrics in Prometheus text format.
std::unique_ptr< dicom_association_collector > association_collector_
pacs_monitor & operator=(const pacs_monitor &)=delete
std::unique_ptr< dicom_service_collector > service_collector_
void collect_all_metrics(metrics_snapshot &snapshot)
dicom_metrics_snapshot get_unified_snapshot() const
Get a snapshot from the unified collector.
metrics_snapshot get_metrics()
Get current metrics snapshot.
pacs_monitor & operator=(pacs_monitor &&)=delete
DICOM Association metrics collector.
CRTP-based unified DICOM metrics collector.
DICOM DIMSE Service metrics collector.
DICOM Storage metrics collector.
Health check service for PACS system components.
monitor_health_status
Standard health status levels.
metric_type
Types of metrics supported by the monitoring system.
@ gauge
Instant value that can go up or down.
@ counter
Monotonic increasing value.
@ summary
Statistical summary (min, max, mean, percentiles)
@ histogram
Distribution of values across buckets.
constexpr std::string_view to_string(health_level level) noexcept
Convert health level to string representation.
@ healthy
All components healthy, system fully operational.
@ degraded
Some non-critical components degraded, system operational.
@ unhealthy
Critical components failing, system may not function correctly.
std::unordered_map< std::string, std::string > config_map
Type alias for configuration map.
Operation metrics collection for PACS DICOM services.
Snapshot of all DICOM metrics at a point in time.
Result of a health check operation.
std::chrono::system_clock::time_point timestamp
std::unordered_map< std::string, std::string > metadata
Standard metric value structure with type information.
metric_value(std::string n, double v, metric_type t=metric_type::gauge, std::unordered_map< std::string, std::string > tg={})
std::chrono::system_clock::time_point timestamp
std::unordered_map< std::string, std::string > tags
Complete snapshot of metrics at a point in time.
void add_metric(const std::string &name, double value, metric_type type=metric_type::gauge)
std::chrono::system_clock::time_point capture_time
std::vector< metric_value > metrics
Configuration for the PACS monitor.
bool enable_unified_collector
Enable unified CRTP-based metrics collector.
bool enable_association_metrics
Enable association metrics collection.
bool enable_service_metrics
Enable DIMSE service metrics collection.
bool enable_storage_metrics
Enable storage metrics collection.
std::string metric_prefix
Metric name prefix for Prometheus export.
std::string ae_title
Application Entity title for metric labels.
bool enable_pool_metrics
Enable object pool metrics.
std::string_view name