blob: 40d62ac314dff775d47d10942fd5277a26930fef [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::inspect::BoundedNode;
use fuchsia_inspect::Node;
use fuchsia_zircon::{ClockId::Monotonic, Time};
#[derive(Debug)]
pub enum Event<'a> {
CheckingForUpdates,
ErrorCheckingForUpdate,
NoUpdateAvailable,
InstallationDeferredByPolicy { target_version: Option<&'a str> },
InstallingUpdate { target_version: Option<&'a str> },
InstallationError { target_version: Option<&'a str> },
WaitingForReboot { target_version: Option<&'a str> },
}
impl Event<'_> {
fn write_to_inspect(&self, node: &Node, session_id: u64) {
node.record_int("ts", Time::get(Monotonic).into_nanos());
node.record_uint("session-id", session_id);
match self {
Self::CheckingForUpdates => {
node.record_string("event", "CheckingForUpdates");
}
Self::ErrorCheckingForUpdate => {
node.record_string("event", "ErrorCheckingForUpdate");
}
Self::NoUpdateAvailable => {
node.record_string("event", "NoUpdateAvailable");
}
Self::InstallationDeferredByPolicy { target_version } => {
node.record_string("event", "InstallationDeferredByPolicy");
node.record_string("target-version", target_version.unwrap_or(""));
}
Self::InstallingUpdate { target_version } => {
node.record_string("event", "InstallingUpdate");
node.record_string("target-version", target_version.unwrap_or(""));
}
Self::InstallationError { target_version } => {
node.record_string("event", "InstallationError");
node.record_string("target-version", target_version.unwrap_or(""));
}
Self::WaitingForReboot { target_version } => {
node.record_string("event", "WaitingForReboot");
node.record_string("target-version", target_version.unwrap_or(""));
}
}
}
}
#[derive(Debug)]
pub struct Emitter {
events: BoundedNode<Node>,
// Used to group events from the same update check. Independent from the Omaha session id.
session_id: u64,
_node: Node,
}
impl Emitter {
pub fn from_node(node: Node) -> Self {
Self {
events: BoundedNode::from_node_and_capacity(node.create_child("events"), 50),
session_id: 0,
_node: node,
}
}
pub fn emit(&mut self, event: Event<'_>) {
if let Event::CheckingForUpdates = event {
self.session_id = make_session_id();
}
let session_id = self.session_id;
self.events.push(|n| {
event.write_to_inspect(&n, session_id);
n
});
}
}
#[cfg(not(test))]
fn make_session_id() -> u64 {
rand::random()
}
#[cfg(test)]
use mock::make_session_id;
#[cfg(test)]
mod mock {
use std::cell::RefCell;
thread_local!(static MOCK_SESSION_ID: RefCell<u64> = RefCell::new(0));
pub fn make_session_id() -> u64 {
MOCK_SESSION_ID.with(|id| *id.borrow())
}
pub fn set_session_id(new_id: u64) {
MOCK_SESSION_ID.with(|id| *id.borrow_mut() = new_id);
}
}
#[cfg(test)]
mod test {
use {
super::*,
fuchsia_inspect::{
assert_inspect_tree,
testing::{AnyProperty, TreeAssertion},
tree_assertion, Inspector,
},
};
static TARGET_VERSION: &'static str = "some-ver";
fn assert_emit_inspect(event: Event<'_>, child: TreeAssertion) {
let inspector = Inspector::new();
let mut emitter = Emitter::from_node(inspector.root().create_child("emitter"));
emitter.emit(event);
assert_inspect_tree!(
inspector,
"root": {
"emitter": {
"events": contains {
"capacity": 50u64,
"children": {
child,
}
}
}
}
);
}
#[test]
fn checking_for_updates() {
mock::set_session_id(9);
assert_emit_inspect(
Event::CheckingForUpdates,
tree_assertion!(
"0": {
"event": "CheckingForUpdates",
"ts": AnyProperty,
"session-id": 9u64
}
),
)
}
#[test]
fn error_checking_for_update() {
mock::set_session_id(9);
assert_emit_inspect(
Event::ErrorCheckingForUpdate,
tree_assertion!(
"0": {
"event": "ErrorCheckingForUpdate",
"ts": AnyProperty,
"session-id": 0u64
}
),
);
}
#[test]
fn no_update_available() {
mock::set_session_id(9);
assert_emit_inspect(
Event::NoUpdateAvailable,
tree_assertion!(
"0": {
"event": "NoUpdateAvailable",
"ts": AnyProperty,
"session-id": 0u64
}
),
)
}
#[test]
fn installation_deferred_by_policy() {
mock::set_session_id(9);
assert_emit_inspect(
Event::InstallationDeferredByPolicy { target_version: Some(TARGET_VERSION) },
tree_assertion!(
"0": {
"event": "InstallationDeferredByPolicy",
"ts": AnyProperty,
"session-id": 0u64,
"target-version": TARGET_VERSION,
}
),
);
}
#[test]
fn installing_update() {
mock::set_session_id(9);
assert_emit_inspect(
Event::InstallingUpdate { target_version: Some(TARGET_VERSION) },
tree_assertion!(
"0": {
"event": "InstallingUpdate",
"ts": AnyProperty,
"session-id": 0u64,
"target-version": TARGET_VERSION,
}
),
);
}
#[test]
fn installation_error() {
mock::set_session_id(9);
assert_emit_inspect(
Event::InstallationError { target_version: Some(TARGET_VERSION) },
tree_assertion!(
"0": {
"event": "InstallationError",
"ts": AnyProperty,
"session-id": 0u64,
"target-version": TARGET_VERSION,
}
),
);
}
#[test]
fn waiting_for_reboot() {
mock::set_session_id(9);
assert_emit_inspect(
Event::WaitingForReboot { target_version: Some(TARGET_VERSION) },
tree_assertion!(
"0": {
"event": "WaitingForReboot",
"ts": AnyProperty,
"session-id": 0u64,
"target-version": TARGET_VERSION,
}
),
);
}
#[test]
fn target_version_defaults_to_empty_string() {
mock::set_session_id(9);
assert_emit_inspect(
Event::InstallationDeferredByPolicy { target_version: None },
tree_assertion!(
"0": {
"event": "InstallationDeferredByPolicy",
"ts": AnyProperty,
"session-id": 0u64,
"target-version": "",
}
),
);
}
#[test]
fn session_id_persists() {
let inspector = Inspector::new();
let mut emitter = Emitter::from_node(inspector.root().create_child("emitter"));
mock::set_session_id(9);
emitter.emit(Event::CheckingForUpdates);
mock::set_session_id(10);
emitter.emit(Event::ErrorCheckingForUpdate);
assert_inspect_tree!(
inspector,
"root": {
"emitter": {
"events": contains {
"capacity": 50u64,
"children": {
"0": {
"event": "CheckingForUpdates",
"ts": AnyProperty,
"session-id": 9u64,
},
"1": {
"event": "ErrorCheckingForUpdate",
"ts": AnyProperty,
"session-id": 9u64,
}
}
}
}
}
)
}
#[test]
fn new_session_new_id() {
let inspector = Inspector::new();
let mut emitter = Emitter::from_node(inspector.root().create_child("emitter"));
mock::set_session_id(9);
emitter.emit(Event::CheckingForUpdates);
mock::set_session_id(10);
emitter.emit(Event::CheckingForUpdates);
assert_inspect_tree!(
inspector,
"root": {
"emitter": {
"events": contains {
"capacity": 50u64,
"children": {
"0": {
"event": "CheckingForUpdates",
"ts": AnyProperty,
"session-id": 9u64,
},
"1": {
"event": "CheckingForUpdates",
"ts": AnyProperty,
"session-id": 10u64,
}
}
}
}
}
)
}
}