aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAria Shrimpton <me@aria.rip>2024-01-20 00:15:57 +0000
committerAria Shrimpton <me@aria.rip>2024-01-20 00:15:57 +0000
commit43b346ef8ab91dc13b773dda871433cf98adade8 (patch)
treefdf6ef7189ca308e757ad48f7987fd2cdcdac974
parent8d83fe167a2eb1ec51e9f2a4692c03ceedad80ee (diff)
feat(cli): command for breakdown of cost estimate
-rw-r--r--src/crates/candelabra/src/profiler.rs47
-rw-r--r--src/crates/cli/src/estimate.rs70
-rw-r--r--src/crates/cli/src/main.rs3
3 files changed, 119 insertions, 1 deletions
diff --git a/src/crates/candelabra/src/profiler.rs b/src/crates/candelabra/src/profiler.rs
index 3897d97..b6e010c 100644
--- a/src/crates/candelabra/src/profiler.rs
+++ b/src/crates/candelabra/src/profiler.rs
@@ -17,7 +17,7 @@ use tempfile::tempdir;
use crate::cache::{gen_tree_hash, FileCache};
use crate::candidates::ConTypeName;
-use crate::cost::CostModel;
+use crate::cost::{Cost, CostModel};
use crate::project::Project;
use crate::{Paths, State};
@@ -32,6 +32,20 @@ pub(crate) struct CacheEntry {
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ProfilerInfo(pub Vec<CollectionLifetime>);
+/// Breakdown of a cost value
+#[derive(Clone, Debug)]
+pub struct CostBreakdown {
+ pub contains: Cost,
+ pub insert: Cost,
+ pub clear: Cost,
+ pub remove: Cost,
+ pub first: Cost,
+ pub last: Cost,
+ pub nth: Cost,
+ pub push: Cost,
+ pub pop: Cost,
+}
+
/// Profiler info collected from the lifetime of a single collection instance
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CollectionLifetime {
@@ -47,12 +61,43 @@ pub struct CollectionLifetime {
pub pop: usize,
}
+macro_rules! agg_op_cost {
+ ($this:ident, $cost_model:ident, $op:ident) => {
+ $this
+ .0
+ .iter()
+ .map(|cl| {
+ $cost_model
+ .by_op
+ .get(stringify!($op))
+ .unwrap()
+ .estimatef(cl.avg_n())
+ * cl.$op as f64
+ })
+ .sum()
+ };
+}
+
impl ProfilerInfo {
pub fn estimate_cost(&self, cost_model: &CostModel) -> f64 {
let sum: f64 = self.0.iter().map(|cl| cl.estimate_cost(cost_model)).sum();
sum / self.0.len() as f64
}
+
+ pub fn cost_breakdown(&self, cost_model: &CostModel) -> CostBreakdown {
+ CostBreakdown {
+ contains: agg_op_cost!(self, cost_model, contains),
+ insert: agg_op_cost!(self, cost_model, insert),
+ clear: agg_op_cost!(self, cost_model, clear),
+ remove: agg_op_cost!(self, cost_model, remove),
+ first: agg_op_cost!(self, cost_model, first),
+ last: agg_op_cost!(self, cost_model, last),
+ nth: agg_op_cost!(self, cost_model, nth),
+ push: agg_op_cost!(self, cost_model, push),
+ pop: agg_op_cost!(self, cost_model, pop),
+ }
+ }
}
impl CollectionLifetime {
diff --git a/src/crates/cli/src/estimate.rs b/src/crates/cli/src/estimate.rs
new file mode 100644
index 0000000..27948b8
--- /dev/null
+++ b/src/crates/cli/src/estimate.rs
@@ -0,0 +1,70 @@
+use std::collections::HashMap;
+
+use anyhow::{anyhow, bail, Result};
+use argh::FromArgs;
+use log::info;
+use tabled::{builder::Builder, settings::Style};
+
+use crate::State;
+
+/// Estimate the cost of a given set of assignments for a specific project, and detail how that was reached.
+#[derive(FromArgs)]
+#[argh(subcommand, name = "estimate")]
+pub struct Args {
+ /// the assignments, written <type name> <implementation> ...
+ #[argh(positional, greedy)]
+ assignments: Vec<String>,
+}
+
+impl State {
+ pub fn cmd_estimate(&self, args: Args) -> Result<()> {
+ if self.projects.len() > 1 {
+ bail!("estimate must target only one project!")
+ }
+ let proj = &self.projects[0];
+
+ // parse assignment list
+ if args.assignments.len() % 2 != 0 {
+ bail!("assignments list must have even length");
+ }
+ let assignments = args
+ .assignments
+ .iter()
+ .step_by(2)
+ .zip(args.assignments.iter().skip(1))
+ .collect::<HashMap<_, _>>();
+
+ // get breakdown by operation for each assignment
+ info!("Contribution to cost by operation:");
+
+ let profile_info = self.inner.profiler_info(proj)?;
+ let mut builder = Builder::default();
+ builder.set_header([
+ "con type", "contains", "insert", "clear", "remove", "first", "last", "nth", "push",
+ "pop",
+ ]);
+ for (con_type_name, impl_name) in assignments.iter() {
+ let profiler = profile_info
+ .get(*con_type_name)
+ .ok_or_else(|| anyhow!("no profiling info for {} - wrong name?", con_type_name))?;
+ let cost_model = self.inner.cost_model(&impl_name)?;
+ let breakdown = profiler.cost_breakdown(&cost_model);
+ builder.push_record([
+ *impl_name,
+ &breakdown.contains.to_string(),
+ &breakdown.insert.to_string(),
+ &breakdown.clear.to_string(),
+ &breakdown.remove.to_string(),
+ &breakdown.first.to_string(),
+ &breakdown.last.to_string(),
+ &breakdown.nth.to_string(),
+ &breakdown.push.to_string(),
+ &breakdown.pop.to_string(),
+ ]);
+ }
+
+ println!("{}", builder.build().with(Style::sharp()));
+
+ Ok(())
+ }
+}
diff --git a/src/crates/cli/src/main.rs b/src/crates/cli/src/main.rs
index 1b926a7..a63f5d5 100644
--- a/src/crates/cli/src/main.rs
+++ b/src/crates/cli/src/main.rs
@@ -4,6 +4,7 @@ use candelabra::{Paths, Project};
use log::info;
mod candidates;
+mod estimate;
mod library;
mod model;
mod profile;
@@ -32,6 +33,7 @@ pub enum Subcommand {
Candidates(candidates::Args),
Profile(profile::Args),
Select(select::Args),
+ Estimate(estimate::Args),
}
fn main() -> Result<()> {
@@ -52,6 +54,7 @@ fn main() -> Result<()> {
Subcommand::Candidates(a) => state.cmd_candidates(a),
Subcommand::Profile(a) => state.cmd_profile(a),
Subcommand::Select(a) => state.cmd_select(a),
+ Subcommand::Estimate(a) => state.cmd_estimate(a),
}
}