commit 5b86785c2b12f0b75ad5c581cd886db87c9b5977 Author: Olivier 'reivilibre Date: Wed Mar 23 06:55:02 2022 +0000 Initial version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a53b1c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +Cargo.lock + +.idea \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8f9d77d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "metrics-process-promstyle" +authors = ["Olivier 'reivilibre'"] +# +version = "0.18.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +metrics = "0.18.1" +procfs = "0.12.0" + +[dev-dependencies] +metrics-exporter-prometheus = "0.9.0" +tokio = { version = "1.17.0", features = ["macros", "rt-multi-thread"] } diff --git a/README.md b/README.md new file mode 100644 index 0000000..e6fd4ba --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# `metrics-process-promstyle`: Prometheus-style process metrics for the `metrics` façade + +This crate provides Prometheus-style ['process metrics'][prom_process_metrics] for the backend-agnostic +[`metrics`][metrics_crate] façade. + +Although these metrics use the conventional Prometheus names, they can be used with any `metrics`-compatible +exporter! + +Process metrics are the following useful statistics: + +- CPU time +- memory usage +- start time of the process +- number of threads + +[prom_process_metrics]: https://prometheus.io/docs/instrumenting/writing_clientlibs/#process-metrics +[metrics_crate]: https://github.com/metrics-rs/metrics + +Unimplemented process metrics: + +- `process_open_fds` +- `process_max_fds` +- `process_virtual_memory_max_bytes` +- `process_heap_bytes` + + +## Usage + +Once your metrics exporter is set up, you can describe the process metrics using `describe()`. + +The `metrics` crate only provides for 'push'-style metrics; there's no way right now to emit statistics on demand. +For that reason, this crate exposes a function called `emit_now()` which, as the name implies, emits the statistics when called. +You'll need to call this occasionally (from a background thread or Tokio task etc, if necessary) to update the metrics. + +(One day, it should be as easy as enabling a feature in this crate and calling the necessary function to set up a timer for you.) + +See the `examples` directory for an example. + + +## Licence + +See `LICENCE`: it's the MIT Licence. + + +## Versioning + +For simplicity's sake, this crate will follow the major version number of `metrics`, so it's easy to tell that compatible versions are being used. +This means that version `0.18.x` of this crate will be compatible with `metrics` `0.18.x`, and a hypothetical version `1.x.x` of this crate will be compatible with `metrics` `1.x.x`. + + +## Contributions + +If you feel like you want to make a contribution, I would be very happy to accept. diff --git a/examples/simple.rs b/examples/simple.rs new file mode 100644 index 0000000..9bdce30 --- /dev/null +++ b/examples/simple.rs @@ -0,0 +1,29 @@ +use std::net::SocketAddr; +use std::str::FromStr; +use std::time::Duration; + +/// Demo for process-style metrics. +/// Open up http://127.0.0.1:3333 whilst it's running to see! +/// We must use Tokio as the Prometheus exporter requires to be called in a Tokio context... +/// (this seems like a bug in `metrics-exporter-prometheus`...?). +#[tokio::main] +pub async fn main() { + metrics_exporter_prometheus::PrometheusBuilder::new() + .with_http_listener(SocketAddr::from_str("127.0.0.1:3333").unwrap()) + .install() + .expect("Can't build Prometheus exporter"); + + // Describes the process metrics. + metrics_process_promstyle::describe(); + + std::thread::spawn(|| { + // 5 seconds is maybe too fast in real-life use. + std::thread::sleep(Duration::from_secs(5)); + + // Emits the process metrics. + metrics_process_promstyle::emit_now().expect("Failed to emit process metrics"); + }); + + // Main program: sleep 2 minutes. + std::thread::sleep(Duration::from_secs(120)); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..4757234 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,75 @@ +use metrics::{describe_gauge, gauge, Unit}; +use procfs::process::Process; +use procfs::{page_size, ticks_per_second, ProcResult}; + +pub fn describe() { + describe_gauge!( + "process_cpu_seconds_total", + Unit::Seconds, + "Total user and system CPU time spent in seconds." + ); + // TODO(feature): Getting the number of open file descriptors involves extra work + // and I don't care *that* much about the metric for now anyway. + // describe_gauge!("process_open_fds", Unit::Count, "Number of open file descriptors."); + // TODO(feature): Getting the max number of file descriptors involves reading the limits file, + // which is easy enough to implement but I don't care about the limit if there's no count of + // open FDs (and may as well reduce the amount of work the collector needs to do). + // describe_gauge!("process_max_fds", Unit::Count, "Maximum number of open file descriptors."); + describe_gauge!( + "process_virtual_memory_bytes", + Unit::Bytes, + "Virtual memory size in bytes." + ); + // TODO(feature): Couldn't immediately see where to get this one, and I don't usually rely on it. + // describe_gauge!("process_virtual_memory_max_bytes", Unit::Bytes, "Maximum amount of virtual memory available in bytes."); + describe_gauge!( + "process_resident_memory_bytes", + Unit::Bytes, + "Resident memory size in bytes." + ); + // TODO(feature): counting heap bytes presumably needs access to the allocator. + // I can see that breaking depending on which allocator is in use, so won't bother for now. + // describe_gauge!("process_heap_bytes", Unit::Bytes, "Process heap size in bytes."); + describe_gauge!( + "process_start_time_seconds", + Unit::Seconds, + "Start time of the process since unix epoch in seconds." + ); + describe_gauge!( + "process_threads", + Unit::Count, + "Number of OS threads in the process." + ); +} + +pub fn emit_now() -> ProcResult<()> { + let boot_time_secs = procfs::boot_time_secs()?; + + let this_process = Process::myself()?; + + let total_time_ticks = this_process.stat.utime + this_process.stat.stime; + let ticks_per_second = ticks_per_second()?; + + let rss_bytes = this_process.stat.rss * page_size()?; + + let start_time_secs = + (this_process.stat.starttime as f64) / (ticks_per_second as f64) + boot_time_secs as f64; + let total_time_secs = (total_time_ticks as f64) / (ticks_per_second as f64); + + let num_threads = this_process.stat.num_threads; + + gauge!("process_cpu_seconds_total", total_time_secs); + // gauge!("process_open_fds", ); + // gauge!("process_max_fds", ); + gauge!( + "process_virtual_memory_bytes", + this_process.stat.vsize as f64 + ); + // gauge!("process_virtual_memory_max_bytes", ); + gauge!("process_resident_memory_bytes", rss_bytes as f64); + // gauge!("process_heap_bytes", ); + gauge!("process_start_time_seconds", start_time_secs); + gauge!("process_threads", num_threads as f64); + + Ok(()) +}