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