Chart session checkpoints as their own cache bucket
This commit is contained in:
@@ -371,21 +371,25 @@ impl App {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disc usage of both cache buckets, split by age. The bar is the age
|
/// Disc usage of both cache buckets. The bar splits the transient store by
|
||||||
/// distribution of what is stored; the budget governs the transient store
|
/// age — the order the budget evicts in — and carries the session
|
||||||
/// only and is reported on its own row.
|
/// checkpoints as one further segment, since those are only freed with
|
||||||
|
/// their session and so compete for disc without ever ageing out.
|
||||||
fn cache_explorer(&self) -> Element<'_, Message> {
|
fn cache_explorer(&self) -> Element<'_, Message> {
|
||||||
let usage = &self.kv_cache_report;
|
let usage = &self.kv_cache_report;
|
||||||
let total = usage.total_bytes();
|
let total = usage.total_bytes();
|
||||||
let capacity = total.max(1);
|
let capacity = total.max(1);
|
||||||
|
let segments = crate::metrics::CACHE_AGE_BUCKETS
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, (label, _))| (*label, usage.age_bytes[index], AGE_COLORS[index]))
|
||||||
|
.chain([("Sessions", usage.session_bytes, SESSION_COLOR)]);
|
||||||
let mut bar = row![].height(14).spacing(2);
|
let mut bar = row![].height(14).spacing(2);
|
||||||
let mut legend = column![].spacing(6);
|
let mut legend = column![].spacing(6);
|
||||||
for (index, (label, _)) in crate::metrics::CACHE_AGE_BUCKETS.iter().enumerate() {
|
for (label, bytes, color) in segments {
|
||||||
let bytes = usage.age_bytes[index];
|
|
||||||
if bytes == 0 {
|
if bytes == 0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let color = AGE_COLORS[index];
|
|
||||||
bar = bar.push(
|
bar = bar.push(
|
||||||
container(Space::new(Length::Fill, Length::Fill))
|
container(Space::new(Length::Fill, Length::Fill))
|
||||||
.width(Length::FillPortion(portion(bytes, capacity)))
|
.width(Length::FillPortion(portion(bytes, capacity)))
|
||||||
@@ -394,7 +398,7 @@ impl App {
|
|||||||
legend = legend.push(
|
legend = legend.push(
|
||||||
row![
|
row![
|
||||||
container(Space::new(9, 9)).style(move |_| chart_bar_style(color)),
|
container(Space::new(9, 9)).style(move |_| chart_bar_style(color)),
|
||||||
text(*label).size(12).color(muted_text()),
|
text(label).size(12).color(muted_text()),
|
||||||
Space::with_width(Length::Fill),
|
Space::with_width(Length::Fill),
|
||||||
text(format!(
|
text(format!(
|
||||||
"{} · {:.0}%",
|
"{} · {:.0}%",
|
||||||
@@ -497,6 +501,9 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sessions sit outside the age scale: they are not evicted, only deleted.
|
||||||
|
const SESSION_COLOR: Color = Color::from_rgb(0.85, 0.44, 0.56);
|
||||||
|
|
||||||
/// Age buckets from green (fresh) to grey (past every hit half-life).
|
/// Age buckets from green (fresh) to grey (past every hit half-life).
|
||||||
const AGE_COLORS: [Color; 5] = [
|
const AGE_COLORS: [Color; 5] = [
|
||||||
Color::from_rgb(0.28, 0.69, 0.44),
|
Color::from_rgb(0.28, 0.69, 0.44),
|
||||||
|
|||||||
@@ -707,7 +707,8 @@ pub(crate) struct KvCacheReport {
|
|||||||
pub(crate) session_files: u64,
|
pub(crate) session_files: u64,
|
||||||
pub(crate) transient_bytes: u64,
|
pub(crate) transient_bytes: u64,
|
||||||
pub(crate) transient_files: u64,
|
pub(crate) transient_files: u64,
|
||||||
/// Bytes per [`CACHE_AGE_BUCKETS`] entry, both buckets combined.
|
/// Bytes per [`CACHE_AGE_BUCKETS`] entry, transient store only: session
|
||||||
|
/// checkpoints never age out, so their age says nothing about eviction.
|
||||||
pub(crate) age_bytes: [u64; CACHE_AGE_BUCKETS.len()],
|
pub(crate) age_bytes: [u64; CACHE_AGE_BUCKETS.len()],
|
||||||
pub(crate) entries: Vec<KvCacheEntry>,
|
pub(crate) entries: Vec<KvCacheEntry>,
|
||||||
}
|
}
|
||||||
@@ -752,11 +753,13 @@ pub(crate) fn kv_cache_report(root: &Path, budget_bytes: u64) -> KvCacheReport {
|
|||||||
.ok()
|
.ok()
|
||||||
.and_then(|modified| now.duration_since(modified).ok())
|
.and_then(|modified| now.duration_since(modified).ok())
|
||||||
.map_or(0, |age| age.as_secs());
|
.map_or(0, |age| age.as_secs());
|
||||||
|
if transient {
|
||||||
let bucket = CACHE_AGE_BUCKETS
|
let bucket = CACHE_AGE_BUCKETS
|
||||||
.iter()
|
.iter()
|
||||||
.position(|(_, limit)| age_seconds < *limit)
|
.position(|(_, limit)| age_seconds < *limit)
|
||||||
.unwrap_or(CACHE_AGE_BUCKETS.len() - 1);
|
.unwrap_or(CACHE_AGE_BUCKETS.len() - 1);
|
||||||
report.age_bytes[bucket] += bytes;
|
report.age_bytes[bucket] += bytes;
|
||||||
|
}
|
||||||
// Index files are counted in the totals but folded into their
|
// Index files are counted in the totals but folded into their
|
||||||
// checkpoint's row.
|
// checkpoint's row.
|
||||||
if path.extension().is_some_and(|value| value == "meta") {
|
if path.extension().is_some_and(|value| value == "meta") {
|
||||||
@@ -915,8 +918,9 @@ mod tests {
|
|||||||
assert_eq!(report.session_files, 1);
|
assert_eq!(report.session_files, 1);
|
||||||
assert_eq!(report.transient_bytes, 140);
|
assert_eq!(report.transient_bytes, 140);
|
||||||
assert_eq!(report.transient_files, 2);
|
assert_eq!(report.transient_files, 2);
|
||||||
// Fresh files in the first bucket, the two-day-old one in "1–7d".
|
// Only the transient store is bucketed by age: its fresh index file in
|
||||||
assert_eq!(report.age_bytes[0], 340);
|
// the first bucket, its two-day-old checkpoint in "1–7d".
|
||||||
|
assert_eq!(report.age_bytes[0], 40);
|
||||||
assert_eq!(report.age_bytes[3], 100);
|
assert_eq!(report.age_bytes[3], 100);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
(
|
(
|
||||||
|
|||||||
Reference in New Issue
Block a user