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: f64Cap on offset-correction slew rate, ppm.
max_freq_ppm: f64Cap on the absolute frequency correction we will command, ppm.
min_poll: i8log2 seconds.
max_poll: i8§iburst: boolSend an initial burst of quick polls to converge fast (chrony iburst).
freq_integral_gain: f64Gain of the integral trim on the frequency estimate. 0 disables it,
leaving a purely proportional loop. See FREQ_INTEGRAL_GAIN.
poll_down_noise_ratio: f64Step 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.05So 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: u32Consecutive 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: f64Width 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: f64Weight-floor width for the OFFSET alone; the slope keeps
weight_floor_ratio. Equal values reproduce the single-weight fit.
offset_age_halflife_s: f64Half-life, seconds, of the age decay on the OFFSET weights. Infinite disables it, weighting by delay alone.
offset_weight_dispersion_k: f64If > 0, take the offset weight floor from measured delay dispersion rather than a fraction of the minimum delay.
slope_density_weighting: boolWeight 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: f64Absolute 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 sNothing 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: boolChoose the regression window length from the data. See
SampleRegister::set_adaptive_window.
corr_time_max_s: f64Longest 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: LeapModeHow 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: u32Updates to allow before the limit applies, so a cold start can make the one large correction it genuinely needs.
max_change_ignore: i32How 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: f64Poll 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
impl Clone for DisciplineConfig
Source§fn clone(&self) -> DisciplineConfig
fn clone(&self) -> DisciplineConfig
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more