1use sim_kernel::{Expr, Symbol};
4use sim_lib_scene::{data_map, node, sym};
5use sim_lib_stream_core::BridgeLatency;
6use sim_lib_topology::{
7 DomainBridge, PlacedNode, PlacementRefusal, PlacementRefusalReason, PlacementReport,
8 PortLatency,
9};
10use sim_lib_web_bridge::BrowserPlacementReport;
11use sim_value::build::uint;
12
13pub const PLACEMENT_INSPECTOR_VIEW_ID: &str = "view:placement-inspector";
15pub const PLACEMENT_GRAPH_VIEW_ID: &str = "view:placement-graph";
17pub const PLACEMENT_BRIDGE_TABLE_VIEW_ID: &str = "view:placement-bridge-table";
19pub const PLACEMENT_LATENCY_BUDGET_VIEW_ID: &str = "view:placement-latency-budget";
21pub const PLACEMENT_REFUSAL_TABLE_VIEW_ID: &str = "view:placement-refusals";
23pub const PLACEMENT_BROWSER_DIAGNOSTICS_VIEW_ID: &str = "view:placement-browser-diagnostics";
25pub const PLACEMENT_RUNTIME_DIAGNOSTICS_VIEW_ID: &str = "view:placement-runtime-diagnostics";
27pub const PLACEMENT_FAULT_TIMELINE_VIEW_ID: &str = "view:placement-fault-timeline";
29
30pub const PLACEMENT_FORCED_REFUSAL_FAULT: &str = "forced-refusal";
32pub const PLACEMENT_JITTER_SPIKE_FAULT: &str = "jitter-spike";
34pub const PLACEMENT_WORKER_STALL_FAULT: &str = "worker-stall";
36pub const PLACEMENT_DISCONNECT_FAULT: &str = "disconnect";
38
39#[derive(Clone, Debug, PartialEq, Eq)]
41pub struct PlacementRuntimeDiagnostic {
42 source: Symbol,
43 diagnostic: Symbol,
44 count: u64,
45}
46
47impl PlacementRuntimeDiagnostic {
48 pub fn new(source: Symbol, diagnostic: Symbol, count: u64) -> Self {
50 Self {
51 source,
52 diagnostic,
53 count,
54 }
55 }
56
57 pub fn source(&self) -> &Symbol {
59 &self.source
60 }
61
62 pub fn diagnostic(&self) -> &Symbol {
64 &self.diagnostic
65 }
66
67 pub fn count(&self) -> u64 {
69 self.count
70 }
71}
72
73#[derive(Clone, Debug, PartialEq, Eq)]
75pub struct PlacementFaultFixture {
76 name: Symbol,
77 response: Symbol,
78 diagnostics: Vec<PlacementRuntimeDiagnostic>,
79}
80
81impl PlacementFaultFixture {
82 pub fn new(
84 name: Symbol,
85 response: Symbol,
86 diagnostics: Vec<PlacementRuntimeDiagnostic>,
87 ) -> Self {
88 Self {
89 name,
90 response,
91 diagnostics,
92 }
93 }
94
95 pub fn name(&self) -> &Symbol {
97 &self.name
98 }
99
100 pub fn response(&self) -> &Symbol {
102 &self.response
103 }
104
105 pub fn diagnostics(&self) -> &[PlacementRuntimeDiagnostic] {
107 &self.diagnostics
108 }
109}
110
111pub fn placement_fault_fixture_names() -> [&'static str; 4] {
113 [
114 PLACEMENT_FORCED_REFUSAL_FAULT,
115 PLACEMENT_JITTER_SPIKE_FAULT,
116 PLACEMENT_WORKER_STALL_FAULT,
117 PLACEMENT_DISCONNECT_FAULT,
118 ]
119}
120
121pub fn placement_fault_fixture(name: &str) -> Option<PlacementFaultFixture> {
123 let fixture = match name {
124 PLACEMENT_FORCED_REFUSAL_FAULT => fault_fixture(name, "refused", 1),
125 PLACEMENT_JITTER_SPIKE_FAULT => fault_fixture(name, "jitter-buffered", 3),
126 PLACEMENT_WORKER_STALL_FAULT => fault_fixture(name, "worker-restarted", 1),
127 PLACEMENT_DISCONNECT_FAULT => fault_fixture(name, "site-disconnected", 1),
128 _ => return None,
129 };
130 Some(fixture)
131}
132
133pub fn placement_inspector_view(report: &PlacementReport) -> Expr {
135 placement_inspector_view_with_diagnostics(report, &[], &[], &[])
136}
137
138pub fn placement_inspector_view_with_diagnostics(
140 report: &PlacementReport,
141 browser_reports: &[BrowserPlacementReport],
142 runtime_diagnostics: &[PlacementRuntimeDiagnostic],
143 faults: &[PlacementFaultFixture],
144) -> Expr {
145 node(
146 "stack",
147 vec![
148 ("lens", sym(PLACEMENT_INSPECTOR_VIEW_ID)),
149 ("role", sym("placement-inspector")),
150 ("dir", sym("column")),
151 ("accepted", Expr::Bool(report.is_accepted())),
152 (
153 "children",
154 Expr::List(vec![
155 placement_graph_view(report),
156 bridge_table_view(&report.bridges),
157 latency_budget_view(&report.latency),
158 refusal_table_view(&report.refusals),
159 runtime_diagnostics_view(runtime_diagnostics),
160 browser_diagnostics_view(browser_reports),
161 fault_timeline_view(faults),
162 ]),
163 ),
164 ],
165 )
166}
167
168fn placement_graph_view(report: &PlacementReport) -> Expr {
169 node(
170 "graph",
171 vec![
172 ("lens", sym(PLACEMENT_GRAPH_VIEW_ID)),
173 ("role", sym("placement-graph")),
174 (
175 "nodes",
176 Expr::List(report.placed.iter().map(placed_node_view).collect()),
177 ),
178 (
179 "edges",
180 Expr::List(report.bridges.iter().map(bridge_edge_view).collect()),
181 ),
182 ],
183 )
184}
185
186fn placed_node_view(placed: &PlacedNode) -> Expr {
187 node(
188 "node",
189 vec![
190 ("id", Expr::Symbol(placed.node.as_symbol().clone())),
191 (
192 "title",
193 Expr::String(placed.node.as_symbol().as_qualified_str()),
194 ),
195 ("site", Expr::Symbol(placed.site.as_symbol().clone())),
196 ("clock-domain", Expr::Symbol(placed.clock_domain.symbol())),
197 ("latency-class", Expr::Symbol(placed.latency_class.symbol())),
198 ("realtime-pin", Expr::Bool(placed.realtime_pin)),
199 (
200 "status",
201 node(
202 "badge",
203 vec![
204 (
205 "status",
206 sym(if placed.realtime_pin {
207 "realtime"
208 } else {
209 "placed"
210 }),
211 ),
212 (
213 "label",
214 Expr::String(if placed.realtime_pin {
215 "realtime".to_owned()
216 } else {
217 "placed".to_owned()
218 }),
219 ),
220 ],
221 ),
222 ),
223 ],
224 )
225}
226
227fn bridge_edge_view(bridge: &DomainBridge) -> Expr {
228 node(
229 "edge",
230 vec![
231 ("id", uint(u64::from(bridge.edge.0))),
232 ("from", Expr::Symbol(bridge.from.as_symbol().clone())),
233 ("to", Expr::Symbol(bridge.to.as_symbol().clone())),
234 (
235 "from-site",
236 Expr::Symbol(bridge.from_site.as_symbol().clone()),
237 ),
238 ("to-site", Expr::Symbol(bridge.to_site.as_symbol().clone())),
239 (
240 "bridge-kind",
241 Expr::Symbol(bridge.descriptor.kind().symbol()),
242 ),
243 (
244 "bridge-diagnostics",
245 Expr::List(
246 bridge
247 .descriptor
248 .diagnostics()
249 .iter()
250 .cloned()
251 .map(Expr::Symbol)
252 .collect(),
253 ),
254 ),
255 ("latency", latency_value(bridge.descriptor.latency())),
256 ],
257 )
258}
259
260fn bridge_table_view(bridges: &[DomainBridge]) -> Expr {
261 node(
262 "table",
263 vec![
264 ("lens", sym(PLACEMENT_BRIDGE_TABLE_VIEW_ID)),
265 ("role", sym("placement-bridge-table")),
266 (
267 "bridges",
268 Expr::List(bridges.iter().map(bridge_row).collect()),
269 ),
270 ],
271 )
272}
273
274fn bridge_row(bridge: &DomainBridge) -> Expr {
275 data_map(vec![
276 ("edge", uint(u64::from(bridge.edge.0))),
277 ("from", Expr::Symbol(bridge.from.as_symbol().clone())),
278 ("to", Expr::Symbol(bridge.to.as_symbol().clone())),
279 (
280 "from-site",
281 Expr::Symbol(bridge.from_site.as_symbol().clone()),
282 ),
283 ("to-site", Expr::Symbol(bridge.to_site.as_symbol().clone())),
284 (
285 "bridge-kind",
286 Expr::Symbol(bridge.descriptor.kind().symbol()),
287 ),
288 (
289 "bridge-name",
290 Expr::String(bridge.descriptor.name().to_owned()),
291 ),
292 (
293 "diagnostics",
294 Expr::List(
295 bridge
296 .descriptor
297 .diagnostics()
298 .iter()
299 .cloned()
300 .map(Expr::Symbol)
301 .collect(),
302 ),
303 ),
304 ("latency", latency_value(bridge.descriptor.latency())),
305 ])
306}
307
308fn latency_budget_view(latencies: &[PortLatency]) -> Expr {
309 node(
310 "table",
311 vec![
312 ("lens", sym(PLACEMENT_LATENCY_BUDGET_VIEW_ID)),
313 ("role", sym("placement-latency-budget")),
314 (
315 "latency",
316 Expr::List(latencies.iter().map(latency_row).collect()),
317 ),
318 ],
319 )
320}
321
322fn latency_row(latency: &PortLatency) -> Expr {
323 data_map(vec![
324 ("node", Expr::Symbol(latency.node.as_symbol().clone())),
325 ("site", Expr::Symbol(latency.site.as_symbol().clone())),
326 ("latency", latency_value(latency.latency)),
327 (
328 "latency-class",
329 Expr::Symbol(latency.latency_class.symbol()),
330 ),
331 ])
332}
333
334fn refusal_table_view(refusals: &[PlacementRefusal]) -> Expr {
335 node(
336 "table",
337 vec![
338 ("lens", sym(PLACEMENT_REFUSAL_TABLE_VIEW_ID)),
339 ("role", sym("placement-refusals")),
340 (
341 "refusals",
342 Expr::List(refusals.iter().map(refusal_row).collect()),
343 ),
344 ],
345 )
346}
347
348fn refusal_row(refusal: &PlacementRefusal) -> Expr {
349 data_map(vec![
350 ("node", Expr::Symbol(refusal.node.as_symbol().clone())),
351 ("site", Expr::Symbol(refusal.site.as_symbol().clone())),
352 (
353 "reason",
354 Expr::Symbol(refusal_reason_symbol(&refusal.reason)),
355 ),
356 ])
357}
358
359fn runtime_diagnostics_view(diagnostics: &[PlacementRuntimeDiagnostic]) -> Expr {
360 node(
361 "table",
362 vec![
363 ("lens", sym(PLACEMENT_RUNTIME_DIAGNOSTICS_VIEW_ID)),
364 ("role", sym("placement-runtime-diagnostics")),
365 (
366 "diagnostics",
367 Expr::List(diagnostics.iter().map(runtime_diagnostic_row).collect()),
368 ),
369 ],
370 )
371}
372
373fn runtime_diagnostic_row(diagnostic: &PlacementRuntimeDiagnostic) -> Expr {
374 data_map(vec![
375 ("source", Expr::Symbol(diagnostic.source().clone())),
376 ("diagnostic", Expr::Symbol(diagnostic.diagnostic().clone())),
377 ("count", uint(diagnostic.count())),
378 ])
379}
380
381fn browser_diagnostics_view(reports: &[BrowserPlacementReport]) -> Expr {
382 node(
383 "table",
384 vec![
385 ("lens", sym(PLACEMENT_BROWSER_DIAGNOSTICS_VIEW_ID)),
386 ("role", sym("placement-browser-diagnostics")),
387 (
388 "reports",
389 Expr::List(reports.iter().map(browser_report_row).collect()),
390 ),
391 ],
392 )
393}
394
395fn browser_report_row(report: &BrowserPlacementReport) -> Expr {
396 data_map(vec![
397 ("fragment", Expr::Symbol(report.fragment_id().clone())),
398 ("site", Expr::Symbol(report.site().clone())),
399 ("engine", Expr::Symbol(report.engine().id().clone())),
400 (
401 "lanes",
402 Expr::List(
403 report
404 .lanes()
405 .iter()
406 .map(|lane| Expr::Symbol(lane.symbol()))
407 .collect(),
408 ),
409 ),
410 (
411 "diagnostics",
412 Expr::List(
413 report
414 .diagnostics()
415 .iter()
416 .cloned()
417 .map(Expr::Symbol)
418 .collect(),
419 ),
420 ),
421 ("outputs", uint(report.output_envelopes().len() as u64)),
422 ])
423}
424
425fn fault_timeline_view(faults: &[PlacementFaultFixture]) -> Expr {
426 node(
427 "timeline",
428 vec![
429 ("lens", sym(PLACEMENT_FAULT_TIMELINE_VIEW_ID)),
430 ("role", sym("placement-fault-timeline")),
431 ("lane", sym("placement-faults")),
432 (
433 "events",
434 Expr::List(
435 faults
436 .iter()
437 .enumerate()
438 .map(|(index, fault)| fault_event(index, fault))
439 .collect(),
440 ),
441 ),
442 ],
443 )
444}
445
446fn fault_event(index: usize, fault: &PlacementFaultFixture) -> Expr {
447 data_map(vec![
448 ("at", uint(index as u64)),
449 ("fault", Expr::Symbol(fault.name().clone())),
450 ("response", Expr::Symbol(fault.response().clone())),
451 (
452 "diagnostics",
453 Expr::List(
454 fault
455 .diagnostics()
456 .iter()
457 .map(runtime_diagnostic_row)
458 .collect(),
459 ),
460 ),
461 ])
462}
463
464fn fault_fixture(name: &str, response: &str, count: u64) -> PlacementFaultFixture {
465 PlacementFaultFixture::new(
466 Symbol::qualified("placement/fault", name),
467 Symbol::qualified("placement/fault-response", response),
468 vec![PlacementRuntimeDiagnostic::new(
469 Symbol::qualified("placement/fault-source", name),
470 Symbol::qualified("placement/fault-diagnostic", name),
471 count,
472 )],
473 )
474}
475
476fn latency_value(latency: BridgeLatency) -> Expr {
477 data_map(vec![
478 ("frames", uint(latency.frame_count())),
479 ("packets", uint(u64::from(latency.packet_count()))),
480 ])
481}
482
483fn refusal_reason_symbol(reason: &PlacementRefusalReason) -> Symbol {
484 let name = match reason {
485 PlacementRefusalReason::UnknownSite => "unknown-site",
486 PlacementRefusalReason::RealtimePinViolation => "realtime-pin-violation",
487 PlacementRefusalReason::UnsupportedLatencyClass => "unsupported-latency-class",
488 #[allow(unreachable_patterns)]
489 other => fallback_refusal_reason(other),
490 };
491 Symbol::qualified("placement/refusal", name)
492}
493
494fn fallback_refusal_reason(reason: &PlacementRefusalReason) -> &'static str {
495 let debug = format!("{reason:?}");
496 if debug.starts_with("UnsupportedClockDomain") {
497 "unsupported-clock-domain"
498 } else if debug.starts_with("UnsupportedStreamPorts") {
499 "unsupported-stream-ports"
500 } else {
501 "incomparable-clock-domain"
502 }
503}