Skip to content

Commit 46df7eb

Browse files
committed
Merge #1540: Overhaul stats: Persist all torrents downloads metric
4febda4 fic: [#1539] persisten metrics should be enabled by config (Jose Celano) 2c9311b test: [#1539] add integration test for persisted downloads counter (Jose Celano) c07f366 feat: [#1539] load global downloads counter from DB (Jose Celano) 9301e58 feat: [#1539] save global downloads counter in DB (Jose Celano) 6f11534 feat: [#1539] add method to Database trait to persis global downloads counter (Jose Celano) Pull request description: Persist "all torrent downloads" metric. ### Subtasks - [x] Add new methods to the `Database` trait. - [x] Make use of the new methods to persist the metric `TRACKER_CORE_PERSISTENT_TORRENTS_DOWNLOADS_TOTAL`. - [x] Add an integration test. ACKs for top commit: josecelano: ACK 4febda4 Tree-SHA512: f2d316361048a368fdf23da4ad5550b2c27544b589fe03f55bae3174e206e1a76b53d1ad5f586e49e2731a738d49c0e3b7d6cc4dda1b7feb936b321d620d8d2c
2 parents 3fce84b + 4febda4 commit 46df7eb

24 files changed

+531
-21
lines changed

packages/metrics/src/counter.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ impl Counter {
2020
pub fn increment(&mut self, value: u64) {
2121
self.0 += value;
2222
}
23+
24+
pub fn absolute(&mut self, value: u64) {
25+
self.0 = value;
26+
}
2327
}
2428

2529
impl From<u64> for Counter {
@@ -73,6 +77,13 @@ mod tests {
7377
assert_eq!(counter.value(), 3);
7478
}
7579

80+
#[test]
81+
fn it_could_set_to_an_absolute_value() {
82+
let mut counter = Counter::new(0);
83+
counter.absolute(1);
84+
assert_eq!(counter.value(), 1);
85+
}
86+
7687
#[test]
7788
fn it_serializes_to_prometheus() {
7889
let counter = Counter::new(42);

packages/metrics/src/metric/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ impl Metric<Counter> {
5555
pub fn increment(&mut self, label_set: &LabelSet, time: DurationSinceUnixEpoch) {
5656
self.sample_collection.increment(label_set, time);
5757
}
58+
59+
pub fn absolute(&mut self, label_set: &LabelSet, value: u64, time: DurationSinceUnixEpoch) {
60+
self.sample_collection.absolute(label_set, value, time);
61+
}
5862
}
5963

6064
impl Metric<Gauge> {

packages/metrics/src/metric_collection.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ impl MetricCollection {
7272
self.counters.get_value(name, label_set)
7373
}
7474

75+
/// Increases the counter for the given metric name and labels.
76+
///
7577
/// # Errors
7678
///
7779
/// Return an error if a metrics of a different type with the same name
@@ -93,6 +95,30 @@ impl MetricCollection {
9395
Ok(())
9496
}
9597

98+
/// Sets the counter for the given metric name and labels.
99+
///
100+
/// # Errors
101+
///
102+
/// Return an error if a metrics of a different type with the same name
103+
/// already exists.
104+
pub fn set_counter(
105+
&mut self,
106+
name: &MetricName,
107+
label_set: &LabelSet,
108+
value: u64,
109+
time: DurationSinceUnixEpoch,
110+
) -> Result<(), Error> {
111+
if self.gauges.metrics.contains_key(name) {
112+
return Err(Error::MetricNameCollisionAdding {
113+
metric_name: name.clone(),
114+
});
115+
}
116+
117+
self.counters.absolute(name, label_set, value, time);
118+
119+
Ok(())
120+
}
121+
96122
pub fn ensure_counter_exists(&mut self, name: &MetricName) {
97123
self.counters.ensure_metric_exists(name);
98124
}
@@ -361,7 +387,7 @@ impl MetricKindCollection<Counter> {
361387
///
362388
/// # Panics
363389
///
364-
/// Panics if the metric does not exist and it could not be created.
390+
/// Panics if the metric does not exist.
365391
pub fn increment(&mut self, name: &MetricName, label_set: &LabelSet, time: DurationSinceUnixEpoch) {
366392
self.ensure_metric_exists(name);
367393

@@ -370,6 +396,21 @@ impl MetricKindCollection<Counter> {
370396
metric.increment(label_set, time);
371397
}
372398

399+
/// Sets the counter to an absolute value for the given metric name and labels.
400+
///
401+
/// If the metric name does not exist, it will be created.
402+
///
403+
/// # Panics
404+
///
405+
/// Panics if the metric does not exist.
406+
pub fn absolute(&mut self, name: &MetricName, label_set: &LabelSet, value: u64, time: DurationSinceUnixEpoch) {
407+
self.ensure_metric_exists(name);
408+
409+
let metric = self.metrics.get_mut(name).expect("Counter metric should exist");
410+
411+
metric.absolute(label_set, value, time);
412+
}
413+
373414
#[must_use]
374415
pub fn get_value(&self, name: &MetricName, label_set: &LabelSet) -> Option<Counter> {
375416
self.metrics

packages/metrics/src/sample.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ impl Measurement<Counter> {
122122
self.value.increment(1);
123123
self.set_recorded_at(time);
124124
}
125+
126+
pub fn absolute(&mut self, value: u64, time: DurationSinceUnixEpoch) {
127+
self.value.absolute(value);
128+
self.set_recorded_at(time);
129+
}
125130
}
126131

127132
impl Measurement<Gauge> {

packages/metrics/src/sample_collection.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ impl SampleCollection<Counter> {
7979

8080
sample.increment(time);
8181
}
82+
83+
pub fn absolute(&mut self, label_set: &LabelSet, value: u64, time: DurationSinceUnixEpoch) {
84+
let sample = self
85+
.samples
86+
.entry(label_set.clone())
87+
.or_insert_with(|| Measurement::new(Counter::default(), time));
88+
89+
sample.absolute(value, time);
90+
}
8291
}
8392

8493
impl SampleCollection<Gauge> {

packages/tracker-core/migrations/mysql/20240730183000_torrust_tracker_create_all_tables.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CREATE TABLE
44
info_hash VARCHAR(40) NOT NULL UNIQUE
55
);
66

7+
# todo: rename to `torrent_metrics`
78
CREATE TABLE
89
IF NOT EXISTS torrents (
910
id integer PRIMARY KEY AUTO_INCREMENT,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
CREATE TABLE
2+
IF NOT EXISTS torrent_aggregate_metrics (
3+
id integer PRIMARY KEY AUTO_INCREMENT,
4+
metric_name VARCHAR(50) NOT NULL UNIQUE,
5+
value INTEGER DEFAULT 0 NOT NULL
6+
);

packages/tracker-core/migrations/sqlite/20240730183000_torrust_tracker_create_all_tables.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CREATE TABLE
44
info_hash TEXT NOT NULL UNIQUE
55
);
66

7+
# todo: rename to `torrent_metrics`
78
CREATE TABLE
89
IF NOT EXISTS torrents (
910
id INTEGER PRIMARY KEY AUTOINCREMENT,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
CREATE TABLE
2+
IF NOT EXISTS torrent_aggregate_metrics (
3+
id INTEGER PRIMARY KEY AUTOINCREMENT,
4+
metric_name TEXT NOT NULL UNIQUE,
5+
value INTEGER DEFAULT 0 NOT NULL
6+
);

packages/tracker-core/src/databases/driver/mod.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ use sqlite::Sqlite;
66
use super::error::Error;
77
use super::Database;
88

9+
/// Metric name in DB for the total number of downloads across all torrents.
10+
const TORRENTS_DOWNLOADS_TOTAL: &str = "torrents_downloads_total";
11+
912
/// The database management system used by the tracker.
1013
///
1114
/// Refer to:
@@ -97,9 +100,14 @@ pub(crate) mod tests {
97100

98101
// Persistent torrents (stats)
99102

103+
// Torrent metrics
100104
handling_torrent_persistence::it_should_save_and_load_persistent_torrents(driver);
101105
handling_torrent_persistence::it_should_load_all_persistent_torrents(driver);
102106
handling_torrent_persistence::it_should_increase_the_number_of_downloads_for_a_given_torrent(driver);
107+
// Aggregate metrics for all torrents
108+
handling_torrent_persistence::it_should_save_and_load_the_global_number_of_downloads(driver);
109+
handling_torrent_persistence::it_should_load_the_global_number_of_downloads(driver);
110+
handling_torrent_persistence::it_should_increase_the_global_number_of_downloads(driver);
103111

104112
// Authentication keys (for private trackers)
105113

@@ -154,6 +162,8 @@ pub(crate) mod tests {
154162
use crate::databases::Database;
155163
use crate::test_helpers::tests::sample_info_hash;
156164

165+
// Metrics per torrent
166+
157167
pub fn it_should_save_and_load_persistent_torrents(driver: &Arc<Box<dyn Database>>) {
158168
let infohash = sample_info_hash();
159169

@@ -192,6 +202,40 @@ pub(crate) mod tests {
192202

193203
assert_eq!(number_of_downloads, 2);
194204
}
205+
206+
// Aggregate metrics for all torrents
207+
208+
pub fn it_should_save_and_load_the_global_number_of_downloads(driver: &Arc<Box<dyn Database>>) {
209+
let number_of_downloads = 1;
210+
211+
driver.save_global_number_of_downloads(number_of_downloads).unwrap();
212+
213+
let number_of_downloads = driver.load_global_number_of_downloads().unwrap().unwrap();
214+
215+
assert_eq!(number_of_downloads, 1);
216+
}
217+
218+
pub fn it_should_load_the_global_number_of_downloads(driver: &Arc<Box<dyn Database>>) {
219+
let number_of_downloads = 1;
220+
221+
driver.save_global_number_of_downloads(number_of_downloads).unwrap();
222+
223+
let number_of_downloads = driver.load_global_number_of_downloads().unwrap().unwrap();
224+
225+
assert_eq!(number_of_downloads, 1);
226+
}
227+
228+
pub fn it_should_increase_the_global_number_of_downloads(driver: &Arc<Box<dyn Database>>) {
229+
let number_of_downloads = 1;
230+
231+
driver.save_global_number_of_downloads(number_of_downloads).unwrap();
232+
233+
driver.increase_global_number_of_downloads().unwrap();
234+
235+
let number_of_downloads = driver.load_global_number_of_downloads().unwrap().unwrap();
236+
237+
assert_eq!(number_of_downloads, 2);
238+
}
195239
}
196240

197241
mod handling_authentication_keys {

0 commit comments

Comments
 (0)