use crate::Action;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EventFilter {
#[serde(default)]
pub event_types: Vec<String>,
#[serde(default)]
pub cpus: Vec<u32>,
#[serde(default)]
pub pids: Vec<u32>,
#[serde(default)]
pub tgids: Vec<u32>,
#[serde(default)]
pub comm_regex: Option<String>,
#[serde(default)]
pub min_latency_us: Option<u64>,
#[serde(default = "default_sample_rate")]
pub sample_rate: f64,
}
fn default_sample_rate() -> f64 {
1.0
}
impl Default for EventFilter {
fn default() -> Self {
Self {
event_types: Vec::new(),
cpus: Vec::new(),
pids: Vec::new(),
tgids: Vec::new(),
comm_regex: None,
min_latency_us: None,
sample_rate: 1.0,
}
}
}
impl EventFilter {
pub fn matches(&self, action: &Action, json: &Value) -> bool {
if !self.event_types.is_empty() {
let event_type = json.get("type").and_then(|v| v.as_str()).unwrap_or("");
if !self.event_types.iter().any(|t| t == event_type) {
return false;
}
}
if !self.cpus.is_empty() {
let cpu = json.get("cpu").and_then(|v| v.as_u64()).map(|v| v as u32);
if cpu.is_none() || !self.cpus.contains(&cpu.unwrap()) {
return false;
}
}
if !self.pids.is_empty() {
let pid = self.extract_pid(json);
if pid.is_none() || !self.pids.contains(&pid.unwrap()) {
return false;
}
}
if !self.tgids.is_empty() {
let tgid = self.extract_tgid(json);
if tgid.is_none() || !self.tgids.contains(&tgid.unwrap()) {
return false;
}
}
if let Some(ref pattern) = self.comm_regex {
if let Ok(regex) = Regex::new(pattern) {
let comm = self.extract_comm(json);
if !regex.is_match(&comm) {
return false;
}
}
}
if let Some(min_lat) = self.min_latency_us {
let latency = self.extract_latency_us(json);
if latency.is_none() || latency.unwrap() < min_lat {
return false;
}
}
if self.sample_rate < 1.0 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
format!("{:?}", action).hash(&mut hasher);
let hash = hasher.finish();
let sample = (hash as f64 / u64::MAX as f64) <= self.sample_rate;
if !sample {
return false;
}
}
true
}
fn extract_pid(&self, json: &Value) -> Option<u32> {
json.get("pid")
.and_then(|v| v.as_u64())
.map(|v| v as u32)
.or_else(|| {
json.get("next_pid")
.and_then(|v| v.as_u64())
.map(|v| v as u32)
})
.or_else(|| {
json.get("prev_pid")
.and_then(|v| v.as_u64())
.map(|v| v as u32)
})
}
fn extract_tgid(&self, json: &Value) -> Option<u32> {
json.get("tgid")
.and_then(|v| v.as_u64())
.map(|v| v as u32)
.or_else(|| {
json.get("next_tgid")
.and_then(|v| v.as_u64())
.map(|v| v as u32)
})
}
fn extract_comm(&self, json: &Value) -> String {
json.get("comm")
.and_then(|v| v.as_str())
.or_else(|| json.get("next_comm").and_then(|v| v.as_str()))
.or_else(|| json.get("prev_comm").and_then(|v| v.as_str()))
.unwrap_or("")
.to_string()
}
fn extract_latency_us(&self, json: &Value) -> Option<u64> {
json.get("next_dsq_lat_us")
.and_then(|v| v.as_u64())
.or_else(|| json.get("latency_us").and_then(|v| v.as_u64()))
}
pub fn validate(&self) -> Result<(), String> {
if self.sample_rate < 0.0 || self.sample_rate > 1.0 {
return Err("sample_rate must be between 0.0 and 1.0".to_string());
}
if let Some(ref pattern) = self.comm_regex {
Regex::new(pattern).map_err(|e| format!("Invalid regex: {}", e))?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_cpu_filter() {
let filter = EventFilter {
cpus: vec![0, 1],
..Default::default()
};
let json1 = json!({"type": "sched_switch", "cpu": 0});
let json2 = json!({"type": "sched_switch", "cpu": 2});
assert!(filter.matches(&crate::Action::None, &json1));
assert!(!filter.matches(&crate::Action::None, &json2));
}
#[test]
fn test_event_type_filter() {
let filter = EventFilter {
event_types: vec!["sched_switch".to_string()],
..Default::default()
};
let json1 = json!({"type": "sched_switch", "cpu": 0});
let json2 = json!({"type": "sched_wakeup", "cpu": 0});
assert!(filter.matches(&crate::Action::None, &json1));
assert!(!filter.matches(&crate::Action::None, &json2));
}
#[test]
fn test_sample_rate() {
let filter = EventFilter {
sample_rate: 0.0, ..Default::default()
};
let json = json!({"type": "sched_switch", "cpu": 0});
assert!(!filter.matches(&crate::Action::None, &json));
}
}