astrid-runtime/astrid · error

throughput sample count must be non-zero

Error message

throughput sample count must be non-zero

What it means

samples() runs a candidate chunker `count` times over the fixture to collect timing samples. A count of zero would produce no durations and no throughput data, so the function rejects it up front rather than returning an empty or NaN timing result.

Solutions

  1. Pass a positive sample count (e.g. >= 3) when calling samples()/measure()
  2. Clamp or validate the configured count before the call: if count == 0 { count = DEFAULT_SAMPLES; }
  3. Reject zero/negative sample counts at config parsing time with a clear message

Example fix

// before
let timing = measure(&candidate, &fixture, true, cfg.samples)?;
// after
let count = cfg.samples.max(1);
let timing = measure(&candidate, &fixture, true, count)?;
Defensive patterns

Strategy: validation

Validate before calling

// rust
let count = configured_samples.unwrap_or(DEFAULT_SAMPLES);
anyhow::ensure!(count > 0, "throughput sample count must be positive, got {count}");

Prevention

When it happens

Trigger: Calling samples() (directly or via measure) with count: 0, e.g. a CLI/config option like --samples 0 or a computed sample count that evaluated to zero.

Common situations: Passing 0 samples via command-line flag or config file; computing sample count from an env var or default that resolves to 0; integer division truncating a configured duration into 0 samples.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/3075c63394908c8e. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-storage-chunker-evidence/src/throughput.rs:51

    let chunk_only = samples(candidate, &fixture, false, DEFAULT_SAMPLES)?;
    let chunk_and_blake3 = samples(candidate, &fixture, true, DEFAULT_SAMPLES)?;
    Ok(ThroughputResult {
        candidate: candidate.name.clone(),
        fixture_bytes: u64::try_from(fixture.len())?,
        samples: u64::try_from(DEFAULT_SAMPLES)?,
        chunk_only,
        chunk_and_blake3,
    })
}

fn samples(
    candidate: &Candidate,
    fixture: &[u8],
    hash_chunks: bool,
    count: usize,
) -> Result<Timing> {
    if count == 0 {
        bail!("throughput sample count must be non-zero");
    }
    let mut durations = Vec::with_capacity(count);
    for _ in 0..count {
        let started = Instant::now();
        let mut guard = [0_u8; 32];
        candidate.visit_records(Cursor::new(fixture), |bytes, logical_chunks| {
            if hash_chunks {
                fold_digest(&mut guard, blake3::hash(bytes).as_bytes());
            } else {
                guard[0] ^= bytes.first().copied().unwrap_or_default();
                guard[1] ^= u8::try_from(logical_chunks & 0xff)?;
            }
            Ok(())
        })?;
        black_box(guard);
        durations.push(started.elapsed());
    }
    durations.sort_unstable();

View on GitHub (pinned to affd8760f4)