use std::sync::mpsc::{Receiver, Sender};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EventCategory {
Lifecycle,
File,
Network,
Process,
}
impl EventCategory {
pub const ALL: [EventCategory; 4] = [
EventCategory::Lifecycle,
EventCategory::File,
EventCategory::Network,
EventCategory::Process,
];
pub fn as_str(self) -> &'static str {
match self {
EventCategory::Lifecycle => "lifecycle",
EventCategory::File => "file",
EventCategory::Network => "network",
EventCategory::Process => "process",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CapabilitySupport {
Supported,
Partial,
Unavailable,
}
impl CapabilitySupport {
pub fn as_str(self) -> &'static str {
match self {
CapabilitySupport::Supported => "supported",
CapabilitySupport::Partial => "partial",
CapabilitySupport::Unavailable => "unavailable",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CategoryCapability {
pub category: EventCategory,
pub support: CapabilitySupport,
pub backend: &'static str,
pub reason: &'static str,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ObserverCapabilities {
categories: Vec<CategoryCapability>,
}
impl ObserverCapabilities {
pub fn negotiate() -> Self {
let categories = EventCategory::ALL
.iter()
.map(|&category| match category {
EventCategory::Lifecycle => CategoryCapability {
category,
support: CapabilitySupport::Supported,
backend: "portable-lifecycle",
reason: "started/exited emitted from the crate spawn and reap path",
},
EventCategory::File => CategoryCapability {
category,
support: CapabilitySupport::Unavailable,
backend: "none",
reason: "requires Phase 3 platform backend (seccomp/eBPF/ETW)",
},
EventCategory::Network => CategoryCapability {
category,
support: CapabilitySupport::Unavailable,
backend: "none",
reason: "requires Phase 3 platform backend (seccomp/eBPF/ETW)",
},
EventCategory::Process => CategoryCapability {
category,
support: CapabilitySupport::Unavailable,
backend: "none",
reason: "requires Phase 3 platform backend (seccomp/eBPF/ETW)",
},
})
.collect();
Self { categories }
}
pub fn categories(&self) -> &[CategoryCapability] {
&self.categories
}
pub fn category(&self, category: EventCategory) -> &CategoryCapability {
self.categories
.iter()
.find(|entry| entry.category == category)
.expect("ObserverCapabilities always contains every EventCategory")
}
pub fn support(&self, category: EventCategory) -> CapabilitySupport {
self.category(category).support
}
pub fn is_supported(&self, category: EventCategory) -> bool {
self.support(category) == CapabilitySupport::Supported
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ObserverEventKind {
Started,
Exited {
exit_code: i32,
},
}
impl ObserverEventKind {
pub fn as_str(&self) -> &'static str {
match self {
ObserverEventKind::Started => "started",
ObserverEventKind::Exited { .. } => "exited",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ObserverEvent {
pub category: EventCategory,
pub kind: ObserverEventKind,
pub pid: u32,
pub timestamp_ms: u128,
}
impl ObserverEvent {
fn now(category: EventCategory, kind: ObserverEventKind, pid: u32) -> Self {
let timestamp_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis())
.unwrap_or(0);
Self {
category,
kind,
pid,
timestamp_ms,
}
}
}
#[derive(Debug, Clone)]
pub struct ObserverConfig {
categories: Vec<EventCategory>,
}
impl ObserverConfig {
pub fn lifecycle() -> Self {
Self {
categories: vec![EventCategory::Lifecycle],
}
}
pub fn with_categories(categories: impl IntoIterator<Item = EventCategory>) -> Self {
Self {
categories: categories.into_iter().collect(),
}
}
pub fn observes(&self, category: EventCategory) -> bool {
self.categories.contains(&category)
}
pub fn categories(&self) -> &[EventCategory] {
&self.categories
}
}
pub struct ObserverSubscriber {
rx: Receiver<ObserverEvent>,
}
impl ObserverSubscriber {
pub fn recv(&self) -> Option<ObserverEvent> {
self.rx.recv().ok()
}
pub fn try_recv(&self) -> Option<ObserverEvent> {
self.rx.try_recv().ok()
}
pub fn drain(&self) -> Vec<ObserverEvent> {
let mut events = Vec::new();
while let Ok(event) = self.rx.try_recv() {
events.push(event);
}
events
}
pub fn receiver(&self) -> &Receiver<ObserverEvent> {
&self.rx
}
}
pub(crate) struct ObserverEmitter {
config: ObserverConfig,
tx: Sender<ObserverEvent>,
}
impl ObserverEmitter {
pub(crate) fn new(config: ObserverConfig) -> (Self, ObserverSubscriber) {
let (tx, rx) = std::sync::mpsc::channel();
(Self { config, tx }, ObserverSubscriber { rx })
}
pub(crate) fn emit_started(&self, pid: u32) {
if !self.config.observes(EventCategory::Lifecycle) {
return;
}
let _ = self.tx.send(ObserverEvent::now(
EventCategory::Lifecycle,
ObserverEventKind::Started,
pid,
));
}
pub(crate) fn emit_exited(&self, pid: u32, exit_code: i32) {
if !self.config.observes(EventCategory::Lifecycle) {
return;
}
let _ = self.tx.send(ObserverEvent::now(
EventCategory::Lifecycle,
ObserverEventKind::Exited { exit_code },
pid,
));
}
}
#[cfg(test)]
mod tests;