Skip to main content

StreamClock

Struct StreamClock 

Source
pub(crate) struct StreamClock {
Show 14 fields pub(crate) sample_interval_ns: u64, pub(crate) next_first_sample_unix_ns: u64, pub(crate) sequence: u64, pub(crate) last_batch_samples: usize, pub(crate) run_started: bool, pub(crate) window_start_unix_ns: u128, pub(crate) window_min_error_ns: Option<i128>, pub(crate) previous_window_error_ns: Option<i128>, pub(crate) step_windows: u32, pub(crate) previous_raw_offset_ns: Option<i128>, pub(crate) slewed_since_log_ns: i128, pub(crate) anchor_checked: bool, pub(crate) recent_offsets_ns: VecDeque<i128>, pub(crate) windows_since_log: u32,
}
Expand description

Tracks timestamp continuity for successive LabJack stream batches.

The LabJack stream API returns value batches without per-sample timestamps, so the streamer derives the first timestamp and sequence number for each published batch from the actual stream rate.

The LabJack crystal and the system clock disagree by a few ppm, which added up to about 4-5 s over a 26-day run on MU1. To stop that accumulating, the clock compares each batch’s last-sample time with the system clock and keeps the smallest difference seen in each 60 s window.

That minimum is not a clean drift measurement on a busy host. On MU1, with the camera software loading the CPU, read latency wandered by tens to hundreds of milliseconds from one minute to the next. An earlier version jumped the timeline by the full window minimum and so chased that noise, stepping the timestamps by up to 430 ms every minute. The clock therefore never jumps for ordinary offsets. It slews:

  • The offset used for slewing is the smallest window minimum over the last 15 windows, because on a busy host a whole minute can pass without a fast read.
  • An offset of at least 5 ms that points the same way in two consecutive windows moves the timeline by at most 1 ms (less at high sample rates, see StreamClock::max_slew_ns). Genuine drift is steady and gets followed; latency noise changes sign or size and mostly does not.
  • Only an offset of 2 s or more that persists for three windows, which is what a system clock step looks like (for example chronyd correcting a badly set clock), moves the timeline in one jump. That jump is logged.

The first batch of a run is anchored to the system clock at the moment it arrives, so whatever delay that one read had is built into the timeline. At the end of the first window the clock therefore corrects the anchor once, in full, by the smallest offset seen in that window, and logs it. After that only the rules above apply.

Slewing is summarized in the log about once an hour.

A new clock is created for every sampling run, so sequence numbers restart at 0 and the first batch of each run is anchored to the system clock again.

Fields§

§sample_interval_ns: u64

Time between consecutive scans, in nanoseconds, from the actual scan rate.

§next_first_sample_unix_ns: u64

Expected Unix timestamp (ns) of the first sample of the next batch.

§sequence: u64

Sequence number that the next batch will get.

§last_batch_samples: usize

Samples per channel in the most recent batch. Recorded but not read.

§run_started: bool

Whether the first batch has been timestamped.

§window_start_unix_ns: u128

System time (Unix ns) at which the current drift window started.

§window_min_error_ns: Option<i128>

Smallest receive-time minus last-sample-time seen in the current window, in nanoseconds. None until the window has a measurement.

§previous_window_error_ns: Option<i128>

Window minimum of the previous window, in nanoseconds.

§step_windows: u32

Consecutive windows whose offset was at least CLOCK_STEP_THRESHOLD_NS in the same direction.

§previous_raw_offset_ns: Option<i128>

This window’s own minimum from the previous window, for step detection.

§slewed_since_log_ns: i128

Total slew applied since the last summary log line, in nanoseconds.

§anchor_checked: bool

Whether the first window has ended and the initial anchor was checked.

§recent_offsets_ns: VecDeque<i128>

Window minima of the most recent windows, oldest first, at most CLOCK_OFFSET_HISTORY_WINDOWS.

§windows_since_log: u32

Windows completed since the last summary log line.

Implementations§

Source§

impl StreamClock

Source

pub(crate) fn new(sample_interval_ns: u64) -> Self

Creates a stream clock that has not yet seen a batch.

§Arguments
Source

pub(crate) fn max_slew_ns(&self) -> i128

Returns the largest slew allowed per window, in nanoseconds.

The smaller of CLOCK_MAX_SLEW_NS and half the sample interval, so that even a backward slew leaves consecutive timestamps strictly increasing.

Source

pub(crate) fn next_batch( &mut self, batch_samples: usize, ) -> Result<(u64, u64), LJMError>

Returns the first sample timestamp and sequence for the next batch.

Reads the system clock and calls Self::next_batch_at. The first batch is anchored so that its last sample falls at the current wall-clock time. Each later batch starts where the previous one ended, with the drift handling described in the type docs.

§Arguments
  • batch_samples - Samples per channel in the batch (scans in the batch).
§Returns

The Unix timestamp of the batch’s first sample in nanoseconds, and the batch sequence number.

§Errors

Returns an error if batch_samples is zero, if the system clock is before the Unix epoch, or if a timestamp overflows u64.

Source

pub(crate) fn next_batch_at( &mut self, batch_samples: usize, now_ns: u64, ) -> Result<(u64, u64), LJMError>

Self::next_batch with an explicit receive time, for testing.

For every batch after the first, it measures the receive time minus the expected time of the batch’s last sample and keeps the window minimum. When the window has lasted CLOCK_CHECK_WINDOW_NS, decide_adjustment chooses whether to slew or step, the adjustment is added to this batch’s first-sample time, and later batches continue from there. The window then restarts at now_ns.

§Arguments
  • batch_samples - Samples per channel in the batch.
  • now_ns - Receive time of the batch, Unix nanoseconds.
§Returns

The Unix timestamp of the batch’s first sample in nanoseconds, and the batch sequence number.

§Errors

Returns an error if batch_samples is zero or if the initial, corrected, or next timestamp does not fit in u64.

Trait Implementations§

Source§

impl Clone for StreamClock

Source§

fn clone(&self) -> StreamClock

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 Debug for StreamClock

Source§

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

Formats the value using the given formatter. Read more

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
§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
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> Same for T

Source§

type Output = T

Should always be Self
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 = Infallible

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.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more