diff --git a/Cargo.lock b/Cargo.lock index 5415149e8..96de11cb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -678,6 +678,7 @@ dependencies = [ "torrust-tracker-configuration", "torrust-tracker-events", "torrust-tracker-located-error", + "torrust-tracker-metrics", "torrust-tracker-primitives", "torrust-tracker-test-helpers", "torrust-tracker-torrent-repository", diff --git a/packages/axum-rest-tracker-api-server/src/v1/context/stats/handlers.rs b/packages/axum-rest-tracker-api-server/src/v1/context/stats/handlers.rs index 552958d74..3a353f1fc 100644 --- a/packages/axum-rest-tracker-api-server/src/v1/context/stats/handlers.rs +++ b/packages/axum-rest-tracker-api-server/src/v1/context/stats/handlers.rs @@ -70,6 +70,7 @@ pub async fn get_metrics_handler( Arc, Arc>, Arc, + Arc, Arc, Arc, Arc, @@ -83,6 +84,7 @@ pub async fn get_metrics_handler( state.3.clone(), state.4.clone(), state.5.clone(), + state.6.clone(), ) .await; diff --git a/packages/axum-rest-tracker-api-server/src/v1/context/stats/routes.rs b/packages/axum-rest-tracker-api-server/src/v1/context/stats/routes.rs index 3eeaa8bf4..f6c661130 100644 --- a/packages/axum-rest-tracker-api-server/src/v1/context/stats/routes.rs +++ b/packages/axum-rest-tracker-api-server/src/v1/context/stats/routes.rs @@ -28,7 +28,9 @@ pub fn add(prefix: &str, router: Router, http_api_container: &Arc, ban_service: Arc>, swarms_stats_repository: Arc, + tracker_core_stats_repository: Arc, http_stats_repository: Arc, udp_stats_repository: Arc, udp_server_stats_repository: Arc, @@ -102,6 +103,7 @@ pub async fn get_labeled_metrics( let _udp_banned_ips_total = ban_service.read().await.get_banned_ips_total(); let swarms_stats = swarms_stats_repository.get_metrics().await; + let tracker_core_stats = tracker_core_stats_repository.get_metrics().await; let http_stats = http_stats_repository.get_stats().await; let udp_stats_repository = udp_stats_repository.get_stats().await; let udp_server_stats = udp_server_stats_repository.get_stats().await; @@ -112,6 +114,9 @@ pub async fn get_labeled_metrics( metrics .merge(&swarms_stats.metric_collection) .expect("msg: failed to merge torrent repository metrics"); + metrics + .merge(&tracker_core_stats.metric_collection) + .expect("msg: failed to merge tracker core metrics"); metrics .merge(&http_stats.metric_collection) .expect("msg: failed to merge HTTP core metrics"); diff --git a/packages/tracker-client/src/http/client/requests/announce.rs b/packages/tracker-client/src/http/client/requests/announce.rs index 7d20fbba8..29b5d1221 100644 --- a/packages/tracker-client/src/http/client/requests/announce.rs +++ b/packages/tracker-client/src/http/client/requests/announce.rs @@ -53,16 +53,16 @@ pub type BaseTenASCII = u64; pub type PortNumber = u16; pub enum Event { - //Started, - //Stopped, + Started, + Stopped, Completed, } impl fmt::Display for Event { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - //Event::Started => write!(f, "started"), - //Event::Stopped => write!(f, "stopped"), + Event::Started => write!(f, "started"), + Event::Stopped => write!(f, "stopped"), Event::Completed => write!(f, "completed"), } } diff --git a/packages/tracker-core/Cargo.toml b/packages/tracker-core/Cargo.toml index 3c89505b2..a2d08dfa0 100644 --- a/packages/tracker-core/Cargo.toml +++ b/packages/tracker-core/Cargo.toml @@ -31,6 +31,7 @@ torrust-tracker-clock = { version = "3.0.0-develop", path = "../clock" } torrust-tracker-configuration = { version = "3.0.0-develop", path = "../configuration" } torrust-tracker-events = { version = "3.0.0-develop", path = "../events" } torrust-tracker-located-error = { version = "3.0.0-develop", path = "../located-error" } +torrust-tracker-metrics = { version = "3.0.0-develop", path = "../metrics" } torrust-tracker-primitives = { version = "3.0.0-develop", path = "../primitives" } torrust-tracker-torrent-repository = { version = "3.0.0-develop", path = "../torrent-repository" } tracing = "0" diff --git a/packages/tracker-core/src/container.rs b/packages/tracker-core/src/container.rs index f4fb272de..ed56fb106 100644 --- a/packages/tracker-core/src/container.rs +++ b/packages/tracker-core/src/container.rs @@ -14,11 +14,11 @@ use crate::scrape_handler::ScrapeHandler; use crate::torrent::manager::TorrentsManager; use crate::torrent::repository::in_memory::InMemoryTorrentRepository; use crate::torrent::repository::persisted::DatabasePersistentTorrentRepository; -use crate::whitelist; use crate::whitelist::authorization::WhitelistAuthorization; use crate::whitelist::manager::WhitelistManager; use crate::whitelist::repository::in_memory::InMemoryWhitelist; use crate::whitelist::setup::initialize_whitelist_manager; +use crate::{statistics, whitelist}; pub struct TrackerCoreContainer { pub core_config: Arc, @@ -33,6 +33,7 @@ pub struct TrackerCoreContainer { pub in_memory_torrent_repository: Arc, pub db_torrent_repository: Arc, pub torrents_manager: Arc, + pub stats_repository: Arc, } impl TrackerCoreContainer { @@ -58,6 +59,8 @@ impl TrackerCoreContainer { &db_torrent_repository, )); + let stats_repository = Arc::new(statistics::repository::Repository::new()); + let announce_handler = Arc::new(AnnounceHandler::new( core_config, &whitelist_authorization, @@ -80,6 +83,7 @@ impl TrackerCoreContainer { in_memory_torrent_repository, db_torrent_repository, torrents_manager, + stats_repository, } } } diff --git a/packages/tracker-core/src/statistics/event/handler.rs b/packages/tracker-core/src/statistics/event/handler.rs index 7b6ce83b7..ac6d0639e 100644 --- a/packages/tracker-core/src/statistics/event/handler.rs +++ b/packages/tracker-core/src/statistics/event/handler.rs @@ -1,14 +1,19 @@ use std::sync::Arc; +use torrust_tracker_metrics::label::LabelSet; +use torrust_tracker_metrics::metric_name; use torrust_tracker_primitives::DurationSinceUnixEpoch; use torrust_tracker_torrent_repository::event::Event; +use crate::statistics::repository::Repository; +use crate::statistics::TRACKER_CORE_PERSISTENT_TORRENTS_DOWNLOADS_TOTAL; use crate::torrent::repository::persisted::DatabasePersistentTorrentRepository; pub async fn handle_event( event: Event, + stats_repository: &Arc, db_torrent_repository: &Arc, - _now: DurationSinceUnixEpoch, + now: DurationSinceUnixEpoch, ) { match event { // Torrent events @@ -36,6 +41,7 @@ pub async fn handle_event( Event::PeerDownloadCompleted { info_hash, peer } => { tracing::debug!(info_hash = ?info_hash, peer = ?peer, "Peer download completed", ); + // Increment the number of downloads for the torrent match db_torrent_repository.increase_number_of_downloads(&info_hash) { Ok(()) => { tracing::debug!(info_hash = ?info_hash, "Number of downloads increased"); @@ -44,6 +50,19 @@ pub async fn handle_event( tracing::error!(info_hash = ?info_hash, error = ?err, "Failed to increase number of downloads"); } } + + // Increment the number of downloads for all the torrents + let _unused = stats_repository + .increment_counter( + &metric_name!(TRACKER_CORE_PERSISTENT_TORRENTS_DOWNLOADS_TOTAL), + &LabelSet::default(), + now, + ) + .await; + + // todo: + // - Persist the metric into the database. + // - Load the metric from the database. } } } diff --git a/packages/tracker-core/src/statistics/event/listener.rs b/packages/tracker-core/src/statistics/event/listener.rs index e04675092..f85b2b7a0 100644 --- a/packages/tracker-core/src/statistics/event/listener.rs +++ b/packages/tracker-core/src/statistics/event/listener.rs @@ -6,26 +6,33 @@ use torrust_tracker_events::receiver::RecvError; use torrust_tracker_torrent_repository::event::receiver::Receiver; use super::handler::handle_event; +use crate::statistics::repository::Repository; use crate::torrent::repository::persisted::DatabasePersistentTorrentRepository; use crate::{CurrentClock, TRACKER_CORE_LOG_TARGET}; #[must_use] pub fn run_event_listener( receiver: Receiver, + repository: &Arc, db_torrent_repository: &Arc, ) -> JoinHandle<()> { + let stats_repository = repository.clone(); let db_torrent_repository: Arc = db_torrent_repository.clone(); tracing::info!(target: TRACKER_CORE_LOG_TARGET, "Starting torrent repository event listener"); tokio::spawn(async move { - dispatch_events(receiver, db_torrent_repository).await; + dispatch_events(receiver, stats_repository, db_torrent_repository).await; tracing::info!(target: TRACKER_CORE_LOG_TARGET, "Torrent repository listener finished"); }) } -async fn dispatch_events(mut receiver: Receiver, db_torrent_repository: Arc) { +async fn dispatch_events( + mut receiver: Receiver, + stats_repository: Arc, + db_torrent_repository: Arc, +) { let shutdown_signal = tokio::signal::ctrl_c(); tokio::pin!(shutdown_signal); @@ -41,7 +48,7 @@ async fn dispatch_events(mut receiver: Receiver, db_torrent_repository: Arc { match result { - Ok(event) => handle_event(event, &db_torrent_repository, CurrentClock::now()).await, + Ok(event) => handle_event(event, &stats_repository, &db_torrent_repository, CurrentClock::now()).await, Err(e) => { match e { RecvError::Closed => { diff --git a/packages/tracker-core/src/statistics/metrics.rs b/packages/tracker-core/src/statistics/metrics.rs new file mode 100644 index 000000000..f8ab3f9d9 --- /dev/null +++ b/packages/tracker-core/src/statistics/metrics.rs @@ -0,0 +1,63 @@ +use serde::Serialize; +use torrust_tracker_metrics::label::LabelSet; +use torrust_tracker_metrics::metric::MetricName; +use torrust_tracker_metrics::metric_collection::{Error, MetricCollection}; +use torrust_tracker_primitives::DurationSinceUnixEpoch; + +/// Metrics collected by the torrent repository. +#[derive(Debug, Clone, PartialEq, Default, Serialize)] +pub struct Metrics { + /// A collection of metrics. + pub metric_collection: MetricCollection, +} + +impl Metrics { + /// # Errors + /// + /// Returns an error if the metric does not exist and it cannot be created. + pub fn increment_counter( + &mut self, + metric_name: &MetricName, + labels: &LabelSet, + now: DurationSinceUnixEpoch, + ) -> Result<(), Error> { + self.metric_collection.increase_counter(metric_name, labels, now) + } + + /// # Errors + /// + /// Returns an error if the metric does not exist and it cannot be created. + pub fn set_gauge( + &mut self, + metric_name: &MetricName, + labels: &LabelSet, + value: f64, + now: DurationSinceUnixEpoch, + ) -> Result<(), Error> { + self.metric_collection.set_gauge(metric_name, labels, value, now) + } + + /// # Errors + /// + /// Returns an error if the metric does not exist and it cannot be created. + pub fn increment_gauge( + &mut self, + metric_name: &MetricName, + labels: &LabelSet, + now: DurationSinceUnixEpoch, + ) -> Result<(), Error> { + self.metric_collection.increment_gauge(metric_name, labels, now) + } + + /// # Errors + /// + /// Returns an error if the metric does not exist and it cannot be created. + pub fn decrement_gauge( + &mut self, + metric_name: &MetricName, + labels: &LabelSet, + now: DurationSinceUnixEpoch, + ) -> Result<(), Error> { + self.metric_collection.decrement_gauge(metric_name, labels, now) + } +} diff --git a/packages/tracker-core/src/statistics/mod.rs b/packages/tracker-core/src/statistics/mod.rs index 53f112654..1cd9aac6b 100644 --- a/packages/tracker-core/src/statistics/mod.rs +++ b/packages/tracker-core/src/statistics/mod.rs @@ -1 +1,27 @@ pub mod event; +pub mod metrics; +pub mod repository; + +use metrics::Metrics; +use torrust_tracker_metrics::metric::description::MetricDescription; +use torrust_tracker_metrics::metric_name; +use torrust_tracker_metrics::unit::Unit; + +// Torrent metrics + +const TRACKER_CORE_PERSISTENT_TORRENTS_DOWNLOADS_TOTAL: &str = "tracker_core_persistent_torrents_downloads_total"; + +#[must_use] +pub fn describe_metrics() -> Metrics { + let mut metrics = Metrics::default(); + + // Torrent metrics + + metrics.metric_collection.describe_counter( + &metric_name!(TRACKER_CORE_PERSISTENT_TORRENTS_DOWNLOADS_TOTAL), + Some(Unit::Count), + Some(&MetricDescription::new("The total number of torrent downloads (persisted).")), + ); + + metrics +} diff --git a/packages/tracker-core/src/statistics/repository.rs b/packages/tracker-core/src/statistics/repository.rs new file mode 100644 index 000000000..fe1292d00 --- /dev/null +++ b/packages/tracker-core/src/statistics/repository.rs @@ -0,0 +1,132 @@ +use std::sync::Arc; + +use tokio::sync::{RwLock, RwLockReadGuard}; +use torrust_tracker_metrics::label::LabelSet; +use torrust_tracker_metrics::metric::MetricName; +use torrust_tracker_metrics::metric_collection::Error; +use torrust_tracker_primitives::DurationSinceUnixEpoch; + +use super::describe_metrics; +use super::metrics::Metrics; + +/// A repository for the torrent repository metrics. +#[derive(Clone)] +pub struct Repository { + pub stats: Arc>, +} + +impl Default for Repository { + fn default() -> Self { + Self::new() + } +} + +impl Repository { + #[must_use] + pub fn new() -> Self { + let stats = Arc::new(RwLock::new(describe_metrics())); + + Self { stats } + } + + pub async fn get_metrics(&self) -> RwLockReadGuard<'_, Metrics> { + self.stats.read().await + } + + /// # Errors + /// + /// This function will return an error if the metric collection fails to + /// increment the counter. + pub async fn increment_counter( + &self, + metric_name: &MetricName, + labels: &LabelSet, + now: DurationSinceUnixEpoch, + ) -> Result<(), Error> { + let mut stats_lock = self.stats.write().await; + + let result = stats_lock.increment_counter(metric_name, labels, now); + + drop(stats_lock); + + match result { + Ok(()) => {} + Err(ref err) => tracing::error!("Failed to increment the counter: {}", err), + } + + result + } + + /// # Errors + /// + /// This function will return an error if the metric collection fails to + /// set the gauge. + pub async fn set_gauge( + &self, + metric_name: &MetricName, + labels: &LabelSet, + value: f64, + now: DurationSinceUnixEpoch, + ) -> Result<(), Error> { + let mut stats_lock = self.stats.write().await; + + let result = stats_lock.set_gauge(metric_name, labels, value, now); + + drop(stats_lock); + + match result { + Ok(()) => {} + Err(ref err) => tracing::error!("Failed to set the gauge: {}", err), + } + + result + } + + /// # Errors + /// + /// This function will return an error if the metric collection fails to + /// increment the gauge. + pub async fn increment_gauge( + &self, + metric_name: &MetricName, + labels: &LabelSet, + now: DurationSinceUnixEpoch, + ) -> Result<(), Error> { + let mut stats_lock = self.stats.write().await; + + let result = stats_lock.increment_gauge(metric_name, labels, now); + + drop(stats_lock); + + match result { + Ok(()) => {} + Err(ref err) => tracing::error!("Failed to increment the gauge: {}", err), + } + + result + } + + /// # Errors + /// + /// This function will return an error if the metric collection fails to + /// decrement the gauge. + pub async fn decrement_gauge( + &self, + metric_name: &MetricName, + labels: &LabelSet, + now: DurationSinceUnixEpoch, + ) -> Result<(), Error> { + let mut stats_lock = self.stats.write().await; + + let result = stats_lock.decrement_gauge(metric_name, labels, now); + + drop(stats_lock); + + match result { + Ok(()) => {} + Err(ref err) => tracing::error!("Failed to decrement the gauge: {}", err), + } + + result + } +} diff --git a/packages/tracker-core/tests/common/test_env.rs b/packages/tracker-core/tests/common/test_env.rs index d4462e3f6..0be8bd4c6 100644 --- a/packages/tracker-core/tests/common/test_env.rs +++ b/packages/tracker-core/tests/common/test_env.rs @@ -56,6 +56,7 @@ impl TestEnv { let job = bittorrent_tracker_core::statistics::event::listener::run_event_listener( self.torrent_repository_container.event_bus.receiver(), + &self.tracker_core_container.stats_repository, &self.tracker_core_container.db_torrent_repository, ); diff --git a/share/default/config/tracker.development.sqlite3.toml b/share/default/config/tracker.development.sqlite3.toml index 89d700132..17a73a1d2 100644 --- a/share/default/config/tracker.development.sqlite3.toml +++ b/share/default/config/tracker.development.sqlite3.toml @@ -7,12 +7,12 @@ schema_version = "2.0.0" threshold = "info" [core] -inactive_peer_cleanup_interval = 60 +inactive_peer_cleanup_interval = 120 listed = false private = false [core.tracker_policy] -max_peer_timeout = 30 +max_peer_timeout = 60 persistent_torrent_completed_stat = true remove_peerless_torrents = true diff --git a/src/bootstrap/jobs/tracker_core.rs b/src/bootstrap/jobs/tracker_core.rs index bb879db6b..37c53b9e4 100644 --- a/src/bootstrap/jobs/tracker_core.rs +++ b/src/bootstrap/jobs/tracker_core.rs @@ -6,11 +6,10 @@ use torrust_tracker_configuration::Configuration; use crate::container::AppContainer; pub fn start_event_listener(config: &Configuration, app_container: &Arc) -> Option> { - // todo: enable this when labeled metrics are implemented. - //if config.core.tracker_usage_statistics || config.core.tracker_policy.persistent_torrent_completed_stat { - if config.core.tracker_policy.persistent_torrent_completed_stat { + if config.core.tracker_usage_statistics || config.core.tracker_policy.persistent_torrent_completed_stat { let job = bittorrent_tracker_core::statistics::event::listener::run_event_listener( app_container.torrent_repository_container.event_bus.receiver(), + &app_container.tracker_core_container.stats_repository, &app_container.tracker_core_container.db_torrent_repository, );