1use kernel::{AxCode, AxError};
11use serde::{Deserialize, Serialize};
12
13pub const BEAT_MIN_MS: u32 = 10;
15pub const BEAT_MAX_MS: u32 = 1_000;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(try_from = "u32", into = "u32")]
23#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
24pub struct BeatMs(u32);
25
26impl BeatMs {
27 pub const DEFAULT: BeatMs = BeatMs(100);
30
31 pub fn new(ms: u32) -> Result<BeatMs, AxError> {
34 if (BEAT_MIN_MS..=BEAT_MAX_MS).contains(&ms) {
35 Ok(BeatMs(ms))
36 } else {
37 Err(AxError::failure(
38 AxCode::InvalidArgs,
39 "set the monitor's beat",
40 format!("{ms} ms"),
41 )
42 .with_recovery(format!(
43 "choose a beat from {BEAT_MIN_MS} to {BEAT_MAX_MS} milliseconds"
44 )))
45 }
46 }
47
48 #[must_use]
49 pub fn ms(self) -> u32 {
50 self.0
51 }
52}
53
54impl TryFrom<u32> for BeatMs {
55 type Error = AxError;
56
57 fn try_from(ms: u32) -> Result<BeatMs, AxError> {
58 BeatMs::new(ms)
59 }
60}
61
62impl From<BeatMs> for u32 {
63 fn from(beat: BeatMs) -> u32 {
64 beat.0
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
70#[serde(rename_all = "snake_case")]
71#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
72pub enum Monitoring {
73 Watch,
75 WatchSummary,
77 Release,
78 Beat(BeatMs),
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum Watched {
86 Everything,
87 Summary,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
93#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
94pub struct Sample {
95 pub core_cpu_permille: u64,
96 pub core_private_bytes: u64,
97 pub core_working_set_bytes: u64,
98 pub core_read_bytes: u64,
99 pub core_written_bytes: u64,
100 pub machine_cpu_permille: u64,
101 pub machine_available_bytes: u64,
102 pub volume_free_bytes: u64,
103 pub ledger_queue_depth: u64,
104 pub durable_lag: u64,
105 pub relay_p50_nanos: u64,
106 pub event_to_screen_p50_nanos: u64,
107 pub queued_runs: u64,
108 pub view_backlog: u64,
111 pub read_nanos: u64,
115 pub beat_ms: u64,
118}
119
120#[cfg(all(test, feature = "server"))]
121#[allow(clippy::unwrap_used, clippy::panic, reason = "test code")]
122mod tests {
123 use crate::frames::ClientFrame;
124 use crate::reception::{BindFace, Keys, SessionState, SessionStep, WelcomeFacts, decide_frame};
125
126 #[test]
130 fn a_beat_in_range_is_a_step_and_one_outside_is_no_frame() {
131 let read = |text: &str| serde_json::from_str::<ClientFrame>(text);
132 let frame = read(r#"{"monitor":{"beat":250}}"#).unwrap();
133 let face = BindFace::Loopback {
134 key: kernel::B3Hash::digest(b"native-key"),
135 };
136 let sessions = crate::reception::Sessions::default();
137 let keys = Keys {
138 face: &face,
139 sessions: &sessions,
140 now: kernel::TimeMs::new(0),
141 };
142 let step = decide_frame(SessionState::Live, frame, &keys, WelcomeFacts::default());
143 let SessionStep::Beat(beat) = step else {
144 panic!("a live session's beat is a step: {step:?}");
145 };
146 assert_eq!(
147 (
148 beat.ms(),
149 read(r#"{"monitor":{"beat":5}}"#).is_err(),
150 read(r#"{"monitor":{"beat":1001}}"#).is_err()
151 ),
152 (250, true, true)
153 );
154 }
155}