Skip to main content

ProxyRecorder

Struct ProxyRecorder 

Source
pub(crate) struct ProxyRecorder {
    legs: [[DirectionCounters; 2]; 2],
    classes: OnceLock<Vec<ClassCounters>>,
    default_class: ClassCounters,
    unshapeable: ClassCounters,
}
Expand description

Proxy-scoped shaping-counter storage — the storage behind ProxyStats.

One per TransparentProxy, owned by its control plane and handed to each session’s ShapeRecorder as that session is attached, so it lives for as long as the proxy does and no session’s ending takes anything out of it.

Every counter here is charged by the same call that charges the session recorder — ShapeRecorder’s note_* methods forward — which is what makes the two recorders unable to disagree. The data path pays one extra relaxed fetch_add per figure and one Option test, on a path that has already done a write_all.

Fields§

§legs: [[DirectionCounters; 2]; 2]

The four cells of ProxyStats::per_leg: legs[leg][direction], indexed by leg_index then Direction::index.

§classes: OnceLock<Vec<ClassCounters>>

The class rows, installed by the first session to attach with a class to install — see Self::adopt_classes for the two kinds of session that have none and must not win this.

A OnceLock and not a Mutex, because the list must be fixed: a Class::Rule(index) is an index into the class list of the scheduler that produced it, so rows that could be resized under a running session would silently relabel every figure in them. First writer wins; a later session whose class list differs charges Self::default_class, which Self::row does and ProxyStats::classes states.

Empty until the first shaped session, so a proxy that has accepted nothing, or only unshaped sessions, reports no class rows rather than rows invented from a profile nothing ran under.

§default_class: ClassCounters§unshapeable: ClassCounters

Implementations§

Source§

impl ProxyRecorder

Source

pub(crate) fn new() -> Self

A recorder with four all-zero cells and no class rows yet.

Source

fn adopt_classes(&self, names: &[String]) -> bool

Size the class rows from names if nothing has yet, and answer whether the installed rows are names — which is whether a session running that class list may charge them by index.

The comparison is over names and order, the same pair same_classes compares in session.rs and for the same reason: that pair is what makes a Class::Rule(index) mean the same thing to the scheduler that produced it and to the row it is charged to. The same names in a different order would charge every class to another one’s row without a single count going missing.

§An empty list never sizes anything

A list with no rows in it names no row, so letting it win the OnceLock would install an empty row set that nothing can ever match again: every classed session accepted for the rest of the proxy’s life would find rows it did not match and charge Self::default_class, with ProxyStats::classes empty forever. Every figure right, every label gone — which is precisely the relabelling this whole sizing rule exists to prevent, and it would arrive silently and be unrecoverable without restarting the proxy.

The way an empty list used to get here was a ShapeProfile with no classes, which the constructor accepted because it validated each class it was given and had nothing to say about being given none. ShapeProfile::try_new now refuses that outright as ShapeError::NoClasses, so no profile can carry an empty list to this call and the branch below is no longer reachable from any public path.

It stays because of what it costs against what it prevents: two lines and a comparison that is already being made, against a proxy-wide, silent, restart-only failure. Answering false costs a caller nothing in any case — a session with no Class::Rule to charge is unaffected by the flag, and Class::Default and Class::Unshapeable are unaffected by it always.

Source

fn classes(&self) -> &[ClassCounters]

The class rows, or an empty slice before any shaped session attached.

Source

fn arrival(&self, side: ProxySide) -> &DirectionCounters

The cell a unit read on side is charged to.

Source

fn departure(&self, side: ProxySide) -> &DirectionCounters

The cell a unit read on side is charged to when it is written: the other leg, the same direction.

Source

fn row(&self, class: Class, sized: bool) -> &ClassCounters

The row one class’s units are charged to.

sized is whether the charging session’s class list is the one these rows were sized from. When it is not, a Class::Rule index names a row belonging to a different rule, so the unit goes to the default row instead — the same answer ShapeRecorder::row gives an impossible index, and for the stronger of the two reasons: here the index is not impossible, it is plausible and wrong.

Class::Unshapeable is unaffected either way. That row is not named for a rule, so no reconfiguration can make it mean something else.

Source

pub(crate) fn snapshot(&self) -> ProxyStats

Read every counter.

Allocates: one Vec and one String per class row. A reader’s call — the control plane’s — never the data path’s.

ProxyStats::sessions is computed here, from the two arrival cells and the class rows, which is why no writer maintains it.

Source

fn leg_stats(&self, leg: Leg) -> LegStats

One leg’s two cells.

Source

pub(crate) fn reset(&self)

Zero every counter this proxy holds, keeping the class rows and their names.

The rows survive because they are what a Class::Rule(index) means: dropping them would let the next session install a different class list and charge figures under it, which is precisely the relabelling Self::adopt_classes exists to prevent. A reset moves the counters to zero, not the schema.

Trait Implementations§

Source§

impl Debug for ProxyRecorder

Source§

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

Prints the snapshot, not the atomics.

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> 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, 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, !>

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