diff --git a/tidepool/src/cli.rs b/tidepool/src/cli.rs index db845f9..63f5a6a 100644 --- a/tidepool/src/cli.rs +++ b/tidepool/src/cli.rs @@ -42,12 +42,12 @@ pub struct CliArgs { #[derive(Clone, Debug, Args)] pub struct OutputDataArgs { - /// Generate profiling data in the output. - #[arg(short = 'P', long)] - pub profile: bool, /// Generate the full list of moves in the output. #[arg(short = 'L', long)] pub list_moves: bool, + /// Generate algorithm metrics in the output. + #[arg(short = 'M', long)] + pub metrics: bool, } #[derive(Clone, Debug, Args)] diff --git a/tidepool/src/main.rs b/tidepool/src/main.rs index ead801d..91bfca4 100644 --- a/tidepool/src/main.rs +++ b/tidepool/src/main.rs @@ -341,6 +341,7 @@ fn run_simulation( exit_early: &atomic::AtomicBool, ) { let mut moves = data_args.list_moves.then(Vec::new); + let mut metrics = data_args.metrics.then(Vec::new); let seed = seed.unwrap_or_else(|| rand::thread_rng().next_u64()); let weights = eval::Weights::default(); @@ -363,6 +364,9 @@ fn run_simulation( sim.matrix(), mino::Queue::new(sim.queue().hold(), sim.queue().next()), ); + if data_args.metrics { + bot.start_instrumenting(); + } while bot.iterations() < config.bot.iters { let gas = std::cmp::min(50_000, config.bot.iters - bot.iterations()); @@ -385,6 +389,9 @@ fn run_simulation( if let Some(moves) = moves.as_mut() { moves.push(output::Move { placement }) } + if let Some(metrics) = metrics.as_mut() { + metrics.push(bot.metrics().unwrap()); + } let status = Status { lines_left: sim.lines_left(), @@ -396,17 +403,13 @@ fn run_simulation( } } - let profile = data_args.profile.then(|| output::Profile { - time: Instant::now() - time_start, - }); - let output = output::Output { seed, cleared: config.game.goal - sim.lines_left(), pieces: sim.pieces(), config: config.game, moves, - profile, + metrics, }; if tx.send(Msg::Output(id, output.into())).is_err() { tracing::debug!("nobody received output id={id}"); diff --git a/tidepool/src/output.rs b/tidepool/src/output.rs index 11a666a..289d946 100644 --- a/tidepool/src/output.rs +++ b/tidepool/src/output.rs @@ -1,6 +1,6 @@ +use fish::bot::Metrics; use mino::srs::Piece; use serde::Serialize; -use std::time::Duration; use crate::config::GameConfig; @@ -11,10 +11,13 @@ pub struct Output { pub config: GameConfig, pub pieces: usize, pub cleared: usize, - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] pub moves: Option>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub profile: Option, + #[serde( + serialize_with = "ser::metrics", + skip_serializing_if = "Option::is_none" + )] + pub metrics: Option>, } impl Output { @@ -30,16 +33,6 @@ pub struct Move { // TODO: spin? } -#[derive(Serialize, Debug)] -pub struct Profile { - #[serde(serialize_with = "ser::seconds")] - pub time: Duration, - // TODO: pps mean/variance - // TODO: timing stats for specific functions/routines - // TODO: stats info about top evaluations - // TODO: histograms about selected nodes -} - #[derive(Copy, Clone, Debug)] pub struct SummaryStats { count: usize, @@ -167,11 +160,56 @@ mod ser { .serialize(serializer) } - pub fn seconds(dur: &Duration, serializer: S) -> Result + pub fn metrics(metrics: &Option>, serializer: S) -> Result where S: serde::Serializer, { - dur.as_secs_f64().serialize(serializer) + #[derive(Serialize)] + struct MoveMetrics { + start: NodeMetrics, + end: NodeMetrics, + } + + #[derive(Serialize)] + struct NodeMetrics { + features: Vec, + heuristic: i32, + #[serde(flatten)] + rating: RatingVariant, + #[serde(skip_serializing_if = "Option::is_none")] + iteration: Option, + } + + #[derive(Serialize)] + #[serde(untagged)] + enum RatingVariant { + NotApplicable, + Rating { rating: i32 }, + Solution { solution: i32 }, + } + + metrics + .iter() + .flat_map(|m| m.iter()) + .map(|m| MoveMetrics { + start: NodeMetrics { + features: m.start_features.1.to_vec(), + heuristic: m.start_heuristic, + rating: RatingVariant::NotApplicable, + iteration: None, + }, + end: NodeMetrics { + features: m.end_features.1.to_vec(), + heuristic: m.end_heuristic, + rating: match m.end_rating { + (false, v) => RatingVariant::Rating { rating: v }, + (true, v) => RatingVariant::Solution { solution: -v }, + }, + iteration: Some(m.end_iteration), + }, + }) + .collect::>() + .serialize(serializer) } pub fn summary_stats(stats: &SummaryStats, serializer: S) -> Result