1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4 
5 use std::time::Duration;
6 
7 use crate::error_recording::{record_error, ErrorType};
8 use crate::metrics::time_unit::TimeUnit;
9 use crate::metrics::Metric;
10 use crate::metrics::MetricType;
11 use crate::storage::StorageManager;
12 use crate::CommonMetricData;
13 use crate::Glean;
14 
15 /// A timespan metric.
16 ///
17 /// Timespans are used to make a measurement of how much time is spent in a particular task.
18 #[derive(Debug)]
19 pub struct TimespanMetric {
20     meta: CommonMetricData,
21     time_unit: TimeUnit,
22     start_time: Option<u64>,
23 }
24 
25 impl MetricType for TimespanMetric {
meta(&self) -> &CommonMetricData26     fn meta(&self) -> &CommonMetricData {
27         &self.meta
28     }
29 
meta_mut(&mut self) -> &mut CommonMetricData30     fn meta_mut(&mut self) -> &mut CommonMetricData {
31         &mut self.meta
32     }
33 }
34 
35 // IMPORTANT:
36 //
37 // When changing this implementation, make sure all the operations are
38 // also declared in the related trait in `../traits/`.
39 impl TimespanMetric {
40     /// Creates a new timespan metric.
new(meta: CommonMetricData, time_unit: TimeUnit) -> Self41     pub fn new(meta: CommonMetricData, time_unit: TimeUnit) -> Self {
42         Self {
43             meta,
44             time_unit,
45             start_time: None,
46         }
47     }
48 
49     /// Starts tracking time for the provided metric.
50     ///
51     /// This records an error if it's already tracking time (i.e. start was
52     /// already called with no corresponding
53     /// [`set_stop`](TimespanMetric::set_stop)): in that case the original start
54     /// time will be preserved.
set_start(&mut self, glean: &Glean, start_time: u64)55     pub fn set_start(&mut self, glean: &Glean, start_time: u64) {
56         if !self.should_record(glean) {
57             return;
58         }
59 
60         if self.start_time.is_some() {
61             record_error(
62                 glean,
63                 &self.meta,
64                 ErrorType::InvalidState,
65                 "Timespan already started",
66                 None,
67             );
68             return;
69         }
70 
71         self.start_time = Some(start_time);
72     }
73 
74     /// Stops tracking time for the provided metric. Sets the metric to the elapsed time.
75     ///
76     /// This will record an error if no [`set_start`](TimespanMetric::set_start) was called.
set_stop(&mut self, glean: &Glean, stop_time: u64)77     pub fn set_stop(&mut self, glean: &Glean, stop_time: u64) {
78         if !self.should_record(glean) {
79             // Reset timer when disabled, so that we don't record timespans across
80             // disabled/enabled toggling.
81             self.start_time = None;
82             return;
83         }
84 
85         if self.start_time.is_none() {
86             record_error(
87                 glean,
88                 &self.meta,
89                 ErrorType::InvalidState,
90                 "Timespan not running",
91                 None,
92             );
93             return;
94         }
95 
96         let start_time = self.start_time.take().unwrap();
97         let duration = match stop_time.checked_sub(start_time) {
98             Some(duration) => duration,
99             None => {
100                 record_error(
101                     glean,
102                     &self.meta,
103                     ErrorType::InvalidValue,
104                     "Timespan was negative",
105                     None,
106                 );
107                 return;
108             }
109         };
110         let duration = Duration::from_nanos(duration);
111         self.set_raw(glean, duration);
112     }
113 
114     /// Aborts a previous [`set_start`](TimespanMetric::set_start) call. No
115     /// error is recorded if no [`set_start`](TimespanMetric::set_start) was
116     /// called.
cancel(&mut self)117     pub fn cancel(&mut self) {
118         self.start_time = None;
119     }
120 
121     /// Explicitly sets the timespan value.
122     ///
123     /// This API should only be used if your library or application requires
124     /// recording times in a way that can not make use of
125     /// [`set_start`](TimespanMetric::set_start)/[`set_stop`](TimespanMetric::set_stop)/[`cancel`](TimespanMetric::cancel).
126     ///
127     /// Care should be taken using this if the ping lifetime might contain more
128     /// than one timespan measurement. To be safe,
129     /// [`set_raw`](TimespanMetric::set_raw) should generally be followed by
130     /// sending a custom ping containing the timespan.
131     ///
132     /// # Arguments
133     ///
134     /// * `elapsed` - The elapsed time to record.
set_raw(&self, glean: &Glean, elapsed: Duration)135     pub fn set_raw(&self, glean: &Glean, elapsed: Duration) {
136         if !self.should_record(glean) {
137             return;
138         }
139 
140         if self.start_time.is_some() {
141             record_error(
142                 glean,
143                 &self.meta,
144                 ErrorType::InvalidState,
145                 "Timespan already running. Raw value not recorded.",
146                 None,
147             );
148             return;
149         }
150 
151         let mut report_value_exists: bool = false;
152         glean.storage().record_with(glean, &self.meta, |old_value| {
153             match old_value {
154                 Some(old @ Metric::Timespan(..)) => {
155                     // If some value already exists, report an error.
156                     // We do this out of the storage since recording an
157                     // error accesses the storage as well.
158                     report_value_exists = true;
159                     old
160                 }
161                 _ => Metric::Timespan(elapsed, self.time_unit),
162             }
163         });
164 
165         if report_value_exists {
166             record_error(
167                 glean,
168                 &self.meta,
169                 ErrorType::InvalidState,
170                 "Timespan value already recorded. New value discarded.",
171                 None,
172             );
173         };
174     }
175 
176     /// **Test-only API (exported for FFI purposes).**
177     ///
178     /// Gets the currently stored value as an integer.
179     ///
180     /// This doesn't clear the stored value.
test_get_value(&self, glean: &Glean, storage_name: &str) -> Option<u64>181     pub fn test_get_value(&self, glean: &Glean, storage_name: &str) -> Option<u64> {
182         match StorageManager.snapshot_metric_for_test(
183             glean.storage(),
184             storage_name,
185             &self.meta.identifier(glean),
186             self.meta.lifetime,
187         ) {
188             Some(Metric::Timespan(time, time_unit)) => Some(time_unit.duration_convert(time)),
189             _ => None,
190         }
191     }
192 }
193