microsoft/openvmm

Public

mirrored fromhttps://github.com/microsoft/openvmmAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
f79b7b44ff6eeac130a5a2e246d16ce341541f45

Branches

Tags

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

Clone

HTTPS

Download ZIP

flowey/flowey_cli/src/pipeline_resolver/viz.rs

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