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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use std::fmt;
// Copyright 2021-2023 Protocol Labs
// SPDX-License-Identifier: Apache-2.0, MIT
use std::sync::{Arc, OnceLock};
use std::time::Duration;

use minstant::Instant;

/// Shared reference between the duration and the timer.
type DurationCell = Arc<OnceLock<Duration>>;

/// Data structure to encapsulate the optional duration which is set by the `GasTimer`.
///
/// This is normally created with an empty inner, because at the point of creation
/// we don't know if tracing is on or not. It will be filled in later by `GasTimer`.
#[derive(Default, Clone)]
pub struct GasDuration(GasDurationInner);

#[derive(Default, Clone)]
pub enum GasDurationInner {
    #[default]
    None,
    Atomic(DurationCell),
    Constant(Duration),
}

impl GasDuration {
    pub fn get(&self) -> Option<&Duration> {
        match &self.0 {
            GasDurationInner::None => None,
            GasDurationInner::Atomic(d) => d.get(),
            GasDurationInner::Constant(d) => Some(d),
        }
    }
}

impl From<Duration> for GasDuration {
    fn from(d: Duration) -> Self {
        GasDuration(GasDurationInner::Constant(d))
    }
}

impl fmt::Debug for GasDuration {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_tuple("GasDuration")
            .field(&self.get() as &dyn fmt::Debug)
            .finish()
    }
}

/// Type alias so that we can disable this with a compiler flag.
pub type GasInstant = Instant;

/// A handle returned by `charge_gas` which must be used to mark the end of
/// the execution associated with that gas.
#[derive(Debug)]
pub struct GasTimer(Option<GasTimerInner>);

#[derive(Debug)]
struct GasTimerInner {
    start: GasInstant,
    elapsed: DurationCell,
}

impl GasTimer {
    /// Convenience method to start measuring time before the charge is made.
    ///
    /// Use the return value with [GasTimer::finish_with] to override the internal
    /// instant that the timer was started with.
    pub fn start() -> GasInstant {
        GasInstant::now()
    }

    /// Create a timer that doesn't measure anything.
    pub fn empty() -> Self {
        GasTimer(None)
    }

    /// Create a new timer that will update the elapsed time of a charge when it's finished.
    ///
    /// As a side effect it will establish the cell in the `GasDuration`, if it has been empty so far.
    ///
    /// When compiled in debug mode, passing a "filled" duration will panic.
    pub fn new(duration: &mut GasDuration) -> Self {
        debug_assert!(duration.get().is_none(), "GasCharge::elapsed already set!");
        let cell = match &duration.0 {
            GasDurationInner::None => {
                let cell = DurationCell::default();
                duration.0 = GasDurationInner::Atomic(cell.clone());
                cell
            }
            GasDurationInner::Atomic(cell) if cell.get().is_none() => cell.clone(),
            // If the duration has already been set, the timer is a no-op.
            _ => return Self(None),
        };

        Self(Some(GasTimerInner {
            start: Self::start(),
            elapsed: cell,
        }))
    }

    /// Record the elapsed time since the charge was made.
    pub fn stop(self) {
        if let Some(timer) = self.0 {
            Self::set_elapsed(timer.elapsed, timer.start)
        }
    }

    /// Record the elapsed time based on an instant taken before the charge was made.
    pub fn stop_with(self, start: GasInstant) {
        if let Some(timer) = self.0 {
            Self::set_elapsed(timer.elapsed, start)
        }
    }

    fn set_elapsed(elapsed: Arc<OnceLock<Duration>>, start: GasInstant) {
        elapsed
            .set(start.elapsed())
            .expect("GasCharge::elapsed already set!")
    }

    /// Convenience method to record the elapsed time only if some execution was successful.
    ///
    /// There's no need to record the time of unsuccessful executions because we don't know
    /// how they would compare to successful ones; maybe the error arised before the bulk
    /// of the computation could have taken place.
    pub fn record<R, E>(self, result: Result<R, E>) -> Result<R, E> {
        if result.is_ok() {
            self.stop()
        }
        result
    }
}