PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
pacs_metrics.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
13
14#include <iomanip>
15#include <sstream>
16
18
19namespace {
20
26std::string counter_to_json(const operation_counter& counter) {
27 std::ostringstream oss;
28 const auto total = counter.total_count();
29 const auto success = counter.success_count.load(std::memory_order_relaxed);
30 const auto failure = counter.failure_count.load(std::memory_order_relaxed);
31 const auto avg_us = counter.average_duration_us();
32 const auto min_us = counter.min_duration_us.load(std::memory_order_relaxed);
33 const auto max_us = counter.max_duration_us.load(std::memory_order_relaxed);
34
35 oss << "{"
36 << R"("total":)" << total
37 << R"(,"success":)" << success
38 << R"(,"failure":)" << failure
39 << R"(,"avg_duration_us":)" << avg_us;
40
41 // Only include min/max if there were operations
42 if (total > 0) {
43 oss << R"(,"min_duration_us":)" << min_us
44 << R"(,"max_duration_us":)" << max_us;
45 }
46
47 oss << "}";
48 return oss.str();
49}
50
58void counter_to_prometheus(std::ostringstream& oss,
59 std::string_view prefix,
60 std::string_view op_name,
61 const operation_counter& counter) {
62 const auto success = counter.success_count.load(std::memory_order_relaxed);
63 const auto failure = counter.failure_count.load(std::memory_order_relaxed);
64 const auto total = counter.total_count();
65 const auto total_duration = counter.total_duration_us.load(std::memory_order_relaxed);
66
67 // Total operations counter
68 oss << "# HELP " << prefix << "_dimse_" << op_name << "_total "
69 << "Total " << op_name << " operations\n"
70 << "# TYPE " << prefix << "_dimse_" << op_name << "_total counter\n"
71 << prefix << "_dimse_" << op_name << "_total " << total << "\n";
72
73 // Success counter
74 oss << "# HELP " << prefix << "_dimse_" << op_name << "_success_total "
75 << "Successful " << op_name << " operations\n"
76 << "# TYPE " << prefix << "_dimse_" << op_name << "_success_total counter\n"
77 << prefix << "_dimse_" << op_name << "_success_total " << success << "\n";
78
79 // Failure counter
80 oss << "# HELP " << prefix << "_dimse_" << op_name << "_failure_total "
81 << "Failed " << op_name << " operations\n"
82 << "# TYPE " << prefix << "_dimse_" << op_name << "_failure_total counter\n"
83 << prefix << "_dimse_" << op_name << "_failure_total " << failure << "\n";
84
85 // Duration sum (for calculating averages)
86 oss << "# HELP " << prefix << "_dimse_" << op_name << "_duration_microseconds_sum "
87 << "Total duration of " << op_name << " operations in microseconds\n"
88 << "# TYPE " << prefix << "_dimse_" << op_name << "_duration_microseconds_sum counter\n"
89 << prefix << "_dimse_" << op_name << "_duration_microseconds_sum " << total_duration << "\n";
90
91 // Duration min/max (only if there were operations)
92 if (total > 0) {
93 const auto min_us = counter.min_duration_us.load(std::memory_order_relaxed);
94 const auto max_us = counter.max_duration_us.load(std::memory_order_relaxed);
95
96 oss << "# HELP " << prefix << "_dimse_" << op_name << "_duration_microseconds_min "
97 << "Minimum duration of " << op_name << " operations in microseconds\n"
98 << "# TYPE " << prefix << "_dimse_" << op_name << "_duration_microseconds_min gauge\n"
99 << prefix << "_dimse_" << op_name << "_duration_microseconds_min " << min_us << "\n";
100
101 oss << "# HELP " << prefix << "_dimse_" << op_name << "_duration_microseconds_max "
102 << "Maximum duration of " << op_name << " operations in microseconds\n"
103 << "# TYPE " << prefix << "_dimse_" << op_name << "_duration_microseconds_max gauge\n"
104 << prefix << "_dimse_" << op_name << "_duration_microseconds_max " << max_us << "\n";
105 }
106}
107
108} // namespace
109
110std::string pacs_metrics::to_json() const {
111 std::ostringstream oss;
112 oss << "{";
113
114 // DIMSE operations section
115 oss << R"("dimse_operations":{)"
116 << R"("c_echo":)" << counter_to_json(c_echo_)
117 << R"(,"c_store":)" << counter_to_json(c_store_)
118 << R"(,"c_find":)" << counter_to_json(c_find_)
119 << R"(,"c_move":)" << counter_to_json(c_move_)
120 << R"(,"c_get":)" << counter_to_json(c_get_)
121 << R"(,"n_create":)" << counter_to_json(n_create_)
122 << R"(,"n_set":)" << counter_to_json(n_set_)
123 << R"(,"n_get":)" << counter_to_json(n_get_)
124 << R"(,"n_action":)" << counter_to_json(n_action_)
125 << R"(,"n_event":)" << counter_to_json(n_event_)
126 << R"(,"n_delete":)" << counter_to_json(n_delete_)
127 << "}";
128
129 // Data transfer section
130 oss << R"(,"data_transfer":{)"
131 << R"("bytes_sent":)" << transfer_.bytes_sent.load(std::memory_order_relaxed)
132 << R"(,"bytes_received":)" << transfer_.bytes_received.load(std::memory_order_relaxed)
133 << R"(,"images_stored":)" << transfer_.images_stored.load(std::memory_order_relaxed)
134 << R"(,"images_retrieved":)" << transfer_.images_retrieved.load(std::memory_order_relaxed)
135 << "}";
136
137 // Associations section
138 oss << R"(,"associations":{)"
139 << R"("total_established":)" << associations_.total_established.load(std::memory_order_relaxed)
140 << R"(,"total_rejected":)" << associations_.total_rejected.load(std::memory_order_relaxed)
141 << R"(,"total_aborted":)" << associations_.total_aborted.load(std::memory_order_relaxed)
142 << R"(,"current_active":)" << associations_.current_active.load(std::memory_order_relaxed)
143 << R"(,"peak_active":)" << associations_.peak_active.load(std::memory_order_relaxed)
144 << "}";
145
146 oss << "}";
147 return oss.str();
148}
149
150std::string pacs_metrics::to_prometheus(std::string_view prefix) const {
151 std::ostringstream oss;
152
153 // DIMSE operation metrics
154 counter_to_prometheus(oss, prefix, "c_echo", c_echo_);
155 counter_to_prometheus(oss, prefix, "c_store", c_store_);
156 counter_to_prometheus(oss, prefix, "c_find", c_find_);
157 counter_to_prometheus(oss, prefix, "c_move", c_move_);
158 counter_to_prometheus(oss, prefix, "c_get", c_get_);
159 counter_to_prometheus(oss, prefix, "n_create", n_create_);
160 counter_to_prometheus(oss, prefix, "n_set", n_set_);
161 counter_to_prometheus(oss, prefix, "n_get", n_get_);
162 counter_to_prometheus(oss, prefix, "n_action", n_action_);
163 counter_to_prometheus(oss, prefix, "n_event", n_event_);
164 counter_to_prometheus(oss, prefix, "n_delete", n_delete_);
165
166 // Data transfer metrics
167 oss << "# HELP " << prefix << "_bytes_sent_total Total bytes sent over network\n"
168 << "# TYPE " << prefix << "_bytes_sent_total counter\n"
169 << prefix << "_bytes_sent_total " << transfer_.bytes_sent.load(std::memory_order_relaxed) << "\n";
170
171 oss << "# HELP " << prefix << "_bytes_received_total Total bytes received from network\n"
172 << "# TYPE " << prefix << "_bytes_received_total counter\n"
173 << prefix << "_bytes_received_total " << transfer_.bytes_received.load(std::memory_order_relaxed) << "\n";
174
175 oss << "# HELP " << prefix << "_images_stored_total Total DICOM images stored\n"
176 << "# TYPE " << prefix << "_images_stored_total counter\n"
177 << prefix << "_images_stored_total " << transfer_.images_stored.load(std::memory_order_relaxed) << "\n";
178
179 oss << "# HELP " << prefix << "_images_retrieved_total Total DICOM images retrieved\n"
180 << "# TYPE " << prefix << "_images_retrieved_total counter\n"
181 << prefix << "_images_retrieved_total " << transfer_.images_retrieved.load(std::memory_order_relaxed) << "\n";
182
183 // Association metrics
184 oss << "# HELP " << prefix << "_associations_established_total Total associations established\n"
185 << "# TYPE " << prefix << "_associations_established_total counter\n"
186 << prefix << "_associations_established_total " << associations_.total_established.load(std::memory_order_relaxed) << "\n";
187
188 oss << "# HELP " << prefix << "_associations_rejected_total Total associations rejected\n"
189 << "# TYPE " << prefix << "_associations_rejected_total counter\n"
190 << prefix << "_associations_rejected_total " << associations_.total_rejected.load(std::memory_order_relaxed) << "\n";
191
192 oss << "# HELP " << prefix << "_associations_aborted_total Total associations aborted\n"
193 << "# TYPE " << prefix << "_associations_aborted_total counter\n"
194 << prefix << "_associations_aborted_total " << associations_.total_aborted.load(std::memory_order_relaxed) << "\n";
195
196 oss << "# HELP " << prefix << "_associations_active Current active associations\n"
197 << "# TYPE " << prefix << "_associations_active gauge\n"
198 << prefix << "_associations_active " << associations_.current_active.load(std::memory_order_relaxed) << "\n";
199
200 oss << "# HELP " << prefix << "_associations_peak_active Peak concurrent associations\n"
201 << "# TYPE " << prefix << "_associations_peak_active gauge\n"
202 << prefix << "_associations_peak_active " << associations_.peak_active.load(std::memory_order_relaxed) << "\n";
203
204 return oss.str();
205}
206
207} // namespace kcenon::pacs::monitoring
std::string to_prometheus(std::string_view prefix="pacs") const
Export metrics in Prometheus text format.
std::string to_json() const
Export metrics as JSON string.
@ counter
Monotonic increasing value.
@ failure
Printer has a critical error.
Operation metrics collection for PACS DICOM services.
std::atomic< std::uint64_t > total_rejected
std::atomic< std::uint64_t > total_aborted
std::atomic< std::uint32_t > current_active
std::atomic< std::uint64_t > total_established
std::atomic< std::uint64_t > images_retrieved
std::atomic< std::uint64_t > bytes_received