Files
sqm-autorate-rust/src/measurement/model.rs
T
Nils Andreas Svee f0fc761fb3 feat(measurement): integrate IRTT and configurable reflector selection
Add protocol-aware reflector definitions, TOML/UCI lists, validation and environment overrides. Resolve and select endpoint-aware measurement streams, honor configured ICMP timing and socket routing, and retain partial-set reselection behavior from the standalone fixes.

Drive the managed IRTT task throughout its lifecycle, retry terminal targets without restarting healthy sessions, normalize blank HMAC keys and use effective RTT for latency fallback. Fold the reviewed configuration, resolution and measurement corrections into their implementation.
2026-10-14 12:13:14 +07:00

156 lines
3.7 KiB
Rust

use std::fmt::{self, Display};
use std::{
net::IpAddr,
time::{Duration, Instant},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MeasurementStreamId(pub u64);
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MeasurementSource {
IcmpEcho,
IcmpTimestamp,
Irtt,
TcpSeqAck,
TcpTimestamp,
}
impl Display for MeasurementSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MeasurementSource::IcmpEcho => write!(f, "icmp-echo"),
MeasurementSource::IcmpTimestamp => write!(f, "icmp-timestamps"),
MeasurementSource::Irtt => write!(f, "irtt"),
MeasurementSource::TcpSeqAck => write!(f, "tcp-seqack"),
MeasurementSource::TcpTimestamp => write!(f, "tcp-timestamp"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MeasurementStream {
pub id: MeasurementStreamId,
pub peer: IpAddr,
pub source: MeasurementSource,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OneWayClock {
Uncalibrated,
Calibrated,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ProbeSchedule {
pub period: Duration,
pub offset: Duration,
}
impl ProbeSchedule {
pub(crate) fn next_probe_at(&self, origin: Instant, now: Instant) -> Instant {
const NANOS_PER_SEC: u128 = 1_000_000_000;
let first = origin + self.offset;
if now < first {
return first;
}
let elapsed_ns = now.duration_since(first).as_nanos();
let period_ns = self.period.as_nanos();
let remainder = elapsed_ns % period_ns;
let until_next = if remainder == 0 {
period_ns
} else {
period_ns - remainder
};
let until_next = Duration::new(
(until_next / NANOS_PER_SEC) as u64,
(until_next % NANOS_PER_SEC) as u32,
);
now + until_next
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct SignedDuration {
nanos: i64,
}
#[allow(dead_code)]
impl SignedDuration {
pub const fn from_nanos(nanos: i64) -> Self {
Self { nanos }
}
pub const fn from_millis(millis: i64) -> Self {
Self {
nanos: millis * 1_000_000,
}
}
pub const fn as_nanos(self) -> i64 {
self.nanos
}
pub fn as_secs_f64(self) -> f64 {
self.nanos as f64 / 1_000_000_000.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OneWayLatency {
pub uplink: SignedDuration,
pub downlink: SignedDuration,
pub clock: OneWayClock,
}
#[derive(Debug, Clone, Copy)]
pub struct MeasurementObservation {
pub stream: MeasurementStream,
/// Local monotonic time at which measurement started.
pub started_at: Instant,
/// Local monotonic time at which the result was observed.
pub observed_at: Instant,
/// Measured round-trip latency when it differs from elapsed local time.
pub round_trip: Option<SignedDuration>,
/// Directional timing, when the measurement mechanism can provide it.
pub one_way: Option<OneWayLatency>,
}
impl MeasurementObservation {
pub fn rtt(&self) -> Duration {
self.observed_at.duration_since(self.started_at)
}
pub fn rtt_ms(&self) -> f64 {
self.round_trip
.map(SignedDuration::as_secs_f64)
.unwrap_or_else(|| self.rtt().as_secs_f64())
* 1000.0
}
}
#[allow(dead_code)]
pub struct MeasurementLoss {
pub stream: MeasurementStream,
pub started_at: Instant,
pub detected_at: Instant,
}
#[allow(dead_code)]
pub enum MeasurementEvent {
Observation(MeasurementObservation),
Loss(MeasurementLoss),
}