[timekeeper] Refactor EstimateUpdated diagnostics event.

In the future we'll have both events from the kalman filter estimate
and from the frequency estimate algorithm so "EstimateUpdate" was not
sufficiently precise. Renamed to "KalmanFilterUpdae".

This also moves the content from a single "offset" field (that
assumed the utc line was parallel to the monotonic line) to a pair of
monotonic/utc which fully specifies a point on the line. Changes to
the frequency will be recorded separately in a different event type
and so don't need to be included.

Bug: 56868
Change-Id: Ibccf1d6f27a53bb3f90d26cfa29d24fb69c2ee34
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/511041
Reviewed-by: Satsuki Ueno <satsukiu@google.com>
Commit-Queue: Jody Sankey <jsankey@google.com>
diff --git a/src/sys/time/timekeeper/src/clock_manager.rs b/src/sys/time/timekeeper/src/clock_manager.rs
index 62954b8..9f9c57c 100644
--- a/src/sys/time/timekeeper/src/clock_manager.rs
+++ b/src/sys/time/timekeeper/src/clock_manager.rs
@@ -815,7 +815,12 @@
         // Check that the correct diagnostic events were logged.
         diagnostics.assert_events(&[
             Event::TimeSourceStatus { role: TEST_ROLE, status: Status::Ok },
-            Event::EstimateUpdated { track: *TEST_TRACK, offset: OFFSET, sqrt_covariance: STD_DEV },
+            Event::KalmanFilterUpdated {
+                track: *TEST_TRACK,
+                monotonic: monotonic_ref,
+                utc: monotonic_ref + OFFSET,
+                sqrt_covariance: STD_DEV,
+            },
             Event::StartClock { track: *TEST_TRACK, source: *START_CLOCK_SOURCE },
             Event::WriteRtc { outcome: WriteRtcOutcome::Succeeded },
         ]);
