Skip to content

Commit 4b5fd1b

Browse files
committed
feat: [torrust#1403] POC. New API endpoint with extendable metrics
This is only a Prood of Concept. It adds a new REST API endpoint with a new format for metrics: URL: http://0.0.0.0:1212/api/v1/metrics?token=MyAccessToken Sample response: ```json { "labeled_metrics": [ { "metric": { "name": "announce_requests_received_total", "kind": "counter", "value": 1 }, "labels": { "ip_version": "ipv4", "protocol": "http", "url": "http://0.0.0.0:7070" } }, { "metric": { "name": "scrape_requests_received_total", "kind": "counter", "value": 1 }, "labels": { "ip_version": "ipv4", "protocol": "http", "url": "http://0.0.0.0:7070" } } ] } ``` It only supports JSON format for now. It doesn't support prometheus format like the current stats endpoint: URL: http://0.0.0.0:1212/api/v1/stats?token=MyAccessToken&format=prometheus TODO: - Replace primitive types in the new labeled metrics with new tpyes to enforce constraints. For example, metrics names, labels, metric types, etc. - Inject the rigth URL scheme. It's hardcoded now. - Implement for UDP metrics. It's only implemented for HTTP tracker metrics. This would require mergin labeled metrics. - Use a f64 for metric values instead of u64. - The metric name and label set pair must be unique in the array of labeled metrics. Enforce constraint. - Implement versioning. A given API version must contain a set of labeled metrics. Clients expects some labeled metrics to be included in a API version. We need to initialize the array of metrics with all the expected labeled metrics with the initial value.
1 parent 326e577 commit 4b5fd1b

File tree

12 files changed

+400
-37
lines changed

12 files changed

+400
-37
lines changed

Cargo.lock

Lines changed: 124 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cSpell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"Eray",
6060
"filesd",
6161
"flamegraph",
62+
"formatjson",
6263
"Freebox",
6364
"Frostegård",
6465
"gecos",

packages/axum-rest-tracker-api-server/src/v1/context/stats/handlers.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ use bittorrent_tracker_core::torrent::repository::in_memory::InMemoryTorrentRepo
99
use bittorrent_udp_tracker_core::services::banning::BanService;
1010
use serde::Deserialize;
1111
use tokio::sync::RwLock;
12-
use torrust_rest_tracker_api_core::statistics::services::get_metrics;
12+
use torrust_rest_tracker_api_core::statistics::services::{get_labeled_metrics, get_metrics};
1313

14-
use super::responses::{metrics_response, stats_response};
14+
use super::responses::{labeled_stats_response, metrics_response, stats_response};
1515

1616
#[derive(Deserialize, Debug, Default)]
1717
#[serde(rename_all = "lowercase")]
@@ -57,3 +57,24 @@ pub async fn get_stats_handler(
5757
None => stats_response(metrics),
5858
}
5959
}
60+
61+
#[allow(clippy::type_complexity)]
62+
pub async fn get_metrics_handler(
63+
State(state): State<(
64+
Arc<InMemoryTorrentRepository>,
65+
Arc<RwLock<BanService>>,
66+
Arc<bittorrent_http_tracker_core::statistics::repository::Repository>,
67+
Arc<torrust_udp_tracker_server::statistics::repository::Repository>,
68+
)>,
69+
params: Query<QueryParams>,
70+
) -> Response {
71+
let metrics = get_labeled_metrics(state.0.clone(), state.1.clone(), state.2.clone(), state.3.clone()).await;
72+
73+
match params.0.format {
74+
Some(format) => match format {
75+
Format::Json => labeled_stats_response(metrics),
76+
Format::Prometheus => todo!(),
77+
},
78+
None => labeled_stats_response(metrics),
79+
}
80+
}

packages/axum-rest-tracker-api-server/src/v1/context/stats/resources.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
//! API resources for the [`stats`](crate::v1::context::stats)
22
//! API context.
3+
use bittorrent_http_tracker_core::statistics::metrics::LabeledMetric;
34
use serde::{Deserialize, Serialize};
4-
use torrust_rest_tracker_api_core::statistics::services::TrackerMetrics;
5+
use torrust_rest_tracker_api_core::statistics::services::{TrackerLabeledMetrics, TrackerMetrics};
56

67
/// It contains all the statistics generated by the tracker.
78
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
@@ -116,6 +117,22 @@ impl From<TrackerMetrics> for Stats {
116117
}
117118
}
118119

