engine

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 29, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

Documentation

Overview

Package engine holds the pure, clock-free measurement logic of netquality: latency statistics, the draft's stability criterion, and (after extraction) the per-interval decisions. It performs no I/O so it can be driven by real transports or by a simulator.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Responsiveness

func Responsiveness(foreign, self []LatencySample, tmp float64) (total, foreignRPM, selfRPM float64)

Responsiveness computes the draft's RPM figures over a sample set.

Types

type Confidence

type Confidence string

Confidence is the draft's Section 5.4.1 confidence score.

const (
	// ConfidenceLow: fewer than MAD intervals ran; moving average is partial.
	ConfidenceLow Confidence = "low"
	// ConfidenceMedium: at least MAD intervals ran but stability was not reached.
	ConfidenceMedium Confidence = "medium"
	// ConfidenceHigh: stability was reached.
	ConfidenceHigh Confidence = "high"
)

type Decision

type Decision struct {
	// AddFlows is how many load-generating connections to open now.
	AddFlows int
	// Stop is true when both series are stable and the phase should end.
	Stop bool
	// Interval-level figures for progress reporting.
	Interval                               int
	ThroughputBPS                          float64 // moving-average goodput
	RPM                                    float64 // 0 until the ramp is done or throughput is stable
	ThroughputStable, ResponsivenessStable bool
	// Hold is true when no flow was added into this interval and it was not
	// a drain interval.
	Hold bool
	// Drain is true when the interval was excluded from measurement because
	// the send-buffer credit of newly opened flows would inflate it.
	Drain bool
}

Decision is what the engine asks the I/O layer to do after an observation.

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine implements the draft's per-interval algorithm without any I/O or clock: feed it one Observation per interval and act on the Decision. It is safe for concurrent use of ProbeGap with Interval.

Deviations from the draft's ramp: flows are added in doubling steps and the ramp stops when a step adds less than RampGainTolerance of goodput; an interval inflated by send-buffer credit is excluded (see StabilityParams); a goodput drop beyond ChangeTolerance restarts goodput tracking.

func New

func New(p StabilityParams, maxFlows int) *Engine

New returns an engine for one load phase.

func (*Engine) InitialFlows

func (e *Engine) InitialFlows() int

InitialFlows is how many connections to open before the first interval.

func (*Engine) Interval

func (e *Engine) Interval(o Observation) Decision

Interval processes one completed interval and returns what to do next.

func (*Engine) ProbeGap

func (e *Engine) ProbeGap(foreignBytes, selfBytes int) time.Duration

ProbeGap is the spacing between individual probes: 1/MPS, stretched so that probe traffic stays under PTC of the current goodput estimate.

func (*Engine) Stopped

func (e *Engine) Stopped() bool

Stopped reports whether Interval has returned Stop.

func (*Engine) Summary

func (e *Engine) Summary(currentForeign, currentSelf []LatencySample) Summary

Summary computes the final figures. current holds samples of an interval that was in progress when the phase ended; they are used only when no interval completed at all.

type LatencySample

type LatencySample struct {
	Total   time.Duration // full wall time of the probe (foreign: incl. connect+TLS; self: request only)
	DNS     time.Duration
	Connect time.Duration // TCP handshake
	TLS     time.Duration // raw TLS handshake time
	TLSRTTs int           // number of round trips the negotiated TLS version needs (0 = no TLS)
	TTFB    time.Duration // request sent -> first response byte
	HTTP    time.Duration // request sent -> full body read (http_f / http_l in the draft)
	Staged  bool          // true when DNS/Connect/TLS/TTFB are populated (fresh connection)
}

LatencySample is one probe measurement.

func (LatencySample) TLSPerRTT

func (s LatencySample) TLSPerRTT() time.Duration

TLSPerRTT returns the TLS handshake time normalised to one round trip.

type LatencyStats

type LatencyStats struct {
	Samples int           `json:"samples"`
	Min     time.Duration `json:"min_ns"`
	Median  time.Duration `json:"median_ns"`
	Mean    time.Duration `json:"mean_ns"`
	P80     time.Duration `json:"p80_ns,omitempty"`
	P90     time.Duration `json:"p90_ns,omitempty"`
	P95     time.Duration `json:"p95_ns,omitempty"`
	P99     time.Duration `json:"p99_ns,omitempty"`
	Max     time.Duration `json:"max_ns"`
	Jitter  time.Duration `json:"jitter_ns"`
	Stages  *StageMedians `json:"stages,omitempty"`
}

LatencyStats summarises a set of latency samples.

Percentiles use the nearest-rank method, which returns the maximum for any percentile above 100·(n-1)/n. A percentile field is therefore present only when the sample count makes it a real order statistic distinct from the maximum: P80 from 5 samples, P90 from 10, P95 from 20, P99 from 100. A field never holds a lower percentile than its name says; absent means "not enough samples", never zero.

Jitter is the mean absolute deviation of the samples from their mean. Stages holds per-stage medians (dns, connect, tls, ttfb) when the samples carry stage timings (foreign probes and idle probes do; self probes do not).

func ComputeLatencyStats

func ComputeLatencyStats(samples []LatencySample) LatencyStats

ComputeLatencyStats builds LatencyStats from samples, using Total as the headline value. Stage medians are computed only over staged samples.

func (LatencyStats) HighestPercentile

func (s LatencyStats) HighestPercentile() (float64, time.Duration)

HighestPercentile returns the largest percentile present and its value, or (0, 0) when the set is too small for any.

type Observation

type Observation struct {
	// Elapsed is the wall time since the previous observation (or phase start).
	Elapsed time.Duration
	// Bytes is the cumulative byte count of the phase so far.
	Bytes int64
	// Flows is the number of load-generating connections currently open.
	Flows int
	// Foreign and Self are the probe samples completed during this interval.
	Foreign, Self []LatencySample
}

