Add torrent statistics to transmission-vpn service
All checks were successful
Build and Release / build-and-release (push) Successful in 1m47s

Implement aggregate torrent statistics display for transmission-vpn service
via Transmission RPC API. Shows active torrent count and total download/upload
speeds. Change VPN route label from "ip:" to "route:" for clarity.

- Add get_transmission_stats() method to query Transmission RPC
- Display format: "X active, ↓ MB/s, ↑ MB/s"
- Update version to v0.1.244
This commit is contained in:
Christoffer Martinsson 2025-12-02 11:12:14 +01:00
parent 7e1962a168
commit 7a3ed17952
5 changed files with 88 additions and 7 deletions

6
Cargo.lock generated
View File

@ -279,7 +279,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
[[package]]
name = "cm-dashboard"
version = "0.1.242"
version = "0.1.243"
dependencies = [
"anyhow",
"chrono",
@ -301,7 +301,7 @@ dependencies = [
[[package]]
name = "cm-dashboard-agent"
version = "0.1.242"
version = "0.1.243"
dependencies = [
"anyhow",
"async-trait",
@ -325,7 +325,7 @@ dependencies = [
[[package]]
name = "cm-dashboard-shared"
version = "0.1.242"
version = "0.1.243"
dependencies = [
"chrono",
"serde",

View File

@ -1,6 +1,6 @@
[package]
name = "cm-dashboard-agent"
version = "0.1.243"
version = "0.1.244"
edition = "2021"
[dependencies]

View File

@ -164,7 +164,7 @@ impl SystemdCollector {
let metrics = Vec::new();
sub_services.push(SubServiceData {
name: format!("ip: {}", external_ip),
name: format!("route: {}", external_ip),
service_status: Status::Info,
metrics,
service_type: "vpn_route".to_string(),
@ -172,6 +172,19 @@ impl SystemdCollector {
}
}
if service_name == "transmission-vpn" && status_info.active_state == "active" {
if let Some((active_count, download_mbps, upload_mbps)) = self.get_transmission_stats() {
let metrics = Vec::new();
sub_services.push(SubServiceData {
name: format!("{} active, ↓ {:.1} MB/s, ↑ {:.1} MB/s", active_count, download_mbps, upload_mbps),
service_status: Status::Info,
metrics,
service_type: "torrent_stats".to_string(),
});
}
}
// Create complete service data
let service_data = ServiceData {
name: service_name.clone(),
@ -878,6 +891,74 @@ impl SystemdCollector {
None
}
/// Get aggregate transmission torrent statistics
/// Returns: (active_count, download_mbps, upload_mbps)
fn get_transmission_stats(&self) -> Option<(u32, f32, f32)> {
let rpc_url = "http://localhost:9091/transmission/rpc";
// Create HTTP client with timeout
let client = reqwest::blocking::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.ok()?;
// First request to get session ID (transmission requires this for CSRF protection)
let session_id = match client.post(rpc_url).send() {
Ok(resp) => {
resp.headers()
.get("X-Transmission-Session-Id")
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string())
}
Err(_) => return None,
}?;
// Request torrent list with session ID
let request_body = serde_json::json!({
"method": "torrent-get",
"arguments": {
"fields": ["status", "rateDownload", "rateUpload"]
}
});
let response = client
.post(rpc_url)
.header("X-Transmission-Session-Id", session_id)
.json(&request_body)
.send()
.ok()?;
let json: serde_json::Value = response.json().ok()?;
// Parse torrent data and calculate aggregates
let torrent_list = json["arguments"]["torrents"].as_array()?;
let mut active_count = 0u32;
let mut total_download_bps = 0.0f64;
let mut total_upload_bps = 0.0f64;
for torrent in torrent_list {
let status_code = torrent["status"].as_i64().unwrap_or(0);
let rate_download = torrent["rateDownload"].as_f64().unwrap_or(0.0);
let rate_upload = torrent["rateUpload"].as_f64().unwrap_or(0.0);
// Status codes: 0=stopped, 4=downloading, 6=seeding
// Count as active if downloading or seeding
if status_code == 4 || status_code == 6 {
active_count += 1;
}
total_download_bps += rate_download;
total_upload_bps += rate_upload;
}
// Convert bytes/s to MB/s
let download_mbps = (total_download_bps / 1024.0 / 1024.0) as f32;
let upload_mbps = (total_upload_bps / 1024.0 / 1024.0) as f32;
Some((active_count, download_mbps, upload_mbps))
}
}
#[async_trait]

View File

@ -1,6 +1,6 @@
[package]
name = "cm-dashboard"
version = "0.1.243"
version = "0.1.244"
edition = "2021"
[dependencies]

View File

@ -1,6 +1,6 @@
[package]
name = "cm-dashboard-shared"
version = "0.1.243"
version = "0.1.244"
edition = "2021"
[dependencies]