Skip to main content

MetricsSnapshot

Struct MetricsSnapshot 

Source
#[non_exhaustive]
pub struct MetricsSnapshot { pub turns: u64, pub depth_samples: u64, pub high_depth_mean: f64, pub high_depth_max: u64, pub low_depth_mean: f64, pub low_depth_max: u64, pub longest: Option<(CommandKind, Duration)>, pub low_starved_turns: u64, pub low_starved_run_max: u64, pub kinds: Vec<KindSnapshot>, }
Expand description

What the actor has done since the database was opened.

Fields (Non-exhaustive)§

This struct is marked as non-exhaustive
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.
§turns: u64

Commands executed, i.e. the sum of KindSnapshot::turns. The two agree by construction rather than by coincidence.

§depth_samples: u64

Loop iterations that took a queue-depth reading. Always at least turns + 1 on a live actor, because the reading is taken on the way in to a select! that has not resolved yet. This is the denominator of the two means below, and it is exposed so the difference is visible rather than looking like drift.

§high_depth_mean: f64§high_depth_max: u64§low_depth_mean: f64§low_depth_max: u64§longest: Option<(CommandKind, Duration)>

The longest hold since open and what caused it. None before the first turn, and — honestly — also when every turn so far took under a microsecond, which on this path does not happen.

§low_starved_turns: u64

Turns spent on high-priority work while low-priority work was already queued (0.12.10, W4.4, D-153).

The actor’s select! is biased and has no floor, so this is the measurement of a bound the design has always had and never observed. On its own it is not alarming: a busy database should prefer interactive writes, and this counter rising is that working. Read it beside Self::low_starved_run_max, which is the number with teeth.

§low_starved_run_max: u64

The longest unbroken run of the above — i.e. the most turns any single low-priority command has waited (0.12.10, W4.4, D-153).

This is the one that answers “can low-priority work be starved”. A large low_starved_turns spread over a long session says the tiers are doing their job; a large run says one specific chunk, rebuild or archive sat behind that many interactive writes in a row.

§There is deliberately no forced-yield policy, and the reason changed

(0.13.26, W10.4, D-199)

It used to be “adding one now would be fixing a bound nobody has observed being hit”. That premise died twice. D-153 hit the bound completely on a synthetic burst, and W10.4 then hit it on an ordinary one: four closed-loop writers — each awaiting its own write before issuing the next, which is what application code does — starve the low tier for essentially all of their writes (examples/fairness_probe.rs). The run is bounded by how long the caller keeps offering interactive work, not by concurrency and not by anything in this crate.

What replaced it is the floor’s own price. “After N starved turns, take one low-priority command” cannot choose which command — the low queue is an mpsc channel and its head is not inspectable — and at least one low-priority kind is exempt from crate::CHUNK_BUDGET by contract: an crate::Database::archive was measured at 3.3 s unwindowed on an 8,000-key backlog. So the floor would add an unbounded term to the interactive worst case in order to unblock work that is declared not to be latency-sensitive, which is the tier split running backwards.

The lever that does work belongs to the caller: 1 ms of think time between a writer’s writes takes four writers from ~78 to ~2. Which makes this field the instrument for a decision the caller owns rather than a defect report about the actor.

§kinds: Vec<KindSnapshot>

Implementations§

Source§

impl MetricsSnapshot

Source

pub fn budget_violations(&self) -> Vec<&KindSnapshot>

Kinds that broke the budget, worst first. The one-line answer to “is the 3 ms bound holding?”.

Source

pub fn exempt_costs(&self) -> Vec<&KindSnapshot>

What the budget-exempt kinds actually cost, longest first — the companion to Self::budget_violations and the only report that reaches them (0.15.28, D-271, review A-5).

§The data was always collected; the report dropped it

A-5 reads the exemption as making these operations invisible. That is not quite where the gap was, and the difference decides the fix. record_hold writes turns, total, KindSnapshot::longest and the full histogram for every kind; the one thing it skips for an exempt one is the KindSnapshot::over_budget counter (CommandKind::exempt_from_budget). So the cost was measured all along, and then budget_violations() — the method every dashboard reaches for — filtered on over_budget > 0, which for an exempt kind is zero by construction. The collection path was fine and the reporting path threw the numbers away, permanently and silently, for exactly the operations whose cost nobody bounds.

That set is not small: an crate::Database::archive was measured at 3.3 s unwindowed on an 8,000-key backlog (D-199), and rebuild_current at 318 ms on 40K rows (D-077). Those are the holds an operator most needs to see, and they were the ones with no way to be seen.

§Sorted by longest, and that is the whole design decision

Self::budget_violations sorts by over_budget because that is its severity axis. Here it is a column of zeros, so sorting by it would order the result by nothing at all. longest is the analogous question — which exempt operation held the lock longest — and it is the field D-233 already established survives an exemption: over_budget counts occurrences, so a kind whose hold doubled reports the same count and a different longest. Read longest beside KindSnapshot::buckets, which says whether that was the shape or one bad turn.

§This is not a gate, and that is D-055 rather than an omission

Nothing asserts a bound on these numbers and perf_claim_tests gains no assertion here. They are seen, not enforced — a threshold on a kind that is exempt by contract would re-impose, in a test, the bound the exemption exists to lift.

Kinds with no turns are omitted, as budget_violations omits kinds with no violations: a row of zeros for an operation the caller has never invoked is noise in a report meant to be read at a glance.

Trait Implementations§

Source§

impl Clone for MetricsSnapshot

Source§

fn clone(&self) -> MetricsSnapshot

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 MetricsSnapshot

Source§

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

Formats the value using the given formatter. Read more
Source§

impl PartialEq for MetricsSnapshot

Source§

fn eq(&self, other: &MetricsSnapshot) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for MetricsSnapshot

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<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

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

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<T> Instrument for T

Source§

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

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

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> IntoRequest<T> for T

Source§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

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

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

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

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

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

fn with_current_subscriber(self) -> WithDispatch<Self>

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