Skip to main content

DisciplineConfig

Struct DisciplineConfig 

Source
pub struct DisciplineConfig {
Show 23 fields pub makestep_threshold: Option<f64>, pub makestep_limit: u32, pub max_slew_ppm: f64, pub max_freq_ppm: f64, pub min_poll: i8, pub max_poll: i8, pub iburst: bool, pub freq_integral_gain: f64, pub poll_down_noise_ratio: f64, pub poll_up_streak: u32, pub weight_floor_ratio: f64, pub offset_weight_floor_ratio: f64, pub offset_age_halflife_s: f64, pub offset_weight_dispersion_k: f64, pub slope_density_weighting: bool, pub corr_time_s: f64, pub adaptive_window: bool, pub corr_time_max_s: f64, pub leap_mode: LeapMode, pub max_change_s: Option<f64>, pub max_change_start: u32, pub max_change_ignore: i32, pub corr_time_ratio: f64,
}
Expand description

Configuration mirroring the chrony.conf directives we honor.

Fields§

§makestep_threshold: Option<f64>

Step (rather than slew) when |offset| exceeds this, during the first makestep_limit updates. None = never step.

§makestep_limit: u32§max_slew_ppm: f64

Cap on offset-correction slew rate, ppm.

§max_freq_ppm: f64

Cap on the absolute frequency correction we will command, ppm.

§min_poll: i8

log2 seconds.

§max_poll: i8§iburst: bool

Send an initial burst of quick polls to converge fast (chrony iburst).

§freq_integral_gain: f64

Gain of the integral trim on the frequency estimate. 0 disables it, leaving a purely proportional loop. See FREQ_INTEGRAL_GAIN.

§poll_down_noise_ratio: f64

Step the poll interval back DOWN when |offset| > this * noise.

This is the packet budget, and the packet budget is most of the accuracy: offset error falls as 1/sqrt(N). Measured on the seeded rig, clknetsim’s own packet counters, same poll bounds for both arms:

           mean poll   median |e| S1   per packet spent
chrony        33.9 s        1.52 us    (baseline)
rusty_time    40.1 s        1.47 us    x1.05

So the estimator was never the deficit — at equal cost it is at parity on S1 and slightly ahead on S8 (x0.96). We were simply buying fewer samples. See POLL_DOWN_NOISE_RATIO.

§poll_up_streak: u32

Consecutive stable samples required before the poll interval doubles.

This, not the dead band, is what sets the packet budget. Sweeping poll_down_noise_ratio from 10 down to 3 moved the mean poll by 0.3 s and the accuracy not at all, because the step-DOWN branch only runs when |offset| >= 2 * noise and a converged loop is almost never there. It is always “stable”, so it always climbs, and it pins at maxpoll. The climb rate is the only term with any authority.

§weight_floor_ratio: f64

Width of the regression’s weight floor, as a fraction of the minimum observed delay. See rusty_time_core::filter::WEIGHT_FLOOR_RATIO.

This is the one knob that can improve accuracy WITHOUT spending more packets, which is why it is worth a sweep: buying accuracy with poll rate leaves per-packet efficiency exactly where it was.

§offset_weight_floor_ratio: f64

Weight-floor width for the OFFSET alone; the slope keeps weight_floor_ratio. Equal values reproduce the single-weight fit.

§offset_age_halflife_s: f64

Half-life, seconds, of the age decay on the OFFSET weights. Infinite disables it, weighting by delay alone.

§offset_weight_dispersion_k: f64

If > 0, take the offset weight floor from measured delay dispersion rather than a fraction of the minimum delay.

§slope_density_weighting: bool

Weight the slope fit by the time each sample represents, so an iburst cluster cannot act as a high-leverage anchor on the frequency estimate.

§corr_time_s: f64

Absolute steady-state correction time, seconds. 0 keeps the default behaviour of CORR_TIME_RATIO * poll_interval.

The drain rate is offset / correction_time, and tying that time to the POLL makes the loop’s aggressiveness a function of how often it looks. Polling twice as fast then does not average twice as much — it halves the time constant and writes twice as much sample noise into the clock, which is why every attempt to buy accuracy with packets has failed here: the packets were spent on twitchiness, not precision.

With an absolute time constant, a faster poll delivers what it should — more samples inside the same correction window.

Off by default: measured, and it does not deliver. The diagnosis is sound — an absolute time constant plus chrony’s packet rate is the only pairing that could turn per-packet parity into raw-accuracy advantage, and neither half can show it alone. Paired against chrony, forty seeded worlds each:

               S1      S2      S4      S6      S8     poll
base        -0.63   +0.63   -1.90   -2.21   -1.26    ~40 s
t=200       -0.95   +0.63   -0.63   -3.48   -1.26    ~38 s
t=120,k8    +0.32   +1.90   -0.32   -2.21   -1.90    ~32 s
t=200,k8    +0.32   +0.63   -0.32   -2.53   -2.85    ~31 s

Nothing resolves ahead anywhere, and S6 stays resolved behind in every arm. The absolute constant also destabilises the poll adaptation — S2 fell to a 21 s poll, spending a third more packets for no gain — because the stability test that raises the interval is calibrated against a correction time that now no longer moves with it.

§adaptive_window: bool

Choose the regression window length from the data. See SampleRegister::set_adaptive_window.

§corr_time_max_s: f64

Longest a steady-state correction may be spread over, in seconds.

The correction time is corr_time_ratio * poll, and the standing offset of a proportional loop is F_residual * correction_time — so tying it to the poll makes the error grow with the poll interval. At a 64 s ceiling that is microseconds. At the DEFAULT 1024 s ceiling it is milliseconds, which is how a corpus measured entirely at maxpoll 6 reported parity with chrony while the shipped configuration was 145x worse.

Capping it decouples the two. Below the cap nothing changes, so every short-poll result stands; above it the loop stops spreading a correction over a quarter of an hour merely because that is how often it looks.

§leap_mode: LeapMode

How to treat a second announced by the upstream source.

§max_change_s: Option<f64>

Largest correction this daemon will ever make, in seconds. None applies no limit.

chrony’s maxchange, and off by default exactly as chrony’s is — because the right value is a policy question about the deployment, not something a library can guess. A machine with a dead RTC legitimately needs to move its clock by years on first sync; a mesh node that has been up for a week does not, and a source asking it to should be refused rather than obeyed.

§max_change_start: u32

Updates to allow before the limit applies, so a cold start can make the one large correction it genuinely needs.

§max_change_ignore: i32

How many refusals to tolerate before giving up. Negative never gives up.

Giving up is the point. A daemon that refuses corrections forever and says nothing is a daemon whose clock is quietly wrong — the operator needs to find out, and an exit is how a service says so.

§corr_time_ratio: f64

Poll intervals over which a steady-state offset is drained. Overrides CORR_TIME_RATIO when > 0.

Poll-SCALED on purpose. An absolute constant measured well on the corpus and is unsafe to ship: the rig runs maxpoll 6 (64 s) while the production default is maxpoll 10 (1024 s), where a fixed 40 s correction time would drain each estimate twenty-five times faster than the loop can see, chasing jitter instead of averaging it.

Trait Implementations§

Source§

impl Clone for DisciplineConfig

Source§

fn clone(&self) -> DisciplineConfig

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for DisciplineConfig

Source§

impl Debug for DisciplineConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for DisciplineConfig

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl PartialEq for DisciplineConfig

Source§

fn eq(&self, other: &DisciplineConfig) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for DisciplineConfig

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.