use std::sync::Arc;
use blue_lang_mondou::recall::{Plan, Policy, Recall, Report};
use blue_lang_mondou::store::{self, Identity, Store};
use blue_lang_runtime::observe::{CallObserver, Definitions};
pub(crate) const ENV: &str = "BLUE_MEMO";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Mode {
Off,
Process,
Store,
}
impl Mode {
pub(crate) fn parse(value: &str) -> Option<Self> {
match value.trim().to_ascii_lowercase().as_str() {
"" | "off" | "0" | "false" | "no" => Some(Mode::Off),
"process" => Some(Mode::Process),
"on" | "1" | "true" | "yes" | "store" => Some(Mode::Store),
_ => None,
}
}
pub(crate) fn persist(self) -> bool {
self == Mode::Store
}
pub(crate) fn of(no_memo: bool, env: Option<&str>, cfg: &crate::config::BlueConfig) -> Self {
if no_memo || cfg.execution_bounds() != blue_lang_runtime::ExecutionBounds::DEFAULT {
return Mode::Off;
}
env.and_then(Mode::parse).unwrap_or(Mode::Off)
}
}
pub(crate) fn mode(no_memo: bool, cfg: &crate::config::BlueConfig) -> Mode {
Mode::of(no_memo, std::env::var(ENV).ok().as_deref(), cfg)
}
pub(crate) fn unless_bounded(
mode: Mode,
bounds: Option<blue_lang_runtime::ExecutionBounds>,
) -> Mode {
if bounds.is_some() {
Mode::Off
} else {
mode
}
}
fn store() -> Option<Arc<Store>> {
let root = store::default_root()?;
Store::open(&root, Identity::of_this_build(), store::DEFAULT_MAX_BYTES)
.ok()
.map(Arc::new)
}
pub(crate) fn over(
persist: bool,
definitions: &Arc<Definitions>,
lowered: &[blue_lang_syntax::Spanned],
erased: &[blue_lang_syntax::Spanned],
inner: Option<Arc<dyn CallObserver>>,
) -> Arc<Recall> {
let plan = Plan::of(definitions, lowered, erased);
Arc::new(Recall::new(
Arc::clone(definitions),
plan,
if persist { store() } else { None },
inner,
Policy::default(),
))
}
pub(crate) fn finish(recall: &Recall) {
let _ = recall.flush();
}
#[allow(clippy::cast_precision_loss)]
fn ms(d: std::time::Duration) -> f64 {
d.as_secs_f64() * 1000.0
}
pub(crate) fn print(report: &Report) {
let t = report.total();
println!(
"memo: {} hit(s) ({} from disk), {} miss(es), {} stored, {:.2} ms saved{}",
t.hits + t.disk_hits,
t.disk_hits,
t.misses,
t.stored,
ms(t.saved),
if report.damaged > 0 {
format!(", {} damaged store entr(ies) ignored", report.damaged)
} else {
String::new()
}
);
if report.rows.is_empty() {
return;
}
println!(
" {:>8} {:>8} {:>8} {:>8} {:>10} {:<32}",
"hits", "disk", "misses", "stored", "saved ms", "function"
);
let mut rows: Vec<_> = report.rows.iter().collect();
rows.sort_by(|a, b| {
b.counts
.saved
.cmp(&a.counts.saved)
.then_with(|| a.name.cmp(&b.name))
});
for r in rows {
let c = &r.counts;
println!(
" {:>8} {:>8} {:>8} {:>8} {:>10.2} {:<32}{}",
c.hits,
c.disk_hits,
c.misses,
c.stored,
ms(c.saved),
r.name,
if c.retired { " (stopped looking)" } else { "" }
);
}
}
pub(crate) fn json(report: &Report) -> serde_json::Value {
let t = report.total();
serde_json::json!({
"hits": t.hits,
"disk_hits": t.disk_hits,
"misses": t.misses,
"stored": t.stored,
"saved_ms": ms(t.saved),
"damaged": report.damaged,
"store": report.store,
"functions": report.rows.iter().map(|r| serde_json::json!({
"name": r.name,
"hits": r.counts.hits,
"disk_hits": r.counts.disk_hits,
"misses": r.counts.misses,
"stored": r.counts.stored,
"saved_ms": ms(r.counts.saved),
"retired": r.counts.retired,
})).collect::<Vec<_>>(),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::BlueConfig;
#[test]
fn the_memo_is_off_unless_the_environment_turns_it_on() {
let cfg = BlueConfig::default();
assert_eq!(Mode::of(false, None, &cfg), Mode::Off);
assert_eq!(Mode::of(false, Some("off"), &cfg), Mode::Off);
assert_eq!(Mode::of(false, Some("maybe"), &cfg), Mode::Off);
assert_eq!(Mode::of(false, Some("process"), &cfg), Mode::Process);
assert_eq!(Mode::of(false, Some(" On "), &cfg), Mode::Store);
assert_eq!(Mode::of(false, Some("store"), &cfg), Mode::Store);
}
#[test]
fn no_memo_and_a_changed_bound_turn_it_off_whatever_the_environment_says() {
let cfg = BlueConfig::default();
assert_eq!(Mode::of(true, Some("on"), &cfg), Mode::Off);
let bounded = BlueConfig {
max_steps: Some(1_000),
..BlueConfig::default()
};
assert_eq!(Mode::of(false, Some("on"), &bounded), Mode::Off);
}
}