// 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 std::time::Duration;
use crate::error_recording::{record_error, ErrorType};
use crate::metrics::time_unit::TimeUnit;
use crate::metrics::Metric;
use crate::metrics::MetricType;
use crate::storage::StorageManager;
use crate::CommonMetricData;
use crate::Glean;
/// A timespan metric.
///
/// Timespans are used to make a measurement of how much time is spent in a particular task.
#[derive(Debug)]
pub struct TimespanMetric {
meta: CommonMetricData,
time_unit: TimeUnit,
start_time: Option<u64>,
}
impl MetricType for TimespanMetric {
fn meta(&self) -> &CommonMetricData {
&self.meta
}
fn meta_mut(&mut self) -> &mut CommonMetricData {
&mut self.meta
}
}
impl TimespanMetric {
/// Create a new timespan metric.
pub fn new(meta: CommonMetricData, time_unit: TimeUnit) -> Self {
Self {
meta,
time_unit,
start_time: None,
}
}
/// Start tracking time for the provided metric.
///
/// This records an error if it's already tracking time (i.e. start was already
/// called with no corresponding `stop`): in that case the original
/// start time will be preserved.
pub fn set_start(&mut self, glean: &Glean, start_time: u64) {
if !self.should_record(glean) {
return;
}
if self.start_time.is_some() {
record_error(
glean,
&self.meta,
ErrorType::InvalidState,
"Timespan already started",
None,
);
return;
}
self.start_time = Some(start_time);
}
/// Stop tracking time for the provided metric. Sets the metric to the elapsed time.
///
/// This will record an error if no `start` was called.
pub fn set_stop(&mut self, glean: &Glean, stop_time: u64) {
if !self.should_record(glean) {
return;
}
if self.start_time.is_none() {
record_error(
glean,
&self.meta,
ErrorType::InvalidState,
"Timespan not running",
None,
);
return;
}
let duration = stop_time - self.start_time.take().unwrap();
let duration = Duration::from_nanos(duration);
self.set_raw(glean, duration, false);
}
/// Abort a previous `start` call. No error is recorded if no `start` was called.
pub fn cancel(&mut self) {
self.start_time = None;
}
/// Explicitly set the timespan value.
///
/// This API should only be used if your library or application requires recording
/// times in a way that can not make use of `start`/`stop`/`cancel`.
///
/// Care should be taken using this if the ping lifetime might contain more than one
/// timespan measurement. To be safe, `set_raw` should generally be followed by
/// sending a custom ping containing the timespan.
///
/// ## Arguments
///
/// * `elapsed` - The elapsed time to record.
/// * `overwrite` - Whether or not to overwrite existing data.
pub fn set_raw(&self, glean: &Glean, elapsed: Duration, overwrite: bool) {
if !self.should_record(glean) {
return;
}
if self.start_time.is_some() {
record_error(
glean,
&self.meta,
ErrorType::InvalidState,
"Timespan already running. Raw value not recorded.",
None,
);
return;
}
let mut report_value_exists: bool = false;
glean.storage().record_with(glean, &self.meta, |old_value| {
if overwrite {
Metric::Timespan(elapsed, self.time_unit)
} else {
match old_value {
Some(old @ Metric::Timespan(..)) => {
// If some value already exists, report an error.
// We do this out of the storage since recording an
// error accesses the storage as well.
report_value_exists = true;
old
}
_ => Metric::Timespan(elapsed, self.time_unit),
}
}
});
if report_value_exists {
record_error(
glean,
&self.meta,
ErrorType::InvalidState,
"Timespan value already recorded. New value discarded.",
None,
);
};
}
/// **Test-only API (exported for FFI purposes).**
///
/// Get the currently stored value as an integer.
///
/// This doesn't clear the stored value.
pub fn test_get_value(&self, glean: &Glean, storage_name: &str) -> Option<u64> {
match StorageManager.snapshot_metric(
glean.storage(),
storage_name,
&self.meta.identifier(glean),
) {
Some(Metric::Timespan(time, time_unit)) => Some(time_unit.duration_convert(time)),
_ => None,
}
}
}