DiskThrottle

Struct DiskThrottle 

Source
pub struct DiskThrottle {
    pub iops: Option<NonZeroU64>,
    pub bytes_per_sec: Option<NonZeroU64>,
    pub iops_burst_capacity: Option<NonZeroU64>,
    pub bytes_burst_capacity: Option<NonZeroU64>,
}
Expand description

IO throttle for one disk. Each field caps a separate dimension; None disables that dimension’s throttle. All None = unthrottled (the device runs at host-pread/pwrite speed).

Burst capacity is the token-bucket capacity (peak instantaneous burst the device will absorb before throttling kicks in). Refill rate is the steady-state allowance (iops / bytes_per_sec). When *_burst_capacity is None, the bucket capacity equals the refill rate, giving a 1-second burst — the historical default. Setting a burst capacity larger than the refill rate models a device that tolerates transient spikes (e.g. a 1-second steady rate of 1000 IOPS with a 5000-IOPS burst capacity allows a 5-second-equivalent burst from a full bucket). A burst capacity without a corresponding rate is meaningless (a bucket that never refills); DiskThrottle::validate rejects it.

Throttle exhaustion stalls the request internally and retries via a timer — it is not surfaced to the guest as VIRTIO_BLK_S_IOERR.

§Worked example: cloud-style 1000 IOPS / 10 MiB·s with 5× burst

Model a “1000 IOPS sustained, tolerate a brief unrestricted spike from a quiescent device” disk:

use ktstr::prelude::*;

let disk = DiskConfig::default()
    // Steady-state allowance — bucket refill rate.
    .iops(1_000)
    // Peak burst — bucket capacity. 5× the refill rate (5_000 ops)
    // is the maximum number of unrestricted ops the device will
    // absorb from a full bucket before throttling kicks in.
    .iops_burst_capacity(5_000)
    // Steady-state bandwidth: 10 MiB/s = 10 * 1024 * 1024 bytes/s.
    .bytes_per_sec(10 * 1024 * 1024)
    // Bandwidth burst — 5× the rate, mirroring the iops ratio.
    .bytes_burst_capacity(50 * 1024 * 1024);
disk.throttle.validate().expect("burst >= rate, rate set");

At VM build time the buckets are seeded full (start of the test = “quiescent device”); a burst-friendly workload draws the bucket down at peak rate until empty, then is rate-limited to the refill rate from then on.

The 5_000-op burst capacity is NOT “5 seconds at 1000 IOPS” in any real-time sense — the bucket drains at whatever rate the guest workload submits ops, which is usually much faster than the refill rate. A workload submitting 10_000 IOPS empties the 5_000-op bucket in ~0.5s, after which the device steady-states at the 1000-IOPS refill rate. The “5 seconds” framing only applies as a hypothetical lower bound: a workload submitting exactly the refill rate (1000 IOPS) would never drain the bucket, and a workload submitting 2× the refill rate would drain a 5×-rate bucket over ~5 seconds. Most real workloads drain bursts much faster than that.

§Picking values

  • iops — peak operations the device must sustain. Includes reads, writes, and flushes (each = 1 op).
  • bytes_per_sec — peak bandwidth the device must sustain for read+write data combined. Flushes do not count toward bandwidth.
  • *_burst_capacity — how long a burst from a full bucket should run before throttling kicks in. burst = N * rate gives ~N seconds of unrestricted IO from a quiescent device. Leave None to default to burst = rate (1-second burst, the pre-burst-feature behaviour).

§Constraint summary

Both rules are enforced by DiskThrottle::validate (run by init_virtio_blk and its x86 sibling init_virtio_blk_pci before the backing file is allocated):

  • *_burst_capacity must be >= *_refill_rate when both are set; a capacity below the refill rate would silently cap the steady-state at the lower capacity instead of the configured rate.
  • *_burst_capacity must not be set without its matching refill rate; a one-shot bucket that never refills doesn’t model any useful throttle.

Clearing a refill rate via the builder (iops(0) / bytes_per_sec(0)) auto-clears its matching *_burst_capacity so the second rule never trips on a cleared-rate chain.

Fields§

§iops: Option<NonZeroU64>

Maximum operations per second (1 read = 1 op, 1 write = 1 op, 1 flush = 1 op). Refill rate of the IOPS token bucket.

Type-enforced nonzero: Option<NonZeroU64> makes Some(0) = unlimited impossible to express at the type level. To disable IOPS throttling, use None (or set 0 through the builder, which the builder converts to None).

§bytes_per_sec: Option<NonZeroU64>

Maximum bytes per second across read+write data. Refill rate of the bandwidth token bucket.

Type-enforced nonzero, same reasoning as iops.

§iops_burst_capacity: Option<NonZeroU64>

IOPS bucket capacity (peak burst). When None, capacity equals the iops refill rate (1-second burst). When Some, the value must be >= iops (a capacity below the refill rate would discard refilled tokens immediately and effectively reduce the steady-state rate); DiskThrottle::validate enforces this. Has no effect when iops is None.

Values above i64::MAX are accepted but the TokenBucket seed is clamped to i64::MAX at construction — the effective initial burst is ~9.2 quintillion, immaterial for realistic settings.

§bytes_burst_capacity: Option<NonZeroU64>

Bandwidth bucket capacity (peak burst, in bytes). When None, capacity equals the bytes_per_sec refill rate (1-second burst). When Some, the value must be >= bytes_per_sec. Has no effect when bytes_per_sec is None.

Values above i64::MAX are accepted but the TokenBucket seed is clamped to i64::MAX at construction — the effective initial burst is ~9.2 exabytes, immaterial for realistic settings.

Implementations§

Source§

impl DiskThrottle

Source

pub fn validate(&self) -> Result<(), DiskThrottleValidationError>

Non-panicking validation of throttle/burst consistency.

Rejects burst capacities below their corresponding refill rate. A bucket with capacity below its refill rate cannot hold a full second of refilled tokens, so the effective steady-state rate would silently be the capacity, not the configured rate — a user who sets iops(1000).iops_burst_capacity(500) would expect 1000 IOPS and silently get 500.

A burst capacity set without a corresponding rate is also rejected: a bucket with no refill rate is functionally unbounded one-shot capacity, which does not match any useful throttling model.

Returns DiskThrottleValidationError on failure — a typed enum so callers can pattern-match the failure mode (e.g. route a programmatic recovery via the dimension() accessor) rather than string-matching the rendered message. The Display impl preserves the wording of the prior String-returning shape, including the “, or pass 0 to clear the burst override” remediation hint, so anyhow-bubbled callers that match on the rendered text still work.

Source§

impl DiskThrottle

Source

pub const DEFAULT: Self

Const-evaluable default — all None (unthrottled), matching Default::default. Required so DiskConfig::DEFAULT can be const.

Trait Implementations§

Source§

impl Clone for DiskThrottle

Source§

fn clone(&self) -> DiskThrottle

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for DiskThrottle

Source§

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

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

impl Default for DiskThrottle

Source§

fn default() -> DiskThrottle

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for DiskThrottle

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Hash for DiskThrottle

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for DiskThrottle

Source§

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

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

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

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Serialize for DiskThrottle

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl Copy for DiskThrottle

Source§

impl Eq for DiskThrottle

Source§

impl StructuralPartialEq for DiskThrottle

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

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. 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> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
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 = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

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

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

§

impl<T> MaybeSend for T
where T: Send,

§

impl<T> MaybeSend for T
where T: Send,