use crate::brick::{Brick, BrickAssertion, BrickBudget, BrickVerification};
use crate::ring_buffer::RingBuffer;
use std::any::Any;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct WosKernelMetrics {
pub timestamp: Instant,
pub process_count: usize,
pub runnable_count: usize,
pub zombie_count: usize,
pub memory_pages: usize,
pub active_vms: usize,
pub jidoka_status: JidokaHealthStatus,
pub syscalls_per_sec: f64,
pub open_fds: usize,
pub shm_segments: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JidokaHealthStatus {
Healthy,
Degraded,
Critical,
}
impl Default for WosKernelMetrics {
fn default() -> Self {
Self {
timestamp: Instant::now(),
process_count: 0,
runnable_count: 0,
zombie_count: 0,
memory_pages: 0,
active_vms: 0,
jidoka_status: JidokaHealthStatus::Healthy,
syscalls_per_sec: 0.0,
open_fds: 0,
shm_segments: 0,
}
}
}
pub struct WosCollectorBrick {
history: RingBuffer<WosKernelMetrics>,
last_syscall_count: u64,
last_collection: Instant,
available: bool,
}
impl WosCollectorBrick {
pub fn new() -> Self {
Self {
history: RingBuffer::new(120), last_syscall_count: 0,
last_collection: Instant::now(),
available: Self::check_availability(),
}
}
fn check_availability() -> bool {
cfg!(feature = "wos-kernel")
}
pub fn collect(&mut self) -> WosKernelMetrics {
let now = Instant::now();
let elapsed = now.duration_since(self.last_collection).as_secs_f64();
let metrics = if self.available {
self.collect_real_metrics(elapsed)
} else {
self.collect_mock_metrics(elapsed)
};
self.last_collection = now;
self.history.push(metrics.clone());
metrics
}
#[cfg(feature = "wos-kernel")]
fn collect_real_metrics(&mut self, elapsed: f64) -> WosKernelMetrics {
self.collect_mock_metrics(elapsed)
}
#[cfg(not(feature = "wos-kernel"))]
fn collect_real_metrics(&mut self, elapsed: f64) -> WosKernelMetrics {
self.collect_mock_metrics(elapsed)
}
fn collect_mock_metrics(&mut self, elapsed: f64) -> WosKernelMetrics {
let syscall_count = self.last_syscall_count + (elapsed * 1000.0) as u64;
let syscalls_per_sec = if elapsed > 0.0 {
(syscall_count - self.last_syscall_count) as f64 / elapsed
} else {
0.0
};
self.last_syscall_count = syscall_count;
WosKernelMetrics {
timestamp: Instant::now(),
process_count: 12,
runnable_count: 3,
zombie_count: 0,
memory_pages: 4096,
active_vms: 2,
jidoka_status: JidokaHealthStatus::Healthy,
syscalls_per_sec,
open_fds: 128,
shm_segments: 4,
}
}
pub fn history(&self) -> &RingBuffer<WosKernelMetrics> {
&self.history
}
pub fn is_available(&self) -> bool {
self.available
}
pub fn interval_hint(&self) -> Duration {
Duration::from_millis(1000)
}
pub fn process_breakdown(&self) -> ProcessBreakdown {
if let Some(last) = self.history.back() {
ProcessBreakdown {
total: last.process_count,
runnable: last.runnable_count,
sleeping: last
.process_count
.saturating_sub(last.runnable_count + last.zombie_count),
zombie: last.zombie_count,
}
} else {
ProcessBreakdown::default()
}
}
pub fn jidoka_summary(&self) -> JidokaSummary {
if let Some(last) = self.history.back() {
JidokaSummary {
status: last.jidoka_status,
checks_passed: match last.jidoka_status {
JidokaHealthStatus::Healthy => 100,
JidokaHealthStatus::Degraded => 85,
JidokaHealthStatus::Critical => 50,
},
last_violation: None,
}
} else {
JidokaSummary::default()
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ProcessBreakdown {
pub total: usize,
pub runnable: usize,
pub sleeping: usize,
pub zombie: usize,
}
#[derive(Debug, Clone)]
pub struct JidokaSummary {
pub status: JidokaHealthStatus,
pub checks_passed: u8,
pub last_violation: Option<String>,
}
impl Default for JidokaSummary {
fn default() -> Self {
Self {
status: JidokaHealthStatus::Healthy,
checks_passed: 100,
last_violation: None,
}
}
}
impl Default for WosCollectorBrick {
fn default() -> Self {
Self::new()
}
}
impl Brick for WosCollectorBrick {
fn brick_name(&self) -> &'static str {
"wos_collector"
}
fn assertions(&self) -> Vec<BrickAssertion> {
vec![
BrickAssertion::ValueInRange {
min: 0.0,
max: 10000.0,
}, BrickAssertion::max_latency_ms(10),
]
}
fn budget(&self) -> BrickBudget {
BrickBudget {
collect_ms: 10,
layout_ms: 0,
render_ms: 0,
}
}
fn verify(&self) -> BrickVerification {
let mut v = BrickVerification::new();
for assertion in self.assertions() {
v.check(&assertion);
}
v
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wos_collector_brick_name() {
let collector = WosCollectorBrick::new();
assert_eq!(collector.brick_name(), "wos_collector");
}
#[test]
fn test_wos_collector_has_assertions() {
let collector = WosCollectorBrick::new();
assert!(!collector.assertions().is_empty());
}
#[test]
fn test_wos_collector_collect() {
let mut collector = WosCollectorBrick::new();
let metrics = collector.collect();
assert!(metrics.process_count > 0);
assert_eq!(metrics.jidoka_status, JidokaHealthStatus::Healthy);
}
#[test]
fn test_wos_process_breakdown() {
let mut collector = WosCollectorBrick::new();
collector.collect();
let breakdown = collector.process_breakdown();
assert_eq!(
breakdown.total,
breakdown.runnable + breakdown.sleeping + breakdown.zombie
);
}
#[test]
fn test_wos_jidoka_summary() {
let mut collector = WosCollectorBrick::new();
collector.collect();
let summary = collector.jidoka_summary();
assert_eq!(summary.status, JidokaHealthStatus::Healthy);
assert!(summary.checks_passed > 0);
}
#[test]
fn test_wos_syscall_rate() {
let mut collector = WosCollectorBrick::new();
collector.collect();
std::thread::sleep(Duration::from_millis(10));
let metrics = collector.collect();
assert!(metrics.syscalls_per_sec >= 0.0);
}
}