ralph/execution_history/
storage.rs1use std::fs;
16use std::path::{Path, PathBuf};
17use std::time::Duration;
18
19use anyhow::{Context, Result};
20
21use crate::progress::ExecutionPhase;
22
23use super::model::{ExecutionEntry, ExecutionHistory};
24
25const EXECUTION_HISTORY_FILE: &str = "execution_history.json";
26const MAX_ENTRIES: usize = 100;
27
28fn execution_history_path(cache_dir: &Path) -> PathBuf {
29 cache_dir.join(EXECUTION_HISTORY_FILE)
30}
31
32pub fn load_execution_history(cache_dir: &Path) -> Result<ExecutionHistory> {
34 let path = execution_history_path(cache_dir);
35 if !path.exists() {
36 return Ok(ExecutionHistory::default());
37 }
38
39 let content = fs::read_to_string(&path)
40 .with_context(|| format!("Failed to read execution history from {}", path.display()))?;
41 serde_json::from_str(&content)
42 .with_context(|| format!("Failed to parse execution history from {}", path.display()))
43}
44
45pub fn save_execution_history(history: &ExecutionHistory, cache_dir: &Path) -> Result<()> {
47 fs::create_dir_all(cache_dir)
48 .with_context(|| format!("Failed to create cache directory {}", cache_dir.display()))?;
49
50 let path = execution_history_path(cache_dir);
51 let content =
52 serde_json::to_string_pretty(history).context("Failed to serialize execution history")?;
53 crate::fsutil::write_atomic(&path, content.as_bytes())
54 .with_context(|| format!("Failed to persist execution history to {}", path.display()))
55}
56
57pub fn record_execution(
59 task_id: &str,
60 runner: &str,
61 model: &str,
62 phase_count: u8,
63 phase_durations: std::collections::HashMap<ExecutionPhase, Duration>,
64 total_duration: Duration,
65 cache_dir: &Path,
66) -> Result<()> {
67 let mut history = load_execution_history(cache_dir)?;
68 history.entries.push(ExecutionEntry {
69 timestamp: crate::timeutil::now_utc_rfc3339_or_fallback(),
70 task_id: task_id.to_string(),
71 runner: runner.to_string(),
72 model: model.to_string(),
73 phase_count,
74 phase_durations,
75 total_duration,
76 });
77
78 prune_old_entries(&mut history);
79 save_execution_history(&history, cache_dir)
80}
81
82pub(crate) fn prune_old_entries(history: &mut ExecutionHistory) {
84 if history.entries.len() <= MAX_ENTRIES {
85 return;
86 }
87
88 history
89 .entries
90 .sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
91 history.entries.truncate(MAX_ENTRIES);
92}