@@ -864,7 +869,12 @@
         // Check that the correct diagnostic events were logged.
         diagnostics.assert_events(&[
             Event::TimeSourceStatus { role: TEST_ROLE, status: Status::Ok },
-            Event::EstimateUpdated { track: *TEST_TRACK, offset: OFFSET, sqrt_covariance: STD_DEV },
+            Event::KalmanFilterUpdated {
+                track: *TEST_TRACK,
+                monotonic: monotonic_ref,
+                utc: monotonic_ref + OFFSET,
+                sqrt_covariance: STD_DEV,
+            },
             Event::StartClock { track: *TEST_TRACK, source: *START_CLOCK_SOURCE },
             Event::UpdateClock { track: *TEST_TRACK, reason: ClockUpdateReason::IncreaseError },
             Event::UpdateClock { track: *TEST_TRACK, reason: ClockUpdateReason::IncreaseError },
@@ -913,11 +923,17 @@
         // Check that the correct diagnostic events were logged.
         diagnostics.assert_events(&[
             Event::TimeSourceStatus { role: TEST_ROLE, status: Status::Ok },
-            Event::EstimateUpdated { track: *TEST_TRACK, offset: OFFSET, sqrt_covariance: STD_DEV },
-            Event::StartClock { track: *TEST_TRACK, source: *START_CLOCK_SOURCE },
-            Event::EstimateUpdated {
+            Event::KalmanFilterUpdated {
                 track: *TEST_TRACK,
-                offset: expected_offset,
+                monotonic: monotonic_ref - SAMPLE_SPACING,
+                utc: monotonic_ref - SAMPLE_SPACING + OFFSET,
+                sqrt_covariance: STD_DEV,
+            },
+            Event::StartClock { track: *TEST_TRACK, source: *START_CLOCK_SOURCE },
+            Event::KalmanFilterUpdated {
+                track: *TEST_TRACK,
+                monotonic: monotonic_ref,
+                utc: monotonic_ref + expected_offset,
                 sqrt_covariance: 62225396.nanos(),
             },
             Event::ClockCorrection {
@@ -1009,11 +1025,17 @@
         // Check that the correct diagnostic events were logged.
         diagnostics.assert_events(&[
             Event::TimeSourceStatus { role: TEST_ROLE, status: Status::Ok },
-            Event::EstimateUpdated { track: *TEST_TRACK, offset: OFFSET, sqrt_covariance: STD_DEV },
-            Event::StartClock { track: *TEST_TRACK, source: *START_CLOCK_SOURCE },
-            Event::EstimateUpdated {
+            Event::KalmanFilterUpdated {
                 track: *TEST_TRACK,
-                offset: OFFSET + filtered_delta_offset,
+                monotonic: monotonic_ref - SAMPLE_SPACING,
+                utc: monotonic_ref - SAMPLE_SPACING + OFFSET,
+                sqrt_covariance: STD_DEV,
+            },
+            Event::StartClock { track: *TEST_TRACK, source: *START_CLOCK_SOURCE },
+            Event::KalmanFilterUpdated {
+                track: *TEST_TRACK,
+                monotonic: monotonic_ref,
+                utc: monotonic_ref + OFFSET + filtered_delta_offset,
                 sqrt_covariance: 62225396.nanos(),
             },
             Event::ClockCorrection {
diff --git a/src/sys/time/timekeeper/src/diagnostics/cobalt.rs b/src/sys/time/timekeeper/src/diagnostics/cobalt.rs
index 5ff5232..8bbae23 100644
--- a/src/sys/time/timekeeper/src/diagnostics/cobalt.rs
+++ b/src/sys/time/timekeeper/src/diagnostics/cobalt.rs
@@ -66,8 +66,8 @@
         }
     }
 
-    /// Records an update to the estimate, including an event and a covariance report.
-    fn record_estimate_update(&self, track: Track, sqrt_covariance: zx::Duration) {
+    /// Records an update to the Kalman filter state, including an event and a covariance report.
+    fn record_kalman_filter_update(&self, track: Track, sqrt_covariance: zx::Duration) {
         let mut locked_sender = self.sender.lock();
         let cobalt_track = Into::<CobaltTrack>::into(track);
         locked_sender.log_event_count(
@@ -176,8 +176,8 @@
                     1,
                 );
             }
-            Event::EstimateUpdated { track, sqrt_covariance, .. } => {
-                self.record_estimate_update(track, sqrt_covariance);
+            Event::KalmanFilterUpdated { track, sqrt_covariance, .. } => {
+                self.record_kalman_filter_update(track, sqrt_covariance);
             }
             Event::ClockCorrection { track, correction, strategy } => {
                 self.record_clock_correction(track, correction, strategy);
@@ -356,9 +356,10 @@
     async fn record_time_track_events() {
         let (diagnostics, mut mpsc_receiver) = create_test_object();
 
-        diagnostics.record(Event::EstimateUpdated {
+        diagnostics.record(Event::KalmanFilterUpdated {
             track: Track::Primary,
-            offset: zx::Duration::from_seconds(333),
+            monotonic: zx::Time::from_nanos(333_000_000_000),
+            utc: zx::Time::from_nanos(4455445544_000_000_000),
             sqrt_covariance: zx::Duration::from_micros(55555),
         });
         assert_eq!(
diff --git a/src/sys/time/timekeeper/src/diagnostics/fake.rs b/src/sys/time/timekeeper/src/diagnostics/fake.rs
index b66b2a35..41a79d8 100644
--- a/src/sys/time/timekeeper/src/diagnostics/fake.rs
+++ b/src/sys/time/timekeeper/src/diagnostics/fake.rs
@@ -97,14 +97,16 @@
                 }
                 _ => false,
             },
-            Event::EstimateUpdated { track, offset, sqrt_covariance } => match other {
-                Event::EstimateUpdated {
+            Event::KalmanFilterUpdated { track, monotonic, utc, sqrt_covariance } => match other {
+                Event::KalmanFilterUpdated {
                     track: other_track,
-                    offset: other_offset,
+                    monotonic: other_monotonic,
+                    utc: other_utc,
                     sqrt_covariance: other_sqrt_cov,
                 } => {
                     track == other_track
-                        && offset.eq_with_any(other_offset)
+                        && monotonic.eq_with_any(other_monotonic)
+                        && utc.eq_with_any(other_utc)
                         && sqrt_covariance.eq_with_any(other_sqrt_cov)
                 }
                 _ => false,
@@ -160,30 +162,41 @@
     #[fuchsia::test]
     fn match_wildcards() {
         let diagnostics = FakeDiagnostics::new();
-        let test_event = Event::EstimateUpdated {
+        let test_event = Event::KalmanFilterUpdated {
             track: Track::Monitor,
-            offset: zx::Duration::from_seconds(1234),
+            monotonic: zx::Time::from_nanos(1234_000_000_000),
+            utc: zx::Time::from_nanos(2345_000_000_000),
             sqrt_covariance: zx::Duration::from_millis(321),
         };
 
         diagnostics.record(test_event.clone());
         diagnostics.assert_events(&[test_event]);
 
-        diagnostics.assert_events(&[Event::EstimateUpdated {
+        diagnostics.assert_events(&[Event::KalmanFilterUpdated {
             track: Track::Monitor,
-            offset: ANY_DURATION,
+            monotonic: ANY_TIME,
+            utc: zx::Time::from_nanos(2345_000_000_000),
             sqrt_covariance: zx::Duration::from_millis(321),
         }]);
 
-        diagnostics.assert_events(&[Event::EstimateUpdated {
+        diagnostics.assert_events(&[Event::KalmanFilterUpdated {
             track: Track::Monitor,
-            offset: zx::Duration::from_seconds(1234),
+            monotonic: zx::Time::from_nanos(1234_000_000_000),
+            utc: ANY_TIME,
+            sqrt_covariance: zx::Duration::from_millis(321),
+        }]);
+
+        diagnostics.assert_events(&[Event::KalmanFilterUpdated {
+            track: Track::Monitor,
+            monotonic: zx::Time::from_nanos(1234_000_000_000),
+            utc: zx::Time::from_nanos(2345_000_000_000),
             sqrt_covariance: ANY_DURATION,
         }]);
 
-        diagnostics.assert_events(&[Event::EstimateUpdated {
+        diagnostics.assert_events(&[Event::KalmanFilterUpdated {
             track: Track::Monitor,
-            offset: ANY_DURATION,
+            monotonic: ANY_TIME,
+            utc: ANY_TIME,
             sqrt_covariance: ANY_DURATION,
         }]);
     }
diff --git a/src/sys/time/timekeeper/src/diagnostics/inspect.rs b/src/sys/time/timekeeper/src/diagnostics/inspect.rs
index 820c5ff..8df90fb7 100644
--- a/src/sys/time/timekeeper/src/diagnostics/inspect.rs
+++ b/src/sys/time/timekeeper/src/diagnostics/inspect.rs
@@ -27,8 +27,8 @@
 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 time estimates that are retained.
-const ESTIMATE_UPDATE_COUNT: usize = 5;
+/// The number of Kalman filter state updates that are retained.
+const FILTER_STATE_COUNT: usize = 5;
 /// The number of clock corrections that are retained.
 const CLOCK_CORRECTION_COUNT: usize = 3;
 
@@ -241,13 +241,13 @@
     }
 }
 
-/// A representation of a single update to a UTC estimate.
+/// A representation of the state of a kalman filter at a point in time.
 #[derive(InspectWritable, Default)]
-pub struct Estimate {
-    /// The monotonic time at which the estimate was received.
+pub struct KalmanFilterState {
+    /// The monotonic time at which the state applies, in nanoseconds.
     monotonic: i64,
-    /// Estimated UTC at reference minus monotonic time at reference, in nanoseconds.
-    offset: 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,
 }
@@ -265,8 +265,8 @@
 
 /// 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 time estimate.
-    estimates: CircularBuffer<Estimate>,
+    /// A circular buffer of recent updates to the kalman filter state.
+    filter_states: CircularBuffer<KalmanFilterState>,
     /// A circular buffer of recently planned clock corrections.
     corrections: CircularBuffer<ClockCorrection>,
     /// The details of the most recent update to the clock object.
@@ -281,7 +281,7 @@
     /// Constructs a new `TrackNode`.
     pub fn new(node: Node, clock: Arc<zx::Clock>) -> Self {
         TrackNode {
-            estimates: CircularBuffer::new(ESTIMATE_UPDATE_COUNT, "estimate_", &node),
+            filter_states: CircularBuffer::new(FILTER_STATE_COUNT, "filter_state_", &node),
             corrections: CircularBuffer::new(CLOCK_CORRECTION_COUNT, "clock_correction_", &node),
             last_update: None,
             clock,
@@ -289,14 +289,19 @@
         }
     }
 
-    /// Records a new estimate of time for the track.
-    pub fn update_estimate(&mut self, offset: zx::Duration, sqrt_covariance: zx::Duration) {
-        let estimate = Estimate {
-            monotonic: monotonic_time(),
-            offset: offset.into_nanos(),
+    /// 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.estimates.update(&estimate);
+        self.filter_states.update(&filter_state);
     }
 
     /// Records a new planned correction for the clock.
@@ -423,11 +428,11 @@
                     .get_mut(&role)
                     .map(|source| source.sample_rejection(error));
             }
-            Event::EstimateUpdated { track, offset, sqrt_covariance } => {
+            Event::KalmanFilterUpdated { track, monotonic, utc, sqrt_covariance } => {
                 self.tracks
                     .lock()
                     .get_mut(&track)
-                    .map(|track| track.update_estimate(offset, sqrt_covariance));
+                    .map(|track| track.update_filter_state(monotonic, utc, sqrt_covariance));
             }
             Event::ClockCorrection { track, correction, strategy } => {
                 self.tracks
@@ -569,10 +574,10 @@
                     status_change_monotonic: AnyProperty,
                 },
                 primary_track: contains {
-                    estimate_0: contains {
+                    filter_state_0: contains {
                         counter: 0u64,
                         monotonic: 0i64,
-                        offset: 0i64,
+                        utc: 0i64,
                         sqrt_covariance: 0u64,
                     }
                     // For brevity we omit the other empty estimates we expect in the circular
@@ -728,34 +733,34 @@
             inspector,
             root: contains {
                 primary_track: contains {
-                    estimate_0: contains {
+                    filter_state_0: contains {
                         counter: 0u64,
                         monotonic: 0i64,
-                        offset: 0i64,
+                        utc: 0i64,
                         sqrt_covariance: 0u64,
                     },
-                    estimate_1: contains {
+                    filter_state_1: contains {
                         counter: 0u64,
                         monotonic: 0i64,
-                        offset: 0i64,
+                        utc: 0i64,
                         sqrt_covariance: 0u64,
                     },
-                    estimate_2: contains {
+                    filter_state_2: contains {
                         counter: 0u64,
                         monotonic: 0i64,
-                        offset: 0i64,
+                        utc: 0i64,
                         sqrt_covariance: 0u64,
                     },
-                    estimate_3: contains {
+                    filter_state_3: contains {
                         counter: 0u64,
                         monotonic: 0i64,
-                        offset: 0i64,
+                        utc: 0i64,
                         sqrt_covariance: 0u64,
                     },
-                    estimate_4: contains {
+                    filter_state_4: contains {
                         counter: 0u64,
                         monotonic: 0i64,
-                        offset: 0i64,
+                        utc: 0i64,
                         sqrt_covariance: 0u64,
                     },
                     clock_correction_0: contains {
@@ -778,11 +783,11 @@
                     },
                 },
                 monitor_track: contains {
-                    estimate_0: contains {},
-                    estimate_1: contains {},
-                    estimate_2: contains {},
-                    estimate_3: contains {},
-                    estimate_4: contains {},
+                    filter_state_0: contains {},
+                    filter_state_1: contains {},
+                    filter_state_2: contains {},
+                    filter_state_3: contains {},
+                    filter_state_4: contains {},
                     clock_correction_0: contains {},
                     clock_correction_1: contains {},
                     clock_correction_2: contains {},
@@ -792,9 +797,10 @@
 
         // Write enough to wrap the circular buffer
         for i in 1..8 {
-            test.record(Event::EstimateUpdated {
+            test.record(Event::KalmanFilterUpdated {
                 track: Track::Primary,
-                offset: OFFSET * i,
+                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::ClockCorrection {
@@ -812,34 +818,34 @@
             inspector,
             root: contains {
                 primary_track: contains {
-                    estimate_0: contains {
+                    filter_state_0: contains {
                         counter: 6u64,
-                        monotonic: AnyProperty,
-                        offset: 6 * OFFSET.into_nanos(),
+                        monotonic: 6 * OFFSET.into_nanos(),
+                        utc: BACKSTOP_TIME + 6 * OFFSET.into_nanos(),
                         sqrt_covariance: 6 * SQRT_COVARIANCE as u64,
                     },
-                    estimate_1: contains {
+                    filter_state_1: contains {
                         counter: 7u64,
-                        monotonic: AnyProperty,
-                        offset: 7 * OFFSET.into_nanos(),
+                        monotonic: 7 * OFFSET.into_nanos(),
+                        utc: BACKSTOP_TIME + 7 * OFFSET.into_nanos(),
                         sqrt_covariance: 7 * SQRT_COVARIANCE as u64,
                     },
-                    estimate_2: contains {
+                    filter_state_2: contains {
                         counter: 3u64,
-                        monotonic: AnyProperty,
-                        offset: 3 * OFFSET.into_nanos(),
+                        monotonic: 3 * OFFSET.into_nanos(),
+                        utc: BACKSTOP_TIME + 3 * OFFSET.into_nanos(),
                         sqrt_covariance: 3 * SQRT_COVARIANCE as u64,
                     },
-                    estimate_3: contains {
+                    filter_state_3: contains {
                         counter: 4u64,
-                        monotonic: AnyProperty,
-                        offset: 4 * OFFSET.into_nanos(),
+                        monotonic: 4 * OFFSET.into_nanos(),
+                        utc: BACKSTOP_TIME + 4 * OFFSET.into_nanos(),
                         sqrt_covariance: 4 * SQRT_COVARIANCE as u64,
                     },
-                    estimate_4: contains {
+                    filter_state_4: contains {
                         counter: 5u64,
-                        monotonic: AnyProperty,
-                        offset: 5 * OFFSET.into_nanos(),
+                        monotonic: 5 * OFFSET.into_nanos(),
+                        utc: BACKSTOP_TIME + 5 * OFFSET.into_nanos(),
                         sqrt_covariance: 5 * SQRT_COVARIANCE as u64,
                     },
                     clock_correction_0: contains {
@@ -871,11 +877,11 @@
                     },
                 },
                 monitor_track: contains {
-                    estimate_0: contains {},
-                    estimate_1: contains {},
-                    estimate_2: contains {},
-                    estimate_3: contains {},
-                    estimate_4: contains {},
+                    filter_state_0: contains {},
+                    filter_state_1: contains {},
+                    filter_state_2: contains {},
+                    filter_state_3: contains {},
+                    filter_state_4: contains {},
                     clock_correction_0: contains {},
                     clock_correction_1: contains {},
                     clock_correction_2: contains {},
diff --git a/src/sys/time/timekeeper/src/diagnostics/mod.rs b/src/sys/time/timekeeper/src/diagnostics/mod.rs
index 6702ed0..7858c3c 100644
--- a/src/sys/time/timekeeper/src/diagnostics/mod.rs
+++ b/src/sys/time/timekeeper/src/diagnostics/mod.rs
@@ -44,12 +44,14 @@
     TimeSourceStatus { role: Role, status: Status },
     /// A sample received from a time source was rejected during validation.
     SampleRejected { role: Role, error: SampleValidationError },
-    /// A time estimate was updated.
-    EstimateUpdated {
+    /// The state of a Kalman filter was updated.
+    KalmanFilterUpdated {
         /// The `Track` of the estimate.
         track: Track,
-        /// Estimated UTC at reference minus monotonic time at reference.
-        offset: zx::Duration,
+        /// The monotonic time at which the state applies.
+        monotonic: zx::Time,
+        /// The estimated UTC corresponding to monotonic.
+        utc: zx::Time,
         /// Square root of element [0,0] of the covariance matrix.
         sqrt_covariance: zx::Duration,
     },
diff --git a/src/sys/time/timekeeper/src/estimator/kalman_filter.rs b/src/sys/time/timekeeper/src/estimator/kalman_filter.rs
index bcc40e7..7f2eb73 100644
--- a/src/sys/time/timekeeper/src/estimator/kalman_filter.rs
+++ b/src/sys/time/timekeeper/src/estimator/kalman_filter.rs
@@ -130,7 +130,7 @@
     pub fn transform(&self) -> Transform {
         Transform {
             monotonic_offset: self.monotonic.into_nanos(),
-            synthetic_offset: (self.reference_utc + f64_to_duration(self.estimate_0)).into_nanos(),
+            synthetic_offset: self.utc().into_nanos(),
             // TODO(jsankey): Accommodate an oscillator frequency error when implementing the
             // frequency correction algorithm.
             rate_adjust_ppm: 0,
@@ -139,9 +139,14 @@
         }
     }
 
-    /// Returns the last updated monotonic to UTC offset.
-    pub fn offset(&self) -> zx::Duration {
-        self.reference_utc + f64_to_duration(self.estimate_0) - self.monotonic
+    /// Returns the monotonic time of the last state update.
+    pub fn monotonic(&self) -> zx::Time {
+        self.monotonic
+    }
+
+    /// Returns the estimated utc at the last state update.
+    pub fn utc(&self) -> zx::Time {
+        self.reference_utc + f64_to_duration(self.estimate_0)
     }
 
     /// Returns the square root of the last updated filter covariance.
@@ -186,7 +191,8 @@
             transform.error_bound(TIME_1 + 1.second()),
             2 * SQRT_COV_1 + 2000 * OSCILLATOR_ERROR_STD_DEV_PPM
         );
-        assert_eq!(filter.offset(), OFFSET_1);
+        assert_eq!(filter.monotonic(), TIME_1);
+        assert_eq!(filter.utc(), TIME_1 + OFFSET_1);
         assert_eq!(filter.sqrt_covariance(), STD_DEV_1);
     }
 
diff --git a/src/sys/time/timekeeper/src/estimator/mod.rs b/src/sys/time/timekeeper/src/estimator/mod.rs
index b5e60b3..8c9d97e 100644
--- a/src/sys/time/timekeeper/src/estimator/mod.rs
+++ b/src/sys/time/timekeeper/src/estimator/mod.rs
@@ -38,9 +38,10 @@
     /// Construct a new estimator initialized to the supplied sample.
     pub fn new(track: Track, sample: Sample, diagnostics: Arc<D>) -> Self {
         let filter = KalmanFilter::new(sample);
-        diagnostics.record(Event::EstimateUpdated {
+        diagnostics.record(Event::KalmanFilterUpdated {
             track,
-            offset: filter.offset(),
+            monotonic: filter.monotonic(),
+            utc: filter.utc(),
             sqrt_covariance: filter.sqrt_covariance(),
         });
         Estimator { filter, track, diagnostics }
@@ -53,18 +54,17 @@
             warn!("Rejected update: {}", err);
             return;
         }
-        let offset = self.filter.offset();
         let sqrt_covariance = self.filter.sqrt_covariance();
-        self.diagnostics.record(Event::EstimateUpdated {
+        self.diagnostics.record(Event::KalmanFilterUpdated {
             track: self.track,
-            offset,
+            monotonic: self.filter.monotonic(),
+            utc: self.filter.utc(),
             sqrt_covariance,
         });
         info!(
-            "received {:?} update to {}. Estimated UTC offset={}ns, sqrt_covariance={}ns",
+            "Received {:?} update to {}. sqrt_covariance={}ns",
             self.track,
             Utc.timestamp_nanos(utc.into_nanos()),
-            offset.into_nanos(),
             sqrt_covariance.into_nanos()
         );
     }
@@ -93,10 +93,15 @@
     const TEST_TRACK: Track = Track::Primary;
     const SQRT_COV_1: u64 = STD_DEV_1.into_nanos() as u64;
 
-    fn create_estimate_event(offset: zx::Duration, sqrt_covariance: u64) -> Event {
-        Event::EstimateUpdated {
+    fn create_filter_event(
+        monotonic: zx::Time,
+        offset: zx::Duration,
+        sqrt_covariance: u64,
+    ) -> Event {
+        Event::KalmanFilterUpdated {
             track: TEST_TRACK,
-            offset,
+            monotonic: monotonic,
+            utc: monotonic + offset,
             sqrt_covariance: zx::Duration::from_nanos(sqrt_covariance as i64),
         }
     }
@@ -109,7 +114,7 @@
             Sample::new(TIME_1 + OFFSET_1, TIME_1, STD_DEV_1),
             Arc::clone(&diagnostics),
         );
-        diagnostics.assert_events(&[create_estimate_event(OFFSET_1, SQRT_COV_1)]);
+        diagnostics.assert_events(&[create_filter_event(TIME_1, OFFSET_1, SQRT_COV_1)]);
         let transform = estimator.transform();
         assert_eq!(transform.synthetic(TIME_1), TIME_1 + OFFSET_1);
         assert_eq!(transform.synthetic(TIME_2), TIME_2 + OFFSET_1);
@@ -139,8 +144,8 @@
         assert_eq!(estimator.transform().synthetic(TIME_3), TIME_3 + expected_offset);
 
         diagnostics.assert_events(&[
-            create_estimate_event(OFFSET_1, SQRT_COV_1),
-            create_estimate_event(expected_offset, expected_sqrt_cov),
+            create_filter_event(TIME_1, OFFSET_1, SQRT_COV_1),
+            create_filter_event(TIME_2, expected_offset, expected_sqrt_cov),
         ]);
     }
 
@@ -156,6 +161,6 @@
         estimator.update(Sample::new(TIME_1 + OFFSET_2, TIME_1, STD_DEV_1));
         assert_eq!(estimator.transform().synthetic(TIME_3), TIME_3 + OFFSET_1);
         // Ignored event should not be logged.
-        diagnostics.assert_events(&[create_estimate_event(OFFSET_1, SQRT_COV_1)]);
+        diagnostics.assert_events(&[create_filter_event(TIME_2, OFFSET_1, SQRT_COV_1)]);
     }
 }
diff --git a/src/sys/time/timekeeper/src/main.rs b/src/sys/time/timekeeper/src/main.rs
index 09ac289..f3fbb3c 100644
--- a/src/sys/time/timekeeper/src/main.rs
+++ b/src/sys/time/timekeeper/src/main.rs
@@ -384,9 +384,10 @@
                 time: Some(INVALID_RTC_TIME),
             },
             Event::TimeSourceStatus { role: Role::Primary, status: ftexternal::Status::Ok },
-            Event::EstimateUpdated {
+            Event::KalmanFilterUpdated {
                 track: Track::Primary,
-                offset: OFFSET,
+                monotonic: monotonic_ref,
+                utc: monotonic_ref + OFFSET,
                 sqrt_covariance: STD_DEV,
             },
             Event::StartClock {
@@ -396,9 +397,10 @@
             Event::WriteRtc { outcome: WriteRtcOutcome::Succeeded },
             Event::TimeSourceStatus { role: Role::Monitor, status: ftexternal::Status::Network },
             Event::TimeSourceStatus { role: Role::Monitor, status: ftexternal::Status::Ok },
-            Event::EstimateUpdated {
+            Event::KalmanFilterUpdated {
                 track: Track::Monitor,
-                offset: OFFSET_2,
+                monotonic: monotonic_ref,
+                utc: monotonic_ref + OFFSET_2,
                 sqrt_covariance: STD_DEV,
             },
             Event::StartClock {