PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
dicom_association_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 <chrono>
21#include <cstdint>
22#include <memory>
23#include <mutex>
24#include <string>
25#include <unordered_map>
26#include <vector>
27
28#include "../pacs_metrics.h"
29
31
37 std::string name;
38 double value;
39 std::string type; // "gauge" or "counter"
40 std::chrono::system_clock::time_point timestamp;
41 std::unordered_map<std::string, std::string> labels;
42
43 association_metric(std::string n,
44 double v,
45 std::string t,
46 std::unordered_map<std::string, std::string> l = {})
47 : name(std::move(n))
48 , value(v)
49 , type(std::move(t))
50 , timestamp(std::chrono::system_clock::now())
51 , labels(std::move(l)) {}
52};
53
80public:
81 // =========================================================================
82 // Construction
83 // =========================================================================
84
89 explicit dicom_association_collector(std::string ae_title = "PACS_SCP");
90
95
96 // Non-copyable, non-movable
101
102 // =========================================================================
103 // Collector Plugin Interface
104 // =========================================================================
105
111 [[nodiscard]] bool initialize(
112 const std::unordered_map<std::string, std::string>& config);
113
118 [[nodiscard]] std::vector<association_metric> collect();
119
124 [[nodiscard]] std::string get_name() const;
125
130 [[nodiscard]] std::vector<std::string> get_metric_types() const;
131
136 [[nodiscard]] bool is_healthy() const;
137
142 [[nodiscard]] std::unordered_map<std::string, double> get_statistics() const;
143
144 // =========================================================================
145 // Configuration
146 // =========================================================================
147
152 void set_ae_title(std::string ae_title);
153
158 [[nodiscard]] std::string get_ae_title() const;
159
160private:
161 // Configuration
162 std::string ae_title_;
163 bool initialized_{false};
164
165 // Statistics
166 mutable std::mutex stats_mutex_;
167 std::uint64_t collection_count_{0};
168 std::chrono::steady_clock::time_point init_time_;
169
170 // Helper to create labeled metric
171 [[nodiscard]] association_metric create_metric(
172 const std::string& name,
173 double value,
174 const std::string& type) const;
175};
176
177// ─────────────────────────────────────────────────────────────────────────────
178// Inline Implementation
179// ─────────────────────────────────────────────────────────────────────────────
180
182 : ae_title_(std::move(ae_title)) {}
183
185 const std::unordered_map<std::string, std::string>& config) {
186 // Extract AE title from config if provided
187 if (auto it = config.find("ae_title"); it != config.end()) {
188 ae_title_ = it->second;
189 }
190
191 init_time_ = std::chrono::steady_clock::now();
192 initialized_ = true;
193 return true;
194}
195
196inline std::vector<association_metric> dicom_association_collector::collect() {
197 std::vector<association_metric> metrics;
198
199 if (!initialized_) {
200 return metrics;
201 }
202
203 const auto& counters = pacs_metrics::global_metrics().associations();
204
205 // Active associations (gauge)
206 metrics.push_back(create_metric(
207 "dicom_associations_active",
208 static_cast<double>(counters.current_active.load(std::memory_order_relaxed)),
209 "gauge"));
210
211 // Peak active associations (gauge)
212 metrics.push_back(create_metric(
213 "dicom_associations_peak_active",
214 static_cast<double>(counters.peak_active.load(std::memory_order_relaxed)),
215 "gauge"));
216
217 // Total established (counter)
218 metrics.push_back(create_metric(
219 "dicom_associations_total",
220 static_cast<double>(counters.total_established.load(std::memory_order_relaxed)),
221 "counter"));
222
223 // Total rejected (counter)
224 metrics.push_back(create_metric(
225 "dicom_associations_rejected_total",
226 static_cast<double>(counters.total_rejected.load(std::memory_order_relaxed)),
227 "counter"));
228
229 // Total aborted (counter)
230 metrics.push_back(create_metric(
231 "dicom_associations_aborted_total",
232 static_cast<double>(counters.total_aborted.load(std::memory_order_relaxed)),
233 "counter"));
234
235 // Success rate (gauge) - calculated metric
236 const auto total = counters.total_established.load(std::memory_order_relaxed);
237 const auto rejected = counters.total_rejected.load(std::memory_order_relaxed);
238 const auto attempted = total + rejected;
239 const double success_rate = (attempted > 0)
240 ? static_cast<double>(total) / static_cast<double>(attempted)
241 : 1.0;
242
243 metrics.push_back(create_metric(
244 "dicom_associations_success_rate",
245 success_rate,
246 "gauge"));
247
248 // Update statistics
249 {
250 std::lock_guard<std::mutex> lock(stats_mutex_);
252 }
253
254 return metrics;
255}
256
257inline std::string dicom_association_collector::get_name() const {
258 return "dicom_association_collector";
259}
260
261inline std::vector<std::string> dicom_association_collector::get_metric_types() const {
262 return {
263 "dicom_associations_active",
264 "dicom_associations_peak_active",
265 "dicom_associations_total",
266 "dicom_associations_rejected_total",
267 "dicom_associations_aborted_total",
268 "dicom_associations_success_rate"
269 };
270}
271
273 return initialized_;
274}
275
276inline std::unordered_map<std::string, double>
278 std::lock_guard<std::mutex> lock(stats_mutex_);
279
280 std::unordered_map<std::string, double> stats;
281 stats["collection_count"] = static_cast<double>(collection_count_);
282
283 if (initialized_) {
284 const auto uptime = std::chrono::duration_cast<std::chrono::seconds>(
285 std::chrono::steady_clock::now() - init_time_);
286 stats["uptime_seconds"] = static_cast<double>(uptime.count());
287 }
288
289 return stats;
290}
291
292inline void dicom_association_collector::set_ae_title(std::string ae_title) {
293 ae_title_ = std::move(ae_title);
294}
295
297 return ae_title_;
298}
299
301 const std::string& name,
302 double value,
303 const std::string& type) const {
304 return association_metric(
305 name,
306 value,
307 type,
308 {{"ae", ae_title_}});
309}
310
311} // namespace kcenon::pacs::monitoring
Collector for DICOM association lifecycle metrics.
dicom_association_collector(std::string ae_title="PACS_SCP")
Default constructor.
std::string get_ae_title() const
Get the current AE title.
bool initialize(const std::unordered_map< std::string, std::string > &config)
Initialize the collector with configuration.
std::vector< std::string > get_metric_types() const
Get supported metric types.
bool is_healthy() const
Check if the collector is healthy.
association_metric create_metric(const std::string &name, double value, const std::string &type) const
dicom_association_collector & operator=(dicom_association_collector &&)=delete
void set_ae_title(std::string ae_title)
Set the AE title for metric labels.
dicom_association_collector(dicom_association_collector &&)=delete
std::vector< association_metric > collect()
Collect current association metrics.
std::unordered_map< std::string, double > get_statistics() const
Get collector statistics.
dicom_association_collector(const dicom_association_collector &)=delete
dicom_association_collector & operator=(const dicom_association_collector &)=delete
static pacs_metrics & global_metrics() noexcept
Get the global singleton instance.
const association_counters & associations() const noexcept
Get association counters.
Operation metrics collection for PACS DICOM services.
Standard metric structure for association data.
std::unordered_map< std::string, std::string > labels
std::chrono::system_clock::time_point timestamp
association_metric(std::string n, double v, std::string t, std::unordered_map< std::string, std::string > l={})
std::string_view name