//! Local-only CPU diagnosis. Does not measure native FPS, canvas redraw, or GPU. //! Usage: architecture_perf_probe.exe PATH_TO_RELEASE1_MODEL_JSON [SAMPLES=40] use oa_core::{persistence, projection, routing, view_options::ViewIndex, *}; use std::{collections::BTreeMap, hint::black_box, time::Instant}; fn measure(label: &str, samples: usize, mut f: impl FnMut(usize) -> T) { for i in 0..3 { black_box(f(i)); } let mut ms = Vec::with_capacity(samples); for i in 0..samples { let start = Instant::now(); let output = black_box(f(i)); ms.push(start.elapsed().as_secs_f64() * 1000.0); drop(output); // Exclude destruction consistently from each timed phase. } ms.sort_by(f64::total_cmp); let percentile = |p: f64| ms[((samples - 1) as f64 * p).ceil() as usize]; println!( "{label}: n={samples} p50_ms={:.4} p95_ms={:.4} max_ms={:.4}", percentile(0.5), percentile(0.95), ms[samples - 1] ); } fn probe(p: &Project, d: &Diagram, samples: usize) { let index = ViewIndex::new(p); let view = projection::project_diagram_with_index(p, d, &index); println!( "\nview={} projection={:?} nodes={} ports={} edges={}", d.name, d.projection, view.nodes.len(), view.ports.len(), view.edges.len() ); measure("index_new", samples, |_| ViewIndex::new(black_box(p))); measure("projection_reused_index", samples, |_| { projection::project_diagram_with_index(black_box(p), black_box(d), black_box(&index)) }); measure("projection_fresh_index", samples, |_| { projection::project_diagram(black_box(p), black_box(d)) }); measure("routing_settled", samples, |_| { routing::route_diagram(black_box(p), black_box(d), black_box(&view)) }); // Deterministic slight motion of the first root card. Includes the same // descendant/route translation as legacy single-card canvas drag. This is // presentation-only, not the allocation transaction pipeline. let Some(root) = view.nodes.iter().find(|n| n.visual_parent.is_none()) else { return; }; println!("moving_root={} element={}", root.id, p.named(root.element)); let mut moved = projection::descendant_nodes(p, d.id, root.id); moved.push(root.id); let make_moved = |i: usize| { let delta = Position { x: (i % 21) as f32 * 3.0, y: (i % 11) as f32 * 2.0, }; let mut preview = d.clone(); let deltas = moved .iter() .map(|id| (*id, delta)) .collect::>(); for route in projection::translated_routes(p, d, &deltas) { if let Some(stored) = preview.routes.iter_mut().find(|r| r.edge == route.edge) { *stored = route; } else { preview.routes.push(route); } } for id in &moved { if let Some(n) = preview.nodes.get_mut(id) { n.position.x += delta.x; n.position.y += delta.y; } } preview }; let previews = (0..samples.max(3)).map(make_moved).collect::>(); let projected = previews .iter() .map(|d| projection::project_diagram_with_index(p, d, &index)) .collect::>(); measure("moved_projection", samples, |i| { projection::project_diagram_with_index(p, black_box(&previews[i]), &index) }); measure("moved_routing", samples, |i| { routing::route_diagram(p, black_box(&previews[i]), black_box(&projected[i])) }); measure("moved_prepare_projection_routing", samples, |i| { let preview = make_moved(i); let view = projection::project_diagram_with_index(p, &preview, &index); routing::route_diagram(p, &preview, &view) }); } fn main() -> Result<(), Box> { let args = std::env::args().collect::>(); let path = args .get(1) .ok_or("usage: architecture_perf_probe PATH [SAMPLES]")?; let samples = args .get(2) .map(|s| s.parse::()) .transpose()? .unwrap_or(40) .max(1); let p = persistence::decode(&std::fs::read(path)?)?; println!( "CPU-only probe; samples={samples}; fixture={path}; elements={} relations={}", p.elements.len(), p.relations.len() ); for d in p.diagrams.values() { probe(&p, d, samples); } // Approximation of the reported eight-card screenshot, not an exact repro: // fixture semantics, Overview projection, synthetic positions/card sizes. let mut approx = p.clone(); let id: Id = "f0440000-0000-4000-8000-000000000001".parse()?; approx.diagrams.insert( id, Diagram { id, alias: "fps44_approx_overview".into(), name: "APPROXIMATION eight-card SA Overview".into(), description: "Synthetic positions, not user geometry".into(), layer: Layer::Sa, projection: ProjectionKind::Overview, view_options: Default::default(), nodes: BTreeMap::new(), routes: vec![], layout: None, }, ); for (ordinal, (alias, x, y)) in [ ("sa_assessment_procedure", 30., 240.), ("sa_triage", 510., 240.), ("sa_control", 990., 240.), ("sa_technician", 530., 440.), ("sa_assess", 1490., 240.), ("sa_repair", 1030., 440.), ("sa_confirm", 1530., 440.), ("sa_reset", 30., 440.), ] .into_iter() .enumerate() { let element = approx .elements .values() .find(|e| e.alias == alias) .ok_or_else(|| format!("missing alias {alias}"))? .id; let generated = approx.place(id, element, Position { x, y }); let diagram = approx.diagrams.get_mut(&id).unwrap(); let mut node = diagram.nodes.remove(&generated).unwrap(); node.id = format!("f0440000-0000-4000-8000-{:012}", ordinal + 2).parse()?; diagram.nodes.insert(node.id, node); } probe(&approx, &approx.diagrams[&id], samples); Ok(()) }