use prometheus::{Encoder, IntCounter, IntCounterVec, IntGauge, Opts, Registry, TextEncoder};
pub struct Metrics {
pub registry: Registry,
requests: IntCounterVec,
upstream: IntCounterVec,
cache_bytes: IntGauge,
cache_mirrors: IntGauge,
evictions: IntCounter,
}
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");
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");
Self {
registry,
requests,
upstream,
cache_bytes,
cache_mirrors,
evictions,
}
}
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 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();
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_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"#));
}
}