Initial version

This commit is contained in:
Olivier 'reivilibre' 2022-03-23 06:55:02 +00:00
commit 5b86785c2b
5 changed files with 177 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target
Cargo.lock
.idea

16
Cargo.toml Normal file
View File

@ -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"] }

53
README.md Normal file
View File

@ -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.

29
examples/simple.rs Normal file
View File

@ -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));
}

75
src/lib.rs Normal file
View File

@ -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(())
}