blob: 07091208a98c831acf6e8d3a0b6cdca3096d7a54 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::update::config::Initiator,
cobalt_client::traits::AsEventCode,
cobalt_sw_delivery_registry as metrics,
futures::future::Future,
std::{
convert::TryInto,
time::{Duration, Instant, SystemTime},
},
};
// See $FUCHSIA_OUT_DIR/gen/src/sys/pkg/bin/amber/cobalt_sw_delivery_registry.rs for more info.
pub use {
metrics::SoftwareDeliveryMetricDimensionPhase as Phase,
metrics::SoftwareDeliveryMetricDimensionStatusCode as StatusCode,
};
pub fn connect_to_cobalt() -> (Client, impl Future<Output = ()>) {
let (cobalt, cobalt_fut) = fuchsia_cobalt::CobaltConnector::default()
.serve(fuchsia_cobalt::ConnectionType::project_id(metrics::PROJECT_ID));
(Client(cobalt), cobalt_fut)
}
pub fn result_to_status_code(res: Result<(), &anyhow::Error>) -> StatusCode {
use fuchsia_zircon::Status;
match res {
Ok(()) => StatusCode::Success,
Err(e) => {
if let Some(crate::update::ResolveError::Status(status)) = e.downcast_ref() {
match *status {
Status::IO => StatusCode::ErrorStorage,
Status::NO_SPACE => StatusCode::ErrorStorageOutOfSpace,
Status::ADDRESS_UNREACHABLE => StatusCode::ErrorUntrustedTufRepo,
Status::UNAVAILABLE => StatusCode::ErrorNetworking,
_ => StatusCode::Error,
}
} else {
// Fallback to a generic catch-all error status code when the error didn't contain
// context indicating more clearly what type of error happened.
StatusCode::Error
}
}
}
}
impl AsEventCode for Initiator {
fn as_event_code(&self) -> u32 {
match self {
Initiator::Automatic => {
metrics::SoftwareDeliveryMetricDimensionInitiator::AutomaticUpdateCheck
}
Initiator::Manual => {
metrics::SoftwareDeliveryMetricDimensionInitiator::UserInitiatedCheck
}
}
.as_event_code()
}
}
fn hour_of_day(when: SystemTime) -> u32 {
use chrono::Timelike;
let when = chrono::DateTime::<chrono::Utc>::from(when);
// TODO insert UTC to local time conversion here...when that is possible to do.
when.hour()
}
fn duration_to_micros(duration: Duration) -> i64 {
duration.as_micros().try_into().unwrap_or(i64::MAX)
}
/// Attempts to determine the monotonic instant that correlates with the given wall time by
/// subtracting the duration from the given time to now (wall time) from now (monotonic time). If
/// `time` is in the future or it corresponds with an invalid monotonic time, returns None.
///
/// This conversion is flawed as there is no guarantee that the monotonic and wall clocks tick at
/// the same rate or that the system was up at the given wall time.
///
/// FIXME: switch start time to initially be based on monotonic time to remove the need for this
/// conversion.
pub fn system_time_to_monotonic_time(time: SystemTime) -> Option<Instant> {
Instant::now().checked_sub(time.elapsed().ok()?)
}
#[derive(Debug, Clone)]
pub struct Client(fuchsia_cobalt::CobaltSender);
impl Client {
pub fn log_ota_start(&mut self, target_version: &str, initiator: Initiator, when: SystemTime) {
self.0.with_component().log_event_count(
metrics::OTA_START_METRIC_ID,
(initiator, hour_of_day(when)),
target_version,
0, // duration
1, // count
);
}
pub fn log_ota_result_attempt(
&mut self,
target_version: &str,
initiator: Initiator,
attempts: u32,
phase: Phase,
status: StatusCode,
) {
self.0.with_component().log_event_count(
metrics::OTA_RESULT_ATTEMPTS_METRIC_ID,
(initiator, phase, status),
target_version,
0,
attempts.into(), // "attempts" is not "count", but that's what the go impl does.
);
}
pub fn log_ota_result_duration(
&mut self,
target_version: &str,
initiator: Initiator,
phase: Phase,
status: StatusCode,
duration: Duration,
) {
self.0.with_component().log_elapsed_time(
metrics::OTA_RESULT_DURATION_METRIC_ID,
(initiator, phase, status),
target_version,
duration_to_micros(duration),
);
}
}
#[cfg(test)]
mod tests {
use {super::*, chrono::prelude::*};
#[test]
fn hour_of_day_returns_hour_of_day() {
let date = Utc.ymd(1971, 1, 1);
assert_eq!(hour_of_day(date.and_hms(4, 0, 0).into()), 4);
assert_eq!(hour_of_day(date.and_hms(23, 0, 0).into()), 23);
}
}