use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct EbpfMetrics {
#[serde(skip_serializing_if = "Option::is_none")]
pub syscalls: Option<SyscallMetrics>,
#[serde(skip_serializing_if = "Option::is_none")]
pub offcpu: Option<OffCpuMetrics>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
impl EbpfMetrics {
pub fn error(message: &str) -> Self {
Self {
syscalls: None,
offcpu: None,
error: Some(message.to_string()),
}
}
pub fn with_syscalls(syscalls: SyscallMetrics) -> Self {
Self {
syscalls: Some(syscalls),
offcpu: None,
error: None,
}
}
pub fn with_offcpu(offcpu: OffCpuMetrics) -> Self {
Self {
syscalls: None,
offcpu: Some(offcpu),
error: None,
}
}
pub fn with_all(syscalls: SyscallMetrics, offcpu: OffCpuMetrics) -> Self {
Self {
syscalls: Some(syscalls),
offcpu: Some(offcpu),
error: None,
}
}
pub fn has_error(&self) -> bool {
self.error.is_some()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SyscallMetrics {
pub total: u64,
pub by_category: HashMap<String, u64>,
pub top_syscalls: Vec<SyscallCount>,
#[serde(skip_serializing_if = "Option::is_none")]
pub analysis: Option<SyscallAnalysis>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyscallAnalysis {
pub behavior_classification: ProcessBehavior,
pub syscall_rate_per_sec: f64,
pub io_intensity: f64,
pub memory_intensity: f64,
pub cpu_intensity: f64,
pub network_intensity: f64,
pub bottleneck_indicators: Vec<String>,
pub performance_profile: PerformanceProfile,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ProcessBehavior {
IoBound,
CpuBound,
MemoryBound,
NetworkBound,
Mixed,
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceProfile {
pub workload_type: String,
pub bottleneck_likelihood: f64,
pub optimization_hints: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyscallCount {
pub name: String,
pub count: u64,
}
pub const SYSCALL_CATEGORIES: &[(u64, &str)] = &[
(0, "read"), (1, "write"), (2, "open"), (3, "close"), (8, "lseek"), (257, "openat"), (9, "mmap"), (11, "munmap"), (12, "brk"), (13, "rt_sigaction"), (56, "clone"), (57, "fork"), (58, "vfork"), (59, "execve"), (60, "exit"), (61, "wait4"), (41, "socket"), (42, "connect"), (43, "accept"), (44, "sendto"), (45, "recvfrom"), (35, "nanosleep"), (96, "gettimeofday"), (201, "time"), (228, "clock_gettime"), ];
pub fn syscall_name(syscall_nr: u64) -> String {
SYSCALL_CATEGORIES
.iter()
.find(|(nr, _)| *nr == syscall_nr)
.map(|(_, name)| name.to_string())
.unwrap_or_else(|| format!("syscall_{}", syscall_nr))
}
pub fn categorize_syscall(syscall_nr: u64) -> String {
match syscall_nr {
0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 40 | 82 | 83 | 84
| 85 | 86 | 87 | 88 | 89 | 90 | 132 | 133 | 187 | 188 | 189 | 190 | 257 | 258 | 259
| 260 | 263 | 264 | 265 | 285 | 286 | 293 | 294 | 295 | 296 | 304 | 305 | 306 | 307 => {
"file_io".to_string()
}
9 | 10 | 11 | 12 | 15 | 25 | 26 | 27 | 28 | 158 | 159 | 160 | 213 | 214 | 215 | 216
| 217 | 218 | 226 | 273 | 274 | 275 | 276 | 317 | 318 | 319 => "memory".to_string(),
56 | 57 | 58 | 59 | 60 | 61 | 62 | 224 | 231 | 232 | 233 | 234 | 235 | 236 | 246 | 266
| 267 | 268 | 269 | 270 | 271 | 272 => "process".to_string(),
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 198 | 199
| 200 | 202 | 203 | 288 | 289 | 290 | 291 | 292 | 326 | 327 | 328 | 329 | 330 | 331
| 332 => "network".to_string(),
23 | 24 | 35 | 96 | 97 | 98 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 201 | 228
| 229 | 230 | 249 | 252 | 277 | 278 | 279 | 280 => "time".to_string(),
63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80
| 81 => "ipc".to_string(),
91 | 92 | 95 | 123 | 124 | 125 | 126 | 137 | 138 | 139 | 140 | 141 | 142 | 157 | 163
| 164 | 165 | 166 | 281 | 282 | 283 | 284 => "security".to_string(),
13 | 14 => "signal".to_string(),
99 | 100 | 101 | 102 | 103 | 153 | 154 | 155 | 156 | 168 | 169 | 170 | 171 | 172 | 173
| 174 | 175 => "system".to_string(),
_ => "other".to_string(),
}
}
use super::offcpu_profiler::{ProcessedOffCpuEvent, StackFrame};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AggregatedStacks {
#[serde(skip_serializing_if = "Vec::is_empty")]
pub user_stack: Vec<StackFrame>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub kernel_stack: Vec<StackFrame>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct OffCpuMetrics {
pub total_time_ns: u64,
pub total_events: u64,
pub avg_time_ns: u64,
pub max_time_ns: u64,
pub min_time_ns: u64,
#[serde(skip_serializing_if = "HashMap::is_empty")]
pub thread_stats: HashMap<String, ThreadOffCpuStats>,
pub top_blocking_threads: Vec<ThreadOffCpuInfo>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub bottlenecks: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub stack_traces: Vec<ProcessedOffCpuEvent>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stacks: Option<AggregatedStacks>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ThreadOffCpuStats {
pub tid: u32,
pub total_time_ns: u64,
pub count: u64,
pub avg_time_ns: u64,
pub max_time_ns: u64,
pub min_time_ns: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThreadOffCpuInfo {
pub tid: u32,
pub pid: u32,
#[serde(rename = "time_ms")]
pub total_time_ms: f64,
#[serde(serialize_with = "serialize_percentage_2dp")]
pub percentage: f64,
}
fn serialize_percentage_2dp<S>(value: &f64, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let rounded = (value * 100.0).round() / 100.0;
serializer.serialize_f64(rounded)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OffCpuAnalysis {
pub bottleneck_type: OffCpuBottleneckType,
pub io_wait_percentage: f64,
pub lock_contention_percentage: f64,
pub sleep_percentage: f64,
pub optimization_hints: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum OffCpuBottleneckType {
IoBlocked,
LockContention,
Sleep,
Mixed,
Unknown,
}
pub fn generate_syscall_analysis(
metrics: &SyscallMetrics,
cpu_usage: f32,
elapsed_seconds: f64,
) -> SyscallAnalysis {
let total = metrics.total as f64;
if total < 1.0 || elapsed_seconds < 0.1 {
return SyscallAnalysis {
behavior_classification: ProcessBehavior::Unknown,
syscall_rate_per_sec: 0.0,
io_intensity: 0.0,
memory_intensity: 0.0,
cpu_intensity: 0.0,
network_intensity: 0.0,
bottleneck_indicators: vec![],
performance_profile: PerformanceProfile {
workload_type: "insufficient_data".to_string(),
bottleneck_likelihood: 0.0,
optimization_hints: vec![],
},
};
}
let io_intensity = *metrics.by_category.get("file_io").unwrap_or(&0) as f64 / total;
let memory_intensity = *metrics.by_category.get("memory").unwrap_or(&0) as f64 / total;
let network_intensity = *metrics.by_category.get("network").unwrap_or(&0) as f64 / total;
let process_intensity = *metrics.by_category.get("process").unwrap_or(&0) as f64 / total;
let syscall_rate_per_sec = total / elapsed_seconds;
let behavior_classification = classify_process_behavior(
io_intensity,
memory_intensity,
network_intensity,
cpu_usage as f64,
syscall_rate_per_sec,
);
let bottleneck_indicators =
detect_bottleneck_indicators(&metrics.by_category, syscall_rate_per_sec, cpu_usage as f64);
let performance_profile = generate_performance_profile(
&behavior_classification,
io_intensity,
memory_intensity,
network_intensity,
syscall_rate_per_sec,
);
SyscallAnalysis {
behavior_classification,
syscall_rate_per_sec,
io_intensity,
memory_intensity,
cpu_intensity: process_intensity, network_intensity,
bottleneck_indicators,
performance_profile,
}
}
fn classify_process_behavior(
io_intensity: f64,
memory_intensity: f64,
network_intensity: f64,
cpu_usage: f64,
syscall_rate: f64,
) -> ProcessBehavior {
if io_intensity > 0.6 && syscall_rate > 100.0 {
return ProcessBehavior::IoBound;
}
if network_intensity > 0.4 {
return ProcessBehavior::NetworkBound;
}
if memory_intensity > 0.3 {
return ProcessBehavior::MemoryBound;
}
if syscall_rate < 50.0 && cpu_usage > 50.0 {
return ProcessBehavior::CpuBound;
}
if io_intensity > 0.2 && memory_intensity > 0.1 {
return ProcessBehavior::Mixed;
}
ProcessBehavior::Unknown
}
fn detect_bottleneck_indicators(
by_category: &HashMap<String, u64>,
syscall_rate: f64,
cpu_usage: f64,
) -> Vec<String> {
let mut indicators = Vec::new();
let file_io = *by_category.get("file_io").unwrap_or(&0) as f64;
let memory = *by_category.get("memory").unwrap_or(&0) as f64;
let network = *by_category.get("network").unwrap_or(&0) as f64;
if file_io > 500.0 {
indicators.push("high_file_io".to_string());
}
if syscall_rate > 1000.0 {
indicators.push("very_high_syscall_rate".to_string());
}
if memory > 100.0 {
indicators.push("frequent_memory_management".to_string());
}
if network > 200.0 {
indicators.push("high_network_activity".to_string());
}
if cpu_usage > 80.0 && syscall_rate < 100.0 {
indicators.push("cpu_intensive".to_string());
}
if file_io > 300.0 && memory > 50.0 {
indicators.push("io_memory_contention".to_string());
}
indicators
}
fn generate_performance_profile(
behavior: &ProcessBehavior,
_io_intensity: f64,
_memory_intensity: f64,
_network_intensity: f64,
_syscall_rate: f64,
) -> PerformanceProfile {
let (workload_type, bottleneck_likelihood, optimization_hints) = match behavior {
ProcessBehavior::IoBound => {
let hints = vec![
"Consider I/O optimization strategies".to_string(),
"Use async I/O or batching".to_string(),
"Check for excessive file operations".to_string(),
];
("file_io_intensive".to_string(), 0.8, hints)
}
ProcessBehavior::CpuBound => {
let hints = vec![
"CPU optimization opportunities".to_string(),
"Consider parallel processing".to_string(),
"Profile for algorithmic improvements".to_string(),
];
("cpu_intensive".to_string(), 0.7, hints)
}
ProcessBehavior::MemoryBound => {
let hints = vec![
"Memory allocation optimization needed".to_string(),
"Consider memory pooling".to_string(),
"Check for memory leaks".to_string(),
];
("memory_intensive".to_string(), 0.75, hints)
}
ProcessBehavior::NetworkBound => {
let hints = vec![
"Network optimization opportunities".to_string(),
"Consider connection pooling".to_string(),
"Optimize network protocols".to_string(),
];
("network_intensive".to_string(), 0.8, hints)
}
ProcessBehavior::Mixed => {
let hints = vec![
"Mixed workload - profile individual components".to_string(),
"Consider workload separation".to_string(),
];
("mixed_workload".to_string(), 0.5, hints)
}
ProcessBehavior::Unknown => ("unknown".to_string(), 0.0, vec![]),
};
PerformanceProfile {
workload_type,
bottleneck_likelihood,
optimization_hints,
}
}