diff --git a/docs_src/tutorials/quickstart.md b/docs_src/tutorials/quickstart.md index ef54d010..909d8b27 100644 --- a/docs_src/tutorials/quickstart.md +++ b/docs_src/tutorials/quickstart.md @@ -12,6 +12,18 @@ Or if you downloaded or built a binary, you'd run: scaphandre stdout -t 15 +## Running scaphandre on Fedora / CentOS Stream / RHEL (or any distribution using SELinux) with podman + +Running scaphandre with podman on a distribution using SELinux may fail because of access denied to `/proc` files. + +To make it work you should run scaphandre in privileged mode : + + podman run --privileged ... + +You'll find explanation of this requirement here : [#106](https://github.com/hubblo-org/scaphandre/issues/106). + +## Output + Here we are using the stdout [exporter](../explanations/internal-structure.md) to print current power consumption usage in the terminal during 15 seconds. You should get an output like: @@ -34,6 +46,8 @@ Then you have the 5 processes consuming the most power during the last two measu If you don't get this output and get an error, jump to the [Troubleshooting](../troubleshooting.md) section of the documentation. +## Going further + At that point, you're ready to use scaphandre. The Stdout exporter is very basic and other exporters should allow you to use and send those metrics the way you like. The [prometheus exporter](references/exporter-prometheus.md), for example, allows you to expose power consumption metrics as an HTTP endpoint that can be scrapped by a [prometheus](https://prometheus.io) instance: diff --git a/src/exporters/prometheus.rs b/src/exporters/prometheus.rs index 5651e70f..c7163e22 100644 --- a/src/exporters/prometheus.rs +++ b/src/exporters/prometheus.rs @@ -11,6 +11,7 @@ use clap::{Arg, ArgMatches}; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Request, Response, Server}; use std::convert::Infallible; +use std::fmt::Write as _; use std::{ collections::HashMap, net::{IpAddr, SocketAddr}, @@ -194,12 +195,12 @@ fn format_metric(key: &str, value: &str, labels: Option<&HashMap if let Some(labels) = labels { result.push('{'); for (k, v) in labels.iter() { - result.push_str(&format!("{}=\"{}\",", k, v.replace('\"', "_"))); + let _ = write!(result, "{}=\"{}\",", k, v.replace('\"', "_")); } result.remove(result.len() - 1); result.push('}'); } - result.push_str(&format!(" {}\n", value)); + let _ = writeln!(result, " {}", value); result } @@ -210,9 +211,12 @@ fn push_metric( metric_type: String, metric_name: String, metric_line: String, + add_help: bool, ) -> String { - body.push_str(&format!("# HELP {} {}", metric_name, help)); - body.push_str(&format!("\n# TYPE {} {}\n", metric_name, metric_type)); + if add_help { + let _ = write!(body, "# HELP {} {}", metric_name, help); + let _ = write!(body, "\n# TYPE {} {}\n", metric_name, metric_type); + } body.push_str(&metric_line); body } @@ -249,6 +253,8 @@ async fn show_metrics( metric_generator.gen_all_metrics(); + let mut metrics_pushed: Vec = vec![]; + // Send all data for msg in metric_generator.pop_metrics() { let mut attributes: Option<&HashMap> = None; @@ -263,16 +269,26 @@ async fn show_metrics( MetricValueType::IntUnsigned(value) => value.to_string(), MetricValueType::Text(ref value) => value.to_string(), }; + + let mut should_i_add_help = true; + + if metrics_pushed.contains(&msg.name) { + should_i_add_help = false; + } else { + metrics_pushed.insert(0, msg.name.clone()); + } + body = push_metric( body, msg.description.clone(), msg.metric_type.clone(), msg.name.clone(), format_metric(&msg.name, &value, attributes), + should_i_add_help, ); } } else { - body.push_str(&format!("Scaphandre's prometheus exporter here. Metrics available on /{}", suffix, suffix)); + let _ = write!(body, "Scaphandre's prometheus exporter here. Metrics available on /{}", suffix, suffix); } Ok(Response::new(body.into())) } diff --git a/src/exporters/stdout.rs b/src/exporters/stdout.rs index 65666a6a..3eea64f9 100644 --- a/src/exporters/stdout.rs +++ b/src/exporters/stdout.rs @@ -4,6 +4,7 @@ use crate::exporters::*; use crate::sensors::Sensor; use colored::*; use regex::Regex; +use std::fmt::Write as _; use std::thread; use std::time::{Duration, Instant}; @@ -215,20 +216,20 @@ impl StdoutExporter { return true; } } - false - }) { - to_print.push_str(&format!( - "{} W\t", - current_domain - .metric_value - .to_string() - .parse::() - .unwrap() - / 1000000.0 - )); - } else { - to_print.push_str("---"); - } + false + }) { + let _ = write!( + to_print, + "{} W\t", + current_domain + .metric_value + .to_string() + .parse::() + .unwrap() + / 1000000.0 + ); + } else { + to_print.push_str("---"); } } diff --git a/src/sensors/utils.rs b/src/sensors/utils.rs index 95f1b3b8..4db15992 100644 --- a/src/sensors/utils.rs +++ b/src/sensors/utils.rs @@ -31,7 +31,7 @@ impl ProcessTracker { pub fn new(max_records_per_process: u16) -> ProcessTracker { let regex_cgroup_docker = Regex::new(r"^/docker/.*$").unwrap(); let regex_cgroup_kubernetes = Regex::new(r"^/kubepods.*$").unwrap(); - let regex_cgroup_containerd = Regex::new("/system.slice/containerd.service").unwrap(); + let regex_cgroup_containerd = Regex::new("/system.slice/containerd.service/.*$").unwrap(); ProcessTracker { procs: vec![], max_records_per_process, @@ -165,6 +165,9 @@ impl ProcessTracker { if container_id.ends_with(".scope") { container_id = container_id.strip_suffix(".scope").unwrap().to_string(); } + if container_id.contains("cri-containerd") { + container_id = container_id.split(':').last().unwrap().to_string(); + } Ok(container_id) } @@ -225,12 +228,20 @@ impl ProcessTracker { } } found = true; - } else if self.regex_cgroup_kubernetes.is_match(&cg.pathname) { - // kubernetes - description.insert( - String::from("container_scheduler"), - String::from("kubernetes"), - ); + } else { + // containerd + if self.regex_cgroup_containerd.is_match(&cg.pathname) { + description.insert( + String::from("container_runtime"), + String::from("containerd"), + ); + } else if self.regex_cgroup_kubernetes.is_match(&cg.pathname) { + // kubernetes not using containerd but we can get the container id + } else { + // cgroup not related to a container technology + continue; + } + let container_id = match self.extract_pod_id_from_cgroup_path(cg.pathname.clone()) { Ok(id) => id, @@ -273,6 +284,10 @@ impl ProcessTracker { } None => false, }) { + description.insert( + String::from("container_scheduler"), + String::from("kubernetes"), + ); if let Some(pod_name) = &pod.metadata.name { description .insert(String::from("kubernetes_pod_name"), pod_name.clone()); @@ -293,13 +308,6 @@ impl ProcessTracker { } } found = true; - } else if self.regex_cgroup_containerd.is_match(&cg.pathname) { - // containerd - description.insert( - String::from("container_runtime"), - String::from("containerd"), - ); - found = true; } //else { // debug!("Cgroup not identified as related to a container technology : {}", &cg.pathname); //}