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 syscall_rate_per_sec: f64,
pub io_intensity: f64,
pub memory_intensity: f64,
pub cpu_intensity: f64,
pub network_intensity: f64,
}
#[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..=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,
elapsed_seconds: f64,
) -> SyscallAnalysis {
let total = metrics.total as f64;
if total < 1.0 || elapsed_seconds < 0.1 {
return SyscallAnalysis {
syscall_rate_per_sec: 0.0,
io_intensity: 0.0,
memory_intensity: 0.0,
cpu_intensity: 0.0,
network_intensity: 0.0,
};
}
SyscallAnalysis {
syscall_rate_per_sec: total / elapsed_seconds,
io_intensity: *metrics.by_category.get("file_io").unwrap_or(&0) as f64 / total,
memory_intensity: *metrics.by_category.get("memory").unwrap_or(&0) as f64 / total,
cpu_intensity: *metrics.by_category.get("process").unwrap_or(&0) as f64 / total,
network_intensity: *metrics.by_category.get("network").unwrap_or(&0) as f64 / total,
}
}