[UniFFI] Migrate datetime metric type implementation

This commit is contained in:
Jan-Erik Rediger 2021-11-25 18:08:58 +01:00 коммит произвёл Jan-Erik Rediger
Родитель 807bd7c24a
Коммит a807650951
11 изменённых файлов: 307 добавлений и 327 удалений

Просмотреть файл

@ -13,6 +13,11 @@ package mozilla.telemetry.glean.private
*/
typealias CommonMetricData = mozilla.telemetry.glean.internal.CommonMetricData
/**
* Representation of a date, time and timezone.
*/
typealias Datetime = mozilla.telemetry.glean.internal.Datetime
/**
* Enumeration of the different kinds of histograms supported by metrics based on histograms.
*/

Просмотреть файл

@ -5,17 +5,10 @@
package mozilla.telemetry.glean.private
import androidx.annotation.VisibleForTesting
import com.sun.jna.StringArray
import java.util.Calendar
import java.util.Date
import java.util.concurrent.TimeUnit as AndroidTimeUnit
import mozilla.telemetry.glean.Dispatchers
import mozilla.telemetry.glean.rust.LibGleanFFI
import mozilla.telemetry.glean.rust.getAndConsumeRustString
import mozilla.telemetry.glean.rust.toBoolean
import mozilla.telemetry.glean.rust.toByte
import mozilla.telemetry.glean.testing.ErrorType
import mozilla.telemetry.glean.utils.parseISOTimeString
import mozilla.telemetry.glean.internal.DatetimeMetric
/**
* This implements the developer facing API for recording datetime metrics.
@ -23,73 +16,20 @@ import mozilla.telemetry.glean.utils.parseISOTimeString
* Instances of this class type are automatically generated by the parsers at build time,
* allowing developers to record values that were previously registered in the metrics.yaml file.
*/
class DatetimeMetricType internal constructor(
private var handle: Long,
private var disabled: Boolean,
private val sendInPings: List<String>
) {
/**
* The public constructor used by automatically generated metrics.
*/
constructor(
disabled: Boolean,
category: String,
lifetime: Lifetime,
name: String,
sendInPings: List<String>,
timeUnit: TimeUnit = TimeUnit.Minute
) : this(handle = 0, disabled = disabled, sendInPings = sendInPings) {
val ffiPingsList = StringArray(sendInPings.toTypedArray(), "utf-8")
this.handle = LibGleanFFI.INSTANCE.glean_new_datetime_metric(
category = category,
name = name,
send_in_pings = ffiPingsList,
send_in_pings_len = sendInPings.size,
lifetime = lifetime.ordinal,
disabled = disabled.toByte(),
time_unit = timeUnit.ordinal
)
}
class DatetimeMetricType(meta: CommonMetricData, timeUnit: TimeUnit = TimeUnit.MINUTE) {
val inner = DatetimeMetric(meta, timeUnit)
/**
* Set a datetime value, truncating it to the metric's resolution.
*
* @param value The [Date] value to set. If not provided, will record the current time.
*/
@JvmOverloads
fun set(value: Date = Date()) {
val cal = Calendar.getInstance()
cal.time = value
set(cal)
}
/**
* Explicitly set a value synchronously.
*
* This is only to be used for the glean-ac to glean-core data migration.
*
* @param cal The [Calendar] value to set.
*/
internal fun setSync(cal: Calendar) {
if (disabled) {
return
}
LibGleanFFI.INSTANCE.glean_datetime_set(
this@DatetimeMetricType.handle,
year = cal.get(Calendar.YEAR),
month = cal.get(Calendar.MONTH) + 1,
day = cal.get(Calendar.DAY_OF_MONTH),
hour = cal.get(Calendar.HOUR_OF_DAY),
minute = cal.get(Calendar.MINUTE),
second = cal.get(Calendar.SECOND),
nano = AndroidTimeUnit.MILLISECONDS.toNanos(cal.get(Calendar.MILLISECOND).toLong()),
offset_seconds = AndroidTimeUnit.MILLISECONDS.toSeconds(
cal.get(Calendar.ZONE_OFFSET).toLong() + cal.get(Calendar.DST_OFFSET)
).toInt()
)
}
/**
* Set a datetime value, truncating it to the metric's resolution.
*
@ -99,109 +39,48 @@ class DatetimeMetricType internal constructor(
*
* @param value The [Calendar] value to set. If not provided, will record the current time.
*/
internal fun set(value: Calendar) {
if (disabled) {
return
}
@Suppress("EXPERIMENTAL_API_USAGE")
Dispatchers.API.launch {
LibGleanFFI.INSTANCE.glean_datetime_set(
this@DatetimeMetricType.handle,
year = value.get(Calendar.YEAR),
month = value.get(Calendar.MONTH) + 1,
day = value.get(Calendar.DAY_OF_MONTH),
hour = value.get(Calendar.HOUR_OF_DAY),
minute = value.get(Calendar.MINUTE),
second = value.get(Calendar.SECOND),
nano = AndroidTimeUnit.MILLISECONDS.toNanos(value.get(Calendar.MILLISECOND).toLong()),
offset_seconds = AndroidTimeUnit.MILLISECONDS.toSeconds(
value.get(Calendar.ZONE_OFFSET).toLong() + value.get(Calendar.DST_OFFSET)
).toInt()
)
}
}
/**
* Tests whether a value is stored for the metric for testing purposes only. This function will
* attempt to await the last task (if any) writing to the the metric's storage engine before
* returning a value.
*
* @param pingName represents the name of the ping to retrieve the metric for.
* Defaults to the first value in `sendInPings`.
* @return true if metric value exists, otherwise false
*/
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
@JvmOverloads
fun testHasValue(pingName: String = sendInPings.first()): Boolean {
@Suppress("EXPERIMENTAL_API_USAGE")
Dispatchers.API.assertInTestingMode()
return LibGleanFFI
.INSTANCE.glean_datetime_test_has_value(this.handle, pingName)
.toBoolean()
}
/**
* Returns the string representation of the stored value for testing purposes only. This
* function will attempt to await the last task (if any) writing to the the metric's storage
* engine before returning a value.
*
* @param pingName represents the name of the ping to retrieve the metric for.
* Defaults to the first value in `sendInPings`.
* @return value of the stored metric
* @throws [NullPointerException] if no value is stored
*/
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
@JvmOverloads
fun testGetValueAsString(pingName: String = sendInPings.first()): String {
@Suppress("EXPERIMENTAL_API_USAGE")
Dispatchers.API.assertInTestingMode()
if (!testHasValue(pingName)) {
throw NullPointerException("Metric has no value")
}
val ptr = LibGleanFFI
.INSTANCE
.glean_datetime_test_get_value_as_string(this.handle, pingName)!!
return ptr.getAndConsumeRustString()
}
/**
* Returns the stored value for testing purposes only. This function will attempt to await the
* last task (if any) writing to the the metric's storage engine before returning a value.
*
* [Date] objects are always in the user's local timezone offset. If you
* care about checking that the timezone offset was set and sent correctly, use
* [testGetValueAsString] and inspect the offset.
*
* @param pingName represents the name of the ping to retrieve the metric for.
* Defaults to the first value in `sendInPings`.
* @return value of the stored metric
* @throws [NullPointerException] if no value is stored
*/
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
@JvmOverloads
fun testGetValue(pingName: String = sendInPings.first()): Date {
return parseISOTimeString(testGetValueAsString(pingName))!!
}
/**
* Returns the number of errors recorded for the given metric.
*
* @param errorType The type of the error recorded.
* @param pingName represents the name of the ping to retrieve the metric for.
* Defaults to the first value in `sendInPings`.
* @return the number of errors recorded for the metric.
*/
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
@JvmOverloads
fun testGetNumRecordedErrors(errorType: ErrorType, pingName: String = sendInPings.first()): Int {
@Suppress("EXPERIMENTAL_API_USAGE")
Dispatchers.API.assertInTestingMode()
return LibGleanFFI.INSTANCE.glean_datetime_test_get_num_recorded_errors(
this.handle, errorType.ordinal, pingName
internal fun set(cal: Calendar) {
val dt = Datetime(
year = cal.get(Calendar.YEAR),
month = (cal.get(Calendar.MONTH) + 1).toUInt(),
day = cal.get(Calendar.DAY_OF_MONTH).toUInt(),
hour = cal.get(Calendar.HOUR_OF_DAY).toUInt(),
minute = cal.get(Calendar.MINUTE).toUInt(),
second = cal.get(Calendar.SECOND).toUInt(),
nanosecond = AndroidTimeUnit.MILLISECONDS.toNanos(cal.get(Calendar.MILLISECOND).toLong()).toUInt(),
offsetSeconds = AndroidTimeUnit.MILLISECONDS.toSeconds(
cal.get(Calendar.ZONE_OFFSET).toLong() + cal.get(Calendar.DST_OFFSET)
).toInt()
)
inner.set(dt)
}
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
@JvmOverloads
fun testGetValue(pingName: String? = null): Date? {
return inner.testGetValue(pingName)?.let { dt ->
val cal = Calendar.getInstance()
cal.set(Calendar.ZONE_OFFSET, AndroidTimeUnit.SECONDS.toMillis(dt.offsetSeconds.toLong()).toInt())
cal.set(Calendar.YEAR, dt.year.toInt())
cal.set(Calendar.MONTH, dt.month.toInt()-1) // java.util.calendar's month is 0-based for months
cal.set(Calendar.DAY_OF_MONTH, dt.day.toInt())
cal.set(Calendar.HOUR_OF_DAY, dt.hour.toInt())
cal.set(Calendar.MINUTE, dt.minute.toInt())
cal.set(Calendar.SECOND, dt.second.toInt())
cal.set(Calendar.MILLISECOND, AndroidTimeUnit.NANOSECONDS.toMillis(dt.nanosecond.toLong()).toInt())
cal.getTime()
}
}
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
@JvmOverloads
fun testGetValueAsString(pingName: String? = null): String? {
return inner.testGetValueAsString(pingName)
}
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
@JvmOverloads
fun testHasValue(pingName: String? = null): Boolean {
return inner.testGetValue(pingName) != null
}
}

Просмотреть файл

@ -12,6 +12,7 @@ package mozilla.telemetry.glean.private
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.telemetry.glean.testing.ErrorType
import mozilla.telemetry.glean.testing.GleanTestRule
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@ -22,6 +23,7 @@ import org.junit.runner.RunWith
import java.util.Calendar
import java.util.Date
import java.util.TimeZone
import java.util.concurrent.TimeUnit as AndroidTimeUnit
const val MILLIS_PER_SEC = 1000L
private fun Date.asSeconds() = time / MILLIS_PER_SEC
@ -35,13 +37,13 @@ class DatetimeMetricTypeTest {
@Test
fun `The API saves to its storage engine`() {
// Define a 'datetimeMetric' datetime metric, which will be stored in "store1"
val datetimeMetric = DatetimeMetricType(
val datetimeMetric = DatetimeMetricType(CommonMetricData(
disabled = false,
category = "telemetry",
lifetime = Lifetime.Application,
lifetime = Lifetime.APPLICATION,
name = "datetime_metric",
sendInPings = listOf("store1")
)
))
val value = Calendar.getInstance()
value.set(2004, 11, 9, 8, 3, 29)
@ -68,27 +70,26 @@ class DatetimeMetricTypeTest {
assertEquals("1969-08-20T20:17-12:00", datetimeMetric.testGetValueAsString())
// A date following 2038 (the extent of signed 32-bits after UNIX epoch)
// This fails on some workers on Taskcluster. 32-bit platforms, perhaps?
// val value4 = Calendar.getInstance()
// value4.set(2039, 7, 20, 20, 17, 3)
// datetimeMetric.set(value4)
// // Check that data was properly recorded.
// assertTrue(datetimeMetric.testHasValue())
// assertEquals("2039-08-20T20:17:03-04:00", datetimeMetric.testGetValueAsString())
val value4 = Calendar.getInstance()
value4.set(2039, 7, 20, 20, 17, 3)
value4.timeZone = TimeZone.getTimeZone("GMT-4")
datetimeMetric.set(value4)
// Check that data was properly recorded.
assertTrue(datetimeMetric.testHasValue())
assertEquals("2039-08-20T20:17-04:00", datetimeMetric.testGetValueAsString())
}
@Test
fun `disabled datetimes must not record data`() {
// Define a 'datetimeMetric' datetime metric, which will be stored in "store1". It's disabled
// so it should not record anything.
val datetimeMetric = DatetimeMetricType(
val datetimeMetric = DatetimeMetricType(CommonMetricData(
disabled = true,
category = "telemetry",
lifetime = Lifetime.Application,
lifetime = Lifetime.APPLICATION,
name = "datetimeMetric",
sendInPings = listOf("store1")
)
))
// Attempt to store the datetime.
datetimeMetric.set()
@ -100,14 +101,13 @@ class DatetimeMetricTypeTest {
// This test is adopted from `SyncTelemetryTest.kt` in android-components.
// Previously we failed to properly deal with DST when converting from `Calendar` into its pieces.
val datetimeMetric = DatetimeMetricType(
val datetimeMetric = DatetimeMetricType(CommonMetricData(
disabled = false,
category = "telemetry",
lifetime = Lifetime.Ping,
lifetime = Lifetime.PING,
name = "datetimeMetric",
sendInPings = listOf("store1"),
timeUnit = TimeUnit.Millisecond
)
), timeUnit = TimeUnit.MILLISECOND)
val nowDate = Date()
val now = nowDate.asSeconds()
@ -115,6 +115,6 @@ class DatetimeMetricTypeTest {
datetimeMetric.set(timestamp)
assertEquals(now, datetimeMetric.testGetValue().asSeconds())
assertEquals(now, datetimeMetric.testGetValue()!!.asSeconds())
}
}

Просмотреть файл

@ -309,8 +309,8 @@ impl Glean {
.get_value(self, "glean_client_info")
.is_none()
{
self.core_metrics.first_run_date.set(self, None);
self.core_metrics.first_run_hour.set(self, None);
self.core_metrics.first_run_date.set_sync(self, None);
self.core_metrics.first_run_hour.set_sync(self, None);
// The `first_run_date` field is generated on the very first run
// and persisted across upload toggling. We can assume that, the only
// time it is set, that's indeed our "first run".
@ -483,14 +483,14 @@ impl Glean {
if let Some(existing_first_run_date) = existing_first_run_date {
self.core_metrics
.first_run_date
.set(self, Some(existing_first_run_date));
.set_sync(self, Some(existing_first_run_date));
}
// Restore the first_run_hour.
if let Some(existing_first_run_hour) = existing_first_run_hour {
self.core_metrics
.first_run_hour
.set(self, Some(existing_first_run_hour));
.set_sync(self, Some(existing_first_run_hour));
}
self.upload_enabled = false;

Просмотреть файл

@ -337,3 +337,27 @@ interface CustomDistributionMetric {
i32 test_get_num_recorded_errors(ErrorType error, optional string? ping_name = null);
};
// Representation of a date, time and timezone.
dictionary Datetime {
i32 year;
u32 month;
u32 day;
u32 hour;
u32 minute;
u32 second;
u32 nanosecond;
i32 offset_seconds;
};
interface DatetimeMetric {
constructor(CommonMetricData meta, TimeUnit time_unit);
void set(optional Datetime? value = null);
Datetime? test_get_value(optional string? ping_name = null);
string? test_get_value_as_string(optional string? ping_name = null);
i32 test_get_num_recorded_errors(ErrorType error, optional string? ping_name = null);
};

Просмотреть файл

@ -51,10 +51,10 @@ pub use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
use crate::histogram::HistogramType;
pub use crate::metrics::labeled::{LabeledBoolean, LabeledCounter, LabeledString};
pub use crate::metrics::{
BooleanMetric, CounterMetric, DistributionData, MemoryDistributionMetric, MemoryUnit, PingType,
QuantityMetric, RecordedExperiment, StringListMetric, StringMetric, TimeUnit, TimespanMetric,
UrlMetric, UuidMetric,
CustomDistributionMetric,
BooleanMetric, CounterMetric, CustomDistributionMetric, Datetime, DatetimeMetric,
DistributionData, MemoryDistributionMetric, MemoryUnit, PingType, QuantityMetric,
RecordedExperiment, StringListMetric, StringMetric, TimeUnit, TimespanMetric, UrlMetric,
UuidMetric,
};
pub use crate::upload::{PingRequest, PingUploadTask, UploadResult};

Просмотреть файл

@ -191,12 +191,12 @@ fn client_id_and_first_run_date_and_first_run_hour_must_be_regenerated() {
assert!(glean
.core_metrics
.first_run_date
.test_get_value_as_string(&glean, "glean_client_info")
.get_value(&glean, "glean_client_info")
.is_none());
assert!(glean
.core_metrics
.first_run_hour
.test_get_value_as_string(&glean, "metrics")
.get_value(&glean, "metrics")
.is_none());
}
@ -210,12 +210,12 @@ fn client_id_and_first_run_date_and_first_run_hour_must_be_regenerated() {
assert!(glean
.core_metrics
.first_run_date
.test_get_value_as_string(&glean, "glean_client_info")
.get_value(&glean, "glean_client_info")
.is_some());
assert!(glean
.core_metrics
.first_run_hour
.test_get_value_as_string(&glean, "metrics")
.get_value(&glean, "metrics")
.is_some());
}
}

Просмотреть файл

@ -2,9 +2,9 @@
// 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/.
#![allow(clippy::too_many_arguments)]
use std::sync::Arc;
use crate::error_recording::{record_error, ErrorType};
use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType};
use crate::metrics::time_unit::TimeUnit;
use crate::metrics::Metric;
use crate::metrics::MetricType;
@ -13,20 +13,41 @@ use crate::util::{get_iso_time_string, local_now_with_offset_and_record};
use crate::CommonMetricData;
use crate::Glean;
use chrono::{DateTime, FixedOffset, TimeZone, Timelike};
use chrono::{DateTime, Datelike, FixedOffset, TimeZone, Timelike};
/// A datetime type.
///
/// Used to feed data to the `DatetimeMetric`.
pub type Datetime = DateTime<FixedOffset>;
pub type ChronoDatetime = DateTime<FixedOffset>;
/// Representation of a date, time and timezone.
pub struct Datetime {
/// The year, e.g. 2021.
pub year: i32,
/// The month, 1=January.
pub month: u32,
/// The day of the month.
pub day: u32,
/// The hour. 0-23
pub hour: u32,
/// The minute. 0-59.
pub minute: u32,
/// The second. 0-60.
pub second: u32,
/// The nanosecond part of the time.
pub nanosecond: u32,
/// The timezone offset from UTC in seconds.
/// Negative for west, positive for east of UTC.
pub offset_seconds: i32,
}
/// A datetime metric.
///
/// Used to record an absolute date and time, such as the time the user first ran
/// the application.
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct DatetimeMetric {
meta: CommonMetricData,
meta: Arc<CommonMetricData>,
time_unit: TimeUnit,
}
@ -36,6 +57,24 @@ impl MetricType for DatetimeMetric {
}
}
impl From<ChronoDatetime> for Datetime {
fn from(dt: ChronoDatetime) -> Self {
let date = dt.date();
let time = dt.time();
let tz = dt.timezone();
Self {
year: date.year(),
month: date.month(),
day: date.day(),
hour: time.hour(),
minute: time.minute(),
second: time.second(),
nanosecond: time.nanosecond(),
offset_seconds: tz.local_minus_utc(),
}
}
}
// IMPORTANT:
//
// When changing this implementation, make sure all the operations are
@ -43,72 +82,63 @@ impl MetricType for DatetimeMetric {
impl DatetimeMetric {
/// Creates a new datetime metric.
pub fn new(meta: CommonMetricData, time_unit: TimeUnit) -> Self {
Self { meta, time_unit }
Self {
meta: Arc::new(meta),
time_unit,
}
}
/// Sets the metric to a date/time including the timezone offset.
///
/// # Arguments
///
/// * `glean` - the Glean instance this metric belongs to.
/// * `year` - the year to set the metric to.
/// * `month` - the month to set the metric to (1-12).
/// * `day` - the day to set the metric to (1-based).
/// * `hour` - the hour to set the metric to.
/// * `minute` - the minute to set the metric to.
/// * `second` - the second to set the metric to.
/// * `nano` - the nanosecond fraction to the last whole second.
/// * `offset_seconds` - the timezone difference, in seconds, for the Eastern
/// Hemisphere. Negative seconds mean Western Hemisphere.
pub fn set_with_details(
&self,
glean: &Glean,
year: i32,
month: u32,
day: u32,
hour: u32,
minute: u32,
second: u32,
nano: u32,
offset_seconds: i32,
) {
if !self.should_record(glean) {
return;
}
/// * `dt` - the optinal datetime to set this to. If missing the current date is used.
pub fn set(&self, dt: Option<Datetime>) {
let metric = self.clone();
crate::launch_with_glean(move |glean| {
if !metric.should_record(glean) {
return;
}
let timezone_offset = FixedOffset::east_opt(offset_seconds);
if timezone_offset.is_none() {
let msg = format!("Invalid timezone offset {}. Not recording.", offset_seconds);
record_error(glean, &self.meta, ErrorType::InvalidValue, msg, None);
return;
};
if dt.is_none() {
return metric.set_sync(glean, None);
}
let datetime_obj = FixedOffset::east(offset_seconds)
.ymd_opt(year, month, day)
.and_hms_nano_opt(hour, minute, second, nano);
let dt = dt.unwrap();
match datetime_obj.single() {
Some(d) => self.set(glean, Some(d)),
_ => {
let timezone_offset = FixedOffset::east_opt(dt.offset_seconds);
if timezone_offset.is_none() {
let msg = format!(
"Invalid timezone offset {}. Not recording.",
dt.offset_seconds
);
record_error(glean, &metric.meta, ErrorType::InvalidValue, msg, None);
return;
};
let datetime_obj = FixedOffset::east(dt.offset_seconds)
.ymd_opt(dt.year, dt.month, dt.day)
.and_hms_nano_opt(dt.hour, dt.minute, dt.second, dt.nanosecond);
if let Some(dt) = datetime_obj.single() {
metric.set_sync(glean, Some(dt))
} else {
record_error(
glean,
&self.meta,
&metric.meta,
ErrorType::InvalidValue,
"Invalid input data. Not recording.",
None,
);
}
}
})
}
/// Sets the metric to a date/time which including the timezone offset.
/// Sets the metric to a date/time which including the timezone offset synchronously.
///
/// # Arguments
///
/// * `glean` - the Glean instance this metric belongs to.
/// * `value` - Some [`DateTime`] value, with offset, to set the metric to.
/// If none, the current local time is used.
pub fn set(&self, glean: &Glean, value: Option<Datetime>) {
/// Use [`set`](Self::set) instead.
#[doc(hidden)]
pub fn set_sync(&self, glean: &Glean, value: Option<ChronoDatetime>) {
if !self.should_record(glean) {
return;
}
@ -119,23 +149,69 @@ impl DatetimeMetric {
}
/// Gets the stored datetime value.
///
/// # Arguments
///
/// * `glean` - the Glean instance this metric belongs to.
/// * `storage_name` - the storage name to look into.
///
/// # Returns
///
/// The stored value or `None` if nothing stored.
pub(crate) fn get_value(&self, glean: &Glean, storage_name: &str) -> Option<Datetime> {
#[doc(hidden)]
pub fn get_value<'a, S: Into<Option<&'a str>>>(
&self,
glean: &Glean,
ping_name: S,
) -> Option<ChronoDatetime> {
let (d, tu) = self.get_value_inner(glean, ping_name.into())?;
// The string version of the test function truncates using string
// parsing. Unfortunately `parse_from_str` errors with `NotEnough` if we
// try to truncate with `get_iso_time_string` and then parse it back
// in a `Datetime`. So we need to truncate manually.
let time = d.time();
match tu {
TimeUnit::Nanosecond => d.date().and_hms_nano_opt(
time.hour(),
time.minute(),
time.second(),
time.nanosecond(),
),
TimeUnit::Microsecond => {
eprintln!(
"microseconds. nanoseconds={}, nanoseconds/1000={}",
time.nanosecond(),
time.nanosecond() / 1000
);
d.date().and_hms_nano_opt(
time.hour(),
time.minute(),
time.second(),
time.nanosecond() / 1000,
)
}
TimeUnit::Millisecond => d.date().and_hms_nano_opt(
time.hour(),
time.minute(),
time.second(),
time.nanosecond() / 1000000,
),
TimeUnit::Second => {
d.date()
.and_hms_nano_opt(time.hour(), time.minute(), time.second(), 0)
}
TimeUnit::Minute => d.date().and_hms_nano_opt(time.hour(), time.minute(), 0, 0),
TimeUnit::Hour => d.date().and_hms_nano_opt(time.hour(), 0, 0, 0),
TimeUnit::Day => d.date().and_hms_nano_opt(0, 0, 0, 0),
}
}
fn get_value_inner(
&self,
glean: &Glean,
ping_name: Option<&str>,
) -> Option<(ChronoDatetime, TimeUnit)> {
let queried_ping_name = ping_name.unwrap_or_else(|| &self.meta().send_in_pings[0]);
match StorageManager.snapshot_metric(
glean.storage(),
storage_name,
queried_ping_name,
&self.meta.identifier(glean),
self.meta.lifetime,
) {
Some(Metric::Datetime(dt, _)) => Some(dt),
Some(Metric::Datetime(d, tu)) => Some((d, tu)),
_ => None,
}
}
@ -154,67 +230,61 @@ impl DatetimeMetric {
/// # Returns
///
/// The stored value or `None` if nothing stored.
pub fn test_get_value(&self, glean: &Glean, storage_name: &str) -> Option<Datetime> {
match StorageManager.snapshot_metric_for_test(
glean.storage(),
storage_name,
&self.meta.identifier(glean),
self.meta.lifetime,
) {
Some(Metric::Datetime(d, tu)) => {
// The string version of the test function truncates using string
// parsing. Unfortunately `parse_from_str` errors with `NotEnough` if we
// try to truncate with `get_iso_time_string` and then parse it back
// in a `Datetime`. So we need to truncate manually.
let time = d.time();
match tu {
TimeUnit::Nanosecond => d.date().and_hms_nano_opt(
time.hour(),
time.minute(),
time.second(),
time.nanosecond(),
),
TimeUnit::Microsecond => d.date().and_hms_nano_opt(
time.hour(),
time.minute(),
time.second(),
time.nanosecond() / 1000,
),
TimeUnit::Millisecond => d.date().and_hms_nano_opt(
time.hour(),
time.minute(),
time.second(),
time.nanosecond() / 1000000,
),
TimeUnit::Second => {
d.date()
.and_hms_nano_opt(time.hour(), time.minute(), time.second(), 0)
}
TimeUnit::Minute => d.date().and_hms_nano_opt(time.hour(), time.minute(), 0, 0),
TimeUnit::Hour => d.date().and_hms_nano_opt(time.hour(), 0, 0, 0),
TimeUnit::Day => d.date().and_hms_nano_opt(0, 0, 0, 0),
}
}
_ => None,
}
pub fn test_get_value(&self, ping_name: Option<String>) -> Option<Datetime> {
crate::block_on_dispatcher();
crate::core::with_glean(|glean| {
let dt = self.get_value(glean, ping_name.as_deref());
dt.map(Datetime::from)
})
}
/// **Test-only API (exported for FFI purposes).**
///
/// Gets the currently stored value as a String.
/// Gets the stored datetime value, formatted as an ISO8601 string.
///
/// The precision of this value is truncated to the `time_unit` precision.
///
/// This doesn't clear the stored value.
pub fn test_get_value_as_string(&self, glean: &Glean, storage_name: &str) -> Option<String> {
match StorageManager.snapshot_metric_for_test(
glean.storage(),
storage_name,
&self.meta.identifier(glean),
self.meta.lifetime,
) {
Some(Metric::Datetime(d, tu)) => Some(get_iso_time_string(d, tu)),
_ => None,
}
/// # Arguments
///
/// * `glean` - the Glean instance this metric belongs to.
/// * `storage_name` - the storage name to look into.
///
/// # Returns
///
/// The stored value or `None` if nothing stored.
pub fn test_get_value_as_string(&self, ping_name: Option<String>) -> Option<String> {
crate::block_on_dispatcher();
crate::core::with_glean(|glean| self.get_value_as_string(glean, ping_name))
}
/// **Test-only API**
///
/// Gets the stored datetime value, formatted as an ISO8601 string.
#[doc(hidden)]
pub fn get_value_as_string(&self, glean: &Glean, ping_name: Option<String>) -> Option<String> {
let value = self.get_value_inner(glean, ping_name.as_deref());
value.map(|(dt, tu)| get_iso_time_string(dt, tu))
}
/// **Exported for test purposes.**
///
/// Gets the number of recorded errors for the given metric and error type.
///
/// # Arguments
///
/// * `error` - The type of error
/// * `ping_name` - represents the optional name of the ping to retrieve the
/// metric for. Defaults to the first value in `send_in_pings`.
///
/// # Returns
///
/// The number of errors reported.
pub fn test_get_num_recorded_errors(&self, error: ErrorType, ping_name: Option<String>) -> i32 {
crate::block_on_dispatcher();
crate::core::with_glean(|glean| {
test_get_num_recorded_errors(glean, self.meta(), error, ping_name.as_deref())
.unwrap_or(0)
})
}
}

Просмотреть файл

@ -113,7 +113,7 @@ impl PingMaker {
let end_time_data = local_now_with_offset_and_record(glean);
// Update the start time with the current time.
start_time.set(glean, Some(end_time_data));
start_time.set_sync(glean, Some(end_time_data));
// Format the times.
let start_time_data = get_iso_time_string(start_time_data, time_unit);

Просмотреть файл

@ -54,7 +54,7 @@ impl MetricsPingSubmitter for GleanMetricsPingSubmitter {
fn submit_metrics_ping(&self, glean: &Glean, reason: Option<&str>, now: DateTime<FixedOffset>) {
glean.submit_ping_by_name("metrics", reason);
// Always update the collection date, irrespective of the ping being sent.
get_last_sent_time_metric().set(glean, Some(now));
get_last_sent_time_metric().set_sync(glean, Some(now));
}
}
@ -351,7 +351,7 @@ mod test {
let (glean, _t) = new_glean(None);
let fake_now = FixedOffset::east(0).ymd(2021, 4, 30).and_hms(14, 36, 14);
get_last_sent_time_metric().set(&glean, Some(fake_now));
get_last_sent_time_metric().set_sync(&glean, Some(fake_now));
let (submitter, submitter_count, scheduler, scheduler_count) = new_proxies(
|_, reason| panic!("Case #1 shouldn't submit a ping! reason: {:?}", reason),
@ -372,7 +372,7 @@ mod test {
let fake_yesterday = FixedOffset::east(0)
.ymd(2021, 4, 29)
.and_hms(SCHEDULED_HOUR, 0, 1);
get_last_sent_time_metric().set(&glean, Some(fake_yesterday));
get_last_sent_time_metric().set_sync(&glean, Some(fake_yesterday));
let fake_now = fake_yesterday + Duration::days(1);
let (submitter, submitter_count, scheduler, scheduler_count) = new_proxies(
@ -395,7 +395,7 @@ mod test {
FixedOffset::east(0)
.ymd(2021, 4, 29)
.and_hms(SCHEDULED_HOUR - 1, 0, 1);
get_last_sent_time_metric().set(&glean, Some(fake_yesterday));
get_last_sent_time_metric().set_sync(&glean, Some(fake_yesterday));
let fake_now = fake_yesterday + Duration::days(1);
let (submitter, submitter_count, scheduler, scheduler_count) = new_proxies(

Просмотреть файл

@ -42,7 +42,7 @@ fn datetime_serializer_should_correctly_serialize_datetime() {
let dt = FixedOffset::east(0)
.ymd(1983, 4, 13)
.and_hms_milli(12, 9, 14, 274);
metric.set(&glean, Some(dt));
metric.set_sync(&glean, Some(dt));
let snapshot = StorageManager
.snapshot_as_json(glean.storage(), "store1", true)
@ -88,13 +88,13 @@ fn set_value_properly_sets_the_value_in_all_stores() {
let dt = FixedOffset::east(0)
.ymd(1983, 4, 13)
.and_hms_nano(12, 9, 14, 1_560_274);
metric.set(&glean, Some(dt));
metric.set_sync(&glean, Some(dt));
for store_name in store_names {
assert_eq!(
"1983-04-13T12:09:14.001560274+00:00",
metric
.test_get_value_as_string(&glean, &store_name)
.get_value_as_string(&glean, Some(store_name))
.unwrap()
);
}
@ -175,11 +175,13 @@ fn test_that_truncation_works() {
},
t.desired_resolution,
);
metric.set(&glean, Some(high_res_datetime));
metric.set_sync(&glean, Some(high_res_datetime));
assert_eq!(
t.expected_result,
metric.test_get_value_as_string(&glean, store_name).unwrap()
metric
.get_value_as_string(&glean, Some(store_name.into()))
.unwrap()
);
}
}