diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-11 16:53:03 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-11 16:53:03 +0000 |
| commit | 2a9160836bb87fdea3ae891563b0169c68d1c2ab (patch) | |
| tree | 583c890687beaf7f380fc0be131bdf17485f06fa /src/metrics | |
| parent | 52489d3b1a7d79e164b4cc901b53fd06c05ce1b1 (diff) | |
fix: resolve all fmt and clippy warnings
Main lib (src/):
- Add #[allow(dead_code)] for build_info field (stored to prevent Prometheus unregistration)
- Add #[allow(dead_code)] for first_seen field (reserved for future rate limiting)
- Replace .or_insert_with(RelaySyncNeeds::default) with .or_default()
- Replace manual div_ceil implementations with .div_ceil(100)
Test code (tests/):
- Replace .expect(&format!(...)) with .unwrap_or_else(|_| panic!(...))
- Remove needless borrows in fetch_metrics() calls
- Add #[allow(dead_code)] and #[allow(unused_imports)] to test helpers module
grasp-audit:
- Apply cargo fmt to fix formatting
Diffstat (limited to 'src/metrics')
| -rw-r--r-- | src/metrics/bandwidth.rs | 13 | ||||
| -rw-r--r-- | src/metrics/connection.rs | 58 | ||||
| -rw-r--r-- | src/metrics/mod.rs | 127 |
3 files changed, 122 insertions, 76 deletions
diff --git a/src/metrics/bandwidth.rs b/src/metrics/bandwidth.rs index d2c53e8..d51af12 100644 --- a/src/metrics/bandwidth.rs +++ b/src/metrics/bandwidth.rs | |||
| @@ -80,7 +80,9 @@ impl BandwidthTracker { | |||
| 80 | &["repo"], | 80 | &["repo"], |
| 81 | ) | 81 | ) |
| 82 | .unwrap(); | 82 | .unwrap(); |
| 83 | registry.register(Box::new(top_repos_gauge.clone())).unwrap(); | 83 | registry |
| 84 | .register(Box::new(top_repos_gauge.clone())) | ||
| 85 | .unwrap(); | ||
| 84 | 86 | ||
| 85 | Self { | 87 | Self { |
| 86 | all_repos: DashMap::new(), | 88 | all_repos: DashMap::new(), |
| @@ -120,7 +122,12 @@ impl BandwidthTracker { | |||
| 120 | // Try to update the timestamp atomically to prevent concurrent refreshes | 122 | // Try to update the timestamp atomically to prevent concurrent refreshes |
| 121 | if self | 123 | if self |
| 122 | .last_refresh_nanos | 124 | .last_refresh_nanos |
| 123 | .compare_exchange(last_refresh, elapsed_nanos, Ordering::SeqCst, Ordering::Relaxed) | 125 | .compare_exchange( |
| 126 | last_refresh, | ||
| 127 | elapsed_nanos, | ||
| 128 | Ordering::SeqCst, | ||
| 129 | Ordering::Relaxed, | ||
| 130 | ) | ||
| 124 | .is_ok() | 131 | .is_ok() |
| 125 | { | 132 | { |
| 126 | self.refresh_top_n(); | 133 | self.refresh_top_n(); |
| @@ -298,4 +305,4 @@ mod tests { | |||
| 298 | // Refresh should not panic on empty data | 305 | // Refresh should not panic on empty data |
| 299 | tracker.refresh_top_n(); | 306 | tracker.refresh_top_n(); |
| 300 | } | 307 | } |
| 301 | } \ No newline at end of file | 308 | } |
diff --git a/src/metrics/connection.rs b/src/metrics/connection.rs index 6a7f406..2d42081 100644 --- a/src/metrics/connection.rs +++ b/src/metrics/connection.rs | |||
| @@ -25,7 +25,8 @@ use tracing::warn; | |||
| 25 | struct ConnectionInfo { | 25 | struct ConnectionInfo { |
| 26 | /// Number of active connections from this IP | 26 | /// Number of active connections from this IP |
| 27 | count: u32, | 27 | count: u32, |
| 28 | /// When the first connection from this IP was established | 28 | /// When the first connection from this IP was established (for future rate limiting) |
| 29 | #[allow(dead_code)] | ||
| 29 | first_seen: Instant, | 30 | first_seen: Instant, |
| 30 | /// Whether this IP has been flagged as potentially abusive | 31 | /// Whether this IP has been flagged as potentially abusive |
| 31 | flagged_as_abuse: bool, | 32 | flagged_as_abuse: bool, |
| @@ -48,16 +49,16 @@ struct ConnectionInfo { | |||
| 48 | pub struct ConnectionTracker { | 49 | pub struct ConnectionTracker { |
| 49 | /// Active connections per IP (INTERNAL ONLY - never exposed to metrics) | 50 | /// Active connections per IP (INTERNAL ONLY - never exposed to metrics) |
| 50 | connections: DashMap<IpAddr, ConnectionInfo>, | 51 | connections: DashMap<IpAddr, ConnectionInfo>, |
| 51 | 52 | ||
| 52 | /// Threshold for abuse flagging (connections per IP) | 53 | /// Threshold for abuse flagging (connections per IP) |
| 53 | abuse_threshold: u32, | 54 | abuse_threshold: u32, |
| 54 | 55 | ||
| 55 | /// Prometheus gauge: total active connections | 56 | /// Prometheus gauge: total active connections |
| 56 | active_connections: IntGauge, | 57 | active_connections: IntGauge, |
| 57 | 58 | ||
| 58 | /// Prometheus gauge: number of unique IPs connected | 59 | /// Prometheus gauge: number of unique IPs connected |
| 59 | unique_ips: IntGauge, | 60 | unique_ips: IntGauge, |
| 60 | 61 | ||
| 61 | /// Prometheus gauge: number of IPs flagged as potential abusers | 62 | /// Prometheus gauge: number of IPs flagged as potential abusers |
| 62 | flagged_abusers: IntGauge, | 63 | flagged_abusers: IntGauge, |
| 63 | } | 64 | } |
| @@ -70,29 +71,30 @@ impl ConnectionTracker { | |||
| 70 | /// * `abuse_threshold` - Number of connections from a single IP before flagging | 71 | /// * `abuse_threshold` - Number of connections from a single IP before flagging |
| 71 | /// * `registry` - Prometheus registry to register metrics with | 72 | /// * `registry` - Prometheus registry to register metrics with |
| 72 | pub fn new(abuse_threshold: u32, registry: &Registry) -> Self { | 73 | pub fn new(abuse_threshold: u32, registry: &Registry) -> Self { |
| 73 | let active_connections = IntGauge::with_opts( | 74 | let active_connections = IntGauge::with_opts(Opts::new( |
| 74 | Opts::new( | 75 | "ngit_websocket_connections_active", |
| 75 | "ngit_websocket_connections_active", | 76 | "Current active WebSocket connections", |
| 76 | "Current active WebSocket connections", | 77 | )) |
| 77 | ) | 78 | .unwrap(); |
| 78 | ).unwrap(); | 79 | registry |
| 79 | registry.register(Box::new(active_connections.clone())).unwrap(); | 80 | .register(Box::new(active_connections.clone())) |
| 80 | 81 | .unwrap(); | |
| 81 | let unique_ips = IntGauge::with_opts( | 82 | |
| 82 | Opts::new( | 83 | let unique_ips = IntGauge::with_opts(Opts::new( |
| 83 | "ngit_websocket_unique_ips", | 84 | "ngit_websocket_unique_ips", |
| 84 | "Number of unique IP addresses connected (NOT the IPs themselves)", | 85 | "Number of unique IP addresses connected (NOT the IPs themselves)", |
| 85 | ) | 86 | )) |
| 86 | ).unwrap(); | 87 | .unwrap(); |
| 87 | registry.register(Box::new(unique_ips.clone())).unwrap(); | 88 | registry.register(Box::new(unique_ips.clone())).unwrap(); |
| 88 | 89 | ||
| 89 | let flagged_abusers = IntGauge::with_opts( | 90 | let flagged_abusers = IntGauge::with_opts(Opts::new( |
| 90 | Opts::new( | 91 | "ngit_websocket_flagged_abusers", |
| 91 | "ngit_websocket_flagged_abusers", | 92 | "Number of IPs exceeding connection threshold", |
| 92 | "Number of IPs exceeding connection threshold", | 93 | )) |
| 93 | ) | 94 | .unwrap(); |
| 94 | ).unwrap(); | 95 | registry |
| 95 | registry.register(Box::new(flagged_abusers.clone())).unwrap(); | 96 | .register(Box::new(flagged_abusers.clone())) |
| 97 | .unwrap(); | ||
| 96 | 98 | ||
| 97 | Self { | 99 | Self { |
| 98 | connections: DashMap::new(), | 100 | connections: DashMap::new(), |
| @@ -140,7 +142,7 @@ impl ConnectionTracker { | |||
| 140 | 142 | ||
| 141 | // Update Prometheus metrics (aggregate counts only) | 143 | // Update Prometheus metrics (aggregate counts only) |
| 142 | self.active_connections.inc(); | 144 | self.active_connections.inc(); |
| 143 | 145 | ||
| 144 | if is_new_ip { | 146 | if is_new_ip { |
| 145 | self.unique_ips.inc(); | 147 | self.unique_ips.inc(); |
| 146 | } | 148 | } |
| @@ -334,4 +336,4 @@ mod tests { | |||
| 334 | assert_eq!(tracker.active_connections(), 0); | 336 | assert_eq!(tracker.active_connections(), 0); |
| 335 | assert_eq!(tracker.unique_ip_count(), 0); | 337 | assert_eq!(tracker.unique_ip_count(), 0); |
| 336 | } | 338 | } |
| 337 | } \ No newline at end of file | 339 | } |
diff --git a/src/metrics/mod.rs b/src/metrics/mod.rs index 736414f..5420dfd 100644 --- a/src/metrics/mod.rs +++ b/src/metrics/mod.rs | |||
| @@ -87,7 +87,8 @@ struct MetricsInner { | |||
| 87 | // === System Health Metrics === | 87 | // === System Health Metrics === |
| 88 | /// Server start time for uptime calculation | 88 | /// Server start time for uptime calculation |
| 89 | pub start_time: Instant, | 89 | pub start_time: Instant, |
| 90 | /// Build information gauge | 90 | /// Build information gauge (stored to prevent unregistration from Prometheus) |
| 91 | #[allow(dead_code)] | ||
| 91 | pub build_info: GaugeVec, | 92 | pub build_info: GaugeVec, |
| 92 | } | 93 | } |
| 93 | 94 | ||
| @@ -158,7 +159,10 @@ impl Metrics { | |||
| 158 | 159 | ||
| 159 | /// Start timing a git operation, returns a timer | 160 | /// Start timing a git operation, returns a timer |
| 160 | pub fn start_git_operation_timer(&self, operation: &str) -> GitOperationTimer { | 161 | pub fn start_git_operation_timer(&self, operation: &str) -> GitOperationTimer { |
| 161 | GitOperationTimer::new(self.inner.git_operation_duration.clone(), operation.to_string()) | 162 | GitOperationTimer::new( |
| 163 | self.inner.git_operation_duration.clone(), | ||
| 164 | operation.to_string(), | ||
| 165 | ) | ||
| 162 | } | 166 | } |
| 163 | 167 | ||
| 164 | /// Record bytes transferred for a git operation | 168 | /// Record bytes transferred for a git operation |
| @@ -266,13 +270,14 @@ impl MetricsInner { | |||
| 266 | } | 270 | } |
| 267 | 271 | ||
| 268 | // WebSocket metrics | 272 | // WebSocket metrics |
| 269 | let websocket_connections_total = Counter::with_opts( | 273 | let websocket_connections_total = Counter::with_opts(Opts::new( |
| 270 | Opts::new( | 274 | "ngit_websocket_connections_total", |
| 271 | "ngit_websocket_connections_total", | 275 | "Total WebSocket connections since startup", |
| 272 | "Total WebSocket connections since startup", | 276 | )) |
| 273 | ) | 277 | .unwrap(); |
| 274 | ).unwrap(); | 278 | REGISTRY |
| 275 | REGISTRY.register(Box::new(websocket_connections_total.clone())).unwrap(); | 279 | .register(Box::new(websocket_connections_total.clone())) |
| 280 | .unwrap(); | ||
| 276 | 281 | ||
| 277 | let websocket_connection_duration = Histogram::with_opts( | 282 | let websocket_connection_duration = Histogram::with_opts( |
| 278 | HistogramOpts::new( | 283 | HistogramOpts::new( |
| @@ -280,8 +285,11 @@ impl MetricsInner { | |||
| 280 | "Duration of WebSocket connections", | 285 | "Duration of WebSocket connections", |
| 281 | ) | 286 | ) |
| 282 | .buckets(vec![1.0, 5.0, 15.0, 30.0, 60.0, 300.0, 900.0, 3600.0]), | 287 | .buckets(vec![1.0, 5.0, 15.0, 30.0, 60.0, 300.0, 900.0, 3600.0]), |
| 283 | ).unwrap(); | 288 | ) |
| 284 | REGISTRY.register(Box::new(websocket_connection_duration.clone())).unwrap(); | 289 | .unwrap(); |
| 290 | REGISTRY | ||
| 291 | .register(Box::new(websocket_connection_duration.clone())) | ||
| 292 | .unwrap(); | ||
| 285 | 293 | ||
| 286 | let websocket_messages_received = CounterVec::new( | 294 | let websocket_messages_received = CounterVec::new( |
| 287 | Opts::new( | 295 | Opts::new( |
| @@ -289,8 +297,11 @@ impl MetricsInner { | |||
| 289 | "WebSocket messages received by type", | 297 | "WebSocket messages received by type", |
| 290 | ), | 298 | ), |
| 291 | &["type"], | 299 | &["type"], |
| 292 | ).unwrap(); | 300 | ) |
| 293 | REGISTRY.register(Box::new(websocket_messages_received.clone())).unwrap(); | 301 | .unwrap(); |
| 302 | REGISTRY | ||
| 303 | .register(Box::new(websocket_messages_received.clone())) | ||
| 304 | .unwrap(); | ||
| 294 | 305 | ||
| 295 | let websocket_messages_sent = CounterVec::new( | 306 | let websocket_messages_sent = CounterVec::new( |
| 296 | Opts::new( | 307 | Opts::new( |
| @@ -298,8 +309,11 @@ impl MetricsInner { | |||
| 298 | "WebSocket messages sent by type", | 309 | "WebSocket messages sent by type", |
| 299 | ), | 310 | ), |
| 300 | &["type"], | 311 | &["type"], |
| 301 | ).unwrap(); | 312 | ) |
| 302 | REGISTRY.register(Box::new(websocket_messages_sent.clone())).unwrap(); | 313 | .unwrap(); |
| 314 | REGISTRY | ||
| 315 | .register(Box::new(websocket_messages_sent.clone())) | ||
| 316 | .unwrap(); | ||
| 303 | 317 | ||
| 304 | // Git operation metrics | 318 | // Git operation metrics |
| 305 | let git_operations_total = CounterVec::new( | 319 | let git_operations_total = CounterVec::new( |
| @@ -308,8 +322,11 @@ impl MetricsInner { | |||
| 308 | "Git operations by type and status", | 322 | "Git operations by type and status", |
| 309 | ), | 323 | ), |
| 310 | &["operation", "status"], | 324 | &["operation", "status"], |
| 311 | ).unwrap(); | 325 | ) |
| 312 | REGISTRY.register(Box::new(git_operations_total.clone())).unwrap(); | 326 | .unwrap(); |
| 327 | REGISTRY | ||
| 328 | .register(Box::new(git_operations_total.clone())) | ||
| 329 | .unwrap(); | ||
| 313 | 330 | ||
| 314 | let git_operation_duration = HistogramVec::new( | 331 | let git_operation_duration = HistogramVec::new( |
| 315 | HistogramOpts::new( | 332 | HistogramOpts::new( |
| @@ -318,8 +335,11 @@ impl MetricsInner { | |||
| 318 | ) | 335 | ) |
| 319 | .buckets(vec![0.1, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0]), | 336 | .buckets(vec![0.1, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0]), |
| 320 | &["operation"], | 337 | &["operation"], |
| 321 | ).unwrap(); | 338 | ) |
| 322 | REGISTRY.register(Box::new(git_operation_duration.clone())).unwrap(); | 339 | .unwrap(); |
| 340 | REGISTRY | ||
| 341 | .register(Box::new(git_operation_duration.clone())) | ||
| 342 | .unwrap(); | ||
| 323 | 343 | ||
| 324 | let git_bytes_total = CounterVec::new( | 344 | let git_bytes_total = CounterVec::new( |
| 325 | Opts::new( | 345 | Opts::new( |
| @@ -327,8 +347,11 @@ impl MetricsInner { | |||
| 327 | "Total bytes transferred for git operations", | 347 | "Total bytes transferred for git operations", |
| 328 | ), | 348 | ), |
| 329 | &["direction"], | 349 | &["direction"], |
| 330 | ).unwrap(); | 350 | ) |
| 331 | REGISTRY.register(Box::new(git_bytes_total.clone())).unwrap(); | 351 | .unwrap(); |
| 352 | REGISTRY | ||
| 353 | .register(Box::new(git_bytes_total.clone())) | ||
| 354 | .unwrap(); | ||
| 332 | 355 | ||
| 333 | let git_push_authorization = CounterVec::new( | 356 | let git_push_authorization = CounterVec::new( |
| 334 | Opts::new( | 357 | Opts::new( |
| @@ -336,8 +359,11 @@ impl MetricsInner { | |||
| 336 | "Push authorization results", | 359 | "Push authorization results", |
| 337 | ), | 360 | ), |
| 338 | &["result"], | 361 | &["result"], |
| 339 | ).unwrap(); | 362 | ) |
| 340 | REGISTRY.register(Box::new(git_push_authorization.clone())).unwrap(); | 363 | .unwrap(); |
| 364 | REGISTRY | ||
| 365 | .register(Box::new(git_push_authorization.clone())) | ||
| 366 | .unwrap(); | ||
| 341 | 367 | ||
| 342 | // Nostr event metrics | 368 | // Nostr event metrics |
| 343 | let events_received_total = CounterVec::new( | 369 | let events_received_total = CounterVec::new( |
| @@ -346,8 +372,11 @@ impl MetricsInner { | |||
| 346 | "Nostr events received by kind", | 372 | "Nostr events received by kind", |
| 347 | ), | 373 | ), |
| 348 | &["kind"], | 374 | &["kind"], |
| 349 | ).unwrap(); | 375 | ) |
| 350 | REGISTRY.register(Box::new(events_received_total.clone())).unwrap(); | 376 | .unwrap(); |
| 377 | REGISTRY | ||
| 378 | .register(Box::new(events_received_total.clone())) | ||
| 379 | .unwrap(); | ||
| 351 | 380 | ||
| 352 | let events_stored_total = CounterVec::new( | 381 | let events_stored_total = CounterVec::new( |
| 353 | Opts::new( | 382 | Opts::new( |
| @@ -355,8 +384,11 @@ impl MetricsInner { | |||
| 355 | "Nostr events successfully stored by kind", | 384 | "Nostr events successfully stored by kind", |
| 356 | ), | 385 | ), |
| 357 | &["kind"], | 386 | &["kind"], |
| 358 | ).unwrap(); | 387 | ) |
| 359 | REGISTRY.register(Box::new(events_stored_total.clone())).unwrap(); | 388 | .unwrap(); |
| 389 | REGISTRY | ||
| 390 | .register(Box::new(events_stored_total.clone())) | ||
| 391 | .unwrap(); | ||
| 360 | 392 | ||
| 361 | let events_rejected_total = CounterVec::new( | 393 | let events_rejected_total = CounterVec::new( |
| 362 | Opts::new( | 394 | Opts::new( |
| @@ -364,31 +396,36 @@ impl MetricsInner { | |||
| 364 | "Nostr events rejected by kind and reason", | 396 | "Nostr events rejected by kind and reason", |
| 365 | ), | 397 | ), |
| 366 | &["kind", "reason"], | 398 | &["kind", "reason"], |
| 367 | ).unwrap(); | 399 | ) |
| 368 | REGISTRY.register(Box::new(events_rejected_total.clone())).unwrap(); | 400 | .unwrap(); |
| 401 | REGISTRY | ||
| 402 | .register(Box::new(events_rejected_total.clone())) | ||
| 403 | .unwrap(); | ||
| 369 | 404 | ||
| 370 | // Repository metrics | 405 | // Repository metrics |
| 371 | let repositories_total = Gauge::with_opts( | 406 | let repositories_total = Gauge::with_opts(Opts::new( |
| 372 | Opts::new( | 407 | "ngit_repositories_total", |
| 373 | "ngit_repositories_total", | 408 | "Total repositories hosted", |
| 374 | "Total repositories hosted", | 409 | )) |
| 375 | ) | 410 | .unwrap(); |
| 376 | ).unwrap(); | 411 | REGISTRY |
| 377 | REGISTRY.register(Box::new(repositories_total.clone())).unwrap(); | 412 | .register(Box::new(repositories_total.clone())) |
| 413 | .unwrap(); | ||
| 378 | 414 | ||
| 379 | // Build info | 415 | // Build info |
| 380 | let build_info = GaugeVec::new( | 416 | let build_info = GaugeVec::new( |
| 381 | Opts::new( | 417 | Opts::new("ngit_build_info", "Build information"), |
| 382 | "ngit_build_info", | ||
| 383 | "Build information", | ||
| 384 | ), | ||
| 385 | &["version", "commit"], | 418 | &["version", "commit"], |
| 386 | ).unwrap(); | 419 | ) |
| 420 | .unwrap(); | ||
| 387 | REGISTRY.register(Box::new(build_info.clone())).unwrap(); | 421 | REGISTRY.register(Box::new(build_info.clone())).unwrap(); |
| 388 | 422 | ||
| 389 | // Set build info gauge to 1 (it's just for labels) | 423 | // Set build info gauge to 1 (it's just for labels) |
| 390 | build_info | 424 | build_info |
| 391 | .with_label_values(&[env!("CARGO_PKG_VERSION"), option_env!("GIT_HASH").unwrap_or("unknown")]) | 425 | .with_label_values(&[ |
| 426 | env!("CARGO_PKG_VERSION"), | ||
| 427 | option_env!("GIT_HASH").unwrap_or("unknown"), | ||
| 428 | ]) | ||
| 392 | .set(1.0); | 429 | .set(1.0); |
| 393 | 430 | ||
| 394 | Self { | 431 | Self { |
| @@ -472,7 +509,7 @@ mod tests { | |||
| 472 | // Note: This test may fail if run with other tests due to global registry | 509 | // Note: This test may fail if run with other tests due to global registry |
| 473 | // In production, consider using a test-specific registry | 510 | // In production, consider using a test-specific registry |
| 474 | let metrics = Metrics::new(10); | 511 | let metrics = Metrics::new(10); |
| 475 | 512 | ||
| 476 | // Test that we can record metrics without panicking | 513 | // Test that we can record metrics without panicking |
| 477 | metrics.record_websocket_connection(); | 514 | metrics.record_websocket_connection(); |
| 478 | metrics.record_message_received("REQ"); | 515 | metrics.record_message_received("REQ"); |
| @@ -484,4 +521,4 @@ mod tests { | |||
| 484 | metrics.record_event_rejected(1, "invalid_signature"); | 521 | metrics.record_event_rejected(1, "invalid_signature"); |
| 485 | metrics.set_repositories_total(5); | 522 | metrics.set_repositories_total(5); |
| 486 | } | 523 | } |
| 487 | } \ No newline at end of file | 524 | } |