about summary refs log tree commit diff
path: root/corgi_stats/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'corgi_stats/src/main.rs')
-rw-r--r--corgi_stats/src/main.rs95
1 files changed, 95 insertions, 0 deletions
diff --git a/corgi_stats/src/main.rs b/corgi_stats/src/main.rs
new file mode 100644
index 0000000..1097685
--- /dev/null
+++ b/corgi_stats/src/main.rs
@@ -0,0 +1,95 @@
+use rusqlite::{Connection, params};
+use std::time::Instant;
+use time::{Duration, OffsetDateTime};
+
+// Thank you, cat, for optimizing my query
+const TOP_TEN_ALL_TIME: &str = "SELECT reqs.cnt, agents.agent 
+FROM agents 
+JOIN (
+  SELECT count(id) as cnt, agent_id 
+  FROM requests 
+  GROUP BY agent_id
+) reqs 
+ON reqs.agent_id=agents.id
+ORDER BY reqs.cnt DESC LIMIT 10;";
+
+fn main() {
+	let db_path = std::env::var("CORGI_STATS_DB").ok();
+	let db = if let Some(path) = db_path {
+		if let Ok(db) = Connection::open(path) {
+			db
+		} else {
+			error_and_die(500, "failed to open database");
+		}
+	} else {
+		error_and_die(500, "database key not set");
+	};
+
+	let mut body = String::new();
+
+	let now = OffsetDateTime::now_utc();
+	let fifteen_ago = now - Duration::minutes(15);
+
+	let start = Instant::now();
+
+	let query = "SELECT count(requests.id) AS request_count, agents.agent FROM requests \
+		INNER JOIN agents ON requests.agent_id = agents.id \
+		WHERE requests.timestamp > ?1 \
+		GROUP BY requests.agent_id;";
+
+	let mut prepared = db.prepare(query).unwrap();
+	let mut agents: Vec<(usize, String)> = prepared
+		.query_map(params![fifteen_ago], |row| Ok((row.get(0)?, row.get(1)?)))
+		.unwrap()
+		.map(|r| r.unwrap())
+		.collect();
+
+	agents.sort_by(|a, b| a.0.cmp(&b.0).reverse());
+
+	let mut prepared = db.prepare(TOP_TEN_ALL_TIME).unwrap();
+	let highest_five: Vec<(usize, String)> = prepared
+		.query_map(params![], |row| Ok((row.get(0)?, row.get(1)?)))
+		.unwrap()
+		.map(|r| r.unwrap())
+		.collect();
+	let sum_highest_five = highest_five.iter().fold(0, |acc, (count, _)| acc + count);
+
+	body.push_str("<p>In the last fifteen minutes:<br/><code><pre>");
+	body.push_str("total | req/m | agent\n");
+	for (count, agent) in &agents {
+		body.push_str(&format!(
+			"{count:<5} | {:<5.1} | {agent}\n",
+			*count as f32 / 15.0
+		));
+	}
+	body.push_str("</pre></code></p>");
+
+	body.push_str("<p>Highest ten all time requesters:<br/><code><pre>");
+	body.push_str("  total  | %of10 | agent\n");
+	for (count, agent) in highest_five {
+		body.push_str(&format!(
+			" {count:<7} | {:<5.1} | {agent}\n",
+			(count as f32 / sum_highest_five as f32) * 100.0
+		));
+	}
+	body.push_str("</pre></code></p>");
+
+	body.push_str(&format!("<p>took {}ms</p>", start.elapsed().as_millis()));
+
+	println!("Status: 200\nContent-Type: text/html\n");
+	println!("{body}");
+}
+
+fn error_and_die<S: Into<String>>(status: u16, msg: S) -> ! {
+	println!("Status: {status}");
+	println!("Content-Type: text/html\n");
+	println!("<html>");
+	println!("\t<head><title>{status}</title></head>");
+	println!("\t<body style='width: 20rem; padding: 0px; margin: 2rem;'>");
+	println!("\t\t<h1>{status}</h1>");
+	println!("\t\t<hr/>");
+	println!("\t\t<p>{}</p>", msg.into());
+	println!("\t</body>\n</html>");
+
+	std::process::exit(0);
+}