Skip to main content

miden_debug_engine/profiling/instrument/
mod.rs

1//! Every distinct kind of profiling data collection is an [`Instrument`] implementation that
2//! lives in a submodule.
3//!
4//! Each instrument is uniquely identified by its [`Instrument::name`].
5
6use alloc::{boxed::Box, string::String};
7
8use miden_core::operations::Operation;
9
10mod op_histogram_global;
11mod op_histogram_proc;
12
13pub use op_histogram_global::OpHistogramGlobal;
14pub use op_histogram_proc::OpHistogramProc;
15
16/// The functionality required for an instrument to be plugged in to `Profiler`.
17pub trait Instrument {
18    /// The human readable name of this instrument used in CLI arguments and user output
19    ///
20    /// This should be the same name as the corresponding `InstrumentRegistration::NAME` constant
21    fn name(&self) -> &'static str;
22    /// To be called each vm cycle an `Operation` is executed.
23    ///
24    /// `proc` is the name of the most recent live procedure, or `None` when the operation cannot
25    /// be attributed to a procedure. For example, it is `None` while executing a program without
26    /// assembly operation metadata.
27    fn on_operation_execution_cycle(&mut self, op: Operation, proc: Option<&str>);
28    /// Write this instrumentation's collected output as a report to `writer`
29    fn write_report_to(&self, writer: &mut dyn OutputWriter) -> OutputResult<()>;
30}
31
32pub type OutputError = Box<dyn core::error::Error + 'static>;
33pub type OutputResult<T> = Result<T, OutputError>;
34
35pub trait OutputWriter {
36    fn write_all(&mut self, buf: &[u8]) -> OutputResult<()>;
37    fn write_fmt(&mut self, args: core::fmt::Arguments<'_>) -> OutputResult<()>;
38}
39
40#[cfg(feature = "std")]
41impl<T: std::io::Write> OutputWriter for T {
42    #[inline]
43    fn write_all(&mut self, buf: &[u8]) -> OutputResult<()> {
44        std::io::Write::write_all(self, buf).map_err(|err| Box::new(err) as Box<_>)
45    }
46
47    #[inline]
48    fn write_fmt(&mut self, args: core::fmt::Arguments<'_>) -> OutputResult<()> {
49        std::io::Write::write_fmt(self, args).map_err(|err| Box::new(err) as Box<_>)
50    }
51}
52
53#[cfg(not(feature = "std"))]
54impl OutputWriter for alloc::vec::Vec<u8> {
55    fn write_all(&mut self, buf: &[u8]) -> OutputResult<()> {
56        self.extend_from_slice(buf);
57        Ok(())
58    }
59
60    fn write_fmt(&mut self, args: core::fmt::Arguments<'_>) -> OutputResult<()> {
61        use alloc::string::ToString;
62        let formatted = args.to_string();
63        self.extend_from_slice(formatted.as_bytes());
64        Ok(())
65    }
66}
67
68/// Represents the information needed to construct an [Instrument] dynamically
69pub trait InstrumentRegistration: Sized + Instrument + 'static {
70    /// The human readable name of this instrument used in CLI arguments and user output
71    const NAME: &'static str;
72
73    /// Create an instance of this instrument with the provided configuration
74    fn build(config: &super::ProfilerConfig) -> Result<Self, InstrumentError>;
75}
76
77#[derive(Debug, thiserror::Error)]
78pub enum InstrumentError {
79    /// The given instrument name is not registered to any known instrument
80    #[error("unknown profiling instrument '{0}'")]
81    Undefined(String),
82    /// We failed to construct the named instrument
83    #[error("failed to construct instrument '{name}': {reason}")]
84    Build { name: String, reason: String },
85}
86
87/// Get an instance of instrument `name`, if one has been registered by that name.
88///
89/// Returns `Err` if no such instrument is registered, or the instrument constructor returned an
90/// error
91#[cfg(feature = "std")]
92pub fn instrument_from_name(
93    name: &str,
94    config: &super::ProfilerConfig,
95) -> Result<Box<dyn Instrument>, InstrumentError> {
96    use alloc::string::ToString;
97
98    for instrument in inventory::iter::<InstrumentRegistrationInfo>() {
99        if instrument.name == name {
100            return (instrument.builder)(config);
101        }
102    }
103    Err(InstrumentError::Undefined(name.to_string()))
104}
105
106#[cfg(feature = "std")]
107#[doc(hidden)]
108pub struct InstrumentRegistrationInfo {
109    name: &'static str,
110    builder: fn(&super::ProfilerConfig) -> Result<Box<dyn Instrument>, InstrumentError>,
111}
112
113#[cfg(feature = "std")]
114impl InstrumentRegistrationInfo {
115    pub const fn new<T: InstrumentRegistration>() -> Self {
116        let name = <T as InstrumentRegistration>::NAME;
117        Self {
118            name,
119            builder: build_instrument::<T>,
120        }
121    }
122}
123
124#[cfg(feature = "std")]
125#[macro_export]
126macro_rules! register_instrument {
127    ($t:ty) => {
128        inventory::submit!($crate::profiling::instrument::InstrumentRegistrationInfo::new::<$t>());
129    };
130}
131
132#[cfg(feature = "std")]
133inventory::collect!(InstrumentRegistrationInfo);
134
135#[cfg(feature = "std")]
136#[inline]
137fn build_instrument<T: InstrumentRegistration>(
138    config: &super::ProfilerConfig,
139) -> Result<Box<dyn Instrument>, InstrumentError> {
140    <T as InstrumentRegistration>::build(config).map(|inst| Box::new(inst) as Box<dyn Instrument>)
141}