use std::collections::BTreeMap;
use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use crate::replay::Moment;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[cfg_attr(feature = "typescript", ts(export_to = "controller-api.ts"))]
#[serde(rename_all = "kebab-case")]
pub enum WatchKind {
Files,
Network,
Processes,
Memory,
Time,
Signals,
Waits,
Other,
PageFaults,
}
impl WatchKind {
pub const ALL: [Self; 9] = [
Self::Files,
Self::Network,
Self::Processes,
Self::Memory,
Self::Time,
Self::Signals,
Self::Waits,
Self::Other,
Self::PageFaults,
];
pub fn name(self) -> &'static str {
match self {
Self::Files => "files",
Self::Network => "network",
Self::Processes => "processes",
Self::Memory => "memory",
Self::Time => "time",
Self::Signals => "signals",
Self::Waits => "waits",
Self::Other => "other",
Self::PageFaults => "page-faults",
}
}
pub fn label(self) -> &'static str {
match self {
Self::Files => "Files",
Self::Network => "Network",
Self::Processes => "Processes",
Self::Memory => "Memory",
Self::Time => "Time",
Self::Signals => "Signals",
Self::Waits => "Waits",
Self::Other => "Other syscalls",
Self::PageFaults => "Page faults",
}
}
fn bit(self) -> u16 {
1 << self as u16
}
}
impl FromStr for WatchKind {
type Err = String;
fn from_str(name: &str) -> Result<Self, String> {
Self::ALL
.into_iter()
.find(|kind| kind.name() == name)
.ok_or_else(|| format!("no kind of event {name:?}"))
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[cfg_attr(feature = "typescript", ts(export_to = "controller-api.ts"))]
#[serde(from = "Vec<WatchKind>", into = "Vec<WatchKind>")]
#[cfg_attr(feature = "typescript", ts(as = "Vec<WatchKind>"))]
pub struct WatchKinds(u16);
impl WatchKinds {
pub const NONE: Self = Self(0);
pub fn with(self, kind: WatchKind) -> Self {
Self(self.0 | kind.bit())
}
pub fn without(self, kind: WatchKind) -> Self {
Self(self.0 & !kind.bit())
}
pub fn contains(self, kind: WatchKind) -> bool {
self.0 & kind.bit() != 0
}
pub fn is_empty(self) -> bool {
self.0 == 0
}
pub fn kinds(self) -> impl Iterator<Item = WatchKind> {
WatchKind::ALL
.into_iter()
.filter(move |&kind| self.contains(kind))
}
}
impl From<Vec<WatchKind>> for WatchKinds {
fn from(kinds: Vec<WatchKind>) -> Self {
kinds.into_iter().fold(Self::NONE, Self::with)
}
}
impl From<WatchKinds> for Vec<WatchKind> {
fn from(kinds: WatchKinds) -> Self {
kinds.kinds().collect()
}
}
impl fmt::Display for WatchKinds {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let names: Vec<&str> = self.kinds().map(WatchKind::name).collect();
f.write_str(&names.join(","))
}
}
impl FromStr for WatchKinds {
type Err = String;
fn from_str(list: &str) -> Result<Self, String> {
list.split(',')
.filter(|name| !name.is_empty())
.try_fold(Self::NONE, |kinds, name| Ok(kinds.with(name.parse()?)))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[cfg_attr(feature = "typescript", ts(export_to = "controller-api.ts"))]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum EventPosition {
At { operation: u64 },
After { operations: u64 },
}
impl EventPosition {
pub fn moment(self, thread: u64) -> Moment {
let operation = match self {
Self::At { operation } => operation,
Self::After { operations } => operations + 1,
};
Moment::Event { thread, operation }
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[cfg_attr(feature = "typescript", ts(export_to = "controller-api.ts"))]
pub struct WatchedEvent {
pub thread: u64,
pub position: EventPosition,
pub at_us: Option<u64>,
pub kind: WatchKind,
pub what: String,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[cfg_attr(feature = "typescript", ts(export_to = "controller-api.ts"))]
pub struct WatchedEvents {
pub watched: WatchKinds,
pub events: Vec<WatchedEvent>,
pub counts: BTreeMap<WatchKind, u64>,
}
impl WatchedEvents {
pub const MOST_OF_A_KIND: usize = 2_000;
pub fn count(&self, kind: WatchKind) -> u64 {
self.counts.get(&kind).copied().unwrap_or(0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kinds_are_read_as_written() {
let kinds: WatchKinds = "files,page-faults".parse().unwrap();
assert_eq!(kinds.to_string(), "files,page-faults");
assert!(kinds.contains(WatchKind::PageFaults));
assert!(!kinds.contains(WatchKind::Network));
assert_eq!("".parse::<WatchKinds>(), Ok(WatchKinds::NONE));
assert!("files,sockets".parse::<WatchKinds>().is_err());
let json = serde_json::to_string(&kinds).unwrap();
assert_eq!(json, r#"["files","page-faults"]"#);
assert_eq!(serde_json::from_str::<WatchKinds>(&json).unwrap(), kinds);
}
#[test]
fn a_page_fault_is_gone_to_at_its_threads_next_operation() {
let after = EventPosition::After { operations: 41 };
assert_eq!(
after.moment(3),
Moment::Event {
thread: 3,
operation: 42
}
);
let at = EventPosition::At { operation: 22 };
assert_eq!(at.moment(5).to_string(), "5:22");
}
}