Shouldn't change anything for now, but in the future it should be much easier to add other package-specific annotations, such as lines of code, downloads per month, time since last release, size in final binary, etc
BRXHJFU7ANVWOFXBNR5EYUGDURCFCZIF66VDV6L3JBYC45CAFNJAC
PJPTNU2SXAGBBBVDFZMHHRMC6X5UGOPBJWT7DP5YXOZKQKGLKHWQC
XVQXXAGZ4WHBQIU3WEUKLZX6FKV5V52LSTTGMFXR42WPWJZJNENQC
YA5ITLOV2UWAQZWFJND2WM45DLWG7PTECNJQOLPZAHH2GETPI3HQC
C43IWI7GMF6UEKGTXTK4GSQLAFD2SH6BKQRGR4ZQNITYSUK6RMXAC
ZEN3WUPDVQWI7LPTEG3WQA5QSEHU74CUXVFJJFIJNSIFC7N2CMEAC
UXJFRBBL7IZ2PR7ZYNFGOJ6A7EH5ZVLYVFLA3HNFCNRVL6KWJUDAC
OPTMCUTBEZQT3HETRWVBU4HYSB7NNBA4TY7QOOKCN3YLSMDKNM2QC
475UXTLYE5LQTMOGOFYC5Z5Z45YJ32M4GD2WOBTXZRBXKUSWFW2AC
LOR3KOXGQ2VYGDHXQ6MG22ZME5TMPFTUW7A5OG36IAVQANOCXBRAC
7CVIL7UJBYEZ4KHKPJ7ZYSVQ7BLQWWUSJLJR5FOXBICQTD5ETK4QC
UQJO24KBYI77E4J6LXWX2IUN7VABQKG6PGKBKWEPDCH5CKYBTC4AC
T34OV3YQGRFMXYWEFLBCFMX3U2TVXF552B5B3S6HMBJZU66PDMYAC
ZPFD3275NTWST7F5YCWYOOUS3QB5HE23LUOZXZIND4HBFSX62NCQC
use guppy::graph::{DependencyDirection, PackageGraph};
pub fn nodes(graph: &PackageGraph) -> Vec<&str> {
graph.packages().map(|pkg| pkg.name()).collect()
pub fn nodes<'graph>(annotations: &'graph Annotations) -> Vec<&'graph str> {
annotations
.packages()
.map(|id| annotations.metadata(id).unwrap().name())
.collect()
pub fn links<'graph>(
graph: &'graph PackageGraph,
timings: &timings::Output,
) -> Vec<(&'graph str, &'graph str, f64)> {
let package_set = graph.resolve_all();
let mut links = Vec::with_capacity(package_set.len());
pub fn links<'graph>(annotations: &'graph Annotations) -> Vec<(&'graph str, &'graph str, f64)> {
let links = annotations.links().collect::<Vec<_>>();
let mut sankey_links = Vec::with_capacity(links.len());
for link in package_set.links(DependencyDirection::Forward) {
let (from, to) = link.endpoints();
let time_taken = timings.pkg_time(to.id()).unwrap_or(0_f64);
// Can't just set the edge weight to duration - if the package has N dependents
// it would appear to take N times longer, just need to divide duration by
// direct dependents to fix
let direct_dependents = to
.direct_links_directed(DependencyDirection::Reverse)
.count();
// Make sure to not divide by 0
let edge_weight = if direct_dependents == 0 {
time_taken
} else {
time_taken / (direct_dependents as f64)
};
for (from, to) in links {
let edge_weight = annotations
.variable(to, Variable::UnitDuration, Measurement::Relative)
.unwrap_or(0_f64);
pub fn axes(timings: &timings::Output) -> (Axis, Axis) {
let pkg_durations: Vec<f64> = timings.pkg_times().collect();
pub fn axes(annotations: &Annotations) -> (Axis, Axis) {
let packages = annotations.packages();
let pkg_durations = packages
.filter_map(|id| annotations.variable(id, Variable::UnitDuration, Measurement::Exact))
.collect::<Vec<_>>();
pub fn data(timings: &timings::Output) -> Vec<DataPoint> {
let pkg_durations: Vec<f64> = timings.pkg_times().collect();
pub fn data(annotations: &Annotations) -> Vec<DataPoint> {
let packages = annotations.packages();
let pkg_durations = packages
.filter_map(|id| annotations.variable(id, Variable::UnitDuration, Measurement::Exact))
.collect::<Vec<_>>();
let edges = package_set
.links(DependencyDirection::Forward)
.map(|link| (link.from(), link.to()))
.map(|(from, to)| (link_index.get(from.id()), link_index.get(to.id())))
let edges = annotations
.links()
.map(|(from, to)| (link_index.get(from), link_index.get(to)))
let sizes = package_set
.package_ids(DependencyDirection::Forward)
.map(|id| timings.pkg_time(id).unwrap_or(0_f64));
let sizes = link_index.keys().map(|id| {
annotations
.variable(id, Variable::UnitDuration, Measurement::Exact)
.unwrap_or(0_f64)
});
let unit_time = if let Some(duration) = timings.pkg_time(id) {
duration
} else {
// TODO: once using the resolved crate graph, `None` should never appear
println!("Queried node outside cargo's unit graph: {id}");
0_f64
};
let unit_time = annotations
.variable(id, Variable::UnitDuration, Measurement::Exact)
.unwrap_or(0_f64);
}
pub fn pkg_time(&self, pkg: &PackageId) -> Option<f64> {
self.repr
.get(pkg)
.map(|timings| timings.iter().map(|msg| msg.duration).sum())
}
pub fn pkg_messages(&self, pkg: &PackageId) -> Option<&Vec<Message>> {
self.repr.get(pkg)
}
// TODO: this returns each total package time, but it would be interesting to filter by
// crate type (lib, binary, proc_macro), target, build script runs etc
pub fn pkg_times<'s>(&'s self) -> impl Iterator<Item = f64> + 's {
self.repr
.values()
.map(|messages| messages.iter().map(|msg| msg.duration).sum())
}
pub fn len(&self) -> usize {
self.repr.len()
use std::collections::HashMap;
use guppy::graph::{DependencyDirection, PackageGraph, PackageMetadata};
use guppy::PackageId;
use indexmap::IndexMap;
use petgraph::data::{Element, FromElements};
use petgraph::graph::NodeIndex;
use petgraph::matrix_graph::Zero;
use petgraph::{Direction, Graph};
pub mod timings;
#[derive(Debug, Clone, Copy)]
pub enum Measurement {
Relative,
Exact,
}
#[derive(Debug, Clone, Copy)]
pub enum Variable {
UnitDuration,
TotalDuration,
}
#[derive(Debug, Clone)]
pub struct Node<'graph> {
id: &'graph PackageId,
timings: Option<Vec<timings::Message>>,
}
impl<'graph> Node<'graph> {
pub fn timings(&self) -> Option<&Vec<timings::Message>> {
self.timings.as_ref()
}
}
#[derive(Debug, Clone)]
pub struct Annotations<'graph> {
package_graph: &'graph PackageGraph,
graph: Graph<Node<'graph>, ()>,
node_indices: IndexMap<&'graph PackageId, usize>,
}
impl<'graph> Annotations<'graph> {
pub fn new(
package_graph: &'graph PackageGraph,
mut timings: HashMap<PackageId, Vec<timings::Message>>,
) -> Self {
let node_indices = package_graph
.package_ids()
.enumerate()
.map(|(index, id)| (id, index))
.collect::<IndexMap<&PackageId, usize>>();
// Iterate over node_indicies.keys() to preserve ordering
let nodes = node_indices.keys().map(|id| Element::Node {
weight: Node {
id,
timings: timings.remove(id),
},
});
let package_set = package_graph.resolve_all();
let edges = package_set
.links(DependencyDirection::Forward)
.map(|link| Element::Edge {
source: *node_indices.get(link.from().id()).unwrap(),
target: *node_indices.get(link.to().id()).unwrap(),
weight: (),
});
let graph = Graph::from_elements(nodes.chain(edges));
Self {
package_graph,
graph,
node_indices,
}
}
pub fn root_packages(&self) -> Vec<PackageMetadata> {
self.package_graph
.resolve_all()
.root_packages(DependencyDirection::Forward)
.collect::<Vec<_>>()
}
fn node_index(&self, id: &PackageId) -> Option<NodeIndex> {
let node_index = *self.node_indices.get(id)?;
Some(NodeIndex::new(node_index))
}
pub fn pkg(&self, id: &PackageId) -> Option<&Node> {
let node_index = self.node_index(id)?;
Some(&self.graph[node_index])
}
pub fn packages(&self) -> impl Iterator<Item = &&PackageId> {
self.node_indices.keys()
}
pub fn links(&self) -> impl Iterator<Item = (&PackageId, &PackageId)> {
self.graph
.raw_edges()
.iter()
.map(|edge| (self.graph[edge.source()].id, self.graph[edge.target()].id))
}
pub fn metadata(&self, id: &PackageId) -> Result<PackageMetadata, guppy::Error> {
self.package_graph.metadata(id)
}
pub fn dependents(&self, id: &PackageId) -> Option<usize> {
let node_index = self.node_index(id)?;
let dependents = self
.graph
.edges_directed(node_index, Direction::Incoming)
.count();
Some(dependents)
}
pub fn variable(
&self,
id: &PackageId,
variable: Variable,
measurement: Measurement,
) -> Option<f64> {
let exact_measurement = match variable {
Variable::UnitDuration => {
let node = self.pkg(id)?;
let timings = node.timings.as_ref()?;
let durations = timings.iter().map(|msg| msg.duration);
if id.repr().contains("windows") {
dbg!(id, timings);
}
durations.sum()
}
Variable::TotalDuration => {
let node_index = self.node_index(id)?;
let dependencies = self
.graph
.neighbors_directed(node_index, Direction::Outgoing);
let timings = dependencies
.filter_map(|dep| self.graph[dep].timings.as_ref())
.flatten()
.map(|msg| msg.duration);
let self_timings = self
.variable(id, Variable::UnitDuration, measurement)
.unwrap_or(0_f64);
timings.sum::<f64>() + self_timings
}
};
Some(match measurement {
Measurement::Exact => exact_measurement,
Measurement::Relative => {
let dependents = self.dependents(id)? as f64;
if dependents.is_zero() {
exact_measurement
} else {
exact_measurement / dependents
}
}
})
}
}