use prometheus::{
Encoder, HistogramOpts, HistogramVec, IntCounter, IntCounterVec, IntGauge, Opts, Registry,
TextEncoder,
};
const DURATION_BUCKETS: &[f64] = &[
0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0, 120.0, 300.0,
];
pub struct Metrics {
pub registry: Registry,
requests: IntCounterVec,
upstream: IntCounterVec,
cache_bytes: IntGauge,
cache_mirrors: IntGauge,
evictions: IntCounter,
upstream_duration: HistogramVec,
serve_duration: HistogramVec,
}
impl Metrics {
pub fn new() -> Self {
let registry = Registry::new();
let requests = IntCounterVec::new(
Opts::new("gitcacheproxy_requests_total", "Git requests served"),
&["kind", "result", "repo"],
)
.expect("valid metric");
let upstream = IntCounterVec::new(
Opts::new(
"gitcacheproxy_upstream_ops_total",
"Upstream clone/fetch operations",
),
&["op", "result", "repo"],
)
.expect("valid metric");
let cache_bytes = IntGauge::new(
"gitcacheproxy_cache_bytes",
"Total size of the on-disk mirror cache in bytes",
)
.expect("valid metric");
let cache_mirrors = IntGauge::new(
"gitcacheproxy_cache_mirrors",
"Number of cached mirrors on disk",
)
.expect("valid metric");
let evictions = IntCounter::new(
"gitcacheproxy_evictions_total",
"Idle mirrors evicted to keep the cache under the configured cap",
)
.expect("valid metric");
let upstream_duration = HistogramVec::new(
HistogramOpts::new(
"gitcacheproxy_upstream_duration_seconds",
"Upstream clone/fetch duration in seconds",
)
.buckets(DURATION_BUCKETS.to_vec()),
&["op", "repo"],
)
.expect("valid metric");
let serve_duration = HistogramVec::new(
HistogramOpts::new(
"gitcacheproxy_serve_duration_seconds",
"Client serve duration in seconds (info/refs advertisement, upload-pack stream)",
)
.buckets(DURATION_BUCKETS.to_vec()),
&["kind", "repo"],
)
.expect("valid metric");
registry
.register(Box::new(requests.clone()))
.expect("register requests");
registry
.register(Box::new(upstream.clone()))
.expect("register upstream");
registry
.register(Box::new(cache_bytes.clone()))
.expect("register cache_bytes");
registry
.register(Box::new(cache_mirrors.clone()))
.expect("register cache_mirrors");
registry
.register(Box::new(evictions.clone()))
.expect("register evictions");
registry
.register(Box::new(upstream_duration.clone()))
.expect("register upstream_duration");
registry
.register(Box::new(serve_duration.clone()))
.expect("register serve_duration");
Self {
registry,
requests,
upstream,
cache_bytes,
cache_mirrors,
evictions,
upstream_duration,
serve_duration,
}
}
pub fn record_request(&self, kind: &str, result: &str, repo: &str) {
self.requests.with_label_values(&[kind, result, repo]).inc();
}
pub fn record_upstream(&self, op: &str, result: &str, repo: &str) {
self.upstream.with_label_values(&[op, result, repo]).inc();
}
pub fn set_cache_size(&self, bytes: u64, mirrors: usize) {
self.cache_bytes.set(bytes as i64);
self.cache_mirrors.set(mirrors as i64);
}
pub fn record_eviction(&self) {
self.evictions.inc();
}
pub fn observe_upstream(&self, op: &str, repo: &str, seconds: f64) {
self.upstream_duration
.with_label_values(&[op, repo])
.observe(seconds);
}
pub fn observe_serve(&self, kind: &str, repo: &str, seconds: f64) {
self.serve_duration
.with_label_values(&[kind, repo])
.observe(seconds);
}
pub fn gather(&self) -> String {
let mut buf = Vec::new();
let enc = TextEncoder::new();
let _ = enc.encode(&self.registry.gather(), &mut buf);
String::from_utf8_lossy(&buf).into_owned()
}
}
impl Default for Metrics {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gather_renders_recorded_series() {
let m = Metrics::new();
m.record_request("info_refs", "ok", "group/foo.git");
m.record_request("upload_pack", "error", "group/bar.git");
m.record_upstream("fetch", "ok", "group/foo.git");
m.record_upstream("clone", "error", "group/bar.git");
m.set_cache_size(2048, 3);
m.record_eviction();
m.record_eviction();
m.observe_upstream("clone", "group/foo.git", 1.5);
m.observe_serve("upload_pack", "group/foo.git", 2.0);
let out = m.gather();
assert!(out.contains("gitcacheproxy_cache_bytes 2048"));
assert!(out.contains("gitcacheproxy_cache_mirrors 3"));
assert!(out.contains("gitcacheproxy_evictions_total 2"));
assert!(out.contains(
r#"gitcacheproxy_upstream_duration_seconds_count{op="clone",repo="group/foo.git"} 1"#
));
assert!(out.contains(
r#"gitcacheproxy_serve_duration_seconds_count{kind="upload_pack",repo="group/foo.git"} 1"#
));
assert!(out.contains(
r#"gitcacheproxy_requests_total{kind="info_refs",repo="group/foo.git",result="ok"} 1"#
));
assert!(out.contains(
r#"gitcacheproxy_requests_total{kind="upload_pack",repo="group/bar.git",result="error"} 1"#
));
assert!(out.contains(r#"op="fetch",repo="group/foo.git",result="ok"#));
assert!(out.contains(r#"op="clone",repo="group/bar.git",result="error"#));
}
}