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 * rategives ~N seconds of unrestricted IO from a quiescent device. LeaveNoneto default toburst = 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_capacitymust be>= *_refill_ratewhen 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_capacitymust 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
impl DiskThrottle
Sourcepub fn validate(&self) -> Result<(), DiskThrottleValidationError>
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
impl DiskThrottle
Sourcepub const DEFAULT: Self
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
impl Clone for DiskThrottle
Source§fn clone(&self) -> DiskThrottle
fn clone(&self) -> DiskThrottle
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for DiskThrottle
impl Debug for DiskThrottle
Source§impl Default for DiskThrottle
impl Default for DiskThrottle
Source§fn default() -> DiskThrottle
fn default() -> DiskThrottle
Source§impl<'de> Deserialize<'de> for DiskThrottle
impl<'de> Deserialize<'de> for DiskThrottle
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl Hash for DiskThrottle
impl Hash for DiskThrottle
Source§impl PartialEq for DiskThrottle
impl PartialEq for DiskThrottle
Source§impl Serialize for DiskThrottle
impl Serialize for DiskThrottle
impl Copy for DiskThrottle
impl Eq for DiskThrottle
impl StructuralPartialEq for DiskThrottle
Auto Trait Implementations§
impl Freeze for DiskThrottle
impl RefUnwindSafe for DiskThrottle
impl Send for DiskThrottle
impl Sync for DiskThrottle
impl Unpin for DiskThrottle
impl UnwindSafe for DiskThrottle
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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