Artwork panels: how full, the hit rate and evictions (#198)

Kept bytes against the limit was a flat line once the cache filled. Now a
percentage of the limit beside the share served from disk, and the images each
sweep evicts. The counter shipped minutes ago as ipx_art_trimmed_total is
renamed ipx_art_evicted_total before anything came to read it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-11 20:17:56 +00:00
parent f032d50ed3
commit 8dc6dd4ce6
6 changed files with 25 additions and 11 deletions

View File

@@ -98,7 +98,7 @@ pub fn trim(dir: &Path, limit: u64) -> usize {
}
}
crate::metrics::STATS.art_cache_bytes.store(total, std::sync::atomic::Ordering::Relaxed);
crate::metrics::STATS.art_trimmed.fetch_add(gone as u64, std::sync::atomic::Ordering::Relaxed);
crate::metrics::STATS.art_evicted.fetch_add(gone as u64, std::sync::atomic::Ordering::Relaxed);
gone
}

View File

@@ -32,7 +32,10 @@ pub struct Stats {
pub art_cache_bytes: AtomicU64,
/// Images the sweep threw out to keep under the limit: kept bytes sit at the limit by design,
/// so this is what says whether the limit is too small.
pub art_trimmed: AtomicU64,
pub art_evicted: AtomicU64,
/// Artwork served from disk, and fetched from the publisher because it was not there.
pub art_hits: AtomicU64,
pub art_misses: AtomicU64,
/// The library as the healthcheck last counted it, every 30 s; -1 until it has.
pub feeds: AtomicI64,
pub pending: AtomicI64,
@@ -55,7 +58,9 @@ impl Stats {
deadair_backlog: AtomicI64::new(0),
deadair_busy_since: AtomicI64::new(0),
art_cache_bytes: AtomicU64::new(0),
art_trimmed: AtomicU64::new(0),
art_evicted: AtomicU64::new(0),
art_hits: AtomicU64::new(0),
art_misses: AtomicU64::new(0),
feeds: AtomicI64::new(-1),
pending: AtomicI64::new(-1),
downloaded: AtomicI64::new(-1),
@@ -209,7 +214,9 @@ fn work(out: &mut String, st: &Stats, event_queue: usize, retries: usize, art_li
gauge(out, "ipx_feed_retries", "Feeds waiting to try a failed name lookup again.", &[("", retries as f64)]);
gauge(out, "ipx_art_cache_bytes", "Artwork kept on disk, as of the last sweep.", &[("", st.art_cache_bytes.load(Relaxed) as f64)]);
gauge(out, "ipx_art_cache_limit_bytes", "The most artwork kept.", &[("", art_limit as f64)]);
counter(out, "ipx_art_trimmed_total", "Images thrown out to keep the artwork under its limit.", &[("", st.art_trimmed.load(Relaxed) as f64)]);
counter(out, "ipx_art_evicted_total", "Images thrown out to keep the artwork under its limit.", &[("", st.art_evicted.load(Relaxed) as f64)]);
counter(out, "ipx_art_requests_total", "Artwork asked for: served from disk, or fetched from the publisher.",
&[("result=\"hit\"", st.art_hits.load(Relaxed) as f64), ("result=\"miss\"", st.art_misses.load(Relaxed) as f64)]);
for (name, help, v) in [
("ipx_feeds", "Feeds in the catalogue, as the healthcheck last counted.", &st.feeds),
("ipx_downloads_pending", "Files a scan will download on its own, as the healthcheck last counted.", &st.pending),

View File

@@ -2115,11 +2115,13 @@ async fn art(State(state): State<WebState>, Query(q): Query<ArtQuery>) -> Respon
let keep = state.ctx.cfg().general.art_cache_mb > 0;
let file = crate::art::path(&q.u);
if keep && let Some((kind, body)) = crate::art::read(&file) {
crate::metrics::STATS.art_hits.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
return art_response(kind, body);
}
if let Some(why) = crate::art::failed(&q.u) {
return (StatusCode::BAD_GATEWAY, why).into_response();
}
crate::metrics::STATS.art_misses.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let got = async {
let mut r = state.ctx.client.get(&q.u).timeout(crate::feed::FEED_TIMEOUT).send().await?.error_for_status()?;
let kind = r.headers().get(header::CONTENT_TYPE).and_then(|v| v.to_str().ok()).unwrap_or("").to_owned();