aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs84
1 files changed, 3 insertions, 81 deletions
diff --git a/src/main.rs b/src/main.rs
index 6a6d61f..2bbfe3f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,18 +1,15 @@
use std::{collections::BTreeMap, sync::Arc, time::Duration};
use anyhow::Result;
-use bollard::{container::StatsOptions, Docker};
-use bollard::models::ContainerSummary;
-use opentelemetry::KeyValue;
+use bollard::Docker;
use config::CONFIG;
-use opentelemetry::metrics::MeterProvider;
use opentelemetry_otlp::{MetricExporter, Protocol, WithExportConfig};
use opentelemetry_sdk::metrics::SdkMeterProvider;
use tokio::task::JoinHandle;
-use tokio_stream::StreamExt;
use tokio_util::sync::CancellationToken;
mod config;
+mod stats_task;
fn setup_otlp() -> Result<SdkMeterProvider> {
let metric_exporter =
@@ -105,7 +102,7 @@ async fn main() -> Result<()> {
let id_string = cont.id.as_ref().unwrap();
if !tasks.contains_key(id_string) {
// all this string cloning hurts me
- tasks.insert(id_string.clone(), launch_stats_task(cont, docker.clone(), meter_provider.clone()));
+ tasks.insert(id_string.clone(), stats_task::launch_stats_task(cont, docker.clone(), meter_provider.clone()));
}
}
}
@@ -118,79 +115,4 @@ async fn main() -> Result<()> {
println!("clean shutdown.");
Ok(())
-}
-
-// I do not enjoy taking a bunch of Rcs but tokio needs ownership so fine.
-fn launch_stats_task(container: ContainerSummary, docker: Arc<Docker>, meter_provider: Arc<SdkMeterProvider>) -> JoinHandle<()> {
- tokio::spawn(async move {
- // extract some container info
- let container_id = container.id.unwrap();
- let container_name = container.names.iter().flatten().next().map(|n| n.trim_start_matches("/").to_owned());
-
- let mut stats_stream =
- docker.stats(container_id.as_str(), Some(StatsOptions {
- stream: true,
- one_shot: false
- }));
-
- // drop the first read
- loop {
- match stats_stream.next().await {
- None => return,
- Some(Ok(_)) => break,
- Some(Err(err)) => {
- // TODO: use json logging or syslog so loki can understand this lol
- println!("Failed to get stats for container {container_id}!: {err:?}");
- }
- }
- }
-
- // container labels shared for all metrics
- let mut shared_labels = vec![
- KeyValue::new("id", container_id.to_owned()),
- KeyValue::new("image", container.image.unwrap_or(container.image_id.unwrap()))
- ];
-
- if let Some(name) = container_name {
- shared_labels.push(KeyValue::new("name", name));
- }
-
- if let Some(docker_labels) = &container.labels {
- for (key, value) in docker_labels {
- shared_labels.push(KeyValue::new("container_label_".to_string() + key, value.clone()))
- }
- }
-
- // free space and make mutable
- shared_labels.shrink_to_fit();
- let shared_labels = &shared_labels[..];
-
- //println!("Starting reporting for container: {shared_labels:?}");
-
- // create meters
- let meter_container_cpu_usage_seconds_total = meter_provider.meter("test_meter").f64_counter("container_cpu_usage_seconds_total").with_unit("s").with_description("Cumulative cpu time consumed").build();
-
- while let Some(val) = stats_stream.next().await {
- if let Ok(stats) = val {
- meter_container_cpu_usage_seconds_total.add(
- cpu_delta_from_docker(stats.cpu_stats.cpu_usage.total_usage, stats.precpu_stats.cpu_usage.total_usage).as_secs_f64(),
- shared_labels);
- }
- else {
- // failed to get stats, log as such:
- // TODO: use json logging or syslog so loki can understand this lol
- println!("Failed to get stats for container {container_id}!: {:?}", val.unwrap_err());
- }
- }
- })
-}
-
-fn cpu_delta_from_docker(cpu_usage: u64, precpu_usage: u64) -> Duration {
- let delta = cpu_usage - precpu_usage;
-
- // https://docs.docker.com/reference/api/engine/version/v1.48/#tag/Container/operation/ContainerStats
- // see response schema > cpu_stats > cpu_usage > total_usage
- let delta_ns = if cfg!(windows) { delta * 100 } else { delta };
-
- Duration::from_nanos(delta_ns)
} \ No newline at end of file