24#ifdef PACS_WITH_DATABASE_SYSTEM
35void add_cors_headers(crow::response& res,
const rest_server_context& ctx) {
36 if (ctx.config && !ctx.config->cors_allowed_origins.empty()) {
37 res.add_header(
"Access-Control-Allow-Origin",
38 ctx.config->cors_allowed_origins);
45std::string health_status_to_json_string(
46 services::monitoring::database_health::status status) {
48 case services::monitoring::database_health::status::healthy:
50 case services::monitoring::database_health::status::degraded:
52 case services::monitoring::database_health::status::unhealthy:
62int get_query_param_int(
const crow::request& req,
const std::string& key,
64 auto value = req.url_params.get(key);
65 if (value ==
nullptr) {
69 return std::stoi(value);
79bool check_metrics_auth(
const std::shared_ptr<rest_server_context>& ctx,
80 const crow::request& req,
81 crow::response& res) {
82 if (ctx->oauth2 && ctx->oauth2->enabled()) {
83 auto auth = ctx->oauth2->authenticate(req, res);
84 if (!auth)
return false;
85 if (!ctx->oauth2->require_any_scope(
86 auth->claims, res, {
"metrics.read",
"admin"})) {
95void register_metrics_endpoints_impl(
97 std::shared_ptr<rest_server_context> ctx) {
100 CROW_ROUTE(app,
"/api/health/database")
101 .methods(crow::HTTPMethod::GET)([ctx](
const crow::request& req) {
103 res.add_header(
"Content-Type",
"application/json");
104 add_cors_headers(res, *ctx);
106 if (!check_metrics_auth(ctx, req, res))
return res;
109 if (!ctx->database_metrics) {
111 "METRICS_UNAVAILABLE",
112 "Database metrics service not configured");
118 auto health = ctx->database_metrics->check_health();
121 std::ostringstream oss;
122 oss << R
"({"status":")" << health_status_to_json_string(health.current_status)
123 << R"(","message":")" << json_escape(health.message)
124 << R"(","response_time_ms":)" << health.response_time.count()
125 << R"(,"active_connections":)" << health.active_connections
126 << R"(,"error_rate":)" << health.error_rate;
128 if (!health.warnings.empty()) {
129 oss << R
"(,"warnings":[)";
130 for (
size_t i = 0; i < health.warnings.size(); ++i) {
131 if (i > 0) oss <<
",";
132 oss << R
"(")" << json_escape(health.warnings[i]) << R"(")";
138 res.body = oss.str();
141 if (health.current_status ==
142 services::monitoring::database_health::status::healthy) {
144 }
else if (health.current_status ==
145 services::monitoring::database_health::status::degraded) {
155 CROW_ROUTE(app,
"/api/metrics/database")
156 .methods(crow::HTTPMethod::GET)([ctx](
const crow::request& req) {
158 res.add_header(
"Content-Type",
"application/json");
159 add_cors_headers(res, *ctx);
161 if (!check_metrics_auth(ctx, req, res))
return res;
163 if (!ctx->database_metrics) {
165 "METRICS_UNAVAILABLE",
166 "Database metrics service not configured");
171 auto metrics = ctx->database_metrics->get_current_metrics();
174 std::ostringstream oss;
175 oss << R
"({"total_queries":)" << metrics.total_queries
176 << R"(,"successful_queries":)" << metrics.successful_queries
177 << R"(,"failed_queries":)" << metrics.failed_queries
178 << R"(,"queries_per_second":)" << metrics.queries_per_second
180 << R"("avg_us":)" << metrics.avg_latency_us
181 << R"(,"min_us":)" << metrics.min_latency_us
182 << R"(,"max_us":)" << metrics.max_latency_us
183 << R"(,"p95_us":)" << metrics.p95_latency_us
184 << R"(,"p99_us":)" << metrics.p99_latency_us << R"(})"
185 << R"(,"connections":{)"
186 << R"("active":)" << metrics.active_connections
187 << R"(,"pool_size":)" << metrics.pool_size
188 << R"(,"utilization":)" << metrics.connection_utilization << R"(})"
189 << R"(,"error_rate":)" << metrics.error_rate
190 << R"(,"slow_query_count":)" << metrics.slow_query_count << "}";
192 res.body = oss.str();
198 CROW_ROUTE(app,
"/api/metrics/database/slow-queries")
199 .methods(crow::HTTPMethod::GET)([ctx](
const crow::request& req) {
201 res.add_header(
"Content-Type",
"application/json");
202 add_cors_headers(res, *ctx);
204 if (!check_metrics_auth(ctx, req, res))
return res;
206 if (!ctx->database_metrics) {
208 "METRICS_UNAVAILABLE",
209 "Database metrics service not configured");
215 int limit = get_query_param_int(req,
"limit", 10);
216 int since_minutes = get_query_param_int(req,
"since_minutes", 5);
218 auto slow_queries = ctx->database_metrics->get_slow_queries(
219 std::chrono::minutes(since_minutes));
222 std::ostringstream oss;
226 for (
const auto& sq : slow_queries) {
227 if (count >=
static_cast<size_t>(limit))
break;
229 if (count > 0) oss <<
",";
231 oss << R
"({"query_preview":")" << json_escape(sq.query_preview)
232 << R"(","duration_us":)" << sq.duration_us
233 << R"(,"timestamp":")" << sq.timestamp
234 << R"(","rows_affected":)" << sq.rows_affected << "}";
240 res.body = oss.str();
246 CROW_ROUTE(app,
"/metrics")
247 .methods(crow::HTTPMethod::GET)([ctx](
const crow::request& req) {
249 add_cors_headers(res, *ctx);
251 if (!check_metrics_auth(ctx, req, res))
return res;
253 if (!ctx->database_metrics) {
255 res.add_header(
"Content-Type",
"text/plain");
256 res.body =
"# Database metrics unavailable\n";
260 auto prometheus_output =
261 ctx->database_metrics->export_prometheus_metrics();
262 res.add_header(
"Content-Type",
"text/plain; version=0.0.4");
263 res.body = prometheus_output;
Database monitoring and metrics service.
Database metrics REST API endpoints.
std::string make_error_json(std::string_view code, std::string_view message)
Create JSON error response body with details.
std::string json_escape(std::string_view s)
Escape a string for JSON.
OAuth 2.0 middleware for DICOMweb endpoint authorization.
Configuration for REST API server.
Common types and utilities for REST API.
System API endpoints for REST server.