PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
monitoring_adapter.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
11
12#include <kcenon/monitoring/core/performance_monitor.h>
13#include <kcenon/monitoring/tracing/distributed_tracer.h>
14
15#include <atomic>
16#include <chrono>
17#include <mutex>
18#include <shared_mutex>
19#include <unordered_map>
20
22
23// =============================================================================
24// Metric Names
25// =============================================================================
26
27namespace metrics {
28// Counter metrics
29constexpr const char* c_store_total = "pacs_c_store_total";
30constexpr const char* c_store_success = "pacs_c_store_success";
31constexpr const char* c_store_failure = "pacs_c_store_failure";
32constexpr const char* c_store_bytes_total = "pacs_c_store_bytes_total";
33constexpr const char* c_find_total = "pacs_c_find_total";
34constexpr const char* associations_total = "pacs_associations_total";
35
36// Histogram metrics (using timing)
37constexpr const char* c_store_duration = "pacs_c_store_duration_seconds";
38constexpr const char* c_find_duration = "pacs_c_find_duration_seconds";
39constexpr const char* c_find_matches = "pacs_c_find_matches";
40
41// Gauge metrics
42constexpr const char* associations_active = "pacs_associations_active";
43constexpr const char* storage_instances = "pacs_storage_instances";
44constexpr const char* storage_bytes = "pacs_storage_bytes";
45} // namespace metrics
46
47// =============================================================================
48// Span Implementation
49// =============================================================================
50
52public:
53 explicit impl(std::string_view operation_name) {
54 auto& tracer = kcenon::monitoring::global_tracer();
55 auto result = tracer.start_span(std::string(operation_name));
56 if (result.is_ok()) {
57 span_ = result.value();
58 valid_ = true;
59 }
60 }
61
63 if (valid_ && span_) {
64 auto& tracer = kcenon::monitoring::global_tracer();
65 tracer.finish_span(span_);
66 }
67 }
68
69 void set_tag(std::string_view key, std::string_view value) {
70 if (valid_ && span_) {
71 span_->tags[std::string(key)] = std::string(value);
72 }
73 }
74
75 void add_event(std::string_view name) {
76 if (valid_ && span_) {
77 // Add event as a tag with timestamp
78 auto now = std::chrono::system_clock::now();
79 auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
80 now.time_since_epoch())
81 .count();
82 span_->tags["event." + std::string(name)] = std::to_string(timestamp);
83 }
84 }
85
86 void set_error(const std::exception& e) {
87 if (valid_ && span_) {
88 span_->status = kcenon::monitoring::trace_span::status_code::error;
89 span_->status_message = e.what();
90 span_->tags["error"] = "true";
91 span_->tags["error.message"] = e.what();
92 }
93 }
94
95 [[nodiscard]] auto trace_id() const -> std::string {
96 if (valid_ && span_) {
97 return span_->trace_id;
98 }
99 return "";
100 }
101
102 [[nodiscard]] auto span_id() const -> std::string {
103 if (valid_ && span_) {
104 return span_->span_id;
105 }
106 return "";
107 }
108
109 [[nodiscard]] auto is_valid() const noexcept -> bool { return valid_; }
110
111private:
112 std::shared_ptr<kcenon::monitoring::trace_span> span_;
113 bool valid_{false};
114};
115
116// Span class implementation
117monitoring_adapter::span::span(std::string_view operation_name)
118 : impl_(std::make_unique<impl>(operation_name)) {}
119
121
122monitoring_adapter::span::span(span&& other) noexcept = default;
123
125
126void monitoring_adapter::span::set_tag(std::string_view key, std::string_view value) {
127 if (impl_) {
128 impl_->set_tag(key, value);
129 }
130}
131
133 if (impl_) {
134 impl_->add_event(name);
135 }
136}
137
138void monitoring_adapter::span::set_error(const std::exception& e) {
139 if (impl_) {
140 impl_->set_error(e);
141 }
142}
143
144auto monitoring_adapter::span::trace_id() const -> std::string {
145 if (impl_) {
146 return impl_->trace_id();
147 }
148 return "";
149}
150
151auto monitoring_adapter::span::span_id() const -> std::string {
152 if (impl_) {
153 return impl_->span_id();
154 }
155 return "";
156}
157
158auto monitoring_adapter::span::is_valid() const noexcept -> bool {
159 return impl_ && impl_->is_valid();
160}
161
162// =============================================================================
163// Monitoring Adapter Implementation
164// =============================================================================
165
167public:
168 impl() = default;
169 ~impl() { shutdown(); }
170
171 void initialize(const monitoring_config& config) {
172 std::lock_guard lock(mutex_);
173
174 if (initialized_) {
175 return;
176 }
177
178 config_ = config;
179
180 // Initialize performance monitor
181 if (config.enable_metrics) {
182 auto& monitor = kcenon::monitoring::global_performance_monitor();
183 monitor.set_enabled(true);
184 monitor.get_profiler().set_max_samples(config.max_samples_per_operation);
185
186 auto init_result = monitor.initialize();
187 if (init_result.is_err()) {
188 // Log warning but continue - metrics will still work locally
189 }
190 }
191
192 // Initialize counters and gauges to zero
193 counters_[metrics::c_store_total] = 0;
194 counters_[metrics::c_store_success] = 0;
195 counters_[metrics::c_store_failure] = 0;
196 counters_[metrics::c_store_bytes_total] = 0;
197 counters_[metrics::c_find_total] = 0;
198 counters_[metrics::associations_total] = 0;
199
200 gauges_[metrics::associations_active] = 0.0;
201 gauges_[metrics::storage_instances] = 0.0;
202 gauges_[metrics::storage_bytes] = 0.0;
203
204 initialized_ = true;
205 }
206
207 void shutdown() {
208 std::lock_guard lock(mutex_);
209
210 if (!initialized_) {
211 return;
212 }
213
214 // Cleanup performance monitor
215 auto& monitor = kcenon::monitoring::global_performance_monitor();
216 auto cleanup_result = monitor.cleanup();
217 (void)cleanup_result; // Ignore cleanup errors during shutdown
218
219 initialized_ = false;
220 }
221
222 [[nodiscard]] auto is_initialized() const noexcept -> bool {
223 return initialized_.load();
224 }
225
226 void increment_counter(std::string_view name, std::int64_t value) {
227 if (!initialized_) return;
228
229 std::lock_guard lock(counters_mutex_);
230 counters_[std::string(name)] += value;
231 }
232
233 void set_gauge(std::string_view name, double value) {
234 if (!initialized_) return;
235
236 std::lock_guard lock(gauges_mutex_);
237 gauges_[std::string(name)] = value;
238 }
239
240 void record_histogram(std::string_view name, double value) {
241 if (!initialized_ || !config_.enable_metrics) return;
242
243 auto& profiler = kcenon::monitoring::global_performance_monitor().get_profiler();
244 // Convert double to nanoseconds for histogram recording
245 auto duration = std::chrono::nanoseconds(static_cast<std::int64_t>(value * 1e9));
246 profiler.record_sample(std::string(name), duration, true);
247 }
248
249 void record_timing(std::string_view name, std::chrono::nanoseconds duration) {
250 if (!initialized_ || !config_.enable_metrics) return;
251
252 auto& profiler = kcenon::monitoring::global_performance_monitor().get_profiler();
253 profiler.record_sample(std::string(name), duration, true);
254 }
255
256 void record_c_store(std::chrono::nanoseconds duration,
257 std::size_t bytes,
258 bool success) {
259 if (!initialized_) return;
260
261 // Update counters
262 {
263 std::lock_guard lock(counters_mutex_);
264 counters_[metrics::c_store_total]++;
265 if (success) {
266 counters_[metrics::c_store_success]++;
267 } else {
268 counters_[metrics::c_store_failure]++;
269 }
270 counters_[metrics::c_store_bytes_total] += static_cast<std::int64_t>(bytes);
271 }
272
273 // Record timing histogram
274 if (config_.enable_metrics) {
275 auto& profiler =
276 kcenon::monitoring::global_performance_monitor().get_profiler();
277 profiler.record_sample(metrics::c_store_duration, duration, success);
278 }
279 }
280
281 void record_c_find(std::chrono::nanoseconds duration,
282 std::size_t matches,
283 query_level level) {
284 if (!initialized_) return;
285
286 // Update counters
287 {
288 std::lock_guard lock(counters_mutex_);
289 counters_[metrics::c_find_total]++;
290 }
291
292 // Record timing and matches histogram
293 if (config_.enable_metrics) {
294 auto& profiler =
295 kcenon::monitoring::global_performance_monitor().get_profiler();
296 profiler.record_sample(metrics::c_find_duration, duration, true);
297
298 // Record matches as a timing metric (converting to nanoseconds)
299 auto matches_as_duration =
300 std::chrono::nanoseconds(static_cast<std::int64_t>(matches));
301 profiler.record_sample(metrics::c_find_matches, matches_as_duration, true);
302 }
303
304 (void)level; // Level used for tagging in tracing
305 }
306
307 void record_association(const std::string& calling_ae, bool established) {
308 if (!initialized_) return;
309
310 // Update counters and gauges
311 {
312 std::lock_guard lock(counters_mutex_);
313 counters_[metrics::associations_total]++;
314 }
315
316 {
317 std::lock_guard lock(gauges_mutex_);
318 if (established) {
319 gauges_[metrics::associations_active]++;
320 } else {
321 gauges_[metrics::associations_active] =
322 (std::max)(0.0, gauges_[metrics::associations_active] - 1);
323 }
324 }
325
326 (void)calling_ae; // Used for logging/tagging
327 }
328
329 void update_storage_stats(std::size_t total_instances, std::size_t total_bytes) {
330 if (!initialized_) return;
331
332 std::lock_guard lock(gauges_mutex_);
333 gauges_[metrics::storage_instances] = static_cast<double>(total_instances);
334 gauges_[metrics::storage_bytes] = static_cast<double>(total_bytes);
335 }
336
339 status.healthy = true;
340 status.status = "healthy";
341
342 std::shared_lock lock(health_checks_mutex_);
343 for (const auto& [component, check] : health_checks_) {
344 try {
345 bool component_healthy = check();
346 status.components[component] = component_healthy ? "healthy" : "unhealthy";
347 if (!component_healthy) {
348 status.healthy = false;
349 status.status = "degraded";
350 }
351 } catch (const std::exception& e) {
352 status.components[component] = std::string("error: ") + e.what();
353 status.healthy = false;
354 status.status = "degraded";
355 }
356 }
357
358 return status;
359 }
360
361 void register_health_check(std::string_view component,
362 std::function<bool()> check) {
363 std::unique_lock lock(health_checks_mutex_);
364 health_checks_[std::string(component)] = std::move(check);
365 }
366
367 void unregister_health_check(std::string_view component) {
368 std::unique_lock lock(health_checks_mutex_);
369 health_checks_.erase(std::string(component));
370 }
371
372 [[nodiscard]] auto get_config() const -> const monitoring_config& {
373 return config_;
374 }
375
376private:
377 mutable std::mutex mutex_;
378 mutable std::mutex counters_mutex_;
379 mutable std::mutex gauges_mutex_;
380 mutable std::shared_mutex health_checks_mutex_;
381
382 std::atomic<bool> initialized_{false};
384
385 // Metric storage
386 std::unordered_map<std::string, std::int64_t> counters_;
387 std::unordered_map<std::string, double> gauges_;
388 std::unordered_map<std::string, std::function<bool()>> health_checks_;
389};
390
391// =============================================================================
392// Static Member Initialization
393// =============================================================================
394
395std::unique_ptr<monitoring_adapter::impl> monitoring_adapter::pimpl_ =
396 std::make_unique<monitoring_adapter::impl>();
397
398// =============================================================================
399// Initialization
400// =============================================================================
401
403 pimpl_->initialize(config);
404}
405
407
408auto monitoring_adapter::is_initialized() noexcept -> bool {
409 return pimpl_->is_initialized();
410}
411
412// =============================================================================
413// Metrics
414// =============================================================================
415
417 std::int64_t value) {
419}
420
421void monitoring_adapter::set_gauge(std::string_view name, double value) {
422 pimpl_->set_gauge(name, value);
423}
424
425void monitoring_adapter::record_histogram(std::string_view name, double value) {
427}
428
430 std::chrono::nanoseconds duration) {
431 pimpl_->record_timing(name, duration);
432}
433
434// =============================================================================
435// DICOM-Specific Metrics
436// =============================================================================
437
438void monitoring_adapter::record_c_store(std::chrono::nanoseconds duration,
439 std::size_t bytes,
440 bool success) {
441 pimpl_->record_c_store(duration, bytes, success);
442}
443
444void monitoring_adapter::record_c_find(std::chrono::nanoseconds duration,
445 std::size_t matches,
446 query_level level) {
447 pimpl_->record_c_find(duration, matches, level);
448}
449
450void monitoring_adapter::record_association(const std::string& calling_ae,
451 bool established) {
452 pimpl_->record_association(calling_ae, established);
453}
454
455void monitoring_adapter::update_storage_stats(std::size_t total_instances,
456 std::size_t total_bytes) {
457 pimpl_->update_storage_stats(total_instances, total_bytes);
458}
459
460// =============================================================================
461// Distributed Tracing
462// =============================================================================
463
464auto monitoring_adapter::start_span(std::string_view operation) -> span {
465 return span(operation);
466}
467
468// =============================================================================
469// Health Check
470// =============================================================================
471
475
476void monitoring_adapter::register_health_check(std::string_view component,
477 std::function<bool()> check) {
478 pimpl_->register_health_check(component, std::move(check));
479}
480
481void monitoring_adapter::unregister_health_check(std::string_view component) {
483}
484
485// =============================================================================
486// Configuration
487// =============================================================================
488
492
493// =============================================================================
494// Private Helpers
495// =============================================================================
496
498 switch (level) {
500 return "PATIENT";
502 return "STUDY";
504 return "SERIES";
506 return "IMAGE";
507 default:
508 return "UNKNOWN";
509 }
510}
511
512} // namespace kcenon::pacs::integration
std::unordered_map< std::string, std::function< bool()> > health_checks_
void record_c_find(std::chrono::nanoseconds duration, std::size_t matches, query_level level)
void increment_counter(std::string_view name, std::int64_t value)
auto get_config() const -> const monitoring_config &
void set_gauge(std::string_view name, double value)
std::unordered_map< std::string, std::int64_t > counters_
auto get_health() -> monitoring_adapter::health_status
void update_storage_stats(std::size_t total_instances, std::size_t total_bytes)
void register_health_check(std::string_view component, std::function< bool()> check)
void record_c_store(std::chrono::nanoseconds duration, std::size_t bytes, bool success)
void record_histogram(std::string_view name, double value)
void record_timing(std::string_view name, std::chrono::nanoseconds duration)
std::unordered_map< std::string, double > gauges_
void record_association(const std::string &calling_ae, bool established)
std::shared_ptr< kcenon::monitoring::trace_span > span_
void set_tag(std::string_view key, std::string_view value)
Represents a unit of work in distributed tracing.
~span()
Destructor - automatically finishes the span.
void set_error(const std::exception &e)
Mark the span as an error.
void set_tag(std::string_view key, std::string_view value)
Set a tag on the span.
auto span_id() const -> std::string
Get the span ID.
void add_event(std::string_view name)
Add an event to the span.
auto is_valid() const noexcept -> bool
Check if span is valid (properly initialized)
auto trace_id() const -> std::string
Get the trace ID.
span(std::string_view operation_name)
Construct a new span.
static void update_storage_stats(std::size_t total_instances, std::size_t total_bytes)
Update storage statistics.
static void record_c_find(std::chrono::nanoseconds duration, std::size_t matches, query_level level)
Record C-FIND operation metrics.
static auto get_health() -> health_status
Get current health status.
static auto get_config() -> const monitoring_config &
Get the current configuration.
static void unregister_health_check(std::string_view component)
Unregister a health check.
static void record_timing(std::string_view name, std::chrono::nanoseconds duration)
Record a timing measurement.
static void record_c_store(std::chrono::nanoseconds duration, std::size_t bytes, bool success)
Record C-STORE operation metrics.
static void record_association(const std::string &calling_ae, bool established)
Record DICOM association metrics.
static auto start_span(std::string_view operation) -> span
Start a new trace span.
static void record_histogram(std::string_view name, double value)
Record a histogram sample.
static void register_health_check(std::string_view component, std::function< bool()> check)
Register a health check for a component.
static void initialize(const monitoring_config &config)
Initialize the monitoring adapter with configuration.
static void increment_counter(std::string_view name, std::int64_t value=1)
Increment a counter metric.
static void set_gauge(std::string_view name, double value)
Set a gauge metric value.
static auto query_level_to_string(query_level level) -> std::string
static void shutdown()
Shutdown the monitoring adapter.
static auto is_initialized() noexcept -> bool
Check if the monitoring adapter is initialized.
Adapter for PACS performance metrics and distributed tracing.
query_level
DICOM query retrieve level.
Health check result containing component status.
Configuration options for the monitoring adapter.
bool enable_metrics
Enable metrics collection.
std::size_t max_samples_per_operation
Maximum samples to keep per operation.
std::string_view name