| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "metrics.h" | ||
| 2 | #include "log.h" | ||
| 3 | #include <stdio.h> | ||
| 4 | #include <stdlib.h> | ||
| 5 | #include <string.h> | ||
| 6 | #include <unistd.h> | ||
| 7 | #include <sys/socket.h> | ||
| 8 | #include <netinet/in.h> | ||
| 9 | #include <arpa/inet.h> | ||
| 10 | |||
| 11 | #define METRICS_BUFFER_SIZE 65536 | ||
| 12 | #define METRICS_REQUEST_BUFFER_SIZE 4096 | ||
| 13 | #define METRICS_RESPONSE_BUFFER_SIZE 8192 | ||
| 14 | #define METRICS_SERVER_BACKLOG 10 | ||
| 15 | |||
| 16 | static MetricsRegistry g_metrics; | ||
| 17 | static int g_metrics_server_fd = -1; | ||
| 18 | static pthread_t g_metrics_server_thread; | ||
| 19 | static _Atomic int g_server_running = 0; | ||
| 20 | |||
| 21 | static const double HISTOGRAM_BUCKET_BOUNDS[HISTOGRAM_BUCKETS] = { | ||
| 22 | 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 25.0, 50.0 | ||
| 23 | }; | ||
| 24 | |||
| 25 | 36 | static void initialize_histogram(MetricHistogram *histogram, const char *name, | |
| 26 | const char *help) | ||
| 27 | { | ||
| 28 | 36 | histogram->metadata.name = name; | |
| 29 | 36 | histogram->metadata.help = help; | |
| 30 | 36 | histogram->metadata.type = "histogram"; | |
| 31 |
2/2✓ Branch 0 taken 504 times.
✓ Branch 1 taken 36 times.
|
540 | for (int i = 0; i < HISTOGRAM_BUCKETS; i++) { |
| 32 | 504 | histogram->data.buckets[i] = HISTOGRAM_BUCKET_BOUNDS[i]; | |
| 33 | } | ||
| 34 | 36 | pthread_mutex_init(&histogram->data.lock, NULL); | |
| 35 | 36 | } | |
| 36 | |||
| 37 | 18 | void metrics_init(void) | |
| 38 | { | ||
| 39 | 18 | memset(&g_metrics, 0, sizeof(g_metrics)); | |
| 40 | |||
| 41 | 18 | g_metrics.requests_total.metadata.name = "emme_requests_total"; | |
| 42 | 18 | g_metrics.requests_total.metadata.help = "Total number of HTTP requests"; | |
| 43 | 18 | g_metrics.requests_total.metadata.type = "counter"; | |
| 44 | |||
| 45 | 18 | g_metrics.tls_handshakes_total.metadata.name = "emme_tls_handshakes_total"; | |
| 46 | 18 | g_metrics.tls_handshakes_total.metadata.help = "Total number of TLS handshakes"; | |
| 47 | 18 | g_metrics.tls_handshakes_total.metadata.type = "counter"; | |
| 48 | |||
| 49 | 18 | g_metrics.request_timeouts_total.metadata.name = "emme_request_timeouts_total"; | |
| 50 | 18 | g_metrics.request_timeouts_total.metadata.help = "Total number of request timeouts"; | |
| 51 | 18 | g_metrics.request_timeouts_total.metadata.type = "counter"; | |
| 52 | |||
| 53 | 18 | g_metrics.security_headers_sent_total.metadata.name = "emme_security_headers_sent_total"; | |
| 54 | 18 | g_metrics.security_headers_sent_total.metadata.help = "Total number of security headers sent"; | |
| 55 | 18 | g_metrics.security_headers_sent_total.metadata.type = "counter"; | |
| 56 | |||
| 57 | 18 | g_metrics.cors_headers_sent_total.metadata.name = "emme_cors_headers_sent_total"; | |
| 58 | 18 | g_metrics.cors_headers_sent_total.metadata.help = "Total number of CORS headers sent"; | |
| 59 | 18 | g_metrics.cors_headers_sent_total.metadata.type = "counter"; | |
| 60 | |||
| 61 | 18 | g_metrics.per_ip_limit_rejected_total.metadata.name = "emme_per_ip_limit_rejected_total"; | |
| 62 | 18 | g_metrics.per_ip_limit_rejected_total.metadata.help = "Total number of connections rejected due to per-IP limit"; | |
| 63 | 18 | g_metrics.per_ip_limit_rejected_total.metadata.type = "counter"; | |
| 64 | |||
| 65 | 18 | g_metrics.ip_limiter_entries_total.metadata.name = "emme_ip_limiter_entries_total"; | |
| 66 | 18 | g_metrics.ip_limiter_entries_total.metadata.help = "Number of unique IPs currently tracked by the IP limiter"; | |
| 67 | 18 | g_metrics.ip_limiter_entries_total.metadata.type = "gauge"; | |
| 68 | |||
| 69 | 18 | g_metrics.active_connections.metadata.name = "emme_active_connections"; | |
| 70 | 18 | g_metrics.active_connections.metadata.help = "Number of active connections"; | |
| 71 | 18 | g_metrics.active_connections.metadata.type = "gauge"; | |
| 72 | |||
| 73 | 18 | g_metrics.thread_pool_active_threads.metadata.name = "emme_thread_pool_active_threads"; | |
| 74 | 18 | g_metrics.thread_pool_active_threads.metadata.help = "Number of active threads in the pool"; | |
| 75 | 18 | g_metrics.thread_pool_active_threads.metadata.type = "gauge"; | |
| 76 | |||
| 77 | 18 | g_metrics.thread_pool_queue_depth.metadata.name = "emme_thread_pool_queue_depth"; | |
| 78 | 18 | g_metrics.thread_pool_queue_depth.metadata.help = "Depth of the thread pool queue"; | |
| 79 | 18 | g_metrics.thread_pool_queue_depth.metadata.type = "gauge"; | |
| 80 | |||
| 81 | 18 | g_metrics.shutdown_drain_active.metadata.name = "emme_shutdown_drain_active"; | |
| 82 | 18 | g_metrics.shutdown_drain_active.metadata.help = "1 if shutdown drain is active, 0 otherwise"; | |
| 83 | 18 | g_metrics.shutdown_drain_active.metadata.type = "gauge"; | |
| 84 | |||
| 85 | 18 | initialize_histogram(&g_metrics.request_duration_seconds, | |
| 86 | "emme_request_duration_seconds", | ||
| 87 | "HTTP request duration in seconds"); | ||
| 88 | |||
| 89 | 18 | initialize_histogram(&g_metrics.tls_handshake_duration_seconds, | |
| 90 | "emme_tls_handshake_duration_seconds", | ||
| 91 | "TLS handshake duration in seconds"); | ||
| 92 | |||
| 93 | 18 | g_metrics.io_uring_sqe_depth.metadata.name = "emme_io_uring_sqe_depth"; | |
| 94 | 18 | g_metrics.io_uring_sqe_depth.metadata.help = "io_uring submission queue depth"; | |
| 95 | 18 | g_metrics.io_uring_sqe_depth.metadata.type = "gauge"; | |
| 96 | |||
| 97 | 18 | g_metrics.io_uring_cqe_depth.metadata.name = "emme_io_uring_cqe_depth"; | |
| 98 | 18 | g_metrics.io_uring_cqe_depth.metadata.help = "io_uring completion queue depth"; | |
| 99 | 18 | g_metrics.io_uring_cqe_depth.metadata.type = "gauge"; | |
| 100 | |||
| 101 | 18 | g_metrics.backend_pool_active.metadata.name = "emme_backend_pool_active_connections"; | |
| 102 | 18 | g_metrics.backend_pool_active.metadata.help = "Number of active backend connections"; | |
| 103 | 18 | g_metrics.backend_pool_active.metadata.type = "gauge"; | |
| 104 | |||
| 105 | 18 | g_metrics.backend_pool_idle.metadata.name = "emme_backend_pool_idle_connections"; | |
| 106 | 18 | g_metrics.backend_pool_idle.metadata.help = "Number of idle backend connections"; | |
| 107 | 18 | g_metrics.backend_pool_idle.metadata.type = "gauge"; | |
| 108 | |||
| 109 | 18 | g_metrics.backend_pool_healthy.metadata.name = "emme_backend_pool_healthy_connections"; | |
| 110 | 18 | g_metrics.backend_pool_healthy.metadata.help = "Number of healthy backend connections"; | |
| 111 | 18 | g_metrics.backend_pool_healthy.metadata.type = "gauge"; | |
| 112 | |||
| 113 | 18 | g_metrics.health_checker_total_checks.metadata.name = "emme_health_checker_total_checks"; | |
| 114 | 18 | g_metrics.health_checker_total_checks.metadata.help = "Total health checks performed"; | |
| 115 | 18 | g_metrics.health_checker_total_checks.metadata.type = "gauge"; | |
| 116 | |||
| 117 | 18 | g_metrics.health_checker_failed_checks.metadata.name = "emme_health_checker_failed_checks"; | |
| 118 | 18 | g_metrics.health_checker_failed_checks.metadata.help = "Total failed health checks"; | |
| 119 | 18 | g_metrics.health_checker_failed_checks.metadata.type = "gauge"; | |
| 120 | |||
| 121 | 18 | g_metrics.health_checker_health_status.metadata.name = "emme_health_checker_health_status"; | |
| 122 | 18 | g_metrics.health_checker_health_status.metadata.help = "Backend health status (0=unknown, 1=healthy, 2=unhealthy)"; | |
| 123 | 18 | g_metrics.health_checker_health_status.metadata.type = "gauge"; | |
| 124 | |||
| 125 | 18 | g_metrics.health_checker_last_check_time.metadata.name = "emme_health_checker_last_check_time_seconds"; | |
| 126 | 18 | g_metrics.health_checker_last_check_time.metadata.help = "Unix timestamp of last health check"; | |
| 127 | 18 | g_metrics.health_checker_last_check_time.metadata.type = "gauge"; | |
| 128 | |||
| 129 | 18 | g_metrics.circuit_breaker_state.metadata.name = "emme_circuit_breaker_state"; | |
| 130 | 18 | g_metrics.circuit_breaker_state.metadata.help = "Circuit breaker state (0=closed, 1=open, 2=half-open)"; | |
| 131 | 18 | g_metrics.circuit_breaker_state.metadata.type = "gauge"; | |
| 132 | |||
| 133 | 18 | g_metrics.circuit_breaker_failure_count.metadata.name = "emme_circuit_breaker_failure_count"; | |
| 134 | 18 | g_metrics.circuit_breaker_failure_count.metadata.help = "Current failure count"; | |
| 135 | 18 | g_metrics.circuit_breaker_failure_count.metadata.type = "gauge"; | |
| 136 | |||
| 137 | 18 | g_metrics.circuit_breaker_total_opens.metadata.name = "emme_circuit_breaker_total_opens"; | |
| 138 | 18 | g_metrics.circuit_breaker_total_opens.metadata.help = "Total times circuit breaker opened"; | |
| 139 | 18 | g_metrics.circuit_breaker_total_opens.metadata.type = "gauge"; | |
| 140 | |||
| 141 | 18 | g_metrics.circuit_breaker_total_closes.metadata.name = "emme_circuit_breaker_total_closes"; | |
| 142 | 18 | g_metrics.circuit_breaker_total_closes.metadata.help = "Total times circuit breaker closed"; | |
| 143 | 18 | g_metrics.circuit_breaker_total_closes.metadata.type = "gauge"; | |
| 144 | |||
| 145 | 18 | log_message(LOG_LEVEL_INFO, "Metrics registry initialized"); | |
| 146 | 18 | } | |
| 147 | |||
| 148 | 18 | void metrics_shutdown(void) | |
| 149 | { | ||
| 150 | 18 | metrics_stop_server(); | |
| 151 | 18 | pthread_mutex_destroy(&g_metrics.request_duration_seconds.data.lock); | |
| 152 | 18 | pthread_mutex_destroy(&g_metrics.tls_handshake_duration_seconds.data.lock); | |
| 153 | 18 | log_message(LOG_LEVEL_INFO, "Metrics registry shutdown complete"); | |
| 154 | 18 | } | |
| 155 | |||
| 156 | 122 | static void histogram_record(HistogramData *histogram, double value) | |
| 157 | { | ||
| 158 | 122 | pthread_mutex_lock(&histogram->lock); | |
| 159 | |||
| 160 |
1/2✓ Branch 0 taken 587 times.
✗ Branch 1 not taken.
|
587 | for (int i = 0; i < HISTOGRAM_BUCKETS; i++) { |
| 161 |
2/2✓ Branch 0 taken 122 times.
✓ Branch 1 taken 465 times.
|
587 | if (value <= histogram->buckets[i]) { |
| 162 | 122 | atomic_fetch_add(&histogram->counts[i], 1); | |
| 163 | 122 | break; | |
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | 122 | atomic_fetch_add(&histogram->sum_count, 1); | |
| 168 | 122 | histogram->sum_total += value; | |
| 169 | |||
| 170 | 122 | pthread_mutex_unlock(&histogram->lock); | |
| 171 | 122 | } | |
| 172 | |||
| 173 | 109 | void metrics_increment_request(const char *method, const char *path, int status) | |
| 174 | { | ||
| 175 | (void)method; | ||
| 176 | (void)path; | ||
| 177 | (void)status; | ||
| 178 | 109 | atomic_fetch_add(&g_metrics.requests_total.value, 1); | |
| 179 | 109 | } | |
| 180 | |||
| 181 | 110 | void metrics_record_request_duration(double duration_seconds) | |
| 182 | { | ||
| 183 | 110 | histogram_record(&g_metrics.request_duration_seconds.data, duration_seconds); | |
| 184 | 110 | } | |
| 185 | |||
| 186 | ✗ | void metrics_increment_request_timeouts(void) | |
| 187 | { | ||
| 188 | ✗ | atomic_fetch_add(&g_metrics.request_timeouts_total.value, 1); | |
| 189 | ✗ | } | |
| 190 | |||
| 191 | 3 | void metrics_increment_security_headers_sent(void) | |
| 192 | { | ||
| 193 | 3 | atomic_fetch_add(&g_metrics.security_headers_sent_total.value, 1); | |
| 194 | 3 | } | |
| 195 | |||
| 196 | 1 | void metrics_increment_cors_headers_sent(void) | |
| 197 | { | ||
| 198 | 1 | atomic_fetch_add(&g_metrics.cors_headers_sent_total.value, 1); | |
| 199 | 1 | } | |
| 200 | |||
| 201 | 122 | void metrics_set_active_connections(long count) | |
| 202 | { | ||
| 203 | 122 | atomic_store(&g_metrics.active_connections.value, count); | |
| 204 | 122 | } | |
| 205 | |||
| 206 | ✗ | void metrics_increment_per_ip_limit_rejected(void) | |
| 207 | { | ||
| 208 | ✗ | atomic_fetch_add(&g_metrics.per_ip_limit_rejected_total.value, 1); | |
| 209 | ✗ | } | |
| 210 | |||
| 211 | ✗ | void metrics_set_ip_limiter_entries(long count) | |
| 212 | { | ||
| 213 | ✗ | atomic_store(&g_metrics.ip_limiter_entries_total.value, count); | |
| 214 | ✗ | } | |
| 215 | |||
| 216 | 1 | void metrics_set_thread_pool_stats(int active_threads, int queue_depth) | |
| 217 | { | ||
| 218 | 1 | atomic_store(&g_metrics.thread_pool_active_threads.value, active_threads); | |
| 219 | 1 | atomic_store(&g_metrics.thread_pool_queue_depth.value, queue_depth); | |
| 220 | 1 | } | |
| 221 | |||
| 222 | 13 | void metrics_increment_tls_handshake(int success) | |
| 223 | { | ||
| 224 | (void)success; | ||
| 225 | 13 | atomic_fetch_add(&g_metrics.tls_handshakes_total.value, 1); | |
| 226 | 13 | } | |
| 227 | |||
| 228 | 12 | void metrics_record_tls_handshake_duration(double duration_seconds) | |
| 229 | { | ||
| 230 | 12 | histogram_record(&g_metrics.tls_handshake_duration_seconds.data, duration_seconds); | |
| 231 | 12 | } | |
| 232 | |||
| 233 | ✗ | void metrics_set_io_uring_depth(int sqe_depth, int cqe_depth) | |
| 234 | { | ||
| 235 | ✗ | atomic_store(&g_metrics.io_uring_sqe_depth.value, sqe_depth); | |
| 236 | ✗ | atomic_store(&g_metrics.io_uring_cqe_depth.value, cqe_depth); | |
| 237 | ✗ | } | |
| 238 | |||
| 239 | 2 | void metrics_set_shutdown_drain(int active) | |
| 240 | { | ||
| 241 | 2 | atomic_store(&g_metrics.shutdown_drain_active.value, active ? 1 : 0); | |
| 242 | 2 | } | |
| 243 | |||
| 244 | 1 | void metrics_set_backend_pool_stats(const char *backend, int active, int idle, int healthy) | |
| 245 | { | ||
| 246 | (void)backend; | ||
| 247 | 1 | atomic_store(&g_metrics.backend_pool_active.value, active); | |
| 248 | 1 | atomic_store(&g_metrics.backend_pool_idle.value, idle); | |
| 249 | 1 | atomic_store(&g_metrics.backend_pool_healthy.value, healthy); | |
| 250 | 1 | } | |
| 251 | |||
| 252 | ✗ | void metrics_set_health_checker_stats(const char *backend, int total_checks, int failed_checks, | |
| 253 | int health_status, long last_check_time) | ||
| 254 | { | ||
| 255 | (void)backend; | ||
| 256 | ✗ | atomic_store(&g_metrics.health_checker_total_checks.value, total_checks); | |
| 257 | ✗ | atomic_store(&g_metrics.health_checker_failed_checks.value, failed_checks); | |
| 258 | ✗ | atomic_store(&g_metrics.health_checker_health_status.value, health_status); | |
| 259 | ✗ | atomic_store(&g_metrics.health_checker_last_check_time.value, last_check_time); | |
| 260 | ✗ | } | |
| 261 | |||
| 262 | ✗ | void metrics_set_circuit_breaker_stats(const char *backend, int state, int failure_count, | |
| 263 | long total_opens, long total_closes) | ||
| 264 | { | ||
| 265 | (void)backend; | ||
| 266 | ✗ | atomic_store(&g_metrics.circuit_breaker_state.value, state); | |
| 267 | ✗ | atomic_store(&g_metrics.circuit_breaker_failure_count.value, failure_count); | |
| 268 | ✗ | atomic_store(&g_metrics.circuit_breaker_total_opens.value, total_opens); | |
| 269 | ✗ | atomic_store(&g_metrics.circuit_breaker_total_closes.value, total_closes); | |
| 270 | ✗ | } | |
| 271 | |||
| 272 | 54 | static int format_counter(char *buffer, size_t remaining, const MetricCounter *counter) | |
| 273 | { | ||
| 274 | 108 | return snprintf(buffer, remaining, | |
| 275 | "# HELP %s %s\n# TYPE %s counter\n%ld\n", | ||
| 276 | 54 | counter->metadata.name, | |
| 277 | 54 | counter->metadata.help, | |
| 278 | 54 | counter->metadata.type, | |
| 279 | 54 | atomic_load(&counter->value)); | |
| 280 | } | ||
| 281 | |||
| 282 | 162 | static int format_gauge(char *buffer, size_t remaining, const MetricGauge *gauge) | |
| 283 | { | ||
| 284 | 324 | return snprintf(buffer, remaining, | |
| 285 | "# HELP %s %s\n# TYPE %s gauge\n%ld\n", | ||
| 286 | 162 | gauge->metadata.name, | |
| 287 | 162 | gauge->metadata.help, | |
| 288 | 162 | gauge->metadata.type, | |
| 289 | 162 | atomic_load(&gauge->value)); | |
| 290 | } | ||
| 291 | |||
| 292 | 18 | static int format_histogram(char *buffer, size_t remaining, const MetricHistogram *histogram) | |
| 293 | { | ||
| 294 | 18 | int written = 0; | |
| 295 | 18 | size_t offset = 0; | |
| 296 | |||
| 297 | 18 | written = snprintf(buffer, remaining, | |
| 298 | "# HELP %s %s\n# TYPE histogram\n", | ||
| 299 | 18 | histogram->metadata.name, | |
| 300 | 18 | histogram->metadata.help); | |
| 301 |
2/4✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 18 times.
|
18 | if (written < 0 || (size_t)written >= remaining) { |
| 302 | ✗ | return -1; | |
| 303 | } | ||
| 304 | 18 | offset = (size_t)written; | |
| 305 | |||
| 306 | 18 | pthread_mutex_lock((pthread_mutex_t *)&histogram->data.lock); | |
| 307 | 18 | long cumulative = 0; | |
| 308 | |||
| 309 |
2/2✓ Branch 0 taken 252 times.
✓ Branch 1 taken 18 times.
|
270 | for (int i = 0; i < HISTOGRAM_BUCKETS; i++) { |
| 310 | 252 | cumulative += atomic_load(&histogram->data.counts[i]); | |
| 311 | 252 | int n = snprintf(buffer + offset, remaining - offset, | |
| 312 | "%s_bucket{le=\"%.3f\"} %ld\n", | ||
| 313 | 252 | histogram->metadata.name, | |
| 314 | 252 | histogram->data.buckets[i], | |
| 315 | cumulative); | ||
| 316 |
2/4✓ Branch 0 taken 252 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 252 times.
|
252 | if (n < 0 || (size_t)n >= remaining - offset) { |
| 317 | ✗ | pthread_mutex_unlock((pthread_mutex_t *)&histogram->data.lock); | |
| 318 | ✗ | return -1; | |
| 319 | } | ||
| 320 | 252 | offset += (size_t)n; | |
| 321 | } | ||
| 322 | |||
| 323 | 18 | int n = snprintf(buffer + offset, remaining - offset, | |
| 324 | "%s_bucket{le=\"+Inf\"} %ld\n", | ||
| 325 | 18 | histogram->metadata.name, | |
| 326 | 18 | atomic_load(&histogram->data.sum_count)); | |
| 327 |
2/4✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 18 times.
|
18 | if (n < 0 || (size_t)n >= remaining - offset) { |
| 328 | ✗ | pthread_mutex_unlock((pthread_mutex_t *)&histogram->data.lock); | |
| 329 | ✗ | return -1; | |
| 330 | } | ||
| 331 | 18 | offset += (size_t)n; | |
| 332 | |||
| 333 | 18 | n = snprintf(buffer + offset, remaining - offset, | |
| 334 | "%s_sum %f\n", | ||
| 335 | 18 | histogram->metadata.name, | |
| 336 | 18 | histogram->data.sum_total); | |
| 337 |
2/4✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 18 times.
|
18 | if (n < 0 || (size_t)n >= remaining - offset) { |
| 338 | ✗ | pthread_mutex_unlock((pthread_mutex_t *)&histogram->data.lock); | |
| 339 | ✗ | return -1; | |
| 340 | } | ||
| 341 | 18 | offset += (size_t)n; | |
| 342 | |||
| 343 | 18 | n = snprintf(buffer + offset, remaining - offset, | |
| 344 | "%s_count %ld\n", | ||
| 345 | 18 | histogram->metadata.name, | |
| 346 | 18 | atomic_load(&histogram->data.sum_count)); | |
| 347 |
2/4✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 18 times.
|
18 | if (n < 0 || (size_t)n >= remaining - offset) { |
| 348 | ✗ | pthread_mutex_unlock((pthread_mutex_t *)&histogram->data.lock); | |
| 349 | ✗ | return -1; | |
| 350 | } | ||
| 351 | 18 | offset += (size_t)n; | |
| 352 | |||
| 353 | 18 | pthread_mutex_unlock((pthread_mutex_t *)&histogram->data.lock); | |
| 354 | 18 | return (int)offset; | |
| 355 | } | ||
| 356 | |||
| 357 | 9 | char *metrics_format_prometheus(void) | |
| 358 | { | ||
| 359 | 9 | char *buffer = malloc(METRICS_BUFFER_SIZE); | |
| 360 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (!buffer) { |
| 361 | ✗ | return NULL; | |
| 362 | } | ||
| 363 | |||
| 364 | 9 | size_t offset = 0; | |
| 365 | 9 | int written = 0; | |
| 366 | |||
| 367 | 9 | written = format_counter(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 368 | &g_metrics.requests_total); | ||
| 369 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 370 | 9 | offset += (size_t)written; | |
| 371 | |||
| 372 | 9 | written = format_counter(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 373 | &g_metrics.tls_handshakes_total); | ||
| 374 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 375 | 9 | offset += (size_t)written; | |
| 376 | |||
| 377 | 9 | written = format_counter(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 378 | &g_metrics.request_timeouts_total); | ||
| 379 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 380 | 9 | offset += (size_t)written; | |
| 381 | |||
| 382 | 9 | written = format_counter(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 383 | &g_metrics.security_headers_sent_total); | ||
| 384 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 385 | 9 | offset += (size_t)written; | |
| 386 | |||
| 387 | 9 | written = format_counter(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 388 | &g_metrics.cors_headers_sent_total); | ||
| 389 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 390 | 9 | offset += (size_t)written; | |
| 391 | |||
| 392 | 9 | written = format_counter(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 393 | &g_metrics.per_ip_limit_rejected_total); | ||
| 394 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 395 | 9 | offset += (size_t)written; | |
| 396 | |||
| 397 | 9 | written = format_gauge(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 398 | &g_metrics.active_connections); | ||
| 399 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 400 | 9 | offset += (size_t)written; | |
| 401 | |||
| 402 | 9 | written = format_gauge(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 403 | &g_metrics.thread_pool_active_threads); | ||
| 404 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 405 | 9 | offset += (size_t)written; | |
| 406 | |||
| 407 | 9 | written = format_gauge(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 408 | &g_metrics.thread_pool_queue_depth); | ||
| 409 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 410 | 9 | offset += (size_t)written; | |
| 411 | |||
| 412 | 9 | written = format_gauge(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 413 | &g_metrics.shutdown_drain_active); | ||
| 414 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 415 | 9 | offset += (size_t)written; | |
| 416 | |||
| 417 | 9 | written = format_histogram(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 418 | &g_metrics.request_duration_seconds); | ||
| 419 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 420 | 9 | offset += (size_t)written; | |
| 421 | |||
| 422 | 9 | written = format_histogram(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 423 | &g_metrics.tls_handshake_duration_seconds); | ||
| 424 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 425 | 9 | offset += (size_t)written; | |
| 426 | |||
| 427 | 9 | written = format_gauge(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 428 | &g_metrics.io_uring_sqe_depth); | ||
| 429 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 430 | 9 | offset += (size_t)written; | |
| 431 | |||
| 432 | 9 | written = format_gauge(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 433 | &g_metrics.io_uring_cqe_depth); | ||
| 434 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 435 | 9 | offset += (size_t)written; | |
| 436 | |||
| 437 | 9 | written = format_gauge(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 438 | &g_metrics.backend_pool_active); | ||
| 439 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 440 | 9 | offset += (size_t)written; | |
| 441 | |||
| 442 | 9 | written = format_gauge(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 443 | &g_metrics.backend_pool_idle); | ||
| 444 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 445 | 9 | offset += (size_t)written; | |
| 446 | |||
| 447 | 9 | written = format_gauge(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 448 | &g_metrics.backend_pool_healthy); | ||
| 449 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 450 | 9 | offset += (size_t)written; | |
| 451 | |||
| 452 | 9 | written = format_gauge(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 453 | &g_metrics.health_checker_total_checks); | ||
| 454 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 455 | 9 | offset += (size_t)written; | |
| 456 | |||
| 457 | 9 | written = format_gauge(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 458 | &g_metrics.ip_limiter_entries_total); | ||
| 459 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 460 | 9 | offset += (size_t)written; | |
| 461 | |||
| 462 | 9 | written = format_gauge(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 463 | &g_metrics.health_checker_failed_checks); | ||
| 464 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 465 | 9 | offset += (size_t)written; | |
| 466 | |||
| 467 | 9 | written = format_gauge(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 468 | &g_metrics.health_checker_health_status); | ||
| 469 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 470 | 9 | offset += (size_t)written; | |
| 471 | |||
| 472 | 9 | written = format_gauge(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 473 | &g_metrics.health_checker_last_check_time); | ||
| 474 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 475 | 9 | offset += (size_t)written; | |
| 476 | |||
| 477 | 9 | written = format_gauge(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 478 | &g_metrics.circuit_breaker_state); | ||
| 479 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 480 | 9 | offset += (size_t)written; | |
| 481 | |||
| 482 | 9 | written = format_gauge(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 483 | &g_metrics.circuit_breaker_failure_count); | ||
| 484 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 485 | 9 | offset += (size_t)written; | |
| 486 | |||
| 487 | 9 | written = format_gauge(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 488 | &g_metrics.circuit_breaker_total_opens); | ||
| 489 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 490 | 9 | offset += (size_t)written; | |
| 491 | |||
| 492 | 9 | written = format_gauge(buffer + offset, METRICS_BUFFER_SIZE - offset, | |
| 493 | &g_metrics.circuit_breaker_total_closes); | ||
| 494 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (written < 0) goto truncation_error; |
| 495 | 9 | offset += (size_t)written; | |
| 496 | |||
| 497 | 9 | return buffer; | |
| 498 | |||
| 499 | ✗ | truncation_error: | |
| 500 | ✗ | free(buffer); | |
| 501 | ✗ | return NULL; | |
| 502 | } | ||
| 503 | |||
| 504 | 9 | void metrics_free_format(char *formatted) | |
| 505 | { | ||
| 506 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | if (formatted) { |
| 507 | 9 | free(formatted); | |
| 508 | } | ||
| 509 | 9 | } | |
| 510 | |||
| 511 | 10 | static void *metrics_server_thread(void *arg) | |
| 512 | { | ||
| 513 | (void)arg; | ||
| 514 | 10 | int server_fd = g_metrics_server_fd; | |
| 515 | |||
| 516 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 10 times.
|
20 | while (atomic_load(&g_server_running)) { |
| 517 | struct sockaddr_in client_addr; | ||
| 518 | 10 | socklen_t client_len = sizeof(client_addr); | |
| 519 | |||
| 520 | 10 | int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len); | |
| 521 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (client_fd < 0) { |
| 522 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (atomic_load(&g_server_running)) { |
| 523 | ✗ | log_message(LOG_LEVEL_ERROR, "Metrics server accept failed"); | |
| 524 | } | ||
| 525 | 10 | continue; | |
| 526 | } | ||
| 527 | |||
| 528 | char request_buffer[METRICS_REQUEST_BUFFER_SIZE]; | ||
| 529 | ✗ | ssize_t bytes_read = read(client_fd, request_buffer, sizeof(request_buffer) - 1); | |
| 530 | ✗ | if (bytes_read > 0) { | |
| 531 | ✗ | request_buffer[bytes_read] = '\0'; | |
| 532 | |||
| 533 | ✗ | if (strstr(request_buffer, "GET /metrics") != NULL) { | |
| 534 | ✗ | char *metrics_data = metrics_format_prometheus(); | |
| 535 | ✗ | if (metrics_data) { | |
| 536 | char response[METRICS_RESPONSE_BUFFER_SIZE]; | ||
| 537 | ✗ | size_t metrics_len = strlen(metrics_data); | |
| 538 | ✗ | int response_len = snprintf(response, sizeof(response), | |
| 539 | "HTTP/1.1 200 OK\r\n" | ||
| 540 | "Content-Type: text/plain; version=0.0.4\r\n" | ||
| 541 | "Content-Length: %zu\r\n" | ||
| 542 | "Connection: close\r\n" | ||
| 543 | "\r\n" | ||
| 544 | "%s", | ||
| 545 | metrics_len, metrics_data); | ||
| 546 | |||
| 547 | ✗ | if (response_len > 0 && response_len < (int)sizeof(response)) { | |
| 548 | ✗ | write(client_fd, response, (size_t)response_len); | |
| 549 | } | ||
| 550 | ✗ | metrics_free_format(metrics_data); | |
| 551 | } | ||
| 552 | } else { | ||
| 553 | ✗ | const char *response = "HTTP/1.1 404 Not Found\r\n\r\n"; | |
| 554 | ✗ | write(client_fd, response, strlen(response)); | |
| 555 | } | ||
| 556 | } | ||
| 557 | |||
| 558 | ✗ | close(client_fd); | |
| 559 | } | ||
| 560 | |||
| 561 | 10 | return NULL; | |
| 562 | } | ||
| 563 | |||
| 564 | 10 | int metrics_start_server(int port) | |
| 565 | { | ||
| 566 | 10 | g_metrics_server_fd = socket(AF_INET, SOCK_STREAM, 0); | |
| 567 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (g_metrics_server_fd < 0) { |
| 568 | ✗ | log_message(LOG_LEVEL_ERROR, "Failed to create metrics socket"); | |
| 569 | ✗ | return -1; | |
| 570 | } | ||
| 571 | |||
| 572 | 10 | int opt = 1; | |
| 573 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
|
10 | if (setsockopt(g_metrics_server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { |
| 574 | ✗ | log_message(LOG_LEVEL_ERROR, "Failed to set SO_REUSEADDR on metrics socket"); | |
| 575 | ✗ | close(g_metrics_server_fd); | |
| 576 | ✗ | return -1; | |
| 577 | } | ||
| 578 | |||
| 579 | 10 | struct sockaddr_in addr = {0}; | |
| 580 | 10 | addr.sin_family = AF_INET; | |
| 581 | 10 | addr.sin_addr.s_addr = inet_addr("127.0.0.1"); | |
| 582 | 10 | addr.sin_port = htons(port); | |
| 583 | |||
| 584 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
|
10 | if (bind(g_metrics_server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { |
| 585 | ✗ | log_message(LOG_LEVEL_ERROR, "Failed to bind metrics server to port %d", port); | |
| 586 | ✗ | close(g_metrics_server_fd); | |
| 587 | ✗ | return -1; | |
| 588 | } | ||
| 589 | |||
| 590 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
|
10 | if (listen(g_metrics_server_fd, METRICS_SERVER_BACKLOG) < 0) { |
| 591 | ✗ | log_message(LOG_LEVEL_ERROR, "Failed to listen on metrics socket"); | |
| 592 | ✗ | close(g_metrics_server_fd); | |
| 593 | ✗ | return -1; | |
| 594 | } | ||
| 595 | |||
| 596 | 10 | atomic_store(&g_server_running, 1); | |
| 597 | |||
| 598 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
|
10 | if (pthread_create(&g_metrics_server_thread, NULL, metrics_server_thread, NULL) != 0) { |
| 599 | ✗ | log_message(LOG_LEVEL_ERROR, "Failed to create metrics server thread"); | |
| 600 | ✗ | atomic_store(&g_server_running, 0); | |
| 601 | ✗ | close(g_metrics_server_fd); | |
| 602 | ✗ | return -1; | |
| 603 | } | ||
| 604 | |||
| 605 | 10 | log_message(LOG_LEVEL_INFO, "Metrics server started on http://127.0.0.1:%d/metrics", port); | |
| 606 | 10 | return 0; | |
| 607 | } | ||
| 608 | |||
| 609 | 18 | void metrics_stop_server(void) | |
| 610 | { | ||
| 611 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 8 times.
|
18 | if (atomic_load(&g_server_running)) { |
| 612 | 10 | atomic_store(&g_server_running, 0); | |
| 613 | |||
| 614 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | if (g_metrics_server_fd >= 0) { |
| 615 | 10 | shutdown(g_metrics_server_fd, SHUT_RDWR); | |
| 616 | 10 | close(g_metrics_server_fd); | |
| 617 | 10 | g_metrics_server_fd = -1; | |
| 618 | } | ||
| 619 | |||
| 620 | 10 | pthread_join(g_metrics_server_thread, NULL); | |
| 621 | 10 | log_message(LOG_LEVEL_INFO, "Metrics server stopped"); | |
| 622 | } | ||
| 623 | 18 | } | |
| 624 |