PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
dicom_service_collector.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
18#pragma once
19
20#include <array>
21#include <chrono>
22#include <cstdint>
23#include <memory>
24#include <mutex>
25#include <string>
26#include <unordered_map>
27#include <vector>
28
29#include "../pacs_metrics.h"
30
32
38 std::string name;
39 double value;
40 std::string type; // "gauge" or "counter"
41 std::chrono::system_clock::time_point timestamp;
42 std::unordered_map<std::string, std::string> labels;
43
44 service_metric(std::string n,
45 double v,
46 std::string t,
47 std::unordered_map<std::string, std::string> l = {})
48 : name(std::move(n))
49 , value(v)
50 , type(std::move(t))
51 , timestamp(std::chrono::system_clock::now())
52 , labels(std::move(l)) {}
53};
54
88public:
89 // =========================================================================
90 // Construction
91 // =========================================================================
92
97 explicit dicom_service_collector(std::string ae_title = "PACS_SCP");
98
103
104 // Non-copyable, non-movable
109
110 // =========================================================================
111 // Collector Plugin Interface
112 // =========================================================================
113
119 [[nodiscard]] bool initialize(
120 const std::unordered_map<std::string, std::string>& config);
121
126 [[nodiscard]] std::vector<service_metric> collect();
127
132 [[nodiscard]] std::string get_name() const;
133
138 [[nodiscard]] std::vector<std::string> get_metric_types() const;
139
144 [[nodiscard]] bool is_healthy() const;
145
150 [[nodiscard]] std::unordered_map<std::string, double> get_statistics() const;
151
152 // =========================================================================
153 // Configuration
154 // =========================================================================
155
160 void set_ae_title(std::string ae_title);
161
166 [[nodiscard]] std::string get_ae_title() const;
167
173 void set_operation_enabled(dimse_operation op, bool enabled);
174
180 [[nodiscard]] bool is_operation_enabled(dimse_operation op) const;
181
182private:
183 // Configuration
184 std::string ae_title_;
185 bool initialized_{false};
186
187 // Operation enable flags (all enabled by default)
188 std::array<bool, 11> operation_enabled_{
189 true, true, true, true, true, // C-ECHO, C-STORE, C-FIND, C-MOVE, C-GET
190 true, true, true, true, true, true // N-CREATE, N-SET, N-GET, N-ACTION, N-EVENT, N-DELETE
191 };
192
193 // Statistics
194 mutable std::mutex stats_mutex_;
195 std::uint64_t collection_count_{0};
196 std::chrono::steady_clock::time_point init_time_;
197
198 // Helper methods
200 std::vector<service_metric>& metrics,
203
204 [[nodiscard]] service_metric create_metric(
205 const std::string& name,
206 double value,
207 const std::string& type,
208 const std::string& operation) const;
209};
210
211// ─────────────────────────────────────────────────────────────────────────────
212// Inline Implementation
213// ─────────────────────────────────────────────────────────────────────────────
214
216 : ae_title_(std::move(ae_title)) {}
217
219 const std::unordered_map<std::string, std::string>& config) {
220 // Extract AE title from config if provided
221 if (auto it = config.find("ae_title"); it != config.end()) {
222 ae_title_ = it->second;
223 }
224
225 init_time_ = std::chrono::steady_clock::now();
226 initialized_ = true;
227 return true;
228}
229
230inline std::vector<service_metric> dicom_service_collector::collect() {
231 std::vector<service_metric> metrics;
232
233 if (!initialized_) {
234 return metrics;
235 }
236
237 const auto& pacs = pacs_metrics::global_metrics();
238
239 // Collect metrics for each enabled DIMSE operation
240 static constexpr std::array<dimse_operation, 11> all_ops = {
252 };
253
254 for (std::size_t i = 0; i < all_ops.size(); ++i) {
255 if (operation_enabled_[i]) {
257 metrics,
258 all_ops[i],
259 pacs.get_counter(all_ops[i]));
260 }
261 }
262
263 // Update statistics
264 {
265 std::lock_guard<std::mutex> lock(stats_mutex_);
267 }
268
269 return metrics;
270}
271
273 std::vector<service_metric>& metrics,
275 const operation_counter& counter) {
276
277 const std::string op_name(to_string(op));
278
279 // Total requests (counter)
280 metrics.push_back(create_metric(
281 "dicom_" + op_name + "_requests_total",
282 static_cast<double>(counter.total_count()),
283 "counter",
284 op_name));
285
286 // Successful requests (counter)
287 metrics.push_back(create_metric(
288 "dicom_" + op_name + "_success_total",
289 static_cast<double>(counter.success_count.load(std::memory_order_relaxed)),
290 "counter",
291 op_name));
292
293 // Failed requests (counter)
294 metrics.push_back(create_metric(
295 "dicom_" + op_name + "_failure_total",
296 static_cast<double>(counter.failure_count.load(std::memory_order_relaxed)),
297 "counter",
298 op_name));
299
300 // Duration metrics (only if there have been requests)
301 if (counter.total_count() > 0) {
302 // Average duration in seconds (gauge)
303 metrics.push_back(create_metric(
304 "dicom_" + op_name + "_duration_seconds_avg",
305 static_cast<double>(counter.average_duration_us()) / 1'000'000.0,
306 "gauge",
307 op_name));
308
309 // Min duration in seconds (gauge)
310 const auto min_us = counter.min_duration_us.load(std::memory_order_relaxed);
311 if (min_us != UINT64_MAX) {
312 metrics.push_back(create_metric(
313 "dicom_" + op_name + "_duration_seconds_min",
314 static_cast<double>(min_us) / 1'000'000.0,
315 "gauge",
316 op_name));
317 }
318
319 // Max duration in seconds (gauge)
320 const auto max_us = counter.max_duration_us.load(std::memory_order_relaxed);
321 if (max_us > 0) {
322 metrics.push_back(create_metric(
323 "dicom_" + op_name + "_duration_seconds_max",
324 static_cast<double>(max_us) / 1'000'000.0,
325 "gauge",
326 op_name));
327 }
328
329 // Total duration in seconds (counter) - for calculating rates
330 metrics.push_back(create_metric(
331 "dicom_" + op_name + "_duration_seconds_sum",
332 static_cast<double>(counter.total_duration_us.load(std::memory_order_relaxed)) / 1'000'000.0,
333 "counter",
334 op_name));
335 }
336}
337
338inline std::string dicom_service_collector::get_name() const {
339 return "dicom_service_collector";
340}
341
342inline std::vector<std::string> dicom_service_collector::get_metric_types() const {
343 std::vector<std::string> types;
344
345 static constexpr std::array<dimse_operation, 11> all_ops = {
357 };
358
359 for (const auto op : all_ops) {
360 const std::string op_name(to_string(op));
361 types.push_back("dicom_" + op_name + "_requests_total");
362 types.push_back("dicom_" + op_name + "_success_total");
363 types.push_back("dicom_" + op_name + "_failure_total");
364 types.push_back("dicom_" + op_name + "_duration_seconds_avg");
365 types.push_back("dicom_" + op_name + "_duration_seconds_min");
366 types.push_back("dicom_" + op_name + "_duration_seconds_max");
367 types.push_back("dicom_" + op_name + "_duration_seconds_sum");
368 }
369
370 return types;
371}
372
374 return initialized_;
375}
376
377inline std::unordered_map<std::string, double>
379 std::lock_guard<std::mutex> lock(stats_mutex_);
380
381 std::unordered_map<std::string, double> stats;
382 stats["collection_count"] = static_cast<double>(collection_count_);
383
384 if (initialized_) {
385 const auto uptime = std::chrono::duration_cast<std::chrono::seconds>(
386 std::chrono::steady_clock::now() - init_time_);
387 stats["uptime_seconds"] = static_cast<double>(uptime.count());
388 }
389
390 return stats;
391}
392
393inline void dicom_service_collector::set_ae_title(std::string ae_title) {
394 ae_title_ = std::move(ae_title);
395}
396
397inline std::string dicom_service_collector::get_ae_title() const {
398 return ae_title_;
399}
400
403 bool enabled) {
404 operation_enabled_[static_cast<std::size_t>(op)] = enabled;
405}
406
408 return operation_enabled_[static_cast<std::size_t>(op)];
409}
410
412 const std::string& name,
413 double value,
414 const std::string& type,
415 const std::string& operation) const {
416 return service_metric(
417 name,
418 value,
419 type,
420 {{"ae", ae_title_}, {"operation", operation}});
421}
422
423} // namespace kcenon::pacs::monitoring
Collector for DICOM DIMSE service operation metrics.
dicom_service_collector(const dicom_service_collector &)=delete
service_metric create_metric(const std::string &name, double value, const std::string &type, const std::string &operation) const
void collect_operation_metrics(std::vector< service_metric > &metrics, dimse_operation op, const operation_counter &counter)
dicom_service_collector & operator=(const dicom_service_collector &)=delete
std::string get_ae_title() const
Get the current AE title.
bool is_operation_enabled(dimse_operation op) const
Check if metrics are enabled for an operation.
dicom_service_collector(std::string ae_title="PACS_SCP")
Default constructor.
std::string get_name() const
Get the collector name.
bool initialize(const std::unordered_map< std::string, std::string > &config)
Initialize the collector with configuration.
dicom_service_collector(dicom_service_collector &&)=delete
std::vector< service_metric > collect()
Collect current DIMSE service metrics.
void set_ae_title(std::string ae_title)
Set the AE title for metric labels.
std::unordered_map< std::string, double > get_statistics() const
Get collector statistics.
void set_operation_enabled(dimse_operation op, bool enabled)
Enable or disable specific operation metrics.
dicom_service_collector & operator=(dicom_service_collector &&)=delete
bool is_healthy() const
Check if the collector is healthy.
std::vector< std::string > get_metric_types() const
Get supported metric types.
static pacs_metrics & global_metrics() noexcept
Get the global singleton instance.
dimse_operation
DICOM Message Service Element (DIMSE) operation types.
@ c_store
C-STORE (Storage Service)
@ c_move
C-MOVE (Retrieve Service)
@ c_echo
C-ECHO (Verification Service)
@ counter
Monotonic increasing value.
constexpr std::string_view to_string(health_level level) noexcept
Convert health level to string representation.
Operation metrics collection for PACS DICOM services.
Atomic counter for tracking operation success/failure counts.
Standard metric structure for DIMSE service data.
service_metric(std::string n, double v, std::string t, std::unordered_map< std::string, std::string > l={})
std::chrono::system_clock::time_point timestamp
std::unordered_map< std::string, std::string > labels
std::string_view name