in src/rust/engine/graph/src/lib.rs [316:402]
fn trace<T: NodeTracer<N>>(&self, roots: &[N], file_path: &Path) -> Result<(), String> {
let root_ids: IndexSet<EntryId, FNV> = roots
.iter()
.filter_map(|nk| self.entry_id(&EntryKey::Valid(nk.clone())))
.cloned()
.collect();
// Find all bottom Nodes for the trace by walking recursively under the roots.
let bottom_nodes = {
let mut queue: VecDeque<_> = root_ids.iter().cloned().collect();
let mut visited: HashSet<EntryId, FNV> = HashSet::default();
let mut bottom_nodes = Vec::new();
while let Some(id) = queue.pop_front() {
if !visited.insert(id) {
continue;
}
// If all dependencies are bottom nodes, then we represent a failure.
let mut non_bottom_deps = self
.pg
.neighbors_directed(id, Direction::Outgoing)
.filter(|dep_id| !T::is_bottom(self.unsafe_entry_for_id(*dep_id).peek()))
.peekable();
if non_bottom_deps.peek().is_none() {
bottom_nodes.push(id);
} else {
// Otherwise, continue recursing on `rest`.
queue.extend(non_bottom_deps);
}
}
bottom_nodes
};
// Invert the graph into a evenly-weighted dependent graph by cloning it and stripping out
// the Nodes (to avoid cloning them), adding equal edge weights, and then reversing it.
// Because we do not remove any Nodes or edges, all EntryIds remain stable.
let dependent_graph = {
let mut dg = self
.pg
.filter_map(|_, _| Some(()), |_, _| Some(1.0))
.clone();
dg.reverse();
dg
};
// Render the shortest path through the dependent graph to any root for each bottom_node.
for bottom_node in bottom_nodes {
// We use Bellman Ford because it actually records paths, unlike Dijkstra's.
let (path_weights, paths) = petgraph::algo::bellman_ford(&dependent_graph, bottom_node)
.unwrap_or_else(|e| {
panic!(
"There should not be any negative edge weights. Got: {:?}",
e
)
});
// Find the root with the shortest path weight.
let minimum_path_id = root_ids
.iter()
.min_by_key(|root_id| path_weights[root_id.index()] as usize)
.ok_or_else(|| "Encountered a Node that was not reachable from any roots.".to_owned())?;
// Collect the path by walking through the `paths` Vec, which contains the indexes of
// predecessor Nodes along a path to the bottom Node.
let path = {
let mut next_id = *minimum_path_id;
let mut path = Vec::new();
path.push(next_id);
while let Some(current_id) = paths[next_id.index()] {
path.push(current_id);
if current_id == bottom_node {
break;
}
next_id = current_id;
}
path
};
// Render the path.
self
.trace_render_path_to_file::<T>(&path, file_path)
.map_err(|e| format!("Failed to render trace to {:?}: {}", file_path, e))?;
}
Ok(())
}