Skip to main content

dinio_sdk/models/
stats.rs

1//! Statistics from Relay telemetry ([`Stats`](crate::api::Stats)). Relay reports in
2//! 10-minute windows. Unlike the rest of the API, every time here is in **milliseconds**
3//! since the epoch; only `step` is in seconds.
4
5use std::collections::BTreeMap;
6
7use serde::{Deserialize, Serialize};
8
9/// A chart-ready set of series over one time range (zone queries, client events and
10/// rcodes).
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub struct StatsSeriesResult {
13    /// Start of the range, ms, aligned to 10 minutes.
14    pub from: i64,
15    /// End of the range, ms, exclusive.
16    pub to: i64,
17    /// Step length in seconds (600 = one Relay window).
18    pub step: u32,
19    /// What point values are: `count` per step, `rate` per second, or `share` of all
20    /// series at that step (0–1).
21    pub mode: String,
22    /// Sum of every series over the range (a count, whatever the mode).
23    pub total: f64,
24    /// `total` per second of the range.
25    pub rate_per_s: f64,
26    /// With `compare`: `total` of the preceding range of equal length.
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub previous_total: Option<f64>,
29    /// With `compare`: `(total - previous_total) / previous_total` (`0.25` = +25 %);
30    /// `None` also when `previous_total` is 0.
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub change: Option<f64>,
33    /// Busiest first. Beyond `limit` the rest is merged into key `__other`.
34    pub series: Vec<StatsSeries>,
35}
36
37/// One series of a [`StatsSeriesResult`].
38#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
39pub struct StatsSeries {
40    /// Raw key, e.g. rcode `0`, a qtype, a client identity uuid; `__other` for the merged
41    /// rest, `__nxdomain` for NXDOMAIN in a zone's qtype/geo/name grouping.
42    pub key: String,
43    /// Readable name, e.g. `NOERROR`, `AAAA`, the client's `identify`.
44    pub label: String,
45    /// Sum over the range (a count, whatever the mode).
46    pub total: f64,
47    /// Part of the result's `total` (0–1).
48    pub share: f64,
49    /// `total` per second of the range.
50    pub rate_per_s: f64,
51    /// The busiest step; `None` without data.
52    #[serde(default)]
53    pub peak: Option<StatsPeak>,
54    /// With `compare`, as on the result.
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub previous_total: Option<f64>,
57    /// With `compare`, as on the result.
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub change: Option<f64>,
60    /// `(t, value)` per step: `t` the start of the step in ms, `value` per `mode`. A step
61    /// without data is `0` (with `fill`, the default) or absent.
62    pub points: Vec<(i64, f64)>,
63}
64
65/// The busiest step of a [`StatsSeries`].
66#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
67pub struct StatsPeak {
68    /// Start of the step, ms.
69    pub t: i64,
70    /// Count in that step.
71    pub v: f64,
72}
73
74/// NXDOMAIN answers under a zone ([`Stats::zone_nx`](crate::api::Stats::zone_nx)).
75#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
76pub struct ZoneNxStats {
77    /// NXDOMAIN answers over time (one series, `all`).
78    pub count: StatsSeriesResult,
79    /// `(t ms, n)`: distinct non-existent names per step, the highest per-Relay estimate,
80    /// so a lower bound. Never add these up.
81    pub unique_max: Vec<(i64, f64)>,
82}
83
84/// A top list (non-existent names of a zone, names blocked for clients).
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub struct StatsTopList {
87    /// ms.
88    pub from: i64,
89    /// ms, exclusive.
90    pub to: i64,
91    /// Most frequent first.
92    pub items: Vec<StatsTopItem>,
93}
94
95/// One entry of a [`StatsTopList`]. Counts are estimates summed over Relays and windows.
96#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
97pub struct StatsTopItem {
98    /// The name.
99    pub key: String,
100    /// Upper bound of the count.
101    pub count: f64,
102    /// Lower bound of the count.
103    pub count_min: f64,
104    /// How many Relays reported it.
105    pub relays: u32,
106}
107
108/// A zone's busiest names ([`Stats::zone_names`](crate::api::Stats::zone_names)).
109#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
110pub struct ZoneNamesStats {
111    /// ms.
112    pub from: i64,
113    /// ms, exclusive.
114    pub to: i64,
115    /// Most queried first.
116    pub items: Vec<ZoneNameStats>,
117}
118
119/// One name of [`ZoneNamesStats`].
120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
121pub struct ZoneNameStats {
122    /// The queried name.
123    pub name: String,
124    /// Queries over the range.
125    pub queries: f64,
126    /// Queries by rcode label (`NOERROR`, `SERVFAIL`, …).
127    pub rcodes: BTreeMap<String, f64>,
128    /// Queries by qtype label (`A`, `AAAA`, …).
129    pub qtypes: BTreeMap<String, f64>,
130    /// Part of the queries answered with anything but NOERROR (0–1).
131    pub error_share: f64,
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn series_result_deserializes() {
140        let result: StatsSeriesResult = serde_json::from_value(serde_json::json!({
141            "from": 1_793_088_000_000_i64, "to": 1_793_174_400_000_i64, "step": 3600,
142            "mode": "count", "total": 120, "rate_per_s": 0.0014,
143            "previous_total": 0, "change": null,
144            "series": [{
145                "key": "0", "label": "NOERROR", "total": 120, "share": 1, "rate_per_s": 0.0014,
146                "peak": { "t": 1_793_088_000_000_i64, "v": 40 },
147                "points": [[1_793_088_000_000_i64, 40], [1_793_091_600_000_i64, 0]]
148            }, {
149                "key": "__other", "label": "Other", "total": 0, "share": 0, "rate_per_s": 0,
150                "peak": null, "points": []
151            }]
152        }))
153        .unwrap();
154        assert_eq!(result.change, None);
155        assert_eq!(result.series[0].points[0], (1_793_088_000_000, 40.0));
156        assert_eq!(result.series[1].peak, None);
157    }
158}