Nix Observability Daemon
observability nix
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

hmm

+23 -35
+23 -35
src/main.rs
··· 9 9 use tokio::sync::Mutex; 10 10 use tracing::{error, info, debug}; 11 11 use sqlx::{sqlite::SqlitePoolOptions, sqlite::SqliteConnectOptions, Pool, Sqlite, Row}; 12 - use chrono::Utc; 12 + use chrono::{Utc, DateTime}; 13 13 use std::str::FromStr; 14 14 use std::fmt; 15 - 16 15 17 16 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 18 17 #[repr(u64)] ··· 81 80 } 82 81 83 82 #[derive(Debug, Deserialize, Serialize)] 84 - #[serde(rename_all = "camelCase")] 85 83 struct NixEvent { 86 84 action: String, 87 85 id: u64, ··· 94 92 fields: Vec<serde_json::Value>, 95 93 #[serde(default)] 96 94 parent: u64, 97 - } 98 - 99 - struct State { 100 - active_activities: HashMap<u64, Activity>, 101 - pool: Pool<Sqlite>, 102 95 } 103 96 104 97 struct Activity { 105 98 id: u64, 106 99 event_type: u64, 107 100 text: String, 108 - start_time: chrono::DateTime<Utc>, 101 + start_time: DateTime<Utc>, 109 102 fields: Vec<serde_json::Value>, 110 103 result_fields: Vec<serde_json::Value>, 111 104 } 112 105 106 + struct State { 107 + active_activities: HashMap<u64, Activity>, 108 + pool: Pool<Sqlite>, 109 + } 110 + 113 111 #[tokio::main] 114 112 async fn main() -> Result<()> { 115 113 tracing_subscriber::fmt::init(); ··· 207 205 208 206 match event.action.as_str() { 209 207 "start" => { 210 - info!(id = event.id, type = event.event_type, "Activity started: {}", event.text); 208 + let act_type = ActivityType::from(event.event_type); 209 + info!(id = event.id, %act_type, "Activity started: {}", event.text); 210 + 211 211 s.active_activities.insert(event.id, Activity { 212 212 id: event.id, 213 213 event_type: event.event_type, ··· 218 218 }); 219 219 } 220 220 "result" => { 221 - debug!(id = event.id, "Received result for activity"); 222 221 if let Some(activity) = s.active_activities.get_mut(&event.id) { 223 222 activity.result_fields = event.fields; 224 223 } ··· 227 226 if let Some(activity) = s.active_activities.remove(&event.id) { 228 227 let end_time = Utc::now(); 229 228 let duration = end_time.signed_duration_since(activity.start_time); 229 + let act_type = ActivityType::from(activity.event_type); 230 230 231 231 info!( 232 232 id = activity.id, 233 + %act_type, 233 234 duration_ms = duration.num_milliseconds(), 234 235 "Activity finished: {}", 235 236 activity.text ··· 238 239 let mut drv_path = None; 239 240 let mut cache_url = None; 240 241 241 - // Expand detection to include more activity types 242 - match activity.event_type { 243 - // 101: CopyPath, 102: FileTransfer, 103: Realise, 105: Build 244 - 101 | 102 | 103 | 105 => { 242 + match act_type { 243 + ActivityType::CopyPath | ActivityType::FileTransfer | 244 + ActivityType::Realise | ActivityType::Builds | ActivityType::Build => { 245 245 drv_path = activity.fields.get(0) 246 246 .and_then(|v| v.as_str()) 247 247 .map(|s| s.to_string()); 248 248 } 249 - // 108: Substitute 250 - 108 => { 249 + ActivityType::Substitute => { 251 250 drv_path = activity.fields.get(0) 252 251 .and_then(|v| v.as_str()) 253 252 .map(|s| s.to_string()); ··· 304 303 return Ok(()); 305 304 } 306 305 307 - // Header 308 306 println!("{:<20} {:>10} {:>15} {:>15}", "Activity", "Count", "Avg Time", "Total Time"); 309 307 println!("{:-<20} {:->10} {:->15} {:->15}", "", "", "", ""); 310 308 311 309 for row in rows { 312 - let ty: i64 = row.get("event_type"); 310 + let ty_code: i64 = row.get("event_type"); 313 311 let count: i64 = row.get("count"); 314 312 let avg: f64 = row.get("avg_ms"); 315 313 let total: i64 = row.get("total_ms"); 316 314 317 - let label = match ty { 318 - 0 => "Log/Output", 319 - 101 => "Copy Path", 320 - 102 => "File Transfer", 321 - 103 => "Realise", 322 - 104 => "Copy Paths", 323 - 105 => "Build", 324 - 108 => "Substitute", 325 - 109 => "Query Info", 326 - _ => "Other", 327 - }; 315 + let label = ActivityType::from(ty_code as u64).to_string(); 328 316 329 317 println!( 330 318 "{:<20} {:>10} {:>14.2}s {:>14.2}s", ··· 335 323 ); 336 324 } 337 325 338 - // Top 5 most "expensive" individual events 339 326 println!("\nTop 5 Most Time-Consuming Tasks:"); 340 327 println!("{:<12} {:<15} {}", "Duration", "Type", "Resource/Text"); 341 328 println!("{:-<12} {:-<15} {:-<30}", "", "", ""); ··· 352 339 353 340 for row in top_events { 354 341 let dur: i64 = row.get("duration_ms"); 355 - let ty: i64 = row.get("event_type"); 342 + let ty_code: i64 = row.get("event_type"); 343 + let label = ActivityType::from(ty_code as u64).to_string(); 356 344 let path: String = row.get::<Option<String>, _>("drv_path") 357 345 .or_else(|| row.get::<Option<String>, _>("text")) 358 346 .unwrap_or_else(|| "unknown".to_string()); 359 347 360 - let short_path = if path.len() > 50 { 361 - format!("...{}", &path[path.len()-47..]) 348 + let short_path = if path.len() > 60 { 349 + format!("...{}", &path[path.len()-57..]) 362 350 } else { 363 351 path 364 352 }; ··· 366 354 println!( 367 355 "{:>10.2}s {:<15} {}", 368 356 dur as f64 / 1000.0, 369 - match ty { 105 => "Build", 108 => "Subst", 102 => "Xfer", 103 => "Realise", _ => "Other" }, 357 + label, 370 358 short_path 371 359 ); 372 360 }