1use std::collections::BTreeMap;
6use std::fmt;
7use std::str::FromStr;
8
9use serde::{Deserialize, Serialize};
10
11use crate::replay::Moment;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
15#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
16#[cfg_attr(feature = "typescript", ts(export_to = "controller-api.ts"))]
17#[serde(rename_all = "kebab-case")]
18pub enum WatchKind {
19 Files,
21 Network,
23 Processes,
25 Memory,
27 Time,
29 Signals,
31 Waits,
33 Other,
35 PageFaults,
37}
38
39impl WatchKind {
40 pub const ALL: [Self; 9] = [
41 Self::Files,
42 Self::Network,
43 Self::Processes,
44 Self::Memory,
45 Self::Time,
46 Self::Signals,
47 Self::Waits,
48 Self::Other,
49 Self::PageFaults,
50 ];
51
52 pub fn name(self) -> &'static str {
54 match self {
55 Self::Files => "files",
56 Self::Network => "network",
57 Self::Processes => "processes",
58 Self::Memory => "memory",
59 Self::Time => "time",
60 Self::Signals => "signals",
61 Self::Waits => "waits",
62 Self::Other => "other",
63 Self::PageFaults => "page-faults",
64 }
65 }
66
67 pub fn label(self) -> &'static str {
69 match self {
70 Self::Files => "Files",
71 Self::Network => "Network",
72 Self::Processes => "Processes",
73 Self::Memory => "Memory",
74 Self::Time => "Time",
75 Self::Signals => "Signals",
76 Self::Waits => "Waits",
77 Self::Other => "Other syscalls",
78 Self::PageFaults => "Page faults",
79 }
80 }
81
82 fn bit(self) -> u16 {
83 1 << self as u16
84 }
85}
86
87impl FromStr for WatchKind {
88 type Err = String;
89
90 fn from_str(name: &str) -> Result<Self, String> {
91 Self::ALL
92 .into_iter()
93 .find(|kind| kind.name() == name)
94 .ok_or_else(|| format!("no kind of event {name:?}"))
95 }
96}
97
98#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
102#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
103#[cfg_attr(feature = "typescript", ts(export_to = "controller-api.ts"))]
104#[serde(from = "Vec<WatchKind>", into = "Vec<WatchKind>")]
105#[cfg_attr(feature = "typescript", ts(as = "Vec<WatchKind>"))]
106pub struct WatchKinds(u16);
107
108impl WatchKinds {
109 pub const NONE: Self = Self(0);
110
111 pub fn with(self, kind: WatchKind) -> Self {
112 Self(self.0 | kind.bit())
113 }
114
115 pub fn without(self, kind: WatchKind) -> Self {
116 Self(self.0 & !kind.bit())
117 }
118
119 pub fn contains(self, kind: WatchKind) -> bool {
120 self.0 & kind.bit() != 0
121 }
122
123 pub fn is_empty(self) -> bool {
124 self.0 == 0
125 }
126
127 pub fn kinds(self) -> impl Iterator<Item = WatchKind> {
129 WatchKind::ALL
130 .into_iter()
131 .filter(move |&kind| self.contains(kind))
132 }
133}
134
135impl From<Vec<WatchKind>> for WatchKinds {
136 fn from(kinds: Vec<WatchKind>) -> Self {
137 kinds.into_iter().fold(Self::NONE, Self::with)
138 }
139}
140
141impl From<WatchKinds> for Vec<WatchKind> {
142 fn from(kinds: WatchKinds) -> Self {
143 kinds.kinds().collect()
144 }
145}
146
147impl fmt::Display for WatchKinds {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 let names: Vec<&str> = self.kinds().map(WatchKind::name).collect();
150 f.write_str(&names.join(","))
151 }
152}
153
154impl FromStr for WatchKinds {
155 type Err = String;
156
157 fn from_str(list: &str) -> Result<Self, String> {
158 list.split(',')
159 .filter(|name| !name.is_empty())
160 .try_fold(Self::NONE, |kinds, name| Ok(kinds.with(name.parse()?)))
161 }
162}
163
164#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
166#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
167#[cfg_attr(feature = "typescript", ts(export_to = "controller-api.ts"))]
168#[serde(tag = "kind", rename_all = "snake_case")]
169pub enum EventPosition {
170 At { operation: u64 },
172 After { operations: u64 },
174}
175
176impl EventPosition {
177 pub fn moment(self, thread: u64) -> Moment {
180 let operation = match self {
181 Self::At { operation } => operation,
182 Self::After { operations } => operations + 1,
183 };
184 Moment::Event { thread, operation }
185 }
186}
187
188#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
190#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
191#[cfg_attr(feature = "typescript", ts(export_to = "controller-api.ts"))]
192pub struct WatchedEvent {
193 pub thread: u64,
194 pub position: EventPosition,
195 pub at_us: Option<u64>,
198 pub kind: WatchKind,
199 pub what: String,
201}
202
203#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
209#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
210#[cfg_attr(feature = "typescript", ts(export_to = "controller-api.ts"))]
211pub struct WatchedEvents {
212 pub watched: WatchKinds,
213 pub events: Vec<WatchedEvent>,
214 pub counts: BTreeMap<WatchKind, u64>,
215}
216
217impl WatchedEvents {
218 pub const MOST_OF_A_KIND: usize = 2_000;
221
222 pub fn count(&self, kind: WatchKind) -> u64 {
224 self.counts.get(&kind).copied().unwrap_or(0)
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn kinds_are_read_as_written() {
234 let kinds: WatchKinds = "files,page-faults".parse().unwrap();
235 assert_eq!(kinds.to_string(), "files,page-faults");
236 assert!(kinds.contains(WatchKind::PageFaults));
237 assert!(!kinds.contains(WatchKind::Network));
238 assert_eq!("".parse::<WatchKinds>(), Ok(WatchKinds::NONE));
239 assert!("files,sockets".parse::<WatchKinds>().is_err());
240 let json = serde_json::to_string(&kinds).unwrap();
241 assert_eq!(json, r#"["files","page-faults"]"#);
242 assert_eq!(serde_json::from_str::<WatchKinds>(&json).unwrap(), kinds);
243 }
244
245 #[test]
246 fn a_page_fault_is_gone_to_at_its_threads_next_operation() {
247 let after = EventPosition::After { operations: 41 };
248 assert_eq!(
249 after.moment(3),
250 Moment::Event {
251 thread: 3,
252 operation: 42
253 }
254 );
255 let at = EventPosition::At { operation: 22 };
256 assert_eq!(at.moment(5).to_string(), "5:22");
257 }
258}