Skip to main content

sim_lib_view_agent/
run.rs

1//! Run state: the live execution view of a topology.
2//!
3//! A run state accumulates execution events into per-node statuses, counters,
4//! an ordered event log, and a set of live edges. It is fed by subscriptions to
5//! a running topology (or, for replay, by a recorded event stream). It is plain
6//! data the monitor view renders; it holds no second topology model.
7
8use std::collections::{BTreeMap, BTreeSet};
9
10use sim_kernel::Symbol;
11
12/// A node's execution status.
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum NodeStatus {
15    /// Not yet started.
16    Idle,
17    /// Currently executing.
18    Running,
19    /// Completed successfully.
20    Ok,
21    /// Failed.
22    Error,
23}
24
25impl NodeStatus {
26    /// The status token (never color alone).
27    pub fn token(self) -> &'static str {
28        match self {
29            NodeStatus::Idle => "idle",
30            NodeStatus::Running => "running",
31            NodeStatus::Ok => "ok",
32            NodeStatus::Error => "error",
33        }
34    }
35}
36
37/// One execution event.
38#[derive(Clone, Debug, PartialEq, Eq)]
39pub struct RunEvent {
40    /// Logical time of the event.
41    pub at: u64,
42    /// The node the event concerns.
43    pub node: Symbol,
44    /// The event kind (`start`, `ok`, `error`, `route`, `log`).
45    pub kind: Symbol,
46    /// A human-readable message.
47    pub message: String,
48    /// For a `route` event, the edge `(from-node, to-node)` it traversed.
49    pub edge: Option<(Symbol, Symbol)>,
50}
51
52impl RunEvent {
53    /// A node lifecycle event.
54    pub fn node(at: u64, node: &str, kind: &str, message: &str) -> Self {
55        Self {
56            at,
57            node: Symbol::new(node),
58            kind: Symbol::new(kind),
59            message: message.to_owned(),
60            edge: None,
61        }
62    }
63
64    /// A routing event marking an edge live.
65    pub fn route(at: u64, from: &str, to: &str) -> Self {
66        Self {
67            at,
68            node: Symbol::new(from),
69            kind: Symbol::new("route"),
70            message: format!("{from} -> {to}"),
71            edge: Some((Symbol::new(from), Symbol::new(to))),
72        }
73    }
74}
75
76/// The accumulated live state of a run.
77#[derive(Clone, Debug, Default)]
78pub struct RunState {
79    /// Per-node status.
80    pub statuses: BTreeMap<Symbol, NodeStatus>,
81    /// Per-node event counter.
82    pub counters: BTreeMap<Symbol, u64>,
83    /// Ordered event log.
84    pub events: Vec<RunEvent>,
85    /// Currently live edges, by `(from-node, to-node)`.
86    pub live_edges: BTreeSet<(Symbol, Symbol)>,
87}
88
89impl RunState {
90    /// An empty run state.
91    pub fn new() -> Self {
92        Self::default()
93    }
94
95    /// Fold one event into the state.
96    pub fn apply_event(&mut self, event: RunEvent) {
97        *self.counters.entry(event.node.clone()).or_insert(0) += 1;
98        match &*event.kind.name {
99            "start" => {
100                self.statuses
101                    .insert(event.node.clone(), NodeStatus::Running);
102            }
103            "ok" => {
104                self.statuses.insert(event.node.clone(), NodeStatus::Ok);
105            }
106            "error" => {
107                self.statuses.insert(event.node.clone(), NodeStatus::Error);
108            }
109            "route" => {
110                if let Some(edge) = &event.edge {
111                    self.live_edges.insert(edge.clone());
112                }
113            }
114            _ => {}
115        }
116        self.events.push(event);
117    }
118
119    /// The status of a node (Idle if unseen).
120    pub fn status(&self, node: &Symbol) -> NodeStatus {
121        self.statuses.get(node).copied().unwrap_or(NodeStatus::Idle)
122    }
123
124    /// The event count of a node.
125    pub fn count(&self, node: &Symbol) -> u64 {
126        self.counters.get(node).copied().unwrap_or(0)
127    }
128
129    /// Whether an edge is currently live.
130    pub fn edge_live(&self, from: &Symbol, to: &Symbol) -> bool {
131        self.live_edges.contains(&(from.clone(), to.clone()))
132    }
133
134    /// The events that concern a node, in order (for drill-down).
135    pub fn events_for(&self, node: &Symbol) -> Vec<&RunEvent> {
136        self.events
137            .iter()
138            .filter(|event| &event.node == node)
139            .collect()
140    }
141}