summaryrefslogtreecommitdiffstats
path: root/toolkit/components/glean/api/src/private/timespan.rs
blob: f817d6996f244d8b9e6c75bf7462c685c8b52b8c (plain)
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use inherent::inherent;

use super::{CommonMetricData, MetricId, TimeUnit};
use std::convert::TryInto;
use std::time::Duration;

use glean::traits::Timespan;

use crate::ipc::need_ipc;

/// A timespan metric.
///
/// Timespans are used to make a measurement of how much time is spent in a particular task.
pub enum TimespanMetric {
    Parent(glean::private::TimespanMetric, TimeUnit),
    Child,
}

impl TimespanMetric {
    /// Create a new timespan metric.
    pub fn new(_id: MetricId, meta: CommonMetricData, time_unit: TimeUnit) -> Self {
        if need_ipc() {
            TimespanMetric::Child
        } else {
            TimespanMetric::Parent(
                glean::private::TimespanMetric::new(meta, time_unit),
                time_unit,
            )
        }
    }

    /// Only to be called from the MLA FFI.
    /// If you don't know what that is, don't call this.
    pub fn set_raw_unitless(&self, duration: u64) {
        match self {
            TimespanMetric::Parent(p, time_unit) => {
                p.set_raw(Duration::from_nanos(time_unit.as_nanos(duration)));
            }
            TimespanMetric::Child => {
                log::error!(
                    "Unable to set_raw_unitless on timespan in non-main process. This operation will be ignored."
                );
                // If we're in automation we can panic so the instrumentor knows they've gone wrong.
                // This is a deliberate violation of Glean's "metric APIs must not throw" design.
                assert!(!crate::ipc::is_in_automation(), "Attempted to set_raw_unitless on timespan metric in non-main process, which is forbidden. This panics in automation.");
                // TODO: Record an error. bug 1704504.
            }
        }
    }
}

#[inherent]
impl Timespan for TimespanMetric {
    pub fn start(&self) {
        match self {
            TimespanMetric::Parent(p, _) => p.start(),
            TimespanMetric::Child => {
                log::error!("Unable to start timespan metric in non-main process. This operation will be ignored.");
                // If we're in automation we can panic so the instrumentor knows they've gone wrong.
                // This is a deliberate violation of Glean's "metric APIs must not throw" design.assert!(!crate::ipc::is_in_automation());
                assert!(!crate::ipc::is_in_automation(), "Attempted to start timespan metric in non-main process, which is forbidden. This panics in automation.");
                // TODO: Record an error. bug 1704504.
            }
        }
    }

    pub fn stop(&self) {
        match self {
            TimespanMetric::Parent(p, _) => p.stop(),
            TimespanMetric::Child => {
                log::error!("Unable to stop timespan metric in non-main process. This operation will be ignored.");
                // If we're in automation we can panic so the instrumentor knows they've gone wrong.
                // This is a deliberate violation of Glean's "metric APIs must not throw" design.assert!(!crate::ipc::is_in_automation());
                assert!(!crate::ipc::is_in_automation(), "Attempted to stop timespan metric in non-main process, which is forbidden. This panics in automation.");
                // TODO: Record an error. bug 1704504.
            }
        }
    }

    pub fn cancel(&self) {
        match self {
            TimespanMetric::Parent(p, _) => p.cancel(),
            TimespanMetric::Child => {
                log::error!("Unable to cancel timespan metric in non-main process. This operation will be ignored.");
                // If we're in automation we can panic so the instrumentor knows they've gone wrong.
                // This is a deliberate violation of Glean's "metric APIs must not throw" design.assert!(!crate::ipc::is_in_automation());
                assert!(!crate::ipc::is_in_automation(), "Attempted to cancel timespan metric in non-main process, which is forbidden. This panics in automation.");
                // TODO: Record an error. bug 1704504.
            }
        }
    }

    pub fn set_raw(&self, elapsed: Duration) {
        let elapsed = elapsed.as_nanos().try_into().unwrap_or(i64::MAX);
        match self {
            TimespanMetric::Parent(p, _) => p.set_raw_nanos(elapsed),
            TimespanMetric::Child => {
                log::error!("Unable to set_raw on timespan in non-main process. This operation will be ignored.");
                // If we're in automation we can panic so the instrumentor knows they've gone wrong.
                // This is a deliberate violation of Glean's "metric APIs must not throw" design.assert!(!crate::ipc::is_in_automation());
                assert!(!crate::ipc::is_in_automation(), "Attempted to set_raw on timespan metric in non-main process, which is forbidden. This panics in automation.");
                // TODO: Record an error. bug 1704504.
            }
        }
    }

    pub fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<u64> {
        let ping_name = ping_name.into().map(|s| s.to_string());
        match self {
            // Conversion is ok here:
            // Timespans are really tricky to set to excessive values with the pleasant APIs.
            TimespanMetric::Parent(p, _) => p.test_get_value(ping_name).map(|i| i as u64),
            TimespanMetric::Child => {
                panic!("Cannot get test value for in non-main process!");
            }
        }
    }

    pub fn test_get_num_recorded_errors(&self, error: glean::ErrorType) -> i32 {
        match self {
            TimespanMetric::Parent(p, _) => p.test_get_num_recorded_errors(error),
            TimespanMetric::Child => {
                panic!("Cannot get the number of recorded errors for timespan metric in non-main process!");
            }
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::{common_test::*, ipc, metrics};

    #[test]
    fn smoke_test_timespan() {
        let _lock = lock_test();

        let metric = TimespanMetric::new(
            0.into(),
            CommonMetricData {
                name: "timespan_metric".into(),
                category: "telemetry".into(),
                send_in_pings: vec!["store1".into()],
                disabled: false,
                ..Default::default()
            },
            TimeUnit::Nanosecond,
        );

        metric.start();
        // Stopping right away might not give us data, if the underlying clock source is not precise
        // enough.
        // So let's cancel and make sure nothing blows up.
        metric.cancel();

        assert_eq!(None, metric.test_get_value("store1"));
    }

    #[test]
    fn timespan_ipc() {
        let _lock = lock_test();
        let _raii = ipc::test_set_need_ipc(true);

        let child_metric = &metrics::test_only::can_we_time_it;

        // Instrumentation calls do not panic.
        child_metric.start();
        // Stopping right away might not give us data,
        // if the underlying clock source is not precise enough.
        // So let's cancel and make sure nothing blows up.
        child_metric.cancel();

        // (They also shouldn't do anything,
        // but that's not something we can inspect in this test)
    }
}