microsoft/openvmm

Public

mirrored from https://github.com/microsoft/openvmmAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/identify-fixed-issues

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

flowey/flowey_cli/src/pipeline_resolver/viz.rs

468lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Debug backend that simply visualizes flows, instead of emitting them in any
5//! runnable format
6
7use crate::flow_resolver::stage1_dag::DepKind;
8use crate::flow_resolver::stage1_dag::OutputGraphEntry;
9use crate::flow_resolver::stage1_dag::StepId;
10use crate::pipeline_resolver::generic::ResolvedPipeline;
11use crate::pipeline_resolver::generic::ResolvedPipelineJob;
12use flowey_core::node::FlowArch;
13use flowey_core::node::FlowBackend;
14use flowey_core::node::FlowPlatform;
15use flowey_core::node::NodeHandle;
16use std::collections::BTreeMap;
17use std::collections::BTreeSet;
18
19pub fn viz_pipeline_toposort(
20 pipeline: ResolvedPipeline,
21 backend: FlowBackend,
22 with_persist_dir: bool,
23) -> anyhow::Result<()> {
24 viz_pipeline_generic(pipeline, backend, with_persist_dir, viz_flow_toposort)
25}
26
27pub fn viz_pipeline_flow_dot(
28 pipeline: ResolvedPipeline,
29 backend: FlowBackend,
30 with_persist_dir: bool,
31) -> anyhow::Result<()> {
32 viz_pipeline_generic(pipeline, backend, with_persist_dir, viz_flow_dot)
33}
34
35fn viz_pipeline_generic(
36 pipeline: ResolvedPipeline,
37 backend: FlowBackend,
38 with_persist_dir: bool,
39 f: fn(
40 seed_nodes: BTreeMap<NodeHandle, (bool, Vec<Box<[u8]>>)>,
41 resolved_patches: flowey_core::patch::ResolvedPatches,
42 external_read_vars: BTreeSet<String>,
43 backend: FlowBackend,
44 platform: FlowPlatform,
45 arch: FlowArch,
46 with_persist_dir: bool,
47 ) -> anyhow::Result<()>,
48) -> anyhow::Result<()> {
49 let ResolvedPipeline {
50 graph,
51 order,
52 parameters: _,
53 ado_name: _,
54 ado_schedule_triggers: _,
55 ado_ci_triggers: _,
56 ado_pr_triggers: _,
57 ado_bootstrap_template: _,
58 ado_resources_repository: _,
59 ado_post_process_yaml_cb: _,
60 ado_variables: _,
61 ado_job_id_overrides: _,
62 gh_name: _,
63 gh_schedule_triggers: _,
64 gh_ci_triggers: _,
65 gh_pr_triggers: _,
66 gh_bootstrap_template: _,
67 } = pipeline;
68
69 for idx in order {
70 let ResolvedPipelineJob {
71 ref root_nodes,
72 ref patches,
73 ref label,
74 platform,
75 arch,
76 cond_param_idx: _,
77 timeout_minutes: _,
78 command_wrapper: _,
79 ref ado_pool,
80 ado_variables: _,
81 gh_override_if: _,
82 gh_global_env: _,
83 ref gh_pool,
84 gh_permissions: _,
85 ref external_read_vars,
86 parameters_used: _,
87 ref artifacts_used,
88 ref artifacts_published,
89 } = graph[idx];
90
91 println!(
92 "== {}{}{} ==",
93 label,
94 ado_pool
95 .as_ref()
96 .map(|s| format!(" - {} ({})", s.name, s.demands.join(",")))
97 .unwrap_or_default(),
98 gh_pool
99 .as_ref()
100 .map(|s| format!(" - {:#?}", s))
101 .unwrap_or_default()
102 );
103 println!(
104 "artifacts used: {}",
105 artifacts_used
106 .iter()
107 .map(|a| a.name.clone())
108 .collect::<Vec<_>>()
109 .join(",\n ")
110 );
111 println!(
112 "artifacts published: {}",
113 artifacts_published
114 .iter()
115 .map(|a| a.name.clone())
116 .collect::<Vec<_>>()
117 .join(",\n ")
118 );
119 println!();
120
121 f(
122 root_nodes
123 .clone()
124 .into_iter()
125 .map(|(node, requests)| (node, (true, requests)))
126 .collect(),
127 patches.clone(),
128 external_read_vars.clone(),
129 backend,
130 platform,
131 arch,
132 with_persist_dir,
133 )?;
134
135 println!();
136 }
137
138 Ok(())
139}
140
141/// (debug) print the modpath of each node in topological sort of the flow
142pub fn viz_flow_toposort(
143 seed_nodes: BTreeMap<NodeHandle, (bool, Vec<Box<[u8]>>)>,
144 resolved_patches: flowey_core::patch::ResolvedPatches,
145 external_read_vars: BTreeSet<String>,
146 backend: FlowBackend,
147 platform: FlowPlatform,
148 arch: FlowArch,
149 with_persist_dir: bool,
150) -> anyhow::Result<()> {
151 // ignore the unreachable nodes error, since we want to allow debugging issues here
152 let (mut output_graph, _, _err_unreachable_nodes) =
153 crate::flow_resolver::stage1_dag::stage1_dag(
154 backend,
155 platform,
156 arch,
157 resolved_patches,
158 seed_nodes,
159 external_read_vars,
160 with_persist_dir.then_some("<dummy>".into()),
161 )?;
162
163 let output_order = petgraph::algo::toposort(&output_graph, None)
164 .expect("runtime variables cannot introduce a DAG cycle");
165
166 let mut max_len = 0;
167 for &idx in output_order.iter().rev() {
168 max_len = max_len.max(
169 output_graph[idx]
170 .1
171 .as_ref()
172 .unwrap()
173 .node_handle
174 .modpath()
175 .len(),
176 )
177 }
178
179 for idx in output_order.into_iter().rev() {
180 let e = output_graph[idx].1.take().unwrap();
181 match &e.step {
182 crate::flow_resolver::stage1_dag::Step::Anchor { .. } => {}
183 crate::flow_resolver::stage1_dag::Step::Rust {
184 idx: _,
185 label,
186 can_merge: _,
187 code: _,
188 } => {
189 println!(
190 "{:width$} - rust - {}",
191 e.node_handle.modpath(),
192 label,
193 width = max_len
194 )
195 }
196 crate::flow_resolver::stage1_dag::Step::AdoYaml {
197 ado_to_rust: _,
198 rust_to_ado: _,
199 label,
200 raw_yaml: _,
201 condvar: _,
202 code_idx: _,
203 code: _,
204 } => println!(
205 "{:width$} - ado - {}",
206 e.node_handle.modpath(),
207 label,
208 width = max_len
209 ),
210 crate::flow_resolver::stage1_dag::Step::GitHubYaml { label, .. } => println!(
211 "{:width$} - github - {}",
212 e.node_handle.modpath(),
213 label,
214 width = max_len
215 ),
216 }
217 }
218
219 Ok(())
220}
221
222pub fn viz_pipeline_dot(pipeline: ResolvedPipeline, _backend: FlowBackend) -> anyhow::Result<()> {
223 let ResolvedPipeline {
224 graph,
225 order: _,
226 parameters: _,
227 ado_name: _,
228 ado_schedule_triggers: _,
229 ado_ci_triggers: _,
230 ado_pr_triggers: _,
231 ado_bootstrap_template: _,
232 ado_resources_repository: _,
233 ado_post_process_yaml_cb: _,
234 ado_variables: _,
235 ado_job_id_overrides: _,
236 gh_name: _,
237 gh_schedule_triggers: _,
238 gh_ci_triggers: _,
239 gh_pr_triggers: _,
240 gh_bootstrap_template: _,
241 } = pipeline;
242
243 #[derive(Clone)]
244 struct VizNode(ResolvedPipelineJob);
245
246 impl From<ResolvedPipelineJob> for VizNode {
247 fn from(value: ResolvedPipelineJob) -> Self {
248 Self(value)
249 }
250 }
251
252 impl std::fmt::Debug for VizNode {
253 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254 let Self(ResolvedPipelineJob {
255 root_nodes: _,
256 patches: _,
257 label,
258 platform: _,
259 arch: _,
260 cond_param_idx: _,
261 timeout_minutes: _,
262 command_wrapper: _,
263 ado_pool,
264 ado_variables: _,
265 gh_override_if: _,
266 gh_global_env: _,
267 gh_pool,
268 gh_permissions: _,
269 external_read_vars: _,
270 parameters_used: _,
271 artifacts_used,
272 artifacts_published,
273 }) = self;
274
275 writeln!(
276 f,
277 "== {}{}{} ==",
278 label,
279 ado_pool
280 .as_ref()
281 .map(|s| format!(" - {} ({})", s.name, s.demands.join(",")))
282 .unwrap_or_default(),
283 gh_pool
284 .as_ref()
285 .map(|s| format!(" - {:#?}", s))
286 .unwrap_or_default()
287 )?;
288
289 writeln!(
290 f,
291 "artifacts used: {}",
292 artifacts_used
293 .iter()
294 .map(|a| a.name.clone())
295 .collect::<Vec<_>>()
296 .join(",\n ")
297 )?;
298 writeln!(
299 f,
300 "artifacts published: {}",
301 artifacts_published
302 .iter()
303 .map(|a| a.name.clone())
304 .collect::<Vec<_>>()
305 .join(",\n ")
306 )?;
307
308 Ok(())
309 }
310 }
311
312 println!(
313 "{:?}",
314 petgraph::dot::Dot::with_config(
315 &petgraph_viz_helper::clone_graph_with_wrappers::<_, _, VizNode, ()>(&graph),
316 &[petgraph::dot::Config::EdgeNoLabel]
317 )
318 );
319
320 Ok(())
321}
322
323/// (debug) emit a graph in the graphviz `.dot` format of the flow
324pub fn viz_flow_dot(
325 seed_nodes: BTreeMap<NodeHandle, (bool, Vec<Box<[u8]>>)>,
326 resolved_patches: flowey_core::patch::ResolvedPatches,
327 external_read_vars: BTreeSet<String>,
328 backend: FlowBackend,
329 platform: FlowPlatform,
330 arch: FlowArch,
331 with_persist_dir: bool,
332) -> anyhow::Result<()> {
333 // ignore the unreachable nodes error, since we want to allow debugging issues here
334 let (output_graph, _, _err_unreachable_nodes) = crate::flow_resolver::stage1_dag::stage1_dag(
335 backend,
336 platform,
337 arch,
338 resolved_patches,
339 seed_nodes,
340 external_read_vars,
341 with_persist_dir.then_some("<dummy>".into()),
342 )?;
343
344 #[derive(Clone)]
345 struct VizNode((StepId, Option<OutputGraphEntry>));
346
347 impl From<(StepId, Option<OutputGraphEntry>)> for VizNode {
348 fn from(value: (StepId, Option<OutputGraphEntry>)) -> Self {
349 Self(value)
350 }
351 }
352
353 impl std::fmt::Debug for VizNode {
354 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
355 if self.0.1.is_none() {
356 return write!(f, "{:?} - ???", self.0.0);
357 }
358
359 let entry = &self.0.1.as_ref().unwrap();
360
361 write!(
362 f,
363 "{}:{}\n\n{}",
364 self.0.0.step_idx,
365 self.0.0.node.modpath(),
366 match &entry.step {
367 crate::flow_resolver::stage1_dag::Step::Anchor { label } => {
368 format!("<anchor:{label}>")
369 }
370 crate::flow_resolver::stage1_dag::Step::Rust {
371 idx,
372 label,
373 can_merge: _,
374 code: _,
375 } => format!("rust{idx}\n\n{}", label),
376 crate::flow_resolver::stage1_dag::Step::AdoYaml {
377 ado_to_rust: _,
378 rust_to_ado: _,
379 label,
380 raw_yaml: _,
381 condvar: _,
382 code_idx: _,
383 code: _,
384 } => format!("ado\n\n{}", label),
385 crate::flow_resolver::stage1_dag::Step::GitHubYaml { label, .. } => {
386 format!("github\n\n{}", label)
387 }
388 }
389 )
390 }
391 }
392
393 println!(
394 r#"
395digraph {{
396 #rankdir="LR"
397 edge [dir="back"];
398{:?}
399}}
400"#,
401 // petgraph::dot::Dot::with_config(
402 // &petgraph::visit::Reversed(&self::petgraph_viz_helper::clone_graph_with_wrappers::<
403 // _,
404 // _,
405 // VizNode,
406 // DepKind,
407 // >(&output_graph)),
408 // &[petgraph::dot::Config::GraphContentOnly]
409 // ),
410 petgraph::dot::Dot::with_config(
411 &petgraph_viz_helper::clone_graph_with_wrappers::<_, _, VizNode, DepKind>(
412 &output_graph
413 ),
414 &[petgraph::dot::Config::GraphContentOnly]
415 )
416 );
417
418 match petgraph::algo::toposort(&output_graph, None) {
419 Ok(order) => {
420 if order
421 .into_iter()
422 .filter(|idx| {
423 output_graph
424 .edges_directed(*idx, petgraph::Direction::Incoming)
425 .count()
426 == 0
427 })
428 .count()
429 != 1
430 {
431 println!("multiple root nodes detected!")
432 }
433 }
434 Err(_) => {
435 println!("Detected Cycle!")
436 }
437 }
438
439 Ok(())
440}
441
442// pub(crate) so dump_stage0_dag can use it
443pub(crate) mod petgraph_viz_helper {
444 use petgraph::visit::EdgeRef;
445 use std::collections::BTreeMap;
446
447 // thanks bing AI!
448 pub fn clone_graph_with_wrappers<N, E, NWrap, EWrap>(
449 graph: &petgraph::Graph<N, E>,
450 ) -> petgraph::Graph<NWrap, EWrap>
451 where
452 N: Clone,
453 E: Clone,
454 NWrap: From<N>,
455 EWrap: From<E>,
456 {
457 let mut new_graph = petgraph::Graph::new();
458 let node_map: BTreeMap<_, _> = graph
459 .node_indices()
460 .map(|i| (i, new_graph.add_node(NWrap::from(graph[i].clone()))))
461 .collect();
462 for edge in graph.edge_references() {
463 let (a, b) = (node_map[&edge.source()], node_map[&edge.target()]);
464 new_graph.add_edge(a, b, EWrap::from(edge.weight().clone()));
465 }
466 new_graph
467 }
468}
469