1 // TODO: should really not unwrap everywhere here....
2 #![allow(clippy::unwrap_used)]
3 use actix_web::{rt::System, web, App, HttpResponse, HttpServer, Responder};
4 use lemmy_api_common::context::LemmyContext;
5 use lemmy_utils::settings::structs::PrometheusConfig;
6 use prometheus::{default_registry, Encoder, Gauge, Opts, TextEncoder};
8 net::{IpAddr, Ipv4Addr},
15 db_pool_metrics: DbPoolMetrics,
18 struct DbPoolMetrics {
24 static DEFAULT_BIND: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
25 static DEFAULT_PORT: i32 = 10002;
27 pub fn serve_prometheus(config: Option<&PrometheusConfig>, lemmy_context: LemmyContext) {
28 let context = Arc::new(PromContext {
30 db_pool_metrics: create_db_pool_metrics(),
33 let (bind, port) = match config {
35 config.bind.unwrap_or(DEFAULT_BIND),
36 config.port.unwrap_or(DEFAULT_PORT),
38 None => (DEFAULT_BIND, DEFAULT_PORT),
41 // spawn thread that blocks on handling requests
42 // only mapping /metrics to a handler
43 thread::spawn(move || {
44 let sys = System::new();
46 let server = HttpServer::new(move || {
48 .app_data(web::Data::new(Arc::clone(&context)))
49 .route("/metrics", web::get().to(metrics))
51 .bind((bind, port as u16))
52 .unwrap_or_else(|_| panic!("Cannot bind to {}:{}", bind, port))
55 if let Err(err) = server.await {
56 eprintln!("Prometheus server error: {}", err);
62 // handler for the /metrics path
63 async fn metrics(context: web::Data<Arc<PromContext>>) -> impl Responder {
65 collect_db_pool_metrics(&context).await;
67 let mut buffer = Vec::new();
68 let encoder = TextEncoder::new();
70 // gather metrics from registry and encode in prometheus format
71 let metric_families = prometheus::gather();
72 encoder.encode(&metric_families, &mut buffer).unwrap();
73 let output = String::from_utf8(buffer).unwrap();
75 HttpResponse::Ok().body(output)
78 // create lemmy_db_pool_* metrics and register them with the default registry
79 fn create_db_pool_metrics() -> DbPoolMetrics {
80 let metrics = DbPoolMetrics {
81 max_size: Gauge::with_opts(Opts::new(
82 "lemmy_db_pool_max_connections",
83 "Maximum number of connections in the pool",
86 size: Gauge::with_opts(Opts::new(
87 "lemmy_db_pool_connections",
88 "Current number of connections in the pool",
91 available: Gauge::with_opts(Opts::new(
92 "lemmy_db_pool_available_connections",
93 "Number of available connections in the pool",
99 .register(Box::new(metrics.max_size.clone()))
102 .register(Box::new(metrics.size.clone()))
105 .register(Box::new(metrics.available.clone()))
111 async fn collect_db_pool_metrics(context: &PromContext) {
112 let pool_status = context.lemmy.inner_pool().status();
116 .set(pool_status.max_size as f64);
117 context.db_pool_metrics.size.set(pool_status.size as f64);
121 .set(pool_status.available as f64);