From 1c38067785b2800334dc7e77fb2165d5c29ffce2 Mon Sep 17 00:00:00 2001 From: DaXcess Date: Tue, 19 Sep 2023 13:49:32 +0200 Subject: [PATCH] Bring back stats collection --- COMPILING.md | 8 ++++++++ Cargo.lock | 41 ++++++++++++++++++++++++++++++++++++----- Cargo.toml | 6 ++++-- Dockerfile | 4 +++- README.md | 1 + src/main.rs | 31 ++++++++++++++++++++++++++++++- src/stats.rs | 26 ++++++++++++++++++++++++++ 7 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 src/stats.rs diff --git a/COMPILING.md b/COMPILING.md index db657bd..fdca56a 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -71,6 +71,14 @@ If you are actively developing Spoticord, you can use the following command to b cargo run ``` +# Features + +As of now, Spoticord has one optional feature: `stats`. This feature enables collecting a few statistics, total and active servers. These statistics will be sent to a redis server, where they then can be read for whatever purpose. If you want to enable this feature, you can do so by running the following command: + +```sh +cargo build [--release] --features metrics +``` + # MSRV The current minimum supported rust version is `1.65.0`. diff --git a/Cargo.lock b/Cargo.lock index 0c836ae..b774a1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,9 +72,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +checksum = "0f2135563fb5c609d2b2b87c1e8ce7bc41b0b45430fa9661f457981503dd5bf0" dependencies = [ "memchr", ] @@ -301,6 +301,16 @@ dependencies = [ "cc", ] +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "command_attr" version = "0.4.2" @@ -1801,6 +1811,21 @@ dependencies = [ "rand", ] +[[package]] +name = "redis" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f49cdc0bb3f412bf8e7d1bd90fe1d9eb10bc5c399ba90973c14662a27b3f8ba" +dependencies = [ + "combine", + "itoa", + "percent-encoding", + "ryu", + "sha1_smol", + "socket2 0.4.9", + "url", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -2243,6 +2268,12 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + [[package]] name = "shannon" version = "0.2.0" @@ -2365,9 +2396,9 @@ version = "2.1.0" dependencies = [ "dotenv", "env_logger 0.10.0", - "lazy_static", "librespot", "log", + "redis", "reqwest", "samplerate", "serde", @@ -2453,9 +2484,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" dependencies = [ "winapi-util", ] diff --git a/Cargo.toml b/Cargo.toml index 5f7231f..571af67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,12 +8,15 @@ rust-version = "1.65.0" name = "spoticord" path = "src/main.rs" +[features] +stats = ["redis"] + [dependencies] dotenv = "0.15.0" env_logger = "0.10.0" -lazy_static = { version = "1.4.0", optional = true } librespot = { version = "0.4.2", default-features = false } log = "0.4.20" +redis = { version = "0.23.3", optional = true } reqwest = "0.11.20" samplerate = "0.2.4" serde = "1.0.188" @@ -28,4 +31,3 @@ zerocopy = "0.7.5" [profile.release] opt-level = 3 lto = true -debug = true \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 1564679..01b8d15 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,9 @@ WORKDIR /app RUN apt-get update && apt-get install -y cmake COPY . . -RUN cargo install --path . + +# Remove `--features stats` if you want to deploy without stats collection +RUN cargo install --path . --features stats # Runtime FROM debian:buster-slim diff --git a/README.md b/README.md index 673c2a5..03eb869 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Spoticord uses environment variables to configure itself. The following variable Additionally you can configure the following variables: - `GUILD_ID`: The ID of the Discord server where this bot will create commands for. This is used during testing to prevent the bot from creating slash commands in other servers, as well as getting the commands quicker. This variable is optional, and if not set, the bot will create commands in all servers it is in (this may take up to 15 minutes). +- `KV_URL`: The connection URL of a redis-server instance used for storing realtime data. This variable is required when compiling with the `stats` feature. #### Providing environment variables You can provide environment variables in a `.env` file at the root of the working directory of Spoticord. diff --git a/src/main.rs b/src/main.rs index 330ab66..89593f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,12 @@ mod player; mod session; mod utils; +#[cfg(feature = "stats")] +mod stats; + +#[cfg(feature = "stats")] +use crate::stats::StatsManager; + #[tokio::main] async fn main() { if std::env::var("RUST_LOG").is_err() { @@ -51,6 +57,11 @@ async fn main() { let token = env::var("DISCORD_TOKEN").expect("a token in the environment"); let db_url = env::var("DATABASE_URL").expect("a database URL in the environment"); + #[cfg(feature = "stats")] + let stats_manager = + StatsManager::new(env::var("KV_URL").expect("a redis URL in the environment")) + .expect("Failed to connect to redis"); + let session_manager = SessionManager::new(); // Create client @@ -73,7 +84,9 @@ async fn main() { } let shard_manager = client.shard_manager.clone(); - let _cache = client.cache_and_http.cache.clone(); + + #[cfg(feature = "stats")] + let cache = client.cache_and_http.cache.clone(); #[cfg(unix)] let mut term: Option> = Some(Box::new( @@ -88,6 +101,22 @@ async fn main() { tokio::spawn(async move { loop { tokio::select! { + _ = tokio::time::sleep(std::time::Duration::from_secs(60)) => { + #[cfg(feature = "stats")] + { + let guild_count = cache.guilds().len(); + let active_count = session_manager.get_active_session_count().await; + + if let Err(why) = stats_manager.set_server_count(guild_count) { + error!("Failed to update server count: {why}"); + } + + if let Err(why) = stats_manager.set_active_count(active_count) { + error!("Failed to update active count: {why}"); + } + } + } + _ = tokio::signal::ctrl_c() => { info!("Received interrupt signal, shutting down..."); diff --git a/src/stats.rs b/src/stats.rs new file mode 100644 index 0000000..7b54e2c --- /dev/null +++ b/src/stats.rs @@ -0,0 +1,26 @@ +use redis::{Client, Commands, RedisResult as Result}; + +#[derive(Clone)] +pub struct StatsManager { + redis: Client, +} + +impl StatsManager { + pub fn new(url: impl AsRef) -> Result { + let redis = Client::open(url.as_ref())?; + + Ok(StatsManager { redis }) + } + + pub fn set_server_count(&self, count: usize) -> Result<()> { + let mut con = self.redis.get_connection()?; + + con.set("sc-bot-total-servers", count.to_string()) + } + + pub fn set_active_count(&self, count: usize) -> Result<()> { + let mut con = self.redis.get_connection()?; + + con.set("sc-bot-active-servers", count.to_string()) + } +}