Skip to content

Commit 23ea962

Browse files
committed
refactor: add timed! macro and TimedStep for timing infrastructure
Replace manual Instant::now()/elapsed()/println!() boilerplate with two primitives: timed! for hot-path Duration accumulation (pipeline.rs) and TimedStep for progressive CLI output with auto dot-padding and tracing spans (main.rs, processing.rs, cv.rs). Duration display uses {:?} for automatic unit selection everywhere.
1 parent 7932791 commit 23ea962

5 files changed

Lines changed: 167 additions & 116 deletions

File tree

rust/timsseek/src/ml/cv.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub use super::TargetDecoy;
2+
use crate::scoring::timings::TimedStep;
23
pub use forust_ml::constraints::{
34
Constraint,
45
ConstraintMap,
@@ -403,9 +404,9 @@ impl<T: FeatureLike> CrossValidatedScorer<T> {
403404
self.fold_classifiers.clear();
404405
(0..self.n_folds).for_each(|_| self.fold_classifiers.push(None));
405406
for fold in 0..self.n_folds {
406-
let start = std::time::Instant::now();
407+
let step = TimedStep::begin_stderr(format_args!(" Training fold {}/{}...", fold + 1, self.n_folds));
407408
self.fit_fold(fold, train_buffer, val_buffer)?;
408-
eprintln!(" Training fold {}/{} ... {:?}", fold + 1, self.n_folds, start.elapsed());
409+
step.finish();
409410
}
410411

411412
Ok(())
@@ -415,7 +416,7 @@ impl<T: FeatureLike> CrossValidatedScorer<T> {
415416
let mut scores = vec![0.0; self.data.len()];
416417
let mut buffer = DataBuffer::default();
417418

418-
let score_start = std::time::Instant::now();
419+
let step = TimedStep::begin_stderr(" Scoring folds...");
419420
for train_i in 0..self.n_folds {
420421
let early_stop_i = self.next_fold(train_i);
421422

@@ -440,7 +441,7 @@ impl<T: FeatureLike> CrossValidatedScorer<T> {
440441
}
441442
}
442443
}
443-
eprintln!(" Scoring folds ... {:?}", score_start.elapsed());
444+
step.finish();
444445

445446
let div_factor = (self.n_folds - 2) as f64;
446447
scores.iter_mut().for_each(|x| {

rust/timsseek/src/scoring/pipeline.rs

Lines changed: 44 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use crate::{
2828
};
2929
#[cfg(feature = "rayon")]
3030
use rayon::prelude::*;
31-
use std::time::Instant;
31+
use crate::timed;
3232
use timscentroid::rt_mapping::{
3333
MS1CycleIndex,
3434
RTIndex,
@@ -599,15 +599,14 @@ impl<I: ScorerQueriable> Scorer<I> {
599599
buffer: &mut TraceScorer,
600600
timings: &mut ScoreTimings,
601601
) -> Option<ScoredCandidate> {
602-
let st = Instant::now();
603-
let (metadata, scoring_ctx) =
602+
let (metadata, scoring_ctx) = timed!(timings.extraction,
604603
tracing::span!(tracing::Level::TRACE, "score_calibrated::extraction").in_scope(
605604
|| match self.build_calibrated_extraction(item, calibration) {
606605
Ok(result) => Some(result),
607606
Err(_) => None,
608607
},
609-
)?;
610-
timings.extraction += st.elapsed();
608+
)
609+
)?;
611610

612611
if scoring_ctx
613612
.expected_intensities
@@ -617,39 +616,37 @@ impl<I: ScorerQueriable> Scorer<I> {
617616
return None;
618617
}
619618

620-
let st = Instant::now();
621-
let apex_score =
619+
let apex_score = timed!(timings.scoring,
622620
tracing::span!(tracing::Level::TRACE, "score_calibrated::apex_scoring").in_scope(
623621
|| {
624622
buffer
625623
.find_apex(&scoring_ctx, &|idx| self.map_rt_index_to_milis(idx))
626624
.ok()
627625
},
628-
)?;
629-
timings.scoring += st.elapsed();
626+
)
627+
)?;
630628

631-
let st = Instant::now();
632-
let spectral_tol = calibration.get_spectral_tolerance();
633-
let isotope_tol = calibration.get_isotope_tolerance();
634-
let (inner_collector, isotope_collector) =
629+
let (inner_collector, isotope_collector) = timed!(timings.spectral_query, {
630+
let spectral_tol = calibration.get_spectral_tolerance();
631+
let isotope_tol = calibration.get_isotope_tolerance();
635632
tracing::span!(tracing::Level::TRACE, "score_calibrated::secondary_query")
636-
.in_scope(|| self.execute_secondary_query(item, &apex_score, &spectral_tol, &isotope_tol));
637-
timings.spectral_query += st.elapsed();
633+
.in_scope(|| self.execute_secondary_query(item, &apex_score, &spectral_tol, &isotope_tol))
634+
});
638635

639636
let nqueries = scoring_ctx.chromatograms.fragments.num_ions() as u8;
640-
let st = Instant::now();
641-
let out = tracing::span!(tracing::Level::TRACE, "score_calibrated::finalize").in_scope(
642-
|| {
643-
self.finalize_results(
644-
&metadata,
645-
nqueries,
646-
&apex_score,
647-
&inner_collector,
648-
&isotope_collector,
649-
)
650-
},
637+
let out = timed!(timings.assembly,
638+
tracing::span!(tracing::Level::TRACE, "score_calibrated::finalize").in_scope(
639+
|| {
640+
self.finalize_results(
641+
&metadata,
642+
nqueries,
643+
&apex_score,
644+
&inner_collector,
645+
&isotope_collector,
646+
)
647+
},
648+
)
651649
);
652-
timings.assembly += st.elapsed();
653650

654651
match out {
655652
Ok(res) => Some(res),
@@ -722,21 +719,20 @@ impl<I: ScorerQueriable> Scorer<I> {
722719
buffer: &mut TraceScorer,
723720
timings: &mut super::timings::PrescoreTimings,
724721
) -> Option<ApexLocation> {
725-
let st = Instant::now();
726-
let scoring_ctx = tracing::span!(tracing::Level::TRACE, "prescore::extraction")
727-
.in_scope(|| {
728-
super::extraction::build_extraction(
729-
&item.query,
730-
item.expected_intensity.clone(),
731-
&self.index,
732-
&self.broad_tolerance,
733-
Some(TOP_N_FRAGMENTS),
734-
)
735-
.ok()
736-
});
737-
timings.extraction += st.elapsed();
722+
let scoring_ctx = timed!(timings.extraction,
723+
tracing::span!(tracing::Level::TRACE, "prescore::extraction")
724+
.in_scope(|| {
725+
super::extraction::build_extraction(
726+
&item.query,
727+
item.expected_intensity.clone(),
728+
&self.index,
729+
&self.broad_tolerance,
730+
Some(TOP_N_FRAGMENTS),
731+
)
732+
.ok()
733+
})
734+
)?;
738735

739-
let scoring_ctx = scoring_ctx?;
740736
if scoring_ctx
741737
.expected_intensities
742738
.fragment_intensities
@@ -745,13 +741,13 @@ impl<I: ScorerQueriable> Scorer<I> {
745741
return None;
746742
}
747743

748-
let st = Instant::now();
749-
let result = tracing::span!(tracing::Level::TRACE, "prescore::scoring").in_scope(|| {
750-
buffer
751-
.find_apex_location(&scoring_ctx, &|idx| self.map_rt_index_to_milis(idx))
752-
.ok()
753-
});
754-
timings.scoring += st.elapsed();
744+
let result = timed!(timings.scoring,
745+
tracing::span!(tracing::Level::TRACE, "prescore::scoring").in_scope(|| {
746+
buffer
747+
.find_apex_location(&scoring_ctx, &|idx| self.map_rt_index_to_milis(idx))
748+
.ok()
749+
})
750+
);
755751

756752
if result.is_some() {
757753
timings.n_scored += 1;

rust/timsseek/src/scoring/timings.rs

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,98 @@
33
//! This module provides timing measurement for each stage of the scoring process.
44
//! The timings are aggregated across parallel scoring operations to provide overall
55
//! performance metrics.
6+
//!
7+
//! Two helpers reduce timing boilerplate:
8+
//!
9+
//! - [`timed!`] — times a block and accumulates elapsed into a `Duration` field.
10+
//! - [`TimedStep`] — progressive CLI output: prints a label immediately, then
11+
//! appends elapsed time when the work finishes.
612
713
use serde::Serialize;
8-
use std::time::Duration;
14+
use std::fmt;
15+
use std::time::{Duration, Instant};
16+
17+
/// Time a block, accumulate elapsed into `$target`, return the block's value.
18+
///
19+
/// ```ignore
20+
/// let ctx = timed!(timings.extraction, {
21+
/// build_extraction(&query, &index, &tolerance)
22+
/// })?;
23+
/// ```
24+
#[doc(hidden)]
25+
#[macro_export]
26+
macro_rules! timed {
27+
($target:expr, $body:expr) => {{
28+
let __start = std::time::Instant::now();
29+
let __result = $body;
30+
$target += __start.elapsed();
31+
__result
32+
}};
33+
}
34+
35+
/// A timed step that prints a dot-padded label immediately, opens a tracing
36+
/// span, and appends elapsed time on finish.
37+
///
38+
/// ```ignore
39+
/// let step = TimedStep::begin("Loading speclib");
40+
/// let speclib = load_speclib()?;
41+
/// let elapsed = step.finish_with(format_args!("{} entries", speclib.len()));
42+
/// // terminal: "Loading speclib .......... 834.567ms (225178 entries)"
43+
/// ```
44+
pub struct TimedStep {
45+
start: Instant,
46+
stderr: bool,
47+
_span: tracing::span::EnteredSpan,
48+
}
49+
50+
/// Column width for dot-padded labels on stdout.
51+
const LABEL_WIDTH: usize = 26;
52+
53+
impl TimedStep {
54+
/// Dot-pad `label` to stdout, open a tracing span, flush, start clock.
55+
pub fn begin(label: impl fmt::Display) -> Self {
56+
let label = label.to_string();
57+
let span = tracing::info_span!("step", label = label.as_str());
58+
let dots = LABEL_WIDTH.saturating_sub(label.len() + 1);
59+
if dots > 0 {
60+
print!("{label} {:.<width$}", "", width = dots);
61+
} else {
62+
print!("{label}");
63+
}
64+
std::io::Write::flush(&mut std::io::stdout()).ok();
65+
Self { start: Instant::now(), stderr: false, _span: span.entered() }
66+
}
67+
68+
/// Print `label` to stderr (no dot-padding), open a tracing span, start clock.
69+
pub fn begin_stderr(label: impl fmt::Display) -> Self {
70+
let label = label.to_string();
71+
let span = tracing::info_span!("step", label = label.as_str());
72+
eprint!("{label}");
73+
Self { start: Instant::now(), stderr: true, _span: span.entered() }
74+
}
75+
76+
/// Print ` {elapsed:?}\n`, return Duration.
77+
pub fn finish(self) -> Duration {
78+
let d = self.start.elapsed();
79+
self.emit(format_args!(" {:?}", d));
80+
d
81+
}
82+
83+
/// Print ` {elapsed:?} ({detail})\n`, return Duration.
84+
pub fn finish_with(self, detail: impl fmt::Display) -> Duration {
85+
let d = self.start.elapsed();
86+
self.emit(format_args!(" {:?} ({})", d, detail));
87+
d
88+
}
89+
90+
fn emit(&self, msg: fmt::Arguments<'_>) {
91+
if self.stderr {
92+
eprintln!("{msg}");
93+
} else {
94+
println!("{msg}");
95+
}
96+
}
97+
}
998

1099
/// Accumulated timing measurements for the four scoring stages.
11100
///

rust/timsseek_cli/src/main.rs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use timsquery::TimsTofPath;
88
use timsquery::serde::load_index_auto;
99
use timsquery::utils::TupleRange;
1010
use timsseek::scoring::Scorer;
11+
use timsseek::scoring::timings::TimedStep;
1112
use tracing::{
1213
error,
1314
info,
@@ -252,7 +253,7 @@ fn process_single_file(
252253
path: Some(dotd_file.to_string_lossy().to_string()),
253254
})?;
254255

255-
let index_start = std::time::Instant::now();
256+
let step = TimedStep::begin("Loading index");
256257
let index = load_index_auto(
257258
dotd_file.to_str().ok_or_else(|| errors::CliError::Io {
258259
source: "Invalid path encoding".to_string(),
@@ -261,8 +262,7 @@ fn process_single_file(
261262
None,
262263
)?
263264
.into_eager()?;
264-
let load_index_ms = index_start.elapsed().as_millis() as u64;
265-
println!("Loading index ........... {:.1}s", load_index_ms as f64 / 1000.0);
265+
let load_index_ms = step.finish().as_millis() as u64;
266266

267267
let fragmented_range = get_frag_range(&timstofpath)?;
268268

@@ -505,31 +505,29 @@ fn main() -> std::result::Result<(), errors::CliError> {
505505
let mut successful_files: Vec<std::path::PathBuf> = Vec::new();
506506

507507
// Load speclib once (shared across all files)
508-
let speclib_start = std::time::Instant::now();
508+
let step = TimedStep::begin("Loading speclib");
509509
info!("Building database from speclib file {:?}", validated.speclib_path);
510510
info!("Decoy generation strategy: {}", config.analysis.decoy_strategy);
511511
let speclib = timsseek::data_sources::speclib::Speclib::from_file(
512512
&validated.speclib_path,
513513
config.analysis.decoy_strategy,
514514
).map_err(|e| errors::CliError::Config { source: format!("Failed to load speclib: {:?}", e) })?;
515-
let load_speclib_ms = speclib_start.elapsed().as_millis() as u64;
516-
println!("Loading speclib ......... {:.1}s ({} entries)", load_speclib_ms as f64 / 1000.0, speclib.len());
515+
let load_speclib_ms = step.finish_with(format_args!("{} entries", speclib.len())).as_millis() as u64;
517516

518517
// Load calibration library once (if provided)
519-
let calib_start = std::time::Instant::now();
520-
let calib_lib = match &validated.calib_lib_path {
518+
let (calib_lib, load_calib_lib_ms) = match &validated.calib_lib_path {
521519
Some(p) => {
520+
let step = TimedStep::begin("Loading calib lib");
522521
info!("Loading calibration library from {:?}", p);
523522
let lib = timsseek::data_sources::speclib::Speclib::from_file(
524523
p,
525524
config.analysis.decoy_strategy,
526525
).map_err(|e| errors::CliError::Config { source: format!("Failed to load calib lib: {:?}", e) })?;
527-
println!("Loading calib lib ....... {:.1}s ({} entries)", calib_start.elapsed().as_secs_f64(), lib.len());
528-
Some(lib)
526+
let ms = step.finish_with(format_args!("{} entries", lib.len())).as_millis() as u64;
527+
(Some(lib), ms)
529528
}
530-
None => None,
529+
None => (None, 0),
531530
};
532-
let load_calib_lib_ms = calib_start.elapsed().as_millis() as u64;
533531

534532
run_report.load_speclib_ms = load_speclib_ms;
535533
run_report.load_calib_lib_ms = load_calib_lib_ms;

0 commit comments

Comments
 (0)