#[cfg(feature = "profiling")]
use core::time::Duration;
#[cfg(feature = "profiling")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Stage {
ContentParse,
Interpretation,
AnnotList,
FormFonts,
GenerateAppearances,
OpenAction,
AnnotLoop,
Raster,
}
#[cfg(feature = "profiling")]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Profile {
pub stage_time: [Duration; 8],
pub stage_calls: [u64; 8],
pub form_font_misses: u64,
}
#[cfg(feature = "profiling")]
impl Stage {
#[must_use]
pub const fn index(self) -> usize {
match self {
Stage::ContentParse => 0,
Stage::Interpretation => 1,
Stage::AnnotList => 2,
Stage::FormFonts => 3,
Stage::GenerateAppearances => 4,
Stage::OpenAction => 5,
Stage::AnnotLoop => 6,
Stage::Raster => 7,
}
}
pub const ALL: [Stage; 8] = [
Stage::ContentParse,
Stage::Interpretation,
Stage::AnnotList,
Stage::FormFonts,
Stage::GenerateAppearances,
Stage::OpenAction,
Stage::AnnotLoop,
Stage::Raster,
];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Stage::ContentParse => "content parse",
Stage::Interpretation => "interpretation",
Stage::AnnotList => "annot list",
Stage::FormFonts => "form fonts",
Stage::GenerateAppearances => "generate aps",
Stage::OpenAction => "open action",
Stage::AnnotLoop => "annot loop",
Stage::Raster => "raster",
}
}
#[must_use]
pub const fn in_annotation_pass(self) -> bool {
matches!(
self,
Stage::AnnotList
| Stage::FormFonts
| Stage::GenerateAppearances
| Stage::OpenAction
| Stage::AnnotLoop
)
}
}
#[cfg(feature = "profiling")]
mod imp {
use core::cell::Cell;
use core::time::Duration;
use super::{Profile, Stage};
thread_local! {
static PROFILE: Cell<Profile> = const { Cell::new(Profile {
stage_time: [Duration::ZERO; 8],
stage_calls: [0; 8],
form_font_misses: 0,
}) };
}
pub fn take() -> Profile {
PROFILE.replace(Profile::default())
}
pub fn form_font_miss() {
let mut p = PROFILE.get();
p.form_font_misses = p.form_font_misses.saturating_add(1);
PROFILE.set(p);
}
pub fn stage<T>(stage: Stage, body: impl FnOnce() -> T) -> T {
let started = std::time::Instant::now();
let out = body();
let elapsed = started.elapsed();
let mut p = PROFILE.get();
let i = stage.index();
if let (Some(t), Some(c)) = (p.stage_time.get_mut(i), p.stage_calls.get_mut(i)) {
*t = t.saturating_add(elapsed);
*c = c.saturating_add(1);
}
PROFILE.set(p);
out
}
}
#[cfg(not(feature = "profiling"))]
mod imp {
#[inline]
pub fn form_font_miss() {}
}
pub use imp::form_font_miss;
#[cfg(feature = "profiling")]
pub use imp::{stage, take};
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "profiling")]
#[test]
fn the_indices_are_dense_and_distinct() {
let stages: Vec<usize> = Stage::ALL.iter().map(|s| s.index()).collect();
assert_eq!(stages, (0..Stage::ALL.len()).collect::<Vec<_>>());
}
#[test]
fn the_miss_counter_is_callable_either_way() {
form_font_miss();
}
#[cfg(feature = "profiling")]
#[test]
fn the_stage_wrapper_returns_the_body_s_value() {
assert_eq!(stage(Stage::Raster, || 7_u32), 7);
}
#[cfg(feature = "profiling")]
#[test]
fn the_annotation_pass_is_the_five_stages_between_the_build_and_the_raster() {
let pass: Vec<&str> = Stage::ALL
.iter()
.filter(|s| s.in_annotation_pass())
.map(|s| s.name())
.collect();
assert_eq!(
pass,
[
"annot list",
"form fonts",
"generate aps",
"open action",
"annot loop"
]
);
for stage in [Stage::ContentParse, Stage::Interpretation, Stage::Raster] {
assert!(!stage.in_annotation_pass(), "{stage:?}");
}
}
#[cfg(feature = "profiling")]
#[test]
fn taking_the_profile_clears_it() {
let _ = take();
stage(Stage::FormFonts, form_font_miss);
let first = take();
assert_eq!(first.stage_calls.get(Stage::FormFonts.index()), Some(&1));
assert_eq!(first.form_font_misses, 1);
assert_eq!(take(), Profile::default());
}
}