diff --git a/Cargo.toml b/Cargo.toml index 5514ab6..6782e18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,13 @@ authors = ["Max Inden "] edition = "2021" description = "Open Metrics client library allowing users to natively instrument applications." license = "Apache-2.0 OR MIT" -keywords = ["openmetrics", "prometheus", "metrics", "instrumentation", "monitoring"] +keywords = [ + "openmetrics", + "prometheus", + "metrics", + "instrumentation", + "monitoring", +] repository = "https://github.com/prometheus/client_rust" homepage = "https://github.com/prometheus/client_rust" documentation = "https://docs.rs/prometheus-client" @@ -16,6 +22,7 @@ protobuf = ["dep:prost", "dep:prost-types", "dep:prost-build"] [workspace] members = ["derive-encode"] +exclude = ["process-collector"] [dependencies] dtoa = "1.0" @@ -35,7 +42,12 @@ quickcheck = "1" rand = "0.8.4" tide = "0.16" actix-web = "4" -tokio = { version = "1", features = ["rt-multi-thread", "net", "macros", "signal"] } +tokio = { version = "1", features = [ + "rt-multi-thread", + "net", + "macros", + "signal", +] } hyper = { version = "1.3.1", features = ["server", "http1"] } hyper-util = { version = "0.1.3", features = ["tokio"] } http-body-util = "0.1.1" diff --git a/process-collector/.gitignore b/process-collector/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/process-collector/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/process-collector/Cargo.toml b/process-collector/Cargo.toml new file mode 100644 index 0000000..c90952b --- /dev/null +++ b/process-collector/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "process-collector" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +procfs = "0.17.0" +prometheus-client = { path = "../" } diff --git a/process-collector/src/lib.rs b/process-collector/src/lib.rs new file mode 100644 index 0000000..becaf24 --- /dev/null +++ b/process-collector/src/lib.rs @@ -0,0 +1,62 @@ +use prometheus_client::{ + collector::Collector, + encoding::{DescriptorEncoder, EncodeMetric}, + metrics::gauge::ConstGauge, + registry::Unit, +}; +use std::time::{SystemTime, UNIX_EPOCH}; + +mod linux; + +#[derive(Debug)] +pub struct ProcessCollector { + namespace: Option, + #[cfg(target_os = "linux")] + system: linux::System, +} + +impl ProcessCollector { + pub fn new(namespace: Option) -> Self { + #[cfg(target_os = "linux")] + let system = linux::System {}; + + ProcessCollector { + namespace, + #[cfg(target_os = "linux")] + system, + } + } +} + +impl Collector for ProcessCollector { + fn encode(&self, mut encoder: DescriptorEncoder) -> Result<(), std::fmt::Error> { + let start_time_from_epoch = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|e| std::fmt::Error)?; + let start_time = ConstGauge::new(start_time_from_epoch.as_secs_f64()); + let start_time_metric = encoder.encode_descriptor( + "process_start_time_seconds", + "Start time of the process since unix epoch in seconds.", + Some(&Unit::Seconds), + start_time.metric_type(), + )?; + start_time.encode(start_time_metric)?; + + #[cfg(target_os = "linux")] + self.system.encode(encoder)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use prometheus_client::registry::Registry; + + #[test] + fn register_process_collector() { + let mut registry = Registry::default(); + registry.register_collector(Box::new(ProcessCollector::new(None))) + } +} diff --git a/process-collector/src/linux.rs b/process-collector/src/linux.rs new file mode 100644 index 0000000..3d31372 --- /dev/null +++ b/process-collector/src/linux.rs @@ -0,0 +1,101 @@ +use procfs::process::{LimitValue, Process}; +use prometheus_client::{ + collector::Collector, + encoding::EncodeMetric, + metrics::{counter::ConstCounter, gauge::ConstGauge}, + registry::Unit, +}; + +#[derive(Debug)] +pub(crate) struct System {} + +impl Collector for System { + fn encode( + &self, + mut encoder: prometheus_client::encoding::DescriptorEncoder, + ) -> Result<(), std::fmt::Error> { + let tps = procfs::ticks_per_second(); + + // TODO: handle errors + let proc = match Process::myself() { + Ok(proc) => proc, + Err(_) => { + return Ok(()); + } + }; + let stat = match proc.stat() { + Ok(stat) => stat, + Err(_) => { + return Ok(()); + } + }; + + let cpu_time = (stat.stime + stat.utime) / tps; + let counter = ConstCounter::new(cpu_time); + let metric_encoder = encoder.encode_descriptor( + "process_cpu_seconds_total", + "Total user and system CPU time spent in seconds.", + Some(&Unit::Seconds), + counter.metric_type(), + )?; + counter.encode(metric_encoder)?; + + if let Ok(limits) = proc.limits() { + let max_open_files = limits.max_open_files; + let max_fds = match max_open_files.soft_limit { + LimitValue::Unlimited => match max_open_files.hard_limit { + LimitValue::Unlimited => 0, + LimitValue::Value(hard) => hard, + }, + LimitValue::Value(soft) => soft, + }; + let gauge = ConstGauge::new(max_fds as i64); + let metric_encoder = encoder.encode_descriptor( + "process_max_fds", + "Maximum number of open file descriptors.", + None, + gauge.metric_type(), + )?; + gauge.encode(metric_encoder)?; + + let max_address_space = limits.max_address_space; + let max_virtual_memory = match max_address_space.soft_limit { + LimitValue::Unlimited => match max_address_space.hard_limit { + LimitValue::Unlimited => 0, + LimitValue::Value(hard) => hard, + }, + LimitValue::Value(soft) => soft, + }; + let gauge = ConstGauge::new(max_virtual_memory as i64); + let metric_encoder = encoder.encode_descriptor( + "process_virtual_memory_max_bytes", + "Maximum amount of virtual memory available in bytes.", + None, + gauge.metric_type(), + )?; + gauge.encode(metric_encoder)?; + } + + let vm_bytes = ConstGauge::new(stat.vsize as i64); + let vme = encoder.encode_descriptor( + "process_virtual_memory_bytes", + "Virtual memory size in bytes", + Some(&Unit::Bytes), + vm_bytes.metric_type(), + )?; + vm_bytes.encode(vme)?; + + // TODO: add rss_bytes (fix self.page_size) + // + // let rss_bytes = ConstGauge::new((stat.rss * self.page_size) as i64); + // let rsse = encoder.encode_descriptor( + // "process_resident_memory_bytes", + // "Resident memory size in bytes.", + // Some(&Unit::Bytes), + // rss_bytes.metric_type(), + // )?; + // rss_bytes.encode(rsse)?; + + Ok(()) + } +}