use std::fs::File; use std::io::BufWriter; use std::path::{Path, PathBuf}; use typst::diag::{bail, StrResult}; use typst::syntax::Span; use typst::World; use crate::args::{CliArguments, Command}; use crate::world::SystemWorld; /// Allows to record timings of function executions. pub struct Timer { /// Where to save the recorded timings of each compilation step. path: Option, /// The current watch iteration. index: usize, } impl Timer { /// Initializes the timing system and returns a timer that can be used to /// record timings for a specific function invocation. pub fn new(args: &CliArguments) -> Timer { let record = match &args.command { Command::Compile(command) => command.timings.clone(), Command::Watch(command) => command.timings.clone(), _ => None, }; // Enable event collection. if record.is_some() { typst_timing::enable(); } let path = record.map(|path| path.unwrap_or_else(|| PathBuf::from("record-{n}.json"))); Timer { path, index: 0 } } /// Records all timings in `f` and writes them to disk. pub fn record( &mut self, world: &mut SystemWorld, f: impl FnOnce(&mut SystemWorld) -> T, ) -> StrResult { let Some(path) = &self.path else { return Ok(f(world)); }; typst_timing::clear(); let string = path.to_str().unwrap_or_default(); let numbered = string.contains("{n}"); if !numbered && self.index > 0 { bail!("cannot export multiple recordings without `{{n}}` in path"); } let storage; let path = if numbered { storage = string.replace("{n}", &self.index.to_string()); Path::new(&storage) } else { path.as_path() }; let output = f(world); self.index += 1; let file = File::create(path).map_err(|e| format!("failed to create file: {e}"))?; let writer = BufWriter::with_capacity(1 << 20, file); typst_timing::export_json(writer, |span| { resolve_span(world, Span::from_raw(span)) .unwrap_or_else(|| ("unknown".to_string(), 0)) })?; Ok(output) } } /// Turns a span into a (file, line) pair. fn resolve_span(world: &SystemWorld, span: Span) -> Option<(String, u32)> { let id = span.id()?; let source = world.source(id).ok()?; let range = source.range(span)?; let line = source.byte_to_line(range.start)?; Some((format!("{id:?}"), line as u32 + 1)) }