microsoft/openvmm

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
58d7ac0c15eef5e011d9b956237ac749dccd14a0

Branches

Tags

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

Clone

HTTPS

Download ZIP

flowey/flowey_cli/src/pipeline_resolver/viz.rs

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