rust_supervisor/dashboard/
state.rs1use crate::dashboard::events::{journal_to_event_records, log_record_for_event};
8use crate::dashboard::model::{
9 DashboardCriticality, DashboardState, RegistrationState, RuntimeState, SupervisorEdge,
10 SupervisorEdgeKind, SupervisorNode, SupervisorNodeKind, SupervisorTopology,
11 TargetConnectionState, TargetProcessIdentity,
12};
13use crate::id::types::SupervisorPath;
14use crate::journal::ring::EventJournal;
15use crate::spec::child::Criticality;
16use crate::spec::supervisor::SupervisorSpec;
17use crate::state::supervisor::SupervisorState;
18use std::collections::BTreeMap;
19
20#[derive(Debug, Clone)]
22pub struct DashboardStateInput {
23 pub target_id: String,
25 pub display_name: String,
27 pub state_generation: u64,
29 pub recent_limit: usize,
31}
32
33pub fn build_dashboard_state(
46 input: DashboardStateInput,
47 spec: &SupervisorSpec,
48 state: &SupervisorState,
49 journal: &EventJournal,
50) -> DashboardState {
51 let config_version = spec.config_version.clone();
52 let recent_events = journal_to_event_records(
53 &input.target_id,
54 &config_version,
55 journal,
56 input.recent_limit,
57 );
58 let recent_logs = recent_events
59 .iter()
60 .map(|event| log_record_for_event(event, format!("event {}", event.event_type)))
61 .collect::<Vec<_>>();
62 DashboardState {
63 target: TargetProcessIdentity {
64 target_id: input.target_id,
65 display_name: input.display_name,
66 registration_state: RegistrationState::Active,
67 connection_state: TargetConnectionState::Registered,
68 },
69 topology: topology_from_spec(spec),
70 runtime_state: runtime_state_rows(state),
71 recent_events,
72 recent_logs,
73 dropped_event_count: journal.dropped_count,
74 dropped_log_count: 0,
75 config_version,
76 generated_at_unix_nanos: state.generated_at_unix_nanos,
77 state_generation: input.state_generation,
78 }
79}
80
81pub fn topology_from_spec(spec: &SupervisorSpec) -> SupervisorTopology {
91 let root_path = spec.path.to_string();
92 let root = SupervisorNode {
93 node_id: root_path.clone(),
94 child_id: None,
95 path: root_path.clone(),
96 name: "root supervisor".to_owned(),
97 kind: SupervisorNodeKind::RootSupervisor,
98 tags: Vec::new(),
99 criticality: DashboardCriticality::Critical,
100 state_summary: "root".to_owned(),
101 diagnostics: BTreeMap::new(),
102 };
103 let mut nodes = vec![root.clone()];
104 let mut edges = Vec::new();
105 let mut declaration_order = vec![root_path.clone()];
106 for (index, child) in spec.children.iter().enumerate() {
107 let child_path = spec.path.join(child.id.value.clone()).to_string();
108 declaration_order.push(child_path.clone());
109 nodes.push(SupervisorNode {
110 node_id: child_path.clone(),
111 child_id: Some(child.id.to_string()),
112 path: child_path.clone(),
113 name: child.name.clone(),
114 kind: SupervisorNodeKind::ChildTask,
115 tags: child.tags.clone(),
116 criticality: criticality(child.criticality),
117 state_summary: "declared".to_owned(),
118 diagnostics: BTreeMap::new(),
119 });
120 edges.push(SupervisorEdge {
121 edge_id: format!("parent:{root_path}->{child_path}"),
122 source_path: root_path.clone(),
123 target_path: child_path.clone(),
124 kind: SupervisorEdgeKind::ParentChild,
125 order: index,
126 });
127 for (dependency_index, dependency) in child.dependencies.iter().enumerate() {
128 let dependency_path = spec.path.join(dependency.value.clone()).to_string();
129 edges.push(SupervisorEdge {
130 edge_id: format!("dependency:{dependency_path}->{child_path}"),
131 source_path: dependency_path,
132 target_path: child_path.clone(),
133 kind: SupervisorEdgeKind::Dependency,
134 order: dependency_index,
135 });
136 }
137 }
138 SupervisorTopology {
139 root,
140 nodes,
141 edges,
142 declaration_order,
143 }
144}
145
146pub fn runtime_state_rows(state: &SupervisorState) -> Vec<RuntimeState> {
156 state
157 .children
158 .values()
159 .map(|child| RuntimeState {
160 child_path: child.path.to_string(),
161 lifecycle_state: child.state.as_label().to_owned(),
162 health: format!("{:?}", child.health).to_lowercase(),
163 readiness: format!("{:?}", child.readiness).to_lowercase(),
164 generation: child.generation.value,
165 attempt: child.attempt.value,
166 restart_count: child.restart_count,
167 last_failure: child
168 .last_failure
169 .as_ref()
170 .map(|failure| format!("{failure:?}")),
171 last_policy_decision: child
172 .last_policy_decision
173 .as_ref()
174 .map(|decision| decision.decision.clone()),
175 shutdown_state: format!("{:?}", state.shutdown_state).to_lowercase(),
176 })
177 .collect()
178}
179
180fn criticality(value: Criticality) -> DashboardCriticality {
190 match value {
191 Criticality::Critical => DashboardCriticality::Critical,
192 Criticality::Optional => DashboardCriticality::Standard,
193 }
194}
195
196pub fn declared_state_from_spec(spec: &SupervisorSpec) -> SupervisorState {
206 spec.children.iter().fold(
207 SupervisorState::new(
208 SupervisorPath::root(),
209 crate::event::time::EventSequence::new(1),
210 1,
211 ),
212 |state, child| {
213 let path = spec.path.join(child.id.value.clone());
214 state.with_child(crate::state::child::ChildState::declared(
215 path,
216 child.id.clone(),
217 child.name.clone(),
218 ))
219 },
220 )
221}