summaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs69
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");