use tracing::{debug, info};
use crate::daemon::{
clock_parameters::ClockParameters,
io::tsc::ReadTscImpl,
time::{
ClockExt,
clocks::{ClockBound, RealTime},
timex::Timex,
},
};
use super::ClockCorrection;
use super::ntp_adjtime::NtpAdjTimeExt;
#[derive(Debug, PartialEq)]
pub(super) struct Initializing;
#[derive(Debug, PartialEq)]
pub(super) struct Running;
#[derive(Debug, PartialEq)]
pub(super) enum State {
Initializing(Initializing),
Running(Running),
}
impl Initializing {
#[must_use]
#[allow(clippy::unused_self)]
pub(super) fn to_running(
&self,
ntp_adjtime: &impl NtpAdjTimeExt,
clock_params: &ClockParameters,
) -> (Running, ClockCorrection) {
let clockbound = ClockBound::new(clock_params.clone(), ReadTscImpl);
let offset = clockbound.get_offset_and_rtt(&RealTime).offset();
let threshold = Timex::MAX_PHASE_OFFSET;
let correction = if offset.abs() > threshold {
ntp_adjtime
.apply_phase_correction_step(offset)
.expect("failed to step clock on startup");
info!("Clock stepped by {} nanoseconds.", offset.as_nanos());
ClockCorrection::Step {
step_ns: offset.as_nanos(),
}
} else {
ntp_adjtime
.apply_phase_correction_smooth(offset, clock_params.clock_error_bound)
.expect("failed to smooth-apply phase correction on startup");
info!("Clock smooth-slewed by {} nanoseconds.", offset.as_nanos());
ClockCorrection::Smooth {
correction_ns: offset.as_nanos(),
}
};
debug!("Initializing now transitioning to Running.");
(Running, correction)
}
}
impl Running {
#[must_use]
#[allow(clippy::unused_self)]
pub(super) fn apply_offset(
&self,
ntp_adjtime: &impl NtpAdjTimeExt,
clock_params: &ClockParameters,
) -> (Running, ClockCorrection) {
let clockbound = ClockBound::new(clock_params.clone(), ReadTscImpl);
let offset = clockbound.get_offset_and_rtt(&RealTime).offset();
let result = ntp_adjtime
.apply_phase_correction_smooth(offset, clock_params.clock_error_bound)
.expect("failed to smooth-apply phase correction");
debug!(
"ntp_adjtime apply_phase_correction_smooth returned kernel state: {:?}.",
result
);
(
Running,
ClockCorrection::Smooth {
correction_ns: offset.as_nanos(),
},
)
}
}
#[cfg(test)]
mod test {
use crate::daemon::{
clock_state::clock_adjust::ntp_adjtime::MockNtpAdjTimeExt,
time::{
Clock, Duration, TscCount,
tsc::{Frequency, Period},
},
};
use super::*;
fn clock_params_with_offset(target_offset: Duration) -> ClockParameters {
let tsc_now = TscCount::new(crate::daemon::io::tsc::read_timestamp_counter_begin() as i64);
let realtime_now = crate::daemon::time::clocks::RealTime.get_time();
ClockParameters {
tsc_count: tsc_now,
period: Period::from_frequency(Frequency::from_hz(1_000_000_000.0)),
time: realtime_now + target_offset,
clock_error_bound: Duration::new(0),
period_max_error: Period::from_seconds(0.0),
as_of_monotonic: realtime_now,
}
}
#[test]
fn initializing_small_offset_smooth_slews_to_running() {
let mut mock = MockNtpAdjTimeExt::new();
mock.expect_apply_phase_correction_smooth()
.once()
.returning(|_, _| Ok(crate::daemon::time::timex::Timex::retrieve()));
let (state, correction) =
Initializing.to_running(&mock, &clock_params_with_offset(Duration::from_millis(100)));
assert!(matches!(state, Running));
assert!(
matches!(correction, ClockCorrection::Smooth { .. }),
"a small offset is smooth-slewed, so a smooth correction should be reported"
);
}
#[test]
fn initializing_large_offset_steps_to_running() {
let mut mock = MockNtpAdjTimeExt::new();
mock.expect_apply_phase_correction_step()
.once()
.returning(|_| Ok(crate::daemon::time::timex::Timex::retrieve()));
let (state, correction) =
Initializing.to_running(&mock, &clock_params_with_offset(Duration::from_secs(1)));
assert!(matches!(state, Running));
assert!(
matches!(correction, ClockCorrection::Step { .. }),
"a large offset is stepped, not smooth-slewed"
);
}
#[test]
fn running_calls_apply_phase_correction_smooth() {
let mut mock = MockNtpAdjTimeExt::new();
mock.expect_apply_phase_correction_smooth()
.once()
.returning(|_, _| Ok(crate::daemon::time::timex::Timex::retrieve()));
let running = Running;
let (next, correction) =
running.apply_offset(&mock, &clock_params_with_offset(Duration::from_millis(50)));
assert!(matches!(next, Running));
assert!(
matches!(correction, ClockCorrection::Smooth { .. }),
"Running always smooth-slews"
);
}
}