Skip to content

Commit f3056ef

Browse files
committed
feat: [#1523] add new metric: number of inactive torrents
1 parent 260f7ff commit f3056ef

File tree

8 files changed

+203
-78
lines changed

8 files changed

+203
-78
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//! Job that runs a task on intervals to update peers' activity metrics.
2+
use std::sync::Arc;
3+
4+
use chrono::Utc;
5+
use tokio::task::JoinHandle;
6+
use torrust_tracker_clock::clock::Time;
7+
use torrust_tracker_metrics::label::LabelSet;
8+
use torrust_tracker_metrics::metric_name;
9+
use torrust_tracker_primitives::DurationSinceUnixEpoch;
10+
use tracing::instrument;
11+
12+
use super::repository::Repository;
13+
use crate::statistics::{TORRENT_REPOSITORY_PEERS_INACTIVE_TOTAL, TORRENT_REPOSITORY_TORRENTS_INACTIVE_TOTAL};
14+
use crate::{CurrentClock, Swarms};
15+
16+
#[must_use]
17+
#[instrument(skip(swarms, stats_repository))]
18+
pub fn start_job(
19+
swarms: &Arc<Swarms>,
20+
stats_repository: &Arc<Repository>,
21+
inactivity_cutoff: DurationSinceUnixEpoch,
22+
) -> JoinHandle<()> {
23+
let weak_swarms = std::sync::Arc::downgrade(swarms);
24+
let weak_stats_repository = std::sync::Arc::downgrade(stats_repository);
25+
26+
let interval_in_secs = 15; // todo: make this configurable
27+
28+
tokio::spawn(async move {
29+
let interval = std::time::Duration::from_secs(interval_in_secs);
30+
let mut interval = tokio::time::interval(interval);
31+
interval.tick().await;
32+
33+
loop {
34+
tokio::select! {
35+
_ = tokio::signal::ctrl_c() => {
36+
tracing::info!("Stopping peers activity metrics update job (ctrl-c signal received) ...");
37+
break;
38+
}
39+
_ = interval.tick() => {
40+
if let (Some(swarms), Some(stats_repository)) = (weak_swarms.upgrade(), weak_stats_repository.upgrade()) {
41+
update_activity_metrics(interval_in_secs, &swarms, &stats_repository, inactivity_cutoff).await;
42+
} else {
43+
tracing::info!("Stopping peers activity metrics update job (can't upgrade weak pointers) ...");
44+
break;
45+
}
46+
}
47+
}
48+
}
49+
})
50+
}
51+
52+
async fn update_activity_metrics(
53+
interval_in_secs: u64,
54+
swarms: &Arc<Swarms>,
55+
stats_repository: &Arc<Repository>,
56+
inactivity_cutoff: DurationSinceUnixEpoch,
57+
) {
58+
let start_time = Utc::now().time();
59+
60+
tracing::debug!(
61+
"Updating peers and torrents activity metrics (executed every {} secs) ...",
62+
interval_in_secs
63+
);
64+
65+
let activity_metadata = swarms.get_activity_metadata(inactivity_cutoff).await;
66+
67+
activity_metadata.log();
68+
69+
update_inactive_peers_total(stats_repository, activity_metadata.inactive_peers_total).await;
70+
update_inactive_torrents_total(stats_repository, activity_metadata.inactive_torrents_total).await;
71+
72+
tracing::debug!(
73+
"Peers and torrents activity metrics updated in {} ms",
74+
(Utc::now().time() - start_time).num_milliseconds()
75+
);
76+
}
77+
78+
async fn update_inactive_peers_total(stats_repository: &Arc<Repository>, inactive_peers_total: usize) {
79+
#[allow(clippy::cast_precision_loss)]
80+
let inactive_peers_total = inactive_peers_total as f64;
81+
82+
let _unused = stats_repository
83+
.set_gauge(
84+
&metric_name!(TORRENT_REPOSITORY_PEERS_INACTIVE_TOTAL),
85+
&LabelSet::default(),
86+
inactive_peers_total,
87+
CurrentClock::now(),
88+
)
89+
.await;
90+
}
91+
92+
async fn update_inactive_torrents_total(stats_repository: &Arc<Repository>, inactive_torrents_total: usize) {
93+
#[allow(clippy::cast_precision_loss)]
94+
let inactive_torrents_total = inactive_torrents_total as f64;
95+
96+
let _unused = stats_repository
97+
.set_gauge(
98+
&metric_name!(TORRENT_REPOSITORY_TORRENTS_INACTIVE_TOTAL),
99+
&LabelSet::default(),
100+
inactive_torrents_total,
101+
CurrentClock::now(),
102+
)
103+
.await;
104+
}

packages/torrent-repository/src/statistics/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
pub mod activity_metrics_updater;
12
pub mod event;
23
pub mod metrics;
3-
pub mod peers_inactivity_update;
44
pub mod repository;
55

66
use metrics::Metrics;
@@ -15,6 +15,7 @@ const TORRENT_REPOSITORY_TORRENTS_REMOVED_TOTAL: &str = "torrent_repository_torr
1515

1616
const TORRENT_REPOSITORY_TORRENTS_TOTAL: &str = "torrent_repository_torrents_total";
1717
const TORRENT_REPOSITORY_TORRENTS_DOWNLOADS_TOTAL: &str = "torrent_repository_torrents_downloads_total";
18+
const TORRENT_REPOSITORY_TORRENTS_INACTIVE_TOTAL: &str = "torrent_repository_torrents_inactive_total";
1819

1920
// Peers metrics
2021

@@ -56,6 +57,12 @@ pub fn describe_metrics() -> Metrics {
5657
Some(&MetricDescription::new("The total number of torrent downloads.")),
5758
);
5859

60+
metrics.metric_collection.describe_gauge(
61+
&metric_name!(TORRENT_REPOSITORY_TORRENTS_INACTIVE_TOTAL),
62+
Some(Unit::Count),
63+
Some(&MetricDescription::new("The total number of inactive torrents.")),
64+
);
65+
5966
// Peers metrics
6067

6168
metrics.metric_collection.describe_counter(

packages/torrent-repository/src/statistics/peers_inactivity_update.rs

Lines changed: 0 additions & 72 deletions
This file was deleted.

packages/torrent-repository/src/swarm.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,17 @@ impl Swarm {
126126
.count()
127127
}
128128

129+
#[must_use]
130+
pub fn get_activity_metadata(&self, current_cutoff: DurationSinceUnixEpoch) -> ActivityMetadata {
131+
let inactive_peers_total = self.count_inactive_peers(current_cutoff);
132+
133+
let active_peers_total = self.len() - inactive_peers_total;
134+
135+
let is_active = active_peers_total > 0;
136+
137+
ActivityMetadata::new(is_active, active_peers_total, inactive_peers_total)
138+
}
139+
129140
#[must_use]
130141
pub fn len(&self) -> usize {
131142
self.peers.len()
@@ -296,6 +307,30 @@ impl Swarm {
296307
}
297308
}
298309

310+
#[derive(Clone)]
311+
pub struct ActivityMetadata {
312+
/// Indicates if the swarm is active. It's inactive if there are no active
313+
/// peers.
314+
pub is_active: bool,
315+
316+
/// The number of active peers in the swarm.
317+
pub active_peers_total: usize,
318+
319+
/// The number of inactive peers in the swarm.
320+
pub inactive_peers_total: usize,
321+
}
322+
323+
impl ActivityMetadata {
324+
#[must_use]
325+
pub fn new(is_active: bool, active_peers_total: usize, inactive_peers_total: usize) -> Self {
326+
Self {
327+
is_active,
328+
active_peers_total,
329+
inactive_peers_total,
330+
}
331+
}
332+
}
333+
299334
#[cfg(test)]
300335
mod tests {
301336

packages/torrent-repository/src/swarms.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,32 @@ impl Swarms {
248248
}
249249
}
250250

251+
pub async fn get_activity_metadata(&self, current_cutoff: DurationSinceUnixEpoch) -> AggregateActivityMetadata {
252+
let mut active_peers_total = 0;
253+
let mut inactive_peers_total = 0;
254+
let mut active_torrents_total = 0;
255+
256+
for swarm_handle in &self.swarms {
257+
let swarm = swarm_handle.value().lock().await;
258+
259+
let activity_metadata = swarm.get_activity_metadata(current_cutoff);
260+
261+
if activity_metadata.is_active {
262+
active_torrents_total += 1;
263+
}
264+
265+
active_peers_total += activity_metadata.active_peers_total;
266+
inactive_peers_total += activity_metadata.inactive_peers_total;
267+
}
268+
269+
AggregateActivityMetadata {
270+
active_peers_total,
271+
inactive_peers_total,
272+
active_torrents_total,
273+
inactive_torrents_total: self.len() - active_torrents_total,
274+
}
275+
}
276+
251277
/// Counts the number of inactive peers across all torrents.
252278
pub async fn count_inactive_peers(&self, current_cutoff: DurationSinceUnixEpoch) -> usize {
253279
let mut inactive_peers_total = 0;
@@ -446,6 +472,31 @@ impl Swarms {
446472
#[derive(thiserror::Error, Debug, Clone)]
447473
pub enum Error {}
448474

475+
#[derive(Clone, Debug, Default)]
476+
pub struct AggregateActivityMetadata {
477+
/// The number of active peers in all swarms.
478+
pub active_peers_total: usize,
479+
480+
/// The number of inactive peers in all swarms.
481+
pub inactive_peers_total: usize,
482+
483+
/// The number of active torrents.
484+
pub active_torrents_total: usize,
485+
486+
/// The number of inactive torrents.
487+
pub inactive_torrents_total: usize,
488+
}
489+
490+
impl AggregateActivityMetadata {
491+
pub fn log(&self) {
492+
tracing::info!(
493+
active_peers_total = self.active_peers_total,
494+
inactive_peers_total = self.inactive_peers_total,
495+
active_torrents_total = self.active_torrents_total,
496+
inactive_torrents_total = self.inactive_torrents_total
497+
);
498+
}
499+
}
449500
#[cfg(test)]
450501
mod tests {
451502

src/app.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use tracing::instrument;
2828

2929
use crate::bootstrap::jobs::manager::JobManager;
3030
use crate::bootstrap::jobs::{
31-
self, health_check_api, http_tracker, peers_inactivity_update, torrent_cleanup, tracker_apis, udp_tracker,
31+
self, health_check_api, http_tracker, activity_metrics_updater, torrent_cleanup, tracker_apis, udp_tracker,
3232
};
3333
use crate::bootstrap::{self};
3434
use crate::container::AppContainer;
@@ -267,7 +267,7 @@ fn start_torrent_cleanup(config: &Configuration, app_container: &Arc<AppContaine
267267

268268
fn start_peers_inactivity_update(config: &Configuration, app_container: &Arc<AppContainer>, job_manager: &mut JobManager) {
269269
if config.core.tracker_usage_statistics {
270-
let handle = peers_inactivity_update::start_job(config, app_container);
270+
let handle = activity_metrics_updater::start_job(config, app_container);
271271

272272
job_manager.push("peers_inactivity_update", handle);
273273
} else {

src/bootstrap/jobs/peers_inactivity_update.rs renamed to src/bootstrap/jobs/activity_metrics_updater.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! Job that runs a task on intervals to update peers' inactivity metrics.
1+
//! Job that runs a task on intervals to update peers' activity metrics.
22
use std::sync::Arc;
33
use std::time::Duration;
44

@@ -11,7 +11,7 @@ use crate::CurrentClock;
1111

1212
#[must_use]
1313
pub fn start_job(config: &Configuration, app_container: &Arc<AppContainer>) -> JoinHandle<()> {
14-
torrust_tracker_torrent_repository::statistics::peers_inactivity_update::start_job(
14+
torrust_tracker_torrent_repository::statistics::activity_metrics_updater::start_job(
1515
&app_container.torrent_repository_container.swarms.clone(),
1616
&app_container.torrent_repository_container.stats_repository.clone(),
1717
peer_inactivity_cutoff_timestamp(config.core.tracker_policy.max_peer_timeout),

src/bootstrap/jobs/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
//! 2. Launch all the application services as concurrent jobs.
77
//!
88
//! This modules contains all the functions needed to start those jobs.
9+
pub mod activity_metrics_updater;
910
pub mod health_check_api;
1011
pub mod http_tracker;
1112
pub mod http_tracker_core;
1213
pub mod manager;
13-
pub mod peers_inactivity_update;
1414
pub mod torrent_cleanup;
1515
pub mod torrent_repository;
1616
pub mod tracker_apis;

0 commit comments

Comments
 (0)