1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
//! Resting place for [Instruments] used across this module
//! TODO 2023-08-02: eventually, merge this and `./src/ogre_std/instruments.rs` together, into OgreStd, probably... making the instrumentation options general


/// Honors the *Zero-Cost Instrumentation Pattern* for the [stream_executors]:\
/// Designed to be used as a const generic parameter for Structs,
/// causes the conditional instrumentation code in client structs to
/// be selectively compiled -- see the implemented *const methods* for
/// documentation of each available instrument.
///
/// Note: Using this enum directly in generics --
/// as in `struct S<const INSTUMENTS: Instruments = { Instruments::NoInstruments }> {...}`
/// -- is not possible yet (as of Rust 1.64): *"adt const params is experimental [E0658]"*,
/// so we'll use it as `struct S<const INSTRUMENTS: usize = 0>` instead, and use [Instruments::from(INSTRUMENTS)]
/// in the instrumentable implementations & `let s = S::<Instruments::<YourOption>::into()> {};` in clients.
#[derive(Debug,Clone,Copy,PartialEq)]
#[repr(usize)]
pub enum Instruments {
    /// No conditional instrumentation code will be included -- bringing in the fastest
    /// possible execution speeds at the expense of lowest operational control
    NoInstruments               = 0,

    LogsWithoutMetrics          = Self::LOG,
    LogsWithMetrics             = Self::LOG | Self::PERFORMANCE_LOG | Self::COUNTERS | Self::SATURATION | Self::CHEAP_PROFILING,
    LogsWithExpensiveMetrics    = Self::LOG | Self::PERFORMANCE_LOG | Self::COUNTERS | Self::SATURATION | Self::EXPENSIVE_PROFILING,
    MetricsWithoutLogs          = Self::COUNTERS | Self::SATURATION | Self::CHEAP_PROFILING,
    ExpensiveMetricsWithoutLogs = Self::COUNTERS | Self::SATURATION | Self::EXPENSIVE_PROFILING,
    Custom(usize),
}

impl Instruments {

    /// metrics: counts every work done & outcome: `processed_events`, `untreated_failures`
    const COUNTERS:            usize = 1;
    /// metrics: keeps track of `avg_buffer_depth`, `max_depth`, serving cheap euristics to avoid needing to use [Instruments::EXPENSIVE_PROFILING]
    const SATURATION:          usize = 2;
    /// keeps track of averages for the execution duration of each received `Future`
    /// -- from the moment the executor starts them to their completion\
    /// (does nothing if the executor is not handling `Future`s)
    const CHEAP_PROFILING:     usize = 4;
    /// keeps track of averages for the total time each event takes to complete
    /// -- from the moment they were *produced* to the moment their *consumption* completes\
    /// (causes an increase in the payload due to the addition of the `u64` *start_time* field)
    const EXPENSIVE_PROFILING: usize = 8;
    /// metrics: computes counters of logarithmic decreasing precision of processing times for [Instruments::CHEAP_PROFILING] & [Instruments::EXPENSIVE_PROFILING], if enabled
    /// -- useful for computing processing/response time percentiles.\
    /// The map is in the form:\
    ///     `counters := {[time] = count, ...}`\
    /// where *time* increase with the power of 2 and counts all requests whose times are between the previous *time* and the current one.\
    /// provides: `futures_times` && `duration_times`
    const PERCENTILES:         usize = 16;
    /// outputs `INFO` on stream's execution start & finish
    const LOG:                 usize = 32;
    /// if [Instruments::LOG] is enabled, periodically outputs information about all enabled metrics & profiling data collected
    const PERFORMANCE_LOG:     usize = 64;
    /// if [Instruments::LOG] is enabled, outputs every event in the *TRACE* level
    const TRACING:             usize = 128;

    /// To be used in if conditions, returns the enum variant
    /// that corresponds to the given const generic numeric value as described in [Self]
    pub const fn from(instruments: usize) -> Self {
        Self::Custom(instruments)
    }

    /// designed to be used by clients of the implementor structs, returns the number to be used as a
    /// const generic numeric value (when instantiating the implementor struct) that corresponds
    /// to the given enum variant
    pub const fn into(self) -> usize {
        match self {
            Self::NoInstruments               => 0,
            Self::LogsWithoutMetrics          => Self::LOG,
            Self::LogsWithMetrics             => Self::LOG | Self::PERFORMANCE_LOG | Self::COUNTERS | Self::SATURATION | Self::CHEAP_PROFILING,
            Self::LogsWithExpensiveMetrics    => Self::LOG | Self::PERFORMANCE_LOG | Self::COUNTERS | Self::SATURATION | Self::EXPENSIVE_PROFILING,
            Self::MetricsWithoutLogs          => Self::COUNTERS | Self::SATURATION | Self::CHEAP_PROFILING,
            Self::ExpensiveMetricsWithoutLogs => Self::COUNTERS | Self::SATURATION | Self::EXPENSIVE_PROFILING,
            Self::Custom(instruments)  => instruments,
        }
    }

    /// returns whether executors should log start/stop in the `INFO` level OR events `TRACING`
    pub const fn logging(self) -> bool {
        self.into() & (Self::LOG | Self::TRACING) > 0
    }

    /// returns whether executors should log events in the `TRACING` level
    pub const fn tracing(self) -> bool {
        self.into() & Self::TRACING > 0
    }

    /// returns whether executors should compute the `CHEAP_PROFILING` metrics
    pub const fn cheap_profiling(self) -> bool {
        self.into() & (Self::COUNTERS | Self::SATURATION | Self::CHEAP_PROFILING | Self::EXPENSIVE_PROFILING) > 0
    }

        /// returns whether executors should compute any of the available metrics
    pub const fn metrics(self) -> bool {
        self.into() & (Self::COUNTERS | Self::SATURATION | Self::CHEAP_PROFILING | Self::EXPENSIVE_PROFILING) > 0
    }



}