Skip to content

Commit 6f11534

Browse files
committed
feat: [torrust#1539] add method to Database trait to persis global downloads counter
It does not use the new methods in production yet.
1 parent 3fce84b commit 6f11534

8 files changed

+214
-3
lines changed

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 {

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

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use r2d2_mysql::mysql::{params, Opts, OptsBuilder};
1515
use r2d2_mysql::MySqlConnectionManager;
1616
use torrust_tracker_primitives::{PersistentTorrent, PersistentTorrents};
1717

18-
use super::{Database, Driver, Error};
18+
use super::{Database, Driver, Error, TORRENTS_DOWNLOADS_TOTAL};
1919
use crate::authentication::key::AUTH_KEY_LENGTH;
2020
use crate::authentication::{self, Key};
2121

@@ -46,6 +46,27 @@ impl Mysql {
4646

4747
Ok(Self { pool })
4848
}
49+
50+
fn load_torrent_aggregate_metric(&self, metric_name: &str) -> Result<Option<PersistentTorrent>, Error> {
51+
let mut conn = self.pool.get().map_err(|e| (e, DRIVER))?;
52+
53+
let query = conn.exec_first::<u32, _, _>(
54+
"SELECT value FROM torrent_aggregate_metrics WHERE metric_name = :metric_name",
55+
params! { "metric_name" => metric_name },
56+
);
57+
58+
let persistent_torrent = query?;
59+
60+
Ok(persistent_torrent)
61+
}
62+
63+
fn save_torrent_aggregate_metric(&self, metric_name: &str, completed: PersistentTorrent) -> Result<(), Error> {
64+
const COMMAND : &str = "INSERT INTO torrent_aggregate_metrics (metric_name, value) VALUES (:metric_name, :completed) ON DUPLICATE KEY UPDATE value = VALUES(value)";
65+
66+
let mut conn = self.pool.get().map_err(|e| (e, DRIVER))?;
67+
68+
Ok(conn.exec_drop(COMMAND, params! { metric_name, completed })?)
69+
}
4970
}
5071

5172
impl Database for Mysql {
@@ -66,6 +87,14 @@ impl Database for Mysql {
6687
);"
6788
.to_string();
6889

90+
let create_torrent_aggregate_metrics_table = "
91+
CREATE TABLE IF NOT EXISTS torrent_aggregate_metrics (
92+
id integer PRIMARY KEY AUTO_INCREMENT,
93+
metric_name VARCHAR(50) NOT NULL UNIQUE,
94+
value INTEGER DEFAULT 0 NOT NULL
95+
);"
96+
.to_string();
97+
6998
let create_keys_table = format!(
7099
"
71100
CREATE TABLE IF NOT EXISTS `keys` (
@@ -82,6 +111,8 @@ impl Database for Mysql {
82111

83112
conn.query_drop(&create_torrents_table)
84113
.expect("Could not create torrents table.");
114+
conn.query_drop(&create_torrent_aggregate_metrics_table)
115+
.expect("Could not create create_torrent_aggregate_metrics_table table.");
85116
conn.query_drop(&create_keys_table).expect("Could not create keys table.");
86117
conn.query_drop(&create_whitelist_table)
87118
.expect("Could not create whitelist table.");
@@ -168,6 +199,30 @@ impl Database for Mysql {
168199
Ok(())
169200
}
170201

202+
/// Refer to [`databases::Database::load_global_number_of_downloads`](crate::core::databases::Database::load_global_number_of_downloads).
203+
fn load_global_number_of_downloads(&self) -> Result<Option<PersistentTorrent>, Error> {
204+
self.load_torrent_aggregate_metric(TORRENTS_DOWNLOADS_TOTAL)
205+
}
206+
207+
/// Refer to [`databases::Database::save_global_number_of_downloads`](crate::core::databases::Database::save_global_number_of_downloads).
208+
fn save_global_number_of_downloads(&self, downloaded: PersistentTorrent) -> Result<(), Error> {
209+
self.save_torrent_aggregate_metric(TORRENTS_DOWNLOADS_TOTAL, downloaded)
210+
}
211+
212+
/// Refer to [`databases::Database::increase_global_number_of_downloads`](crate::core::databases::Database::increase_global_number_of_downloads).
213+
fn increase_global_number_of_downloads(&self) -> Result<(), Error> {
214+
let mut conn = self.pool.get().map_err(|e| (e, DRIVER))?;
215+
216+
let metric_name = TORRENTS_DOWNLOADS_TOTAL;
217+
218+
conn.exec_drop(
219+
"UPDATE torrent_aggregate_metrics SET value = value + 1 WHERE metric_name = :metric_name",
220+
params! { metric_name },
221+
)?;
222+
223+
Ok(())
224+
}
225+
171226
/// Refer to [`databases::Database::load_keys`](crate::core::databases::Database::load_keys).
172227
fn load_keys(&self) -> Result<Vec<authentication::PeerKey>, Error> {
173228
let mut conn = self.pool.get().map_err(|e| (e, DRIVER))?;

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

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use r2d2_sqlite::rusqlite::types::Null;
1515
use r2d2_sqlite::SqliteConnectionManager;
1616
use torrust_tracker_primitives::{DurationSinceUnixEpoch, PersistentTorrent, PersistentTorrents};
1717

18-
use super::{Database, Driver, Error};
18+
use super::{Database, Driver, Error, TORRENTS_DOWNLOADS_TOTAL};
1919
use crate::authentication::{self, Key};
2020

2121
const DRIVER: Driver = Driver::Sqlite3;
@@ -49,6 +49,39 @@ impl Sqlite {
4949

5050
Ok(Self { pool })
5151
}
52+
53+
fn load_torrent_aggregate_metric(&self, metric_name: &str) -> Result<Option<PersistentTorrent>, Error> {
54+
let conn = self.pool.get().map_err(|e| (e, DRIVER))?;
55+
56+
let mut stmt = conn.prepare("SELECT value FROM torrent_aggregate_metrics WHERE metric_name = ?")?;
57+
58+
let mut rows = stmt.query([metric_name])?;
59+
60+
let persistent_torrent = rows.next()?;
61+
62+
Ok(persistent_torrent.map(|f| {
63+
let value: i64 = f.get(0).unwrap();
64+
u32::try_from(value).unwrap()
65+
}))
66+
}
67+
68+
fn save_torrent_aggregate_metric(&self, metric_name: &str, completed: PersistentTorrent) -> Result<(), Error> {
69+
let conn = self.pool.get().map_err(|e| (e, DRIVER))?;
70+
71+
let insert = conn.execute(
72+
"INSERT INTO torrent_aggregate_metrics (metric_name, value) VALUES (?1, ?2) ON CONFLICT(metric_name) DO UPDATE SET value = ?2",
73+
[metric_name.to_string(), completed.to_string()],
74+
)?;
75+
76+
if insert == 0 {
77+
Err(Error::InsertFailed {
78+
location: Location::caller(),
79+
driver: DRIVER,
80+
})
81+
} else {
82+
Ok(())
83+
}
84+
}
5285
}
5386

5487
impl Database for Sqlite {
@@ -69,6 +102,14 @@ impl Database for Sqlite {
69102
);"
70103
.to_string();
71104

105+
let create_torrent_aggregate_metrics_table = "
106+
CREATE TABLE IF NOT EXISTS torrent_aggregate_metrics (
107+
id INTEGER PRIMARY KEY AUTOINCREMENT,
108+
metric_name TEXT NOT NULL UNIQUE,
109+
value INTEGER DEFAULT 0 NOT NULL
110+
);"
111+
.to_string();
112+
72113
let create_keys_table = "
73114
CREATE TABLE IF NOT EXISTS keys (
74115
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -82,6 +123,7 @@ impl Database for Sqlite {
82123
conn.execute(&create_whitelist_table, [])?;
83124
conn.execute(&create_keys_table, [])?;
84125
conn.execute(&create_torrents_table, [])?;
126+
conn.execute(&create_torrent_aggregate_metrics_table, [])?;
85127

86128
Ok(())
87129
}
@@ -172,6 +214,30 @@ impl Database for Sqlite {
172214
Ok(())
173215
}
174216

217+
/// Refer to [`databases::Database::load_global_number_of_downloads`](crate::core::databases::Database::load_global_number_of_downloads).
218+
fn load_global_number_of_downloads(&self) -> Result<Option<PersistentTorrent>, Error> {
219+
self.load_torrent_aggregate_metric(TORRENTS_DOWNLOADS_TOTAL)
220+
}
221+
222+
/// Refer to [`databases::Database::save_global_number_of_downloads`](crate::core::databases::Database::save_global_number_of_downloads).
223+
fn save_global_number_of_downloads(&self, downloaded: PersistentTorrent) -> Result<(), Error> {
224+
self.save_torrent_aggregate_metric(TORRENTS_DOWNLOADS_TOTAL, downloaded)
225+
}
226+
227+
/// Refer to [`databases::Database::increase_global_number_of_downloads`](crate::core::databases::Database::increase_global_number_of_downloads).
228+
fn increase_global_number_of_downloads(&self) -> Result<(), Error> {
229+
let conn = self.pool.get().map_err(|e| (e, DRIVER))?;
230+
231+
let metric_name = TORRENTS_DOWNLOADS_TOTAL;
232+
233+
let _ = conn.execute(
234+
"UPDATE torrent_aggregate_metrics SET value = value + 1 WHERE metric_name = ?",
235+
[metric_name],
236+
)?;
237+
238+
Ok(())
239+
}
240+
175241
/// Refer to [`databases::Database::load_keys`](crate::core::databases::Database::load_keys).
176242
fn load_keys(&self) -> Result<Vec<authentication::PeerKey>, Error> {
177243
let conn = self.pool.get().map_err(|e| (e, DRIVER))?;

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,16 +131,48 @@ pub trait Database: Sync + Send {
131131
/// It does not create a new entry if the torrent is not found and it does
132132
/// not return an error.
133133
///
134+
/// # Context: Torrent Metrics
135+
///
136+
/// # Arguments
137+
///
138+
/// * `info_hash` - A reference to the torrent's info hash.
139+
///
140+
/// # Errors
141+
///
142+
/// Returns an [`Error`] if the query failed.
143+
fn increase_number_of_downloads(&self, info_hash: &InfoHash) -> Result<(), Error>;
144+
145+
/// Loads the total number of downloads for all torrents from the database.
146+
///
147+
/// # Context: Torrent Metrics
148+
///
149+
/// # Errors
150+
///
151+
/// Returns an [`Error`] if the total downloads cannot be loaded.
152+
fn load_global_number_of_downloads(&self) -> Result<Option<PersistentTorrent>, Error>;
153+
154+
/// Saves the total number of downloads for all torrents into the database.
155+
///
156+
/// # Context: Torrent Metrics
157+
///
134158
/// # Arguments
135159
///
136160
/// * `info_hash` - A reference to the torrent's info hash.
161+
/// * `downloaded` - The number of times the torrent has been downloaded.
162+
///
163+
/// # Errors
164+
///
165+
/// Returns an [`Error`] if the total downloads cannot be saved.
166+
fn save_global_number_of_downloads(&self, downloaded: PersistentTorrent) -> Result<(), Error>;
167+
168+
/// Increases the total number of downloads for all torrents.
137169
///
138170
/// # Context: Torrent Metrics
139171
///
140172
/// # Errors
141173
///
142174
/// Returns an [`Error`] if the query failed.
143-
fn increase_number_of_downloads(&self, info_hash: &InfoHash) -> Result<(), Error>;
175+
fn increase_global_number_of_downloads(&self) -> Result<(), Error>;
144176

145177
// Whitelist
146178

0 commit comments

Comments
 (0)