pub struct ShapeStats {
pub classes: Vec<ClassStats>,
pub default_class: ClassStats,
pub unshapeable: ClassStats,
pub objects_seen: u64,
pub bytes_shaped: u64,
pub objects_expired: u64,
pub streams_reset_by_shaping: u64,
pub streams_with_mixed_classes: u64,
pub uplink: DirectionStats,
pub downlink: DirectionStats,
}Expand description
Shaping statistics for one session.
Zero-valued on any session with no ShapeProfile, which is what makes
this session shaped nothing a falsifiable claim rather than a promise.
Reported separately from Counters so that crate’s whole-struct == Counters::default() assertions keep meaning what they mean.
Read through
ProxySession::shape_stats.
The session totals appear twice: flat, summed over the whole session,
and again under Self::uplink and Self::downlink for one leg
each. The flat figure is the sum of the two by construction, so the two
forms can never disagree — pick the leg when the question is which side
stalled, and the aggregate when it is whether the profile ran at all.
Every field here is written. A zero row is still reported, never omitted — a class that saw nothing is present and empty, which is the difference between a starved class and a mis-typed one.
Fields§
§classes: Vec<ClassStats>One entry per configured class, in ShapeProfile::classes order.
default_class: ClassStatsUnits that matched no rule.
unshapeable: ClassStatsUnits with no object metadata at all: subgroup and fetch stream
headers, oversized passthrough objects, bypassed streams — every
fetch stream on drafts 15-19, which have no fetch object codec.
A separate row from Self::default_class, and the distinction is
the point: the default row is the rules saw this unit and none claimed
it, this row is no rule could have seen it. Merging them would make a
mis-aimed matcher indistinguishable from a stream the framer cannot
address.
These bytes are not paced: they charge no bucket, so a class rate can be exceeded by exactly one oversized object.
objects_seen: u64Hook-visible units the classifier saw.
Objects only. A stream header is counted in Self::bytes_shaped
and in Self::unshapeable, and not here.
bytes_shaped: u64Bytes the shaper accounted for — every byte it saw, whether a bucket
granted it, a policy dropped it, or it was unshapeable.
Deliberately not bytes that passed through a bucket: the
unshapeable row never touches a bucket, and the conservation identity
this total exists for — Σ classes(delivered + dropped) + default + unshapeable == bytes_shaped — has to hold across that row too, or bytes
the shaper declined to shape would vanish from the accounting.
objects_expired: u64Objects whose max_hold elapsed under Expiry::ResetStream.
Zero under the default Expiry::Deliver, which has no producer.
streams_reset_by_shaping: u64Destination streams abandoned by an overflow or expiry policy.
streams_with_mixed_classes: u64Streams on which two units resolved to different classes.
Head-gating means such a stream’s throughput is decided by whichever class is at the head, so without this count configured shaping and head-of-line blocking are indistinguishable from outside.
uplink: DirectionStatsThe same totals for the client’s traffic on its way to the relay.
Every flat total above is this plus Self::downlink, term by
term. Read a leg when the question is which side stalled; read the
aggregate when it is did the profile do anything at all. A session
shaping only one leg reports the other as all zeros, which is an
answer rather than an absence.
downlink: DirectionStatsThe same totals for the relay’s traffic on its way to the client.