blob: 04e563780eb8f46c08d53cae158652ae7d63ec12 [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::{
diagnostics::{Diagnostics, Event},
enums::{
ClockCorrectionStrategy, ClockUpdateReason, FrequencyDiscardReason,
InitializeRtcOutcome, Role, SampleValidationError, TimeSourceError, Track,
WriteRtcOutcome,
},
MonitorTrack, PrimaryTrack, TimeSource,
},
fidl_fuchsia_time_external::Status,
fuchsia_inspect::{
Inspector, IntProperty, Node, NumericProperty, Property, StringProperty, UintProperty,
},
fuchsia_zircon as zx,
futures::FutureExt,
inspect_writable::{InspectWritable, InspectWritableNode},
lazy_static::lazy_static,
parking_lot::Mutex,
std::{collections::HashMap, sync::Arc},
tracing::warn,
};
const ONE_MILLION: i32 = 1_000_000;
/// The value stored in place of any time that could not be generated.
const FAILED_TIME: i64 = -1;
/// The number of Kalman filter state updates that are retained.
const FILTER_STATE_COUNT: usize = 5;
/// The number of frequency estimates that are retained.
const FREQUENCY_COUNT: usize = 3;
/// The number of clock corrections that are retained.
const CLOCK_CORRECTION_COUNT: usize = 3;
lazy_static! {
pub static ref INSPECTOR: Inspector = Inspector::new();
}
fn monotonic_time() -> i64 {
zx::Time::get_monotonic().into_nanos()
}
/// A vector of inspect nodes used to store some struct implementing `InspectWritable`, where the
/// contents of the oldest node are replaced on each write.
///
/// An 'counter' field is added to each node labeling which write led to the current node contents.
/// The first write will have a counter of 1 and the node with the highest counter value is always
/// the most recently written.
///
/// Potentially this is worth moving into a library at some point.
pub struct CircularBuffer<T: InspectWritable + Default> {
count: usize,
nodes: Vec<T::NodeType>,
counters: Vec<UintProperty>,
}
impl<T: InspectWritable + Default> CircularBuffer<T> {
/// Construct a new `CircularBuffer` of the supplied size within the supplied parent node.
/// Each node is named with the supplied prefix and an integer suffix, all nodes are initialized
/// to default values.
fn new(size: usize, prefix: &str, node: &Node) -> Self {
let mut nodes: Vec<T::NodeType> = Vec::new();
let mut counters: Vec<UintProperty> = Vec::new();
for i in 0..size {
let child = node.create_child(format!("{}{}", prefix, i));
counters.push(child.create_uint("counter", 0));
nodes.push(T::default().create(child));
}
CircularBuffer { count: 0, nodes, counters }
}
/// Write the supplied data into the oldest node in the circular buffer.
fn update(&mut self, data: &T) {
let index = self.count % self.nodes.len();
self.count += 1;
self.nodes[index].update(data);
self.counters[index].set(self.count as u64);
}
}
/// A representation of a point in time as measured by all pertinent clocks.
#[derive(InspectWritable)]
pub struct TimeSet {
/// The ZX_CLOCK_MONOTONIC time, in ns.
monotonic: i64,
/// The UTC zx::Clock time, in ns.
clock_utc: i64,
}
impl TimeSet {
/// Creates a new `TimeSet` set to current time.
pub fn now(clock: &zx::Clock) -> Self {
TimeSet {
monotonic: monotonic_time(),
clock_utc: clock.read().map(zx::Time::into_nanos).unwrap_or(FAILED_TIME),
}
}
}
/// A representation of a single update to a UTC zx::Clock.
#[derive(InspectWritable)]
pub struct ClockDetails {
/// The monotonic time at which the details were retrieved. Note this is the time the Rust
/// object was created, which may not exactly match the time its contents were supplied by
/// the kernel.
retrieval_monotonic: i64,
/// The generation counter as documented in the zx::Clock.
generation_counter: u32,
/// The monotonic time from the monotonic-UTC correspondence pair, in ns.
monotonic_offset: i64,
/// The UTC time from the monotonic-UTC correspondence pair, in ns.
utc_offset: i64,
/// The ratio between UTC tick rate and monotonic tick rate in parts per million, expressed as
/// a PPM deviation from nominal. A positive number means UTC is running faster than monotonic.
rate_ppm: i32,
/// The error bounds as documented in the zx::Clock.
error_bounds: u64,
/// The reason this clock update occurred, if known.
reason: Option<ClockUpdateReason>,
}
impl ClockDetails {
/// Attaches a reason for the clock update.
pub fn with_reason(mut self, reason: ClockUpdateReason) -> Self {
self.reason = Some(reason);
self
}
}
impl From<zx::ClockDetails> for ClockDetails {
fn from(details: zx::ClockDetails) -> ClockDetails {
// Handle the potential for a divide by zero in an unset rate.
let rate_ppm = match (
details.mono_to_synthetic.rate.synthetic_ticks,
details.mono_to_synthetic.rate.reference_ticks,
) {
(0, _) => -ONE_MILLION,
(_, 0) => std::i32::MAX,
(syn, refr) => ((syn as i64 * ONE_MILLION as i64) / refr as i64) as i32 - ONE_MILLION,
};
ClockDetails {
retrieval_monotonic: monotonic_time(),
generation_counter: details.generation_counter,
monotonic_offset: details.mono_to_synthetic.reference_offset,
utc_offset: details.mono_to_synthetic.synthetic_offset,
rate_ppm,
error_bounds: details.error_bounds,
reason: None,
}
}
}
/// An inspect `Node` and properties used to describe interactions with a real time clock.
struct RealTimeClockNode {
/// The number of successful writes to the RTC.
write_success_counter: UintProperty,
/// The number of failed writes to the RTC.
write_failure_counter: UintProperty,
/// The inspect Node these fields are exported to.
_node: Node,
}
impl RealTimeClockNode {
/// Constructs a new `RealTimeClockNode`, recording the initial state.
pub fn new(node: Node, outcome: InitializeRtcOutcome, initial_time: Option<zx::Time>) -> Self {
node.record_string("initialization", format!("{:?}", outcome));
if let Some(time) = initial_time {
node.record_int("initial_time", time.into_nanos());
}
RealTimeClockNode {
write_success_counter: node.create_uint("write_successes", 0u64),
write_failure_counter: node.create_uint("write_failures", 0u64),
_node: node,
}
}
/// Records an attempt to write to the clock.
pub fn write(&mut self, outcome: WriteRtcOutcome) {
match outcome {
WriteRtcOutcome::Succeeded => self.write_success_counter.add(1),
WriteRtcOutcome::Failed => self.write_failure_counter.add(1),
}
}
}
/// An inspect `Node` and properties used to describe the health of a time source.
struct TimeSourceNode {
/// The most recent status of the time source.
status: StringProperty,
/// The monotonic time at which the time source last changed.
status_change: IntProperty,
/// The number of time source failures for each failure mode.
failure_counters: HashMap<TimeSourceError, UintProperty>,
/// The number of sample validation failutes for each rejection mode.
rejection_counters: HashMap<SampleValidationError, UintProperty>,
/// The inspect Node these fields are exported to.
node: Node,
}
impl TimeSourceNode {
/// Constructs a new `TimeSourceNode`, recording the initial state.
pub fn new<T: TimeSource>(node: Node, time_source: &T) -> Self {
node.record_string("component", format!("{:?}", time_source));
TimeSourceNode {
status: node.create_string("status", "Launched"),
status_change: node.create_int("status_change_monotonic", monotonic_time()),
failure_counters: HashMap::new(),
rejection_counters: HashMap::new(),
node: node,
}
}
/// Records a change in status of the time source.
pub fn status(&mut self, status: Status) {
self.status.set(&format!("{:?}", &status));
self.status_change.set(monotonic_time());
}
/// Records a failure of the time source.
pub fn failure(&mut self, error: TimeSourceError) {
self.status.set(&format!("Failed({:?})", error));
self.status_change.set(monotonic_time());
match self.failure_counters.get_mut(&error) {
Some(field) => field.add(1),
None => {
let property = self.node.create_uint(&format!("failure_count_{:?}", &error), 1);
self.failure_counters.insert(error, property);
}
}
}
/// Records a rejection of a sample produced by the time source.
pub fn sample_rejection(&mut self, error: SampleValidationError) {
match self.rejection_counters.get_mut(&error) {
Some(field) => field.add(1),
None => {
let property = self.node.create_uint(&format!("rejection_count_{:?}", &error), 1);
self.rejection_counters.insert(error, property);
}
}
}
}
/// A representation of the state of a Kalman filter at a point in time.
#[derive(InspectWritable, Default)]
pub struct KalmanFilterState {
/// The monotonic time at which the state applies, in nanoseconds.
monotonic: i64,
/// The estimated UTC corresponding to monotonic, in nanoseconds.
utc: i64,
/// The square root of element [0,0] of the covariance matrix, in nanoseconds.
sqrt_covariance: u64,
}
/// A representation of a frequency estimate at a point in time.
#[derive(InspectWritable, Default)]
pub struct FrequencyState {
/// The monotonic time at which the state applies, in nanoseconds.
monotonic: i64,
/// The estimated frequency as a PPM deviation from nominal. A positive number means UTC is
/// running faster than monotonic, i.e. the oscillator is slow.
rate_adjust_ppm: i32,
/// The number of frequency windows that contributed to this estimate.
window_count: u32,
}
/// A representation of a single planned clock correction.
#[derive(InspectWritable, Default)]
pub struct ClockCorrection {
/// The monotonic time at which the clock correction was received.
monotonic: i64,
/// The change to be applied to the current clock value, in nanoseconds.
correction: i64,
/// The strategy that will be used to apply this correction.
strategy: ClockCorrectionStrategy,
}
/// An inspect `Node` and properties used to describe the state and history of a time track.
struct TrackNode {
/// A circular buffer of recent updates to the Kalman filter state.
filter_states: CircularBuffer<KalmanFilterState>,
/// A circular buffer of recent updates to the frequency.
frequencies: CircularBuffer<FrequencyState>,
/// A circular buffer of recently planned clock corrections.
corrections: CircularBuffer<ClockCorrection>,
/// The details of the most recent update to the clock object.
last_update: Option<<ClockDetails as InspectWritable>::NodeType>,
/// The number of frequency window discards for each failure mode.
frequency_discard_counters: HashMap<FrequencyDiscardReason, UintProperty>,
/// The clock used to determine the result of a clock update operation.
clock: Arc<zx::Clock>,
/// The inspect `Node` these fields are exported to.
node: Node,
}
impl TrackNode {
/// Constructs a new `TrackNode`.
pub fn new(node: Node, clock: Arc<zx::Clock>) -> Self {
TrackNode {
filter_states: CircularBuffer::new(FILTER_STATE_COUNT, "filter_state_", &node),
frequencies: CircularBuffer::new(FREQUENCY_COUNT, "frequency_", &node),
corrections: CircularBuffer::new(CLOCK_CORRECTION_COUNT, "clock_correction_", &node),
frequency_discard_counters: HashMap::new(),
last_update: None,
clock,
node,
}
}
/// Records the discard of a frequency window.
pub fn discard_frequency(&mut self, reason: FrequencyDiscardReason) {
match self.frequency_discard_counters.get_mut(&reason) {
Some(field) => field.add(1),
None => {
let prop = self.node.create_uint(&format!("frequency_discard_{:?}", &reason), 1);
self.frequency_discard_counters.insert(reason, prop);
}
}
}
/// Records a new Kalman filter update for the track.
pub fn update_filter_state(
&mut self,
monotonic: zx::Time,
utc: zx::Time,
sqrt_covariance: zx::Duration,
) {
let filter_state = KalmanFilterState {
monotonic: monotonic.into_nanos(),
utc: utc.into_nanos(),
sqrt_covariance: sqrt_covariance.into_nanos() as u64,
};
self.filter_states.update(&filter_state);
}
/// Records a new frequency update for the track.
pub fn update_frequency(
&mut self,
monotonic: zx::Time,
rate_adjust_ppm: i32,
window_count: u32,
) {
let frequency_state =
FrequencyState { monotonic: monotonic.into_nanos(), rate_adjust_ppm, window_count };
self.frequencies.update(&frequency_state);
}
/// Records a new planned correction for the clock.
pub fn clock_correction(
&mut self,
correction: zx::Duration,
strategy: ClockCorrectionStrategy,
) {
let clock_correction = ClockCorrection {
monotonic: monotonic_time(),
correction: correction.into_nanos(),
strategy,
};
self.corrections.update(&clock_correction);
}
/// Records an update to the clock object.
pub fn update_clock(&mut self, reason: Option<ClockUpdateReason>) {
match self.clock.get_details() {
Ok(details) => {
let mut details_struct: ClockDetails = details.into();
if let Some(reason) = reason {
details_struct = details_struct.with_reason(reason);
}
if let Some(last_update_node) = &self.last_update {
last_update_node.update(&details_struct);
} else {
self.last_update
.replace(details_struct.create(self.node.create_child("last_update")));
}
}
Err(err) => {
warn!("Failed to export clock update to inspect: {}", err);
}
};
}
}
/// The complete set of Timekeeper information exported through Inspect.
pub struct InspectDiagnostics {
/// Details of the health of time sources.
time_sources: Mutex<HashMap<Role, TimeSourceNode>>,
/// Details of the current state and history of time tracks.
tracks: Mutex<HashMap<Track, TrackNode>>,
/// Details of interactions with the real time clock.
rtc: Mutex<Option<RealTimeClockNode>>,
/// The inspect node used to export the contents of this `InspectDiagnostics`.
node: Node,
}
impl InspectDiagnostics {
/// Construct a new `InspectDiagnostics` exporting at the supplied `Node` using data from
/// the supplied clock.
pub(crate) fn new<T: TimeSource>(
node: &Node,
primary: &PrimaryTrack<T>,
optional_monitor: &Option<MonitorTrack<T>>,
) -> Self {
// Record fixed data directly into the node without retaining any references.
node.record_child("initialization", |child| TimeSet::now(&primary.clock).record(child));
let backstop = primary.clock.get_details().expect("failed to get clock details").backstop;
node.record_int("backstop", backstop.into_nanos());
let mut time_sources_hashmap = HashMap::new();
let mut tracks_hashmap = HashMap::new();
time_sources_hashmap.insert(
Role::Primary,
TimeSourceNode::new(node.create_child("primary_time_source"), &primary.time_source),
);
tracks_hashmap.insert(
Track::Primary,
TrackNode::new(node.create_child("primary_track"), Arc::clone(&primary.clock)),
);
if let Some(monitor) = optional_monitor {
time_sources_hashmap.insert(
Role::Monitor,
TimeSourceNode::new(node.create_child("monitor_time_source"), &monitor.time_source),
);
tracks_hashmap.insert(
Track::Monitor,
TrackNode::new(node.create_child("monitor_track"), Arc::clone(&monitor.clock)),
);
}
let diagnostics = InspectDiagnostics {
time_sources: Mutex::new(time_sources_hashmap),
tracks: Mutex::new(tracks_hashmap),
rtc: Mutex::new(None),
node: node.clone_weak(),
};
let clock = Arc::clone(&primary.clock);
node.record_lazy_child("current", move || {
let clock_clone = Arc::clone(&clock);
async move {
let inspector = Inspector::new();
TimeSet::now(&clock_clone).record(inspector.root());
Ok(inspector)
}
.boxed()
});
diagnostics
}
fn update_source<F>(&self, role: Role, function: F)
where
F: FnOnce(&mut TimeSourceNode),
{
self.time_sources.lock().get_mut(&role).map(function);
}
fn update_track<F>(&self, track: Track, function: F)
where
F: FnOnce(&mut TrackNode),
{
self.tracks.lock().get_mut(&track).map(function);
}
}
impl Diagnostics for InspectDiagnostics {
fn record(&self, event: Event) {
match event {
Event::Initialized { .. } => {}
Event::InitializeRtc { outcome, time } => {
self.rtc.lock().get_or_insert_with(|| {
RealTimeClockNode::new(self.node.create_child("real_time_clock"), outcome, time)
});
}
Event::TimeSourceFailed { role, error } => {
self.update_source(role, |tsn| tsn.failure(error));
}
Event::TimeSourceStatus { role, status } => {
self.update_source(role, |tsn| tsn.status(status));
}
Event::SampleRejected { role, error } => {
self.update_source(role, |tsn| tsn.sample_rejection(error));
}
Event::FrequencyWindowDiscarded { track, reason } => {
self.update_track(track, |tn| tn.discard_frequency(reason));
}
Event::KalmanFilterUpdated { track, monotonic, utc, sqrt_covariance } => {
self.update_track(track, |tn| {
tn.update_filter_state(monotonic, utc, sqrt_covariance)
});
}
Event::FrequencyUpdated { track, monotonic, rate_adjust_ppm, window_count } => {
self.update_track(track, |tn| {
tn.update_frequency(monotonic, rate_adjust_ppm, window_count)
});
}
Event::ClockCorrection { track, correction, strategy } => {
self.update_track(track, |tn| tn.clock_correction(correction, strategy));
}
Event::WriteRtc { outcome } => {
if let Some(ref mut rtc_node) = *self.rtc.lock() {
rtc_node.write(outcome);
}
}
Event::StartClock { track, .. } => {
self.update_track(track, |tn| tn.update_clock(None));
}
Event::UpdateClock { track, reason } => {
self.update_track(track, |tn| tn.update_clock(Some(reason)));
}
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{
enums::{
FrequencyDiscardReason as FDR, SampleValidationError as SVE, StartClockSource,
TimeSourceError as TSE,
},
time_source::FakeTimeSource,
},
fuchsia_inspect::{assert_data_tree, testing::AnyProperty},
lazy_static::lazy_static,
};
const BACKSTOP_TIME: i64 = 111111111;
const RTC_INITIAL_TIME: i64 = 111111234;
const RATE_ADJUST: i32 = 222;
const ERROR_BOUNDS: u64 = 4444444444;
const GENERATION_COUNTER: u32 = 7777;
const OFFSET: zx::Duration = zx::Duration::from_seconds(311);
const CORRECTION: zx::Duration = zx::Duration::from_millis(88);
const SQRT_COVARIANCE: i64 = 5454545454;
lazy_static! {
static ref VALID_DETAILS: zx::sys::zx_clock_details_v1_t = zx::sys::zx_clock_details_v1_t {
options: 0,
backstop_time: BACKSTOP_TIME,
ticks_to_synthetic: zx::sys::zx_clock_transformation_t {
reference_offset: 777777777777,
synthetic_offset: 787878787878,
rate: zx::sys::zx_clock_rate_t { reference_ticks: 1_000, synthetic_ticks: 1_000 },
},
mono_to_synthetic: zx::sys::zx_clock_transformation_t {
reference_offset: 888888888888,
synthetic_offset: 898989898989,
rate: zx::sys::zx_clock_rate_t {
reference_ticks: ONE_MILLION as u32,
synthetic_ticks: (RATE_ADJUST + ONE_MILLION) as u32,
},
},
error_bound: ERROR_BOUNDS,
query_ticks: 12345789,
last_value_update_ticks: 36363636,
last_rate_adjust_update_ticks: 37373737,
last_error_bounds_update_ticks: 38383838,
generation_counter: GENERATION_COUNTER,
padding1: Default::default()
};
}
/// Creates a new wrapped clock set to backstop time.
fn create_clock() -> Arc<zx::Clock> {
Arc::new(
zx::Clock::create(zx::ClockOpts::empty(), Some(zx::Time::from_nanos(BACKSTOP_TIME)))
.unwrap(),
)
}
/// Creates a new `InspectDiagnostics` object recording to the root of the supplied inspector,
/// returning a tuple of the object and the primary clock it is using.
fn create_test_object(
inspector: &Inspector,
include_monitor: bool,
) -> (InspectDiagnostics, Arc<zx::Clock>) {
let primary =
PrimaryTrack { time_source: FakeTimeSource::failing(), clock: create_clock() };
let monitor = match include_monitor {
true => {
Some(MonitorTrack { time_source: FakeTimeSource::failing(), clock: create_clock() })
}
false => None,
};
(InspectDiagnostics::new(inspector.root(), &primary, &monitor), primary.clock)
}
#[fuchsia::test]
fn valid_clock_details_conversion() {
let details = ClockDetails::from(zx::ClockDetails::from(VALID_DETAILS.clone()));
assert_eq!(details.generation_counter, GENERATION_COUNTER);
assert_eq!(details.utc_offset, VALID_DETAILS.mono_to_synthetic.synthetic_offset);
assert_eq!(details.monotonic_offset, VALID_DETAILS.mono_to_synthetic.reference_offset);
assert_eq!(details.rate_ppm, RATE_ADJUST);
assert_eq!(details.error_bounds, ERROR_BOUNDS);
}
#[fuchsia::test]
fn invalid_clock_details_conversion() {
let mut zx_details = zx::ClockDetails::from(VALID_DETAILS.clone());
zx_details.mono_to_synthetic.rate.synthetic_ticks = 1000;
zx_details.mono_to_synthetic.rate.reference_ticks = 0;
let details = ClockDetails::from(zx::ClockDetails::from(zx_details));
assert_eq!(details.generation_counter, GENERATION_COUNTER);
assert_eq!(details.utc_offset, VALID_DETAILS.mono_to_synthetic.synthetic_offset);
assert_eq!(details.monotonic_offset, VALID_DETAILS.mono_to_synthetic.reference_offset);
assert_eq!(details.rate_ppm, std::i32::MAX);
assert_eq!(details.error_bounds, ERROR_BOUNDS);
}
#[fuchsia::test]
fn after_initialization() {
let inspector = &Inspector::new();
let (_inspect_diagnostics, _) = create_test_object(&inspector, false);
assert_data_tree!(
inspector,
root: contains {
initialization: contains {
monotonic: AnyProperty,
clock_utc: AnyProperty,
},
backstop: BACKSTOP_TIME,
current: contains {
monotonic: AnyProperty,
clock_utc: AnyProperty,
},
primary_time_source: contains {
component: "FakeTimeSource",
status: "Launched",
status_change_monotonic: AnyProperty,
},
primary_track: contains {
filter_state_0: contains {
counter: 0u64,
monotonic: 0i64,
utc: 0i64,
sqrt_covariance: 0u64,
}
// For brevity we omit the other empty estimates we expect in the circular
// buffer.
},
}
);
}
#[fuchsia::test]
fn after_update() {
let inspector = &Inspector::new();
let (inspect_diagnostics, clock) = create_test_object(&inspector, false);
// Perform two updates to the clock. The inspect data should reflect the most recent.
let monotonic_time = zx::Time::get_monotonic();
clock
.update(
zx::ClockUpdate::builder()
.absolute_value(monotonic_time, zx::Time::from_nanos(BACKSTOP_TIME + 1234))
.rate_adjust(0)
.error_bounds(0),
)
.expect("Failed to update test clock");
inspect_diagnostics
.record(Event::StartClock { track: Track::Primary, source: StartClockSource::Rtc });
let monotonic_time = zx::Time::get_monotonic();
clock
.update(
zx::ClockUpdate::builder()
.absolute_value(monotonic_time, zx::Time::from_nanos(BACKSTOP_TIME + 2345))
.rate_adjust(RATE_ADJUST)
.error_bounds(ERROR_BOUNDS),
)
.expect("Failed to update test clock");
inspect_diagnostics.record(Event::UpdateClock {
track: Track::Primary,
reason: ClockUpdateReason::TimeStep,
});
assert_data_tree!(
inspector,
root: contains {
initialization: contains {
monotonic: AnyProperty,
clock_utc: AnyProperty,
},
backstop: BACKSTOP_TIME,
current: contains {
monotonic: AnyProperty,
clock_utc: AnyProperty,
},
primary_track: contains {
last_update: contains {
retrieval_monotonic: AnyProperty,
generation_counter: 2u64,
monotonic_offset: monotonic_time.into_nanos() as i64,
utc_offset: (BACKSTOP_TIME + 2345) as i64,
rate_ppm: RATE_ADJUST as i64,
error_bounds: ERROR_BOUNDS,
reason: "Some(TimeStep)",
},
}
}
);
}
#[fuchsia::test]
fn real_time_clock() {
let inspector = &Inspector::new();
let (inspect_diagnostics, _) = create_test_object(&inspector, false);
inspect_diagnostics.record(Event::InitializeRtc {
outcome: InitializeRtcOutcome::Succeeded,
time: Some(zx::Time::from_nanos(RTC_INITIAL_TIME)),
});
assert_data_tree!(
inspector,
root: contains {
real_time_clock: contains {
initialization: "Succeeded",
initial_time: RTC_INITIAL_TIME,
write_successes: 0u64,
write_failures: 0u64,
}
}
);
inspect_diagnostics.record(Event::WriteRtc { outcome: WriteRtcOutcome::Succeeded });
inspect_diagnostics.record(Event::WriteRtc { outcome: WriteRtcOutcome::Succeeded });
inspect_diagnostics.record(Event::WriteRtc { outcome: WriteRtcOutcome::Failed });
assert_data_tree!(
inspector,
root: contains {
real_time_clock: contains {
initialization: "Succeeded",
initial_time: RTC_INITIAL_TIME,
write_successes: 2u64,
write_failures: 1u64,
}
}
);
}
#[fuchsia::test]
fn time_sources() {
let inspector = &Inspector::new();
let (test, _) = create_test_object(&inspector, true);
assert_data_tree!(
inspector,
root: contains {
primary_time_source: contains {
component: "FakeTimeSource",
status: "Launched",
status_change_monotonic: AnyProperty,
},
monitor_time_source: contains {
component: "FakeTimeSource",
status: "Launched",
status_change_monotonic: AnyProperty,
}
}
);
test.record(Event::TimeSourceFailed { role: Role::Primary, error: TSE::LaunchFailed });
test.record(Event::TimeSourceFailed { role: Role::Primary, error: TSE::CallFailed });
test.record(Event::TimeSourceStatus { role: Role::Primary, status: Status::Ok });
test.record(Event::SampleRejected { role: Role::Primary, error: SVE::BeforeBackstop });
test.record(Event::TimeSourceFailed { role: Role::Primary, error: TSE::CallFailed });
test.record(Event::TimeSourceStatus { role: Role::Monitor, status: Status::Network });
assert_data_tree!(
inspector,
root: contains {
primary_time_source: contains {
component: "FakeTimeSource",
status: "Failed(CallFailed)",
status_change_monotonic: AnyProperty,
failure_count_LaunchFailed: 1u64,
failure_count_CallFailed: 2u64,
rejection_count_BeforeBackstop: 1u64,
},
monitor_time_source: contains {
component: "FakeTimeSource",
status: "Network",
status_change_monotonic: AnyProperty,
}
}
);
}
#[fuchsia::test]
fn tracks() {
let inspector = &Inspector::new();
let (test, _) = create_test_object(&inspector, true);
assert_data_tree!(
inspector,
root: contains {
// For brevity we only verify the uninitialized contents of one entry per buffer.
primary_track: contains {
filter_state_0: contains {
counter: 0u64,
monotonic: 0i64,
utc: 0i64,
sqrt_covariance: 0u64,
},
filter_state_1: contains {},
filter_state_2: contains {},
filter_state_3: contains {},
filter_state_4: contains {},
frequency_0: contains {
counter: 0u64,
monotonic: 0i64,
rate_adjust_ppm: 0i64,
window_count: 0u64,
},
frequency_1: contains {},
frequency_2: contains {},
clock_correction_0: contains {
counter: 0u64,
monotonic: 0i64,
correction: 0i64,
strategy: "Step",
},
clock_correction_1: contains {},
clock_correction_2: contains {},
},
monitor_track: contains {
filter_state_0: contains {},
filter_state_1: contains {},
filter_state_2: contains {},
filter_state_3: contains {},
filter_state_4: contains {},
frequency_0: contains {},
frequency_1: contains {},
frequency_2: contains {},
clock_correction_0: contains {},
clock_correction_1: contains {},
clock_correction_2: contains {},
},
}
);
// Write enough to wrap all of the circular buffers
for i in 1..8 {
test.record(Event::KalmanFilterUpdated {
track: Track::Primary,
monotonic: zx::Time::ZERO + OFFSET * i,
utc: zx::Time::from_nanos(BACKSTOP_TIME) + OFFSET * i,
sqrt_covariance: zx::Duration::from_nanos(SQRT_COVARIANCE) * i,
});
test.record(Event::FrequencyUpdated {
track: Track::Primary,
monotonic: zx::Time::ZERO + OFFSET * i,
rate_adjust_ppm: -i,
window_count: i as u32,
});
test.record(Event::ClockCorrection {
track: Track::Primary,
correction: CORRECTION * i,
strategy: ClockCorrectionStrategy::MaxDurationSlew,
});
test.record(Event::UpdateClock {
track: Track::Primary,
reason: ClockUpdateReason::BeginSlew,
});
}
// And record a few frequency window discard events.
let make_event = |reason| Event::FrequencyWindowDiscarded { track: Track::Primary, reason };
test.record(make_event(FDR::InsufficientSamples));
test.record(make_event(FDR::UtcBeforeWindow));
test.record(make_event(FDR::InsufficientSamples));
assert_data_tree!(
inspector,
root: contains {
primary_track: contains {
filter_state_0: contains {
counter: 6u64,
monotonic: 6 * OFFSET.into_nanos(),
utc: BACKSTOP_TIME + 6 * OFFSET.into_nanos(),
sqrt_covariance: 6 * SQRT_COVARIANCE as u64,
},
filter_state_1: contains {
counter: 7u64,
monotonic: 7 * OFFSET.into_nanos(),
utc: BACKSTOP_TIME + 7 * OFFSET.into_nanos(),
sqrt_covariance: 7 * SQRT_COVARIANCE as u64,
},
filter_state_2: contains {
counter: 3u64,
monotonic: 3 * OFFSET.into_nanos(),
utc: BACKSTOP_TIME + 3 * OFFSET.into_nanos(),
sqrt_covariance: 3 * SQRT_COVARIANCE as u64,
},
filter_state_3: contains {
counter: 4u64,
monotonic: 4 * OFFSET.into_nanos(),
utc: BACKSTOP_TIME + 4 * OFFSET.into_nanos(),
sqrt_covariance: 4 * SQRT_COVARIANCE as u64,
},
filter_state_4: contains {
counter: 5u64,
monotonic: 5 * OFFSET.into_nanos(),
utc: BACKSTOP_TIME + 5 * OFFSET.into_nanos(),
sqrt_covariance: 5 * SQRT_COVARIANCE as u64,
},
frequency_0: contains {
counter: 7u64,
monotonic: 7 * OFFSET.into_nanos(),
rate_adjust_ppm: -7i64,
window_count: 7u64,
},
frequency_1: contains {
counter: 5u64,
monotonic: 5 * OFFSET.into_nanos(),
rate_adjust_ppm: -5i64,
window_count: 5u64,
},
frequency_2: contains {
counter: 6u64,
monotonic: 6 * OFFSET.into_nanos(),
rate_adjust_ppm: -6i64,
window_count: 6u64,
},
clock_correction_0: contains {
counter: 7u64,
monotonic: AnyProperty,
correction: 7 * CORRECTION.into_nanos(),
strategy: "MaxDurationSlew",
},
clock_correction_1: contains {
counter: 5u64,
monotonic: AnyProperty,
correction: 5 * CORRECTION.into_nanos(),
strategy: "MaxDurationSlew",
},
clock_correction_2: contains {
counter: 6u64,
monotonic: AnyProperty,
correction: 6 * CORRECTION.into_nanos(),
strategy: "MaxDurationSlew",
},
last_update: contains {
retrieval_monotonic: AnyProperty,
generation_counter: 0u64,
monotonic_offset: AnyProperty,
utc_offset: AnyProperty,
rate_ppm: AnyProperty,
error_bounds: AnyProperty,
reason: "Some(BeginSlew)",
},
frequency_discard_InsufficientSamples: 2u64,
frequency_discard_UtcBeforeWindow: 1u64,
},
monitor_track: contains {
filter_state_0: contains {},
filter_state_1: contains {},
filter_state_2: contains {},
filter_state_3: contains {},
filter_state_4: contains {},
frequency_0: contains {},
frequency_1: contains {},
frequency_2: contains {},
clock_correction_0: contains {},
clock_correction_1: contains {},
clock_correction_2: contains {},
},
}
);
}
}