liminal_server/console/readings.rs
1//! The console's readings (SRV-015 R1): numbers the map can be wired to, on
2//! their own subject, `console.readings`.
3//!
4//! A source is a named kind of number about one kind of target (a device, a
5//! station or a line), offered by the server or by a participant. A reading
6//! is one source's number for one target. Every `console.readings` record is
7//! the whole current set, so, as on `console.topology`, a reader's snapshot
8//! is the subject's latest record; `console.topology` itself never carries a
9//! reading (SRV-015 R1 and R4, amended 7 Oct 14:30).
10//!
11//! No source is required. A source nothing measures is absent, and an absent
12//! reading means no reading, never zero (Tom 7 Oct 13:56: "we don't want to
13//! sort of require like CPU and temperature").
14
15use serde::{Serialize, Serializer};
16
17use super::records::RECORD_VERSION;
18
19/// The subject the readings records go out on.
20pub const READINGS_SUBJECT: &str = "console.readings";
21
22/// What a source's readings are about.
23#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
24#[serde(rename_all = "lowercase")]
25pub enum TargetKind {
26 /// A device: this one or a peer.
27 Device,
28 /// A station.
29 Station,
30 /// A line.
31 Line,
32}
33
34/// Who offers a source: the server, from what it measures itself, or the
35/// participant that first published it.
36#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
37pub enum Origin {
38 /// The server's own measure, sent as `"server"`.
39 Server,
40 /// A participant's, sent as its id.
41 Participant(String),
42}
43
44impl Serialize for Origin {
45 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
46 match self {
47 Self::Server => serializer.serialize_str("server"),
48 Self::Participant(id) => serializer.serialize_str(id),
49 }
50 }
51}
52
53/// A reading's number: always finite, with one zero, so two readings compare
54/// equal exactly when they are the same number.
55#[derive(Clone, Copy, Debug)]
56pub struct ReadingValue(f64);
57
58impl ReadingValue {
59 /// The value, or `None` for NaN or an infinity. Minus zero is taken as
60 /// zero.
61 #[must_use]
62 pub fn new(value: f64) -> Option<Self> {
63 // adding zero turns minus zero into zero and leaves every other value as it was
64 value.is_finite().then_some(Self(value + 0.0))
65 }
66
67 /// The number.
68 #[must_use]
69 pub const fn get(self) -> f64 {
70 self.0
71 }
72}
73
74impl PartialEq for ReadingValue {
75 fn eq(&self, other: &Self) -> bool {
76 self.0.to_bits() == other.0.to_bits()
77 }
78}
79
80impl Eq for ReadingValue {}
81
82impl Serialize for ReadingValue {
83 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
84 serializer.serialize_f64(self.0)
85 }
86}
87
88/// A named kind of number about one kind of target.
89#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
90pub struct Source {
91 /// Dotted and lower case: `line.rate`, `worker.backlog`.
92 pub id: String,
93 /// How the console names it.
94 pub label: String,
95 /// Its unit: `msg/s`, `msg`, `ms`.
96 pub unit: String,
97 /// What its readings are about.
98 pub target: TargetKind,
99 /// Who offers it.
100 pub from: Origin,
101 /// Its range, low then high, when the source knows one.
102 #[serde(skip_serializing_if = "Option::is_none")]
103 pub range: Option<[ReadingValue; 2]>,
104}
105
106/// One source's number for one target.
107#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
108pub struct Reading {
109 /// The source's id.
110 pub source: String,
111 /// The id of the device, station or line it is about.
112 pub target: String,
113 /// The number.
114 pub value: ReadingValue,
115 /// When it was read, in wall-clock milliseconds since the epoch.
116 pub at: u64,
117}
118
119/// One `console.readings` record: every source and reading as they stand.
120#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
121pub struct ReadingsRecord {
122 v: u64,
123 #[serde(rename = "type")]
124 record_type: &'static str,
125 /// One more than the previous readings record's, from 1 at start.
126 pub rev: u64,
127 /// The `console.topology` document's rev the targets were read against.
128 pub topology_rev: u64,
129 /// Every source offered now.
130 pub sources: Vec<Source>,
131 /// Every reading held now.
132 pub readings: Vec<Reading>,
133}
134
135impl ReadingsRecord {
136 /// A record at `rev`, read against topology document `topology_rev`.
137 #[must_use]
138 pub const fn new(
139 rev: u64,
140 topology_rev: u64,
141 sources: Vec<Source>,
142 readings: Vec<Reading>,
143 ) -> Self {
144 Self {
145 v: RECORD_VERSION,
146 record_type: "readings",
147 rev,
148 topology_rev,
149 sources,
150 readings,
151 }
152 }
153}