dag/
render.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8#[cfg(any(test, feature = "indexedlog-backend"))]
9use std::cmp::Ordering;
10#[cfg(any(test, feature = "indexedlog-backend"))]
11use std::io::Write;
12
13use anyhow::Result;
14use nonblocking::non_blocking_result;
15// Re-export
16pub use renderdag::Ancestor;
17pub use renderdag::GraphRow;
18pub use renderdag::GraphRowRenderer;
19pub use renderdag::Renderer;
20pub use renderdag::*;
21
22#[cfg(any(test, feature = "indexedlog-backend"))]
23use crate::ops::IdConvert;
24use crate::set::SyncSetQuery;
25#[cfg(any(test, feature = "indexedlog-backend"))]
26use crate::Dag;
27use crate::DagAlgorithm;
28#[cfg(any(test, feature = "indexedlog-backend"))]
29use crate::Group;
30#[cfg(any(test, feature = "indexedlog-backend"))]
31use crate::IdSpan;
32#[cfg(any(test, feature = "indexedlog-backend"))]
33use crate::Level;
34use crate::Set;
35use crate::Vertex;
36
37/// Render a Dag or MemDag into a String.
38pub fn render_dag(
39    dag: &(impl DagAlgorithm + ?Sized),
40    get_message: impl Fn(&Vertex) -> Option<String>,
41) -> Result<String> {
42    let mut renderer = GraphRowRenderer::new().output().build_box_drawing();
43
44    let mut out = String::new();
45    let next_rows = dag_to_renderer_next_rows(dag, None)?;
46    for (node, parents) in next_rows {
47        let mut name = format!("{:?}", &node);
48        let message = get_message(&node).unwrap_or_default();
49        let row = if name.len() == 1 {
50            renderer.next_row(node, parents, name, message)
51        } else {
52            if !message.is_empty() {
53                name += &format!(" {}", message);
54            }
55            renderer.next_row(node, parents, String::from("o"), name)
56        };
57        out.push_str(&row);
58    }
59
60    let output = format!(
61        "\n{}",
62        out.trim_end()
63            .lines()
64            .map(|l| format!("            {}", l))
65            .collect::<Vec<_>>()
66            .join("\n")
67    );
68    Ok(output)
69}
70
71/// Render a Dag or MemDag into structured `GraphRow`s.
72/// The `GraphRow` can serialize to other formats.
73///
74/// If `subset` is provided, only render a subset of the graph.
75pub fn render_dag_structured(
76    dag: &dyn DagAlgorithm,
77    subset: Option<Set>,
78) -> Result<Vec<GraphRow<Vertex>>> {
79    let mut renderer = GraphRowRenderer::new();
80    let next_rows = dag_to_renderer_next_rows(dag, subset)?;
81    let mut out = Vec::with_capacity(next_rows.len());
82    for (node, parents) in next_rows {
83        let name = String::from_utf8_lossy(node.as_ref()).into_owned();
84        let message = Default::default();
85        let row = renderer.next_row(node, parents, name, message);
86        out.push(row);
87    }
88    Ok(out)
89}
90
91/// Produce inputs (node, parents) for graph_row.
92///
93/// If `subset` is provided, only render a subset of the graph in
94/// the `subset` order.
95fn dag_to_renderer_next_rows(
96    dag: &(impl DagAlgorithm + ?Sized),
97    subset: Option<Set>,
98) -> Result<Vec<(Vertex, Vec<Ancestor<Vertex>>)>> {
99    let subset = match subset {
100        None => non_blocking_result(dag.all())?,
101        Some(set) => set,
102    };
103    let iter: Vec<_> = subset.iter()?.collect::<crate::Result<_>>()?;
104    let subdag = non_blocking_result(dag.subdag(subset))?;
105
106    let mut out = Vec::with_capacity(iter.len());
107    for node in iter {
108        let direct_parents = non_blocking_result(dag.parent_names(node.clone()))?;
109        let subdag_parents = non_blocking_result(subdag.parent_names(node.clone()))?;
110        let mut parents: Vec<Ancestor<Vertex>> = subdag_parents
111            .iter()
112            .cloned()
113            .map(|p| {
114                if direct_parents.contains(&p) {
115                    Ancestor::Parent(p)
116                } else {
117                    Ancestor::Ancestor(p)
118                }
119            })
120            .collect();
121        if parents.len() < direct_parents.len() {
122            let subdag_ancestors =
123                non_blocking_result(dag.ancestors(Set::from_static_names(subdag_parents.clone())))?;
124            for p in &direct_parents {
125                if subdag_parents.contains(p) {
126                    continue;
127                }
128                // Is the direct parent connect to any of the subdag parents?
129                let direct_reachable = non_blocking_result(dag.ancestors(p.into()))?;
130                if direct_reachable
131                    .intersection(&subdag_ancestors)
132                    .is_empty()?
133                {
134                    parents.push(Ancestor::Anonymous)
135                }
136            }
137        }
138        out.push((node, parents));
139    }
140
141    Ok(out)
142}
143
144#[cfg(any(test, feature = "indexedlog-backend"))]
145pub fn render_segment_dag(
146    mut out: impl Write,
147    dag: &Dag,
148    level: Level,
149    group: Group,
150) -> Result<()> {
151    let mut renderer = renderdag::GraphRowRenderer::new()
152        .output()
153        .build_box_drawing();
154    let segs = dag.dag.next_segments(group.min_id(), level)?;
155
156    for seg in segs.iter().rev() {
157        let mut parents = vec![];
158        for parent_id in seg.parents()? {
159            // For each parent Id, look for the containing segment.
160            let parent_span: IdSpan = parent_id.into();
161            let parent_idx = segs.binary_search_by(|s| {
162                let span = s.span().unwrap();
163                if span.contains(parent_id) {
164                    Ordering::Equal
165                } else {
166                    span.cmp(&parent_span)
167                }
168            });
169
170            if let Ok(parent_idx) = parent_idx {
171                parents.push(Ancestor::Parent(&segs[parent_idx]));
172            } else {
173                // Probably a non-master segment with master parent.
174                parents.push(Ancestor::Anonymous);
175            }
176        }
177
178        let span = seg.span()?;
179        let get_hex = |id| -> String {
180            non_blocking_result(dag.vertex_name(id))
181                .map(|s| format!("{:.12?}", s))
182                .unwrap_or_default()
183        };
184        let name = format!(
185            "{}({})-{}({})",
186            get_hex(span.low),
187            span.low,
188            get_hex(span.high),
189            span.high,
190        );
191        let row = renderer.next_row(seg, parents, String::from("o"), name);
192        write!(out, "{}", row)?;
193    }
194
195    Ok(())
196}