c0re: history retention is a policy, split from the graph it reads
`visible_dags` mixed two things: walking the graph to classify containers live-vs-terminal, and the sort-and-truncate that decides what the dashboard sees. `retain_history` is the second half, generic over the handle so it is reachable without a graph at all — a `NodeId` cannot be fabricated, so a test forced to pass real ones could only get them by submitting and running DAGs. Which is exactly what the old test did: `MAX_HISTORY_DAGS + 8` submits, claim and fail each node, read the ids back out of a snapshot — the scheduler, the roll-up and the wire projection all standing in the path of a policy that reads none of them. And it only ever exercised the tiebreak, because every DAG in that loop settled inside the same wall-clock second, so `finished_at` tied on all of them. Eviction *by time* — the actual policy — had no coverage. It does now, along with the live-never-competes case. `live_count` and `templates::reconcile_only` were both test-only and lose their last caller here.
This commit is contained in:
parent
5f5898d167
commit
ab5744a2bd
3 changed files with 72 additions and 68 deletions
|
|
@ -363,17 +363,6 @@ impl JobQueue {
|
|||
.filter_map(|c| dag_view(&inner, c))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Number of live (non-terminal) DAGs — tests + diagnostics.
|
||||
#[cfg(test)]
|
||||
#[must_use]
|
||||
pub fn live_count(&self) -> usize {
|
||||
let inner = self.lock();
|
||||
containers(&inner)
|
||||
.into_iter()
|
||||
.filter(|&c| inner.graph().is_settled(c) == Some(false))
|
||||
.count()
|
||||
}
|
||||
}
|
||||
|
||||
/// The container node of `dag_id` — the `NodeKind::Dag` root whose id equals
|
||||
|
|
@ -535,19 +524,34 @@ fn containers(sched: &Sched) -> Vec<NodeId> {
|
|||
/// this filter is what bounds what the dashboard sees.
|
||||
fn visible_dags(sched: &Sched) -> Vec<NodeId> {
|
||||
let mut live: Vec<NodeId> = Vec::new();
|
||||
let mut terminal: Vec<(NodeId, i64)> = Vec::new();
|
||||
let mut terminal: Vec<(NodeId, i64, u64)> = Vec::new();
|
||||
for c in containers(sched) {
|
||||
if sched.graph().is_settled(c) == Some(true) {
|
||||
terminal.push((c, dag_finished_at(sched, c)));
|
||||
terminal.push((c, dag_finished_at(sched, c), c.get()));
|
||||
} else {
|
||||
live.push(c);
|
||||
}
|
||||
}
|
||||
retain_history(live, terminal, MAX_HISTORY_DAGS)
|
||||
}
|
||||
|
||||
/// [`visible_dags`]'s policy, split from the graph it reads: keep every live
|
||||
/// DAG, plus the newest `cap` terminal ones.
|
||||
///
|
||||
/// `terminal` rows are `(handle, finished_at, tiebreak)`. The tiebreak orders
|
||||
/// DAGs that settled inside the same wall-clock second — which is *most* of
|
||||
/// them under a burst, and all of them in a test, so it is load-bearing rather
|
||||
/// than a formality.
|
||||
///
|
||||
/// Generic over the handle purely so this is reachable without a graph: a
|
||||
/// `NodeId` cannot be fabricated, so a test that had to pass real ones could
|
||||
/// only get them by submitting and running DAGs.
|
||||
fn retain_history<T>(live: Vec<T>, mut terminal: Vec<(T, i64, u64)>, cap: usize) -> Vec<T> {
|
||||
// Newest first, so truncating to the cap keeps the most recent.
|
||||
terminal.sort_by(|a, b| b.1.cmp(&a.1).then(b.0.get().cmp(&a.0.get())));
|
||||
terminal.truncate(MAX_HISTORY_DAGS);
|
||||
terminal.sort_by(|a, b| b.1.cmp(&a.1).then(b.2.cmp(&a.2)));
|
||||
terminal.truncate(cap);
|
||||
let mut kept = live;
|
||||
kept.extend(terminal.into_iter().map(|(c, _)| c));
|
||||
kept.extend(terminal.into_iter().map(|(handle, _, _)| handle));
|
||||
kept
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue