diff --git a/src/servers/apis/v1/context/stats/handlers.rs b/src/servers/apis/v1/context/stats/handlers.rs index c3be5dc7a..8b11b1ff1 100644 --- a/src/servers/apis/v1/context/stats/handlers.rs +++ b/src/servers/apis/v1/context/stats/handlers.rs @@ -3,19 +3,46 @@ use std::sync::Arc; use axum::extract::State; -use axum::response::Json; +use axum::response::Response; +use axum_extra::extract::Query; +use serde::Deserialize; -use super::resources::Stats; -use super::responses::stats_response; +use super::responses::{metrics_response, stats_response}; use crate::core::services::statistics::get_metrics; use crate::core::Tracker; +#[derive(Deserialize, Debug, Default)] +#[serde(rename_all = "lowercase")] +pub enum Format { + #[default] + Json, + Prometheus, +} + +#[derive(Deserialize, Debug)] +pub struct QueryParams { + /// The [`Format`] of the stats. + #[serde(default)] + pub format: Option, +} + /// It handles the request to get the tracker statistics. /// -/// It returns a `200` response with a json [`Stats`] +/// By default it returns a `200` response with the stats in JSON format. +/// +/// You can add the GET parameter `format=prometheus` to get the stats in +/// Prometheus Text Exposition Format. /// /// Refer to the [API endpoint documentation](crate::servers::apis::v1::context::stats#get-tracker-statistics) /// for more information about this endpoint. -pub async fn get_stats_handler(State(tracker): State>) -> Json { - stats_response(get_metrics(tracker.clone()).await) +pub async fn get_stats_handler(State(tracker): State>, params: Query) -> Response { + let metrics = get_metrics(tracker.clone()).await; + + match params.0.format { + Some(format) => match format { + Format::Json => stats_response(metrics), + Format::Prometheus => metrics_response(&metrics), + }, + None => stats_response(metrics), + } } diff --git a/src/servers/apis/v1/context/stats/responses.rs b/src/servers/apis/v1/context/stats/responses.rs index 9d03ccedf..4fd8be94f 100644 --- a/src/servers/apis/v1/context/stats/responses.rs +++ b/src/servers/apis/v1/context/stats/responses.rs @@ -1,11 +1,86 @@ //! API responses for the [`stats`](crate::servers::apis::v1::context::stats) //! API context. -use axum::response::Json; +use axum::response::{IntoResponse, Json, Response}; use super::resources::Stats; use crate::core::services::statistics::TrackerMetrics; /// `200` response that contains the [`Stats`] resource as json. -pub fn stats_response(tracker_metrics: TrackerMetrics) -> Json { - Json(Stats::from(tracker_metrics)) +#[must_use] +pub fn stats_response(tracker_metrics: TrackerMetrics) -> Response { + Json(Stats::from(tracker_metrics)).into_response() +} + +/// `200` response that contains the [`Stats`] resource in Prometheus Text Exposition Format . +#[must_use] +pub fn metrics_response(tracker_metrics: &TrackerMetrics) -> Response { + let mut lines = vec![]; + + lines.push(format!("torrents {}", tracker_metrics.torrents_metrics.torrents)); + lines.push(format!("seeders {}", tracker_metrics.torrents_metrics.complete)); + lines.push(format!("completed {}", tracker_metrics.torrents_metrics.downloaded)); + lines.push(format!("leechers {}", tracker_metrics.torrents_metrics.incomplete)); + + lines.push(format!( + "tcp4_connections_handled {}", + tracker_metrics.protocol_metrics.tcp4_connections_handled + )); + lines.push(format!( + "tcp4_announces_handled {}", + tracker_metrics.protocol_metrics.tcp4_announces_handled + )); + lines.push(format!( + "tcp4_scrapes_handled {}", + tracker_metrics.protocol_metrics.tcp4_scrapes_handled + )); + + lines.push(format!( + "tcp6_connections_handled {}", + tracker_metrics.protocol_metrics.tcp6_connections_handled + )); + lines.push(format!( + "tcp6_announces_handled {}", + tracker_metrics.protocol_metrics.tcp6_announces_handled + )); + lines.push(format!( + "tcp6_scrapes_handled {}", + tracker_metrics.protocol_metrics.tcp6_scrapes_handled + )); + + lines.push(format!( + "udp4_connections_handled {}", + tracker_metrics.protocol_metrics.udp4_connections_handled + )); + lines.push(format!( + "udp4_announces_handled {}", + tracker_metrics.protocol_metrics.udp4_announces_handled + )); + lines.push(format!( + "udp4_scrapes_handled {}", + tracker_metrics.protocol_metrics.udp4_scrapes_handled + )); + lines.push(format!( + "udp4_errors_handled {}", + tracker_metrics.protocol_metrics.udp4_errors_handled + )); + + lines.push(format!( + "udp6_connections_handled {}", + tracker_metrics.protocol_metrics.udp6_connections_handled + )); + lines.push(format!( + "udp6_announces_handled {}", + tracker_metrics.protocol_metrics.udp6_announces_handled + )); + lines.push(format!( + "udp6_scrapes_handled {}", + tracker_metrics.protocol_metrics.udp6_scrapes_handled + )); + lines.push(format!( + "udp6_errors_handled {}", + tracker_metrics.protocol_metrics.udp6_errors_handled + )); + + // Return the plain text response + lines.join("\n").into_response() }