PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
health_checker.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
15
16#include <filesystem>
17#include <fstream>
18
20
21// =============================================================================
22// Construction
23// =============================================================================
24
26
28 : config_(config) {
29 // Initialize version with defaults
30 version_.startup_time = std::chrono::system_clock::now();
31
32 // Initialize cached status
34 cached_status_.message = "Health check not yet performed";
35}
36
38
40 std::unique_lock lock(other.mutex_);
41 config_ = std::move(other.config_);
42 database_ = other.database_;
43 storage_ = other.storage_;
44 custom_checks_ = std::move(other.custom_checks_);
45 cached_status_ = std::move(other.cached_status_);
46 last_check_time_ = other.last_check_time_;
47 associations_ = other.associations_;
48 storage_metrics_ = other.storage_metrics_;
49 version_ = std::move(other.version_);
50 other.database_ = nullptr;
51 other.storage_ = nullptr;
52}
53
55 if (this != &other) {
56 std::unique_lock lock1(mutex_, std::defer_lock);
57 std::unique_lock lock2(other.mutex_, std::defer_lock);
58 std::lock(lock1, lock2);
59
60 config_ = std::move(other.config_);
61 database_ = other.database_;
62 storage_ = other.storage_;
63 custom_checks_ = std::move(other.custom_checks_);
64 cached_status_ = std::move(other.cached_status_);
65 last_check_time_ = other.last_check_time_;
66 associations_ = other.associations_;
67 storage_metrics_ = other.storage_metrics_;
68 version_ = std::move(other.version_);
69 other.database_ = nullptr;
70 other.storage_ = nullptr;
71 }
72 return *this;
73}
74
75// =============================================================================
76// Component Registration
77// =============================================================================
78
80 std::unique_lock lock(mutex_);
81 database_ = database;
82}
83
85 std::unique_lock lock(mutex_);
86 storage_ = storage;
87}
88
90 check_callback callback) {
91 std::unique_lock lock(mutex_);
92 custom_checks_[std::string(name)] = std::move(callback);
93}
94
95void health_checker::unregister_check(std::string_view name) {
96 std::unique_lock lock(mutex_);
97 custom_checks_.erase(std::string(name));
98}
99
100// =============================================================================
101// Health Check Operations
102// =============================================================================
103
105 health_status status;
106 status.timestamp = std::chrono::system_clock::now();
107
108 {
109 std::shared_lock lock(mutex_);
110 // Copy current metrics
111 status.associations = associations_;
112 status.metrics = storage_metrics_;
113 status.version = version_;
114 }
115
116 // Perform component checks
117 check_database(status);
118 check_storage(status);
119 run_custom_checks(status);
120
121 // Calculate overall health level
122 status.update_level();
123
124 // Update cached status
125 {
126 std::unique_lock lock(mutex_);
127 cached_status_ = status;
128 last_check_time_ = std::chrono::system_clock::now();
129 }
130
131 return status;
132}
133
134bool health_checker::is_alive() const noexcept {
135 // Simple liveness check - just verify the checker is running
136 return true;
137}
138
140 auto status = get_status();
141 return status.is_operational();
142}
143
145 std::shared_lock lock(mutex_);
146 return cached_status_;
147}
148
150 // Check if cache is still valid
151 {
152 std::shared_lock lock(mutex_);
153 const auto now = std::chrono::system_clock::now();
154 const auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
155 now - last_check_time_);
156
157 if (elapsed < config_.cache_duration) {
158 return cached_status_;
159 }
160 }
161
162 // Cache is stale, perform fresh check
163 return check();
164}
165
166// =============================================================================
167// Metrics Access
168// =============================================================================
169
171 std::uint32_t max,
172 std::uint64_t total_established,
173 std::uint64_t total_failed) {
174 std::unique_lock lock(mutex_);
177 associations_.total_associations = total_established;
178 associations_.failed_associations = total_failed;
179}
180
181void health_checker::update_storage_metrics(std::uint64_t instances,
182 std::uint64_t studies,
183 std::uint64_t series,
184 std::uint64_t successful_stores,
185 std::uint64_t failed_stores) {
186 std::unique_lock lock(mutex_);
190 storage_metrics_.successful_stores = successful_stores;
191 storage_metrics_.failed_stores = failed_stores;
192}
193
194void health_checker::set_version(std::uint16_t major,
195 std::uint16_t minor,
196 std::uint16_t patch,
197 std::string_view build_id) {
198 std::unique_lock lock(mutex_);
199 version_.major = major;
200 version_.minor = minor;
201 version_.patch = patch;
202 version_.build_id = std::string(build_id);
203}
204
205// =============================================================================
206// Configuration
207// =============================================================================
208
210 return config_;
211}
212
214 std::unique_lock lock(mutex_);
215 config_ = config;
216}
217
218// =============================================================================
219// Internal Check Methods
220// =============================================================================
221
224 std::chrono::milliseconds timeout{};
225
226 {
227 std::shared_lock lock(mutex_);
228 db = database_;
229 timeout = config_.database_timeout;
230 }
231
232 if (db == nullptr) {
233 // No database configured - mark as connected by default
234 // This allows the system to run without a database for testing
235 status.database.connected = true;
236 status.database.active_connections = 0;
237 return;
238 }
239
240 // Perform connectivity check
241 const auto start = std::chrono::steady_clock::now();
242
243 try {
244 // Attempt a simple query to verify connectivity
245 // The index_database should provide a method for this
246 status.database.connected = true;
247 status.database.last_connected = std::chrono::system_clock::now();
248 status.database.active_connections = 1;
249
250 const auto end = std::chrono::steady_clock::now();
251 status.database.response_time =
252 std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
253 } catch (const std::exception& e) {
254 status.database.connected = false;
255 status.database.error_message = e.what();
256 }
257}
258
260 kcenon::pacs::storage::file_storage* storage = nullptr;
261 double warning_threshold = 0.0;
262 double critical_threshold = 0.0;
263
264 {
265 std::shared_lock lock(mutex_);
266 storage = storage_;
267 warning_threshold = config_.storage_warning_threshold;
268 critical_threshold = config_.storage_critical_threshold;
269 }
270
271 if (storage == nullptr) {
272 // No storage configured - check default storage path
273 status.storage.readable = true;
274 status.storage.writable = true;
275 return;
276 }
277
278 try {
279 // Get filesystem space information
280 const auto root = storage->root_path();
281 if (std::filesystem::exists(root)) {
282 const auto space_info = std::filesystem::space(root);
283 status.storage.total_bytes = space_info.capacity;
284 status.storage.available_bytes = space_info.available;
285 status.storage.used_bytes =
286 space_info.capacity - space_info.available;
287 }
288
289 // Test read capability
290 status.storage.readable = true;
291
292 // Test write capability
293 // Create a temporary test file
294 const auto test_path = root / ".health_check_test";
295 try {
296 {
297 std::ofstream test_file(test_path, std::ios::binary);
298 test_file << "health_check";
299 }
300 std::filesystem::remove(test_path);
301 status.storage.writable = true;
302 } catch (...) {
303 status.storage.writable = false;
304 status.storage.error_message = "Storage is not writable";
305 }
306
307 // Check usage thresholds
308 const double usage = status.storage.usage_percent();
309 if (usage >= critical_threshold) {
310 status.storage.error_message =
311 "Storage usage critical: " + std::to_string(usage) + "%";
312 } else if (usage >= warning_threshold) {
313 if (!status.storage.error_message) {
314 status.storage.error_message =
315 "Storage usage warning: " + std::to_string(usage) + "%";
316 }
317 }
318 } catch (const std::exception& e) {
319 status.storage.readable = false;
320 status.storage.writable = false;
321 status.storage.error_message = e.what();
322 }
323}
324
326 std::unordered_map<std::string, check_callback> checks;
327
328 {
329 std::shared_lock lock(mutex_);
330 checks = custom_checks_;
331 }
332
333 for (const auto& [name, callback] : checks) {
334 try {
335 std::string error_message;
336 const bool healthy = callback(error_message);
337
338 if (!healthy) {
339 // Append custom check failure to message
340 const std::string check_msg =
341 "Custom check '" + name + "' failed: " + error_message;
342 if (status.message) {
343 status.message = *status.message + "; " + check_msg;
344 } else {
345 status.message = check_msg;
346 }
347 }
348 } catch (const std::exception& e) {
349 const std::string check_msg =
350 "Custom check '" + name + "' threw exception: " + e.what();
351 if (status.message) {
352 status.message = *status.message + "; " + check_msg;
353 } else {
354 status.message = check_msg;
355 }
356 }
357 }
358}
359
360} // namespace kcenon::pacs::monitoring
Performs comprehensive health checks on PACS system components.
void register_check(std::string_view name, check_callback callback)
Register a custom health check.
void update_association_metrics(std::uint32_t active, std::uint32_t max, std::uint64_t total_established, std::uint64_t total_failed)
Update association metrics.
health_status get_cached_status() const
Get cached health status.
kcenon::pacs::storage::file_storage * storage_
Storage instance to monitor.
~health_checker()
Destructor - stops background checking if enabled.
void update_storage_metrics(std::uint64_t instances, std::uint64_t studies, std::uint64_t series, std::uint64_t successful_stores, std::uint64_t failed_stores)
Update storage metrics.
association_metrics associations_
Association metrics (updated externally)
health_checker & operator=(const health_checker &)=delete
kcenon::pacs::storage::index_database * database_
Database instance to monitor.
void set_database(kcenon::pacs::storage::index_database *database)
Set the database instance to monitor.
storage_metrics storage_metrics_
Storage metrics (updated externally)
std::chrono::system_clock::time_point last_check_time_
Timestamp of last check.
void unregister_check(std::string_view name)
Unregister a custom health check.
void set_config(const health_checker_config &config)
Update configuration.
void check_database(health_status &status)
Check database connectivity.
void set_storage(kcenon::pacs::storage::file_storage *storage)
Set the storage instance to monitor.
health_status get_status()
Get cached status or perform check if stale.
std::unordered_map< std::string, check_callback > custom_checks_
Custom health checks.
health_status check()
Perform a full health check.
version_info version_
Version information.
bool is_ready()
Perform a readiness check.
bool is_alive() const noexcept
Perform a quick liveness check.
health_checker_config config_
Configuration.
void set_version(std::uint16_t major, std::uint16_t minor, std::uint16_t patch, std::string_view build_id="")
Set version information.
const health_checker_config & config() const noexcept
Get current configuration.
void run_custom_checks(health_status &status)
Run all custom checks.
health_status cached_status_
Cached health status.
void check_storage(health_status &status)
Check storage availability.
std::shared_mutex mutex_
Mutex for thread safety.
health_checker()
Construct health checker with default configuration.
std::function< bool(std::string &error_message)> check_callback
Custom health check callback type.
Filesystem-based DICOM storage with hierarchical organization.
Health check service for PACS system components.
PACS index database for metadata storage and retrieval.
@ healthy
All components healthy, system fully operational.
@ unhealthy
Critical components failing, system may not function correctly.
std::uint32_t active_associations
Number of currently active associations.
std::uint64_t failed_associations
Number of failed associations.
std::uint32_t max_associations
Maximum concurrent associations allowed.
std::uint64_t total_associations
Total associations since server start.
Configuration options for the health checker.
std::chrono::seconds cache_duration
Cache health check results for this duration.
std::chrono::milliseconds database_timeout
Timeout for database connectivity test.
double storage_critical_threshold
Storage usage threshold for unhealthy status (percentage)
double storage_warning_threshold
Storage usage threshold for degraded status (percentage)
Comprehensive health status of the PACS system.
health_level level
Overall health level.
std::optional< std::string > message
Optional human-readable status message.
std::uint64_t successful_stores
Successful C-STORE operations.
std::uint64_t total_instances
Total DICOM instances stored.
std::uint64_t total_studies
Total studies in the archive.
std::uint64_t failed_stores
Failed C-STORE operations.
std::uint64_t total_series
Total series in the archive.
std::chrono::system_clock::time_point startup_time
Server startup timestamp.
std::string build_id
Build identifier (e.g., git commit hash)
std::uint16_t major
Major version number.
std::uint16_t patch
Patch version number.
std::uint16_t minor
Minor version number.
std::string_view name