diff options
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 69 |
1 files changed, 65 insertions, 4 deletions
diff --git a/src/main.rs b/src/main.rs index a4a731a..ff3e596 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod version; mod docker; mod manifest; mod dockerfile; +mod metrics; use crate::parser::*; use crate::version::{VersionPattern, CompareOutcome}; @@ -28,7 +29,8 @@ Search FILE for docker images and suggest updates Options: --auth <REGISTRY> <USER> <PASS> Authenticate against REGISTRY (repeatable) --config <PATH> Read config from PATH ---scratch <DIR> Store temporary files in DIR", +--scratch <DIR> Store temporary files in DIR +--metrics-port <PORT> Serve Prometheus metrics on PORT (default: disabled)", cmd ); } @@ -172,36 +174,56 @@ fn perform_registry_request(registry: &str, url: &str, accept: &'static str, aut request = state.add_to_request(request); } + let start_time = std::time::Instant::now(); let response = request.call().unwrap(); + let duration = start_time.elapsed().as_secs_f64(); + metrics::get().http_request_duration.with_label_values(&[registry]).observe(duration); if response.status() == 401 { if !authentication_retry { if let Some(ref mut state) = state { - state.authenticate(&response).map_err(RegistryError::Other)?; - authentication_retry = true; - continue; + match state.authenticate(&response) { + Ok(()) => { + metrics::get().auth_attempts.with_label_values(&[registry, "success"]).inc(); + authentication_retry = true; + continue; + } + Err(e) => { + metrics::get().auth_attempts.with_label_values(&[registry, "failure"]).inc(); + return Err(RegistryError::Other(e)); + } + } } else { + metrics::get().http_request.with_label_values(&[registry, "error"]).inc(); return Err(RegistryError::Other(format!("Server {} returned 401 but we have no credentials", registry))); } } else { + metrics::get().auth_attempts.with_label_values(&[registry, "failure"]).inc(); + metrics::get().http_request.with_label_values(&[registry, "error"]).inc(); return Err(RegistryError::Other(format!("Authentication failed"))); } } if response.status() == 429 { + metrics::get().rate_limits.with_label_values(&[registry]).inc(); + metrics::get().state.set(metrics::STATE_THROTTLED); println!("Too many requests"); std::thread::sleep(std::time::Duration::from_secs(8)); + metrics::get().state.set(metrics::STATE_ACTIVE); continue; } if response.status() == 404 { + metrics::get().http_request.with_label_values(&[registry, "not_found"]).inc(); return Err(RegistryError::NotFound); } if response.status() != 200 { + metrics::get().http_request.with_label_values(&[registry, "error"]).inc(); return Err(RegistryError::Other(format!("Unexpected status code: {}", response.status()))); } + metrics::get().http_request.with_label_values(&[registry, "success"]).inc(); return Ok(response); } } @@ -227,6 +249,8 @@ fn update_images(db: &Connection, file: &str, auth: &mut Auth, img: DockerRef, e let mut tag = img.tag.as_ref().map(|x| file[x.clone()].to_string()); let image = &file[img.image.clone()]; + metrics::get().images_checked.with_label_values(&[registry]).inc(); + let _last_checked = match db.query_one(" SELECT last_checked FROM images WHERE registry = ?1 AND image = ?2 @@ -301,6 +325,7 @@ fn update_images(db: &Connection, file: &str, auth: &mut Auth, img: DockerRef, e position: img.tag.unwrap(), content: new_tag, }); + metrics::get().images_updated.with_label_values(&[registry]).inc(); } } @@ -323,6 +348,7 @@ fn update_images(db: &Connection, file: &str, auth: &mut Auth, img: DockerRef, e position: img.digest.unwrap(), content: digest_string, }); + metrics::get().images_updated.with_label_values(&[registry]).inc(); } } @@ -596,9 +622,11 @@ fn run_tool(db: &Connection, auth: &mut Auth, repos: &Vec<Repository>, mut infil let exit = gitcmd.status() .expect("Git command failed"); if !exit.success() { + metrics::get().git_operations.with_label_values(&["commit", "failure"]).inc(); println!("Commit failed, presumably there were no changes"); continue; } + metrics::get().git_operations.with_label_values(&["commit", "success"]).inc(); let mut gitcmd = std::process::Command::new("git"); gitcmd @@ -615,8 +643,10 @@ fn run_tool(db: &Connection, auth: &mut Auth, repos: &Vec<Repository>, mut infil let exit = gitcmd.status() .expect("Git command failed"); if !exit.success() { + metrics::get().git_operations.with_label_values(&["push", "failure"]).inc(); panic!("Git exited with failure"); } + metrics::get().git_operations.with_label_values(&["push", "success"]).inc(); } } @@ -631,6 +661,7 @@ fn main() { let mut config_path = None; let mut scratch_path = None; let mut continuous = false; + let mut metrics_port: Option<u16> = None; loop { match it.next().map(|x| x.as_str()) { @@ -668,6 +699,22 @@ fn main() { Some("--continuous") => { continuous = true; }, + Some("--metrics-port") => { + if let Some(port_str) = it.next() { + match port_str.parse::<u16>() { + Ok(port) => metrics_port = Some(port), + Err(_) => { + println!("Error: --metrics-port requires a valid port number"); + help(cmd); + std::process::exit(1); + } + } + } else { + println!("Error: --metrics-port requires a parameter"); + help(cmd); + std::process::exit(1); + } + }, Some("-i") | Some("--inplace") => { overwrite = true; }, @@ -936,8 +983,22 @@ fn main() { let mut auth = Auth::new(auths); + metrics::init(); + metrics::get().state.set(metrics::STATE_IDLE); + if let Some(port) = metrics_port { + metrics::serve(port); + } + loop { + metrics::get().state.set(metrics::STATE_ACTIVE); + let run_start = std::time::Instant::now(); + run_tool(&db, &mut auth, &repos, infile_paths.clone(), overwrite); + + let run_duration = run_start.elapsed().as_secs_f64(); + metrics::get().run_duration.set(run_duration); + metrics::get().state.set(metrics::STATE_IDLE); + if !continuous { break; } println!("Waiting for next run"); |
