Skip to main content

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}