120+
/// It contains all the statistics generated by the tracker.
121+
#[derive(Serialize, Debug, PartialEq, Eq)]
122+
pub struct LabeledStats {
123+
// Extendable metrics
124+
labeled_metrics: Vec<LabeledMetric>,
125+
}
126+
127+
impl From<TrackerLabeledMetrics> for LabeledStats {
128+
#[allow(deprecated)]
129+
fn from(metrics: TrackerLabeledMetrics) -> Self {
130+
Self {
131+
labeled_metrics: metrics.labeled_metrics,
132+
}
133+
}
134+
}
135+
119136
#[cfg(test)]
120137
mod tests {
121138
use torrust_rest_tracker_api_core::statistics::metrics::Metrics;

packages/axum-rest-tracker-api-server/src/v1/context/stats/responses.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
//! API responses for the [`stats`](crate::v1::context::stats)
22
//! API context.
33
use axum::response::{IntoResponse, Json, Response};
4-
use torrust_rest_tracker_api_core::statistics::services::TrackerMetrics;
4+
use torrust_rest_tracker_api_core::statistics::services::{TrackerLabeledMetrics, TrackerMetrics};
55

6-
use super::resources::Stats;
6+
use super::resources::{LabeledStats, Stats};
77

88
/// `200` response that contains the [`Stats`] resource as json.
99
#[must_use]
1010
pub fn stats_response(tracker_metrics: TrackerMetrics) -> Response {
1111
Json(Stats::from(tracker_metrics)).into_response()
1212
}
1313

14+
/// `200` response that contains the [`LabeledStats`] resource as json.
15+
#[must_use]
16+
pub fn labeled_stats_response(tracker_metrics: TrackerLabeledMetrics) -> Response {
17+
Json(LabeledStats::from(tracker_metrics)).into_response()
18+
}
19+
1420
/// `200` response that contains the [`Stats`] resource in Prometheus Text Exposition Format .
1521
#[allow(deprecated)]
1622
#[must_use]

packages/axum-rest-tracker-api-server/src/v1/context/stats/routes.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,27 @@ use axum::routing::get;
99
use axum::Router;
1010
use torrust_rest_tracker_api_core::container::TrackerHttpApiCoreContainer;
1111

12-
use super::handlers::get_stats_handler;
12+
use super::handlers::{get_metrics_handler, get_stats_handler};
1313

1414
/// It adds the routes to the router for the [`stats`](crate::v1::context::stats) API context.
1515
pub fn add(prefix: &str, router: Router, http_api_container: &Arc<TrackerHttpApiCoreContainer>) -> Router {
16-
router.route(
17-
&format!("{prefix}/stats"),
18-
get(get_stats_handler).with_state((
19-
http_api_container.tracker_core_container.in_memory_torrent_repository.clone(),
20-
http_api_container.ban_service.clone(),
21-
http_api_container.http_stats_repository.clone(),
22-
http_api_container.udp_server_stats_repository.clone(),
23-
)),
24-
)
16+
router
17+
.route(
18+
&format!("{prefix}/stats"),
19+
get(get_stats_handler).with_state((
20+
http_api_container.tracker_core_container.in_memory_torrent_repository.clone(),
21+
http_api_container.ban_service.clone(),
22+
http_api_container.http_stats_repository.clone(),
23+
http_api_container.udp_server_stats_repository.clone(),
24+
)),
25+
)
26+
.route(
27+
&format!("{prefix}/metrics"),
28+
get(get_metrics_handler).with_state((
29+
http_api_container.tracker_core_container.in_memory_torrent_repository.clone(),
30+
http_api_container.ban_service.clone(),
31+
http_api_container.http_stats_repository.clone(),
32+
http_api_container.udp_server_stats_repository.clone(),
33+
)),
34+
)
2535
}

packages/http-tracker-core/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,17 @@ bittorrent-primitives = "0.1.0"
2020
bittorrent-tracker-core = { version = "3.0.0-develop", path = "../tracker-core" }
2121
criterion = { version = "0.5.1", features = ["async_tokio"] }
2222
futures = "0"
23+
serde = "1.0.219"
2324
thiserror = "2"
2425
tokio = { version = "1", features = ["macros", "net", "rt-multi-thread", "signal", "sync"] }
2526
torrust-tracker-configuration = { version = "3.0.0-develop", path = "../configuration" }
2627
torrust-tracker-primitives = { version = "3.0.0-develop", path = "../primitives" }
2728
tracing = "0"
2829

2930
[dev-dependencies]
31+
formatjson = "0.3.1"
3032
mockall = "0"
33+
serde_json = "1.0.140"
3134
torrust-tracker-test-helpers = { version = "3.0.0-develop", path = "../test-helpers" }
3235

3336
[[bench]]

0 commit comments

Comments
 (0)