From 6b294ab114fd6e7b17d415ac4dc0883cbded30fe Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sun, 14 Jun 2026 20:48:02 +0200 Subject: [PATCH 01/40] [#186]: added trace_cpu_frequency and trace_cpu_idle tracepoints in metrics_tracer.rs. Added CpuFrequency structure --- core/src/components/metrics_tracer/src/cpu.rs | 36 +++++ .../metrics_tracer/src/data_structures.rs | 36 +++-- .../src/components/metrics_tracer/src/main.rs | 131 ++++++++++++------ core/src/components/metrics_tracer/src/mod.rs | 3 +- 4 files changed, 151 insertions(+), 55 deletions(-) create mode 100644 core/src/components/metrics_tracer/src/cpu.rs diff --git a/core/src/components/metrics_tracer/src/cpu.rs b/core/src/components/metrics_tracer/src/cpu.rs new file mode 100644 index 00000000..c5b76f58 --- /dev/null +++ b/core/src/components/metrics_tracer/src/cpu.rs @@ -0,0 +1,36 @@ +//tracepoint:power:cpu_frequency +//tracepoint:power:cpu_frequency_limits +//tracepoint:power:cpu_idle +//tracepoint:power:cpu_idle_miss +use aya_ebpf::programs::TracePointContext; +use aya_log_ebpf::info; + +use crate::data_structures::{CPU_FREQUENCY, CpuFrequency}; + +//sys/kernel/tracing/events/power/cpu_frequency + +pub fn cpu_frequency(ctx: TracePointContext) -> Result<(), i64> { + let state_offset = 8; + let cpu_id_offset = 12; + let state: u32 = unsafe { ctx.read_at(state_offset) }?; + let cpu_id: u32 = unsafe { ctx.read_at(cpu_id_offset) }?; + + let cpu_freq_data = CpuFrequency { + cpu_id, + cpu_freq: state, + }; + + CPU_FREQUENCY.output(&ctx, &cpu_freq_data, 0); + Ok(()) +} +//sys/kernel/tracing/events/power/cpu_idle + +pub fn cpu_idle(ctx: TracePointContext) -> Result<(), i64> { + let state_offset = 8; + let cpu_id_offset = 12; + let state: u32 = unsafe { ctx.read_at(state_offset) }?; + let cpu_id: u32 = unsafe { ctx.read_at(cpu_id_offset) }?; + + info!(&ctx, "CPU idle: State: {} cpu_id: {}", state, cpu_id); + Ok(()) +} diff --git a/core/src/components/metrics_tracer/src/data_structures.rs b/core/src/components/metrics_tracer/src/data_structures.rs index e9866a83..ca2d4373 100644 --- a/core/src/components/metrics_tracer/src/data_structures.rs +++ b/core/src/components/metrics_tracer/src/data_structures.rs @@ -1,22 +1,25 @@ -use aya_ebpf::{macros::map, maps::{LruPerCpuHashMap, HashMap, PerfEventArray}}; +use aya_ebpf::{ + macros::map, + maps::{HashMap, LruPerCpuHashMap, PerfEventArray}, +}; pub const TASK_COMM_LEN: usize = 16; -#[repr(C,packed)] +#[repr(C, packed)] pub struct NetworkMetrics { pub tgid: u32, pub comm: [u8; TASK_COMM_LEN], pub ts_us: u64, - pub sk_err: i32, // Offset 284 - pub sk_err_soft: i32, // Offset 600 - pub sk_backlog_len: i32, // Offset 196 - pub sk_write_memory_queued: i32,// Offset 376 - pub sk_receive_buffer_size: i32,// Offset 244 - pub sk_ack_backlog: u32, // Offset 604 - pub sk_drops: i32, // Offset 136 + pub sk_err: i32, // Offset 284 + pub sk_err_soft: i32, // Offset 600 + pub sk_backlog_len: i32, // Offset 196 + pub sk_write_memory_queued: i32, // Offset 376 + pub sk_receive_buffer_size: i32, // Offset 244 + pub sk_ack_backlog: u32, // Offset 604 + pub sk_drops: i32, // Offset 136 } -#[repr(C,packed)] +#[repr(C, packed)] #[derive(Copy, Clone)] pub struct TimeStampStartInfo { pub comm: [u8; TASK_COMM_LEN], @@ -25,7 +28,7 @@ pub struct TimeStampStartInfo { } // Event we send to userspace when latency is computed -#[repr(C,packed)] +#[repr(C, packed)] #[derive(Copy, Clone)] pub struct TimeStampEvent { pub delta_us: u64, @@ -41,6 +44,11 @@ pub struct TimeStampEvent { pub daddr_v6: [u32; 4], } +pub struct CpuFrequency { + pub(crate) cpu_id: u32, + pub(crate) cpu_freq: u32, +} + // Map: connect-start timestamp by socket pointer #[map(name = "time_stamp_start")] pub static mut TIME_STAMP_START: HashMap<*mut core::ffi::c_void, TimeStampStartInfo> = @@ -48,7 +56,11 @@ pub static mut TIME_STAMP_START: HashMap<*mut core::ffi::c_void, TimeStampStartI // Perf event channel for emitting Event to userspace #[map(name = "time_stamp_events")] -pub static mut TIME_STAMP_EVENTS: PerfEventArray = PerfEventArray::::new(0); +pub static mut TIME_STAMP_EVENTS: PerfEventArray = + PerfEventArray::::new(0); #[map(name = "net_metrics")] pub static NET_METRICS: PerfEventArray = PerfEventArray::new(0); + +#[map(name = "cpu_frequency")] +pub static CPU_FREQUENCY: PerfEventArray = PerfEventArray::new(0); diff --git a/core/src/components/metrics_tracer/src/main.rs b/core/src/components/metrics_tracer/src/main.rs index 216a6aca..c4dd7ceb 100644 --- a/core/src/components/metrics_tracer/src/main.rs +++ b/core/src/components/metrics_tracer/src/main.rs @@ -3,22 +3,28 @@ #![allow(warnings)] mod bindings; +mod cpu; mod data_structures; - use core::{mem, ptr}; use crate::bindings::net_device; -use aya_ebpf::helpers::generated::{bpf_ktime_get_ns, bpf_perf_event_output}; +use crate::cpu::{cpu_frequency, cpu_idle}; +use crate::data_structures::NET_METRICS; +use crate::data_structures::{ + NetworkMetrics, TASK_COMM_LEN, TIME_STAMP_EVENTS, TIME_STAMP_START, TimeStampEvent, + TimeStampStartInfo, +}; use aya_ebpf::EbpfContext; -use aya_ebpf::helpers::{bpf_get_current_comm, bpf_probe_read_kernel, bpf_probe_read_kernel_str_bytes}; -use aya_ebpf::macros::{kprobe, map}; -use aya_ebpf::maps::{HashMap, PerfEventArray}; -use aya_ebpf::programs::ProbeContext; use aya_ebpf::helpers::bpf_get_current_pid_tgid; -use crate::data_structures::{NetworkMetrics, TimeStampEvent, TimeStampStartInfo, TASK_COMM_LEN, TIME_STAMP_EVENTS, TIME_STAMP_START}; -use crate::data_structures::NET_METRICS; +use aya_ebpf::helpers::generated::{bpf_ktime_get_ns, bpf_perf_event_output}; +use aya_ebpf::helpers::{ + bpf_get_current_comm, bpf_probe_read_kernel, bpf_probe_read_kernel_str_bytes, +}; +use aya_ebpf::macros::{kprobe, map, tracepoint}; +use aya_ebpf::maps::{HashMap, PerfEventArray}; +use aya_ebpf::programs::{ProbeContext, TracePointContext}; -const AF_INET: u16 = 2; +const AF_INET: u16 = 2; const AF_INET6: u16 = 10; const TCP_SYN_SENT: u8 = 2; @@ -48,13 +54,33 @@ fn try_metrics_tracer(ctx: ProbeContext) -> Result { let sk_ack_backlog_offset = 604; let sk_drops_offset = 136; - let sk_err = unsafe { bpf_probe_read_kernel::(sk_pointer.add(sk_err_offset) as *const i32).map_err(|_| 1)? }; - let sk_err_soft = unsafe { bpf_probe_read_kernel::(sk_pointer.add(sk_err_soft_offset) as *const i32).map_err(|_| 1)? }; - let sk_backlog_len = unsafe { bpf_probe_read_kernel::(sk_pointer.add(sk_backlog_len_offset) as *const i32).map_err(|_| 1)? }; - let sk_write_memory_queued = unsafe { bpf_probe_read_kernel::(sk_pointer.add(sk_write_memory_queued_offset) as *const i32).map_err(|_| 1)? }; - let sk_receive_buffer_size = unsafe { bpf_probe_read_kernel::(sk_pointer.add(sk_receive_buffer_size_offset) as *const i32).map_err(|_| 1)? }; - let sk_ack_backlog = unsafe { bpf_probe_read_kernel::(sk_pointer.add(sk_ack_backlog_offset) as *const u32).map_err(|_| 1)? }; - let sk_drops = unsafe { bpf_probe_read_kernel::(sk_pointer.add(sk_drops_offset) as *const i32).map_err(|_| 1)? }; + let sk_err = unsafe { + bpf_probe_read_kernel::(sk_pointer.add(sk_err_offset) as *const i32).map_err(|_| 1)? + }; + let sk_err_soft = unsafe { + bpf_probe_read_kernel::(sk_pointer.add(sk_err_soft_offset) as *const i32) + .map_err(|_| 1)? + }; + let sk_backlog_len = unsafe { + bpf_probe_read_kernel::(sk_pointer.add(sk_backlog_len_offset) as *const i32) + .map_err(|_| 1)? + }; + let sk_write_memory_queued = unsafe { + bpf_probe_read_kernel::(sk_pointer.add(sk_write_memory_queued_offset) as *const i32) + .map_err(|_| 1)? + }; + let sk_receive_buffer_size = unsafe { + bpf_probe_read_kernel::(sk_pointer.add(sk_receive_buffer_size_offset) as *const i32) + .map_err(|_| 1)? + }; + let sk_ack_backlog = unsafe { + bpf_probe_read_kernel::(sk_pointer.add(sk_ack_backlog_offset) as *const u32) + .map_err(|_| 1)? + }; + let sk_drops = unsafe { + bpf_probe_read_kernel::(sk_pointer.add(sk_drops_offset) as *const i32) + .map_err(|_| 1)? + }; let net_metrics = NetworkMetrics { tgid: tgid, @@ -79,16 +105,21 @@ fn try_metrics_tracer(ctx: ProbeContext) -> Result { // Monitor on tcp_sendmsg, tcp_v4_connect #[kprobe] fn tcp_v6_connect(ctx: ProbeContext) -> u32 { - match on_connect(ctx) { Ok(_) => 0, Err(e) => e as u32 } + match on_connect(ctx) { + Ok(_) => 0, + Err(e) => e as u32, + } } // Monitor on tcp_sendmsg, tcp_v4_connect #[kprobe] fn tcp_v4_connect(ctx: ProbeContext) -> u32 { - match on_connect(ctx) { Ok(_) => 0, Err(e) => e as u32 } + match on_connect(ctx) { + Ok(_) => 0, + Err(e) => e as u32, + } } - fn on_connect(ctx: ProbeContext) -> Result<(), i64> { let sk = ctx.arg::<*mut bindings::sock>(0).ok_or(1i64)?; if sk.is_null() { @@ -107,14 +138,19 @@ fn on_connect(ctx: ProbeContext) -> Result<(), i64> { start.comm.copy_from_slice(&comm); } let map_ptr = &raw mut TIME_STAMP_START; - (*map_ptr).insert(&(sk as *mut core::ffi::c_void), &start, 0).map_err(|_| 1)?; + (*map_ptr) + .insert(&(sk as *mut core::ffi::c_void), &start, 0) + .map_err(|_| 1)?; } Ok(()) } #[kprobe] fn tcp_rcv_state_process(ctx: ProbeContext) -> u32 { - match on_rcv_state_process(ctx) { Ok(_) => 0, Err(e) => e as u32 } + match on_rcv_state_process(ctx) { + Ok(_) => 0, + Err(e) => e as u32, + } } fn on_rcv_state_process(ctx: ProbeContext) -> Result<(), i64> { @@ -122,25 +158,25 @@ fn on_rcv_state_process(ctx: ProbeContext) -> Result<(), i64> { let sk = ctx.arg::<*mut bindings::sock>(0).unwrap_or(ptr::null_mut()); let sk = if sk.is_null() { ctx.arg::<*mut bindings::sock>(1).ok_or(1i64)? - } else { sk }; + } else { + sk + }; if sk.is_null() { return Err(1); } let skc_daddr_off = 0; - let skc_rcv_saddr_off = 4; + let skc_rcv_saddr_off = 4; let skc_dport_off = 12; let skc_num_off = 14; let skc_family_off = 16; let skc_state_off = 18; - let skc_v6_daddr_off = 56; - let skc_v6_rcv_saddr_off = 72; - - let state = unsafe { - bpf_probe_read_kernel::((sk as usize + skc_state_off) as *const u8) - }.map_err(|_| 1)?; + let skc_v6_daddr_off = 56; + let skc_v6_rcv_saddr_off = 72; + let state = unsafe { bpf_probe_read_kernel::((sk as usize + skc_state_off) as *const u8) } + .map_err(|_| 1)?; if state != TCP_SYN_SENT { return Ok(()); @@ -149,7 +185,8 @@ fn on_rcv_state_process(ctx: ProbeContext) -> Result<(), i64> { let start = unsafe { let map_ptr = &raw const TIME_STAMP_START; (*map_ptr).get(&((sk as usize) as *mut core::ffi::c_void)) - }.ok_or(1i64)?; + } + .ok_or(1i64)?; let now = unsafe { bpf_ktime_get_ns() }; let delta = now as i64 - start.ts_ns as i64; if delta <= 0 { @@ -176,16 +213,13 @@ fn on_rcv_state_process(ctx: ProbeContext) -> Result<(), i64> { // family, ports ev.af = unsafe { - bpf_probe_read_kernel::((sk as usize + skc_family_off) as *const u16) - .map_err(|_| 1)? + bpf_probe_read_kernel::((sk as usize + skc_family_off) as *const u16).map_err(|_| 1)? }; ev.lport = unsafe { - bpf_probe_read_kernel::((sk as usize + skc_num_off) as *const u16) - .map_err(|_| 1)? + bpf_probe_read_kernel::((sk as usize + skc_num_off) as *const u16).map_err(|_| 1)? }; ev.dport_be = unsafe { - bpf_probe_read_kernel::((sk as usize + skc_dport_off) as *const u16) - .map_err(|_| 1)? + bpf_probe_read_kernel::((sk as usize + skc_dport_off) as *const u16).map_err(|_| 1)? }; if ev.af == AF_INET { @@ -202,13 +236,13 @@ fn on_rcv_state_process(ctx: ProbeContext) -> Result<(), i64> { for i in 0..4 { ev.saddr_v6[i] = unsafe { bpf_probe_read_kernel::( - (sk as usize + skc_v6_rcv_saddr_off + i * 4) as *const u32 - ).map_err(|_| 1)? + (sk as usize + skc_v6_rcv_saddr_off + i * 4) as *const u32, + ) + .map_err(|_| 1)? }; ev.daddr_v6[i] = unsafe { - bpf_probe_read_kernel::( - (sk as usize + skc_v6_daddr_off + i * 4) as *const u32 - ).map_err(|_| 1)? + bpf_probe_read_kernel::((sk as usize + skc_v6_daddr_off + i * 4) as *const u32) + .map_err(|_| 1)? }; } } @@ -226,11 +260,24 @@ fn on_rcv_state_process(ctx: ProbeContext) -> Result<(), i64> { let _ = (*map_ptr).remove(&((sk as usize) as *mut core::ffi::c_void)); } - Ok(()) } +#[tracepoint] +fn trace_cpu_frequency(ctx: TracePointContext) -> u32 { + match cpu_frequency(ctx) { + Ok(_) => 0, + Err(e) => e as u32, + } +} +#[tracepoint] +fn trace_cpu_idle(ctx: TracePointContext) -> u32 { + match cpu_idle(ctx) { + Ok(_) => 0, + Err(e) => e as u32, + } +} // panic handler #[panic_handler] diff --git a/core/src/components/metrics_tracer/src/mod.rs b/core/src/components/metrics_tracer/src/mod.rs index 76d66830..35ef0d6d 100644 --- a/core/src/components/metrics_tracer/src/mod.rs +++ b/core/src/components/metrics_tracer/src/mod.rs @@ -1,2 +1,3 @@ mod bindings; -mod data_structures; \ No newline at end of file +mod cpu; +mod data_structures; From 936b5d86b4ea3b5eb252778dc5ad1a68530538a9 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sun, 14 Jun 2026 20:49:14 +0200 Subject: [PATCH 02/40] [#186]: added cpu frequency tracer user space functions to read the data from the perf buffer --- core/common/src/buffer_type.rs | 63 ++++++++++++++++++++++ core/common/src/program_handlers.rs | 52 +++++++++++++++++- core/src/components/metrics/src/helpers.rs | 26 +++++++++ core/src/components/metrics/src/main.rs | 18 ++++++- 4 files changed, 156 insertions(+), 3 deletions(-) diff --git a/core/common/src/buffer_type.rs b/core/common/src/buffer_type.rs index 45d82c81..22037cc3 100644 --- a/core/common/src/buffer_type.rs +++ b/core/common/src/buffer_type.rs @@ -128,6 +128,14 @@ pub struct TimeStampMetrics { } #[cfg(feature = "monitoring-structs")] unsafe impl aya::Pod for TimeStampMetrics {} +#[cfg(feature = "monitoring-structs")] +#[repr(C, packed)] +#[derive(Clone, Copy, Zeroable)] +pub struct CpuFrequency { + pub cpu_id: u32, + pub cpu_freq: u32, +} +unsafe impl aya::Pod for CpuFrequency {} // docs: // This function perform a byte swap from little-endian to big-endian @@ -156,6 +164,8 @@ pub enum BufferType { NetworkMetrics, #[cfg(feature = "monitoring-structs")] TimeStampMetrics, + #[cfg(feature = "monitoring-structs")] + CpuFrequency, } // IDEA: this is an experimental implementation to centralize buffer reading logic @@ -476,6 +486,45 @@ impl BufferType { } } } + + #[cfg(feature = "monitoring-structs")] + pub async fn read_cpu_frequency( + buffers: &mut [BytesMut], + tot_events: i32, + offset: i32, + //exporter: &str, + //metrics: Arc, + ) { + for i in offset..tot_events { + let vec_bytes = &buffers[i as usize]; + if vec_bytes.len() < std::mem::size_of::() { + error!( + "Corrupted Cpu Frequency Metrics data. Raw data: {}. Readed {} bytes expected {} bytes", + vec_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(" "), + vec_bytes.len(), + std::mem::size_of::() + ); + continue; + } + if vec_bytes.len() >= std::mem::size_of::() { + let cpu_freq_metrics: CpuFrequency = + unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; + + //match exporter { + // "otlp" => metrics.record_timestamp_metrics(&time_stamp_event), + // _ => continue, + //} + + let cpu_id = cpu_freq_metrics.cpu_id; + let cpu_freq = cpu_freq_metrics.cpu_freq; + info!("Cpu id: {} Cpu frequency: {}", cpu_id, cpu_freq); + } + } + } } // docs: read buffer function: @@ -548,6 +597,11 @@ pub async fn read_perf_buffer>( ) .await } + #[cfg(feature = "monitoring-structs")] + BufferType::CpuFrequency => { + BufferType::read_cpu_frequency(&mut buffers, tot_events, offset) + .await + } } } } @@ -572,6 +626,8 @@ pub enum BufferSize { NetworkMetricsEvents, #[cfg(feature = "monitoring-structs")] TimeMetricsEvents, + #[cfg(feature = "monitoring-structs")] + CpuFrequency, } #[cfg(feature = "buffer-reader")] impl BufferSize { @@ -587,6 +643,8 @@ impl BufferSize { BufferSize::NetworkMetricsEvents => std::mem::size_of::(), #[cfg(feature = "monitoring-structs")] BufferSize::TimeMetricsEvents => std::mem::size_of::(), + #[cfg(feature = "monitoring-structs")] + BufferSize::CpuFrequency => std::mem::size_of::(), } } pub fn set_buffer(&self) -> Vec { @@ -630,6 +688,11 @@ impl BufferSize { let capacity = self.get_size() * 1024; return vec![BytesMut::with_capacity(capacity); tot_cpu]; } + #[cfg(feature = "monitoring-structs")] + BufferSize::CpuFrequency => { + let capacity = self.get_size() * 1024; + return vec![BytesMut::with_capacity(capacity); tot_cpu]; + } } } } diff --git a/core/common/src/program_handlers.rs b/core/common/src/program_handlers.rs index 347be51f..fc3d6d51 100644 --- a/core/common/src/program_handlers.rs +++ b/core/common/src/program_handlers.rs @@ -1,4 +1,7 @@ -use aya::{Ebpf, programs::KProbe}; +use aya::{ + Ebpf, + programs::{KProbe, TracePoint}, +}; use std::convert::TryInto; use std::sync::{Arc, Mutex}; use tracing::{error, info}; @@ -48,3 +51,50 @@ pub fn load_program( Ok(()) } + +#[cfg(feature = "program-handlers")] +pub fn load_tracepoint_program( + bpf: Arc>, + program_name: &str, + tracepoint_type: &str, + tracepoint_symbol: &str, +) -> Result<(), anyhow::Error> { + let mut bpf_new = bpf + .lock() + .map_err(|e| anyhow::anyhow!("Cannot get value from lock. Reason: {}", e))?; + + // Load and attach the eBPF program + let program: &mut TracePoint = bpf_new + .program_mut(program_name) + .ok_or_else(|| anyhow::anyhow!("Program {} not found", program_name))? + .try_into() + .map_err(|e| anyhow::anyhow!("Failed to convert program: {:?}", e))?; + + // STEP 1: load program + + program + .load() + .map_err(|e| anyhow::anyhow!("Cannot load program: {}. Error: {}", &program_name, e))?; + + // STEP 2: Attach the loaded program to kernel symbol + match program.attach(tracepoint_type, tracepoint_symbol) { + Ok(_) => info!( + "{} program attached successfully to tracepoint {}", + &program_name, &tracepoint_symbol + ), + Err(e) => { + error!( + "Error attaching {} program to tracepoint {}. Reason: {:?}", + &program_name, &tracepoint_symbol, e + ); + return Err(anyhow::anyhow!( + "Failed to attach program {} to tracepoint {}. Reason {:?}", + &program_name, + &tracepoint_symbol, + e + )); + } + }; + + Ok(()) +} diff --git a/core/src/components/metrics/src/helpers.rs b/core/src/components/metrics/src/helpers.rs index 804e9306..8eb01d37 100644 --- a/core/src/components/metrics/src/helpers.rs +++ b/core/src/components/metrics/src/helpers.rs @@ -62,9 +62,14 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a .remove("net_metrics") .expect("Cannot create net_perf_buffer"); + let (_cpu_frequency_events_array, cpu_frequency_perf_buffer) = maps + .remove("cpu_frequency") + .expect("Cannot create cpu_frequency_perf_buffer"); + // Allocate byte-buffers sized for each structure type let net_metrics_buffers = BufferSize::NetworkMetricsEvents.set_buffer(); let time_stamp_events_buffers = BufferSize::TimeMetricsEvents.set_buffer(); + let cpu_frequency_events_buffers = BufferSize::CpuFrequency.set_buffer(); let metrics = Arc::new(Metrics::new(&meter)); @@ -100,6 +105,21 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a }) }; + let cpu_frequency_metrics = { + let metrics = Arc::clone(&metrics); + let mut array_buffers = cpu_frequency_perf_buffer; + let mut buffers = cpu_frequency_events_buffers; + tokio::spawn(async move { + read_perf_buffer( + array_buffers, + buffers, + BufferType::CpuFrequency, + Some(metrics), + ) + .await; + }) + }; + info!("Event listeners started, entering main loop..."); tokio::select! { @@ -115,6 +135,12 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a } } + result = cpu_frequency_metrics => { + if let Err(e) = result { + error!("Cpu frequency events task failed: {:?}", e); + } + } + _ = signal::ctrl_c() => { info!("Ctrl-C received, shutting down..."); } diff --git a/core/src/components/metrics/src/main.rs b/core/src/components/metrics/src/main.rs index 0211be68..b8f3c862 100644 --- a/core/src/components/metrics/src/main.rs +++ b/core/src/components/metrics/src/main.rs @@ -26,7 +26,7 @@ use cortexbrain_common::{ constants, logger::otlp_logger_init, map_handlers::{init_bpf_maps, map_pinner}, - program_handlers::load_program, + program_handlers::{load_program, load_tracepoint_program}, }; #[tokio::main] @@ -46,6 +46,7 @@ async fn main() -> Result<(), anyhow::Error> { let tcp_bpf = bpf.clone(); let tcp_rev_bpf = bpf.clone(); let tcp_v6_bpf = bpf.clone(); + let cpu_frequency = bpf.clone(); info!("Running Ebpf logger"); info!("loading programs"); @@ -53,7 +54,11 @@ async fn main() -> Result<(), anyhow::Error> { let bpf_map_save_path = env::var(constants::PIN_MAP_PATH).context("PIN_MAP_PATH environment variable required")?; - let map_data = vec!["time_stamp_events".to_string(), "net_metrics".to_string()]; + let map_data = vec![ + "time_stamp_events".to_string(), + "net_metrics".to_string(), + "cpu_frequency".to_string(), + ]; match init_bpf_maps(bpf.clone(), map_data) { Ok(bpf_maps) => { @@ -85,6 +90,15 @@ async fn main() -> Result<(), anyhow::Error> { .context( "An error occurred during the execution of load_program function", )?; + load_tracepoint_program( + cpu_frequency, + "trace_cpu_frequency", + "power", + "cpu_frequency", + ) + .context( + "An error occurred during the execution of load_program function", + )?; } // Hand off to the async event consumer From f240ff9e55a3ca48346e22059f04f08fe7477741 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sun, 14 Jun 2026 20:51:09 +0200 Subject: [PATCH 03/40] [#186]: updated metrics.yaml kubernetes manifest. Added tracefs path using volumeMounts --- core/src/testing/metrics.yaml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/core/src/testing/metrics.yaml b/core/src/testing/metrics.yaml index 8a6c7d82..ce797950 100644 --- a/core/src/testing/metrics.yaml +++ b/core/src/testing/metrics.yaml @@ -19,7 +19,7 @@ spec: hostNetwork: true containers: - name: metrics - image: lorenzotettamanti/cortexflow-metrics:otel-test-2 + image: lorenzotettamanti/cortexflow-metrics:otel-test-5 command: ["/bin/bash", "-c"] args: - | @@ -47,6 +47,9 @@ spec: - name: kernel-dev mountPath: /lib/modules readOnly: false + - name: tracefs + mountPath: /sys/kernel/debug + readOnly: false securityContext: privileged: true allowPrivilegeEscalation: true @@ -71,6 +74,9 @@ spec: - name: kernel-dev mountPath: /lib/modules readOnly: false + - name: tracefs + mountPath: /sys/kernel/debug + readOnly: false securityContext: privileged: true allowPrivilegeEscalation: true @@ -94,3 +100,10 @@ spec: hostPath: path: /lib/modules type: Directory + + - name: tracefs + hostPath: + path: /sys/kernel/debug + type: Directory + + \ No newline at end of file From 9802b0db756019882bd73a925059ffd4d8f3e7f2 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sat, 20 Jun 2026 14:33:34 +0200 Subject: [PATCH 04/40] (feat): added memory tracing in kernel space using sys_enter_mmap tracepoint --- core/src/components/metrics_tracer/src/cpu.rs | 35 ++++++------ .../metrics_tracer/src/data_structures.rs | 21 ++++++- .../src/components/metrics_tracer/src/main.rs | 56 ++++++++++++++++++- .../components/metrics_tracer/src/memory.rs | 16 ++++++ core/src/components/metrics_tracer/src/mod.rs | 1 + 5 files changed, 106 insertions(+), 23 deletions(-) create mode 100644 core/src/components/metrics_tracer/src/memory.rs diff --git a/core/src/components/metrics_tracer/src/cpu.rs b/core/src/components/metrics_tracer/src/cpu.rs index c5b76f58..02eae0f1 100644 --- a/core/src/components/metrics_tracer/src/cpu.rs +++ b/core/src/components/metrics_tracer/src/cpu.rs @@ -2,35 +2,34 @@ //tracepoint:power:cpu_frequency_limits //tracepoint:power:cpu_idle //tracepoint:power:cpu_idle_miss -use aya_ebpf::programs::TracePointContext; +use aya_ebpf::{EbpfContext, programs::TracePointContext}; use aya_log_ebpf::info; use crate::data_structures::{CPU_FREQUENCY, CpuFrequency}; -//sys/kernel/tracing/events/power/cpu_frequency - -pub fn cpu_frequency(ctx: TracePointContext) -> Result<(), i64> { +pub fn cpu_idle(ctx: TracePointContext) -> Result<(), i64> { let state_offset = 8; let cpu_id_offset = 12; let state: u32 = unsafe { ctx.read_at(state_offset) }?; let cpu_id: u32 = unsafe { ctx.read_at(cpu_id_offset) }?; - let cpu_freq_data = CpuFrequency { - cpu_id, - cpu_freq: state, - }; - - CPU_FREQUENCY.output(&ctx, &cpu_freq_data, 0); + info!(&ctx, "CPU idle: State: {} cpu_id: {}", state, cpu_id); Ok(()) } -//sys/kernel/tracing/events/power/cpu_idle -pub fn cpu_idle(ctx: TracePointContext) -> Result<(), i64> { - let state_offset = 8; - let cpu_id_offset = 12; - let state: u32 = unsafe { ctx.read_at(state_offset) }?; - let cpu_id: u32 = unsafe { ctx.read_at(cpu_id_offset) }?; +pub fn per_cpu_bytes_alloc(ctx: &TracePointContext) -> Result<((u32, u32, [u8; 16])), i64> { + let bytes_alloc_offset = 64; + let pid_offset = 4; + let bytes_alloc = unsafe { ctx.read_at(bytes_alloc_offset) }?; + let pid = unsafe { ctx.read_at(pid_offset) }?; + let command = ctx.command()?; - info!(&ctx, "CPU idle: State: {} cpu_id: {}", state, cpu_id); - Ok(()) + //let cpu_freq_data = CpuFrequency { + // cpu_id, + // cpu_freq: state, + //}; + + //CPU_FREQUENCY.output(&ctx, &cpu_freq_data, 0); + + Ok((bytes_alloc, pid, command)) } diff --git a/core/src/components/metrics_tracer/src/data_structures.rs b/core/src/components/metrics_tracer/src/data_structures.rs index ca2d4373..877cc991 100644 --- a/core/src/components/metrics_tracer/src/data_structures.rs +++ b/core/src/components/metrics_tracer/src/data_structures.rs @@ -44,9 +44,23 @@ pub struct TimeStampEvent { pub daddr_v6: [u32; 4], } +#[repr(C, packed)] +#[derive(Copy, Clone)] pub struct CpuFrequency { - pub(crate) cpu_id: u32, - pub(crate) cpu_freq: u32, + //pub(crate) cpu_id: u32, + //pub(crate) cpu_freq: u32, + pub(crate) bytes_alloc: u32, + pub(crate) pid: u32, + pub(crate) command: [u8; 16], +} + +#[repr(C, packed)] +#[derive(Copy, Clone)] +pub struct MemAlloc { + pub(crate) tgid: u32, + pub(crate) length: u64, + pub(crate) addr: u64, + pub(crate) command: [u8; 16], } // Map: connect-start timestamp by socket pointer @@ -64,3 +78,6 @@ pub static NET_METRICS: PerfEventArray = PerfEventArray::new(0); #[map(name = "cpu_frequency")] pub static CPU_FREQUENCY: PerfEventArray = PerfEventArray::new(0); + +#[map(name = "mem_alloc")] +pub static MEM_ALLOC: PerfEventArray = PerfEventArray::new(0); diff --git a/core/src/components/metrics_tracer/src/main.rs b/core/src/components/metrics_tracer/src/main.rs index c4dd7ceb..bdc44550 100644 --- a/core/src/components/metrics_tracer/src/main.rs +++ b/core/src/components/metrics_tracer/src/main.rs @@ -5,15 +5,20 @@ mod bindings; mod cpu; mod data_structures; -use core::{mem, ptr}; +mod memory; use crate::bindings::net_device; -use crate::cpu::{cpu_frequency, cpu_idle}; +use crate::cpu::{cpu_idle, per_cpu_bytes_alloc}; +use crate::data_structures::CPU_FREQUENCY; +use crate::data_structures::CpuFrequency; +use crate::data_structures::MEM_ALLOC; +use crate::data_structures::MemAlloc; use crate::data_structures::NET_METRICS; use crate::data_structures::{ NetworkMetrics, TASK_COMM_LEN, TIME_STAMP_EVENTS, TIME_STAMP_START, TimeStampEvent, TimeStampStartInfo, }; +use crate::memory::enter_mmap; use aya_ebpf::EbpfContext; use aya_ebpf::helpers::bpf_get_current_pid_tgid; use aya_ebpf::helpers::generated::{bpf_ktime_get_ns, bpf_perf_event_output}; @@ -23,6 +28,7 @@ use aya_ebpf::helpers::{ use aya_ebpf::macros::{kprobe, map, tracepoint}; use aya_ebpf::maps::{HashMap, PerfEventArray}; use aya_ebpf::programs::{ProbeContext, TracePointContext}; +use core::{mem, ptr}; const AF_INET: u16 = 2; const AF_INET6: u16 = 10; @@ -265,7 +271,7 @@ fn on_rcv_state_process(ctx: ProbeContext) -> Result<(), i64> { #[tracepoint] fn trace_cpu_frequency(ctx: TracePointContext) -> u32 { - match cpu_frequency(ctx) { + match trace_cpu_metrics(&ctx) { Ok(_) => 0, Err(e) => e as u32, } @@ -279,6 +285,50 @@ fn trace_cpu_idle(ctx: TracePointContext) -> u32 { } } +fn trace_cpu_metrics(ctx: &TracePointContext) -> Result<(), i64> { + let (bytes_alloc, pid, command) = per_cpu_bytes_alloc(ctx)?; + //let (cpu_id, cpu_freq) = cpu_frequency(&ctx)?; + let cpu_metrics = CpuFrequency { + // cpu_id, + // cpu_freq, + bytes_alloc, + pid, + command, + }; + + unsafe { CPU_FREQUENCY.output(ctx, &cpu_metrics, 0) }; + + Ok(()) +} + +/// Tracepoint attached to `syscalls:sys_enter_mmap`. +/// +/// Emits a `MemAlloc` event for every `mmap` syscall. No PID/command filter +/// is applied yet (see the next update), so this will generate events for every +/// process in the system. +#[tracepoint] +fn trace_enter_mmap(ctx: TracePointContext) -> u32 { + match trace_memory_allocation(&ctx) { + Ok(_) => 0, + Err(e) => e as u32, + } +} + +fn trace_memory_allocation(ctx: &TracePointContext) -> Result<(), i64> { + let (tgid, addr, length, command) = enter_mmap(ctx)?; + + let memory_alloc_metrics = MemAlloc { + tgid, + addr, + length, + command, + }; + + unsafe { MEM_ALLOC.output(ctx, &memory_alloc_metrics, 0) }; + + Ok(()) +} + // panic handler #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { diff --git a/core/src/components/metrics_tracer/src/memory.rs b/core/src/components/metrics_tracer/src/memory.rs new file mode 100644 index 00000000..de14c7a8 --- /dev/null +++ b/core/src/components/metrics_tracer/src/memory.rs @@ -0,0 +1,16 @@ +use aya_ebpf::{EbpfContext, programs::TracePointContext}; + +/// Read the fields of the `syscalls:sys_enter_mmap` tracepoint. +pub fn enter_mmap(ctx: &TracePointContext) -> Result<((u32, u64, u64, [u8; 16])), i64> { + // For syscall tracepoints `common_pid` is the TGID of the calling thread. + let tgid_offset = 4; + let addr_offset = 16; + let len_offset = 24; + + let tgid: u32 = unsafe { ctx.read_at(tgid_offset) }?; + let addr: u64 = unsafe { ctx.read_at(addr_offset) }?; + let len: u64 = unsafe { ctx.read_at(len_offset) }?; + let command = ctx.command()?; + + Ok((tgid, addr, len, command)) +} diff --git a/core/src/components/metrics_tracer/src/mod.rs b/core/src/components/metrics_tracer/src/mod.rs index 35ef0d6d..56f80cc7 100644 --- a/core/src/components/metrics_tracer/src/mod.rs +++ b/core/src/components/metrics_tracer/src/mod.rs @@ -1,3 +1,4 @@ mod bindings; mod cpu; mod data_structures; +mod memory; From ce341e58fdd69d4314636717d9fcb937046d73e7 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sat, 20 Jun 2026 14:36:52 +0200 Subject: [PATCH 05/40] (feat: #186): added cpu_bytes_alloc_events_total, cpy_bytes_alloc mem_alloc_events_total, enter_mem_alloc metrics --- core/common/src/otel_metrics.rs | 75 ++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/core/common/src/otel_metrics.rs b/core/common/src/otel_metrics.rs index f123b3b4..c256cc1d 100644 --- a/core/common/src/otel_metrics.rs +++ b/core/common/src/otel_metrics.rs @@ -11,7 +11,7 @@ //! extracted from the eBPF struct, allowing downstream collectors to group //! telemetry by process. -use crate::buffer_type::{NetworkMetrics, TimeStampMetrics}; +use crate::buffer_type::{CpuFrequency, MemAlloc, NetworkMetrics, TimeStampMetrics}; use opentelemetry::KeyValue; use opentelemetry::metrics::{Counter, Gauge, Histogram, Meter}; pub struct Metrics { @@ -35,6 +35,18 @@ pub struct Metrics { /// Histogram of `ts_us` values seen in both `net_metrics` and /// `time_stamp_events`. pub ts_us: Histogram, + + /// Cpu bytes alloc total events + pub cpu_bytes_alloc_events_total: Counter, + + /// Cpu bytes allocation + pub cpu_bytes_alloc: Gauge, + + /// Total number of memory allocation (mmap) events processed. + pub mem_alloc_events_total: Counter, + + /// Observed bytes requested via mmap syscalls. + pub enter_mem_alloc: Gauge, } impl Metrics { @@ -66,7 +78,7 @@ impl Metrics { // delta microseconds let delta_us = meter - .u64_histogram("cortexbrain_delta_us") + .u64_histogram("delta_us") .with_description("Distribution of delta_us values from timestamp events") .build(); @@ -76,6 +88,30 @@ impl Metrics { .with_description("Distribution of timestamp values from eBPF events") .build(); + // cpu bytes alloc total events + let cpu_bytes_alloc_events_total = meter + .u64_counter("bytes_alloc_events_total") + .with_description("Total bytes_alloc events occuring in the CPU") + .build(); + + // cpu bytes allocation + let cpu_bytes_alloc = meter + .i64_gauge("cpu_bytes_alloc") + .with_description("Cpu bytes allocation per event") + .build(); + + // memory allocation (mmap) events total + let mem_alloc_events_total = meter + .u64_counter("mem_alloc_events_total") + .with_description("Total number of memory allocation (mmap) events processed") + .build(); + + // bytes requested via mmap syscalls + let enter_mem_alloc = meter + .i64_gauge("enter_mem_alloc") + .with_description("Bytes requested via mmap syscalls") + .build(); + Self { events_total, packets_total, @@ -83,6 +119,10 @@ impl Metrics { sk_err, delta_us, ts_us, + cpu_bytes_alloc, + cpu_bytes_alloc_events_total, + mem_alloc_events_total, + enter_mem_alloc, } } @@ -130,4 +170,35 @@ impl Metrics { self.delta_us.record(m.delta_us, attrs); self.ts_us.record(m.ts_us, attrs); } + + pub fn record_cpu_bytes_alloc(&self, m: &CpuFrequency) { + let bytes_allocated = m.bytes_alloc; + let tgid = m.pid; // percpu tracepoints expose TGID in common_pid + let comm = String::from_utf8_lossy(&m.command); + let command = comm.trim_end_matches('\0').to_string(); + let attrs = &[ + KeyValue::new("tgid", tgid as i64), + KeyValue::new("command", command), + ]; + self.cpu_bytes_alloc_events_total.add(1, attrs); + self.cpu_bytes_alloc.record(bytes_allocated as i64, attrs); + } + + /// Record a single [`MemAlloc`] event (mmap syscall). + /// + /// Increments the dedicated `mem_alloc_events_total` counter and records + /// the requested length in the `enter_mem_alloc` gauge. The shared + /// `events_total` counter is intentionally **not** incremented for these + /// events. + pub fn record_enter_mem_alloc(&self, m: &MemAlloc) { + let comm = String::from_utf8_lossy(&m.command); + let command = comm.trim_end_matches('\0').to_string(); + let attrs = &[ + KeyValue::new("tgid", m.tgid as i64), + KeyValue::new("command", command), + ]; + + self.mem_alloc_events_total.add(1, attrs); + self.enter_mem_alloc.record(m.length as i64, attrs); + } } From 12ae5b52d6ccdbe74a1de4c4f4155b26e63acf95 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sat, 20 Jun 2026 14:43:38 +0200 Subject: [PATCH 06/40] (feat : #186): added userspace consumer for memory allocation events. Fixed typo in percpu_alloc_percpu tracepoint --- core/common/src/buffer_type.rs | 126 ++++++++++++++++++--- core/src/components/metrics/src/helpers.rs | 26 +++++ core/src/components/metrics/src/main.rs | 15 ++- 3 files changed, 150 insertions(+), 17 deletions(-) diff --git a/core/common/src/buffer_type.rs b/core/common/src/buffer_type.rs index 22037cc3..63b01c23 100644 --- a/core/common/src/buffer_type.rs +++ b/core/common/src/buffer_type.rs @@ -132,11 +132,26 @@ unsafe impl aya::Pod for TimeStampMetrics {} #[repr(C, packed)] #[derive(Clone, Copy, Zeroable)] pub struct CpuFrequency { - pub cpu_id: u32, - pub cpu_freq: u32, + //pub cpu_id: u32, + //pub cpu_freq: u32, + pub bytes_alloc: u32, + pub pid: u32, + pub command: [u8; 16], } unsafe impl aya::Pod for CpuFrequency {} +#[cfg(feature = "monitoring-structs")] +#[repr(C, packed)] +#[derive(Clone, Copy, Zeroable)] +pub struct MemAlloc { + pub tgid: u32, + pub length: u64, + pub addr: u64, + pub command: [u8; TASK_COMM_LEN], +} +#[cfg(feature = "monitoring-structs")] +unsafe impl aya::Pod for MemAlloc {} + // docs: // This function perform a byte swap from little-endian to big-endian // It's used to reconstruct the correct IPv4 address from the u32 representation @@ -166,10 +181,10 @@ pub enum BufferType { TimeStampMetrics, #[cfg(feature = "monitoring-structs")] CpuFrequency, + #[cfg(feature = "monitoring-structs")] + MemAlloc, } -// IDEA: this is an experimental implementation to centralize buffer reading logic -// TODO: add variant for cortexflow API exporter #[cfg(feature = "buffer-reader")] impl BufferType { #[cfg(feature = "network-structs")] @@ -492,8 +507,8 @@ impl BufferType { buffers: &mut [BytesMut], tot_events: i32, offset: i32, - //exporter: &str, - //metrics: Arc, + exporter: &str, + metrics: Arc, ) { for i in offset..tot_events { let vec_bytes = &buffers[i as usize]; @@ -514,14 +529,69 @@ impl BufferType { let cpu_freq_metrics: CpuFrequency = unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; - //match exporter { - // "otlp" => metrics.record_timestamp_metrics(&time_stamp_event), - // _ => continue, - //} + match exporter { + "otlp" => metrics.record_cpu_bytes_alloc(&cpu_freq_metrics), + _ => continue, + } + + //let cpu_id = cpu_freq_metrics.cpu_id; + //let cpu_freq = cpu_freq_metrics.cpu_freq; + let bytes_alloc = cpu_freq_metrics.bytes_alloc; + //info!( + // "Cpu id: {} Cpu frequency: {} Bytes alloc: {}", + // cpu_id, cpu_freq, bytes_alloc + //); + let pid = cpu_freq_metrics.pid; + let command = cpu_freq_metrics.command; + info!( + "Cpu Bytes alloc: {} pid : {} command: {:?}", + bytes_alloc, pid, command + ); + } + } + } + + #[cfg(feature = "monitoring-structs")] + pub async fn read_mem_alloc( + buffers: &mut [BytesMut], + tot_events: i32, + offset: i32, + exporter: &str, + metrics: Arc, + ) { + for i in offset..tot_events { + let vec_bytes = &buffers[i as usize]; + if vec_bytes.len() < std::mem::size_of::() { + error!( + "Corrupted MemAlloc data. Raw data: {}. Readed {} bytes expected {} bytes", + vec_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(" "), + vec_bytes.len(), + std::mem::size_of::() + ); + continue; + } + if vec_bytes.len() >= std::mem::size_of::() { + let mem_alloc: MemAlloc = + unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; + + match exporter { + "otlp" => metrics.record_enter_mem_alloc(&mem_alloc), + _ => continue, + } + + let tgid = mem_alloc.tgid; + let command = String::from_utf8_lossy(&mem_alloc.command); + let addr = mem_alloc.addr; + let length = mem_alloc.length; - let cpu_id = cpu_freq_metrics.cpu_id; - let cpu_freq = cpu_freq_metrics.cpu_freq; - info!("Cpu id: {} Cpu frequency: {}", cpu_id, cpu_freq); + info!( + "MemAlloc - tgid: {}, command: {}, addr: {}, length: {}", + tgid, command, addr, length + ); } } } @@ -599,8 +669,25 @@ pub async fn read_perf_buffer>( } #[cfg(feature = "monitoring-structs")] BufferType::CpuFrequency => { - BufferType::read_cpu_frequency(&mut buffers, tot_events, offset) - .await + BufferType::read_cpu_frequency( + &mut buffers, + tot_events, + offset, + "otlp", + metrics.clone().expect("Metric required for CpuFrequency"), + ) + .await + } + #[cfg(feature = "monitoring-structs")] + BufferType::MemAlloc => { + BufferType::read_mem_alloc( + &mut buffers, + tot_events, + offset, + "otlp", + metrics.clone().expect("Metric required for MemAlloc"), + ) + .await } } } @@ -628,6 +715,8 @@ pub enum BufferSize { TimeMetricsEvents, #[cfg(feature = "monitoring-structs")] CpuFrequency, + #[cfg(feature = "monitoring-structs")] + MemAlloc, } #[cfg(feature = "buffer-reader")] impl BufferSize { @@ -645,6 +734,8 @@ impl BufferSize { BufferSize::TimeMetricsEvents => std::mem::size_of::(), #[cfg(feature = "monitoring-structs")] BufferSize::CpuFrequency => std::mem::size_of::(), + #[cfg(feature = "monitoring-structs")] + BufferSize::MemAlloc => std::mem::size_of::(), } } pub fn set_buffer(&self) -> Vec { @@ -693,6 +784,11 @@ impl BufferSize { let capacity = self.get_size() * 1024; return vec![BytesMut::with_capacity(capacity); tot_cpu]; } + #[cfg(feature = "monitoring-structs")] + BufferSize::MemAlloc => { + let capacity = self.get_size() * 1024; + return vec![BytesMut::with_capacity(capacity); tot_cpu]; + } } } } diff --git a/core/src/components/metrics/src/helpers.rs b/core/src/components/metrics/src/helpers.rs index 8eb01d37..ba63c9f9 100644 --- a/core/src/components/metrics/src/helpers.rs +++ b/core/src/components/metrics/src/helpers.rs @@ -66,10 +66,15 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a .remove("cpu_frequency") .expect("Cannot create cpu_frequency_perf_buffer"); + let (_mem_alloc_array, mem_alloc_perf_buffer) = maps + .remove("mem_alloc") + .expect("Cannot create mem_alloc perf buffer"); + // Allocate byte-buffers sized for each structure type let net_metrics_buffers = BufferSize::NetworkMetricsEvents.set_buffer(); let time_stamp_events_buffers = BufferSize::TimeMetricsEvents.set_buffer(); let cpu_frequency_events_buffers = BufferSize::CpuFrequency.set_buffer(); + let mem_alloc_buffers = BufferSize::MemAlloc.set_buffer(); let metrics = Arc::new(Metrics::new(&meter)); @@ -120,6 +125,21 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a }) }; + let mem_alloc_metrics = { + let metrics = Arc::clone(&metrics); + let mut array_buffers = mem_alloc_perf_buffer; + let mut buffers = mem_alloc_buffers; + tokio::spawn(async move { + read_perf_buffer( + array_buffers, + buffers, + BufferType::MemAlloc, + Some(metrics), + ) + .await; + }) + }; + info!("Event listeners started, entering main loop..."); tokio::select! { @@ -141,6 +161,12 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a } } + result = mem_alloc_metrics => { + if let Err(e) = result { + error!("MemAlloc events task failed: {:?}", e); + } + } + _ = signal::ctrl_c() => { info!("Ctrl-C received, shutting down..."); } diff --git a/core/src/components/metrics/src/main.rs b/core/src/components/metrics/src/main.rs index b8f3c862..591b4b3c 100644 --- a/core/src/components/metrics/src/main.rs +++ b/core/src/components/metrics/src/main.rs @@ -47,6 +47,7 @@ async fn main() -> Result<(), anyhow::Error> { let tcp_rev_bpf = bpf.clone(); let tcp_v6_bpf = bpf.clone(); let cpu_frequency = bpf.clone(); + let mem_alloc_bpf = bpf.clone(); info!("Running Ebpf logger"); info!("loading programs"); @@ -58,6 +59,7 @@ async fn main() -> Result<(), anyhow::Error> { "time_stamp_events".to_string(), "net_metrics".to_string(), "cpu_frequency".to_string(), + "mem_alloc".to_string(), ]; match init_bpf_maps(bpf.clone(), map_data) { @@ -93,8 +95,17 @@ async fn main() -> Result<(), anyhow::Error> { load_tracepoint_program( cpu_frequency, "trace_cpu_frequency", - "power", - "cpu_frequency", + "percpu", + "percpu_alloc_percpu", + ) + .context( + "An error occurred during the execution of load_program function", + )?; + load_tracepoint_program( + mem_alloc_bpf, + "trace_enter_mmap", + "syscalls", + "sys_enter_mmap", ) .context( "An error occurred during the execution of load_program function", From fc158c8a223391750cdab9237bad24f9f0a574f2 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sun, 21 Jun 2026 22:48:36 +0200 Subject: [PATCH 07/40] (feat: #186) : Added scheduler tracing metrics (sched_stat_wait,sched_stat_runtime) --- core/src/components/metrics_tracer/src/cpu.rs | 24 +++++++++ .../metrics_tracer/src/data_structures.rs | 22 ++++++++ .../src/components/metrics_tracer/src/main.rs | 52 +++++++++++++++++-- 3 files changed, 94 insertions(+), 4 deletions(-) diff --git a/core/src/components/metrics_tracer/src/cpu.rs b/core/src/components/metrics_tracer/src/cpu.rs index 02eae0f1..ba545150 100644 --- a/core/src/components/metrics_tracer/src/cpu.rs +++ b/core/src/components/metrics_tracer/src/cpu.rs @@ -33,3 +33,27 @@ pub fn per_cpu_bytes_alloc(ctx: &TracePointContext) -> Result<((u32, u32, [u8; 1 Ok((bytes_alloc, pid, command)) } + +pub fn sched_stat_wait(ctx: &TracePointContext) -> Result<((u32, u64, [u8; 16])), i64> { + let pid_offset = 4; + let delay_offset = 16; + + let pid = unsafe { ctx.read_at(pid_offset) }?; + + let delay = unsafe { ctx.read_at(delay_offset) }?; + let command = ctx.command()?; + + Ok((pid, delay, command)) +} + +pub fn sched_stat_runtime(ctx: &TracePointContext) -> Result<((u32, u64, [u8; 16])), i64> { + let pid_offset = 4; + let runtime_offset = 16; + + let pid = unsafe { ctx.read_at(pid_offset) }?; + + let runtime = unsafe { ctx.read_at(runtime_offset) }?; + let command = ctx.command()?; + + Ok((pid, runtime, command)) +} diff --git a/core/src/components/metrics_tracer/src/data_structures.rs b/core/src/components/metrics_tracer/src/data_structures.rs index 877cc991..91249a14 100644 --- a/core/src/components/metrics_tracer/src/data_structures.rs +++ b/core/src/components/metrics_tracer/src/data_structures.rs @@ -63,6 +63,22 @@ pub struct MemAlloc { pub(crate) command: [u8; 16], } +#[repr(C, packed)] +#[derive(Copy, Clone)] +pub struct SchedStatWait { + pub(crate) tgid: u32, + pub(crate) delay: u64, + pub(crate) command: [u8; 16], +} + +#[repr(C, packed)] +#[derive(Copy, Clone)] +pub struct SchedStatRuntime { + pub(crate) tgid: u32, + pub(crate) runtime: u64, + pub(crate) command: [u8; 16], +} + // Map: connect-start timestamp by socket pointer #[map(name = "time_stamp_start")] pub static mut TIME_STAMP_START: HashMap<*mut core::ffi::c_void, TimeStampStartInfo> = @@ -81,3 +97,9 @@ pub static CPU_FREQUENCY: PerfEventArray = PerfEventArray::new(0); #[map(name = "mem_alloc")] pub static MEM_ALLOC: PerfEventArray = PerfEventArray::new(0); + +#[map(name = "sched_stat_wait")] +pub static SCHED_STAT_WAIT: PerfEventArray = PerfEventArray::new(0); + +#[map(name = "sched_stat_runtime")] +pub static SCHED_STAT_RUNTIME: PerfEventArray = PerfEventArray::new(0); diff --git a/core/src/components/metrics_tracer/src/main.rs b/core/src/components/metrics_tracer/src/main.rs index bdc44550..1ff160ad 100644 --- a/core/src/components/metrics_tracer/src/main.rs +++ b/core/src/components/metrics_tracer/src/main.rs @@ -8,12 +8,12 @@ mod data_structures; mod memory; use crate::bindings::net_device; -use crate::cpu::{cpu_idle, per_cpu_bytes_alloc}; -use crate::data_structures::CPU_FREQUENCY; +use crate::cpu::{cpu_idle, per_cpu_bytes_alloc, sched_stat_runtime, sched_stat_wait}; use crate::data_structures::CpuFrequency; -use crate::data_structures::MEM_ALLOC; -use crate::data_structures::MemAlloc; use crate::data_structures::NET_METRICS; +use crate::data_structures::{CPU_FREQUENCY, SchedStatWait}; +use crate::data_structures::{MEM_ALLOC, SCHED_STAT_RUNTIME, SCHED_STAT_WAIT}; +use crate::data_structures::{MemAlloc, SchedStatRuntime}; use crate::data_structures::{ NetworkMetrics, TASK_COMM_LEN, TIME_STAMP_EVENTS, TIME_STAMP_START, TimeStampEvent, TimeStampStartInfo, @@ -329,6 +329,50 @@ fn trace_memory_allocation(ctx: &TracePointContext) -> Result<(), i64> { Ok(()) } +#[tracepoint] +fn trace_sched_stat_wait(ctx: TracePointContext) -> u32 { + match sched_stat_wait_tracer(&ctx) { + Ok(_) => 0, + Err(e) => e as u32, + } +} + +fn sched_stat_wait_tracer(ctx: &TracePointContext) -> Result<(), i64> { + let (tgid, delay, command) = sched_stat_wait(ctx)?; + + let sched_stat_wait_data = SchedStatWait { + tgid, + delay, + command, + }; + + unsafe { SCHED_STAT_WAIT.output(ctx, &sched_stat_wait_data, 0) }; + + Ok(()) +} + +#[tracepoint] +fn trace_sched_stat_runtime(ctx: TracePointContext) -> u32 { + match sched_stat_runtime_tracer(&ctx) { + Ok(_) => 0, + Err(e) => e as u32, + } +} + +fn sched_stat_runtime_tracer(ctx: &TracePointContext) -> Result<(), i64> { + let (tgid, runtime, command) = sched_stat_runtime(ctx)?; + + let sched_stat_runtime_data = SchedStatRuntime { + tgid, + runtime, + command, + }; + + unsafe { SCHED_STAT_RUNTIME.output(ctx, &sched_stat_runtime_data, 0) }; + + Ok(()) +} + // panic handler #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { From 84ca27fb5e712ae0beec0ca30ac80af9b730e898 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sun, 21 Jun 2026 22:49:32 +0200 Subject: [PATCH 08/40] (feat: #186) : added userspace consumer for scheduler metrics --- core/common/src/buffer_type.rs | 154 +++++++++++++++++++++ core/common/src/otel_metrics.rs | 54 +++++++- core/src/components/metrics/src/helpers.rs | 52 +++++++ core/src/components/metrics/src/main.rs | 22 +++ 4 files changed, 281 insertions(+), 1 deletion(-) diff --git a/core/common/src/buffer_type.rs b/core/common/src/buffer_type.rs index 63b01c23..887d855a 100644 --- a/core/common/src/buffer_type.rs +++ b/core/common/src/buffer_type.rs @@ -152,6 +152,28 @@ pub struct MemAlloc { #[cfg(feature = "monitoring-structs")] unsafe impl aya::Pod for MemAlloc {} +#[cfg(feature = "monitoring-structs")] +#[repr(C, packed)] +#[derive(Clone, Copy, Zeroable)] +pub struct SchedStatWait { + pub tgid: u32, + pub delay: u64, + pub command: [u8; TASK_COMM_LEN], +} +#[cfg(feature = "monitoring-structs")] +unsafe impl aya::Pod for SchedStatWait {} + +#[cfg(feature = "monitoring-structs")] +#[repr(C, packed)] +#[derive(Clone, Copy, Zeroable)] +pub struct SchedStatRuntime { + pub tgid: u32, + pub runtime: u64, + pub command: [u8; TASK_COMM_LEN], +} +#[cfg(feature = "monitoring-structs")] +unsafe impl aya::Pod for SchedStatRuntime {} + // docs: // This function perform a byte swap from little-endian to big-endian // It's used to reconstruct the correct IPv4 address from the u32 representation @@ -183,6 +205,10 @@ pub enum BufferType { CpuFrequency, #[cfg(feature = "monitoring-structs")] MemAlloc, + #[cfg(feature = "monitoring-structs")] + SchedStatWait, + #[cfg(feature = "monitoring-structs")] + SchedStatRuntime, } #[cfg(feature = "buffer-reader")] @@ -595,6 +621,94 @@ impl BufferType { } } } + + #[cfg(feature = "monitoring-structs")] + pub async fn read_sched_stat_wait( + buffers: &mut [BytesMut], + tot_events: i32, + offset: i32, + exporter: &str, + metrics: Arc, + ) { + for i in offset..tot_events { + let vec_bytes = &buffers[i as usize]; + if vec_bytes.len() < std::mem::size_of::() { + error!( + "Corrupted SchedStatWait data. Raw data: {}. Readed {} bytes expected {} bytes", + vec_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(" "), + vec_bytes.len(), + std::mem::size_of::() + ); + continue; + } + if vec_bytes.len() >= std::mem::size_of::() { + let sched_stat_wait: SchedStatWait = + unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; + + match exporter { + "otlp" => metrics.record_sched_stat_wait(&sched_stat_wait), + _ => continue, + } + + let tgid = sched_stat_wait.tgid; + let command = String::from_utf8_lossy(&sched_stat_wait.command); + let delay = sched_stat_wait.delay; + + info!( + "SchedStatWait - tgid: {}, command: {}, delay: {}", + tgid, command, delay + ); + } + } + } + + #[cfg(feature = "monitoring-structs")] + pub async fn read_sched_stat_runtime( + buffers: &mut [BytesMut], + tot_events: i32, + offset: i32, + exporter: &str, + metrics: Arc, + ) { + for i in offset..tot_events { + let vec_bytes = &buffers[i as usize]; + if vec_bytes.len() < std::mem::size_of::() { + error!( + "Corrupted SchedStatRuntime data. Raw data: {}. Readed {} bytes expected {} bytes", + vec_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(" "), + vec_bytes.len(), + std::mem::size_of::() + ); + continue; + } + if vec_bytes.len() >= std::mem::size_of::() { + let sched_stat_runtime: SchedStatRuntime = + unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; + + match exporter { + "otlp" => metrics.record_sched_stat_runtime(&sched_stat_runtime), + _ => continue, + } + + let tgid = sched_stat_runtime.tgid; + let command = String::from_utf8_lossy(&sched_stat_runtime.command); + let runtime = sched_stat_runtime.runtime; + + info!( + "SchedStatRuntime - tgid: {}, command: {}, runtime: {}", + tgid, command, runtime + ); + } + } + } } // docs: read buffer function: @@ -689,6 +803,28 @@ pub async fn read_perf_buffer>( ) .await } + #[cfg(feature = "monitoring-structs")] + BufferType::SchedStatWait => { + BufferType::read_sched_stat_wait( + &mut buffers, + tot_events, + offset, + "otlp", + metrics.clone().expect("Metric required for SchedStatWait"), + ) + .await + } + #[cfg(feature = "monitoring-structs")] + BufferType::SchedStatRuntime => { + BufferType::read_sched_stat_runtime( + &mut buffers, + tot_events, + offset, + "otlp", + metrics.clone().expect("Metric required for SchedStatRuntime"), + ) + .await + } } } } @@ -717,6 +853,10 @@ pub enum BufferSize { CpuFrequency, #[cfg(feature = "monitoring-structs")] MemAlloc, + #[cfg(feature = "monitoring-structs")] + SchedStatWait, + #[cfg(feature = "monitoring-structs")] + SchedStatRuntime, } #[cfg(feature = "buffer-reader")] impl BufferSize { @@ -736,6 +876,10 @@ impl BufferSize { BufferSize::CpuFrequency => std::mem::size_of::(), #[cfg(feature = "monitoring-structs")] BufferSize::MemAlloc => std::mem::size_of::(), + #[cfg(feature = "monitoring-structs")] + BufferSize::SchedStatWait => std::mem::size_of::(), + #[cfg(feature = "monitoring-structs")] + BufferSize::SchedStatRuntime => std::mem::size_of::(), } } pub fn set_buffer(&self) -> Vec { @@ -789,6 +933,16 @@ impl BufferSize { let capacity = self.get_size() * 1024; return vec![BytesMut::with_capacity(capacity); tot_cpu]; } + #[cfg(feature = "monitoring-structs")] + BufferSize::SchedStatWait => { + let capacity = self.get_size() * 1024; + return vec![BytesMut::with_capacity(capacity); tot_cpu]; + } + #[cfg(feature = "monitoring-structs")] + BufferSize::SchedStatRuntime => { + let capacity = self.get_size() * 1024; + return vec![BytesMut::with_capacity(capacity); tot_cpu]; + } } } } diff --git a/core/common/src/otel_metrics.rs b/core/common/src/otel_metrics.rs index c256cc1d..37611729 100644 --- a/core/common/src/otel_metrics.rs +++ b/core/common/src/otel_metrics.rs @@ -11,7 +11,9 @@ //! extracted from the eBPF struct, allowing downstream collectors to group //! telemetry by process. -use crate::buffer_type::{CpuFrequency, MemAlloc, NetworkMetrics, TimeStampMetrics}; +use crate::buffer_type::{ + CpuFrequency, MemAlloc, NetworkMetrics, SchedStatRuntime, SchedStatWait, TimeStampMetrics, +}; use opentelemetry::KeyValue; use opentelemetry::metrics::{Counter, Gauge, Histogram, Meter}; pub struct Metrics { @@ -47,6 +49,12 @@ pub struct Metrics { /// Observed bytes requested via mmap syscalls. pub enter_mem_alloc: Gauge, + + /// Observed scheduler wait time in nanoseconds (sched_stat_wait). + pub sched_stat_wait: Gauge, + + /// Observed scheduler runtime in nanoseconds (sched_stat_runtime). + pub sched_stat_runtime: Gauge, } impl Metrics { @@ -112,6 +120,18 @@ impl Metrics { .with_description("Bytes requested via mmap syscalls") .build(); + // scheduler wait time in nanoseconds + let sched_stat_wait = meter + .i64_gauge("sched_stat_wait") + .with_description("Scheduler wait time in nanoseconds from sched_stat_wait") + .build(); + + // scheduler runtime in nanoseconds + let sched_stat_runtime = meter + .i64_gauge("sched_stat_runtime") + .with_description("Scheduler runtime in nanoseconds from sched_stat_runtime") + .build(); + Self { events_total, packets_total, @@ -123,6 +143,8 @@ impl Metrics { cpu_bytes_alloc_events_total, mem_alloc_events_total, enter_mem_alloc, + sched_stat_wait, + sched_stat_runtime, } } @@ -201,4 +223,34 @@ impl Metrics { self.mem_alloc_events_total.add(1, attrs); self.enter_mem_alloc.record(m.length as i64, attrs); } + + /// Record a single [`SchedStatWait`] event. + /// + /// Records `delay` in the `sched_stat_wait` gauge. No shared or dedicated + /// counter is incremented, as requested. + pub fn record_sched_stat_wait(&self, m: &SchedStatWait) { + let comm = String::from_utf8_lossy(&m.command); + let command = comm.trim_end_matches('\0').to_string(); + let attrs = &[ + KeyValue::new("tgid", m.tgid as i64), + KeyValue::new("command", command), + ]; + + self.sched_stat_wait.record(m.delay as i64, attrs); + } + + /// Record a single [`SchedStatRuntime`] event. + /// + /// Records `runtime` in the `sched_stat_runtime` gauge. No shared or + /// dedicated counter is incremented, as requested. + pub fn record_sched_stat_runtime(&self, m: &SchedStatRuntime) { + let comm = String::from_utf8_lossy(&m.command); + let command = comm.trim_end_matches('\0').to_string(); + let attrs = &[ + KeyValue::new("tgid", m.tgid as i64), + KeyValue::new("command", command), + ]; + + self.sched_stat_runtime.record(m.runtime as i64, attrs); + } } diff --git a/core/src/components/metrics/src/helpers.rs b/core/src/components/metrics/src/helpers.rs index ba63c9f9..95232c53 100644 --- a/core/src/components/metrics/src/helpers.rs +++ b/core/src/components/metrics/src/helpers.rs @@ -70,11 +70,21 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a .remove("mem_alloc") .expect("Cannot create mem_alloc perf buffer"); + let (_sched_stat_wait_array, sched_stat_wait_perf_buffer) = maps + .remove("sched_stat_wait") + .expect("Cannot create sched_stat_wait perf buffer"); + + let (_sched_stat_runtime_array, sched_stat_runtime_perf_buffer) = maps + .remove("sched_stat_runtime") + .expect("Cannot create sched_stat_runtime perf buffer"); + // Allocate byte-buffers sized for each structure type let net_metrics_buffers = BufferSize::NetworkMetricsEvents.set_buffer(); let time_stamp_events_buffers = BufferSize::TimeMetricsEvents.set_buffer(); let cpu_frequency_events_buffers = BufferSize::CpuFrequency.set_buffer(); let mem_alloc_buffers = BufferSize::MemAlloc.set_buffer(); + let sched_stat_wait_buffers = BufferSize::SchedStatWait.set_buffer(); + let sched_stat_runtime_buffers = BufferSize::SchedStatRuntime.set_buffer(); let metrics = Arc::new(Metrics::new(&meter)); @@ -140,6 +150,36 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a }) }; + let sched_stat_wait_metrics = { + let metrics = Arc::clone(&metrics); + let mut array_buffers = sched_stat_wait_perf_buffer; + let mut buffers = sched_stat_wait_buffers; + tokio::spawn(async move { + read_perf_buffer( + array_buffers, + buffers, + BufferType::SchedStatWait, + Some(metrics), + ) + .await; + }) + }; + + let sched_stat_runtime_metrics = { + let metrics = Arc::clone(&metrics); + let mut array_buffers = sched_stat_runtime_perf_buffer; + let mut buffers = sched_stat_runtime_buffers; + tokio::spawn(async move { + read_perf_buffer( + array_buffers, + buffers, + BufferType::SchedStatRuntime, + Some(metrics), + ) + .await; + }) + }; + info!("Event listeners started, entering main loop..."); tokio::select! { @@ -167,6 +207,18 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a } } + result = sched_stat_wait_metrics => { + if let Err(e) = result { + error!("SchedStatWait events task failed: {:?}", e); + } + } + + result = sched_stat_runtime_metrics => { + if let Err(e) = result { + error!("SchedStatRuntime events task failed: {:?}", e); + } + } + _ = signal::ctrl_c() => { info!("Ctrl-C received, shutting down..."); } diff --git a/core/src/components/metrics/src/main.rs b/core/src/components/metrics/src/main.rs index 591b4b3c..a3657b26 100644 --- a/core/src/components/metrics/src/main.rs +++ b/core/src/components/metrics/src/main.rs @@ -48,6 +48,8 @@ async fn main() -> Result<(), anyhow::Error> { let tcp_v6_bpf = bpf.clone(); let cpu_frequency = bpf.clone(); let mem_alloc_bpf = bpf.clone(); + let sched_stat_wait_bpf = bpf.clone(); + let sched_stat_runtime_bpf = bpf.clone(); info!("Running Ebpf logger"); info!("loading programs"); @@ -60,6 +62,8 @@ async fn main() -> Result<(), anyhow::Error> { "net_metrics".to_string(), "cpu_frequency".to_string(), "mem_alloc".to_string(), + "sched_stat_wait".to_string(), + "sched_stat_runtime".to_string(), ]; match init_bpf_maps(bpf.clone(), map_data) { @@ -110,6 +114,24 @@ async fn main() -> Result<(), anyhow::Error> { .context( "An error occurred during the execution of load_program function", )?; + load_tracepoint_program( + sched_stat_wait_bpf, + "trace_sched_stat_wait", + "sched", + "sched_stat_wait", + ) + .context( + "An error occurred during the execution of load_program function", + )?; + load_tracepoint_program( + sched_stat_runtime_bpf, + "trace_sched_stat_runtime", + "sched", + "sched_stat_runtime", + ) + .context( + "An error occurred during the execution of load_program function", + )?; } // Hand off to the async event consumer From c8141f474af825df6c7f2b6fc6c6165bd82eca6d Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Mon, 22 Jun 2026 14:29:11 +0200 Subject: [PATCH 09/40] (example): added grafana dashboard example --- Examples/dashboard-template.yaml | 656 +++++++++++++++++++++++++++++++ 1 file changed, 656 insertions(+) create mode 100644 Examples/dashboard-template.yaml diff --git a/Examples/dashboard-template.yaml b/Examples/dashboard-template.yaml new file mode 100644 index 00000000..84b6f930 --- /dev/null +++ b/Examples/dashboard-template.yaml @@ -0,0 +1,656 @@ +apiVersion: dashboard.grafana.app/v2 +kind: Dashboard +metadata: + name: adzxtsp + namespace: default + uid: d2def6af-04c7-43d3-9f90-969b4bc414ee + resourceVersion: '1782049084952992' + generation: 32 + creationTimestamp: '2026-06-04T18:44:45Z' + labels: + grafana.app/deprecatedInternalID: '4461471636946944' + annotations: + grafana.app/createdBy: user:afo4ysywj3xmof + grafana.app/folder: '' + grafana.app/saved-from-ui: Grafana v13.0.2 (3fcdbc5a) + grafana.app/updatedBy: user:afo4ysywj3xmof + grafana.app/updatedTimestamp: '2026-06-21T13:38:04Z' +spec: + annotations: + - kind: AnnotationQuery + spec: + query: + kind: DataQuery + group: grafana + version: v0 + datasource: + name: '-- Grafana --' + spec: {} + enable: true + hide: true + iconColor: rgba(0, 211, 255, 1) + name: Annotations & Alerts + builtIn: true + cursorSync: 'Off' + editable: true + elements: + panel-1: + kind: Panel + spec: + id: 1 + title: Total packets + description: '' + links: [] + data: + kind: QueryGroup + spec: + queries: + - kind: PanelQuery + spec: + query: + kind: DataQuery + group: prometheus + version: v0 + datasource: + name: ffo50cmg1o1s0a + spec: + editorMode: builder + expr: cortexbrain_packets_total + legendFormat: __auto + range: true + refId: A + hidden: false + transformations: [] + queryOptions: {} + vizConfig: + kind: VizConfig + group: timeseries + version: 13.0.2 + spec: + options: + annotations: + clustering: -1 + multiLane: false + legend: + calcs: [] + displayMode: list + placement: bottom + showLegend: true + tooltip: + hideZeros: false + mode: single + sort: none + fieldConfig: + defaults: + thresholds: + mode: absolute + steps: + - value: 0 + color: green + - value: 80 + color: red + color: + mode: palette-classic + custom: + axisBorderShow: false + axisCenteredZero: false + axisColorMode: text + axisLabel: '' + axisPlacement: auto + barAlignment: 0 + barWidthFactor: 0.6 + drawStyle: line + fillOpacity: 0 + gradientMode: none + hideFrom: + legend: false + tooltip: false + viz: false + insertNulls: false + lineInterpolation: linear + lineWidth: 1 + pointSize: 5 + scaleDistribution: + type: linear + showPoints: auto + showValues: false + spanNulls: false + stacking: + group: A + mode: none + thresholdsStyle: + mode: 'off' + overrides: [] + panel-2: + kind: Panel + spec: + id: 2 + title: Cpu bytes allocation + description: '' + links: [] + data: + kind: QueryGroup + spec: + queries: + - kind: PanelQuery + spec: + query: + kind: DataQuery + group: prometheus + version: v0 + datasource: + name: ffo50cmg1o1s0a + spec: + editorMode: builder + expr: cortexbrain_cpu_bytes_alloc + legendFormat: __auto + range: true + refId: A + hidden: false + transformations: [] + queryOptions: {} + vizConfig: + kind: VizConfig + group: timeseries + version: 13.0.2 + spec: + options: + annotations: + clustering: -1 + multiLane: false + legend: + calcs: [] + displayMode: list + placement: bottom + showLegend: true + tooltip: + hideZeros: false + mode: single + sort: none + fieldConfig: + defaults: + thresholds: + mode: absolute + steps: + - value: 0 + color: green + - value: 80 + color: red + color: + mode: palette-classic + custom: + axisBorderShow: false + axisCenteredZero: false + axisColorMode: text + axisLabel: '' + axisPlacement: auto + barAlignment: 0 + barWidthFactor: 0.6 + drawStyle: line + fillOpacity: 25 + gradientMode: none + hideFrom: + legend: false + tooltip: false + viz: false + insertNulls: false + lineInterpolation: linear + lineWidth: 1 + pointSize: 5 + scaleDistribution: + type: linear + showPoints: auto + showValues: false + spanNulls: false + stacking: + group: A + mode: normal + thresholdsStyle: + mode: 'off' + overrides: [] + panel-3: + kind: Panel + spec: + id: 3 + title: Timestamp microseconds histograms + description: '' + links: [] + data: + kind: QueryGroup + spec: + queries: + - kind: PanelQuery + spec: + query: + kind: DataQuery + group: prometheus + version: v0 + datasource: + name: ffo50cmg1o1s0a + spec: + editorMode: builder + expr: cortexbrain_cpu_bytes_alloc + legendFormat: __auto + range: true + refId: A + hidden: false + transformations: [] + queryOptions: {} + vizConfig: + kind: VizConfig + group: bargauge + version: 13.0.2 + spec: + options: + displayMode: gradient + legend: + calcs: [] + displayMode: list + placement: bottom + showLegend: false + maxVizHeight: 300 + minVizHeight: 16 + minVizWidth: 8 + namePlacement: auto + orientation: horizontal + reduceOptions: + calcs: + - lastNotNull + fields: '' + values: false + showUnfilled: true + sizing: auto + valueMode: color + fieldConfig: + defaults: + thresholds: + mode: percentage + steps: + - value: 0 + color: green + - value: 80 + color: red + color: + mode: thresholds + fieldMinMax: false + overrides: [] + transparent: true + panel-4: + kind: Panel + spec: + id: 4 + title: Mem Alloc size + description: '' + links: [] + data: + kind: QueryGroup + spec: + queries: + - kind: PanelQuery + spec: + query: + kind: DataQuery + group: prometheus + version: v0 + datasource: + name: ffo50cmg1o1s0a + spec: + editorMode: builder + expr: cortexbrain_enter_mem_alloc + legendFormat: __auto + range: true + refId: A + hidden: false + transformations: [] + queryOptions: {} + vizConfig: + kind: VizConfig + group: timeseries + version: 13.0.2 + spec: + options: + annotations: + clustering: -1 + multiLane: false + legend: + calcs: [] + displayMode: list + placement: bottom + showLegend: true + tooltip: + hideZeros: false + mode: single + sort: none + fieldConfig: + defaults: + thresholds: + mode: absolute + steps: + - value: 0 + color: green + - value: 80 + color: red + color: + mode: palette-classic + custom: + axisBorderShow: false + axisCenteredZero: false + axisColorMode: text + axisLabel: '' + axisPlacement: auto + barAlignment: 0 + barWidthFactor: 0.6 + drawStyle: line + fillOpacity: 25 + gradientMode: none + hideFrom: + legend: false + tooltip: false + viz: false + insertNulls: false + lineInterpolation: linear + lineWidth: 1 + pointSize: 5 + scaleDistribution: + type: linear + showPoints: auto + showValues: false + spanNulls: false + stacking: + group: A + mode: normal + thresholdsStyle: + mode: 'off' + overrides: [] + panel-5: + kind: Panel + spec: + id: 5 + title: Mem alloc tot events + description: '' + links: [] + data: + kind: QueryGroup + spec: + queries: + - kind: PanelQuery + spec: + query: + kind: DataQuery + group: prometheus + version: v0 + datasource: + name: ffo50cmg1o1s0a + spec: + editorMode: builder + expr: cortexbrain_mem_alloc_events_total + legendFormat: __auto + range: true + refId: A + hidden: false + transformations: [] + queryOptions: {} + vizConfig: + kind: VizConfig + group: timeseries + version: 13.0.2 + spec: + options: + annotations: + clustering: -1 + multiLane: false + legend: + calcs: [] + displayMode: list + placement: bottom + showLegend: true + tooltip: + hideZeros: false + mode: single + sort: none + fieldConfig: + defaults: + thresholds: + mode: absolute + steps: + - value: 0 + color: green + - value: 80 + color: red + color: + mode: palette-classic + custom: + axisBorderShow: false + axisCenteredZero: false + axisColorMode: text + axisLabel: '' + axisPlacement: auto + barAlignment: 0 + barWidthFactor: 0.6 + drawStyle: line + fillOpacity: 0 + gradientMode: none + hideFrom: + legend: false + tooltip: false + viz: false + insertNulls: false + lineInterpolation: linear + lineWidth: 1 + pointSize: 5 + scaleDistribution: + type: linear + showPoints: auto + showValues: false + spanNulls: false + stacking: + group: A + mode: none + thresholdsStyle: + mode: 'off' + overrides: [] + panel-6: + kind: Panel + spec: + id: 6 + title: Scheduler Runtime (us) + description: '' + links: [] + data: + kind: QueryGroup + spec: + queries: + - kind: PanelQuery + spec: + query: + kind: DataQuery + group: prometheus + version: v0 + datasource: + name: ffo50cmg1o1s0a + spec: + editorMode: builder + expr: cortexbrain_sched_stat_runtime + legendFormat: __auto + range: true + refId: A + hidden: false + transformations: [] + queryOptions: {} + vizConfig: + kind: VizConfig + group: bargauge + version: 13.0.2 + spec: + options: + displayMode: gradient + legend: + calcs: [] + displayMode: list + placement: bottom + showLegend: false + maxVizHeight: 300 + minVizHeight: 16 + minVizWidth: 8 + namePlacement: auto + orientation: horizontal + reduceOptions: + calcs: + - lastNotNull + fields: '' + values: false + showUnfilled: true + sizing: auto + valueMode: color + fieldConfig: + defaults: + thresholds: + mode: absolute + steps: + - value: 0 + color: green + - value: 80 + color: red + color: + mode: thresholds + fieldMinMax: false + overrides: [] + transparent: true + layout: + kind: TabsLayout + spec: + tabs: + - kind: TabsLayoutTab + spec: + title: Events + layout: + kind: AutoGridLayout + spec: + maxColumnCount: 2 + columnWidthMode: standard + rowHeightMode: standard + items: + - kind: AutoGridLayoutItem + spec: + element: + kind: ElementReference + name: panel-1 + - kind: AutoGridLayoutItem + spec: + element: + kind: ElementReference + name: panel-2 + - kind: AutoGridLayoutItem + spec: + element: + kind: ElementReference + name: panel-4 + - kind: AutoGridLayoutItem + spec: + element: + kind: ElementReference + name: panel-5 + - kind: TabsLayoutTab + spec: + title: Latencies + layout: + kind: AutoGridLayout + spec: + maxColumnCount: 3 + columnWidthMode: standard + rowHeightMode: standard + items: + - kind: AutoGridLayoutItem + spec: + element: + kind: ElementReference + name: panel-6 + - kind: AutoGridLayoutItem + spec: + element: + kind: ElementReference + name: panel-3 + links: [] + liveNow: false + preload: false + tags: [] + timeSettings: + timezone: browser + from: now-6h + to: now + autoRefresh: '' + autoRefreshIntervals: + - 5s + - 10s + - 30s + - 1m + - 5m + - 15m + - 30m + - 1h + - 2h + - 1d + hideTimepicker: false + fiscalYearStartMonth: 0 + title: Dashboard + variables: + - kind: CustomVariable + spec: + name: custom0 + query: '' + current: + text: '' + value: '' + options: [] + multi: false + includeAll: false + hide: dontHide + skipUrlSync: false + allowCustomValue: true + valuesFormat: csv + - kind: QueryVariable + spec: + name: query0 + current: + text: '' + value: '' + hide: dontHide + refresh: onDashboardLoad + skipUrlSync: false + query: + kind: DataQuery + group: prometheus + version: v0 + datasource: + name: ffo50cmg1o1s0a + spec: {} + regex: '' + regexApplyTo: value + sort: disabled + options: [] + multi: false + includeAll: false + allowCustomValue: true + - kind: DatasourceVariable + spec: + name: datasource0 + pluginId: prometheus + refresh: onDashboardLoad + regex: '' + current: + text: prometheus + value: ffo50cmg1o1s0a + options: [] + multi: false + includeAll: false + hide: dontHide + skipUrlSync: false + allowCustomValue: true + preferences: + layout: + kind: AutoGridLayout + spec: + maxColumnCount: 3 + columnWidthMode: standard + rowHeightMode: standard + items: [] From 85b28841160044bce2f6451b023f53555912bc5d Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Mon, 22 Jun 2026 22:35:59 +0200 Subject: [PATCH 10/40] [#186]: Added cpu idle metrics in kernel space --- core/src/components/metrics_tracer/src/cpu.rs | 22 ++++++++++++++++++- .../metrics_tracer/src/data_structures.rs | 14 ++++++++++++ .../src/components/metrics_tracer/src/main.rs | 6 ++--- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/core/src/components/metrics_tracer/src/cpu.rs b/core/src/components/metrics_tracer/src/cpu.rs index ba545150..9d4b73a0 100644 --- a/core/src/components/metrics_tracer/src/cpu.rs +++ b/core/src/components/metrics_tracer/src/cpu.rs @@ -5,7 +5,7 @@ use aya_ebpf::{EbpfContext, programs::TracePointContext}; use aya_log_ebpf::info; -use crate::data_structures::{CPU_FREQUENCY, CpuFrequency}; +use crate::data_structures::{CPU_FREQUENCY, CPU_IDLE, CPU_IDLE_LAST_STATE, CpuFrequency, CpuIdle}; pub fn cpu_idle(ctx: TracePointContext) -> Result<(), i64> { let state_offset = 8; @@ -13,6 +13,26 @@ pub fn cpu_idle(ctx: TracePointContext) -> Result<(), i64> { let state: u32 = unsafe { ctx.read_at(state_offset) }?; let cpu_id: u32 = unsafe { ctx.read_at(cpu_id_offset) }?; + let map_ptr = unsafe { &raw mut CPU_IDLE_LAST_STATE }; + + // skip the data when: + // - last_state is equal to the current state + // - last_state is equal to 4294967295 or -1. This codes means that the cpu is exiting from the current state and entering a new state + let emit = match unsafe { (*map_ptr).get(&cpu_id) } { + Some(last_state) + if (*last_state == state) || (*last_state == 4294967295) || (*last_state == -1) => + { + false + } + _ => true, + }; + + if emit { + let _ = unsafe { (*map_ptr).insert(&cpu_id, &state, 0) }; + let event = CpuIdle { cpu_id, state }; + unsafe { CPU_IDLE.output(&ctx, &event, 0) }; + } + info!(&ctx, "CPU idle: State: {} cpu_id: {}", state, cpu_id); Ok(()) } diff --git a/core/src/components/metrics_tracer/src/data_structures.rs b/core/src/components/metrics_tracer/src/data_structures.rs index 91249a14..d74ef35a 100644 --- a/core/src/components/metrics_tracer/src/data_structures.rs +++ b/core/src/components/metrics_tracer/src/data_structures.rs @@ -79,6 +79,13 @@ pub struct SchedStatRuntime { pub(crate) command: [u8; 16], } +#[repr(C, packed)] +#[derive(Copy, Clone)] +pub struct CpuIdle { + pub(crate) cpu_id: u32, + pub(crate) state: u32, +} + // Map: connect-start timestamp by socket pointer #[map(name = "time_stamp_start")] pub static mut TIME_STAMP_START: HashMap<*mut core::ffi::c_void, TimeStampStartInfo> = @@ -103,3 +110,10 @@ pub static SCHED_STAT_WAIT: PerfEventArray = PerfEventArray::new( #[map(name = "sched_stat_runtime")] pub static SCHED_STAT_RUNTIME: PerfEventArray = PerfEventArray::new(0); + +#[map(name = "cpu_idle")] +pub static CPU_IDLE: PerfEventArray = PerfEventArray::new(0); + +#[map(name = "cpu_idle_last_state")] +pub static mut CPU_IDLE_LAST_STATE: HashMap = + HashMap::::with_max_entries(256, 0); diff --git a/core/src/components/metrics_tracer/src/main.rs b/core/src/components/metrics_tracer/src/main.rs index 1ff160ad..55faa453 100644 --- a/core/src/components/metrics_tracer/src/main.rs +++ b/core/src/components/metrics_tracer/src/main.rs @@ -12,12 +12,12 @@ use crate::cpu::{cpu_idle, per_cpu_bytes_alloc, sched_stat_runtime, sched_stat_w use crate::data_structures::CpuFrequency; use crate::data_structures::NET_METRICS; use crate::data_structures::{CPU_FREQUENCY, SchedStatWait}; -use crate::data_structures::{MEM_ALLOC, SCHED_STAT_RUNTIME, SCHED_STAT_WAIT}; -use crate::data_structures::{MemAlloc, SchedStatRuntime}; use crate::data_structures::{ - NetworkMetrics, TASK_COMM_LEN, TIME_STAMP_EVENTS, TIME_STAMP_START, TimeStampEvent, + CPU_IDLE, NetworkMetrics, TASK_COMM_LEN, TIME_STAMP_EVENTS, TIME_STAMP_START, TimeStampEvent, TimeStampStartInfo, }; +use crate::data_structures::{MEM_ALLOC, SCHED_STAT_RUNTIME, SCHED_STAT_WAIT}; +use crate::data_structures::{MemAlloc, SchedStatRuntime}; use crate::memory::enter_mmap; use aya_ebpf::EbpfContext; use aya_ebpf::helpers::bpf_get_current_pid_tgid; From 86c88ef7201a9cbe5325068185d321d93d28f578 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Mon, 22 Jun 2026 22:36:59 +0200 Subject: [PATCH 11/40] [#186]: added userspace consumer for the cpu idle events --- core/common/src/buffer_type.rs | 79 +++++++++++++++++++++- core/common/src/otel_metrics.rs | 23 ++++++- core/src/components/metrics/src/helpers.rs | 28 ++++++-- core/src/components/metrics/src/main.rs | 11 +++ 4 files changed, 132 insertions(+), 9 deletions(-) diff --git a/core/common/src/buffer_type.rs b/core/common/src/buffer_type.rs index 887d855a..7124e55f 100644 --- a/core/common/src/buffer_type.rs +++ b/core/common/src/buffer_type.rs @@ -174,6 +174,16 @@ pub struct SchedStatRuntime { #[cfg(feature = "monitoring-structs")] unsafe impl aya::Pod for SchedStatRuntime {} +#[cfg(feature = "monitoring-structs")] +#[repr(C, packed)] +#[derive(Clone, Copy, Zeroable)] +pub struct CpuIdle { + pub cpu_id: u32, + pub state: u32, +} +#[cfg(feature = "monitoring-structs")] +unsafe impl aya::Pod for CpuIdle {} + // docs: // This function perform a byte swap from little-endian to big-endian // It's used to reconstruct the correct IPv4 address from the u32 representation @@ -209,6 +219,8 @@ pub enum BufferType { SchedStatWait, #[cfg(feature = "monitoring-structs")] SchedStatRuntime, + #[cfg(feature = "monitoring-structs")] + CpuIdle, } #[cfg(feature = "buffer-reader")] @@ -709,6 +721,49 @@ impl BufferType { } } } + + #[cfg(feature = "monitoring-structs")] + pub async fn read_cpu_idle( + buffers: &mut [BytesMut], + tot_events: i32, + offset: i32, + exporter: &str, + metrics: Arc, + ) { + for i in offset..tot_events { + let vec_bytes = &buffers[i as usize]; + if vec_bytes.len() < std::mem::size_of::() { + error!( + "Corrupted CpuIdle data. Raw data: {}. Readed {} bytes expected {} bytes", + vec_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(" "), + vec_bytes.len(), + std::mem::size_of::() + ); + continue; + } + if vec_bytes.len() >= std::mem::size_of::() { + let cpu_idle: CpuIdle = + unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; + + match exporter { + "otlp" => metrics.record_cpu_idle(&cpu_idle), + _ => continue, + } + + let cpu_id = cpu_idle.cpu_id; + let state = cpu_idle.state; + + info!( + "CpuIdle state changed - cpu_id: {}, state: {}", + cpu_id, state + ); + } + } + } } // docs: read buffer function: @@ -821,7 +876,20 @@ pub async fn read_perf_buffer>( tot_events, offset, "otlp", - metrics.clone().expect("Metric required for SchedStatRuntime"), + metrics + .clone() + .expect("Metric required for SchedStatRuntime"), + ) + .await + } + #[cfg(feature = "monitoring-structs")] + BufferType::CpuIdle => { + BufferType::read_cpu_idle( + &mut buffers, + tot_events, + offset, + "otlp", + metrics.clone().expect("Metric required for CpuIdle"), ) .await } @@ -857,6 +925,8 @@ pub enum BufferSize { SchedStatWait, #[cfg(feature = "monitoring-structs")] SchedStatRuntime, + #[cfg(feature = "monitoring-structs")] + CpuIdle, } #[cfg(feature = "buffer-reader")] impl BufferSize { @@ -880,6 +950,8 @@ impl BufferSize { BufferSize::SchedStatWait => std::mem::size_of::(), #[cfg(feature = "monitoring-structs")] BufferSize::SchedStatRuntime => std::mem::size_of::(), + #[cfg(feature = "monitoring-structs")] + BufferSize::CpuIdle => std::mem::size_of::(), } } pub fn set_buffer(&self) -> Vec { @@ -943,6 +1015,11 @@ impl BufferSize { let capacity = self.get_size() * 1024; return vec![BytesMut::with_capacity(capacity); tot_cpu]; } + #[cfg(feature = "monitoring-structs")] + BufferSize::CpuIdle => { + let capacity = self.get_size() * 1024; + return vec![BytesMut::with_capacity(capacity); tot_cpu]; + } } } } diff --git a/core/common/src/otel_metrics.rs b/core/common/src/otel_metrics.rs index 37611729..79d08b88 100644 --- a/core/common/src/otel_metrics.rs +++ b/core/common/src/otel_metrics.rs @@ -12,7 +12,8 @@ //! telemetry by process. use crate::buffer_type::{ - CpuFrequency, MemAlloc, NetworkMetrics, SchedStatRuntime, SchedStatWait, TimeStampMetrics, + CpuFrequency, CpuIdle, MemAlloc, NetworkMetrics, SchedStatRuntime, SchedStatWait, + TimeStampMetrics, }; use opentelemetry::KeyValue; use opentelemetry::metrics::{Counter, Gauge, Histogram, Meter}; @@ -55,6 +56,9 @@ pub struct Metrics { /// Observed scheduler runtime in nanoseconds (sched_stat_runtime). pub sched_stat_runtime: Gauge, + + /// Current CPU idle C-state per cpu_id, updated only on state change. + pub cpu_idle_state: Gauge, } impl Metrics { @@ -132,6 +136,12 @@ impl Metrics { .with_description("Scheduler runtime in nanoseconds from sched_stat_runtime") .build(); + // current CPU idle C-state per cpu_id + let cpu_idle_state = meter + .i64_gauge("cpu_idle_state") + .with_description("Current CPU idle C-state per cpu_id, updated only on state change") + .build(); + Self { events_total, packets_total, @@ -145,6 +155,7 @@ impl Metrics { enter_mem_alloc, sched_stat_wait, sched_stat_runtime, + cpu_idle_state, } } @@ -253,4 +264,14 @@ impl Metrics { self.sched_stat_runtime.record(m.runtime as i64, attrs); } + + /// Record a single [`CpuIdle`] event. + /// + /// Updates `cpu_idle_state` gauge to the latest C-state for the given + /// `cpu_id`. Events are only emitted by eBPF when the state changes. + pub fn record_cpu_idle(&self, m: &CpuIdle) { + let attrs = &[KeyValue::new("cpu_id", m.cpu_id as i64)]; + + self.cpu_idle_state.record(m.state as i64, attrs); + } } diff --git a/core/src/components/metrics/src/helpers.rs b/core/src/components/metrics/src/helpers.rs index 95232c53..141cad6f 100644 --- a/core/src/components/metrics/src/helpers.rs +++ b/core/src/components/metrics/src/helpers.rs @@ -66,6 +66,10 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a .remove("cpu_frequency") .expect("Cannot create cpu_frequency_perf_buffer"); + let (_cpu_idle_array, cpu_idle_perf_buffer) = maps + .remove("cpu_idle") + .expect("Cannot create cpu_idle perf buffer"); + let (_mem_alloc_array, mem_alloc_perf_buffer) = maps .remove("mem_alloc") .expect("Cannot create mem_alloc perf buffer"); @@ -82,6 +86,7 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a let net_metrics_buffers = BufferSize::NetworkMetricsEvents.set_buffer(); let time_stamp_events_buffers = BufferSize::TimeMetricsEvents.set_buffer(); let cpu_frequency_events_buffers = BufferSize::CpuFrequency.set_buffer(); + let cpu_idle_buffers = BufferSize::CpuIdle.set_buffer(); let mem_alloc_buffers = BufferSize::MemAlloc.set_buffer(); let sched_stat_wait_buffers = BufferSize::SchedStatWait.set_buffer(); let sched_stat_runtime_buffers = BufferSize::SchedStatRuntime.set_buffer(); @@ -135,18 +140,21 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a }) }; + let cpu_idle_metrics = { + let metrics = Arc::clone(&metrics); + let mut array_buffers = cpu_idle_perf_buffer; + let mut buffers = cpu_idle_buffers; + tokio::spawn(async move { + read_perf_buffer(array_buffers, buffers, BufferType::CpuIdle, Some(metrics)).await; + }) + }; + let mem_alloc_metrics = { let metrics = Arc::clone(&metrics); let mut array_buffers = mem_alloc_perf_buffer; let mut buffers = mem_alloc_buffers; tokio::spawn(async move { - read_perf_buffer( - array_buffers, - buffers, - BufferType::MemAlloc, - Some(metrics), - ) - .await; + read_perf_buffer(array_buffers, buffers, BufferType::MemAlloc, Some(metrics)).await; }) }; @@ -201,6 +209,12 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a } } + result = cpu_idle_metrics => { + if let Err(e) = result { + error!("CpuIdle events task failed: {:?}", e); + } + } + result = mem_alloc_metrics => { if let Err(e) = result { error!("MemAlloc events task failed: {:?}", e); diff --git a/core/src/components/metrics/src/main.rs b/core/src/components/metrics/src/main.rs index a3657b26..bcf7de72 100644 --- a/core/src/components/metrics/src/main.rs +++ b/core/src/components/metrics/src/main.rs @@ -47,6 +47,7 @@ async fn main() -> Result<(), anyhow::Error> { let tcp_rev_bpf = bpf.clone(); let tcp_v6_bpf = bpf.clone(); let cpu_frequency = bpf.clone(); + let cpu_idle_bpf = bpf.clone(); let mem_alloc_bpf = bpf.clone(); let sched_stat_wait_bpf = bpf.clone(); let sched_stat_runtime_bpf = bpf.clone(); @@ -61,6 +62,7 @@ async fn main() -> Result<(), anyhow::Error> { "time_stamp_events".to_string(), "net_metrics".to_string(), "cpu_frequency".to_string(), + "cpu_idle".to_string(), "mem_alloc".to_string(), "sched_stat_wait".to_string(), "sched_stat_runtime".to_string(), @@ -105,6 +107,15 @@ async fn main() -> Result<(), anyhow::Error> { .context( "An error occurred during the execution of load_program function", )?; + load_tracepoint_program( + cpu_idle_bpf, + "trace_cpu_idle", + "power", + "cpu_idle", + ) + .context( + "An error occurred during the execution of load_program function", + )?; load_tracepoint_program( mem_alloc_bpf, "trace_enter_mmap", From f244487efbba2f9b5b774a10bbc6fe2a22568e30 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Fri, 3 Jul 2026 21:03:17 +0200 Subject: [PATCH 12/40] (example): Added docker example(only metrics). improved dashboard template --- Examples/dashboard-template.yaml | 656 ------------------ Examples/run-with-docker/docker-compose.yaml | 109 +++ .../dashboards/cortexbrain-dashboard.json | 605 ++++++++++++++++ .../provisioning/dashboards/dashboards.yml | 11 + .../provisioning/datasources/prometheus.yml | 9 + .../otel-collector-config.yaml | 34 + Examples/run-with-docker/prometheus.yml | 8 + 7 files changed, 776 insertions(+), 656 deletions(-) delete mode 100644 Examples/dashboard-template.yaml create mode 100644 Examples/run-with-docker/docker-compose.yaml create mode 100644 Examples/run-with-docker/grafana/dashboards/cortexbrain-dashboard.json create mode 100644 Examples/run-with-docker/grafana/provisioning/dashboards/dashboards.yml create mode 100644 Examples/run-with-docker/grafana/provisioning/datasources/prometheus.yml create mode 100644 Examples/run-with-docker/otel-collector-config.yaml create mode 100644 Examples/run-with-docker/prometheus.yml diff --git a/Examples/dashboard-template.yaml b/Examples/dashboard-template.yaml deleted file mode 100644 index 84b6f930..00000000 --- a/Examples/dashboard-template.yaml +++ /dev/null @@ -1,656 +0,0 @@ -apiVersion: dashboard.grafana.app/v2 -kind: Dashboard -metadata: - name: adzxtsp - namespace: default - uid: d2def6af-04c7-43d3-9f90-969b4bc414ee - resourceVersion: '1782049084952992' - generation: 32 - creationTimestamp: '2026-06-04T18:44:45Z' - labels: - grafana.app/deprecatedInternalID: '4461471636946944' - annotations: - grafana.app/createdBy: user:afo4ysywj3xmof - grafana.app/folder: '' - grafana.app/saved-from-ui: Grafana v13.0.2 (3fcdbc5a) - grafana.app/updatedBy: user:afo4ysywj3xmof - grafana.app/updatedTimestamp: '2026-06-21T13:38:04Z' -spec: - annotations: - - kind: AnnotationQuery - spec: - query: - kind: DataQuery - group: grafana - version: v0 - datasource: - name: '-- Grafana --' - spec: {} - enable: true - hide: true - iconColor: rgba(0, 211, 255, 1) - name: Annotations & Alerts - builtIn: true - cursorSync: 'Off' - editable: true - elements: - panel-1: - kind: Panel - spec: - id: 1 - title: Total packets - description: '' - links: [] - data: - kind: QueryGroup - spec: - queries: - - kind: PanelQuery - spec: - query: - kind: DataQuery - group: prometheus - version: v0 - datasource: - name: ffo50cmg1o1s0a - spec: - editorMode: builder - expr: cortexbrain_packets_total - legendFormat: __auto - range: true - refId: A - hidden: false - transformations: [] - queryOptions: {} - vizConfig: - kind: VizConfig - group: timeseries - version: 13.0.2 - spec: - options: - annotations: - clustering: -1 - multiLane: false - legend: - calcs: [] - displayMode: list - placement: bottom - showLegend: true - tooltip: - hideZeros: false - mode: single - sort: none - fieldConfig: - defaults: - thresholds: - mode: absolute - steps: - - value: 0 - color: green - - value: 80 - color: red - color: - mode: palette-classic - custom: - axisBorderShow: false - axisCenteredZero: false - axisColorMode: text - axisLabel: '' - axisPlacement: auto - barAlignment: 0 - barWidthFactor: 0.6 - drawStyle: line - fillOpacity: 0 - gradientMode: none - hideFrom: - legend: false - tooltip: false - viz: false - insertNulls: false - lineInterpolation: linear - lineWidth: 1 - pointSize: 5 - scaleDistribution: - type: linear - showPoints: auto - showValues: false - spanNulls: false - stacking: - group: A - mode: none - thresholdsStyle: - mode: 'off' - overrides: [] - panel-2: - kind: Panel - spec: - id: 2 - title: Cpu bytes allocation - description: '' - links: [] - data: - kind: QueryGroup - spec: - queries: - - kind: PanelQuery - spec: - query: - kind: DataQuery - group: prometheus - version: v0 - datasource: - name: ffo50cmg1o1s0a - spec: - editorMode: builder - expr: cortexbrain_cpu_bytes_alloc - legendFormat: __auto - range: true - refId: A - hidden: false - transformations: [] - queryOptions: {} - vizConfig: - kind: VizConfig - group: timeseries - version: 13.0.2 - spec: - options: - annotations: - clustering: -1 - multiLane: false - legend: - calcs: [] - displayMode: list - placement: bottom - showLegend: true - tooltip: - hideZeros: false - mode: single - sort: none - fieldConfig: - defaults: - thresholds: - mode: absolute - steps: - - value: 0 - color: green - - value: 80 - color: red - color: - mode: palette-classic - custom: - axisBorderShow: false - axisCenteredZero: false - axisColorMode: text - axisLabel: '' - axisPlacement: auto - barAlignment: 0 - barWidthFactor: 0.6 - drawStyle: line - fillOpacity: 25 - gradientMode: none - hideFrom: - legend: false - tooltip: false - viz: false - insertNulls: false - lineInterpolation: linear - lineWidth: 1 - pointSize: 5 - scaleDistribution: - type: linear - showPoints: auto - showValues: false - spanNulls: false - stacking: - group: A - mode: normal - thresholdsStyle: - mode: 'off' - overrides: [] - panel-3: - kind: Panel - spec: - id: 3 - title: Timestamp microseconds histograms - description: '' - links: [] - data: - kind: QueryGroup - spec: - queries: - - kind: PanelQuery - spec: - query: - kind: DataQuery - group: prometheus - version: v0 - datasource: - name: ffo50cmg1o1s0a - spec: - editorMode: builder - expr: cortexbrain_cpu_bytes_alloc - legendFormat: __auto - range: true - refId: A - hidden: false - transformations: [] - queryOptions: {} - vizConfig: - kind: VizConfig - group: bargauge - version: 13.0.2 - spec: - options: - displayMode: gradient - legend: - calcs: [] - displayMode: list - placement: bottom - showLegend: false - maxVizHeight: 300 - minVizHeight: 16 - minVizWidth: 8 - namePlacement: auto - orientation: horizontal - reduceOptions: - calcs: - - lastNotNull - fields: '' - values: false - showUnfilled: true - sizing: auto - valueMode: color - fieldConfig: - defaults: - thresholds: - mode: percentage - steps: - - value: 0 - color: green - - value: 80 - color: red - color: - mode: thresholds - fieldMinMax: false - overrides: [] - transparent: true - panel-4: - kind: Panel - spec: - id: 4 - title: Mem Alloc size - description: '' - links: [] - data: - kind: QueryGroup - spec: - queries: - - kind: PanelQuery - spec: - query: - kind: DataQuery - group: prometheus - version: v0 - datasource: - name: ffo50cmg1o1s0a - spec: - editorMode: builder - expr: cortexbrain_enter_mem_alloc - legendFormat: __auto - range: true - refId: A - hidden: false - transformations: [] - queryOptions: {} - vizConfig: - kind: VizConfig - group: timeseries - version: 13.0.2 - spec: - options: - annotations: - clustering: -1 - multiLane: false - legend: - calcs: [] - displayMode: list - placement: bottom - showLegend: true - tooltip: - hideZeros: false - mode: single - sort: none - fieldConfig: - defaults: - thresholds: - mode: absolute - steps: - - value: 0 - color: green - - value: 80 - color: red - color: - mode: palette-classic - custom: - axisBorderShow: false - axisCenteredZero: false - axisColorMode: text - axisLabel: '' - axisPlacement: auto - barAlignment: 0 - barWidthFactor: 0.6 - drawStyle: line - fillOpacity: 25 - gradientMode: none - hideFrom: - legend: false - tooltip: false - viz: false - insertNulls: false - lineInterpolation: linear - lineWidth: 1 - pointSize: 5 - scaleDistribution: - type: linear - showPoints: auto - showValues: false - spanNulls: false - stacking: - group: A - mode: normal - thresholdsStyle: - mode: 'off' - overrides: [] - panel-5: - kind: Panel - spec: - id: 5 - title: Mem alloc tot events - description: '' - links: [] - data: - kind: QueryGroup - spec: - queries: - - kind: PanelQuery - spec: - query: - kind: DataQuery - group: prometheus - version: v0 - datasource: - name: ffo50cmg1o1s0a - spec: - editorMode: builder - expr: cortexbrain_mem_alloc_events_total - legendFormat: __auto - range: true - refId: A - hidden: false - transformations: [] - queryOptions: {} - vizConfig: - kind: VizConfig - group: timeseries - version: 13.0.2 - spec: - options: - annotations: - clustering: -1 - multiLane: false - legend: - calcs: [] - displayMode: list - placement: bottom - showLegend: true - tooltip: - hideZeros: false - mode: single - sort: none - fieldConfig: - defaults: - thresholds: - mode: absolute - steps: - - value: 0 - color: green - - value: 80 - color: red - color: - mode: palette-classic - custom: - axisBorderShow: false - axisCenteredZero: false - axisColorMode: text - axisLabel: '' - axisPlacement: auto - barAlignment: 0 - barWidthFactor: 0.6 - drawStyle: line - fillOpacity: 0 - gradientMode: none - hideFrom: - legend: false - tooltip: false - viz: false - insertNulls: false - lineInterpolation: linear - lineWidth: 1 - pointSize: 5 - scaleDistribution: - type: linear - showPoints: auto - showValues: false - spanNulls: false - stacking: - group: A - mode: none - thresholdsStyle: - mode: 'off' - overrides: [] - panel-6: - kind: Panel - spec: - id: 6 - title: Scheduler Runtime (us) - description: '' - links: [] - data: - kind: QueryGroup - spec: - queries: - - kind: PanelQuery - spec: - query: - kind: DataQuery - group: prometheus - version: v0 - datasource: - name: ffo50cmg1o1s0a - spec: - editorMode: builder - expr: cortexbrain_sched_stat_runtime - legendFormat: __auto - range: true - refId: A - hidden: false - transformations: [] - queryOptions: {} - vizConfig: - kind: VizConfig - group: bargauge - version: 13.0.2 - spec: - options: - displayMode: gradient - legend: - calcs: [] - displayMode: list - placement: bottom - showLegend: false - maxVizHeight: 300 - minVizHeight: 16 - minVizWidth: 8 - namePlacement: auto - orientation: horizontal - reduceOptions: - calcs: - - lastNotNull - fields: '' - values: false - showUnfilled: true - sizing: auto - valueMode: color - fieldConfig: - defaults: - thresholds: - mode: absolute - steps: - - value: 0 - color: green - - value: 80 - color: red - color: - mode: thresholds - fieldMinMax: false - overrides: [] - transparent: true - layout: - kind: TabsLayout - spec: - tabs: - - kind: TabsLayoutTab - spec: - title: Events - layout: - kind: AutoGridLayout - spec: - maxColumnCount: 2 - columnWidthMode: standard - rowHeightMode: standard - items: - - kind: AutoGridLayoutItem - spec: - element: - kind: ElementReference - name: panel-1 - - kind: AutoGridLayoutItem - spec: - element: - kind: ElementReference - name: panel-2 - - kind: AutoGridLayoutItem - spec: - element: - kind: ElementReference - name: panel-4 - - kind: AutoGridLayoutItem - spec: - element: - kind: ElementReference - name: panel-5 - - kind: TabsLayoutTab - spec: - title: Latencies - layout: - kind: AutoGridLayout - spec: - maxColumnCount: 3 - columnWidthMode: standard - rowHeightMode: standard - items: - - kind: AutoGridLayoutItem - spec: - element: - kind: ElementReference - name: panel-6 - - kind: AutoGridLayoutItem - spec: - element: - kind: ElementReference - name: panel-3 - links: [] - liveNow: false - preload: false - tags: [] - timeSettings: - timezone: browser - from: now-6h - to: now - autoRefresh: '' - autoRefreshIntervals: - - 5s - - 10s - - 30s - - 1m - - 5m - - 15m - - 30m - - 1h - - 2h - - 1d - hideTimepicker: false - fiscalYearStartMonth: 0 - title: Dashboard - variables: - - kind: CustomVariable - spec: - name: custom0 - query: '' - current: - text: '' - value: '' - options: [] - multi: false - includeAll: false - hide: dontHide - skipUrlSync: false - allowCustomValue: true - valuesFormat: csv - - kind: QueryVariable - spec: - name: query0 - current: - text: '' - value: '' - hide: dontHide - refresh: onDashboardLoad - skipUrlSync: false - query: - kind: DataQuery - group: prometheus - version: v0 - datasource: - name: ffo50cmg1o1s0a - spec: {} - regex: '' - regexApplyTo: value - sort: disabled - options: [] - multi: false - includeAll: false - allowCustomValue: true - - kind: DatasourceVariable - spec: - name: datasource0 - pluginId: prometheus - refresh: onDashboardLoad - regex: '' - current: - text: prometheus - value: ffo50cmg1o1s0a - options: [] - multi: false - includeAll: false - hide: dontHide - skipUrlSync: false - allowCustomValue: true - preferences: - layout: - kind: AutoGridLayout - spec: - maxColumnCount: 3 - columnWidthMode: standard - rowHeightMode: standard - items: [] diff --git a/Examples/run-with-docker/docker-compose.yaml b/Examples/run-with-docker/docker-compose.yaml new file mode 100644 index 00000000..5c4cebe9 --- /dev/null +++ b/Examples/run-with-docker/docker-compose.yaml @@ -0,0 +1,109 @@ +services: + cortexflow-metrics: + image: lorenzotettamanti/cortexflow-metrics:otel-metrics-15 + container_name: cortexflow-metrics + stop_signal: SIGINT + restart: unless-stopped + privileged: true + pid: host + init: true + command: ["/usr/local/bin/cortexflow-metrics"] + environment: + - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 + - OTEL_EXPORTER_OTLP_PROTOCOL=grpc + volumes: + - type: bind + source: /sys/fs/bpf + target: /sys/fs/bpf + bind: + propagation: shared + - /lib/modules:/lib/modules:ro + - /sys/kernel/debug:/sys/kernel/debug + - /proc:/host/proc:ro + networks: + - cortexflow + + #cortexflow-identity: + # image: lorenzotettamanti/cortexflow-identity:0.1.2 + # container_name: cortexflow-identity + # stop_signal: SIGINT + # restart: unless-stopped + # privileged: true + # pid: host + # init: true + # command: ["/usr/local/bin/cortexflow-identity-service"] + # volumes: + # - type: bind + # source: /sys/fs/bpf + # target: /sys/fs/bpf + # bind: + # propagation: shared + # - /lib/modules:/lib/modules:ro + # - /sys/kernel/debug:/sys/kernel/debug + # - /proc:/host/proc:ro + # networks: + # - cortexflow + + otel-collector: + image: otel/opentelemetry-collector:0.95.0 + container_name: otel-collector + command: + - "--config=/conf/otel-collector-config.yaml" + ports: + - "4317:4317" + - "4318:4318" + - "8889:8889" + environment: + - GOMEMLIMIT=1600MiB + volumes: + - ./otel-collector-config.yaml:/conf/otel-collector-config.yaml:ro + networks: + - cortexflow + depends_on: + # - cortexflow-identity + - cortexflow-metrics + + prometheus: + image: prom/prometheus:v2.51.2 + container_name: prometheus + user: "65534:65534" + command: + - "--config.file=/etc/prometheus/prometheus.yml" + - "--storage.tsdb.path=/prometheus" + - "--web.enable-lifecycle" + ports: + - "9090:9090" + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro + - prometheus-data:/prometheus + networks: + - cortexflow + depends_on: + - otel-collector + + grafana: + image: grafana/grafana:latest + container_name: grafana + user: "472:472" + ports: + - "3000:3000" + environment: + - GF_SECURITY_ADMIN_USER=admin + - GF_SECURITY_ADMIN_PASSWORD=admin + - GF_USERS_ALLOW_SIGN_UP=false + - GF_AUTH_ANONYMOUS_ENABLED=false + volumes: + - ./grafana/provisioning:/etc/grafana/provisioning:ro + - ./grafana/dashboards:/var/lib/grafana/dashboards:ro + - grafana-data:/var/lib/grafana + networks: + - cortexflow + depends_on: + - prometheus + +networks: + cortexflow: + +volumes: + prometheus-data: + grafana-data: diff --git a/Examples/run-with-docker/grafana/dashboards/cortexbrain-dashboard.json b/Examples/run-with-docker/grafana/dashboards/cortexbrain-dashboard.json new file mode 100644 index 00000000..89e182fb --- /dev/null +++ b/Examples/run-with-docker/grafana/dashboards/cortexbrain-dashboard.json @@ -0,0 +1,605 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "builder", + "expr": "cortexbrain_packets_total", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Total packets", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 25, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "builder", + "expr": "cortexbrain_cpu_bytes_alloc", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Cpu bytes allocation", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 25, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "builder", + "expr": "cortexbrain_enter_mem_alloc", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Mem Alloc size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "builder", + "expr": "cortexbrain_mem_alloc_events_total", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Mem alloc tot events", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 16 + }, + "id": 6, + "options": { + "displayMode": "gradient", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "builder", + "expr": "cortexbrain_sched_stat_runtime", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Scheduler Runtime (us)", + "transparent": true, + "type": "bargauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 16, + "x": 8, + "y": 16 + }, + "id": 3, + "options": { + "displayMode": "gradient", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" + }, + "pluginVersion": "10.4.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "builder", + "expr": "cortexbrain_cpu_bytes_alloc", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Timestamp microseconds histograms", + "transparent": true, + "type": "bargauge" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 39, + "tags": [], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "prometheus" + }, + "hide": 0, + "includeAll": false, + "label": "datasource", + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "browser", + "title": "CortexBrain Dashboard", + "uid": "cortexbrain-main", + "version": 1 +} diff --git a/Examples/run-with-docker/grafana/provisioning/dashboards/dashboards.yml b/Examples/run-with-docker/grafana/provisioning/dashboards/dashboards.yml new file mode 100644 index 00000000..0bcf3d81 --- /dev/null +++ b/Examples/run-with-docker/grafana/provisioning/dashboards/dashboards.yml @@ -0,0 +1,11 @@ +apiVersion: 1 + +providers: + - name: 'default' + orgId: 1 + folder: '' + type: file + disableDeletion: false + editable: true + options: + path: /var/lib/grafana/dashboards diff --git a/Examples/run-with-docker/grafana/provisioning/datasources/prometheus.yml b/Examples/run-with-docker/grafana/provisioning/datasources/prometheus.yml new file mode 100644 index 00000000..bb009bb2 --- /dev/null +++ b/Examples/run-with-docker/grafana/provisioning/datasources/prometheus.yml @@ -0,0 +1,9 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true + editable: false diff --git a/Examples/run-with-docker/otel-collector-config.yaml b/Examples/run-with-docker/otel-collector-config.yaml new file mode 100644 index 00000000..91cae017 --- /dev/null +++ b/Examples/run-with-docker/otel-collector-config.yaml @@ -0,0 +1,34 @@ +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 + +processors: + memory_limiter: + limit_mib: 1500 + spike_limit_mib: 512 + check_interval: 5s + +exporters: + logging: {} + prometheus: + endpoint: 0.0.0.0:8889 + namespace: cortexbrain + +service: + pipelines: + traces: + receivers: [otlp] + processors: [memory_limiter] + exporters: [logging] + logs: + receivers: [otlp] + processors: [memory_limiter] + exporters: [logging] + metrics: + receivers: [otlp] + processors: [memory_limiter] + exporters: [logging, prometheus] diff --git a/Examples/run-with-docker/prometheus.yml b/Examples/run-with-docker/prometheus.yml new file mode 100644 index 00000000..faeda702 --- /dev/null +++ b/Examples/run-with-docker/prometheus.yml @@ -0,0 +1,8 @@ +global: + scrape_interval: 5s + evaluation_interval: 5s + +scrape_configs: + - job_name: "otel-collector" + static_configs: + - targets: ["otel-collector:8889"] From 46cdb15882f2f8a31eb5de5b04c24a116c0a248c Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Fri, 3 Jul 2026 21:20:14 +0200 Subject: [PATCH 13/40] [#186]: fixed typo in cpu.rs --- core/src/components/metrics_tracer/src/cpu.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/src/components/metrics_tracer/src/cpu.rs b/core/src/components/metrics_tracer/src/cpu.rs index 9d4b73a0..374ea36a 100644 --- a/core/src/components/metrics_tracer/src/cpu.rs +++ b/core/src/components/metrics_tracer/src/cpu.rs @@ -19,11 +19,7 @@ pub fn cpu_idle(ctx: TracePointContext) -> Result<(), i64> { // - last_state is equal to the current state // - last_state is equal to 4294967295 or -1. This codes means that the cpu is exiting from the current state and entering a new state let emit = match unsafe { (*map_ptr).get(&cpu_id) } { - Some(last_state) - if (*last_state == state) || (*last_state == 4294967295) || (*last_state == -1) => - { - false - } + Some(last_state) if (*last_state == state) || (*last_state == 4294967295) => false, _ => true, }; From 845c5c417a5e5ce60a64d691f699d799cac7ddf5 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Fri, 3 Jul 2026 21:20:40 +0200 Subject: [PATCH 14/40] (chores): updated metrics image --- Examples/run-with-docker/docker-compose.yaml | 2 +- core/src/testing/metrics.yaml | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Examples/run-with-docker/docker-compose.yaml b/Examples/run-with-docker/docker-compose.yaml index 5c4cebe9..a77444cc 100644 --- a/Examples/run-with-docker/docker-compose.yaml +++ b/Examples/run-with-docker/docker-compose.yaml @@ -1,6 +1,6 @@ services: cortexflow-metrics: - image: lorenzotettamanti/cortexflow-metrics:otel-metrics-15 + image: lorenzotettamanti/cortexflow-metrics:otel-test-15 container_name: cortexflow-metrics stop_signal: SIGINT restart: unless-stopped diff --git a/core/src/testing/metrics.yaml b/core/src/testing/metrics.yaml index ce797950..7e748fd6 100644 --- a/core/src/testing/metrics.yaml +++ b/core/src/testing/metrics.yaml @@ -19,7 +19,7 @@ spec: hostNetwork: true containers: - name: metrics - image: lorenzotettamanti/cortexflow-metrics:otel-test-5 + image: lorenzotettamanti/cortexflow-metrics:otel-test-15 command: ["/bin/bash", "-c"] args: - | @@ -62,7 +62,7 @@ spec: - SYS_PTRACE - name: bpftool-control-manager image: danielpacak/bpftool-runner:latest - command: ["/bin/bash", "-c","sleep infinity"] + command: ["/bin/bash", "-c", "sleep infinity"] volumeMounts: - name: bpf mountPath: /sys/fs/bpf @@ -103,7 +103,6 @@ spec: - name: tracefs hostPath: - path: /sys/kernel/debug - type: Directory + path: /sys/kernel/debug + type: Directory - \ No newline at end of file From fabdc00c988520e14d6f9424c929127e407b6c97 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Fri, 10 Jul 2026 22:05:33 +0200 Subject: [PATCH 15/40] (fix): fixed grafana version --- Examples/run-with-docker/docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/run-with-docker/docker-compose.yaml b/Examples/run-with-docker/docker-compose.yaml index a77444cc..4e486583 100644 --- a/Examples/run-with-docker/docker-compose.yaml +++ b/Examples/run-with-docker/docker-compose.yaml @@ -82,7 +82,7 @@ services: - otel-collector grafana: - image: grafana/grafana:latest + image: grafana/grafana:11.6.16 container_name: grafana user: "472:472" ports: From 7dda5d6e7ed492c37a10aaee55092d06c6d86565 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sat, 11 Jul 2026 17:22:30 +0200 Subject: [PATCH 16/40] (refactor): refactored network tracing using a network.rs module --- .../src/components/metrics_tracer/src/main.rs | 212 +---------------- core/src/components/metrics_tracer/src/mod.rs | 1 + .../components/metrics_tracer/src/network.rs | 217 ++++++++++++++++++ 3 files changed, 229 insertions(+), 201 deletions(-) create mode 100644 core/src/components/metrics_tracer/src/network.rs diff --git a/core/src/components/metrics_tracer/src/main.rs b/core/src/components/metrics_tracer/src/main.rs index 55faa453..1da3451b 100644 --- a/core/src/components/metrics_tracer/src/main.rs +++ b/core/src/components/metrics_tracer/src/main.rs @@ -6,6 +6,7 @@ mod bindings; mod cpu; mod data_structures; mod memory; +mod network; use crate::bindings::net_device; use crate::cpu::{cpu_idle, per_cpu_bytes_alloc, sched_stat_runtime, sched_stat_wait}; @@ -13,12 +14,13 @@ use crate::data_structures::CpuFrequency; use crate::data_structures::NET_METRICS; use crate::data_structures::{CPU_FREQUENCY, SchedStatWait}; use crate::data_structures::{ - CPU_IDLE, NetworkMetrics, TASK_COMM_LEN, TIME_STAMP_EVENTS, TIME_STAMP_START, TimeStampEvent, - TimeStampStartInfo, + CPU_IDLE, PacketLossMetrics, TASK_COMM_LEN, TIME_STAMP_EVENTS, TIME_STAMP_START, + TimeStampEvent, TimeStampStartInfo, }; use crate::data_structures::{MEM_ALLOC, SCHED_STAT_RUNTIME, SCHED_STAT_WAIT}; use crate::data_structures::{MemAlloc, SchedStatRuntime}; use crate::memory::enter_mmap; +use crate::network::{detect_packet_loss, on_connect, on_rcv_state_process}; use aya_ebpf::EbpfContext; use aya_ebpf::helpers::bpf_get_current_pid_tgid; use aya_ebpf::helpers::generated::{bpf_ktime_get_ns, bpf_perf_event_output}; @@ -35,7 +37,7 @@ const AF_INET6: u16 = 10; const TCP_SYN_SENT: u8 = 2; #[kprobe] -fn metrics_tracer(ctx: ProbeContext) -> u32 { +fn packet_loss_tracer(ctx: ProbeContext) -> u32 { match try_metrics_tracer(ctx) { Ok(ret) => ret, Err(ret) => ret.try_into().unwrap_or(1), @@ -43,64 +45,7 @@ fn metrics_tracer(ctx: ProbeContext) -> u32 { } fn try_metrics_tracer(ctx: ProbeContext) -> Result { - let sk_pointer = ctx.arg::<*const u8>(0).ok_or(1i64)?; - - if sk_pointer.is_null() { - return Err(1); - } - - let tgid = (unsafe { bpf_get_current_pid_tgid() } >> 32) as u32; - let comm = unsafe { bpf_get_current_comm() }.map_err(|_| 1i64)?; - let ts_us: u64 = unsafe { bpf_ktime_get_ns() } / 1_000; - let sk_err_offset = 284; - let sk_err_soft_offset = 600; - let sk_backlog_len_offset = 196; - let sk_write_memory_queued_offset = 376; - let sk_receive_buffer_size_offset = 244; - let sk_ack_backlog_offset = 604; - let sk_drops_offset = 136; - - let sk_err = unsafe { - bpf_probe_read_kernel::(sk_pointer.add(sk_err_offset) as *const i32).map_err(|_| 1)? - }; - let sk_err_soft = unsafe { - bpf_probe_read_kernel::(sk_pointer.add(sk_err_soft_offset) as *const i32) - .map_err(|_| 1)? - }; - let sk_backlog_len = unsafe { - bpf_probe_read_kernel::(sk_pointer.add(sk_backlog_len_offset) as *const i32) - .map_err(|_| 1)? - }; - let sk_write_memory_queued = unsafe { - bpf_probe_read_kernel::(sk_pointer.add(sk_write_memory_queued_offset) as *const i32) - .map_err(|_| 1)? - }; - let sk_receive_buffer_size = unsafe { - bpf_probe_read_kernel::(sk_pointer.add(sk_receive_buffer_size_offset) as *const i32) - .map_err(|_| 1)? - }; - let sk_ack_backlog = unsafe { - bpf_probe_read_kernel::(sk_pointer.add(sk_ack_backlog_offset) as *const u32) - .map_err(|_| 1)? - }; - let sk_drops = unsafe { - bpf_probe_read_kernel::(sk_pointer.add(sk_drops_offset) as *const i32) - .map_err(|_| 1)? - }; - - let net_metrics = NetworkMetrics { - tgid: tgid, - comm: comm, - ts_us: ts_us, - sk_err: sk_err, - sk_err_soft: sk_err_soft, - sk_backlog_len: sk_backlog_len, - sk_write_memory_queued: sk_write_memory_queued, - sk_receive_buffer_size: sk_receive_buffer_size, - sk_ack_backlog: sk_ack_backlog, - sk_drops: sk_drops, - }; - + let net_metrics = detect_packet_loss(&ctx)?; unsafe { NET_METRICS.output(&ctx, &net_metrics, 0); } @@ -108,7 +53,7 @@ fn try_metrics_tracer(ctx: ProbeContext) -> Result { Ok(0) } -// Monitor on tcp_sendmsg, tcp_v4_connect +/// Monitor on tcp_sendmsg, tcp_v6_connect #[kprobe] fn tcp_v6_connect(ctx: ProbeContext) -> u32 { match on_connect(ctx) { @@ -117,7 +62,7 @@ fn tcp_v6_connect(ctx: ProbeContext) -> u32 { } } -// Monitor on tcp_sendmsg, tcp_v4_connect +/// Monitor on tcp_sendmsg, tcp_v4_connect #[kprobe] fn tcp_v4_connect(ctx: ProbeContext) -> u32 { match on_connect(ctx) { @@ -126,149 +71,14 @@ fn tcp_v4_connect(ctx: ProbeContext) -> u32 { } } -fn on_connect(ctx: ProbeContext) -> Result<(), i64> { - let sk = ctx.arg::<*mut bindings::sock>(0).ok_or(1i64)?; - if sk.is_null() { - return Err(1); - } - - let tgid = (unsafe { bpf_get_current_pid_tgid() } >> 32) as u32; - let mut start = TimeStampStartInfo { - comm: [0; TASK_COMM_LEN], - ts_ns: unsafe { bpf_ktime_get_ns() }, - tgid, - }; - unsafe { - let comm_result = bpf_get_current_comm(); - if let Ok(comm) = comm_result { - start.comm.copy_from_slice(&comm); - } - let map_ptr = &raw mut TIME_STAMP_START; - (*map_ptr) - .insert(&(sk as *mut core::ffi::c_void), &start, 0) - .map_err(|_| 1)?; - } - Ok(()) -} - #[kprobe] -fn tcp_rcv_state_process(ctx: ProbeContext) -> u32 { +fn tcp_latency_monitor(ctx: ProbeContext) -> u32 { match on_rcv_state_process(ctx) { Ok(_) => 0, Err(e) => e as u32, } } -fn on_rcv_state_process(ctx: ProbeContext) -> Result<(), i64> { - // On some kernels, kprobe wrapper puts `sk` at arg0; on others arg1. - let sk = ctx.arg::<*mut bindings::sock>(0).unwrap_or(ptr::null_mut()); - let sk = if sk.is_null() { - ctx.arg::<*mut bindings::sock>(1).ok_or(1i64)? - } else { - sk - }; - - if sk.is_null() { - return Err(1); - } - - let skc_daddr_off = 0; - let skc_rcv_saddr_off = 4; - let skc_dport_off = 12; - let skc_num_off = 14; - let skc_family_off = 16; - let skc_state_off = 18; - let skc_v6_daddr_off = 56; - let skc_v6_rcv_saddr_off = 72; - - let state = unsafe { bpf_probe_read_kernel::((sk as usize + skc_state_off) as *const u8) } - .map_err(|_| 1)?; - - if state != TCP_SYN_SENT { - return Ok(()); - } - - let start = unsafe { - let map_ptr = &raw const TIME_STAMP_START; - (*map_ptr).get(&((sk as usize) as *mut core::ffi::c_void)) - } - .ok_or(1i64)?; - let now = unsafe { bpf_ktime_get_ns() }; - let delta = now as i64 - start.ts_ns as i64; - if delta <= 0 { - unsafe { - let map_ptr = &raw mut TIME_STAMP_START; - let _ = (*map_ptr).remove(&((sk as usize) as *mut core::ffi::c_void)); - } - return Ok(()); - } - - let mut ev = TimeStampEvent { - delta_us: (delta as u64) / 1_000, - ts_us: now / 1_000, - tgid: start.tgid, - comm: start.comm, - lport: 0, - dport_be: 0, - af: 0, - saddr_v4: 0, - daddr_v4: 0, - saddr_v6: [0; 4], - daddr_v6: [0; 4], - }; - - // family, ports - ev.af = unsafe { - bpf_probe_read_kernel::((sk as usize + skc_family_off) as *const u16).map_err(|_| 1)? - }; - ev.lport = unsafe { - bpf_probe_read_kernel::((sk as usize + skc_num_off) as *const u16).map_err(|_| 1)? - }; - ev.dport_be = unsafe { - bpf_probe_read_kernel::((sk as usize + skc_dport_off) as *const u16).map_err(|_| 1)? - }; - - if ev.af == AF_INET { - ev.saddr_v4 = unsafe { - bpf_probe_read_kernel::((sk as usize + skc_rcv_saddr_off) as *const u32) - .map_err(|_| 1)? - }; - ev.daddr_v4 = unsafe { - bpf_probe_read_kernel::((sk as usize + skc_daddr_off) as *const u32) - .map_err(|_| 1)? - }; - } else { - // read 16 bytes as four u32 words - for i in 0..4 { - ev.saddr_v6[i] = unsafe { - bpf_probe_read_kernel::( - (sk as usize + skc_v6_rcv_saddr_off + i * 4) as *const u32, - ) - .map_err(|_| 1)? - }; - ev.daddr_v6[i] = unsafe { - bpf_probe_read_kernel::((sk as usize + skc_v6_daddr_off + i * 4) as *const u32) - .map_err(|_| 1)? - }; - } - } - - // emit + cleanup - unsafe { - bpf_perf_event_output( - ctx.as_ptr(), - &raw const TIME_STAMP_EVENTS as *const _ as *mut _, - 0, // BPF_F_CURRENT_CPU - &ev as *const _ as *mut _, - (mem::size_of::() as u32).into(), - ); - let map_ptr = &raw mut TIME_STAMP_START; - let _ = (*map_ptr).remove(&((sk as usize) as *mut core::ffi::c_void)); - } - - Ok(()) -} - #[tracepoint] fn trace_cpu_frequency(ctx: TracePointContext) -> u32 { match trace_cpu_metrics(&ctx) { @@ -286,13 +96,13 @@ fn trace_cpu_idle(ctx: TracePointContext) -> u32 { } fn trace_cpu_metrics(ctx: &TracePointContext) -> Result<(), i64> { - let (bytes_alloc, pid, command) = per_cpu_bytes_alloc(ctx)?; + let (bytes_alloc, tgid, command) = per_cpu_bytes_alloc(ctx)?; //let (cpu_id, cpu_freq) = cpu_frequency(&ctx)?; let cpu_metrics = CpuFrequency { // cpu_id, // cpu_freq, bytes_alloc, - pid, + tgid, command, }; diff --git a/core/src/components/metrics_tracer/src/mod.rs b/core/src/components/metrics_tracer/src/mod.rs index 56f80cc7..e62fb543 100644 --- a/core/src/components/metrics_tracer/src/mod.rs +++ b/core/src/components/metrics_tracer/src/mod.rs @@ -2,3 +2,4 @@ mod bindings; mod cpu; mod data_structures; mod memory; +mod network; diff --git a/core/src/components/metrics_tracer/src/network.rs b/core/src/components/metrics_tracer/src/network.rs new file mode 100644 index 00000000..4cc50bb1 --- /dev/null +++ b/core/src/components/metrics_tracer/src/network.rs @@ -0,0 +1,217 @@ +use crate::bindings::{self, net_device}; +use crate::data_structures::{NET_METRICS, PacketLossMetrics}; +use crate::data_structures::{ + TASK_COMM_LEN, TIME_STAMP_EVENTS, TIME_STAMP_START, TimeStampEvent, TimeStampStartInfo, +}; +use aya_ebpf::EbpfContext; +use aya_ebpf::helpers::bpf_get_current_pid_tgid; +use aya_ebpf::helpers::generated::{bpf_ktime_get_ns, bpf_perf_event_output}; +use aya_ebpf::helpers::{ + bpf_get_current_comm, bpf_probe_read_kernel, bpf_probe_read_kernel_str_bytes, +}; +use aya_ebpf::macros::{kprobe, map, tracepoint}; +use aya_ebpf::maps::{HashMap, PerfEventArray}; +use aya_ebpf::programs::ProbeContext; +use core::{mem, ptr}; + +const AF_INET: u16 = 2; +const AF_INET6: u16 = 10; +const TCP_SYN_SENT: u8 = 2; + +/// packet loss tracer +pub fn detect_packet_loss(ctx: &ProbeContext) -> Result { + let sk_pointer = ctx.arg::<*const u8>(0).ok_or(1i64)?; + + if sk_pointer.is_null() { + return Err(1); + } + + let tgid = (unsafe { bpf_get_current_pid_tgid() } >> 32) as u32; + let comm = unsafe { bpf_get_current_comm() }.map_err(|_| 1i64)?; + let ts_us: u64 = unsafe { bpf_ktime_get_ns() } / 1_000; + let sk_err_offset = 284; + let sk_err_soft_offset = 600; + let sk_backlog_len_offset = 196; + let sk_write_memory_queued_offset = 376; + let sk_receive_buffer_size_offset = 244; + let sk_ack_backlog_offset = 604; + let sk_drops_offset = 136; + + let sk_err = unsafe { + bpf_probe_read_kernel::(sk_pointer.add(sk_err_offset) as *const i32).map_err(|_| 1)? + }; + let sk_err_soft = unsafe { + bpf_probe_read_kernel::(sk_pointer.add(sk_err_soft_offset) as *const i32) + .map_err(|_| 1)? + }; + let sk_backlog_len = unsafe { + bpf_probe_read_kernel::(sk_pointer.add(sk_backlog_len_offset) as *const i32) + .map_err(|_| 1)? + }; + let sk_write_memory_queued = unsafe { + bpf_probe_read_kernel::(sk_pointer.add(sk_write_memory_queued_offset) as *const i32) + .map_err(|_| 1)? + }; + let sk_receive_buffer_size = unsafe { + bpf_probe_read_kernel::(sk_pointer.add(sk_receive_buffer_size_offset) as *const i32) + .map_err(|_| 1)? + }; + let sk_ack_backlog = unsafe { + bpf_probe_read_kernel::(sk_pointer.add(sk_ack_backlog_offset) as *const u32) + .map_err(|_| 1)? + }; + let sk_drops = unsafe { + bpf_probe_read_kernel::(sk_pointer.add(sk_drops_offset) as *const i32) + .map_err(|_| 1)? + }; + + let packet_loss_metrics = PacketLossMetrics { + tgid: tgid, + comm: comm, + ts_us: ts_us, + sk_err: sk_err, + sk_err_soft: sk_err_soft, + sk_backlog_len: sk_backlog_len, + sk_write_memory_queued: sk_write_memory_queued, + sk_receive_buffer_size: sk_receive_buffer_size, + sk_ack_backlog: sk_ack_backlog, + sk_drops: sk_drops, + }; + + Ok(packet_loss_metrics) +} + +pub fn on_connect(ctx: ProbeContext) -> Result<(), i64> { + let sk = ctx.arg::<*mut bindings::sock>(0).ok_or(1i64)?; + if sk.is_null() { + return Err(1); + } + + let tgid = (unsafe { bpf_get_current_pid_tgid() } >> 32) as u32; + let mut start = TimeStampStartInfo { + comm: [0; TASK_COMM_LEN], + ts_ns: unsafe { bpf_ktime_get_ns() }, + tgid, + }; + unsafe { + let comm_result = bpf_get_current_comm(); + if let Ok(comm) = comm_result { + start.comm.copy_from_slice(&comm); + } + let map_ptr = &raw mut TIME_STAMP_START; + (*map_ptr) + .insert(&(sk as *mut core::ffi::c_void), &start, 0) + .map_err(|_| 1)?; + } + Ok(()) +} + +pub fn on_rcv_state_process(ctx: ProbeContext) -> Result<(), i64> { + // On some kernels, kprobe wrapper puts `sk` at arg0; on others arg1. + let sk = ctx.arg::<*mut bindings::sock>(0).unwrap_or(ptr::null_mut()); + let sk = if sk.is_null() { + ctx.arg::<*mut bindings::sock>(1).ok_or(1i64)? + } else { + sk + }; + + if sk.is_null() { + return Err(1); + } + + let skc_daddr_off = 0; + let skc_rcv_saddr_off = 4; + let skc_dport_off = 12; + let skc_num_off = 14; + let skc_family_off = 16; + let skc_state_off = 18; + let skc_v6_daddr_off = 56; + let skc_v6_rcv_saddr_off = 72; + + let state = unsafe { bpf_probe_read_kernel::((sk as usize + skc_state_off) as *const u8) } + .map_err(|_| 1)?; + + if state != TCP_SYN_SENT { + return Ok(()); + } + + let start = unsafe { + let map_ptr = &raw const TIME_STAMP_START; + (*map_ptr).get(&((sk as usize) as *mut core::ffi::c_void)) + } + .ok_or(1i64)?; + let now = unsafe { bpf_ktime_get_ns() }; + let delta = now as i64 - start.ts_ns as i64; + if delta <= 0 { + unsafe { + let map_ptr = &raw mut TIME_STAMP_START; + let _ = (*map_ptr).remove(&((sk as usize) as *mut core::ffi::c_void)); + } + return Ok(()); + } + + let mut ev = TimeStampEvent { + delta_us: (delta as u64) / 1_000, + ts_us: now / 1_000, + tgid: start.tgid, + comm: start.comm, + lport: 0, + dport_be: 0, + af: 0, + saddr_v4: 0, + daddr_v4: 0, + saddr_v6: [0; 4], + daddr_v6: [0; 4], + }; + + // family, ports + ev.af = unsafe { + bpf_probe_read_kernel::((sk as usize + skc_family_off) as *const u16).map_err(|_| 1)? + }; + ev.lport = unsafe { + bpf_probe_read_kernel::((sk as usize + skc_num_off) as *const u16).map_err(|_| 1)? + }; + ev.dport_be = unsafe { + bpf_probe_read_kernel::((sk as usize + skc_dport_off) as *const u16).map_err(|_| 1)? + }; + + if ev.af == AF_INET { + ev.saddr_v4 = unsafe { + bpf_probe_read_kernel::((sk as usize + skc_rcv_saddr_off) as *const u32) + .map_err(|_| 1)? + }; + ev.daddr_v4 = unsafe { + bpf_probe_read_kernel::((sk as usize + skc_daddr_off) as *const u32) + .map_err(|_| 1)? + }; + } else { + // read 16 bytes as four u32 words + for i in 0..4 { + ev.saddr_v6[i] = unsafe { + bpf_probe_read_kernel::( + (sk as usize + skc_v6_rcv_saddr_off + i * 4) as *const u32, + ) + .map_err(|_| 1)? + }; + ev.daddr_v6[i] = unsafe { + bpf_probe_read_kernel::((sk as usize + skc_v6_daddr_off + i * 4) as *const u32) + .map_err(|_| 1)? + }; + } + } + + // emit + cleanup + unsafe { + bpf_perf_event_output( + ctx.as_ptr(), + &raw const TIME_STAMP_EVENTS as *const _ as *mut _, + 0, // BPF_F_CURRENT_CPU + &ev as *const _ as *mut _, + (mem::size_of::() as u32).into(), + ); + let map_ptr = &raw mut TIME_STAMP_START; + let _ = (*map_ptr).remove(&((sk as usize) as *mut core::ffi::c_void)); + } + + Ok(()) +} From 46f33b8796125c1cb32ee4351bd8b8c78a264e69 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sat, 11 Jul 2026 17:23:16 +0200 Subject: [PATCH 17/40] (refactor): changed NetworkMetrics data structure name to PacketLossMetrics --- .../metrics_tracer/src/data_structures.rs | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/core/src/components/metrics_tracer/src/data_structures.rs b/core/src/components/metrics_tracer/src/data_structures.rs index d74ef35a..cfc80b6f 100644 --- a/core/src/components/metrics_tracer/src/data_structures.rs +++ b/core/src/components/metrics_tracer/src/data_structures.rs @@ -6,7 +6,7 @@ use aya_ebpf::{ pub const TASK_COMM_LEN: usize = 16; #[repr(C, packed)] -pub struct NetworkMetrics { +pub struct PacketLossMetrics { pub tgid: u32, pub comm: [u8; TASK_COMM_LEN], pub ts_us: u64, @@ -27,7 +27,8 @@ pub struct TimeStampStartInfo { pub tgid: u32, } -// Event we send to userspace when latency is computed +/// Event we send to userspace when latency is computed +/// used to compute tcp_delta_us, tcp_ts_us metrics #[repr(C, packed)] #[derive(Copy, Clone)] pub struct TimeStampEvent { @@ -50,7 +51,7 @@ pub struct CpuFrequency { //pub(crate) cpu_id: u32, //pub(crate) cpu_freq: u32, pub(crate) bytes_alloc: u32, - pub(crate) pid: u32, + pub(crate) tgid: u32, pub(crate) command: [u8; 16], } @@ -86,6 +87,25 @@ pub struct CpuIdle { pub(crate) state: u32, } +#[repr(C, packed)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +pub struct SslEvent { + pub tgid: u32, + pub comm: [u8; TASK_COMM_LEN], + pub ts_us: u64, + pub direction: u8, // 0 = read, 1 = write + pub size: i32, // return value (bytes transferred or <0 on error) + pub requested: i32, // num argument passed to SSL_read/SSL_write +} + +#[repr(C, packed)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +pub struct SslCtxInfo { + pub ssl: u64, // SSL* pointer + pub requested: i32, + pub ts_ns: u64, +} + // Map: connect-start timestamp by socket pointer #[map(name = "time_stamp_start")] pub static mut TIME_STAMP_START: HashMap<*mut core::ffi::c_void, TimeStampStartInfo> = @@ -97,7 +117,7 @@ pub static mut TIME_STAMP_EVENTS: PerfEventArray = PerfEventArray::::new(0); #[map(name = "net_metrics")] -pub static NET_METRICS: PerfEventArray = PerfEventArray::new(0); +pub static NET_METRICS: PerfEventArray = PerfEventArray::new(0); #[map(name = "cpu_frequency")] pub static CPU_FREQUENCY: PerfEventArray = PerfEventArray::new(0); @@ -117,3 +137,10 @@ pub static CPU_IDLE: PerfEventArray = PerfEventArray::new(0); #[map(name = "cpu_idle_last_state")] pub static mut CPU_IDLE_LAST_STATE: HashMap = HashMap::::with_max_entries(256, 0); + +#[map(name = "ssl_ctx_map")] +pub static mut SSL_CTX_MAP: HashMap = + HashMap::::with_max_entries(4096, 0); + +#[map(name = "ssl_events")] +pub static SSL_EVENTS: PerfEventArray = PerfEventArray::new(0); From 02222aef3549772bc07d43ff5e670b097ca7646f Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sat, 11 Jul 2026 17:26:06 +0200 Subject: [PATCH 18/40] (fix): fixed subtle bug occurring. changed way to return pid and tgid using bpf_get_current_pid_tgid helper --- core/src/components/metrics_tracer/src/cpu.rs | 20 ++++++++++++------- .../components/metrics_tracer/src/memory.rs | 6 ++++-- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/core/src/components/metrics_tracer/src/cpu.rs b/core/src/components/metrics_tracer/src/cpu.rs index 374ea36a..dbab7d42 100644 --- a/core/src/components/metrics_tracer/src/cpu.rs +++ b/core/src/components/metrics_tracer/src/cpu.rs @@ -2,7 +2,7 @@ //tracepoint:power:cpu_frequency_limits //tracepoint:power:cpu_idle //tracepoint:power:cpu_idle_miss -use aya_ebpf::{EbpfContext, programs::TracePointContext}; +use aya_ebpf::{EbpfContext, helpers::bpf_get_current_pid_tgid, programs::TracePointContext}; use aya_log_ebpf::info; use crate::data_structures::{CPU_FREQUENCY, CPU_IDLE, CPU_IDLE_LAST_STATE, CpuFrequency, CpuIdle}; @@ -37,7 +37,9 @@ pub fn per_cpu_bytes_alloc(ctx: &TracePointContext) -> Result<((u32, u32, [u8; 1 let bytes_alloc_offset = 64; let pid_offset = 4; let bytes_alloc = unsafe { ctx.read_at(bytes_alloc_offset) }?; - let pid = unsafe { ctx.read_at(pid_offset) }?; + //let tgid: u32 = unsafe { ctx.read_at(tgid_offset) }?; + let pid_tgid: u64 = bpf_get_current_pid_tgid(); + let tgid: u32 = (pid_tgid >> 32) as u32; let command = ctx.command()?; //let cpu_freq_data = CpuFrequency { @@ -47,29 +49,33 @@ pub fn per_cpu_bytes_alloc(ctx: &TracePointContext) -> Result<((u32, u32, [u8; 1 //CPU_FREQUENCY.output(&ctx, &cpu_freq_data, 0); - Ok((bytes_alloc, pid, command)) + Ok((bytes_alloc, tgid, command)) } pub fn sched_stat_wait(ctx: &TracePointContext) -> Result<((u32, u64, [u8; 16])), i64> { let pid_offset = 4; let delay_offset = 16; - let pid = unsafe { ctx.read_at(pid_offset) }?; + //let tgid: u32 = unsafe { ctx.read_at(tgid_offset) }?; + let pid_tgid: u64 = bpf_get_current_pid_tgid(); + let tgid: u32 = (pid_tgid >> 32) as u32; let delay = unsafe { ctx.read_at(delay_offset) }?; let command = ctx.command()?; - Ok((pid, delay, command)) + Ok((tgid, delay, command)) } pub fn sched_stat_runtime(ctx: &TracePointContext) -> Result<((u32, u64, [u8; 16])), i64> { let pid_offset = 4; let runtime_offset = 16; - let pid = unsafe { ctx.read_at(pid_offset) }?; + //let tgid: u32 = unsafe { ctx.read_at(tgid_offset) }?; + let pid_tgid: u64 = bpf_get_current_pid_tgid(); + let tgid: u32 = (pid_tgid >> 32) as u32; let runtime = unsafe { ctx.read_at(runtime_offset) }?; let command = ctx.command()?; - Ok((pid, runtime, command)) + Ok((tgid, runtime, command)) } diff --git a/core/src/components/metrics_tracer/src/memory.rs b/core/src/components/metrics_tracer/src/memory.rs index de14c7a8..a7ee4d4e 100644 --- a/core/src/components/metrics_tracer/src/memory.rs +++ b/core/src/components/metrics_tracer/src/memory.rs @@ -1,4 +1,4 @@ -use aya_ebpf::{EbpfContext, programs::TracePointContext}; +use aya_ebpf::{EbpfContext, helpers::bpf_get_current_pid_tgid, programs::TracePointContext}; /// Read the fields of the `syscalls:sys_enter_mmap` tracepoint. pub fn enter_mmap(ctx: &TracePointContext) -> Result<((u32, u64, u64, [u8; 16])), i64> { @@ -7,7 +7,9 @@ pub fn enter_mmap(ctx: &TracePointContext) -> Result<((u32, u64, u64, [u8; 16])) let addr_offset = 16; let len_offset = 24; - let tgid: u32 = unsafe { ctx.read_at(tgid_offset) }?; + //let tgid: u32 = unsafe { ctx.read_at(tgid_offset) }?; + let pid_tgid: u64 = bpf_get_current_pid_tgid(); + let tgid: u32 = (pid_tgid >> 32) as u32; let addr: u64 = unsafe { ctx.read_at(addr_offset) }?; let len: u64 = unsafe { ctx.read_at(len_offset) }?; let command = ctx.command()?; From 0d30ead6e37b2efc909dbf905e5724b1a8d2de47 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sat, 11 Jul 2026 17:27:36 +0200 Subject: [PATCH 19/40] (refactor): updated consumer userspace API with the latest kernel space data structure (NetworkMetrics->PacketLossMetrics) update --- core/common/src/buffer_type.rs | 46 +++++++++++----------- core/src/components/metrics/src/helpers.rs | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/core/common/src/buffer_type.rs b/core/common/src/buffer_type.rs index 7124e55f..8ec000c0 100644 --- a/core/common/src/buffer_type.rs +++ b/core/common/src/buffer_type.rs @@ -95,7 +95,7 @@ pub const TASK_COMM_LEN: usize = 16; // linux/sched.h #[cfg(feature = "monitoring-structs")] #[repr(C, packed)] #[derive(Clone, Copy, Zeroable)] -pub struct NetworkMetrics { +pub struct PacketLossMetrics { pub tgid: u32, pub comm: [u8; TASK_COMM_LEN], pub ts_us: u64, @@ -108,7 +108,7 @@ pub struct NetworkMetrics { pub sk_drops: i32, // Offset 136 } #[cfg(feature = "monitoring-structs")] -unsafe impl aya::Pod for NetworkMetrics {} +unsafe impl aya::Pod for PacketLossMetrics {} #[cfg(feature = "monitoring-structs")] #[repr(C, packed)] @@ -208,7 +208,7 @@ pub enum BufferType { #[cfg(feature = "network-structs")] VethLog, #[cfg(feature = "monitoring-structs")] - NetworkMetrics, + PacketLossMetrics, #[cfg(feature = "monitoring-structs")] TimeStampMetrics, #[cfg(feature = "monitoring-structs")] @@ -436,7 +436,7 @@ impl BufferType { /// /// Counterpart to [`read_network_buffer`] for the `time_stamp_events` map. - pub async fn read_network_metrics( + pub async fn read_packet_loss_metrics( buffers: &mut [BytesMut], tot_events: i32, offset: i32, @@ -445,7 +445,7 @@ impl BufferType { ) { for i in offset..tot_events { let vec_bytes = &buffers[i as usize]; - if vec_bytes.len() < std::mem::size_of::() { + if vec_bytes.len() < std::mem::size_of::() { error!( "Corrupted Network Metrics data. Raw data: {}. Readed {} bytes expected {} bytes", vec_bytes @@ -454,28 +454,28 @@ impl BufferType { .collect::>() .join(" "), vec_bytes.len(), - std::mem::size_of::() + std::mem::size_of::() ); continue; } - if vec_bytes.len() >= std::mem::size_of::() { - let net_metrics: NetworkMetrics = + if vec_bytes.len() >= std::mem::size_of::() { + let packet_loss: PacketLossMetrics = unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; match exporter { - "otlp" => metrics.record_network_metrics(&net_metrics), + "otlp" => metrics.record_packet_loss_metrics(&packet_loss), _ => continue, // skip } - let tgid = net_metrics.tgid; - let comm = String::from_utf8_lossy(&net_metrics.comm); - let ts_us = net_metrics.ts_us; - let sk_drop_count = net_metrics.sk_drops; - let sk_err = net_metrics.sk_err; - let sk_err_soft = net_metrics.sk_err_soft; - let sk_backlog_len = net_metrics.sk_backlog_len; - let sk_write_memory_queued = net_metrics.sk_write_memory_queued; - let sk_ack_backlog = net_metrics.sk_ack_backlog; - let sk_receive_buffer_size = net_metrics.sk_receive_buffer_size; + let tgid = packet_loss.tgid; + let comm = String::from_utf8_lossy(&packet_loss.comm); + let ts_us = packet_loss.ts_us; + let sk_drop_count = packet_loss.sk_drops; + let sk_err = packet_loss.sk_err; + let sk_err_soft = packet_loss.sk_err_soft; + let sk_backlog_len = packet_loss.sk_backlog_len; + let sk_write_memory_queued = packet_loss.sk_write_memory_queued; + let sk_ack_backlog = packet_loss.sk_ack_backlog; + let sk_receive_buffer_size = packet_loss.sk_receive_buffer_size; info!( "tgid: {}, comm: {}, ts_us: {}, sk_drops: {}, sk_err: {}, sk_err_soft: {}, sk_backlog_len: {}, sk_write_memory_queued: {}, sk_ack_backlog: {}, sk_receive_buffer_size: {}", @@ -811,15 +811,15 @@ pub async fn read_perf_buffer>( .await } #[cfg(feature = "monitoring-structs")] - BufferType::NetworkMetrics => { - BufferType::read_network_metrics( + BufferType::PacketLossMetrics => { + BufferType::read_packet_loss_metrics( &mut buffers, tot_events, offset, "otlp", metrics .clone() - .expect("Metrics required for NetworkMetrics"), + .expect("Metrics required for PacketLossMetrics"), ) .await } @@ -939,7 +939,7 @@ impl BufferSize { #[cfg(feature = "network-structs")] BufferSize::TcpEvents => std::mem::size_of::(), #[cfg(feature = "monitoring-structs")] - BufferSize::NetworkMetricsEvents => std::mem::size_of::(), + BufferSize::NetworkMetricsEvents => std::mem::size_of::(), #[cfg(feature = "monitoring-structs")] BufferSize::TimeMetricsEvents => std::mem::size_of::(), #[cfg(feature = "monitoring-structs")] diff --git a/core/src/components/metrics/src/helpers.rs b/core/src/components/metrics/src/helpers.rs index 141cad6f..f162048e 100644 --- a/core/src/components/metrics/src/helpers.rs +++ b/core/src/components/metrics/src/helpers.rs @@ -103,7 +103,7 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a read_perf_buffer( array_buffers, buffers, - BufferType::NetworkMetrics, + BufferType::PacketLossMetrics, Some(metrics), ) .await; From 47708df0d56817791cf111e06fc5f9c71dbcd89e Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sat, 11 Jul 2026 17:29:37 +0200 Subject: [PATCH 20/40] (refactor): updated metrics/main.rs with the latest metrics_tracer changes. Improve semantics --- core/src/components/metrics/src/main.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/core/src/components/metrics/src/main.rs b/core/src/components/metrics/src/main.rs index bcf7de72..3a7bcbb6 100644 --- a/core/src/components/metrics/src/main.rs +++ b/core/src/components/metrics/src/main.rs @@ -79,10 +79,14 @@ async fn main() -> Result<(), anyhow::Error> { info!("BPF maps pinned successfully to {}", bpf_map_save_path); { - load_program(bpf.clone(), "metrics_tracer", "tcp_identify_packet_loss") - .context( - "An error occurred during the execution of load_program function", - )?; + load_program( + bpf.clone(), + "packet_loss_tracer", + "tcp_identify_packet_loss", + ) + .context( + "An error occurred during the execution of load_program function", + )?; load_program(tcp_bpf, "tcp_v4_connect", "tcp_v4_connect") .context("An error occurred during the execution of load_and_attach_tcp_programs function")?; @@ -90,12 +94,8 @@ async fn main() -> Result<(), anyhow::Error> { load_program(tcp_v6_bpf, "tcp_v6_connect", "tcp_v6_connect") .context("An error occurred during the execution of load_and_attach_tcp_programs function")?; - load_program( - tcp_rev_bpf, - "tcp_rcv_state_process", - "tcp_rcv_state_process", - ) - .context( + load_program(tcp_rev_bpf, "tcp_latency_monitor", "tcp_rcv_state_process") + .context( "An error occurred during the execution of load_program function", )?; load_tracepoint_program( From dfe2d33daecdfa3ea5ef188ceabb4fde6f39189a Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sat, 11 Jul 2026 17:31:00 +0200 Subject: [PATCH 21/40] (metrics): updated metrics semantics to better represent the metrics traced --- core/common/src/otel_metrics.rs | 64 +++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/core/common/src/otel_metrics.rs b/core/common/src/otel_metrics.rs index 79d08b88..af2ba76c 100644 --- a/core/common/src/otel_metrics.rs +++ b/core/common/src/otel_metrics.rs @@ -12,7 +12,7 @@ //! telemetry by process. use crate::buffer_type::{ - CpuFrequency, CpuIdle, MemAlloc, NetworkMetrics, SchedStatRuntime, SchedStatWait, + CpuFrequency, CpuIdle, MemAlloc, PacketLossMetrics, SchedStatRuntime, SchedStatWait, TimeStampMetrics, }; use opentelemetry::KeyValue; @@ -23,7 +23,7 @@ pub struct Metrics { /// Total number of network-related events produced by the `net_metrics` /// eBPF map. - pub packets_total: Counter, + pub socket_events_total: Counter, /// Observed socket drop count (`sk_drops`) from the kernel sock struct. pub sk_drops: Gauge, @@ -33,11 +33,11 @@ pub struct Metrics { /// Histogram of `delta_us` values supplied by the `time_stamp_events` /// perf buffer. - pub delta_us: Histogram, + pub tcp_latency_us: Histogram, /// Histogram of `ts_us` values seen in both `net_metrics` and /// `time_stamp_events`. - pub ts_us: Histogram, + //pub tcp_ts_us: Histogram, /// Cpu bytes alloc total events pub cpu_bytes_alloc_events_total: Counter, @@ -61,6 +61,8 @@ pub struct Metrics { pub cpu_idle_state: Gauge, } +// TODO: add identity metrics with TC classifier packet counts +// TODO: introduce a metric called total_tcp_packets total_udp_packets impl Metrics { /// Initialise all instruments backed by the supplied [`Meter`]. pub fn new(meter: &Meter) -> Self { @@ -70,10 +72,10 @@ impl Metrics { .with_description("Total number of eBPF events processed") .build(); - // total packets - let packets_total = meter - .u64_counter("packets_total") - .with_description("Total number of network events processed") + // total socket events + let socket_events_total = meter + .u64_counter("socket_events_total") + .with_description("Total number of socket state events processed") .build(); // socket drops @@ -88,52 +90,58 @@ impl Metrics { .with_description("Socket error count per event") .build(); - // delta microseconds - let delta_us = meter - .u64_histogram("delta_us") - .with_description("Distribution of delta_us values from timestamp events") + // tcp latency microseconds + let tcp_latency_us = meter + .u64_histogram("latency_us") + .with_description("Distribution of latency values from timestamp events") .build(); - // timestamp microseconds grouped - let ts_us = meter - .u64_histogram("ts_us") - .with_description("Distribution of timestamp values from eBPF events") - .build(); + // tcp timestamp microseconds grouped + //let tcp_ts_us = meter + // .u64_histogram("ts_us") + // .with_description("Distribution of timestamp values from eBPF events") + // .build(); // cpu bytes alloc total events let cpu_bytes_alloc_events_total = meter .u64_counter("bytes_alloc_events_total") .with_description("Total bytes_alloc events occuring in the CPU") + .with_unit("n") .build(); // cpu bytes allocation let cpu_bytes_alloc = meter .i64_gauge("cpu_bytes_alloc") .with_description("Cpu bytes allocation per event") + .with_unit("bytes") .build(); // memory allocation (mmap) events total let mem_alloc_events_total = meter .u64_counter("mem_alloc_events_total") .with_description("Total number of memory allocation (mmap) events processed") + .with_unit("n") .build(); // bytes requested via mmap syscalls let enter_mem_alloc = meter .i64_gauge("enter_mem_alloc") .with_description("Bytes requested via mmap syscalls") + .with_unit("bytes") .build(); // scheduler wait time in nanoseconds let sched_stat_wait = meter .i64_gauge("sched_stat_wait") .with_description("Scheduler wait time in nanoseconds from sched_stat_wait") + .with_unit("ns") .build(); // scheduler runtime in nanoseconds let sched_stat_runtime = meter .i64_gauge("sched_stat_runtime") .with_description("Scheduler runtime in nanoseconds from sched_stat_runtime") + .with_unit("ns") .build(); // current CPU idle C-state per cpu_id @@ -144,11 +152,11 @@ impl Metrics { Self { events_total, - packets_total, + socket_events_total, sk_drops, sk_err, - delta_us, - ts_us, + tcp_latency_us, + //tcp_ts_us, cpu_bytes_alloc, cpu_bytes_alloc_events_total, mem_alloc_events_total, @@ -169,7 +177,7 @@ impl Metrics { /// -`tgid` – task group ID. /// - `comm` – command name (null-terminated bytes converted to a UTF-8 /// string and trimmed). - pub fn record_network_metrics(&self, m: &NetworkMetrics) { + pub fn record_packet_loss_metrics(&self, m: &PacketLossMetrics) { let comm = String::from_utf8_lossy(&m.comm); let comm_trimmed = comm.trim_end_matches('\0').to_string(); let attrs = &[ @@ -178,10 +186,10 @@ impl Metrics { ]; self.events_total.add(1, attrs); - self.packets_total.add(1, attrs); + self.socket_events_total.add(1, attrs); self.sk_drops.record(m.sk_drops as i64, attrs); - self.sk_err.record(m.sk_err as i64, attrs); - self.ts_us.record(m.ts_us, attrs); + //self.sk_err.record(m.sk_err as i64, attrs); + //self.tcp_ts_us.record(m.tcp_ts_us, attrs); } /// Record a single [`TimeStampMetrics`] event. @@ -200,8 +208,8 @@ impl Metrics { ]; self.events_total.add(1, attrs); - self.delta_us.record(m.delta_us, attrs); - self.ts_us.record(m.ts_us, attrs); + self.tcp_latency_us.record(m.delta_us, attrs); + //self.tcp_ts_us.record(m.ts_us, attrs); } pub fn record_cpu_bytes_alloc(&self, m: &CpuFrequency) { @@ -231,6 +239,7 @@ impl Metrics { KeyValue::new("command", command), ]; + self.events_total.add(1, attrs); self.mem_alloc_events_total.add(1, attrs); self.enter_mem_alloc.record(m.length as i64, attrs); } @@ -247,6 +256,7 @@ impl Metrics { KeyValue::new("command", command), ]; + self.events_total.add(1, attrs); self.sched_stat_wait.record(m.delay as i64, attrs); } @@ -262,6 +272,7 @@ impl Metrics { KeyValue::new("command", command), ]; + self.events_total.add(1, attrs); self.sched_stat_runtime.record(m.runtime as i64, attrs); } @@ -272,6 +283,7 @@ impl Metrics { pub fn record_cpu_idle(&self, m: &CpuIdle) { let attrs = &[KeyValue::new("cpu_id", m.cpu_id as i64)]; + self.events_total.add(1, attrs); self.cpu_idle_state.record(m.state as i64, attrs); } } From 09dea56284507f6fd687123a350f00d7616f107a Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sun, 12 Jul 2026 23:14:39 +0200 Subject: [PATCH 22/40] (fix): fixed typo in build-local-metrics --- core/src/components/metrics/build-local-metrics.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/components/metrics/build-local-metrics.sh b/core/src/components/metrics/build-local-metrics.sh index b861e6ec..d0ed7523 100755 --- a/core/src/components/metrics/build-local-metrics.sh +++ b/core/src/components/metrics/build-local-metrics.sh @@ -3,7 +3,7 @@ # Building identity files echo "Building the metrics-tracer files" pushd ../metrics_tracer -./build-metrics-tracer.sh +./build-local-metrics-tracer.sh popd echo "Copying metrics_tracer binaries" From b91f8321c104b74ec588e8cd4b550964833ef0f1 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sun, 12 Jul 2026 23:15:39 +0200 Subject: [PATCH 23/40] (fix): fixed typos introduced during the merge --- core/common/src/otel_metrics.rs | 114 ------------------------ core/src/components/metrics/src/main.rs | 45 ---------- 2 files changed, 159 deletions(-) diff --git a/core/common/src/otel_metrics.rs b/core/common/src/otel_metrics.rs index 8d718ea1..123c44cb 100644 --- a/core/common/src/otel_metrics.rs +++ b/core/common/src/otel_metrics.rs @@ -149,49 +149,6 @@ impl Metrics { .i64_gauge("cpu_idle_state") .with_description("Current CPU idle C-state per cpu_id, updated only on state change") .build(); - - // cpu bytes alloc total events - let cpu_bytes_alloc_events_total = meter - .u64_counter("bytes_alloc_events_total") - .with_description("Total bytes_alloc events occuring in the CPU") - .build(); - - // cpu bytes allocation - let cpu_bytes_alloc = meter - .i64_gauge("cpu_bytes_alloc") - .with_description("Cpu bytes allocation per event") - .build(); - - // memory allocation (mmap) events total - let mem_alloc_events_total = meter - .u64_counter("mem_alloc_events_total") - .with_description("Total number of memory allocation (mmap) events processed") - .build(); - - // bytes requested via mmap syscalls - let enter_mem_alloc = meter - .i64_gauge("enter_mem_alloc") - .with_description("Bytes requested via mmap syscalls") - .build(); - - // scheduler wait time in nanoseconds - let sched_stat_wait = meter - .i64_gauge("sched_stat_wait") - .with_description("Scheduler wait time in nanoseconds from sched_stat_wait") - .build(); - - // scheduler runtime in nanoseconds - let sched_stat_runtime = meter - .i64_gauge("sched_stat_runtime") - .with_description("Scheduler runtime in nanoseconds from sched_stat_runtime") - .build(); - - // current CPU idle C-state per cpu_id - let cpu_idle_state = meter - .i64_gauge("cpu_idle_state") - .with_description("Current CPU idle C-state per cpu_id, updated only on state change") - .build(); - Self { events_total, socket_events_total, @@ -328,75 +285,4 @@ impl Metrics { self.events_total.add(1, attrs); self.cpu_idle_state.record(m.state as i64, attrs); } - - pub fn record_cpu_bytes_alloc(&self, m: &CpuFrequency) { - let bytes_allocated = m.bytes_alloc; - let tgid = m.pid; // percpu tracepoints expose TGID in common_pid - let comm = String::from_utf8_lossy(&m.command); - let command = comm.trim_end_matches('\0').to_string(); - let attrs = &[ - KeyValue::new("tgid", tgid as i64), - KeyValue::new("command", command), - ]; - self.cpu_bytes_alloc_events_total.add(1, attrs); - self.cpu_bytes_alloc.record(bytes_allocated as i64, attrs); - } - - /// Record a single [`MemAlloc`] event (mmap syscall). - /// - /// Increments the dedicated `mem_alloc_events_total` counter and records - /// the requested length in the `enter_mem_alloc` gauge. The shared - /// `events_total` counter is intentionally **not** incremented for these - /// events. - pub fn record_enter_mem_alloc(&self, m: &MemAlloc) { - let comm = String::from_utf8_lossy(&m.command); - let command = comm.trim_end_matches('\0').to_string(); - let attrs = &[ - KeyValue::new("tgid", m.tgid as i64), - KeyValue::new("command", command), - ]; - - self.mem_alloc_events_total.add(1, attrs); - self.enter_mem_alloc.record(m.length as i64, attrs); - } - - /// Record a single [`SchedStatWait`] event. - /// - /// Records `delay` in the `sched_stat_wait` gauge. No shared or dedicated - /// counter is incremented, as requested. - pub fn record_sched_stat_wait(&self, m: &SchedStatWait) { - let comm = String::from_utf8_lossy(&m.command); - let command = comm.trim_end_matches('\0').to_string(); - let attrs = &[ - KeyValue::new("tgid", m.tgid as i64), - KeyValue::new("command", command), - ]; - - self.sched_stat_wait.record(m.delay as i64, attrs); - } - - /// Record a single [`SchedStatRuntime`] event. - /// - /// Records `runtime` in the `sched_stat_runtime` gauge. No shared or - /// dedicated counter is incremented, as requested. - pub fn record_sched_stat_runtime(&self, m: &SchedStatRuntime) { - let comm = String::from_utf8_lossy(&m.command); - let command = comm.trim_end_matches('\0').to_string(); - let attrs = &[ - KeyValue::new("tgid", m.tgid as i64), - KeyValue::new("command", command), - ]; - - self.sched_stat_runtime.record(m.runtime as i64, attrs); - } - - /// Record a single [`CpuIdle`] event. - /// - /// Updates `cpu_idle_state` gauge to the latest C-state for the given - /// `cpu_id`. Events are only emitted by eBPF when the state changes. - pub fn record_cpu_idle(&self, m: &CpuIdle) { - let attrs = &[KeyValue::new("cpu_id", m.cpu_id as i64)]; - - self.cpu_idle_state.record(m.state as i64, attrs); - } } diff --git a/core/src/components/metrics/src/main.rs b/core/src/components/metrics/src/main.rs index 80cd109a..3a7bcbb6 100644 --- a/core/src/components/metrics/src/main.rs +++ b/core/src/components/metrics/src/main.rs @@ -143,51 +143,6 @@ async fn main() -> Result<(), anyhow::Error> { .context( "An error occurred during the execution of load_program function", )?; - load_tracepoint_program( - cpu_frequency, - "trace_cpu_frequency", - "percpu", - "percpu_alloc_percpu", - ) - .context( - "An error occurred during the execution of load_program function", - )?; - load_tracepoint_program( - cpu_idle_bpf, - "trace_cpu_idle", - "power", - "cpu_idle", - ) - .context( - "An error occurred during the execution of load_program function", - )?; - load_tracepoint_program( - mem_alloc_bpf, - "trace_enter_mmap", - "syscalls", - "sys_enter_mmap", - ) - .context( - "An error occurred during the execution of load_program function", - )?; - load_tracepoint_program( - sched_stat_wait_bpf, - "trace_sched_stat_wait", - "sched", - "sched_stat_wait", - ) - .context( - "An error occurred during the execution of load_program function", - )?; - load_tracepoint_program( - sched_stat_runtime_bpf, - "trace_sched_stat_runtime", - "sched", - "sched_stat_runtime", - ) - .context( - "An error occurred during the execution of load_program function", - )?; } // Hand off to the async event consumer From 97ddf521041d4c4bad065ced030283dc19f797e2 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Tue, 14 Jul 2026 00:43:52 +0200 Subject: [PATCH 24/40] [#201]: added semantic.rs --- core/common/src/lib.rs | 1 + core/common/src/otel_metrics.rs | 53 ++++++++++++++-------------- core/common/src/semantic.rs | 61 +++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 28 deletions(-) create mode 100644 core/common/src/semantic.rs diff --git a/core/common/src/lib.rs b/core/common/src/lib.rs index 15c4ad70..a0b28770 100644 --- a/core/common/src/lib.rs +++ b/core/common/src/lib.rs @@ -13,3 +13,4 @@ pub mod map_handlers; pub mod otel_metrics; #[cfg(feature = "program-handlers")] pub mod program_handlers; +mod semantic; diff --git a/core/common/src/otel_metrics.rs b/core/common/src/otel_metrics.rs index 123c44cb..a97a66fc 100644 --- a/core/common/src/otel_metrics.rs +++ b/core/common/src/otel_metrics.rs @@ -15,6 +15,7 @@ use crate::buffer_type::{ CpuFrequency, CpuIdle, MemAlloc, PacketLossMetrics, SchedStatRuntime, SchedStatWait, TimeStampMetrics, }; +use crate::semantic::Semantic; use opentelemetry::KeyValue; use opentelemetry::metrics::{Counter, Gauge, Histogram, Meter}; pub struct Metrics { @@ -35,10 +36,6 @@ pub struct Metrics { /// perf buffer. pub tcp_latency_us: Histogram, - /// Histogram of `ts_us` values seen in both `net_metrics` and - /// `time_stamp_events`. - //pub tcp_ts_us: Histogram, - /// Cpu bytes alloc total events pub cpu_bytes_alloc_events_total: Counter, @@ -68,32 +65,32 @@ impl Metrics { pub fn new(meter: &Meter) -> Self { // total events let events_total = meter - .u64_counter("events_total") - .with_description("Total number of eBPF events processed") + .u64_counter(Semantic::TOTAL_EVENTS.title()) + .with_description(Semantic::TOTAL_EVENTS.description()) .build(); // total socket events let socket_events_total = meter - .u64_counter("socket_events_total") - .with_description("Total number of socket state events processed") + .u64_counter(Semantic::SOCKET_TOTAL_EVENTS.title()) + .with_description(Semantic::SOCKET_TOTAL_EVENTS.description()) .build(); // socket drops let sk_drops = meter - .i64_gauge("sk_drops") - .with_description("Socket drop count per event") + .i64_gauge(Semantic::SOCKET_DROPS.title()) + .with_description(Semantic::SOCKET_DROPS.description()) .build(); // socket errors let sk_err = meter - .i64_gauge("sk_err") - .with_description("Socket error count per event") + .i64_gauge(Semantic::SOCKET_ERRORS_COUNT.title()) + .with_description(Semantic::SOCKET_ERRORS_COUNT.description()) .build(); // tcp latency microseconds let tcp_latency_us = meter - .u64_histogram("latency_us") - .with_description("Distribution of latency values from timestamp events") + .u64_histogram(Semantic::LATENCY.title()) + .with_description(Semantic::LATENCY.description()) .build(); // tcp timestamp microseconds grouped @@ -104,50 +101,50 @@ impl Metrics { // cpu bytes alloc total events let cpu_bytes_alloc_events_total = meter - .u64_counter("bytes_alloc_events_total") - .with_description("Total bytes_alloc events occuring in the CPU") + .u64_counter(Semantic::PERCPU_TOTAL_EVENTS.title()) + .with_description(Semantic::PERCPU_TOTAL_EVENTS.description()) .with_unit("n") .build(); // cpu bytes allocation let cpu_bytes_alloc = meter - .i64_gauge("cpu_bytes_alloc") - .with_description("Cpu bytes allocation per event") + .i64_gauge(Semantic::PERCPU_BYTES_ALLOCATED.title()) + .with_description(Semantic::PERCPU_BYTES_ALLOCATED.description()) .with_unit("bytes") .build(); // memory allocation (mmap) events total let mem_alloc_events_total = meter - .u64_counter("mem_alloc_events_total") - .with_description("Total number of memory allocation (mmap) events processed") + .u64_counter(Semantic::TOTAL_MEMORY_ALLOCATION_EVENTS.title()) + .with_description(Semantic::TOTAL_MEMORY_ALLOCATION_EVENTS.description()) .with_unit("n") .build(); // bytes requested via mmap syscalls let enter_mem_alloc = meter - .i64_gauge("enter_mem_alloc") - .with_description("Bytes requested via mmap syscalls") + .i64_gauge(Semantic::REQUESTED_MEMORY_BYTES.title()) + .with_description(Semantic::REQUESTED_MEMORY_BYTES.description()) .with_unit("bytes") .build(); // scheduler wait time in nanoseconds let sched_stat_wait = meter - .i64_gauge("sched_stat_wait") - .with_description("Scheduler wait time in nanoseconds from sched_stat_wait") + .i64_gauge(Semantic::SCHEDULER_WAIT_TIME.title()) + .with_description(Semantic::SCHEDULER_WAIT_TIME.description()) .with_unit("ns") .build(); // scheduler runtime in nanoseconds let sched_stat_runtime = meter - .i64_gauge("sched_stat_runtime") - .with_description("Scheduler runtime in nanoseconds from sched_stat_runtime") + .i64_gauge(Semantic::SCHEDULER_RUNTIME.title()) + .with_description(Semantic::SCHEDULER_RUNTIME.description()) .with_unit("ns") .build(); // current CPU idle C-state per cpu_id let cpu_idle_state = meter - .i64_gauge("cpu_idle_state") - .with_description("Current CPU idle C-state per cpu_id, updated only on state change") + .i64_gauge(Semantic::CPU_IDLE_STATE.title()) + .with_description(Semantic::CPU_IDLE_STATE.description()) .build(); Self { events_total, diff --git a/core/common/src/semantic.rs b/core/common/src/semantic.rs new file mode 100644 index 00000000..ee78f1fe --- /dev/null +++ b/core/common/src/semantic.rs @@ -0,0 +1,61 @@ +/// semantic conventions + +pub enum Semantic { + TOTAL_EVENTS, + SOCKET_TOTAL_EVENTS, + SOCKET_DROPS, + SOCKET_ERRORS_COUNT, + LATENCY, + PERCPU_TOTAL_EVENTS, + PERCPU_BYTES_ALLOCATED, + SCHEDULER_RUNTIME, + SCHEDULER_WAIT_TIME, + TOTAL_MEMORY_ALLOCATION_EVENTS, + REQUESTED_MEMORY_BYTES, + CPU_IDLE_STATE, +} + +impl Semantic { + pub fn title(&self) -> &'static str { + match self { + Semantic::TOTAL_EVENTS => "events_total", + Semantic::SOCKET_TOTAL_EVENTS => "socket_events_total", + Semantic::SOCKET_DROPS => "sk_drops", + Semantic::SOCKET_ERRORS_COUNT => "sk_err", + Semantic::LATENCY => "latency_us", + Semantic::PERCPU_TOTAL_EVENTS => "bytes_alloc_events_total", + Semantic::PERCPU_BYTES_ALLOCATED => "cpu_bytes_alloc", + Semantic::SCHEDULER_RUNTIME => "sched_stat_runtime", + Semantic::SCHEDULER_WAIT_TIME => "sched_stat_wait", + Semantic::TOTAL_MEMORY_ALLOCATION_EVENTS => "mem_alloc_events_total", + Semantic::REQUESTED_MEMORY_BYTES => "enter_mem_alloc", + Semantic::CPU_IDLE_STATE => "cpu_idle_state", + } + } + pub fn description(&self) -> &'static str { + match self { + Semantic::TOTAL_EVENTS => { + "Total number of eBPF events processed across all perf buffers" + } + Semantic::SOCKET_TOTAL_EVENTS => "Total number of socket state events processed", + Semantic::SOCKET_DROPS => "Socket drop count per event", + Semantic::SOCKET_ERRORS_COUNT => "Socket error count per event", + Semantic::LATENCY => "Distribution of latency values from timestamp events", + Semantic::PERCPU_TOTAL_EVENTS => "Total bytes_alloc events occuring in the CPU", + Semantic::PERCPU_BYTES_ALLOCATED => "Cpu bytes allocation per event", + Semantic::SCHEDULER_RUNTIME => { + "Scheduler runtime in nanoseconds from sched_stat_runtime" + } + Semantic::SCHEDULER_WAIT_TIME => { + "Scheduler wait time in nanoseconds from sched_stat_wait" + } + Semantic::TOTAL_MEMORY_ALLOCATION_EVENTS => { + "Total number of memory allocation (mmap) events processed" + } + Semantic::REQUESTED_MEMORY_BYTES => "Bytes requested via mmap syscalls", + Semantic::CPU_IDLE_STATE => { + "Current CPU idle C-state per cpu_id, updated only on state change" + } + } + } +} From bb373a6a263c837be83f8d32918589bcdac9c327 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Wed, 15 Jul 2026 20:33:16 +0200 Subject: [PATCH 25/40] (cleaning): removed deprecated code. Fixed uppercase enum variant with PascalCase enum variant for Semantic enum --- core/common/src/otel_metrics.rs | 82 ++++++++++++++++----------------- core/common/src/semantic.rs | 72 ++++++++++++++--------------- 2 files changed, 75 insertions(+), 79 deletions(-) diff --git a/core/common/src/otel_metrics.rs b/core/common/src/otel_metrics.rs index a97a66fc..a1b2c866 100644 --- a/core/common/src/otel_metrics.rs +++ b/core/common/src/otel_metrics.rs @@ -65,86 +65,85 @@ impl Metrics { pub fn new(meter: &Meter) -> Self { // total events let events_total = meter - .u64_counter(Semantic::TOTAL_EVENTS.title()) - .with_description(Semantic::TOTAL_EVENTS.description()) + .u64_counter(Semantic::TotalEvents.title()) + .with_description(Semantic::TotalEvents.description()) + .with_unit("1") .build(); // total socket events let socket_events_total = meter - .u64_counter(Semantic::SOCKET_TOTAL_EVENTS.title()) - .with_description(Semantic::SOCKET_TOTAL_EVENTS.description()) + .u64_counter(Semantic::SocketTotalEvents.title()) + .with_description(Semantic::SocketTotalEvents.description()) + .with_unit("1") .build(); // socket drops let sk_drops = meter - .i64_gauge(Semantic::SOCKET_DROPS.title()) - .with_description(Semantic::SOCKET_DROPS.description()) + .i64_gauge(Semantic::SocketDrops.title()) + .with_description(Semantic::SocketDrops.description()) + .with_unit("1") .build(); // socket errors let sk_err = meter - .i64_gauge(Semantic::SOCKET_ERRORS_COUNT.title()) - .with_description(Semantic::SOCKET_ERRORS_COUNT.description()) + .i64_gauge(Semantic::SocketErrorsCount.title()) + .with_description(Semantic::SocketErrorsCount.description()) + .with_unit("1") .build(); // tcp latency microseconds let tcp_latency_us = meter - .u64_histogram(Semantic::LATENCY.title()) - .with_description(Semantic::LATENCY.description()) + .u64_histogram(Semantic::Latency.title()) + .with_description(Semantic::Latency.description()) + .with_unit("us") .build(); - // tcp timestamp microseconds grouped - //let tcp_ts_us = meter - // .u64_histogram("ts_us") - // .with_description("Distribution of timestamp values from eBPF events") - // .build(); - // cpu bytes alloc total events let cpu_bytes_alloc_events_total = meter - .u64_counter(Semantic::PERCPU_TOTAL_EVENTS.title()) - .with_description(Semantic::PERCPU_TOTAL_EVENTS.description()) - .with_unit("n") + .u64_counter(Semantic::PerCpuTotalEvents.title()) + .with_description(Semantic::PerCpuTotalEvents.description()) + .with_unit("1") .build(); // cpu bytes allocation let cpu_bytes_alloc = meter - .i64_gauge(Semantic::PERCPU_BYTES_ALLOCATED.title()) - .with_description(Semantic::PERCPU_BYTES_ALLOCATED.description()) + .i64_gauge(Semantic::PerCpuBytesAllocated.title()) + .with_description(Semantic::PerCpuBytesAllocated.description()) .with_unit("bytes") .build(); // memory allocation (mmap) events total let mem_alloc_events_total = meter - .u64_counter(Semantic::TOTAL_MEMORY_ALLOCATION_EVENTS.title()) - .with_description(Semantic::TOTAL_MEMORY_ALLOCATION_EVENTS.description()) - .with_unit("n") + .u64_counter(Semantic::TotalMemoryAllocationEvents.title()) + .with_description(Semantic::TotalMemoryAllocationEvents.description()) + .with_unit("1") .build(); // bytes requested via mmap syscalls let enter_mem_alloc = meter - .i64_gauge(Semantic::REQUESTED_MEMORY_BYTES.title()) - .with_description(Semantic::REQUESTED_MEMORY_BYTES.description()) + .i64_gauge(Semantic::RequestedMemoryBytes.title()) + .with_description(Semantic::RequestedMemoryBytes.description()) .with_unit("bytes") .build(); // scheduler wait time in nanoseconds let sched_stat_wait = meter - .i64_gauge(Semantic::SCHEDULER_WAIT_TIME.title()) - .with_description(Semantic::SCHEDULER_WAIT_TIME.description()) + .i64_gauge(Semantic::SchedulerWaitTime.title()) + .with_description(Semantic::SchedulerWaitTime.description()) .with_unit("ns") .build(); // scheduler runtime in nanoseconds let sched_stat_runtime = meter - .i64_gauge(Semantic::SCHEDULER_RUNTIME.title()) - .with_description(Semantic::SCHEDULER_RUNTIME.description()) + .i64_gauge(Semantic::SchedulerRuntime.title()) + .with_description(Semantic::SchedulerRuntime.description()) .with_unit("ns") .build(); // current CPU idle C-state per cpu_id let cpu_idle_state = meter - .i64_gauge(Semantic::CPU_IDLE_STATE.title()) - .with_description(Semantic::CPU_IDLE_STATE.description()) + .i64_gauge(Semantic::CpuIdleState.title()) + .with_description(Semantic::CpuIdleState.description()) .build(); Self { events_total, @@ -152,7 +151,6 @@ impl Metrics { sk_drops, sk_err, tcp_latency_us, - //tcp_ts_us, cpu_bytes_alloc, cpu_bytes_alloc_events_total, mem_alloc_events_total, @@ -163,14 +161,14 @@ impl Metrics { } } - /// Record a single [`NetworkMetrics`] event. + /// Record a single [`PacketLossMetrics`] event. /// - /// Increments `events_total` and `packets_total`, records `sk_drops` and - /// `sk_err` as gauges, and observes `ts_us` in the timestamp histogram. + /// Increments `events_total` and `socket_events_total`, records `sk_drops` + /// and `sk_err` as gauges. /// /// Every observation carries: /// - /// -`tgid` – task group ID. + /// - `tgid` – task group ID. /// - `comm` – command name (null-terminated bytes converted to a UTF-8 /// string and trimmed). pub fn record_packet_loss_metrics(&self, m: &PacketLossMetrics) { @@ -184,17 +182,16 @@ impl Metrics { self.events_total.add(1, attrs); self.socket_events_total.add(1, attrs); self.sk_drops.record(m.sk_drops as i64, attrs); - //self.sk_err.record(m.sk_err as i64, attrs); - //self.tcp_ts_us.record(m.tcp_ts_us, attrs); + self.sk_err.record(m.sk_err as i64, attrs); } /// Record a single [`TimeStampMetrics`] event. /// - /// Increments `events_total`, and records `delta_us` and `ts_us` in their - /// respective histograms. + /// Increments `events_total`, and records `delta_us` in the latency + /// histogram. /// /// Every observation carries `tgid` and `comm` (see - /// [`record_network_metrics`]). + /// [`record_packet_loss_metrics`]). pub fn record_timestamp_metrics(&self, m: &TimeStampMetrics) { let comm = String::from_utf8_lossy(&m.comm); let comm_trimmed = comm.trim_end_matches('\0').to_string(); @@ -205,7 +202,6 @@ impl Metrics { self.events_total.add(1, attrs); self.tcp_latency_us.record(m.delta_us, attrs); - //self.tcp_ts_us.record(m.ts_us, attrs); } pub fn record_cpu_bytes_alloc(&self, m: &CpuFrequency) { diff --git a/core/common/src/semantic.rs b/core/common/src/semantic.rs index ee78f1fe..b16bdbe9 100644 --- a/core/common/src/semantic.rs +++ b/core/common/src/semantic.rs @@ -1,59 +1,59 @@ /// semantic conventions pub enum Semantic { - TOTAL_EVENTS, - SOCKET_TOTAL_EVENTS, - SOCKET_DROPS, - SOCKET_ERRORS_COUNT, - LATENCY, - PERCPU_TOTAL_EVENTS, - PERCPU_BYTES_ALLOCATED, - SCHEDULER_RUNTIME, - SCHEDULER_WAIT_TIME, - TOTAL_MEMORY_ALLOCATION_EVENTS, - REQUESTED_MEMORY_BYTES, - CPU_IDLE_STATE, + TotalEvents, + SocketTotalEvents, + SocketDrops, + SocketErrorsCount, + Latency, + PerCpuTotalEvents, + PerCpuBytesAllocated, + SchedulerRuntime, + SchedulerWaitTime, + TotalMemoryAllocationEvents, + RequestedMemoryBytes, + CpuIdleState, } impl Semantic { pub fn title(&self) -> &'static str { match self { - Semantic::TOTAL_EVENTS => "events_total", - Semantic::SOCKET_TOTAL_EVENTS => "socket_events_total", - Semantic::SOCKET_DROPS => "sk_drops", - Semantic::SOCKET_ERRORS_COUNT => "sk_err", - Semantic::LATENCY => "latency_us", - Semantic::PERCPU_TOTAL_EVENTS => "bytes_alloc_events_total", - Semantic::PERCPU_BYTES_ALLOCATED => "cpu_bytes_alloc", - Semantic::SCHEDULER_RUNTIME => "sched_stat_runtime", - Semantic::SCHEDULER_WAIT_TIME => "sched_stat_wait", - Semantic::TOTAL_MEMORY_ALLOCATION_EVENTS => "mem_alloc_events_total", - Semantic::REQUESTED_MEMORY_BYTES => "enter_mem_alloc", - Semantic::CPU_IDLE_STATE => "cpu_idle_state", + Semantic::TotalEvents => "events_total", + Semantic::SocketTotalEvents => "socket_events_total", + Semantic::SocketDrops => "sk_drops", + Semantic::SocketErrorsCount => "sk_err", + Semantic::Latency => "latency_us", + Semantic::PerCpuTotalEvents => "bytes_alloc_events_total", + Semantic::PerCpuBytesAllocated => "cpu_bytes_alloc", + Semantic::SchedulerRuntime => "sched_stat_runtime", + Semantic::SchedulerWaitTime => "sched_stat_wait", + Semantic::TotalMemoryAllocationEvents => "mem_alloc_events_total", + Semantic::RequestedMemoryBytes => "enter_mem_alloc", + Semantic::CpuIdleState => "cpu_idle_state", } } pub fn description(&self) -> &'static str { match self { - Semantic::TOTAL_EVENTS => { + Semantic::TotalEvents => { "Total number of eBPF events processed across all perf buffers" } - Semantic::SOCKET_TOTAL_EVENTS => "Total number of socket state events processed", - Semantic::SOCKET_DROPS => "Socket drop count per event", - Semantic::SOCKET_ERRORS_COUNT => "Socket error count per event", - Semantic::LATENCY => "Distribution of latency values from timestamp events", - Semantic::PERCPU_TOTAL_EVENTS => "Total bytes_alloc events occuring in the CPU", - Semantic::PERCPU_BYTES_ALLOCATED => "Cpu bytes allocation per event", - Semantic::SCHEDULER_RUNTIME => { + Semantic::SocketTotalEvents => "Total number of socket state events processed", + Semantic::SocketDrops => "Socket drop count per event", + Semantic::SocketErrorsCount => "Socket error count per event", + Semantic::Latency => "Distribution of latency values from timestamp events", + Semantic::PerCpuTotalEvents => "Total bytes_alloc events occurring in the CPU", + Semantic::PerCpuBytesAllocated => "CPU bytes allocation per event", + Semantic::SchedulerRuntime => { "Scheduler runtime in nanoseconds from sched_stat_runtime" } - Semantic::SCHEDULER_WAIT_TIME => { + Semantic::SchedulerWaitTime => { "Scheduler wait time in nanoseconds from sched_stat_wait" } - Semantic::TOTAL_MEMORY_ALLOCATION_EVENTS => { + Semantic::TotalMemoryAllocationEvents => { "Total number of memory allocation (mmap) events processed" } - Semantic::REQUESTED_MEMORY_BYTES => "Bytes requested via mmap syscalls", - Semantic::CPU_IDLE_STATE => { + Semantic::RequestedMemoryBytes => "Bytes requested via mmap syscalls", + Semantic::CpuIdleState => { "Current CPU idle C-state per cpu_id, updated only on state change" } } From 353d9916e609bc20b604a90bcfb7157cf96d7a15 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Thu, 16 Jul 2026 20:58:49 +0200 Subject: [PATCH 26/40] [#186]: Histogram distributions for sched_stat_wait and sched_stat_runtime metrics --- core/common/src/otel_metrics.rs | 42 ++++++++++++++++++++++++++------- core/common/src/semantic.rs | 10 ++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/core/common/src/otel_metrics.rs b/core/common/src/otel_metrics.rs index a1b2c866..e3c1fcc0 100644 --- a/core/common/src/otel_metrics.rs +++ b/core/common/src/otel_metrics.rs @@ -51,9 +51,15 @@ pub struct Metrics { /// Observed scheduler wait time in nanoseconds (sched_stat_wait). pub sched_stat_wait: Gauge, + /// Distribution of scheduler wait times in nanoseconds (sched_stat_wait). + pub sched_stat_wait_distribution: Histogram, + /// Observed scheduler runtime in nanoseconds (sched_stat_runtime). pub sched_stat_runtime: Gauge, + /// Distribution of scheduler runtimes in nanoseconds (sched_stat_runtime). + pub sched_stat_runtime_distribution: Histogram, + /// Current CPU idle C-state per cpu_id, updated only on state change. pub cpu_idle_state: Gauge, } @@ -133,6 +139,13 @@ impl Metrics { .with_unit("ns") .build(); + // distribution of scheduler wait times + let sched_stat_wait_distribution = meter + .u64_histogram(Semantic::SchedulerWaitTimeDistribution.title()) + .with_description(Semantic::SchedulerWaitTimeDistribution.description()) + .with_unit("ns") + .build(); + // scheduler runtime in nanoseconds let sched_stat_runtime = meter .i64_gauge(Semantic::SchedulerRuntime.title()) @@ -140,6 +153,13 @@ impl Metrics { .with_unit("ns") .build(); + // distribution of scheduler runtimes + let sched_stat_runtime_distribution = meter + .u64_histogram(Semantic::SchedulerRuntimeDistribution.title()) + .with_description(Semantic::SchedulerRuntimeDistribution.description()) + .with_unit("ns") + .build(); + // current CPU idle C-state per cpu_id let cpu_idle_state = meter .i64_gauge(Semantic::CpuIdleState.title()) @@ -156,7 +176,9 @@ impl Metrics { mem_alloc_events_total, enter_mem_alloc, sched_stat_wait, + sched_stat_wait_distribution, sched_stat_runtime, + sched_stat_runtime_distribution, cpu_idle_state, } } @@ -169,14 +191,14 @@ impl Metrics { /// Every observation carries: /// /// - `tgid` – task group ID. - /// - `comm` – command name (null-terminated bytes converted to a UTF-8 + /// - `command` – command name (null-terminated bytes converted to a UTF-8 /// string and trimmed). pub fn record_packet_loss_metrics(&self, m: &PacketLossMetrics) { let comm = String::from_utf8_lossy(&m.comm); let comm_trimmed = comm.trim_end_matches('\0').to_string(); let attrs = &[ KeyValue::new("tgid", m.tgid as i64), - KeyValue::new("comm", comm_trimmed), + KeyValue::new("command", comm_trimmed), ]; self.events_total.add(1, attrs); @@ -190,14 +212,14 @@ impl Metrics { /// Increments `events_total`, and records `delta_us` in the latency /// histogram. /// - /// Every observation carries `tgid` and `comm` (see + /// Every observation carries `tgid` and `command` (see /// [`record_packet_loss_metrics`]). pub fn record_timestamp_metrics(&self, m: &TimeStampMetrics) { let comm = String::from_utf8_lossy(&m.comm); let comm_trimmed = comm.trim_end_matches('\0').to_string(); let attrs = &[ KeyValue::new("tgid", m.tgid as i64), - KeyValue::new("comm", comm_trimmed), + KeyValue::new("command", comm_trimmed), ]; self.events_total.add(1, attrs); @@ -238,8 +260,9 @@ impl Metrics { /// Record a single [`SchedStatWait`] event. /// - /// Records `delay` in the `sched_stat_wait` gauge. No shared or dedicated - /// counter is incremented, as requested. + /// Increments `events_total`, records `delay` in the `sched_stat_wait` + /// gauge, and observes `delay` in the `sched_stat_wait_distribution` + /// histogram. pub fn record_sched_stat_wait(&self, m: &SchedStatWait) { let comm = String::from_utf8_lossy(&m.command); let command = comm.trim_end_matches('\0').to_string(); @@ -250,12 +273,14 @@ impl Metrics { self.events_total.add(1, attrs); self.sched_stat_wait.record(m.delay as i64, attrs); + self.sched_stat_wait_distribution.record(m.delay, attrs); } /// Record a single [`SchedStatRuntime`] event. /// - /// Records `runtime` in the `sched_stat_runtime` gauge. No shared or - /// dedicated counter is incremented, as requested. + /// Increments `events_total`, records `runtime` in the `sched_stat_runtime` + /// gauge, and observes `runtime` in the `sched_stat_runtime_distribution` + /// histogram. pub fn record_sched_stat_runtime(&self, m: &SchedStatRuntime) { let comm = String::from_utf8_lossy(&m.command); let command = comm.trim_end_matches('\0').to_string(); @@ -266,6 +291,7 @@ impl Metrics { self.events_total.add(1, attrs); self.sched_stat_runtime.record(m.runtime as i64, attrs); + self.sched_stat_runtime_distribution.record(m.runtime, attrs); } /// Record a single [`CpuIdle`] event. diff --git a/core/common/src/semantic.rs b/core/common/src/semantic.rs index b16bdbe9..a0381d81 100644 --- a/core/common/src/semantic.rs +++ b/core/common/src/semantic.rs @@ -9,7 +9,9 @@ pub enum Semantic { PerCpuTotalEvents, PerCpuBytesAllocated, SchedulerRuntime, + SchedulerRuntimeDistribution, SchedulerWaitTime, + SchedulerWaitTimeDistribution, TotalMemoryAllocationEvents, RequestedMemoryBytes, CpuIdleState, @@ -26,7 +28,9 @@ impl Semantic { Semantic::PerCpuTotalEvents => "bytes_alloc_events_total", Semantic::PerCpuBytesAllocated => "cpu_bytes_alloc", Semantic::SchedulerRuntime => "sched_stat_runtime", + Semantic::SchedulerRuntimeDistribution => "sched_stat_runtime_distribution", Semantic::SchedulerWaitTime => "sched_stat_wait", + Semantic::SchedulerWaitTimeDistribution => "sched_stat_wait_distribution", Semantic::TotalMemoryAllocationEvents => "mem_alloc_events_total", Semantic::RequestedMemoryBytes => "enter_mem_alloc", Semantic::CpuIdleState => "cpu_idle_state", @@ -46,9 +50,15 @@ impl Semantic { Semantic::SchedulerRuntime => { "Scheduler runtime in nanoseconds from sched_stat_runtime" } + Semantic::SchedulerRuntimeDistribution => { + "Distribution of scheduler runtimes in nanoseconds from sched_stat_runtime" + } Semantic::SchedulerWaitTime => { "Scheduler wait time in nanoseconds from sched_stat_wait" } + Semantic::SchedulerWaitTimeDistribution => { + "Distribution of scheduler wait times in nanoseconds from sched_stat_wait" + } Semantic::TotalMemoryAllocationEvents => { "Total number of memory allocation (mmap) events processed" } From f56d53c722c159d8c4081ad84ae217e3d6f2f71f Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Mon, 20 Jul 2026 16:25:37 +0200 Subject: [PATCH 27/40] [#119]: moved read packet functions to dedicated consumer.rs module --- core/common/src/buffer_type.rs | 796 ++------------------- core/common/src/consumer.rs | 779 ++++++++++++++++++++ core/src/components/metrics/src/helpers.rs | 16 +- 3 files changed, 831 insertions(+), 760 deletions(-) create mode 100644 core/common/src/consumer.rs diff --git a/core/common/src/buffer_type.rs b/core/common/src/buffer_type.rs index 8ec000c0..17351684 100644 --- a/core/common/src/buffer_type.rs +++ b/core/common/src/buffer_type.rs @@ -1,20 +1,25 @@ -#[cfg(feature = "monitoring-structs")] -use crate::otel_metrics::Metrics; +//! eBPF data structures and buffer size definitions. +//! +//! This module contains: +//! - C-compatible structs emitted by the eBPF programs (`PacketLossMetrics`, `SchedStatWait`, etc.). +//! - [`BufferSize`] for pre-allocating per-CPU byte buffers. +//! - [`IpProtocols`] for L4 protocol reconstruction. +//! +//! The consumer logic has been moved to [`crate::consumer`]. + +use aya::maps::perf::PerfEventArrayBuffer; #[cfg(feature = "buffer-reader")] use aya::maps::{MapData, PerfEventArray}; -use aya::{maps::perf::PerfEventArrayBuffer, util::online_cpus}; +use aya::util::online_cpus; use bytemuck_derive::Zeroable; use bytes::BytesMut; use std::net::Ipv4Addr; -#[cfg(feature = "buffer-reader")] -#[cfg(feature = "monitoring-structs")] -use std::sync::Arc; use tracing::{error, info, warn}; -// -// IpProtocols enum to reconstruct the packet protocol based on the -// IPV4 Header Protocol code -// +/// +/// IpProtocols enum to reconstruct the packet protocol based on the +/// IPV4 Header Protocol code +/// #[derive(Debug)] #[repr(u8)] @@ -24,11 +29,11 @@ pub enum IpProtocols { UDP = 17, } -// -// TryFrom Trait implementation for IpProtocols enum -// This is used to reconstruct the packet protocol based on the -// IPV4 Header Protocol code -// +/// +/// TryFrom Trait implementation for IpProtocols enum +/// This is used to reconstruct the packet protocol based on the +/// IPV4 Header Protocol code +/// impl TryFrom for IpProtocols { type Error = (); @@ -42,10 +47,10 @@ impl TryFrom for IpProtocols { } } -// -// Structure PacketLog -//This structure is used to store the packet information -// +/// +/// Structure PacketLog +/// This structure is used to store the packet information +/// #[cfg(feature = "network-structs")] #[repr(C)] #[derive(Clone, Copy, Zeroable)] @@ -91,7 +96,7 @@ pub struct TcpPacketRegistry { unsafe impl aya::Pod for TcpPacketRegistry {} #[cfg(feature = "monitoring-structs")] -pub const TASK_COMM_LEN: usize = 16; // linux/sched.h +pub const TASK_COMM_LEN: usize = 16; #[cfg(feature = "monitoring-structs")] #[repr(C, packed)] #[derive(Clone, Copy, Zeroable)] @@ -132,12 +137,11 @@ unsafe impl aya::Pod for TimeStampMetrics {} #[repr(C, packed)] #[derive(Clone, Copy, Zeroable)] pub struct CpuFrequency { - //pub cpu_id: u32, - //pub cpu_freq: u32, pub bytes_alloc: u32, pub pid: u32, pub command: [u8; 16], } +#[cfg(feature = "monitoring-structs")] unsafe impl aya::Pod for CpuFrequency {} #[cfg(feature = "monitoring-structs")] @@ -184,727 +188,21 @@ pub struct CpuIdle { #[cfg(feature = "monitoring-structs")] unsafe impl aya::Pod for CpuIdle {} -// docs: -// This function perform a byte swap from little-endian to big-endian -// It's used to reconstruct the correct IPv4 address from the u32 representation -// -// Takes a u32 address in big-endian format and returns a Ipv4Addr with reversed octets -// +/// Perform a byte swap from little-endian to big-endian. +/// +/// Used to reconstruct the correct IPv4 address from the u32 representation. +/// Takes a `u32` address in big-endian format and returns an [`Ipv4Addr`] with reversed octets. #[inline(always)] pub fn reverse_be_addr(addr: u32) -> Ipv4Addr { let octects = addr.to_be_bytes(); let [a, b, c, d] = [octects[3], octects[2], octects[1], octects[0]]; - let reversed_ip = Ipv4Addr::new(a, b, c, d); - reversed_ip -} - -// enum BuffersType -#[cfg(feature = "buffer-reader")] -pub enum BufferType { - #[cfg(feature = "network-structs")] - PacketLog, - #[cfg(feature = "network-structs")] - TcpPacketRegistry, - #[cfg(feature = "network-structs")] - VethLog, - #[cfg(feature = "monitoring-structs")] - PacketLossMetrics, - #[cfg(feature = "monitoring-structs")] - TimeStampMetrics, - #[cfg(feature = "monitoring-structs")] - CpuFrequency, - #[cfg(feature = "monitoring-structs")] - MemAlloc, - #[cfg(feature = "monitoring-structs")] - SchedStatWait, - #[cfg(feature = "monitoring-structs")] - SchedStatRuntime, - #[cfg(feature = "monitoring-structs")] - CpuIdle, -} - -#[cfg(feature = "buffer-reader")] -impl BufferType { - #[cfg(feature = "network-structs")] - pub async fn read_packet_log(buffers: &mut [BytesMut], tot_events: i32, offset: i32) { - for i in offset..tot_events { - let vec_bytes = &buffers[i as usize]; - if vec_bytes.len() < std::mem::size_of::() { - error!( - "Corrupted Packet log data. Raw data: {}. Readed {} bytes expected {} bytes", - vec_bytes - .iter() - .map(|b| format!("{:02x}", b)) - .collect::>() - .join(" "), - vec_bytes.len(), - std::mem::size_of::() - ); - continue; - } - if vec_bytes.len() >= std::mem::size_of::() { - let pl: PacketLog = - unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; // reading raw bytes - - // extracting struct info from bytes - let src_ip = reverse_be_addr(pl.src_ip); - let dst_ip = reverse_be_addr(pl.dst_ip); - let src_port = u16::from_be(pl.src_port); - let dst_port = u16::from_be(pl.dst_port); - let event_id = pl.pid; - let protocol = pl.proto; - - // protocol extraction - match IpProtocols::try_from(protocol) { - Ok(proto) => { - info!( - "Event Id: {} Protocol: {:?} SRC: {}:{} -> DST: {}:{}", - event_id, proto, src_ip, src_port, dst_ip, dst_port - ); - } - Err(e) => { - error!("Unknown protocol. Data maybe corrupted. Reason:{:?}", e); - } - } - } - } - } - #[cfg(feature = "network-structs")] - pub async fn read_tcp_registry_log(buffers: &mut [BytesMut], tot_events: i32, offset: i32) { - for i in offset..tot_events { - let vec_bytes = &buffers[i as usize]; - if vec_bytes.len() < std::mem::size_of::() { - error!( - "Corrupted data Tcp Registry data. Raw data: {}. Readed {} bytes expected {} bytes", - vec_bytes - .iter() - .map(|b| format!("{:02x}", b)) - .collect::>() - .join(" "), - vec_bytes.len(), - std::mem::size_of::() - ); - continue; - } - if vec_bytes.len() >= std::mem::size_of::() { - let pl: TcpPacketRegistry = - unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; // reading raw bytes - - // extracting struct info from bytes - let src = reverse_be_addr(pl.src_ip); - let dst = reverse_be_addr(pl.dst_ip); - let src_port = u16::from_be(pl.src_port); - let dst_port = u16::from_be(pl.dst_port); - let event_id = pl.pid; - let command = pl.command.to_vec(); - let end = command - .iter() - .position(|&x| x == 0) - .unwrap_or(command.len()); - let command_str = String::from_utf8_lossy(&command[..end]).to_string(); - let cgroup_id = pl.cgroup_id; - let protocol = pl.proto; - - // protocol extraction - match IpProtocols::try_from(protocol) { - Ok(proto) => { - info!( - "Event Id: {} Protocol: {:?} SRC: {}:{} -> DST: {}:{} Command: {} Cgroup_id: {}", - event_id, - proto, - src, - src_port, - dst, - dst_port, - command_str, - cgroup_id //proc_content - ); - } - Err(e) => { - error!("Unknown protocol. Data maybe corrupted. Reason:{:?}", e); - } - } - } - } - } - #[cfg(feature = "network-structs")] - pub async fn read_and_handle_veth_log(buffers: &mut [BytesMut], tot_events: i32, offset: i32) { - for i in offset..tot_events { - let vec_bytes = &buffers[i as usize]; - if vec_bytes.len() < std::mem::size_of::() { - error!( - "Corrupted data VethLog data. Raw data: {}. Readed {} bytes expected {} bytes", - vec_bytes - .iter() - .map(|b| format!("{:02x}", b)) - .collect::>() - .join(" "), - vec_bytes.len(), - std::mem::size_of::() - ); - continue; - } - if vec_bytes.len() >= std::mem::size_of::() { - let vthl: VethLog = - unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; // reading raw bytes - - // extracting struct info from bytes - let name_bytes = vthl.name; - let dev_addr_bytes = vthl.dev_addr; - let name = std::str::from_utf8(&name_bytes); - let state = vthl.state; - - let dev_addr = dev_addr_bytes; - let netns = vthl.netns; - let mut event_type = String::new(); - - // event_type extraction - match vthl.event_type { - 1 => { - event_type = "creation".to_string(); - match name { - Ok(veth_name) => { - info!( - "[{}] Veth Event: Type: {} Name: {} Dev_addr: {:x?} State: {}", - netns, - event_type, - veth_name.trim_end_matches("\0"), - dev_addr, - state - ); - } - Err(e) => { - error!( - "Failed to extract veth name during event_type = creation (1).Reason:{}", - e - ); - } - } - } - 2 => { - event_type = "deletion".to_string(); - match name { - Ok(veth_name) => { - info!( - "[{}] Veth Event: Type: {} Name: {} Dev_addr: {:x?} State: {}", - netns, - event_type, - veth_name.trim_end_matches("\0"), - dev_addr, - state - ); - } - Err(e) => { - error!( - "Failed to extract veth name during event_type = deletion (2).Reason:{}", - e - ); - } - } - } - _ => { - warn!("Unknown event type") - } - } - } - } - } - #[cfg(feature = "monitoring-structs")] - /// Continuously read [`NetworkMetrics`] events and record OpenTelemetry - /// observations. - /// - /// This helper mirrors the core behaviour of - /// [`cortexbrain_common::buffer_type::read_perf_buffer`] but adds the OTel - /// instrumentation layer. - /// - /// # Loop - /// - /// 1. For every CPU buffer call `read_events`. - /// 2. Parse each raw [`BytesMut`] into [`NetworkMetrics`] using an - /// unaligned read (the struct is `#[repr(C, packed)]` and `Pod`). - /// 3. Call [`Metrics::record_network_metrics`]. - /// 4. Retain the legacy `tracing::info!` log for human-readable local output. - /// 5. Sleep 100 ms between polls. - /// - /// # Safety - /// - /// `std::ptr::read_unaligned` is safe here because the eBPF program writes - /// exactly the `NetworkMetrics` layout into the ring buffer and the struct - /// implements [`aya::Pod`]. - /// Continuously read [`TimeStampMetrics`] events and record OpenTelemetry - /// observations. - /// - /// Counterpart to [`read_network_buffer`] for the `time_stamp_events` map. - - pub async fn read_packet_loss_metrics( - buffers: &mut [BytesMut], - tot_events: i32, - offset: i32, - exporter: &str, - metrics: Arc, - ) { - for i in offset..tot_events { - let vec_bytes = &buffers[i as usize]; - if vec_bytes.len() < std::mem::size_of::() { - error!( - "Corrupted Network Metrics data. Raw data: {}. Readed {} bytes expected {} bytes", - vec_bytes - .iter() - .map(|b| format!("{:02x}", b)) - .collect::>() - .join(" "), - vec_bytes.len(), - std::mem::size_of::() - ); - continue; - } - if vec_bytes.len() >= std::mem::size_of::() { - let packet_loss: PacketLossMetrics = - unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; - - match exporter { - "otlp" => metrics.record_packet_loss_metrics(&packet_loss), - _ => continue, // skip - } - let tgid = packet_loss.tgid; - let comm = String::from_utf8_lossy(&packet_loss.comm); - let ts_us = packet_loss.ts_us; - let sk_drop_count = packet_loss.sk_drops; - let sk_err = packet_loss.sk_err; - let sk_err_soft = packet_loss.sk_err_soft; - let sk_backlog_len = packet_loss.sk_backlog_len; - let sk_write_memory_queued = packet_loss.sk_write_memory_queued; - let sk_ack_backlog = packet_loss.sk_ack_backlog; - let sk_receive_buffer_size = packet_loss.sk_receive_buffer_size; - - info!( - "tgid: {}, comm: {}, ts_us: {}, sk_drops: {}, sk_err: {}, sk_err_soft: {}, sk_backlog_len: {}, sk_write_memory_queued: {}, sk_ack_backlog: {}, sk_receive_buffer_size: {}", - tgid, - comm, - ts_us, - sk_drop_count, - sk_err, - sk_err_soft, - sk_backlog_len, - sk_write_memory_queued, - sk_ack_backlog, - sk_receive_buffer_size - ); - } - } - } - #[cfg(feature = "monitoring-structs")] - pub async fn read_timestamp_metrics( - buffers: &mut [BytesMut], - tot_events: i32, - offset: i32, - exporter: &str, - metrics: Arc, - ) { - for i in offset..tot_events { - let vec_bytes = &buffers[i as usize]; - if vec_bytes.len() < std::mem::size_of::() { - error!( - "Corrupted Network Metrics data. Raw data: {}. Readed {} bytes expected {} bytes", - vec_bytes - .iter() - .map(|b| format!("{:02x}", b)) - .collect::>() - .join(" "), - vec_bytes.len(), - std::mem::size_of::() - ); - continue; - } - if vec_bytes.len() >= std::mem::size_of::() { - let time_stamp_event: TimeStampMetrics = - unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; - - match exporter { - "otlp" => metrics.record_timestamp_metrics(&time_stamp_event), - _ => continue, - } - - let delta_us = time_stamp_event.delta_us; - let ts_us = time_stamp_event.ts_us; - let tgid = time_stamp_event.tgid; - let comm = String::from_utf8_lossy(&time_stamp_event.comm); - let lport = time_stamp_event.lport; - let dport_be = time_stamp_event.dport_be; - let af = time_stamp_event.af; - info!( - "TimeStampEvent - delta_us: {}, ts_us: {}, tgid: {}, comm: {}, lport: {}, dport_be: {}, af: {}", - delta_us, ts_us, tgid, comm, lport, dport_be, af - ); - } - } - } - - #[cfg(feature = "monitoring-structs")] - pub async fn read_cpu_frequency( - buffers: &mut [BytesMut], - tot_events: i32, - offset: i32, - exporter: &str, - metrics: Arc, - ) { - for i in offset..tot_events { - let vec_bytes = &buffers[i as usize]; - if vec_bytes.len() < std::mem::size_of::() { - error!( - "Corrupted Cpu Frequency Metrics data. Raw data: {}. Readed {} bytes expected {} bytes", - vec_bytes - .iter() - .map(|b| format!("{:02x}", b)) - .collect::>() - .join(" "), - vec_bytes.len(), - std::mem::size_of::() - ); - continue; - } - if vec_bytes.len() >= std::mem::size_of::() { - let cpu_freq_metrics: CpuFrequency = - unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; - - match exporter { - "otlp" => metrics.record_cpu_bytes_alloc(&cpu_freq_metrics), - _ => continue, - } - - //let cpu_id = cpu_freq_metrics.cpu_id; - //let cpu_freq = cpu_freq_metrics.cpu_freq; - let bytes_alloc = cpu_freq_metrics.bytes_alloc; - //info!( - // "Cpu id: {} Cpu frequency: {} Bytes alloc: {}", - // cpu_id, cpu_freq, bytes_alloc - //); - let pid = cpu_freq_metrics.pid; - let command = cpu_freq_metrics.command; - info!( - "Cpu Bytes alloc: {} pid : {} command: {:?}", - bytes_alloc, pid, command - ); - } - } - } - - #[cfg(feature = "monitoring-structs")] - pub async fn read_mem_alloc( - buffers: &mut [BytesMut], - tot_events: i32, - offset: i32, - exporter: &str, - metrics: Arc, - ) { - for i in offset..tot_events { - let vec_bytes = &buffers[i as usize]; - if vec_bytes.len() < std::mem::size_of::() { - error!( - "Corrupted MemAlloc data. Raw data: {}. Readed {} bytes expected {} bytes", - vec_bytes - .iter() - .map(|b| format!("{:02x}", b)) - .collect::>() - .join(" "), - vec_bytes.len(), - std::mem::size_of::() - ); - continue; - } - if vec_bytes.len() >= std::mem::size_of::() { - let mem_alloc: MemAlloc = - unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; - - match exporter { - "otlp" => metrics.record_enter_mem_alloc(&mem_alloc), - _ => continue, - } - - let tgid = mem_alloc.tgid; - let command = String::from_utf8_lossy(&mem_alloc.command); - let addr = mem_alloc.addr; - let length = mem_alloc.length; - - info!( - "MemAlloc - tgid: {}, command: {}, addr: {}, length: {}", - tgid, command, addr, length - ); - } - } - } - - #[cfg(feature = "monitoring-structs")] - pub async fn read_sched_stat_wait( - buffers: &mut [BytesMut], - tot_events: i32, - offset: i32, - exporter: &str, - metrics: Arc, - ) { - for i in offset..tot_events { - let vec_bytes = &buffers[i as usize]; - if vec_bytes.len() < std::mem::size_of::() { - error!( - "Corrupted SchedStatWait data. Raw data: {}. Readed {} bytes expected {} bytes", - vec_bytes - .iter() - .map(|b| format!("{:02x}", b)) - .collect::>() - .join(" "), - vec_bytes.len(), - std::mem::size_of::() - ); - continue; - } - if vec_bytes.len() >= std::mem::size_of::() { - let sched_stat_wait: SchedStatWait = - unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; - - match exporter { - "otlp" => metrics.record_sched_stat_wait(&sched_stat_wait), - _ => continue, - } - - let tgid = sched_stat_wait.tgid; - let command = String::from_utf8_lossy(&sched_stat_wait.command); - let delay = sched_stat_wait.delay; - - info!( - "SchedStatWait - tgid: {}, command: {}, delay: {}", - tgid, command, delay - ); - } - } - } - - #[cfg(feature = "monitoring-structs")] - pub async fn read_sched_stat_runtime( - buffers: &mut [BytesMut], - tot_events: i32, - offset: i32, - exporter: &str, - metrics: Arc, - ) { - for i in offset..tot_events { - let vec_bytes = &buffers[i as usize]; - if vec_bytes.len() < std::mem::size_of::() { - error!( - "Corrupted SchedStatRuntime data. Raw data: {}. Readed {} bytes expected {} bytes", - vec_bytes - .iter() - .map(|b| format!("{:02x}", b)) - .collect::>() - .join(" "), - vec_bytes.len(), - std::mem::size_of::() - ); - continue; - } - if vec_bytes.len() >= std::mem::size_of::() { - let sched_stat_runtime: SchedStatRuntime = - unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; - - match exporter { - "otlp" => metrics.record_sched_stat_runtime(&sched_stat_runtime), - _ => continue, - } - - let tgid = sched_stat_runtime.tgid; - let command = String::from_utf8_lossy(&sched_stat_runtime.command); - let runtime = sched_stat_runtime.runtime; - - info!( - "SchedStatRuntime - tgid: {}, command: {}, runtime: {}", - tgid, command, runtime - ); - } - } - } - - #[cfg(feature = "monitoring-structs")] - pub async fn read_cpu_idle( - buffers: &mut [BytesMut], - tot_events: i32, - offset: i32, - exporter: &str, - metrics: Arc, - ) { - for i in offset..tot_events { - let vec_bytes = &buffers[i as usize]; - if vec_bytes.len() < std::mem::size_of::() { - error!( - "Corrupted CpuIdle data. Raw data: {}. Readed {} bytes expected {} bytes", - vec_bytes - .iter() - .map(|b| format!("{:02x}", b)) - .collect::>() - .join(" "), - vec_bytes.len(), - std::mem::size_of::() - ); - continue; - } - if vec_bytes.len() >= std::mem::size_of::() { - let cpu_idle: CpuIdle = - unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; - - match exporter { - "otlp" => metrics.record_cpu_idle(&cpu_idle), - _ => continue, - } - - let cpu_id = cpu_idle.cpu_id; - let state = cpu_idle.state; - - info!( - "CpuIdle state changed - cpu_id: {}, state: {}", - cpu_id, state - ); - } - } - } -} - -// docs: read buffer function: -// template function that take a mut perf_event_array_buffer of type T and a mutable buffer of Vec -#[cfg(feature = "buffer-reader")] -pub async fn read_perf_buffer>( - mut array_buffers: Vec>, - mut buffers: Vec, - buffer_type: BufferType, - #[cfg(feature = "monitoring-structs")] metrics: Option>, -) { - // loop over the buffers - loop { - for buf in array_buffers.iter_mut() { - match buf.read_events(&mut buffers) { - Ok(events) => { - // triggered if some events are lost - if events.lost > 0 { - tracing::debug!("Lost events: {} ", events.lost); - } - // triggered if some events are readed - if events.read > 0 { - tracing::debug!("Readed events: {}", events.read); - let offset = 0; - let tot_events = events.read as i32; - - //read the events in the buffer - match buffer_type { - #[cfg(feature = "network-structs")] - BufferType::PacketLog => { - BufferType::read_packet_log(&mut buffers, tot_events, offset).await - } - #[cfg(feature = "network-structs")] - BufferType::TcpPacketRegistry => { - BufferType::read_tcp_registry_log(&mut buffers, tot_events, offset) - .await - } - #[cfg(feature = "network-structs")] - BufferType::VethLog => { - BufferType::read_and_handle_veth_log( - &mut buffers, - tot_events, - offset, - ) - .await - } - #[cfg(feature = "monitoring-structs")] - BufferType::PacketLossMetrics => { - BufferType::read_packet_loss_metrics( - &mut buffers, - tot_events, - offset, - "otlp", - metrics - .clone() - .expect("Metrics required for PacketLossMetrics"), - ) - .await - } - #[cfg(feature = "monitoring-structs")] - BufferType::TimeStampMetrics => { - BufferType::read_timestamp_metrics( - &mut buffers, - tot_events, - offset, - "otlp", - metrics - .clone() - .expect("Metric required for TimeStampMetrics"), - ) - .await - } - #[cfg(feature = "monitoring-structs")] - BufferType::CpuFrequency => { - BufferType::read_cpu_frequency( - &mut buffers, - tot_events, - offset, - "otlp", - metrics.clone().expect("Metric required for CpuFrequency"), - ) - .await - } - #[cfg(feature = "monitoring-structs")] - BufferType::MemAlloc => { - BufferType::read_mem_alloc( - &mut buffers, - tot_events, - offset, - "otlp", - metrics.clone().expect("Metric required for MemAlloc"), - ) - .await - } - #[cfg(feature = "monitoring-structs")] - BufferType::SchedStatWait => { - BufferType::read_sched_stat_wait( - &mut buffers, - tot_events, - offset, - "otlp", - metrics.clone().expect("Metric required for SchedStatWait"), - ) - .await - } - #[cfg(feature = "monitoring-structs")] - BufferType::SchedStatRuntime => { - BufferType::read_sched_stat_runtime( - &mut buffers, - tot_events, - offset, - "otlp", - metrics - .clone() - .expect("Metric required for SchedStatRuntime"), - ) - .await - } - #[cfg(feature = "monitoring-structs")] - BufferType::CpuIdle => { - BufferType::read_cpu_idle( - &mut buffers, - tot_events, - offset, - "otlp", - metrics.clone().expect("Metric required for CpuIdle"), - ) - .await - } - } - } - } - Err(e) => { - error!("Cannot read events from buffer. Reason: {} ", e); - } - } - } - tokio::time::sleep(std::time::Duration::from_millis(100)).await; // small sleep - } + Ipv4Addr::new(a, b, c, d) } +/// Buffer size presets for per-CPU perf-buffer allocation. +/// +/// Each variant carries a multiplier that determines how many struct-sized +/// slots are pre-allocated per CPU in [`BufferSize::set_buffer`]. #[cfg(feature = "buffer-reader")] pub enum BufferSize { #[cfg(feature = "network-structs")] @@ -928,8 +226,10 @@ pub enum BufferSize { #[cfg(feature = "monitoring-structs")] CpuIdle, } + #[cfg(feature = "buffer-reader")] impl BufferSize { + /// Return the size in bytes of the struct associated with this variant. pub fn get_size(&self) -> usize { match self { #[cfg(feature = "network-structs")] @@ -954,21 +254,14 @@ impl BufferSize { BufferSize::CpuIdle => std::mem::size_of::(), } } - pub fn set_buffer(&self) -> Vec { - // iter returns and iterator of cpu ids, - // we need only the total number of cpus to set the buffer size so we use .len() to get - // the count of total cpus and then we allocate a buffer for each cpu with a capacity - // based on the structure size * a factor to have a bigger buffer to avoid overflows and lost events - // Old buffers where 1024 bytes long. Now we set different buffer size based on - // the frequence of the events. - // ClassifierNetEvents are triggered by the TC classifier program, events has high frequency - // VethEvents are triggered by the creation and deletion of veth interfaces, events has small frequency compared to classifier events - // TcpEvents are triggered by TCP events and connections. Events has similar frequency to ClassifierNetEvents. + /// Allocate one `BytesMut` per CPU with capacity tuned to the event type. + pub fn set_buffer(&self) -> Vec { + use aya::util::online_cpus; - let tot_cpu = online_cpus().iter().len(); // total number of cpus + let tot_cpu = online_cpus().iter().len(); - // TODO: finish to do all the calculations for the buffer sizes + // TODO: finish buffer size calculations match self { #[cfg(feature = "network-structs")] BufferSize::ClassifierNetEvents => { @@ -977,7 +270,7 @@ impl BufferSize { } #[cfg(feature = "network-structs")] BufferSize::VethEvents => { - let capacity = self.get_size() * 100; // Allocates 4Kb of memory for the buffers + let capacity = self.get_size() * 100; return vec![BytesMut::with_capacity(capacity); tot_cpu]; } #[cfg(feature = "network-structs")] @@ -1024,11 +317,10 @@ impl BufferSize { } } +/// Open a [`PerfEventArrayBuffer`] for every online CPU and append them to `vec_of_buffers`. #[cfg(feature = "buffer-reader")] pub fn fill_buffers( - //buf: PerfEventArrayBuffer, mut vec_of_buffers: Vec>, - //buffers: Vec, mut events_array: PerfEventArray, ) -> Vec> { for cpu_id in online_cpus() diff --git a/core/common/src/consumer.rs b/core/common/src/consumer.rs new file mode 100644 index 00000000..bd7d2676 --- /dev/null +++ b/core/common/src/consumer.rs @@ -0,0 +1,779 @@ +//! Perf-buffer consumers for eBPF events. +//! +//! This module provides the [`Consumer`] enum and its associated `read` methods +//! that parse raw bytes from [`aya::maps::perf::PerfEventArrayBuffer`] into +//! strongly-typed eBPF structs and forward them to the OpenTelemetry metrics +//! pipeline via [`crate::otel_metrics::Metrics`]. +//! +//! Each consumer method: +//! 1. Validates the raw byte buffer length against the expected struct size. +//! 2. Performs an unaligned read into the `#[repr(C, packed)]` struct. +//! 3. Builds [`crate::metadata::Metadata`] (with optional Docker/K8s enrichment). +//! 4. Records the observation through [`Metrics::record_*`]. + +#[cfg(feature = "monitoring-structs")] +use crate::buffer_type::{ + CpuFrequency, CpuIdle, MemAlloc, PacketLossMetrics, SchedStatRuntime, SchedStatWait, + TimeStampMetrics, +}; +#[cfg(feature = "network-structs")] +use crate::buffer_type::{PacketLog, TcpPacketRegistry, VethLog}; +#[cfg(feature = "monitoring-structs")] +use crate::metadata::Metadata; +#[cfg(feature = "monitoring-structs")] +use crate::otel_metrics::Metrics; +use bytes::BytesMut; +#[cfg(feature = "monitoring-structs")] +use std::sync::Arc; +use tracing::{error, info, warn}; + +/// Discriminator for perf-buffer event types consumed by the collector. +/// +/// Each variant maps to an eBPF program output struct and a dedicated +/// `read_*` method that knows how to parse it. +#[cfg(feature = "buffer-reader")] +pub enum Consumer { + #[cfg(feature = "network-structs")] + PacketLog, + #[cfg(feature = "network-structs")] + TcpPacketRegistry, + #[cfg(feature = "network-structs")] + VethLog, + #[cfg(feature = "monitoring-structs")] + PacketLossMetrics, + #[cfg(feature = "monitoring-structs")] + TimeStampMetrics, + #[cfg(feature = "monitoring-structs")] + CpuFrequency, + #[cfg(feature = "monitoring-structs")] + MemAlloc, + #[cfg(feature = "monitoring-structs")] + SchedStatWait, + #[cfg(feature = "monitoring-structs")] + SchedStatRuntime, + #[cfg(feature = "monitoring-structs")] + CpuIdle, +} + +#[cfg(feature = "buffer-reader")] +impl Consumer { + /// Read and log [`PacketLog`] events from the perf buffer. + /// + /// Parses IPv4 addresses, ports and L4 protocol from raw eBPF bytes and + /// emits human-readable `tracing::info!` lines. + #[cfg(feature = "network-structs")] + pub async fn read_packet_log(buffers: &mut [BytesMut], tot_events: i32, offset: i32) { + use crate::buffer_type::{IpProtocols, reverse_be_addr}; + + for i in offset..tot_events { + let vec_bytes = &buffers[i as usize]; + if vec_bytes.len() < std::mem::size_of::() { + error!( + "Corrupted Packet log data. Raw data: {}. Readed {} bytes expected {} bytes", + vec_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(" "), + vec_bytes.len(), + std::mem::size_of::() + ); + continue; + } + if vec_bytes.len() >= std::mem::size_of::() { + let pl: PacketLog = + unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; + + let src_ip = reverse_be_addr(pl.src_ip); + let dst_ip = reverse_be_addr(pl.dst_ip); + let src_port = u16::from_be(pl.src_port); + let dst_port = u16::from_be(pl.dst_port); + let event_id = pl.pid; + let protocol = pl.proto; + + match IpProtocols::try_from(protocol) { + Ok(proto) => { + info!( + "Event Id: {} Protocol: {:?} SRC: {}:{} -> DST: {}:{}", + event_id, proto, src_ip, src_port, dst_ip, dst_port + ); + } + Err(e) => { + error!("Unknown protocol. Data maybe corrupted. Reason:{:?}", e); + } + } + } + } + } + + /// Read and log [`TcpPacketRegistry`] events from the perf buffer. + /// + /// Similar to [`read_packet_log`] but additionally prints the command name + /// and cgroup ID extracted from the eBPF struct. + #[cfg(feature = "network-structs")] + pub async fn read_tcp_registry_log(buffers: &mut [BytesMut], tot_events: i32, offset: i32) { + use crate::buffer_type::{IpProtocols, reverse_be_addr}; + + for i in offset..tot_events { + let vec_bytes = &buffers[i as usize]; + if vec_bytes.len() < std::mem::size_of::() { + error!( + "Corrupted data Tcp Registry data. Raw data: {}. Readed {} bytes expected {} bytes", + vec_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(" "), + vec_bytes.len(), + std::mem::size_of::() + ); + continue; + } + if vec_bytes.len() >= std::mem::size_of::() { + let pl: TcpPacketRegistry = + unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; + + let src = reverse_be_addr(pl.src_ip); + let dst = reverse_be_addr(pl.dst_ip); + let src_port = u16::from_be(pl.src_port); + let dst_port = u16::from_be(pl.dst_port); + let event_id = pl.pid; + let command = pl.command.to_vec(); + let end = command + .iter() + .position(|&x| x == 0) + .unwrap_or(command.len()); + let command_str = String::from_utf8_lossy(&command[..end]).to_string(); + let cgroup_id = pl.cgroup_id; + let protocol = pl.proto; + + match IpProtocols::try_from(protocol) { + Ok(proto) => { + info!( + "Event Id: {} Protocol: {:?} SRC: {}:{} -> DST: {}:{} Command: {} Cgroup_id: {}", + event_id, proto, src, src_port, dst, dst_port, command_str, cgroup_id + ); + } + Err(e) => { + error!("Unknown protocol. Data maybe corrupted. Reason:{:?}", e); + } + } + } + } + } + + /// Read and log [`VethLog`] events from the perf buffer. + /// + /// Distinguishes between veth interface creation (event_type == 1) and + /// deletion (event_type == 2) and logs the interface name, MAC address and + /// netns. + #[cfg(feature = "network-structs")] + pub async fn read_and_handle_veth_log(buffers: &mut [BytesMut], tot_events: i32, offset: i32) { + for i in offset..tot_events { + let vec_bytes = &buffers[i as usize]; + if vec_bytes.len() < std::mem::size_of::() { + error!( + "Corrupted data VethLog data. Raw data: {}. Readed {} bytes expected {} bytes", + vec_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(" "), + vec_bytes.len(), + std::mem::size_of::() + ); + continue; + } + if vec_bytes.len() >= std::mem::size_of::() { + let vthl: VethLog = + unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; + + let name_bytes = vthl.name; + let dev_addr_bytes = vthl.dev_addr; + let name = std::str::from_utf8(&name_bytes); + let state = vthl.state; + let dev_addr = dev_addr_bytes; + let netns = vthl.netns; + let mut event_type = String::new(); + + match vthl.event_type { + 1 => { + event_type = "creation".to_string(); + match name { + Ok(veth_name) => { + info!( + "[{}] Veth Event: Type: {} Name: {} Dev_addr: {:x?} State: {}", + netns, + event_type, + veth_name.trim_end_matches("\0"), + dev_addr, + state + ); + } + Err(e) => { + error!( + "Failed to extract veth name during event_type = creation (1).Reason:{}", + e + ); + } + } + } + 2 => { + event_type = "deletion".to_string(); + match name { + Ok(veth_name) => { + info!( + "[{}] Veth Event: Type: {} Name: {} Dev_addr: {:x?} State: {}", + netns, + event_type, + veth_name.trim_end_matches("\0"), + dev_addr, + state + ); + } + Err(e) => { + error!( + "Failed to extract veth name during event_type = deletion (2).Reason:{}", + e + ); + } + } + } + _ => { + warn!("Unknown event type") + } + } + } + } + } + + /// Read [`PacketLossMetrics`] events and record OpenTelemetry observations. + /// + /// # Arguments + /// - `buffers` — raw byte buffers populated by `PerfEventArrayBuffer::read_events`. + /// - `tot_events` — number of events to process. + /// - `offset` — start index in `buffers`. + /// - `exporter` — `"otlp"` forwards to [`Metrics`], any other value is skipped. + /// - `metrics` — shared [`Metrics`] handle. + /// + /// # Safety + /// Uses `std::ptr::read_unaligned` on `#[repr(C, packed)]` structs that implement [`aya::Pod`]. + /// + /// # Metadata enrichment + /// If `exporter == "otlp"`, constructs [`Metadata`] and calls [`Metadata::enrich()`] to resolve + /// Docker container name from `/proc//cgroup` when available. + #[cfg(feature = "monitoring-structs")] + pub async fn read_packet_loss_metrics( + buffers: &mut [BytesMut], + tot_events: i32, + offset: i32, + exporter: &str, + metrics: Arc, + ) { + for i in offset..tot_events { + let vec_bytes = &buffers[i as usize]; + if vec_bytes.len() < std::mem::size_of::() { + error!( + "Corrupted Network Metrics data. Raw data: {}. Readed {} bytes expected {} bytes", + vec_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(" "), + vec_bytes.len(), + std::mem::size_of::() + ); + continue; + } + if vec_bytes.len() >= std::mem::size_of::() { + let packet_loss: PacketLossMetrics = + unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; + + match exporter { + "otlp" => { + let mut metadata = + Metadata::from_ebpf(Some(packet_loss.tgid), &packet_loss.comm); + metadata.enrich(); + metrics.record_packet_loss_metrics(&packet_loss, &metadata); + } + _ => continue, + } + + let tgid = packet_loss.tgid; + let comm = String::from_utf8_lossy(&packet_loss.comm); + let ts_us = packet_loss.ts_us; + let sk_drop_count = packet_loss.sk_drops; + let sk_err = packet_loss.sk_err; + let sk_err_soft = packet_loss.sk_err_soft; + let sk_backlog_len = packet_loss.sk_backlog_len; + let sk_write_memory_queued = packet_loss.sk_write_memory_queued; + let sk_ack_backlog = packet_loss.sk_ack_backlog; + let sk_receive_buffer_size = packet_loss.sk_receive_buffer_size; + + info!( + "tgid: {}, comm: {}, ts_us: {}, sk_drops: {}, sk_err: {}, sk_err_soft: {}, sk_backlog_len: {}, sk_write_memory_queued: {}, sk_ack_backlog: {}, sk_receive_buffer_size: {}", + tgid, + comm, + ts_us, + sk_drop_count, + sk_err, + sk_err_soft, + sk_backlog_len, + sk_write_memory_queued, + sk_ack_backlog, + sk_receive_buffer_size + ); + } + } + } + + /// Read [`TimeStampMetrics`] events and record OpenTelemetry observations. + /// + /// Counterpart to [`read_packet_loss_metrics`] for the `time_stamp_events` map. + #[cfg(feature = "monitoring-structs")] + pub async fn read_timestamp_metrics( + buffers: &mut [BytesMut], + tot_events: i32, + offset: i32, + exporter: &str, + metrics: Arc, + ) { + for i in offset..tot_events { + let vec_bytes = &buffers[i as usize]; + if vec_bytes.len() < std::mem::size_of::() { + error!( + "Corrupted Network Metrics data. Raw data: {}. Readed {} bytes expected {} bytes", + vec_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(" "), + vec_bytes.len(), + std::mem::size_of::() + ); + continue; + } + if vec_bytes.len() >= std::mem::size_of::() { + let time_stamp_event: TimeStampMetrics = + unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; + + match exporter { + "otlp" => { + let mut metadata = Metadata::from_ebpf( + Some(time_stamp_event.tgid), + &time_stamp_event.comm, + ); + metadata.enrich(); + metrics.record_timestamp_metrics(&time_stamp_event, &metadata); + } + _ => continue, + } + + let delta_us = time_stamp_event.delta_us; + let ts_us = time_stamp_event.ts_us; + let tgid = time_stamp_event.tgid; + let comm = String::from_utf8_lossy(&time_stamp_event.comm); + let lport = time_stamp_event.lport; + let dport_be = time_stamp_event.dport_be; + let af = time_stamp_event.af; + info!( + "TimeStampEvent - delta_us: {}, ts_us: {}, tgid: {}, comm: {}, lport: {}, dport_be: {}, af: {}", + delta_us, ts_us, tgid, comm, lport, dport_be, af + ); + } + } + } + + /// Read [`CpuFrequency`] events and record OpenTelemetry observations. + #[cfg(feature = "monitoring-structs")] + pub async fn read_cpu_frequency( + buffers: &mut [BytesMut], + tot_events: i32, + offset: i32, + exporter: &str, + metrics: Arc, + ) { + for i in offset..tot_events { + let vec_bytes = &buffers[i as usize]; + if vec_bytes.len() < std::mem::size_of::() { + error!( + "Corrupted Cpu Frequency Metrics data. Raw data: {}. Readed {} bytes expected {} bytes", + vec_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(" "), + vec_bytes.len(), + std::mem::size_of::() + ); + continue; + } + if vec_bytes.len() >= std::mem::size_of::() { + let cpu_freq_metrics: CpuFrequency = + unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; + + match exporter { + "otlp" => { + let mut metadata = Metadata::from_ebpf( + Some(cpu_freq_metrics.pid), + &cpu_freq_metrics.command, + ); + metadata.enrich(); + metrics.record_cpu_bytes_alloc(&cpu_freq_metrics, &metadata); + } + _ => continue, + } + + let bytes_alloc = cpu_freq_metrics.bytes_alloc; + let pid = cpu_freq_metrics.pid; + let command = cpu_freq_metrics.command; + info!( + "Cpu Bytes alloc: {} pid : {} command: {:?}", + bytes_alloc, pid, command + ); + } + } + } + + /// Read [`MemAlloc`] events and record OpenTelemetry observations. + #[cfg(feature = "monitoring-structs")] + pub async fn read_mem_alloc( + buffers: &mut [BytesMut], + tot_events: i32, + offset: i32, + exporter: &str, + metrics: Arc, + ) { + for i in offset..tot_events { + let vec_bytes = &buffers[i as usize]; + if vec_bytes.len() < std::mem::size_of::() { + error!( + "Corrupted MemAlloc data. Raw data: {}. Readed {} bytes expected {} bytes", + vec_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(" "), + vec_bytes.len(), + std::mem::size_of::() + ); + continue; + } + if vec_bytes.len() >= std::mem::size_of::() { + let mem_alloc: MemAlloc = + unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; + + match exporter { + "otlp" => { + let mut metadata = + Metadata::from_ebpf(Some(mem_alloc.tgid), &mem_alloc.command); + metadata.enrich(); + metrics.record_enter_mem_alloc(&mem_alloc, &metadata); + } + _ => continue, + } + + let tgid = mem_alloc.tgid; + let command = String::from_utf8_lossy(&mem_alloc.command); + let addr = mem_alloc.addr; + let length = mem_alloc.length; + + info!( + "MemAlloc - tgid: {}, command: {}, addr: {}, length: {}", + tgid, command, addr, length + ); + } + } + } + + /// Read [`SchedStatWait`] events and record OpenTelemetry observations. + #[cfg(feature = "monitoring-structs")] + pub async fn read_sched_stat_wait( + buffers: &mut [BytesMut], + tot_events: i32, + offset: i32, + exporter: &str, + metrics: Arc, + ) { + for i in offset..tot_events { + let vec_bytes = &buffers[i as usize]; + if vec_bytes.len() < std::mem::size_of::() { + error!( + "Corrupted SchedStatWait data. Raw data: {}. Readed {} bytes expected {} bytes", + vec_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(" "), + vec_bytes.len(), + std::mem::size_of::() + ); + continue; + } + if vec_bytes.len() >= std::mem::size_of::() { + let sched_stat_wait: SchedStatWait = + unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; + + match exporter { + "otlp" => { + let mut metadata = Metadata::from_ebpf( + Some(sched_stat_wait.tgid), + &sched_stat_wait.command, + ); + metadata.enrich(); + metrics.record_sched_stat_wait(&sched_stat_wait, &metadata); + } + _ => continue, + } + + let tgid = sched_stat_wait.tgid; + let command = String::from_utf8_lossy(&sched_stat_wait.command); + let delay = sched_stat_wait.delay; + + info!( + "SchedStatWait - tgid: {}, command: {}, delay: {}", + tgid, command, delay + ); + } + } + } + + /// Read [`SchedStatRuntime`] events and record OpenTelemetry observations. + #[cfg(feature = "monitoring-structs")] + pub async fn read_sched_stat_runtime( + buffers: &mut [BytesMut], + tot_events: i32, + offset: i32, + exporter: &str, + metrics: Arc, + ) { + for i in offset..tot_events { + let vec_bytes = &buffers[i as usize]; + if vec_bytes.len() < std::mem::size_of::() { + error!( + "Corrupted SchedStatRuntime data. Raw data: {}. Readed {} bytes expected {} bytes", + vec_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(" "), + vec_bytes.len(), + std::mem::size_of::() + ); + continue; + } + if vec_bytes.len() >= std::mem::size_of::() { + let sched_stat_runtime: SchedStatRuntime = + unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; + + match exporter { + "otlp" => { + let mut metadata = Metadata::from_ebpf( + Some(sched_stat_runtime.tgid), + &sched_stat_runtime.command, + ); + metadata.enrich(); + metrics.record_sched_stat_runtime(&sched_stat_runtime, &metadata); + } + _ => continue, + } + + let tgid = sched_stat_runtime.tgid; + let command = String::from_utf8_lossy(&sched_stat_runtime.command); + let runtime = sched_stat_runtime.runtime; + + info!( + "SchedStatRuntime - tgid: {}, command: {}, runtime: {}", + tgid, command, runtime + ); + } + } + } + + /// Read [`CpuIdle`] events and record OpenTelemetry observations. + #[cfg(feature = "monitoring-structs")] + pub async fn read_cpu_idle( + buffers: &mut [BytesMut], + tot_events: i32, + offset: i32, + exporter: &str, + metrics: Arc, + ) { + for i in offset..tot_events { + let vec_bytes = &buffers[i as usize]; + if vec_bytes.len() < std::mem::size_of::() { + error!( + "Corrupted CpuIdle data. Raw data: {}. Readed {} bytes expected {} bytes", + vec_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(" "), + vec_bytes.len(), + std::mem::size_of::() + ); + continue; + } + if vec_bytes.len() >= std::mem::size_of::() { + let cpu_idle: CpuIdle = + unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; + + match exporter { + "otlp" => { + let metadata = Metadata::from_ebpf(None, &[]); + metrics.record_cpu_idle(&cpu_idle, &metadata); + } + _ => continue, + } + + let cpu_id = cpu_idle.cpu_id; + let state = cpu_idle.state; + + info!( + "CpuIdle state changed - cpu_id: {}, state: {}", + cpu_id, state + ); + } + } + } +} + +/// Read perf-buffer events in a loop and dispatch to the appropriate [`Consumer`] handler. +/// +/// This function runs indefinitely (or until the process receives `SIGINT`). +/// It polls every CPU buffer every 100 ms, reads available events, and routes +/// them to the matching `Consumer::read_*` method. +/// +/// # Arguments +/// - `array_buffers` — per-CPU `PerfEventArrayBuffer` handles opened by [`fill_buffers`]. +/// - `buffers` — pre-allocated `BytesMut` scratch space sized by [`BufferSize::set_buffer`]. +/// - `consumer` — discriminator that selects which `read_*` method to invoke. +/// - `metrics` — optional [`Metrics`] handle; required when `consumer` is a monitoring variant. +#[cfg(feature = "buffer-reader")] +pub async fn read_perf_buffer>( + mut array_buffers: Vec>, + mut buffers: Vec, + consumer: Consumer, + #[cfg(feature = "monitoring-structs")] metrics: Option>, +) { + loop { + for buf in array_buffers.iter_mut() { + match buf.read_events(&mut buffers) { + Ok(events) => { + if events.lost > 0 { + tracing::debug!("Lost events: {} ", events.lost); + } + if events.read > 0 { + tracing::debug!("Readed events: {}", events.read); + let offset = 0; + let tot_events = events.read as i32; + + match consumer { + #[cfg(feature = "network-structs")] + Consumer::PacketLog => { + Consumer::read_packet_log(&mut buffers, tot_events, offset).await + } + #[cfg(feature = "network-structs")] + Consumer::TcpPacketRegistry => { + Consumer::read_tcp_registry_log(&mut buffers, tot_events, offset) + .await + } + #[cfg(feature = "network-structs")] + Consumer::VethLog => { + Consumer::read_and_handle_veth_log(&mut buffers, tot_events, offset) + .await + } + #[cfg(feature = "monitoring-structs")] + Consumer::PacketLossMetrics => { + Consumer::read_packet_loss_metrics( + &mut buffers, + tot_events, + offset, + "otlp", + metrics + .clone() + .expect("Metrics required for PacketLossMetrics"), + ) + .await + } + #[cfg(feature = "monitoring-structs")] + Consumer::TimeStampMetrics => { + Consumer::read_timestamp_metrics( + &mut buffers, + tot_events, + offset, + "otlp", + metrics + .clone() + .expect("Metric required for TimeStampMetrics"), + ) + .await + } + #[cfg(feature = "monitoring-structs")] + Consumer::CpuFrequency => { + Consumer::read_cpu_frequency( + &mut buffers, + tot_events, + offset, + "otlp", + metrics.clone().expect("Metric required for CpuFrequency"), + ) + .await + } + #[cfg(feature = "monitoring-structs")] + Consumer::MemAlloc => { + Consumer::read_mem_alloc( + &mut buffers, + tot_events, + offset, + "otlp", + metrics.clone().expect("Metric required for MemAlloc"), + ) + .await + } + #[cfg(feature = "monitoring-structs")] + Consumer::SchedStatWait => { + Consumer::read_sched_stat_wait( + &mut buffers, + tot_events, + offset, + "otlp", + metrics.clone().expect("Metric required for SchedStatWait"), + ) + .await + } + #[cfg(feature = "monitoring-structs")] + Consumer::SchedStatRuntime => { + Consumer::read_sched_stat_runtime( + &mut buffers, + tot_events, + offset, + "otlp", + metrics + .clone() + .expect("Metric required for SchedStatRuntime"), + ) + .await + } + #[cfg(feature = "monitoring-structs")] + Consumer::CpuIdle => { + Consumer::read_cpu_idle( + &mut buffers, + tot_events, + offset, + "otlp", + metrics.clone().expect("Metric required for CpuIdle"), + ) + .await + } + } + } + } + Err(e) => { + error!("Cannot read events from buffer. Reason: {} ", e); + } + } + } + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + } +} diff --git a/core/src/components/metrics/src/helpers.rs b/core/src/components/metrics/src/helpers.rs index f162048e..6c526f0d 100644 --- a/core/src/components/metrics/src/helpers.rs +++ b/core/src/components/metrics/src/helpers.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use tokio::signal; use tracing::{error, info}; -use cortexbrain_common::buffer_type::{BufferType, read_perf_buffer}; +use cortexbrain_common::consumer::{Consumer, read_perf_buffer}; use cortexbrain_common::otel_metrics::Metrics; /// Listen for eBPF perf-buffer events and record OpenTelemetry metrics. @@ -103,7 +103,7 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a read_perf_buffer( array_buffers, buffers, - BufferType::PacketLossMetrics, + Consumer::PacketLossMetrics, Some(metrics), ) .await; @@ -118,7 +118,7 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a read_perf_buffer( array_buffers, buffers, - BufferType::TimeStampMetrics, + Consumer::TimeStampMetrics, Some(metrics), ) .await; @@ -133,7 +133,7 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a read_perf_buffer( array_buffers, buffers, - BufferType::CpuFrequency, + Consumer::CpuFrequency, Some(metrics), ) .await; @@ -145,7 +145,7 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a let mut array_buffers = cpu_idle_perf_buffer; let mut buffers = cpu_idle_buffers; tokio::spawn(async move { - read_perf_buffer(array_buffers, buffers, BufferType::CpuIdle, Some(metrics)).await; + read_perf_buffer(array_buffers, buffers, Consumer::CpuIdle, Some(metrics)).await; }) }; @@ -154,7 +154,7 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a let mut array_buffers = mem_alloc_perf_buffer; let mut buffers = mem_alloc_buffers; tokio::spawn(async move { - read_perf_buffer(array_buffers, buffers, BufferType::MemAlloc, Some(metrics)).await; + read_perf_buffer(array_buffers, buffers, Consumer::MemAlloc, Some(metrics)).await; }) }; @@ -166,7 +166,7 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a read_perf_buffer( array_buffers, buffers, - BufferType::SchedStatWait, + Consumer::SchedStatWait, Some(metrics), ) .await; @@ -181,7 +181,7 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a read_perf_buffer( array_buffers, buffers, - BufferType::SchedStatRuntime, + Consumer::SchedStatRuntime, Some(metrics), ) .await; From 9f9aa593bd30dfbd06127ed29e9a8e8408a57e12 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Mon, 20 Jul 2026 16:40:10 +0200 Subject: [PATCH 28/40] (#150): added service_discovery.rs module in the core library --- core/common/src/service_discovery.rs | 166 +++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 core/common/src/service_discovery.rs diff --git a/core/common/src/service_discovery.rs b/core/common/src/service_discovery.rs new file mode 100644 index 00000000..af2efd19 --- /dev/null +++ b/core/common/src/service_discovery.rs @@ -0,0 +1,166 @@ +#[cfg(feature = "experimental")] +use anyhow::Error; +#[cfg(feature = "experimental")] +use std::fs; + +/// Supported runtime prefixes for extracting the container ID from a cgroup path. +const RUNTIME_PREFIXES: &[&str] = &["docker-", "cri-containerd-", "crio-"]; + +/// Extract the container ID (e.g. Docker runtime ID) from a cgroup filesystem path. +/// +/// Supports multiple prefixes: docker-, cri-containerd-, crio-. +#[cfg(feature = "experimental")] +pub fn extract_container_id(cgroup_path: &str) -> Result { + let splits: Vec<&str> = cgroup_path.split('/').collect(); + + for prefix in RUNTIME_PREFIXES { + if let Ok(index) = extract_target_from_splits(&splits, prefix) { + let id = splits[index] + .trim_start_matches(prefix) + .trim_end_matches(".scope"); + return Ok(id.to_string()); + } + } + + Err(Error::msg(format!( + "No known runtime prefix found in cgroup path: {}", + cgroup_path + ))) +} + +/// Extract the Pod UID from a Kubernetes cgroup filesystem path. +/// +/// Example inputs: +/// - `/sys/fs/cgroup/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pod231bd2d7_0f09_4781_a4e1_e4ea026342dd.slice` +/// - `/sys/fs/cgroup/kubelet.slice/kubelet-kubepods.slice/kubelet-kubepods-besteffort.slice/kubelet-kubepods-besteffort-pod231bd2d7_0f09_4781_a4e1_e4ea026342dd.slice` +#[cfg(feature = "experimental")] +pub fn extract_pod_uid(cgroup_path: &str) -> Result { + let splits: Vec<&str> = cgroup_path.split('/').collect(); + + let index = extract_target_from_splits(&splits, "-pod")?; + + let pod_split = splits[index] + .trim_start_matches("kubelet-kubepods-besteffort-") + .trim_start_matches("kubelet-kubepods-burstable-") + .trim_start_matches("kubepods-besteffort-") + .trim_start_matches("kubepods-burstable-"); + + let uid_ = pod_split + .trim_start_matches("pod") + .trim_end_matches(".slice"); + + let uid = uid_.replace('_', "-"); + Ok(uid) +} + +/// Scan a given cgroup directory and return subdirectory paths. +/// +/// If `path` does not exist or is empty, falls back to the default K8s kubepods.slice path. +#[cfg(feature = "experimental")] +pub fn scan_cgroup_paths(path: &str) -> Result, Error> { + let mut cgroup_paths: Vec = Vec::new(); + let default_path = "/sys/fs/cgroup/kubepods.slice"; + + let target_path = if path.is_empty() || fs::metadata(path).is_err() { + default_path + } else { + path + }; + + let entries = match fs::read_dir(target_path) { + Ok(entries) => entries, + Err(e) => { + tracing::error!("Error reading cgroup directory {:?}: {}", target_path, e); + return Ok(cgroup_paths); + } + }; + + for entry in entries { + if let Ok(entry) = entry { + let path = entry.path(); + if path.is_dir() { + if let Some(path_str) = path.to_str() { + cgroup_paths.push(path_str.to_string()); + } + } + } + } + + Ok(cgroup_paths) +} + +#[cfg(feature = "experimental")] +fn extract_target_from_splits(splits: &[&str], target: &str) -> Result { + for (index, split) in splits.iter().enumerate() { + if split.contains(target) { + return Ok(index); + } + } + Err(Error::msg(format!("'{target}' word not found in split"))) +} + +#[cfg(feature = "experimental")] +mod tests { + use super::*; + + #[test] + fn extract_uid_from_string() { + let cgroup_paths = vec![ + "/sys/fs/cgroup/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pod231bd2d7_0f09_4781_a4e1_e4ea026342dd.slice".to_string(), + "/sys/fs/cgroup/kubelet.slice/kubelet-kubepods.slice/kubelet-kubepods-besteffort.slice/kubelet-kubepods-besteffort-pod231bd2d7_0f09_4781_a4e1_e4ea026342dd.slice".to_string(), + ]; + + let mut uid_vec = Vec::::new(); + + for cgroup_path in cgroup_paths { + let uid = extract_pod_uid(&cgroup_path) + .map_err(|e| format!("An error occurred {}", e)) + .unwrap(); + uid_vec.push(uid); + } + + let check = vec![ + "231bd2d7-0f09-4781-a4e1-e4ea026342dd".to_string(), + "231bd2d7-0f09-4781-a4e1-e4ea026342dd".to_string(), + ]; + + assert_eq!(uid_vec, check); + } + + #[test] + fn test_extract_target_index() { + let cgroup_paths = vec![ + "/sys/fs/cgroup/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pod231bd2d7_0f09_4781_a4e1_e4ea026342dd.slice".to_string(), + "/sys/fs/cgroup/kubelet.slice/kubelet-kubepods.slice/kubelet-kubepods-besteffort.slice/kubelet-kubepods-besteffort-pod231bd2d7_0f09_4781_a4e1_e4ea026342dd.slice".to_string(), + ]; + + let mut index_vec = Vec::::new(); + for cgroup_path in cgroup_paths { + let splits: Vec<&str> = cgroup_path.split('/').collect(); + + let target_index = extract_target_from_splits(&splits, "-pod").unwrap(); + index_vec.push(target_index); + } + let index_check = vec![6, 7]; + assert_eq!(index_vec, index_check); + } + + #[test] + fn extract_docker_id() { + let cgroup_paths = vec![ + "/sys/fs/cgroup/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pod17fd3f7c_37e4_4009_8c38_e58b30691af3.slice/docker-13abd64c0ba349975a762476c9703b642d18077eabeb3aa1d941132048afc861.scope".to_string(), + "/sys/fs/cgroup/kubelet.slice/kubelet-kubepods.slice/kubelet-kubepods-besteffort.slice/kubelet-kubepods-besteffort-pod17fd3f7c_37e4_4009_8c38_e58b30691af3.slice/docker-13abd64c0ba349975a762476c9703b642d18077eabeb3aa1d941132048afc861.scope".to_string(), + ]; + + let mut id_vec = Vec::::new(); + for cgroup_path in cgroup_paths { + let id = extract_container_id(&cgroup_path).unwrap(); + id_vec.push(id); + } + let id_check = vec![ + "13abd64c0ba349975a762476c9703b642d18077eabeb3aa1d941132048afc861".to_string(), + "13abd64c0ba349975a762476c9703b642d18077eabeb3aa1d941132048afc861".to_string(), + ]; + assert_eq!(id_vec, id_check); + } +} From 7a997b88c0340982bfc69588de5cff1c0eb9c8b0 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Mon, 20 Jul 2026 16:48:47 +0200 Subject: [PATCH 29/40] [#202]: added metadata.rs module to enrich the eBPF context with container data from docker and kubernetes --- core/common/src/metadata.rs | 202 ++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 core/common/src/metadata.rs diff --git a/core/common/src/metadata.rs b/core/common/src/metadata.rs new file mode 100644 index 00000000..9c97bbb1 --- /dev/null +++ b/core/common/src/metadata.rs @@ -0,0 +1,202 @@ +use std::fs; + +/// Detected container runtime. +#[derive(Debug, Clone, PartialEq)] +pub enum ContainerRuntime { + Docker, + Kubernetes, + Unknown, +} + +/// Process / container metadata enriched by service discovery. +/// +/// Built from raw eBPF data and enriched with a cgroup -> Docker -> K8s lookup. +#[derive(Debug, Clone)] +pub struct Metadata { + pub tgid: Option, + pub command: String, + pub runtime: ContainerRuntime, + pub container_name: Option, + pub container_id: Option, + pub pod_name: Option, + pub namespace: Option, +} + +impl Metadata { + /// Build base metadata from eBPF data. + pub fn from_ebpf(tgid: Option, command: &[u8]) -> Self { + let command = String::from_utf8_lossy(command) + .trim_end_matches('\0') + .to_string(); + Self { + tgid, + command, + runtime: ContainerRuntime::Unknown, + container_name: None, + container_id: None, + pod_name: None, + namespace: None, + } + } + + /// Lookup rules: first Docker (filesystem), then Kubernetes (API). + /// + /// 1. Reads `/proc//cgroup`. + /// 2. Extracts the container ID from the cgroup path. + /// 3. Tries to resolve the container name from `/var/lib/docker/containers//config.v2.json`. + /// 4. If Docker is not found, attempts K8s lookup. + pub fn enrich(&mut self) { + self.try_resolve_docker(); + // K8s lookup will be enabled later with an LRU cache. + } + + /// Docker resolution via local filesystem. + /// + // TODO: this is working for Linux, can anyone check if this works on macOs systems ? + fn try_resolve_docker(&mut self) { + let Some(tgid) = self.tgid else { return }; + + // Step 1: read the cgroup path from procfs + let cgroup_info = match fs::read_to_string(format!("/proc/{}/cgroup", tgid)) { + Ok(s) => s, + Err(e) => { + tracing::debug!("Cannot read /proc/{}/cgroup: {}", tgid, e); + return; + } + }; + + // Extract the actual path from the cgroup file (format: hierarchy:id:path) + let cgroup_path = cgroup_info + .lines() + .filter_map(|line| line.split(':').nth(2)) + .next() + .unwrap_or(""); + + if cgroup_path.is_empty() { + return; + } + + // Step 2: extract container ID from the path + if let Some(id) = extract_container_id_from_path(cgroup_path) { + self.container_id = Some(id.clone()); + self.runtime = ContainerRuntime::Docker; + + // Step 3: resolve container name from Docker metadata JSON + match resolve_docker_name(&id) { + Some(name) => self.container_name = Some(name), + None => self.container_name = Some("null".to_string()), + } + } + } + + /// Manual enrichment from Kubernetes (for external use, e.g. identity service). + pub fn enrich_from_k8s(&mut self, pod_name: impl Into, namespace: impl Into) { + self.runtime = ContainerRuntime::Kubernetes; + self.pod_name = Some(pod_name.into()); + self.namespace = Some(namespace.into()); + } +} + +/// Extract the container ID from a cgroup path, supporting multiple prefixes. +fn extract_container_id_from_path(cgroup_path: &str) -> Option { + let parts: Vec<&str> = cgroup_path.split('/').collect(); + + for part in &parts { + // Docker with systemd (docker-.scope) + if let Some(id) = part.strip_prefix("docker-") { + return Some(id.strip_suffix(".scope").unwrap_or(id).to_string()); + } + // containerd (cri-containerd-.scope) + if let Some(id) = part.strip_prefix("cri-containerd-") { + return Some(id.strip_suffix(".scope").unwrap_or(id).to_string()); + } + // CRI-O (crio-.scope) + if let Some(id) = part.strip_prefix("crio-") { + return Some(id.strip_suffix(".scope").unwrap_or(id).to_string()); + } + // Docker cgroup v1 plain (/docker/) + if *part == "docker" { + if let Some(next) = + parts.get(parts.iter().position(|p| *p == "docker").unwrap_or(0) + 1) + { + return Some(next.to_string()); + } + } + } + + None +} + +/// Resolve a Docker container name by reading `config.v2.json`. +/// +// TODO: Does this work on macOs? +fn resolve_docker_name(container_id: &str) -> Option { + let path = format!("/var/lib/docker/containers/{}/config.v2.json", container_id); + let json_str = fs::read_to_string(&path).ok()?; + let parsed: serde_json::Value = serde_json::from_str(&json_str).ok()?; + let image_name = parsed + .get("Config")? + .get("Image")? + .as_str()? + .trim_start_matches('/'); + Some(image_name.to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_extract_container_id_docker_systemd() { + let path = "/sys/fs/cgroup/system.slice/docker-13abd64c0ba349975a762476c9703b642d18077eabeb3aa1d941132048afc861.scope"; + assert_eq!( + extract_container_id_from_path(path), + Some("13abd64c0ba349975a762476c9703b642d18077eabeb3aa1d941132048afc861".to_string()) + ); + } + + #[test] + fn test_extract_container_id_containerd() { + let path = "/sys/fs/cgroup/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-podb8701d38_3791_422d_ad15_890ad1a0844b.slice/cri-containerd-abc123.scope"; + assert_eq!( + extract_container_id_from_path(path), + Some("abc123".to_string()) + ); + } + + #[test] + fn test_extract_container_id_docker_v1() { + let path = "/docker/13abd64c0ba349975a762476c9703b642d18077eabeb3aa1d941132048afc861"; + assert_eq!( + extract_container_id_from_path(path), + Some("13abd64c0ba349975a762476c9703b642d18077eabeb3aa1d941132048afc861".to_string()) + ); + } + + #[test] + fn test_extract_container_id_not_found() { + let path = "/sys/fs/cgroup/system.slice/systemd-journald.service"; + assert_eq!(extract_container_id_from_path(path), None); + } + #[test] + fn resolve_docker_name_test() { + use std::process::Command; + // create a docker container from a testing image + let create_test_container_command = Command::new("/usr/bin/docker") + .args(["run", "--rm", "-d", "busybox:latest"]) + .output() + .expect("Cannot create test container"); + let output = create_test_container_command.stdout; + let container_id = String::from_utf8(output) + .expect("Cannot extract container id") + .trim() + .to_string(); + println!("{}", &container_id); + let docker_container_name = match resolve_docker_name(&container_id) { + Some(name) => Some(name), + None => Some("null".to_string()), + }; + + assert_eq!(docker_container_name, Some("busybox:latest".to_string())) + } +} From acb9d48141a9efcb09500c68c0058d0c46d3415f Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Mon, 20 Jul 2026 16:49:49 +0200 Subject: [PATCH 30/40] [#202]: updated consumer API with enriched Metadata --- core/common/src/otel_metrics.rs | 149 ++++++++++++++++---------------- 1 file changed, 76 insertions(+), 73 deletions(-) diff --git a/core/common/src/otel_metrics.rs b/core/common/src/otel_metrics.rs index e3c1fcc0..f9a68d40 100644 --- a/core/common/src/otel_metrics.rs +++ b/core/common/src/otel_metrics.rs @@ -7,17 +7,18 @@ //! //! - An [`Arc`] is moved into each Tokio //! task that reads a perf buffer. All instrument operations are lock-free. -//! - Every observation is tagged with `tgid` and `comm` -//! extracted from the eBPF struct, allowing downstream collectors to group -//! telemetry by process. +//! - Every observation is tagged with process and container metadata +//! extracted from the eBPF struct via [`Metadata`]. use crate::buffer_type::{ CpuFrequency, CpuIdle, MemAlloc, PacketLossMetrics, SchedStatRuntime, SchedStatWait, TimeStampMetrics, }; +use crate::metadata::{ContainerRuntime, Metadata}; use crate::semantic::Semantic; use opentelemetry::KeyValue; use opentelemetry::metrics::{Counter, Gauge, Histogram, Meter}; + pub struct Metrics { /// Total number of eBPF events processed across all perf buffers. pub events_total: Counter, @@ -73,91 +74,91 @@ impl Metrics { let events_total = meter .u64_counter(Semantic::TotalEvents.title()) .with_description(Semantic::TotalEvents.description()) - .with_unit("1") + //.with_unit("1") .build(); // total socket events let socket_events_total = meter .u64_counter(Semantic::SocketTotalEvents.title()) .with_description(Semantic::SocketTotalEvents.description()) - .with_unit("1") + //.with_unit("1") .build(); // socket drops let sk_drops = meter .i64_gauge(Semantic::SocketDrops.title()) .with_description(Semantic::SocketDrops.description()) - .with_unit("1") + //.with_unit("1") .build(); // socket errors let sk_err = meter .i64_gauge(Semantic::SocketErrorsCount.title()) .with_description(Semantic::SocketErrorsCount.description()) - .with_unit("1") + //.with_unit("1") .build(); // tcp latency microseconds let tcp_latency_us = meter .u64_histogram(Semantic::Latency.title()) .with_description(Semantic::Latency.description()) - .with_unit("us") + //.with_unit("us") .build(); // cpu bytes alloc total events let cpu_bytes_alloc_events_total = meter .u64_counter(Semantic::PerCpuTotalEvents.title()) .with_description(Semantic::PerCpuTotalEvents.description()) - .with_unit("1") + //.with_unit("1") .build(); // cpu bytes allocation let cpu_bytes_alloc = meter .i64_gauge(Semantic::PerCpuBytesAllocated.title()) .with_description(Semantic::PerCpuBytesAllocated.description()) - .with_unit("bytes") + //.with_unit("bytes") .build(); // memory allocation (mmap) events total let mem_alloc_events_total = meter .u64_counter(Semantic::TotalMemoryAllocationEvents.title()) .with_description(Semantic::TotalMemoryAllocationEvents.description()) - .with_unit("1") + //.with_unit("1") .build(); // bytes requested via mmap syscalls let enter_mem_alloc = meter .i64_gauge(Semantic::RequestedMemoryBytes.title()) .with_description(Semantic::RequestedMemoryBytes.description()) - .with_unit("bytes") + //.with_unit("bytes") .build(); // scheduler wait time in nanoseconds let sched_stat_wait = meter .i64_gauge(Semantic::SchedulerWaitTime.title()) .with_description(Semantic::SchedulerWaitTime.description()) - .with_unit("ns") + //.with_unit("ns") .build(); // distribution of scheduler wait times let sched_stat_wait_distribution = meter .u64_histogram(Semantic::SchedulerWaitTimeDistribution.title()) .with_description(Semantic::SchedulerWaitTimeDistribution.description()) - .with_unit("ns") + //.with_unit("ns") .build(); // scheduler runtime in nanoseconds let sched_stat_runtime = meter .i64_gauge(Semantic::SchedulerRuntime.title()) .with_description(Semantic::SchedulerRuntime.description()) - .with_unit("ns") + //.with_unit("ns") .build(); // distribution of scheduler runtimes let sched_stat_runtime_distribution = meter .u64_histogram(Semantic::SchedulerRuntimeDistribution.title()) .with_description(Semantic::SchedulerRuntimeDistribution.description()) - .with_unit("ns") + //.with_unit("ns") .build(); // current CPU idle C-state per cpu_id @@ -183,23 +184,50 @@ impl Metrics { } } + /// Build OpenTelemetry attributes from [`Metadata`]. + fn build_attrs(&self, metadata: &Metadata) -> Vec { + let mut attrs = Vec::with_capacity(10); + + // base + attrs.push(KeyValue::new( + "tgid", + metadata.tgid.map(|v| v as i64).unwrap_or(-1), + )); + attrs.push(KeyValue::new("command", metadata.command.clone())); + + // container metadata + attrs.push(KeyValue::new( + "container.name", + match &metadata.container_name { + Some(name) => name.clone(), + None => "null".to_string(), + }, + )); + + if let Some(ref id) = metadata.container_id { + attrs.push(KeyValue::new("container.id", id.clone())); + } + //if let Some(ref runtime) = metadata.runtime { + // attrs.push(KeyValue::new("container.runtime", runtime.clone())); + //} + + // k8s metadata + if let Some(ref pod) = metadata.pod_name { + attrs.push(KeyValue::new("k8s.pod.name", pod.clone())); + } + if let Some(ref ns) = metadata.namespace { + attrs.push(KeyValue::new("k8s.namespace.name", ns.clone())); + } + + attrs + } + /// Record a single [`PacketLossMetrics`] event. /// /// Increments `events_total` and `socket_events_total`, records `sk_drops` /// and `sk_err` as gauges. - /// - /// Every observation carries: - /// - /// - `tgid` – task group ID. - /// - `command` – command name (null-terminated bytes converted to a UTF-8 - /// string and trimmed). - pub fn record_packet_loss_metrics(&self, m: &PacketLossMetrics) { - let comm = String::from_utf8_lossy(&m.comm); - let comm_trimmed = comm.trim_end_matches('\0').to_string(); - let attrs = &[ - KeyValue::new("tgid", m.tgid as i64), - KeyValue::new("command", comm_trimmed), - ]; + pub fn record_packet_loss_metrics(&self, m: &PacketLossMetrics, metadata: &Metadata) { + let attrs = &self.build_attrs(metadata); self.events_total.add(1, attrs); self.socket_events_total.add(1, attrs); @@ -211,30 +239,18 @@ impl Metrics { /// /// Increments `events_total`, and records `delta_us` in the latency /// histogram. - /// - /// Every observation carries `tgid` and `command` (see - /// [`record_packet_loss_metrics`]). - pub fn record_timestamp_metrics(&self, m: &TimeStampMetrics) { - let comm = String::from_utf8_lossy(&m.comm); - let comm_trimmed = comm.trim_end_matches('\0').to_string(); - let attrs = &[ - KeyValue::new("tgid", m.tgid as i64), - KeyValue::new("command", comm_trimmed), - ]; + pub fn record_timestamp_metrics(&self, m: &TimeStampMetrics, metadata: &Metadata) { + let attrs = &self.build_attrs(metadata); self.events_total.add(1, attrs); self.tcp_latency_us.record(m.delta_us, attrs); } - pub fn record_cpu_bytes_alloc(&self, m: &CpuFrequency) { + /// Record a single [`CpuFrequency`] event. + pub fn record_cpu_bytes_alloc(&self, m: &CpuFrequency, metadata: &Metadata) { let bytes_allocated = m.bytes_alloc; - let tgid = m.pid; // percpu tracepoints expose TGID in common_pid - let comm = String::from_utf8_lossy(&m.command); - let command = comm.trim_end_matches('\0').to_string(); - let attrs = &[ - KeyValue::new("tgid", tgid as i64), - KeyValue::new("command", command), - ]; + let attrs = &self.build_attrs(metadata); + self.cpu_bytes_alloc_events_total.add(1, attrs); self.cpu_bytes_alloc.record(bytes_allocated as i64, attrs); } @@ -245,13 +261,8 @@ impl Metrics { /// the requested length in the `enter_mem_alloc` gauge. The shared /// `events_total` counter is intentionally **not** incremented for these /// events. - pub fn record_enter_mem_alloc(&self, m: &MemAlloc) { - let comm = String::from_utf8_lossy(&m.command); - let command = comm.trim_end_matches('\0').to_string(); - let attrs = &[ - KeyValue::new("tgid", m.tgid as i64), - KeyValue::new("command", command), - ]; + pub fn record_enter_mem_alloc(&self, m: &MemAlloc, metadata: &Metadata) { + let attrs = &self.build_attrs(metadata); self.events_total.add(1, attrs); self.mem_alloc_events_total.add(1, attrs); @@ -263,13 +274,8 @@ impl Metrics { /// Increments `events_total`, records `delay` in the `sched_stat_wait` /// gauge, and observes `delay` in the `sched_stat_wait_distribution` /// histogram. - pub fn record_sched_stat_wait(&self, m: &SchedStatWait) { - let comm = String::from_utf8_lossy(&m.command); - let command = comm.trim_end_matches('\0').to_string(); - let attrs = &[ - KeyValue::new("tgid", m.tgid as i64), - KeyValue::new("command", command), - ]; + pub fn record_sched_stat_wait(&self, m: &SchedStatWait, metadata: &Metadata) { + let attrs = &self.build_attrs(metadata); self.events_total.add(1, attrs); self.sched_stat_wait.record(m.delay as i64, attrs); @@ -281,27 +287,24 @@ impl Metrics { /// Increments `events_total`, records `runtime` in the `sched_stat_runtime` /// gauge, and observes `runtime` in the `sched_stat_runtime_distribution` /// histogram. - pub fn record_sched_stat_runtime(&self, m: &SchedStatRuntime) { - let comm = String::from_utf8_lossy(&m.command); - let command = comm.trim_end_matches('\0').to_string(); - let attrs = &[ - KeyValue::new("tgid", m.tgid as i64), - KeyValue::new("command", command), - ]; + pub fn record_sched_stat_runtime(&self, m: &SchedStatRuntime, metadata: &Metadata) { + let attrs = &self.build_attrs(metadata); self.events_total.add(1, attrs); self.sched_stat_runtime.record(m.runtime as i64, attrs); - self.sched_stat_runtime_distribution.record(m.runtime, attrs); + self.sched_stat_runtime_distribution + .record(m.runtime, attrs); } /// Record a single [`CpuIdle`] event. /// /// Updates `cpu_idle_state` gauge to the latest C-state for the given /// `cpu_id`. Events are only emitted by eBPF when the state changes. - pub fn record_cpu_idle(&self, m: &CpuIdle) { - let attrs = &[KeyValue::new("cpu_id", m.cpu_id as i64)]; + pub fn record_cpu_idle(&self, m: &CpuIdle, metadata: &Metadata) { + let mut attrs = self.build_attrs(metadata); + attrs.push(KeyValue::new("cpu_id", m.cpu_id as i64)); - self.events_total.add(1, attrs); - self.cpu_idle_state.record(m.state as i64, attrs); + self.events_total.add(1, &attrs); + self.cpu_idle_state.record(m.state as i64, &attrs); } } From 2e333bf55585c229e1218e59c4a9b58b9462f41d Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Mon, 20 Jul 2026 16:50:45 +0200 Subject: [PATCH 31/40] added new modules (metadata and consumer) in the common crate --- core/Cargo.lock | 9 ++------- core/common/Cargo.toml | 3 ++- core/common/src/lib.rs | 7 ++++++- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/core/Cargo.lock b/core/Cargo.lock index 6ae4f98d..6fbd9e1e 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -415,9 +415,10 @@ dependencies = [ "opentelemetry", "opentelemetry-appender-tracing", "opentelemetry-otlp", - "opentelemetry-semantic-conventions", "opentelemetry-stdout", "opentelemetry_sdk", + "serde", + "serde_json", "tokio", "tracing", "tracing-subscriber", @@ -1419,12 +1420,6 @@ dependencies = [ "tonic-prost", ] -[[package]] -name = "opentelemetry-semantic-conventions" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ca2f98a0437b427b4b08f19f1caa3c44db885a202bc12cfea13d6c702243d68" - [[package]] name = "opentelemetry-stdout" version = "0.32.0" diff --git a/core/common/Cargo.toml b/core/common/Cargo.toml index e1c39c5c..49a65b94 100644 --- a/core/common/Cargo.toml +++ b/core/common/Cargo.toml @@ -25,7 +25,8 @@ bytemuck = "1.25.0" bytes = "1.11.0" bytemuck_derive = "1.10.2" tokio = "1.49.0" -opentelemetry-semantic-conventions = "0.32.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" [features] map-handlers = [] diff --git a/core/common/src/lib.rs b/core/common/src/lib.rs index a0b28770..39231dd5 100644 --- a/core/common/src/lib.rs +++ b/core/common/src/lib.rs @@ -13,4 +13,9 @@ pub mod map_handlers; pub mod otel_metrics; #[cfg(feature = "program-handlers")] pub mod program_handlers; -mod semantic; +pub mod semantic; +pub mod metadata; +#[cfg(feature = "buffer-reader")] +pub mod consumer; +#[cfg(feature = "experimental")] +pub mod service_discovery; From b4efecbe63ffcfdc908d97e60e23f25d53384c2e Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sun, 26 Jul 2026 10:24:29 +0200 Subject: [PATCH 32/40] (feature): added SSL tracing using uprobes and uretprobes (SSL_read/SSL_write). --- .../metrics_tracer/src/data_structures.rs | 12 +---- .../src/components/metrics_tracer/src/main.rs | 43 +++++++++++++++- core/src/components/metrics_tracer/src/ssl.rs | 50 +++++++++++++++++++ 3 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 core/src/components/metrics_tracer/src/ssl.rs diff --git a/core/src/components/metrics_tracer/src/data_structures.rs b/core/src/components/metrics_tracer/src/data_structures.rs index cfc80b6f..d7a28771 100644 --- a/core/src/components/metrics_tracer/src/data_structures.rs +++ b/core/src/components/metrics_tracer/src/data_structures.rs @@ -98,14 +98,6 @@ pub struct SslEvent { pub requested: i32, // num argument passed to SSL_read/SSL_write } -#[repr(C, packed)] -#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] -pub struct SslCtxInfo { - pub ssl: u64, // SSL* pointer - pub requested: i32, - pub ts_ns: u64, -} - // Map: connect-start timestamp by socket pointer #[map(name = "time_stamp_start")] pub static mut TIME_STAMP_START: HashMap<*mut core::ffi::c_void, TimeStampStartInfo> = @@ -139,8 +131,8 @@ pub static mut CPU_IDLE_LAST_STATE: HashMap = HashMap::::with_max_entries(256, 0); #[map(name = "ssl_ctx_map")] -pub static mut SSL_CTX_MAP: HashMap = - HashMap::::with_max_entries(4096, 0); +pub static mut SSL_CTX_MAP: HashMap = + HashMap::::with_max_entries(4096, 0); #[map(name = "ssl_events")] pub static SSL_EVENTS: PerfEventArray = PerfEventArray::new(0); diff --git a/core/src/components/metrics_tracer/src/main.rs b/core/src/components/metrics_tracer/src/main.rs index 1da3451b..8a2541be 100644 --- a/core/src/components/metrics_tracer/src/main.rs +++ b/core/src/components/metrics_tracer/src/main.rs @@ -7,6 +7,7 @@ mod cpu; mod data_structures; mod memory; mod network; +mod ssl; use crate::bindings::net_device; use crate::cpu::{cpu_idle, per_cpu_bytes_alloc, sched_stat_runtime, sched_stat_wait}; @@ -21,15 +22,16 @@ use crate::data_structures::{MEM_ALLOC, SCHED_STAT_RUNTIME, SCHED_STAT_WAIT}; use crate::data_structures::{MemAlloc, SchedStatRuntime}; use crate::memory::enter_mmap; use crate::network::{detect_packet_loss, on_connect, on_rcv_state_process}; +use crate::ssl::{try_ssl_event_end, try_ssl_start}; use aya_ebpf::EbpfContext; use aya_ebpf::helpers::bpf_get_current_pid_tgid; use aya_ebpf::helpers::generated::{bpf_ktime_get_ns, bpf_perf_event_output}; use aya_ebpf::helpers::{ bpf_get_current_comm, bpf_probe_read_kernel, bpf_probe_read_kernel_str_bytes, }; -use aya_ebpf::macros::{kprobe, map, tracepoint}; +use aya_ebpf::macros::{kprobe, map, tracepoint, uprobe, uretprobe}; use aya_ebpf::maps::{HashMap, PerfEventArray}; -use aya_ebpf::programs::{ProbeContext, TracePointContext}; +use aya_ebpf::programs::{ProbeContext, RetProbeContext, TracePointContext}; use core::{mem, ptr}; const AF_INET: u16 = 2; @@ -183,6 +185,43 @@ fn sched_stat_runtime_tracer(ctx: &TracePointContext) -> Result<(), i64> { Ok(()) } +const SSL_READ_DIR: u8 = 0; +const SSL_WRITE_DIR: u8 = 1; + +#[uprobe] +fn ssl_read(ctx: ProbeContext) -> u32 { + match try_ssl_start(&ctx) { + Ok(_) => 0, + Err(_) => 0, // fail silently to avoid perturbing the application + } +} + +#[uretprobe] +fn ssl_read_ret(ctx: RetProbeContext) -> u32 { + match try_ssl_event_end(&ctx, SSL_READ_DIR) { + Ok(_) => 0, + Err(_) => 0, + } +} + +// ssl write +#[uprobe] +//uprobe reads input data from the userspace +fn ssl_write(ctx: ProbeContext) -> u32 { + match try_ssl_start(&ctx) { + Ok(_) => 0, + Err(_) => 0, + } +} + +#[uretprobe] +//uretprobe best fits for measuring returning data +fn ssl_write_ret(ctx: RetProbeContext) -> u32 { + match try_ssl_event_end(&ctx, SSL_WRITE_DIR) { + Ok(_) => 0, + Err(_) => 0, + } +} // panic handler #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { diff --git a/core/src/components/metrics_tracer/src/ssl.rs b/core/src/components/metrics_tracer/src/ssl.rs new file mode 100644 index 00000000..8bafdd9a --- /dev/null +++ b/core/src/components/metrics_tracer/src/ssl.rs @@ -0,0 +1,50 @@ +// observe L5 and L6 connections + +use crate::data_structures::{SSL_CTX_MAP, SSL_EVENTS, SslEvent}; +use aya_ebpf::helpers::{bpf_get_current_comm, bpf_get_current_pid_tgid, bpf_ktime_get_ns}; +use aya_ebpf::programs::{ProbeContext, RetProbeContext}; + +/// store requested bytes keyed by pid_tgid +/// This is the main entrypoint when working with SSL +pub fn try_ssl_start(ctx: &ProbeContext) -> Result<(), i64> { + let num_bytes = ctx.arg::(2).ok_or(1i64)?; + let pid_tgid = unsafe { bpf_get_current_pid_tgid() }; + let map_ptr = unsafe { &raw mut SSL_CTX_MAP }; + + unsafe { + (*map_ptr) + .insert(&pid_tgid, &num_bytes, 0) + .map_err(|_| 1i64)?; + } + + Ok(()) +} + +// ssl return: emit event with actual transferred bytes +pub fn try_ssl_event_end(ctx: &RetProbeContext, direction: u8) -> Result<(), i64> { + let size = ctx.ret::(); + let pid_tgid = unsafe { bpf_get_current_pid_tgid() }; // extracts pid and tgid + let tgid = (pid_tgid >> 32) as u32; // read only tgid + + let map_ptr = unsafe { &raw mut SSL_CTX_MAP }; + let requested = unsafe { (*map_ptr).get(&pid_tgid) }.copied().ok_or(1i64)?; + + let comm = unsafe { bpf_get_current_comm() }.map_err(|_| 1i64)?; // get current command that generates the event + let ts_us = unsafe { bpf_ktime_get_ns() } / 1_000; // get current time in nanoseconds + + let ev = SslEvent { + tgid, + comm, + ts_us, + direction, + size, + requested, + }; + + unsafe { + SSL_EVENTS.output(ctx, &ev, 0); // emit the event + (*map_ptr).remove(&pid_tgid); // remove the emitted event from the MAP + } + + Ok(()) +} From 21a2ecd3a70180295b2dc05e7f1b2787d6de5c66 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sun, 26 Jul 2026 10:26:59 +0200 Subject: [PATCH 33/40] (feature): added SslEvents structure in userspace. Added SSL events consumer and semantic --- core/common/src/buffer_type.rs | 23 ++++++++- core/common/src/consumer.rs | 85 +++++++++++++++++++++++++++++++++ core/common/src/otel_metrics.rs | 31 +++++++++++- core/common/src/semantic.rs | 6 +++ 4 files changed, 143 insertions(+), 2 deletions(-) diff --git a/core/common/src/buffer_type.rs b/core/common/src/buffer_type.rs index 17351684..2a0f1f06 100644 --- a/core/common/src/buffer_type.rs +++ b/core/common/src/buffer_type.rs @@ -14,7 +14,6 @@ use aya::util::online_cpus; use bytemuck_derive::Zeroable; use bytes::BytesMut; use std::net::Ipv4Addr; -use tracing::{error, info, warn}; /// /// IpProtocols enum to reconstruct the packet protocol based on the @@ -188,6 +187,19 @@ pub struct CpuIdle { #[cfg(feature = "monitoring-structs")] unsafe impl aya::Pod for CpuIdle {} +#[cfg(feature = "monitoring-structs")] +#[repr(C, packed)] +#[derive(Copy, Clone, Zeroable)] +pub struct SslEvent { + pub tgid: u32, + pub comm: [u8; TASK_COMM_LEN], + pub ts_us: u64, + pub direction: u8, // 0 = read, 1 = write + pub size: i32, // return value (bytes transferred or <0 on error) + pub requested: i32, // num argument passed to SSL_read/SSL_write +} +unsafe impl aya::Pod for SslEvent {} + /// Perform a byte swap from little-endian to big-endian. /// /// Used to reconstruct the correct IPv4 address from the u32 representation. @@ -225,6 +237,8 @@ pub enum BufferSize { SchedStatRuntime, #[cfg(feature = "monitoring-structs")] CpuIdle, + #[cfg(feature = "monitoring-structs")] + SslEvents, } #[cfg(feature = "buffer-reader")] @@ -252,6 +266,8 @@ impl BufferSize { BufferSize::SchedStatRuntime => std::mem::size_of::(), #[cfg(feature = "monitoring-structs")] BufferSize::CpuIdle => std::mem::size_of::(), + #[cfg(feature = "monitoring-structs")] + BufferSize::SslEvents => std::mem::size_of::(), } } @@ -313,6 +329,11 @@ impl BufferSize { let capacity = self.get_size() * 1024; return vec![BytesMut::with_capacity(capacity); tot_cpu]; } + #[cfg(feature = "monitoring-structs")] + BufferSize::SslEvents => { + let capacity = self.get_size() * 1024; + return vec![BytesMut::with_capacity(capacity); tot_cpu]; + } } } } diff --git a/core/common/src/consumer.rs b/core/common/src/consumer.rs index bd7d2676..ef082bc6 100644 --- a/core/common/src/consumer.rs +++ b/core/common/src/consumer.rs @@ -53,6 +53,8 @@ pub enum Consumer { SchedStatRuntime, #[cfg(feature = "monitoring-structs")] CpuIdle, + #[cfg(feature = "monitoring-structs")] + SslEvents, } #[cfg(feature = "buffer-reader")] @@ -636,6 +638,78 @@ impl Consumer { } } } + + pub async fn read_ssl_events( + buffers: &mut [BytesMut], + tot_events: i32, + offset: i32, + exporter: &str, + metrics: Arc, + ) { + for i in offset..tot_events { + use crate::buffer_type::SslEvent; + + let vec_bytes = &buffers[i as usize]; + if vec_bytes.len() < std::mem::size_of::() { + error!( + "Corrupted SslEvent data. Raw data: {}. Readed {} bytes expected {} bytes", + vec_bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(" "), + vec_bytes.len(), + std::mem::size_of::() + ); + continue; + } + if vec_bytes.len() >= std::mem::size_of::() { + let ssl_event: SslEvent = + unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) }; + + let direction = ssl_event.direction; + + match direction { + // 0 = read // 1= write + 0 => match exporter { + "otlp" => { + let mut metadata = Metadata::from_ebpf(None, &[]); + metadata.enrich(); + metrics.record_ssl_read_bytes(&ssl_event, &metadata); + let tgid = ssl_event.tgid; + let command = String::from_utf8_lossy(&ssl_event.comm); + let size = ssl_event.size; + let requested = ssl_event.requested; + + info!( + "SSL event: - tgid: {},- command : {}, - direction: {}, - size: {} , - requested : {}", + tgid, command, direction, size, requested + ); + } + _ => continue, + }, + 1 => match exporter { + "otlp" => { + let mut metadata = Metadata::from_ebpf(None, &[]); + metadata.enrich(); + metrics.record_ssl_write_bytes(&ssl_event, &metadata); + let tgid = ssl_event.tgid; + let command = String::from_utf8_lossy(&ssl_event.comm); + let size = ssl_event.size; + let requested = ssl_event.requested; + + info!( + "SSL event: - tgid: {},- command : {}, - direction: {}, - size: {} , - requested : {}", + tgid, command, direction, size, requested + ); + } + _ => continue, + }, + _ => continue, // direction data not logged or recorded + } + } + } + } } /// Read perf-buffer events in a loop and dispatch to the appropriate [`Consumer`] handler. @@ -766,6 +840,17 @@ pub async fn read_perf_buffer>( ) .await } + #[cfg(feature = "monitoring-structs")] + Consumer::SslEvents => { + Consumer::read_ssl_events( + &mut buffers, + tot_events, + offset, + "otlp", + metrics.clone().expect("Metric required for SslEvents"), + ) + .await + } } } } diff --git a/core/common/src/otel_metrics.rs b/core/common/src/otel_metrics.rs index f9a68d40..5e3c3fbc 100644 --- a/core/common/src/otel_metrics.rs +++ b/core/common/src/otel_metrics.rs @@ -11,7 +11,7 @@ //! extracted from the eBPF struct via [`Metadata`]. use crate::buffer_type::{ - CpuFrequency, CpuIdle, MemAlloc, PacketLossMetrics, SchedStatRuntime, SchedStatWait, + CpuFrequency, CpuIdle, MemAlloc, PacketLossMetrics, SchedStatRuntime, SchedStatWait, SslEvent, TimeStampMetrics, }; use crate::metadata::{ContainerRuntime, Metadata}; @@ -63,6 +63,9 @@ pub struct Metrics { /// Current CPU idle C-state per cpu_id, updated only on state change. pub cpu_idle_state: Gauge, + + pub ssl_read_bytes: Gauge, + pub ssl_write_bytes: Gauge, } // TODO: add identity metrics with TC classifier packet counts @@ -166,6 +169,17 @@ impl Metrics { .i64_gauge(Semantic::CpuIdleState.title()) .with_description(Semantic::CpuIdleState.description()) .build(); + + let ssl_read_bytes = meter + .i64_gauge(Semantic::SslReadBytes.title()) + .with_description(Semantic::SslReadBytes.description()) + .build(); + + let ssl_write_bytes = meter + .i64_gauge(Semantic::SslWriteBytes.title()) + .with_description(Semantic::SslWriteBytes.description()) + .build(); + Self { events_total, socket_events_total, @@ -181,6 +195,8 @@ impl Metrics { sched_stat_runtime, sched_stat_runtime_distribution, cpu_idle_state, + ssl_read_bytes, + ssl_write_bytes, } } @@ -307,4 +323,17 @@ impl Metrics { self.events_total.add(1, &attrs); self.cpu_idle_state.record(m.state as i64, &attrs); } + + pub fn record_ssl_read_bytes(&self, m: &SslEvent, metadata: &Metadata) { + let attrs = self.build_attrs(metadata); + + self.events_total.add(1, &attrs); + self.ssl_read_bytes.record(m.size as i64, &attrs); + } + pub fn record_ssl_write_bytes(&self, m: &SslEvent, metadata: &Metadata) { + let attrs = self.build_attrs(metadata); + + self.events_total.add(1, &attrs); + self.ssl_write_bytes.record(m.size as i64, &attrs); + } } diff --git a/core/common/src/semantic.rs b/core/common/src/semantic.rs index a0381d81..2536147c 100644 --- a/core/common/src/semantic.rs +++ b/core/common/src/semantic.rs @@ -15,6 +15,8 @@ pub enum Semantic { TotalMemoryAllocationEvents, RequestedMemoryBytes, CpuIdleState, + SslReadBytes, + SslWriteBytes, } impl Semantic { @@ -34,6 +36,8 @@ impl Semantic { Semantic::TotalMemoryAllocationEvents => "mem_alloc_events_total", Semantic::RequestedMemoryBytes => "enter_mem_alloc", Semantic::CpuIdleState => "cpu_idle_state", + Semantic::SslReadBytes => "ssl_read_bytes", + Semantic::SslWriteBytes => "ssl_write_bytes", } } pub fn description(&self) -> &'static str { @@ -66,6 +70,8 @@ impl Semantic { Semantic::CpuIdleState => { "Current CPU idle C-state per cpu_id, updated only on state change" } + Semantic::SslReadBytes => "Total bytes requested by the ssl_read function", + Semantic::SslWriteBytes => "Total bytes requested by the ssl_write function", } } } From fae39ba27db740efca35dfa1423ad23e134787a2 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sun, 26 Jul 2026 10:27:44 +0200 Subject: [PATCH 34/40] (feature): added support for uprobes and uretprobes to attach the programs --- core/common/src/program_handlers.rs | 50 ++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/core/common/src/program_handlers.rs b/core/common/src/program_handlers.rs index fc3d6d51..78d11326 100644 --- a/core/common/src/program_handlers.rs +++ b/core/common/src/program_handlers.rs @@ -1,6 +1,6 @@ use aya::{ Ebpf, - programs::{KProbe, TracePoint}, + programs::{KProbe, TracePoint, UProbe}, }; use std::convert::TryInto; use std::sync::{Arc, Mutex}; @@ -98,3 +98,51 @@ pub fn load_tracepoint_program( Ok(()) } + +#[cfg(feature = "program-handlers")] +pub fn load_uprobe_program( + bpf: Arc>, + program_name: &str, + uspace_fn: &str, + target: &str, + pid: Option, +) -> Result<(), anyhow::Error> { + let mut bpf_new = bpf + .lock() + .map_err(|e| anyhow::anyhow!("Cannot get value from lock. Reason: {}", e))?; + + // Load and attach the eBPF program + let program: &mut UProbe = bpf_new + .program_mut(program_name) + .ok_or_else(|| anyhow::anyhow!("Program {} not found", program_name))? + .try_into() + .map_err(|e| anyhow::anyhow!("Failed to convert program: {:?}", e))?; + + // STEP 1: load program + + program + .load() + .map_err(|e| anyhow::anyhow!("Cannot load program: {}. Error: {}", &program_name, e))?; + + // STEP 2: Attach the loaded program to userspace function + match program.attach(Some(uspace_fn), 0, target, pid) { + Ok(_) => info!( + "{} program attached successfully to user space function {}", + &program_name, &target + ), + Err(e) => { + error!( + "Error attaching {} program to userspace function {}. Reason: {:?}", + &program_name, &target, e + ); + return Err(anyhow::anyhow!( + "Failed to attach program {} to userspace function {}. Reason {:?}", + &program_name, + &target, + e + )); + } + }; + + Ok(()) +} From 1451eb37ca41a68cfce8048253140b782c9b5a98 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sun, 26 Jul 2026 10:29:31 +0200 Subject: [PATCH 35/40] (feature): attached new ssl uprobes in metrics/main.rs. Added events consumer in metrics/main.rs --- core/src/components/metrics/src/helpers.rs | 22 +++++++++++- core/src/components/metrics/src/main.rs | 42 +++++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/core/src/components/metrics/src/helpers.rs b/core/src/components/metrics/src/helpers.rs index 6c526f0d..7371df81 100644 --- a/core/src/components/metrics/src/helpers.rs +++ b/core/src/components/metrics/src/helpers.rs @@ -82,6 +82,10 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a .remove("sched_stat_runtime") .expect("Cannot create sched_stat_runtime perf buffer"); + let (_ssl_events, ssl_events_perf_buffer) = maps + .remove("ssl_events") + .expect("Cannot create ssl_events perf buffer"); + // Allocate byte-buffers sized for each structure type let net_metrics_buffers = BufferSize::NetworkMetricsEvents.set_buffer(); let time_stamp_events_buffers = BufferSize::TimeMetricsEvents.set_buffer(); @@ -90,6 +94,7 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a let mem_alloc_buffers = BufferSize::MemAlloc.set_buffer(); let sched_stat_wait_buffers = BufferSize::SchedStatWait.set_buffer(); let sched_stat_runtime_buffers = BufferSize::SchedStatRuntime.set_buffer(); + let ssl_events_buffers = BufferSize::SslEvents.set_buffer(); let metrics = Arc::new(Metrics::new(&meter)); @@ -103,7 +108,7 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a read_perf_buffer( array_buffers, buffers, - Consumer::PacketLossMetrics, + Consumer::PacketLossMetrics, Some(metrics), ) .await; @@ -188,6 +193,15 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a }) }; + let ssl_events_metrics = { + let metrics = Arc::clone(&metrics); + let mut array_buffers = ssl_events_perf_buffer; + let mut buffers = ssl_events_buffers; + tokio::spawn(async move { + read_perf_buffer(array_buffers, buffers, Consumer::SslEvents, Some(metrics)).await; + }) + }; + info!("Event listeners started, entering main loop..."); tokio::select! { @@ -233,6 +247,12 @@ pub async fn event_listener(bpf_maps: BpfMapsData, meter: Meter) -> Result<(), a } } + result = ssl_events_metrics => { + if let Err(e) = result { + error!("Ssl events task failed: {:?}", e); + } + } + _ = signal::ctrl_c() => { info!("Ctrl-C received, shutting down..."); } diff --git a/core/src/components/metrics/src/main.rs b/core/src/components/metrics/src/main.rs index 3a7bcbb6..4713c3e6 100644 --- a/core/src/components/metrics/src/main.rs +++ b/core/src/components/metrics/src/main.rs @@ -26,7 +26,7 @@ use cortexbrain_common::{ constants, logger::otlp_logger_init, map_handlers::{init_bpf_maps, map_pinner}, - program_handlers::{load_program, load_tracepoint_program}, + program_handlers::{load_program, load_tracepoint_program, load_uprobe_program}, }; #[tokio::main] @@ -51,6 +51,10 @@ async fn main() -> Result<(), anyhow::Error> { let mem_alloc_bpf = bpf.clone(); let sched_stat_wait_bpf = bpf.clone(); let sched_stat_runtime_bpf = bpf.clone(); + let ssl_read_bpf = bpf.clone(); + let ssl_read_ret_bpf = bpf.clone(); + let ssl_write_bpf = bpf.clone(); + let ssl_write_ret_bpf = bpf.clone(); info!("Running Ebpf logger"); info!("loading programs"); @@ -66,6 +70,7 @@ async fn main() -> Result<(), anyhow::Error> { "mem_alloc".to_string(), "sched_stat_wait".to_string(), "sched_stat_runtime".to_string(), + "ssl_events".to_string(), ]; match init_bpf_maps(bpf.clone(), map_data) { @@ -143,6 +148,41 @@ async fn main() -> Result<(), anyhow::Error> { .context( "An error occurred during the execution of load_program function", )?; + load_uprobe_program( + ssl_read_bpf, + "ssl_read", + "SSL_read", + "/usr/lib/x86_64-linux-gnu/libssl.so", + None, + ) + .expect("An error occured during the execution of load_uprobe_program"); + + load_uprobe_program( + ssl_read_ret_bpf, + "ssl_read_ret", + "SSL_read", + "/usr/lib/x86_64-linux-gnu/libssl.so", + None, + ) + .expect("An error occured during the execution of load_uprobe_program"); + + load_uprobe_program( + ssl_write_bpf, + "ssl_write", + "SSL_write", + "/usr/lib/x86_64-linux-gnu/libssl.so", + None, + ) + .expect("An error occured during the execution of load_uprobe_program"); + + load_uprobe_program( + ssl_write_ret_bpf, + "ssl_write_ret", + "SSL_write", + "/usr/lib/x86_64-linux-gnu/libssl.so", + None, + ) + .expect("An error occured during the execution of load_uprobe_program"); } // Hand off to the async event consumer From a6d8cedc003bb045f42bc9d630cd8f1ec27f8bb5 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sun, 26 Jul 2026 11:44:14 +0200 Subject: [PATCH 36/40] (dashboard): updated dashboard --- .../dashboards/cortexbrain-dashboard.json | 2614 +++++++++++++---- 1 file changed, 2077 insertions(+), 537 deletions(-) diff --git a/Examples/run-with-docker/grafana/dashboards/cortexbrain-dashboard.json b/Examples/run-with-docker/grafana/dashboards/cortexbrain-dashboard.json index 89e182fb..a942a876 100644 --- a/Examples/run-with-docker/grafana/dashboards/cortexbrain-dashboard.json +++ b/Examples/run-with-docker/grafana/dashboards/cortexbrain-dashboard.json @@ -1,605 +1,2145 @@ { - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] + "apiVersion": "dashboard.grafana.app/v2", + "kind": "Dashboard", + "metadata": { + "name": "cortexbrain-main", + "namespace": "default", + "uid": "8e2e2ca5-bfe0-451e-89f8-e306c0dcda13", + "resourceVersion": "1784563365099996", + "generation": 9, + "creationTimestamp": "2026-07-03T18:56:24Z", + "labels": { + "grafana.app/deprecatedInternalID": "1462851711197184" + }, + "annotations": { + "grafana.app/createdBy": "access-policy:service", + "grafana.app/managedBy": "classic-file-provisioning", + "grafana.app/managerId": "default", + "grafana.app/sourceChecksum": "1c118da3c05eccab5136e7a7ed465b05", + "grafana.app/sourcePath": "/var/lib/grafana/dashboards/cortexbrain-dashboard.json", + "grafana.app/sourceTimestamp": "1784563359000", + "grafana.app/updatedBy": "access-policy:service", + "grafana.app/updatedTimestamp": "2026-07-20T16:02:45Z", + "grafana.app/folder": "" + } }, - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 0, - "id": null, - "links": [], - "liveNow": false, - "panels": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "builtIn": true, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "query": { + "datasource": { + "name": "-- Grafana --" }, - "thresholdsStyle": { - "mode": "off" + "group": "grafana", + "kind": "DataQuery", + "spec": {}, + "version": "v0" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "${datasource}" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "builder", + "expr": "sum by() (sum by(container_name) (rate(cortexbrain_events_total[1m])))", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] } }, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null + "description": "", + "id": 1, + "links": [], + "title": "Total events (1m rate)", + "vizConfig": { + "group": "stat", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] }, - { - "color": "red", - "value": 80 + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true } - ] + }, + "version": "13.1.0" } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 0 - }, - "id": 1, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" } }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" + "panel-15": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "${datasource}" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "builder", + "expr": "sum by(command) (rate(cortexbrain_events_total[10m]))", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } }, - "editorMode": "builder", - "expr": "cortexbrain_packets_total", - "legendFormat": "__auto", - "range": true, - "refId": "A" + "description": "", + "id": 15, + "links": [], + "title": "Total events", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + } + }, + "version": "13.1.0" + } } - ], - "title": "Total packets", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" + "panel-16": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "${datasource}" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "builder", + "expr": "sum by(command) (rate(cortexbrain_cpu_bytes_alloc[10m]))", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 25, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "normal" + "description": "", + "id": 16, + "links": [], + "title": "Cpu bytes allocation", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 25, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + } }, - "thresholdsStyle": { - "mode": "off" + "version": "13.1.0" + } + } + }, + "panel-17": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "${datasource}" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "builder", + "expr": "group by(container_id) (cortexbrain_enter_mem_alloc_bytes)", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] } }, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null + "description": "", + "id": 17, + "links": [], + "title": "Mem Alloc size", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 25, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] }, - { - "color": "red", - "value": 80 + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } } - ] + }, + "version": "13.1.0" } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 0 - }, - "id": 2, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" } }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" + "panel-18": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "${datasource}" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "builder", + "expr": "sum by(command) (rate(cortexbrain_mem_alloc_events_total[10m]))", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } }, - "editorMode": "builder", - "expr": "cortexbrain_cpu_bytes_alloc", - "legendFormat": "__auto", - "range": true, - "refId": "A" + "description": "", + "id": 18, + "links": [], + "title": "Mem alloc tot events", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic", + "seriesBy": "max" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.9, + "drawStyle": "line", + "fillOpacity": 50, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 2, + "pointSize": 1, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "always", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + } + }, + "version": "13.1.0" + } } - ], - "title": "Cpu bytes allocation", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" + "panel-2": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "${datasource}" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "builder", + "expr": "sum by(container_name) (rate(cortexbrain_cpu_bytes_alloc[10m]))", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 25, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "normal" + "description": "", + "id": 2, + "links": [], + "title": "Cpu bytes allocation", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 25, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + } }, - "thresholdsStyle": { - "mode": "off" + "version": "13.1.0" + } + } + }, + "panel-22": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "PBFA97CFB590B2093" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "builder", + "expr": "sum by(container_name) (rate(cortexbrain_sched_stat_runtime[1m]))", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] } }, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null + "description": "", + "id": 22, + "links": [], + "title": "Scheduler Runtime (1m rate)", + "vizConfig": { + "group": "bargauge", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-BlYlRd" + }, + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "orange", + "value": 60 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] }, - { - "color": "red", - "value": 80 + "options": { + "displayMode": "gradient", + "legend": { + "calcs": [], + "displayMode": "list", + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": false + }, + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "valueMode": "color" } - ] + }, + "version": "13.1.0" } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 8 - }, - "id": 4, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" } }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" + "panel-23": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "PBFA97CFB590B2093" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "builder", + "expr": "sum by(le, container_name) (rate(cortexbrain_sched_stat_runtime_distribution_bucket[1m]))", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + }, + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "PBFA97CFB590B2093" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "expr": "", + "instant": false, + "range": true + }, + "version": "v0" + }, + "refId": "B" + } + } + ], + "queryOptions": {}, + "transformations": [] + } }, - "editorMode": "builder", - "expr": "cortexbrain_enter_mem_alloc", - "legendFormat": "__auto", - "range": true, - "refId": "A" + "description": "", + "id": 23, + "links": [], + "title": "Scheduler Runtime distribution (1m rate)", + "vizConfig": { + "group": "histogram", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "stacking": { + "group": "A", + "mode": "none" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + } + }, + "version": "13.1.0" + } } - ], - "title": "Mem Alloc size", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" + "panel-24": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "PBFA97CFB590B2093" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "builder", + "expr": "sum by(container_name) (cortexbrain_latency_us_bucket)", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" + "description": "", + "id": 24, + "links": [], + "title": "TCP L4 Latency Distribution", + "vizConfig": { + "group": "histogram", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "stacking": { + "group": "A", + "mode": "none" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + } }, - "thresholdsStyle": { - "mode": "off" + "version": "13.1.0" + } + } + }, + "panel-25": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "PBFA97CFB590B2093" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "builder", + "expr": "sum by(container_name) (rate(cortexbrain_socket_events_total[10m]))", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] } }, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null + "description": "", + "id": 25, + "links": [], + "title": "L4 Network Packets", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] }, - { - "color": "red", - "value": 80 + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } } - ] + }, + "version": "13.1.0" } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 8 - }, - "id": 5, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "single", - "sort": "none" } }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" + "panel-26": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "PBFA97CFB590B2093" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "builder", + "expr": "sum by(container_name) (histogram_quantile(0.9, rate(cortexbrain_latency_us_bucket[10m])))", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + }, + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "PBFA97CFB590B2093" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "builder", + "expr": "sum by(container_name) (histogram_quantile(0.75, rate(cortexbrain_latency_us_bucket[10m])))", + "instant": false, + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "B" + } + }, + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "PBFA97CFB590B2093" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "builder", + "expr": "sum by(container_name) (histogram_quantile(0.99, rate(cortexbrain_latency_us_bucket[10m])))", + "instant": false, + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "C" + } + } + ], + "queryOptions": {}, + "transformations": [] + } }, - "editorMode": "builder", - "expr": "cortexbrain_mem_alloc_events_total", - "legendFormat": "__auto", - "range": true, - "refId": "A" + "description": "", + "id": 26, + "links": [], + "title": "TCP Latency P99/P90/P75", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "blue", + "mode": "fixed" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "#EAB839", + "value": 60 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "sum by(container_name) (histogram_quantile(0.99, rate(cortexbrain_latency_us_bucket[10m])))" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "sum by(container_name) (histogram_quantile(0.75, rate(cortexbrain_latency_us_bucket[10m])))" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "sum by(container_name) (histogram_quantile(0.9, rate(cortexbrain_latency_us_bucket[10m])))" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + } + ] + }, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + } + }, + "version": "13.1.0" + } } - ], - "title": "Mem alloc tot events", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" + "panel-27": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "PBFA97CFB590B2093" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "builder", + "expr": "sum by(container_name) (cortexbrain_ssl_read_bytes)", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } }, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null + "description": "", + "id": 27, + "links": [], + "title": "SSL read bytes", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] }, - { - "color": "red", - "value": 80 + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } } - ] + }, + "version": "13.1.0" } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 8, - "x": 0, - "y": 16 - }, - "id": 6, - "options": { - "displayMode": "gradient", - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "maxVizHeight": 300, - "minVizHeight": 16, - "minVizWidth": 8, - "namePlacement": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showUnfilled": true, - "sizing": "auto", - "valueMode": "color" + } }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" + "panel-28": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "PBFA97CFB590B2093" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "builder", + "expr": "sum by(container_name) (cortexbrain_ssl_write_bytes)", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } }, - "editorMode": "builder", - "expr": "cortexbrain_sched_stat_runtime", - "legendFormat": "__auto", - "range": true, - "refId": "A" + "description": "", + "id": 28, + "links": [], + "title": "SSL write bytes", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + } + }, + "version": "13.1.0" + } } - ], - "title": "Scheduler Runtime (us)", - "transparent": true, - "type": "bargauge" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" + "panel-4": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "${datasource}" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "builder", + "expr": "sum by() (rate(cortexbrain_enter_mem_alloc_bytes[10m]))", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } }, - "thresholds": { - "mode": "percentage", - "steps": [ - { - "color": "green", - "value": null + "description": "", + "id": 4, + "links": [], + "title": "Mem Alloc size", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 25, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] }, - { - "color": "red", - "value": 80 + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } } - ] + }, + "version": "13.1.0" } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 16, - "x": 8, - "y": 16 - }, - "id": 3, - "options": { - "displayMode": "gradient", - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": false - }, - "maxVizHeight": 300, - "minVizHeight": 16, - "minVizWidth": 8, - "namePlacement": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showUnfilled": true, - "sizing": "auto", - "valueMode": "color" + } }, - "pluginVersion": "10.4.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${datasource}" + "panel-5": { + "kind": "Panel", + "spec": { + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "hidden": false, + "query": { + "datasource": { + "name": "${datasource}" + }, + "group": "prometheus", + "kind": "DataQuery", + "spec": { + "editorMode": "builder", + "expr": "sum by(container_name) (rate(cortexbrain_mem_alloc_events_total[10m]))", + "legendFormat": "__auto", + "range": true + }, + "version": "v0" + }, + "refId": "A" + } + } + ], + "queryOptions": {}, + "transformations": [] + } }, - "editorMode": "builder", - "expr": "cortexbrain_cpu_bytes_alloc", - "legendFormat": "__auto", - "range": true, - "refId": "A" + "description": "", + "id": 5, + "links": [], + "title": "Mem alloc tot events", + "vizConfig": { + "group": "timeseries", + "kind": "VizConfig", + "spec": { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic", + "seriesBy": "max" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.9, + "drawStyle": "line", + "fillOpacity": 50, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 2, + "pointSize": 1, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "always", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "options": { + "annotations": { + "clustering": -1, + "multiLane": false + }, + "legend": { + "calcs": [], + "displayMode": "list", + "enableFacetedFilter": false, + "overflow": "ellipsis", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + } + }, + "version": "13.1.0" + } } + } + }, + "layout": { + "kind": "TabsLayout", + "spec": { + "tabs": [ + { + "kind": "TabsLayoutTab", + "spec": { + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-1" + }, + "height": 8, + "width": 12, + "x": 0, + "y": 0 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-2" + }, + "height": 8, + "width": 12, + "x": 12, + "y": 0 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-4" + }, + "height": 8, + "width": 12, + "x": 0, + "y": 8 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-5" + }, + "height": 8, + "width": 12, + "x": 12, + "y": 8 + } + } + ] + } + }, + "title": "System Overview" + } + }, + { + "kind": "TabsLayoutTab", + "spec": { + "layout": { + "kind": "AutoGridLayout", + "spec": { + "columnWidthMode": "standard", + "items": [ + { + "kind": "AutoGridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-22" + } + } + }, + { + "kind": "AutoGridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-23" + } + } + } + ], + "maxColumnCount": 3, + "rowHeightMode": "standard" + } + }, + "title": "Scheduler" + } + }, + { + "kind": "TabsLayoutTab", + "spec": { + "layout": { + "kind": "AutoGridLayout", + "spec": { + "columnWidthMode": "standard", + "items": [ + { + "kind": "AutoGridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-25" + } + } + }, + { + "kind": "AutoGridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-24" + } + } + }, + { + "kind": "AutoGridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-26" + } + } + }, + { + "kind": "AutoGridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-27" + } + } + }, + { + "kind": "AutoGridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-28" + } + } + } + ], + "maxColumnCount": 3, + "rowHeightMode": "standard" + } + }, + "title": "Network" + } + }, + { + "kind": "TabsLayoutTab", + "spec": { + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-15" + }, + "height": 8, + "width": 12, + "x": 0, + "y": 0 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-16" + }, + "height": 8, + "width": 12, + "x": 12, + "y": 0 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-17" + }, + "height": 8, + "width": 12, + "x": 0, + "y": 8 + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "element": { + "kind": "ElementReference", + "name": "panel-18" + }, + "height": 8, + "width": 12, + "x": 12, + "y": 8 + } + } + ] + } + }, + "title": "Command aggregations" + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" ], - "title": "Timestamp microseconds histograms", - "transparent": true, - "type": "bargauge" - } - ], - "preload": false, - "refresh": "", - "schemaVersion": 39, - "tags": [], - "templating": { - "list": [ + "fiscalYearStartMonth": 0, + "from": "now-6h", + "hideTimepicker": false, + "timezone": "browser", + "to": "now" + }, + "title": "CortexBrain Dashboard", + "variables": [ { - "current": { - "selected": false, - "text": "Prometheus", - "value": "prometheus" - }, - "hide": 0, - "includeAll": false, - "label": "datasource", - "multi": false, - "name": "datasource", - "options": [], - "query": "prometheus", - "queryValue": "", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "type": "datasource" + "kind": "DatasourceVariable", + "spec": { + "allowCustomValue": true, + "current": { + "text": "Prometheus", + "value": "prometheus" + }, + "hide": "dontHide", + "includeAll": false, + "label": "datasource", + "multi": false, + "name": "datasource", + "options": [], + "pluginId": "prometheus", + "refresh": "onDashboardLoad", + "regex": "", + "skipUrlSync": false + } } ] - }, - "time": { - "from": "now-6h", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ] - }, - "timezone": "browser", - "title": "CortexBrain Dashboard", - "uid": "cortexbrain-main", - "version": 1 -} + } +} \ No newline at end of file From a16738daa606d3e706d8afa03d6ac8e68eeb4534 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sun, 26 Jul 2026 11:44:37 +0200 Subject: [PATCH 37/40] (chore): updated dependencies --- cli/Cargo.lock | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 7c843ec0..c77c8044 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -361,9 +361,10 @@ dependencies = [ "opentelemetry", "opentelemetry-appender-tracing", "opentelemetry-otlp", - "opentelemetry-semantic-conventions", "opentelemetry-stdout", "opentelemetry_sdk", + "serde", + "serde_json", "tokio", "tracing", "tracing-subscriber", @@ -1327,12 +1328,6 @@ dependencies = [ "tonic-prost", ] -[[package]] -name = "opentelemetry-semantic-conventions" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ca2f98a0437b427b4b08f19f1caa3c44db885a202bc12cfea13d6c702243d68" - [[package]] name = "opentelemetry-stdout" version = "0.32.0" From 863f7741a4ffa58e0736e0b836864f37613a7ed8 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Sun, 26 Jul 2026 11:46:14 +0200 Subject: [PATCH 38/40] (build): fixed bindgen generation --- core/src/components/metrics/Dockerfile | 8 ++++---- .../metrics_tracer/build-local-metrics-tracer.sh | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/core/src/components/metrics/Dockerfile b/core/src/components/metrics/Dockerfile index 7feac2d7..55cb4785 100644 --- a/core/src/components/metrics/Dockerfile +++ b/core/src/components/metrics/Dockerfile @@ -22,12 +22,12 @@ WORKDIR /usr/src/app COPY . . RUN bpftool btf dump file /sys/kernel/btf/vmlinux format c > src/components/metrics_tracer/vmlinux.h \ - && bindgen src/components/metrics_tracer/vmlinux.h \ - -o src/components/metrics_tracer/src/bindings.rs \ - --use-core \ + && bindgen --use-core --no-layout-tests \ --allowlist-type 'sk_buff' \ --allowlist-type 'sock_common' \ - --allowlist-type 'in6_addr' + --allowlist-type 'in6_addr' \ + src/components/metrics_tracer/vmlinux.h \ + -o src/components/metrics_tracer/src/bindings.rs RUN cargo +nightly build -Z build-std=core --target bpfel-unknown-none --release -p metrics_tracer diff --git a/core/src/components/metrics_tracer/build-local-metrics-tracer.sh b/core/src/components/metrics_tracer/build-local-metrics-tracer.sh index dde20cb4..f61fbe01 100755 --- a/core/src/components/metrics_tracer/build-local-metrics-tracer.sh +++ b/core/src/components/metrics_tracer/build-local-metrics-tracer.sh @@ -13,7 +13,11 @@ if ! command -v bindgen &> /dev/null; then export PATH="$HOME/.cargo/bin:$PATH" #add the ./cargo/bin directory to the PATH env variable fi -bindgen vmlinux.h -o src/bindings.rs --use-core --allowlist-type 'sk_buff' 'sock_common' 'in6_addr' +bindgen --use-core --no-layout-tests \ + --allowlist-type 'sk_buff' \ + --allowlist-type 'sock_common' \ + --allowlist-type 'in6_addr' \ + vmlinux.h -o src/bindings.rs cargo +nightly build -Z build-std=core --target bpfel-unknown-none --release --bin metrics_tracer From ce75299c7ed0f7534382b28d912346d51528b4f6 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Mon, 27 Jul 2026 10:45:14 +0200 Subject: [PATCH 39/40] (feature): added mcp connector --- mcp/Cargo.lock | 3450 +++++++++++++++++++++++++++++++++++++++++ mcp/Cargo.toml | 16 + mcp/src/main.rs | 22 + mcp/src/mod.rs | 3 + mcp/src/prometheus.rs | 91 ++ mcp/src/tools.rs | 141 ++ 6 files changed, 3723 insertions(+) create mode 100644 mcp/Cargo.lock create mode 100644 mcp/Cargo.toml create mode 100644 mcp/src/main.rs create mode 100644 mcp/src/mod.rs create mode 100644 mcp/src/prometheus.rs create mode 100644 mcp/src/tools.rs diff --git a/mcp/Cargo.lock b/mcp/Cargo.lock new file mode 100644 index 00000000..2650cdfb --- /dev/null +++ b/mcp/Cargo.lock @@ -0,0 +1,3450 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "async-compression" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-trait" +version = "0.1.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "aws-lc-rs" +version = "1.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "aya" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18bc4e506fbb85ab7392ed993a7db4d1a452c71b75a246af4a80ab8c9d2dd50" +dependencies = [ + "assert_matches", + "aya-obj", + "bitflags", + "bytes", + "libc", + "log", + "object", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "aya-obj" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51b96c5a8ed8705b40d655273bc4212cbbf38d4e3be2788f36306f154523ec7" +dependencies = [ + "bytes", + "core-error", + "hashbrown 0.15.5", + "log", + "object", + "thiserror 1.0.69", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + +[[package]] +name = "bytemuck_derive" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f65693059b6b9c588b9f62fed1cedbf0a8b805631457ea162d68f0de186f3de5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "compression-codecs" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" +dependencies = [ + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-error" +version = "0.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efcdb2972eb64230b4c50646d8498ff73f5128d196a90c7236eec4cbe8619b8f" +dependencies = [ + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cortexbrain-common" +version = "0.1.0" +dependencies = [ + "anyhow", + "aya", + "bytemuck", + "bytemuck_derive", + "bytes", + "k8s-openapi", + "kube", + "opentelemetry", + "opentelemetry-appender-tracing", + "opentelemetry-otlp", + "opentelemetry-stdout", + "opentelemetry_sdk", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", + "unicode-xid", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom", + "pin-project-lite", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-executor" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" + +[[package]] +name = "futures-macro" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "futures-sink" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "genai" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d12aba7e9dc2c4d54654566dc3dc8383b5cb52e0cfc5754989afe0480d933e3" +dependencies = [ + "base64", + "bytes", + "derive_more", + "eventsource-stream", + "futures", + "mime_guess", + "paste", + "regex", + "reqwest", + "serde", + "serde_json", + "serde_with", + "strum", + "tokio", + "tokio-stream", + "tracing", + "uuid", + "value-ext", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.19", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "jsonpath-rust" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c00ae348f9f8fd2d09f82a98ca381c60df9e0820d8d79fce43e649b4dc3128b" +dependencies = [ + "pest", + "pest_derive", + "regex", + "serde_json", + "thiserror 2.0.19", +] + +[[package]] +name = "k8s-openapi" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06d9e5e61dd037cdc51da0d7e2b2be10f497478ea7e120d85dad632adb99882b" +dependencies = [ + "base64", + "chrono", + "serde", + "serde_json", +] + +[[package]] +name = "kube" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e7bb0b6a46502cc20e4575b6ff401af45cfea150b34ba272a3410b78aa014e" +dependencies = [ + "k8s-openapi", + "kube-client", + "kube-core", +] + +[[package]] +name = "kube-client" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4987d57a184d2b5294fdad3d7fc7f278899469d21a4da39a8f6ca16426567a36" +dependencies = [ + "base64", + "bytes", + "chrono", + "either", + "futures", + "home", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-timeout", + "hyper-util", + "jsonpath-rust", + "k8s-openapi", + "kube-core", + "pem", + "rustls", + "secrecy", + "serde", + "serde_json", + "serde_yaml", + "thiserror 2.0.19", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tracing", +] + +[[package]] +name = "kube-core" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914bbb770e7bb721a06e3538c0edd2babed46447d128f7c21caa68747060ee73" +dependencies = [ + "chrono", + "derive_more", + "form_urlencoded", + "http", + "k8s-openapi", + "serde", + "serde-value", + "serde_json", + "thiserror 2.0.19", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "mcp" +version = "0.1.0" +dependencies = [ + "anyhow", + "cortexbrain-common", + "dotenv", + "genai", + "reqwest", + "rmcp", + "schemars 1.2.1", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "crc32fast", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "opentelemetry" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0142c63252a9e054e68a4c61a5778f7b14f576274d593f8ce883d191a099682" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.19", + "tracing", +] + +[[package]] +name = "opentelemetry-appender-tracing" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0080f0dc1d7c786f467cd85a4e395fcab11ee852004f39a29a18ab7c25d837" +dependencies = [ + "opentelemetry", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "opentelemetry-http" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5683015d09e2df236ef005b17f6f196f0d5f6313c4fa43a7b6a53b52776e4331" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry", + "reqwest", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9966929966d17620d7c316c643ba62631826e10021409357772d5eea84f62c35" +dependencies = [ + "http", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "reqwest", + "thiserror 2.0.19", + "tokio", + "tonic", + "tonic-types", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d658ba1faf63f7b9c492cfbe6e0ec365440a16132d3270c1065f7b33f1b638" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic", + "tonic-prost", +] + +[[package]] +name = "opentelemetry-stdout" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b1c6a247d79091f0062a5f4bd058589525cf987a8d4c169440d9c1be72f0ad" +dependencies = [ + "chrono", + "opentelemetry", + "opentelemetry_sdk", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b59f80e1ac4d5ff7a2db8fb6c80badb7f0f3f858211fba08dd9aaec750894f9" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "portable-atomic", + "rand 0.9.5", + "thiserror 2.0.19", + "tokio", + "tokio-stream", +] + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pest_meta" +version = "2.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c" +dependencies = [ + "pest", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "portable-atomic" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "process-wrap" +version = "8.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ef4f2f0422f23a82ec9f628ea2acd12871c81a9362b02c43c1aa86acfc3ba1" +dependencies = [ + "futures", + "indexmap 2.14.0", + "nix", + "tokio", + "tracing", + "windows", +] + +[[package]] +name = "prost" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "prost-types" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" +dependencies = [ + "prost", +] + +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.19", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.19", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rmcp" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5947688160b56fb6c827e3c20a72c90392a1d7e9dec74749197aa1780ac42ca" +dependencies = [ + "base64", + "chrono", + "futures", + "paste", + "pin-project-lite", + "process-wrap", + "rmcp-macros", + "schemars 1.2.1", + "serde", + "serde_json", + "thiserror 2.0.19", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + +[[package]] +name = "rmcp-macros" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01263441d3f8635c628e33856c468b96ebbce1af2d3699ea712ca71432d4ee7a" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.119", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.23.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "chrono", + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.119", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "base64", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.14.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl 2.0.19", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tonic" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" +dependencies = [ + "async-trait", + "base64", + "bytes", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "sync_wrapper", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-prost" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "tonic-types" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab1b02061f83d519bba3caa167f88f261ef05720ab8ebc954ade70de3348e8" +dependencies = [ + "prost", + "prost-types", + "tonic", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 2.14.0", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "async-compression", + "base64", + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "tokio", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "value-ext" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca945a9c7463ad3085da59b5dc4f8faf4dff6f3e7d835fa7ef08a3b36926512d" +dependencies = [ + "derive_more", + "serde", + "serde_json", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/mcp/Cargo.toml b/mcp/Cargo.toml new file mode 100644 index 00000000..439903f7 --- /dev/null +++ b/mcp/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "mcp" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = "1.0.104" +dotenv = "0.15.0" +genai = "0.6.5" +rmcp = { version = "0.8.0", features = ["server", "client", "transport-io", "transport-child-process"] } +tokio = { version = "1.47.1", features = ["full"] } +serde_json = "1.0.151" +reqwest = {version="0.13.4",features=["json","query"]} +schemars = "1.2.1" +serde = "1.0.229" +cortexbrain-common={path="../core/common"} diff --git a/mcp/src/main.rs b/mcp/src/main.rs new file mode 100644 index 00000000..0ea23be8 --- /dev/null +++ b/mcp/src/main.rs @@ -0,0 +1,22 @@ +mod client; +mod prometheus; +mod tools; + +use crate::tools::PrometheusTool; +use anyhow::Result; +use rmcp::ServiceExt; +use tokio::io::{BufReader, BufWriter}; + +#[tokio::main] +async fn main() -> Result<()> { + eprintln!("Starting cortexflow-mcp"); + + let stdin = BufReader::new(tokio::io::stdin()); + let stdout = BufWriter::new(tokio::io::stdout()); + + let service = PrometheusTool::new()?.serve((stdin, stdout)).await?; + service.waiting().await?; + + eprintln!("cortexflow-mcp running"); + Ok(()) +} diff --git a/mcp/src/mod.rs b/mcp/src/mod.rs new file mode 100644 index 00000000..3c619b60 --- /dev/null +++ b/mcp/src/mod.rs @@ -0,0 +1,3 @@ +//mod client; +mod prometheus; +mod tools; \ No newline at end of file diff --git a/mcp/src/prometheus.rs b/mcp/src/prometheus.rs new file mode 100644 index 00000000..716e9cd8 --- /dev/null +++ b/mcp/src/prometheus.rs @@ -0,0 +1,91 @@ +use anyhow::{Ok, Result}; +/// main prometheus client +/// accept a request as a query +use reqwest::Client; +#[derive(Clone)] +pub struct PromClient { + baseurl: String, + client: Client, +} + +const PROMETHEUS_SERVER: &str = "http://localhost:9090/api/v1/query"; + +impl PromClient { + pub fn new() -> Result { + Ok(PromClient { + client: Client::new(), + baseurl: PROMETHEUS_SERVER.to_string(), + }) + } + /// creates a query using promQL language + pub async fn query(&self, promql_query: &str) -> Result { + let response = self + .client + .get(format!("{}", self.baseurl)) + .query(&[("query", promql_query)]) + .send() + .await?; + Ok(response.json().await?) + } + pub async fn query_get_cpu_bytes(&self, container_name: &str, timeframe: &str) -> Result { + //query: sum by(container_name) (rate(cortexbrain_cpu_bytes_alloc[10m])) + let promql = format!( + r#"sum by(container_name) (rate(cortexbrain_cpu_bytes_alloc{{container_name=~".*{container_name}.*"}}[{timeframe}]))"# + ); + let res = serde_json::to_string_pretty(&self.query(&promql).await?)?; + + Ok(res) + } + pub async fn query_get_memory_allocated_bytes( + &self, + container_name: &str, + timeframe: &str, + ) -> Result { + //query: sum by(container_name) (rate(cortexbrain_enter_mem_alloc{container_name="..."}[10m])) + let promql = format!( + r#"sum by(container_name) (rate(cortexbrain_enter_mem_alloc{{container_name=~".*{container_name}.*"}}[{timeframe}]))"# + ); + let res = serde_json::to_string_pretty(&self.query(&promql).await?)?; + Ok(res) + } + pub async fn query_get_events(&self, container_name: &str, timeframe: &str) -> Result { + //query: sum by(container_name) (rate(cortexbrain_events_total{container_name="..."}[1m])) + let promql = format!( + r#"sum by(container_name) (rate(cortexbrain_events_total{{container_name=~".*{container_name}.*"}}[{timeframe}]))"# + ); + let res = serde_json::to_string_pretty(&self.query(&promql).await?)?; + Ok(res) + } + pub async fn query_get_l4_events(&self, container_name: &str, timeframe: &str) -> Result { + //query: sum by(container_name) (rate(cortexbrain_socket_events_total{container_name="..."}[10m])) + let promql = format!( + r#"sum by(container_name) (rate(cortexbrain_socket_events_total{{container_name=~".*{container_name}.*"}}[{timeframe}]))"# + ); + let res = serde_json::to_string_pretty(&self.query(&promql).await?)?; + Ok(res) + } + pub async fn query_get_ssl_write_events( + &self, + container_name: &str, + timeframe: &str, + ) -> Result { + //query: sum by(container_name) (rate(cortexbrain_ssl_write_bytes{container_name="..."}[10m])) + let promql = format!( + r#"sum by(container_name) (rate(cortexbrain_ssl_write_bytes{{container_name=~".*{container_name}.*"}}[{timeframe}]))"# + ); + let res = serde_json::to_string_pretty(&self.query(&promql).await?)?; + Ok(res) + } + pub async fn query_get_ssl_read_events( + &self, + container_name: &str, + timeframe: &str, + ) -> Result { + //query: sum by(container_name) (rate(cortexbrain_ssl_read_bytes{container_name="..."}[10m])) + let promql = format!( + r#"sum by(container_name) (rate(cortexbrain_ssl_read_bytes{{container_name=~".*{container_name}.*"}}[{timeframe}]))"# + ); + let res = serde_json::to_string_pretty(&self.query(&promql).await?)?; + Ok(res) + } +} diff --git a/mcp/src/tools.rs b/mcp/src/tools.rs new file mode 100644 index 00000000..dde642b2 --- /dev/null +++ b/mcp/src/tools.rs @@ -0,0 +1,141 @@ +use crate::prometheus::PromClient; +use anyhow::Result; +use rmcp::handler::server::ServerHandler; +use rmcp::handler::server::router::tool::ToolRouter; +use rmcp::handler::server::wrapper::Parameters; +use rmcp::model::{Implementation, ServerCapabilities, ServerInfo}; +use rmcp::{tool, tool_handler, tool_router}; +use schemars::JsonSchema; +use serde::Deserialize; + +#[derive(Deserialize, JsonSchema)] +struct Params { + container_name: String, // example "grafana/grafana:13.1.0" + timeframe: String, //example "10m" +} + +#[derive(Clone)] +pub struct PrometheusTool { + prometheus: PromClient, + tool_router: ToolRouter, +} + +impl PrometheusTool { + pub fn new() -> Result { + Ok(Self { + prometheus: PromClient::new()?, + tool_router: Self::tool_router(), + }) + } +} + +#[tool_router] +impl PrometheusTool { + #[tool(name = "get_cpu_bytes", description = "CPU bytes allocation per event")] + pub async fn get_cpu_bytes( + &self, + Parameters(params): Parameters, + ) -> Result { + let res = self + .prometheus + .query_get_cpu_bytes(¶ms.container_name, ¶ms.timeframe) + .await + .expect("An error occured"); + Ok(res) + } + + #[tool( + name = "get_memory_allocated_bytes", + description = "Bytes requested via mmap syscalls" + )] + pub async fn get_memory_allocated_bytes( + &self, + Parameters(params): Parameters, + ) -> Result { + let res = self + .prometheus + .query_get_memory_allocated_bytes(¶ms.container_name, ¶ms.timeframe) + .await + .expect("An error occured"); + Ok(res) + } + + #[tool( + name = "get_events", + description = "Total number of eBPF events processed across all perf buffers" + )] + pub async fn get_events(&self, Parameters(params): Parameters) -> Result { + let res = self + .prometheus + .query_get_events(¶ms.container_name, ¶ms.timeframe) + .await + .expect("An error occured"); + Ok(res) + } + + #[tool( + name = "get_l4_events", + description = "Total number of socket state events processed" + )] + pub async fn get_l4_events( + &self, + Parameters(params): Parameters, + ) -> Result { + let res = self + .prometheus + .query_get_l4_events(¶ms.container_name, ¶ms.timeframe) + .await + .expect("An error occured"); + Ok(res) + } + + #[tool( + name = "get_ssl_write_events", + description = "Total bytes requested by the ssl_write function" + )] + pub async fn get_ssl_write_events( + &self, + Parameters(params): Parameters, + ) -> Result { + let res = self + .prometheus + .query_get_ssl_write_events(¶ms.container_name, ¶ms.timeframe) + .await + .expect("An error occured"); + Ok(res) + } + + #[tool( + name = "get_ssl_read_events", + description = "Total bytes requested by the ssl_read function" + )] + pub async fn get_ssl_read_events( + &self, + Parameters(params): Parameters, + ) -> Result { + let res = self + .prometheus + .query_get_ssl_read_events(¶ms.container_name, ¶ms.timeframe) + .await + .expect("An error occured"); + Ok(res) + } +} + +#[tool_handler] +impl ServerHandler for PrometheusTool { + fn get_info(&self) -> ServerInfo { + ServerInfo { + server_info: Implementation { + name: "cortexflow-mcp".to_string(), + title: Some("Cortexflow MCP Server".to_string()), + version: env!("CARGO_PKG_VERSION").to_string(), + ..Default::default() + }, + capabilities: ServerCapabilities::builder() + .enable_tools() + .build(), + ..Default::default() + } + } +} From 9b14694b8e69ed4bea5feefe6b3e801f6b8e4d44 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Mon, 27 Jul 2026 10:45:33 +0200 Subject: [PATCH 40/40] updated docker compose example --- Examples/run-with-docker/docker-compose.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Examples/run-with-docker/docker-compose.yaml b/Examples/run-with-docker/docker-compose.yaml index 4e486583..dea30cd9 100644 --- a/Examples/run-with-docker/docker-compose.yaml +++ b/Examples/run-with-docker/docker-compose.yaml @@ -1,6 +1,6 @@ services: cortexflow-metrics: - image: lorenzotettamanti/cortexflow-metrics:otel-test-15 + image: lorenzotettamanti/cortexflow-metrics:otel-metrics-23 container_name: cortexflow-metrics stop_signal: SIGINT restart: unless-stopped @@ -20,6 +20,8 @@ services: - /lib/modules:/lib/modules:ro - /sys/kernel/debug:/sys/kernel/debug - /proc:/host/proc:ro + - /var/run/docker.sock:/var/run/docker.sock + - /var/lib/docker:/var/lib/docker:ro networks: - cortexflow @@ -57,6 +59,7 @@ services: - GOMEMLIMIT=1600MiB volumes: - ./otel-collector-config.yaml:/conf/otel-collector-config.yaml:ro + - /var/run/docker.sock:/var/run/docker.sock networks: - cortexflow depends_on: @@ -76,13 +79,14 @@ services: volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro - prometheus-data:/prometheus + - /var/run/docker.sock:/var/run/docker.sock networks: - cortexflow depends_on: - otel-collector grafana: - image: grafana/grafana:11.6.16 + image: grafana/grafana:13.1.0 container_name: grafana user: "472:472" ports: @@ -96,6 +100,7 @@ services: - ./grafana/provisioning:/etc/grafana/provisioning:ro - ./grafana/dashboards:/var/lib/grafana/dashboards:ro - grafana-data:/var/lib/grafana + - /var/run/docker.sock:/var/run/docker.sock networks: - cortexflow depends_on: