stat page and cache working

This commit is contained in:
2025-06-25 18:03:44 +02:00
parent 0878b7dcec
commit 2d373da4c5
6 changed files with 248 additions and 5 deletions

View File

@@ -25,6 +25,8 @@ enum Commands {
},
/// Watch for changes in the posts directory
Watch,
/// Show Rust parser statistics
Rsparseinfo,
}
fn main() {
@@ -65,5 +67,8 @@ fn main() {
std::thread::sleep(std::time::Duration::from_secs(60));
}
}
Commands::Rsparseinfo => {
println!("{}", markdown::rsparseinfo());
}
}
}

View File

@@ -11,11 +11,14 @@ use ammonia::clean;
use slug::slugify;
use notify::{RecursiveMode, RecommendedWatcher, Watcher, Config};
use std::sync::mpsc::channel;
use std::time::Duration;
use std::time::{Duration, Instant};
use syntect::highlighting::{ThemeSet, Style};
use syntect::parsing::SyntaxSet;
use syntect::html::{highlighted_html_for_string, IncludeBackground};
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::RwLock;
use serde_json;
#[derive(Debug, Deserialize, Clone, serde::Serialize)]
pub struct PostFrontmatter {
@@ -37,6 +40,19 @@ pub struct Post {
pub author: String,
}
#[derive(Debug, Clone, serde::Serialize, Default)]
pub struct PostStats {
pub slug: String,
pub cache_hits: u64,
pub cache_misses: u64,
pub last_interpret_time_ms: u128,
pub last_compile_time_ms: u128,
}
static POST_CACHE: Lazy<RwLock<HashMap<String, Post>>> = Lazy::new(|| RwLock::new(HashMap::new()));
static ALL_POSTS_CACHE: Lazy<RwLock<Option<Vec<Post>>>> = Lazy::new(|| RwLock::new(None));
static POST_STATS: Lazy<RwLock<HashMap<String, PostStats>>> = Lazy::new(|| RwLock::new(HashMap::new()));
fn get_posts_directory() -> PathBuf {
let candidates = [
"./posts",
@@ -111,7 +127,27 @@ static AMMONIA: Lazy<ammonia::Builder<'static>> = Lazy::new(|| {
builder
});
pub fn rsparseinfo() -> String {
let stats = POST_STATS.read().unwrap();
serde_json::to_string(&stats.values().collect::<Vec<_>>()).unwrap_or_else(|_| "[]".to_string())
}
pub fn get_post_by_slug(slug: &str) -> Result<Post, Box<dyn std::error::Error>> {
let start = Instant::now();
let mut stats = POST_STATS.write().unwrap();
let entry = stats.entry(slug.to_string()).or_insert_with(|| PostStats {
slug: slug.to_string(),
..Default::default()
});
// Try cache first
if let Some(post) = POST_CACHE.read().unwrap().get(slug).cloned() {
entry.cache_hits += 1;
entry.last_interpret_time_ms = 0;
entry.last_compile_time_ms = 0;
return Ok(post);
}
entry.cache_misses += 1;
drop(stats); // Release lock before heavy work
let posts_dir = get_posts_directory();
let file_path = posts_dir.join(format!("{}.md", slug));
let file_content = fs::read_to_string(&file_path)?;
@@ -202,7 +238,9 @@ pub fn get_post_by_slug(slug: &str) -> Result<Post, Box<dyn std::error::Error>>
let sanitized_html = AMMONIA.clean(&html_output).to_string();
Ok(Post {
let interpret_time = start.elapsed();
let compile_start = Instant::now();
let post = Post {
slug: slug.to_string(),
title: front.title,
date: front.date,
@@ -211,10 +249,26 @@ pub fn get_post_by_slug(slug: &str) -> Result<Post, Box<dyn std::error::Error>>
content: sanitized_html,
created_at: created_at.to_rfc3339(),
author: std::env::var("BLOG_OWNER").unwrap_or_else(|_| "Anonymous".to_string()),
})
};
let compile_time = compile_start.elapsed();
// Insert into cache
POST_CACHE.write().unwrap().insert(slug.to_string(), post.clone());
// Update stats
let mut stats = POST_STATS.write().unwrap();
let entry = stats.entry(slug.to_string()).or_insert_with(|| PostStats {
slug: slug.to_string(),
..Default::default()
});
entry.last_interpret_time_ms = interpret_time.as_millis();
entry.last_compile_time_ms = compile_time.as_millis();
Ok(post)
}
pub fn get_all_posts() -> Result<Vec<Post>, Box<dyn std::error::Error>> {
// Try cache first
if let Some(posts) = ALL_POSTS_CACHE.read().unwrap().clone() {
return Ok(posts);
}
let posts_dir = get_posts_directory();
let mut posts = Vec::new();
for entry in fs::read_dir(posts_dir)? {
@@ -228,6 +282,8 @@ pub fn get_all_posts() -> Result<Vec<Post>, Box<dyn std::error::Error>> {
}
}
posts.sort_by(|a, b| b.created_at.cmp(&a.created_at));
// Cache the result
*ALL_POSTS_CACHE.write().unwrap() = Some(posts.clone());
Ok(posts)
}
@@ -240,11 +296,13 @@ pub fn watch_posts<F: Fn() + Send + 'static>(on_change: F) -> notify::Result<Rec
let (tx, rx) = channel();
let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
watcher.watch(get_posts_directory().as_path(), RecursiveMode::Recursive)?;
std::thread::spawn(move || {
loop {
match rx.recv() {
Ok(_event) => {
// Invalidate caches on any change
POST_CACHE.write().unwrap().clear();
*ALL_POSTS_CACHE.write().unwrap() = None;
on_change();
},
Err(e) => {
@@ -254,6 +312,5 @@ pub fn watch_posts<F: Fn() + Send + 'static>(on_change: F) -> notify::Result<Rec
}
}
});
Ok(watcher)
}