Skip to main content

iqdb_eval/
report.rs

1//! Report types returned by the eval functions.
2//!
3//! The two report types ([`RecallReport`] and [`LatencyReport`]) are plain
4//! data: numeric summaries of one measurement run. Both derive
5//! `serde::Serialize` and `serde::Deserialize` when the crate's `serde`
6//! feature is enabled, mirroring the gating pattern in
7//! [`iqdb_types::Hit`].
8
9/// Summary of a recall@k measurement against a known or computed
10/// ground-truth set.
11///
12/// `mean_recall`, `min_recall`, and `max_recall` are aggregated across the
13/// query set; each per-query recall is `|retrieved_topk ∩ true_topk| / k`
14/// and lies in `[0.0, 1.0]`. Per-query values are not retained because
15/// they grow O(n_queries) and the use cases at this version only need the
16/// summary.
17///
18/// # Examples
19///
20/// ```
21/// use iqdb_eval::RecallReport;
22///
23/// let r = RecallReport {
24///     k: 10,
25///     query_count: 100,
26///     mean_recall: 0.97,
27///     min_recall: 0.80,
28///     max_recall: 1.00,
29/// };
30/// assert!(r.min_recall <= r.mean_recall && r.mean_recall <= r.max_recall);
31/// ```
32#[derive(Debug, Clone, PartialEq)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34pub struct RecallReport {
35    /// The `k` used for the top-`k` queries that produced this report.
36    pub k: usize,
37    /// The number of queries the report was aggregated over.
38    pub query_count: usize,
39    /// Arithmetic mean of per-query recall across the query set.
40    pub mean_recall: f64,
41    /// Smallest per-query recall observed in the query set.
42    pub min_recall: f64,
43    /// Largest per-query recall observed in the query set.
44    pub max_recall: f64,
45}
46
47/// Summary of a per-query latency measurement.
48///
49/// All latency values are reported in **microseconds**. Percentiles use
50/// the **nearest-rank** method: for a sorted (ascending) sample of `n`
51/// values, `p_q` is the value at index `clamp(ceil(q × n) − 1, 0, n − 1)`.
52/// This matches Criterion and `hdrhistogram` defaults — every reported
53/// percentile is an observed latency, never an interpolation.
54///
55/// `qps` is single-threaded throughput derived as
56/// `query_count / sum_of_latencies_seconds`. Warm-up samples are excluded
57/// from every field.
58///
59/// # Examples
60///
61/// ```
62/// use iqdb_eval::LatencyReport;
63///
64/// let r = LatencyReport {
65///     query_count: 1_000,
66///     mean_us: 250.0, min_us: 100.0, max_us: 900.0,
67///     p50_us: 220.0, p95_us: 600.0, p99_us: 850.0,
68///     qps: 4_000.0,
69/// };
70/// assert!(r.p50_us <= r.p95_us);
71/// assert!(r.p95_us <= r.p99_us);
72/// ```
73#[derive(Debug, Clone, PartialEq)]
74#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
75pub struct LatencyReport {
76    /// The number of queries the report was aggregated over (warm-up
77    /// queries are not included).
78    pub query_count: usize,
79    /// Arithmetic mean of per-query latency, in microseconds.
80    pub mean_us: f64,
81    /// Smallest per-query latency observed, in microseconds.
82    pub min_us: f64,
83    /// Largest per-query latency observed, in microseconds.
84    pub max_us: f64,
85    /// 50th-percentile (median) per-query latency, in microseconds.
86    pub p50_us: f64,
87    /// 95th-percentile per-query latency, in microseconds.
88    pub p95_us: f64,
89    /// 99th-percentile per-query latency, in microseconds.
90    pub p99_us: f64,
91    /// Single-threaded throughput: `query_count / total_latency_seconds`.
92    pub qps: f64,
93}