diff options
author | gennyble <gen@nyble.dev> | 2025-02-15 14:57:35 -0600 |
---|---|---|
committer | gennyble <gen@nyble.dev> | 2025-02-15 14:57:35 -0600 |
commit | 9143dab218dee9e1095ba52ded7fdca96471e5be (patch) | |
tree | 74f91367b276023e2cfbd0236a6bf6228d135247 | |
parent | 11abf8128d619282498c4b2572f73421e8adf300 (diff) | |
download | awake-9143dab218dee9e1095ba52ded7fdca96471e5be.tar.gz awake-9143dab218dee9e1095ba52ded7fdca96471e5be.zip |
Low-budget stats
-rwxr-xr-x | .gitignore | 1 | ||||
-rwxr-xr-x | Cargo.lock | 30 | ||||
-rwxr-xr-x | Cargo.toml | 3 | ||||
-rwxr-xr-x | dev.conf | 3 | ||||
-rw-r--r-- | src/db.rs | 64 | ||||
-rwxr-xr-x | src/main.rs | 111 |
6 files changed, 197 insertions, 15 deletions
diff --git a/.gitignore b/.gitignore index ea8c4bf..2ea0d11 100755 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +database.sqlite \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 861ad2c..a34a6f4 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,6 +54,7 @@ dependencies = [ "cutie", "rand", "rusqlite", + "serde", "sha2", "snafu", "time", @@ -173,6 +174,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" [[package]] +name = "cc" +version = "1.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +dependencies = [ + "shlex", +] + +[[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -472,6 +482,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad8935b44e7c13394a179a438e0cebba0fe08fe01b54f152e29a93b5cf993fd4" dependencies = [ + "cc", "pkg-config", "vcpkg", ] @@ -781,6 +792,7 @@ dependencies = [ "hashlink", "libsqlite3-sys", "smallvec", + "time", ] [[package]] @@ -809,18 +821,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -882,6 +894,12 @@ dependencies = [ ] [[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] name = "signal-hook-registry" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -929,9 +947,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 41dba62..173a739 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,8 @@ time = { version = "0.3.36", features = [ ] } sha2 = "0.10.8" url = "2.5.0" -rusqlite = "0.33.0" +rusqlite = { version = "0.33.0", features = ["bundled", "time"] } +serde = { version = "1.0.217", features = ["derive"] } # for generating Atom URNs [dependencies.rand] diff --git a/dev.conf b/dev.conf index 44275bc..2e1e952 100755 --- a/dev.conf +++ b/dev.conf @@ -1,3 +1,4 @@ -Webroot /Users/gen/src/inf/served +Webroot /home/gen/src/inf/served Templates ../templates Hostname dreamy.place +Database database.sqlite diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..d4038cb --- /dev/null +++ b/src/db.rs @@ -0,0 +1,64 @@ +use std::sync::Mutex; + +use camino::Utf8PathBuf; +use rusqlite::{params, Connection, OptionalExtension}; +use time::OffsetDateTime; + +use crate::Meminfo; + + +pub struct Database{ + db_path: Utf8PathBuf, + conn: Mutex<Connection> +} + +impl Database { + pub fn new(db_path: Utf8PathBuf) -> Self { + Self { + conn:Mutex::new(Connection::open(&db_path).unwrap()), + db_path, + } + } + + pub fn create_tables(&self) { + let conn = self.conn.lock().unwrap(); + conn.execute(CREATE_TABLE_HOSTMEM, params![]).unwrap(); + } + + pub fn insert_host_meminfo(&self, meminfo: Meminfo) { + let conn = self.conn.lock().unwrap(); + + conn.execute("INSERT INTO stats_hostmem(total_kb, available_kb) VALUES (?1, ?2)", params![meminfo.total, meminfo.avaialable]).unwrap(); + } + + pub fn get_last_host_meminfo(&self) -> DbMeminfo { + let conn = self.conn.lock().unwrap(); + + conn.query_row("SELECT * FROM stats_hostmem ORDER BY stamp DESC LIMIT 1", [], |row| { + let (stamp, total_kb, available_kb) = row.try_into().unwrap(); + + Ok(DbMeminfo { + stamp, total_kb, available_kb + }) + }).optional().unwrap().unwrap() + } +} + +pub const CREATE_TABLE_HOSTMEM: &'static str = "\ + CREATE TABLE IF NOT EXISTS stats_hostmem ( + stamp TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + total_kb INTEGER NOT NULL, + available_kb INTEGER NOT NULL + );"; + +pub struct DbMeminfo { + pub stamp: OffsetDateTime, + pub total_kb: usize, + pub available_kb: usize +} + +impl DbMeminfo { + pub fn usage(&self) -> usize { + self.total_kb - self.available_kb + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index fa1ac5c..26e1cf0 100755 --- a/src/main.rs +++ b/src/main.rs @@ -7,12 +7,13 @@ mod settings; mod templated; mod timeparse; mod util; +mod db; -use std::{io::Write, os::unix::fs::MetadataExt, str::FromStr}; +use std::{fs::File, io::{BufRead, BufReader, Write}, os::unix::fs::MetadataExt, str::FromStr, sync::Arc, time::Duration}; use axum::{ body::Body, - extract::Path, + extract::{Path, State}, http::{header, StatusCode}, response::Response, routing::get, @@ -21,9 +22,11 @@ use axum::{ use bempline::{variables, Document, Options}; use camino::Utf8PathBuf; use confindent::{Confindent, Node}; +use db::Database; pub use error::RuntimeError; use fs::Filesystem; use settings::Settings; +use templated::Frontmatter; use tokio_util::io::ReaderStream; use tracing_subscriber::{fmt::time, prelude::*, EnvFilter}; use util::{Referer, RemoteIp, SessionId}; @@ -33,6 +36,11 @@ use crate::{ templated::Templated, }; +#[derive(Clone)] +pub struct AwakeState { + pub database: Arc<Database> +} + #[tokio::main] async fn main() { match std::env::args().nth(1).as_deref() { @@ -55,9 +63,27 @@ async fn main() { let webroot: Utf8PathBuf = conf.child_parse("Webroot").unwrap(); let templates = conf.child_value("Templates").unwrap(); let hostname = conf.child_owned("Hostname").unwrap(); + let dbpath = conf.child_owned("Database").unwrap(); + + let database = Database::new(dbpath.into()); + database.create_tables(); let fs = Filesystem::new(&webroot); + let state = AwakeState { + database: Arc::new(database) + }; + + let mi_state = state.clone(); + let meminfo_thread = std::thread::spawn(move || { + loop { + let meminfo = Meminfo::current(); + mi_state.database.insert_host_meminfo(meminfo); + + std::thread::sleep(Duration::from_secs(60)); + } + }); + let settings = Settings { template_dir: Utf8PathBuf::from(webroot.join(templates)) .canonicalize_utf8() @@ -69,29 +95,31 @@ async fn main() { .route("/", get(index_handler)) .route("/*path", get(handler)) .layer(Extension(fs)) - .layer(Extension(settings)); + .layer(Extension(settings)).with_state(state); let listener = tokio::net::TcpListener::bind("0.0.0.0:2560").await.unwrap(); axum::serve(listener, app).await.unwrap() } async fn index_handler( + state: State<AwakeState>, fse: Extension<Filesystem>, se: Extension<Settings>, sid: SessionId, rfr: Option<Referer>, ) -> Response { - handler(fse, se, sid, rfr, Path(String::from("/"))).await + handler(state, fse, se, sid, rfr, Path(String::from("/"))).await } async fn handler( + State(state): State<AwakeState>, Extension(fs): Extension<Filesystem>, Extension(settings): Extension<Settings>, sid: SessionId, rfr: Option<Referer>, Path(path): Path<String>, ) -> Response { - match falible_handler(fs, settings, sid, rfr, path).await { + match falible_handler(state, fs, settings, sid, rfr, path).await { Ok(resp) => resp, Err(re) => Response::builder() .body(Body::from(re.to_string())) @@ -100,6 +128,7 @@ async fn handler( } async fn falible_handler( + state: AwakeState, fs: Filesystem, settings: Settings, sid: SessionId, @@ -134,7 +163,7 @@ async fn falible_handler( let result = Templated::from_str(&content); match result { - Ok(templated) => send_template(templated, resolve, webpath, settings, sid).await, + Ok(templated) => send_template(templated, resolve, webpath, state, settings, sid).await, Err(e) => { tracing::warn!("error sending template {e}"); @@ -203,6 +232,7 @@ async fn send_template( templated: Templated, resolve: PathResolution, webpath: Webpath, + state: AwakeState, settings: Settings, sid: SessionId, ) -> Result<Response, RuntimeError> { @@ -335,7 +365,11 @@ async fn send_template( tracing::trace!("finished published block"); // insert the page content itself - let markedup = markup::process(&templated.content); + let mut markedup = markup::process(&templated.content); + if templated.frontmatter.get("use-template").is_some() { + markedup = template_content(state, &templated.frontmatter, markedup); + } + template.set("main", markedup); Ok(Response::builder() @@ -343,3 +377,66 @@ async fn send_template( .body(Body::from(template.compile())) .unwrap()) } + +fn template_content(state: AwakeState, frontmatter: &Frontmatter, marked: String) -> String { + let Ok(mut doc) = Document::from_str(&marked, Options::default()) else { + return marked + }; + + if frontmatter.get("system-stats").is_some() { + let mem = state.database.get_last_host_meminfo(); + + doc.set("stats.mem.total", mem.total_kb / 1024); + doc.set("stats.mem.usage", mem.usage() / 1024); + } + + doc.compile() +} + +struct Meminfo { + pub total: usize, + pub free: usize, + pub avaialable: usize +} + +impl Meminfo { + pub fn current() -> Self { + let procinfo = File::open("/proc/meminfo").unwrap(); + let bread = BufReader::new(procinfo); + + let mut meminfo = Meminfo { + total: 0, + free: 0, + avaialable: 0 + }; + + for line in bread.lines() { + let line = line.unwrap(); + + if let Some((raw_key, raw_value_kb)) = line.split_once(':') { + let value = if let Some(raw_value) = raw_value_kb.trim().strip_suffix(" kB") { + if let Ok(parsed) = raw_value.parse() { + parsed + } else { + continue; + } + } else { + continue; + }; + + match raw_key.trim() { + "MemTotal" => meminfo.total = value, + "MemFree" => meminfo.free = value, + "MemAvailable" => meminfo.avaialable = value, + _ => () + } + } + } + + meminfo + } + + pub fn usage(&self) -> usize { + self.total - self.avaialable + } +} \ No newline at end of file |