Observation is what the I/O layer reports to the engine once per interval.

type StabilityParams

type StabilityParams struct {
	// MovingAverageDistance (MAD): number of intervals in the moving average.
	MovingAverageDistance int
	// Interval (ID): how often stability is re-evaluated and flows are added.
	Interval time.Duration
	// TrimmedMeanPercent (TMP): single-sided trimmed-mean percentile for latency.
	TrimmedMeanPercent float64
	// StdDevTolerance (SDT): stability is declared when the standard deviation of
	// the last MAD moving averages is below this fraction of the current one.
	StdDevTolerance float64
	// InitialFlows (INP) and FlowIncrement (INC). The ramp doubles the flow
	// count at each step; FlowIncrement is the floor of a step.
	InitialFlows  int
	FlowIncrement int
	// MaxProbesPerSecond (MPS) and ProbeCapacityPercent (PTC).
	MaxProbesPerSecond   int
	ProbeCapacityPercent float64

	// SendBufferBytes is the per-flow credit the transport accepts before
	// bytes reach the wire (HTTP/2 stream window plus socket buffer). An
	// interval in which the credit of newly opened flows exceeds
	// StdDevTolerance of the interval's goodput is a drain interval: it is
	// neither measured nor used for decisions. 0 (the default) disables it;
	// the I/O layer sets it for uploads, where bytes are counted on hand-over.
	SendBufferBytes int64
	// RampGainTolerance stops the flow ramp: after an add, if goodput grew by
	// less than this fraction the link is saturated and no more flows open.
	// Negative never stops the ramp (flows are added up to the maximum).
	RampGainTolerance float64
	// ChangeTolerance restarts goodput stability tracking when a hold
	// interval's goodput drops by more than this fraction below the moving
	// average (a capacity change mid-run).
	ChangeTolerance float64
}

StabilityParams are the draft's algorithm parameters (Section 5.2).

func DefaultStabilityParams

func DefaultStabilityParams() StabilityParams

DefaultStabilityParams returns the draft-09 defaults, except Interval, which is 1s instead of 5s so that a phase can stabilise within the 12s MaxDuration budget (see README "Deviations").

func (StabilityParams) WithDefaults

func (p StabilityParams) WithDefaults() StabilityParams

type StageMedians

type StageMedians struct {
	DNS       time.Duration `json:"dns_ns"`
	Connect   time.Duration `json:"connect_ns"`
	TLS       time.Duration `json:"tls_ns"`
	TLSPerRTT time.Duration `json:"tls_per_rtt_ns"`
	TTFB      time.Duration `json:"ttfb_ns"`
}

StageMedians holds median per-stage timings from net/http/httptrace. TLS is the raw handshake time; TLSPerRTT is the same value normalised to a single round trip (TLS 1.3 = 1 RTT, TLS 1.2 = 2 RTTs) as the draft requires.

type Summary

type Summary struct {
	Intervals                              int
	ThroughputBPS, PeakThroughputBPS       float64
	ThroughputStable, ResponsivenessStable bool
	ThroughputConfidence                   Confidence
	ResponsivenessConfidence               Confidence
	RPM, ForeignRPM, SelfRPM               float64
	Foreign, Self                          []LatencySample // samples the final figures were computed from
	// LowerBoundBPS is the lowest goodput of the latest sustained window:
	// MovingAverageDistance consecutive measured (non-drain) intervals whose
	// goodputs are within StdDevTolerance of their mean — agreement across a
	// flow add shows the add changed nothing. 0 when no such window formed.
	// LowerBoundStart is the 1-based first interval of the window and
	// LowerBoundIntervals its length.
	LowerBoundBPS       float64
	LowerBoundStart     int
	LowerBoundIntervals int
	// RPMUpperBound is the responsiveness over the lower-bound window. The
	// queue may not have been full, so it bounds the loaded RPM from above.
	RPMUpperBound float64
	// WindowFrom is the 1-based first interval the RPM and the Foreign/Self
	// samples were taken from (through the last interval). PhaseForeign and
	// PhaseSelf count the samples of the whole phase, so a caller can tell a
	// series that never produced a sample from one whose samples all fell
	// outside the window.
	WindowFrom              int
	PhaseForeign, PhaseSelf int
}

Summary is the engine's final view of a phase; the I/O layer adds bytes, duration, protocol and error facts.

type Tracker

type Tracker struct {
	// contains filtered or unexported fields
}

Tracker implements the draft's moving-average stability criterion for a single series (goodput or responsiveness). It is deterministic and independent of wall time: callers push one value per interval.

func NewTracker

func NewTracker(mad int, tolerance float64) *Tracker

func NewWindowedTracker added in v0.4.0

func NewWindowedTracker(mad int, tolerance float64) *Tracker

NewWindowedTracker returns a tracker for values that are already means over MAD intervals.

func (*Tracker) Confidence

func (t *Tracker) Confidence() Confidence

func (*Tracker) Current

func (t *Tracker) Current() float64

Current returns the most recent moving average.

func (*Tracker) Intervals

func (t *Tracker) Intervals() int

Intervals returns how many values have been pushed.

func (*Tracker) Push

func (t *Tracker) Push(v float64) float64

Push records the value for the current interval and returns the moving average over the last MAD intervals (partial window while warming up).

func (*Tracker) Stable

func (t *Tracker) Stable() bool

Stable reports whether the standard deviation of the last MAD moving averages (or values, for a windowed series) is within tolerance of the current one. Requires at least MAD intervals (draft: "Low" confidence otherwise).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL