Skip to content

Commit aa9f438

Browse files
[#175]: added modules to initialize the Metrics exporter using opentelemetry sdk. added srtruct Metrics to group all the metrics in one place. added auxiliary functions record_network_metrics and record_timestamp_metrics. added exporter setting in buffer_type/read_network_metrics and buffer_type/read_timestamp_metrics
1 parent bf1720b commit aa9f438

4 files changed

Lines changed: 333 additions & 7 deletions

File tree

core/common/src/buffer_type.rs

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
#[cfg(feature = "monitoring-structs")]
2+
use crate::otel_metrics::Metrics;
13
#[cfg(feature = "buffer-reader")]
24
use aya::maps::{MapData, PerfEventArray};
35
use aya::{maps::perf::PerfEventArrayBuffer, util::online_cpus};
46
use bytemuck_derive::Zeroable;
57
use bytes::BytesMut;
68
use std::net::Ipv4Addr;
9+
#[cfg(feature = "buffer-reader")]
10+
#[cfg(feature = "monitoring-structs")]
11+
use std::sync::Arc;
712
use tracing::{error, info, warn};
813

914
//
@@ -342,7 +347,39 @@ impl BufferType {
342347
}
343348
}
344349
#[cfg(feature = "monitoring-structs")]
345-
pub async fn read_network_metrics(buffers: &mut [BytesMut], tot_events: i32, offset: i32) {
350+
/// Continuously read [`NetworkMetrics`] events and record OpenTelemetry
351+
/// observations.
352+
///
353+
/// This helper mirrors the core behaviour of
354+
/// [`cortexbrain_common::buffer_type::read_perf_buffer`] but adds the OTel
355+
/// instrumentation layer.
356+
///
357+
/// # Loop
358+
///
359+
/// 1. For every CPU buffer call `read_events`.
360+
/// 2. Parse each raw [`BytesMut`] into [`NetworkMetrics`] using an
361+
/// unaligned read (the struct is `#[repr(C, packed)]` and `Pod`).
362+
/// 3. Call [`Metrics::record_network_metrics`].
363+
/// 4. Retain the legacy `tracing::info!` log for human-readable local output.
364+
/// 5. Sleep 100 ms between polls.
365+
///
366+
/// # Safety
367+
///
368+
/// `std::ptr::read_unaligned` is safe here because the eBPF program writes
369+
/// exactly the `NetworkMetrics` layout into the ring buffer and the struct
370+
/// implements [`aya::Pod`].
371+
/// Continuously read [`TimeStampMetrics`] events and record OpenTelemetry
372+
/// observations.
373+
///
374+
/// Counterpart to [`read_network_buffer`] for the `time_stamp_events` map.
375+
376+
pub async fn read_network_metrics(
377+
buffers: &mut [BytesMut],
378+
tot_events: i32,
379+
offset: i32,
380+
exporter: &str,
381+
metrics: Arc<Metrics>,
382+
) {
346383
for i in offset..tot_events {
347384
let vec_bytes = &buffers[i as usize];
348385
if vec_bytes.len() < std::mem::size_of::<NetworkMetrics>() {
@@ -361,6 +398,11 @@ impl BufferType {
361398
if vec_bytes.len() >= std::mem::size_of::<NetworkMetrics>() {
362399
let net_metrics: NetworkMetrics =
363400
unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) };
401+
402+
match exporter {
403+
"otlp" => metrics.record_network_metrics(&net_metrics),
404+
_ => continue, // skip
405+
}
364406
let tgid = net_metrics.tgid;
365407
let comm = String::from_utf8_lossy(&net_metrics.comm);
366408
let ts_us = net_metrics.ts_us;
@@ -389,7 +431,13 @@ impl BufferType {
389431
}
390432
}
391433
#[cfg(feature = "monitoring-structs")]
392-
pub async fn read_timestamp_metrics(buffers: &mut [BytesMut], tot_events: i32, offset: i32) {
434+
pub async fn read_timestamp_metrics(
435+
buffers: &mut [BytesMut],
436+
tot_events: i32,
437+
offset: i32,
438+
exporter: &str,
439+
metrics: Arc<Metrics>,
440+
) {
393441
for i in offset..tot_events {
394442
let vec_bytes = &buffers[i as usize];
395443
if vec_bytes.len() < std::mem::size_of::<TimeStampMetrics>() {
@@ -408,6 +456,12 @@ impl BufferType {
408456
if vec_bytes.len() >= std::mem::size_of::<TimeStampMetrics>() {
409457
let time_stamp_event: TimeStampMetrics =
410458
unsafe { std::ptr::read_unaligned(vec_bytes.as_ptr() as *const _) };
459+
460+
match exporter {
461+
"otlp" => metrics.record_timestamp_metrics(&time_stamp_event),
462+
_ => continue,
463+
}
464+
411465
let delta_us = time_stamp_event.delta_us;
412466
let ts_us = time_stamp_event.ts_us;
413467
let tgid = time_stamp_event.tgid;
@@ -431,6 +485,7 @@ pub async fn read_perf_buffer<T: std::borrow::BorrowMut<aya::maps::MapData>>(
431485
mut array_buffers: Vec<PerfEventArrayBuffer<T>>,
432486
mut buffers: Vec<bytes::BytesMut>,
433487
buffer_type: BufferType,
488+
#[cfg(feature = "monitoring-structs")] metrics: Option<Arc<Metrics>>,
434489
) {
435490
// loop over the buffers
436491
loop {
@@ -469,13 +524,29 @@ pub async fn read_perf_buffer<T: std::borrow::BorrowMut<aya::maps::MapData>>(
469524
}
470525
#[cfg(feature = "monitoring-structs")]
471526
BufferType::NetworkMetrics => {
472-
BufferType::read_network_metrics(&mut buffers, tot_events, offset)
473-
.await
527+
BufferType::read_network_metrics(
528+
&mut buffers,
529+
tot_events,
530+
offset,
531+
"otlp",
532+
metrics
533+
.clone()
534+
.expect("Metrics required for NetworkMetrics"),
535+
)
536+
.await
474537
}
475538
#[cfg(feature = "monitoring-structs")]
476539
BufferType::TimeStampMetrics => {
477-
BufferType::read_timestamp_metrics(&mut buffers, tot_events, offset)
478-
.await
540+
BufferType::read_timestamp_metrics(
541+
&mut buffers,
542+
tot_events,
543+
offset,
544+
"otlp",
545+
metrics
546+
.clone()
547+
.expect("Metric required for TimeStampMetrics"),
548+
)
549+
.await
479550
}
480551
}
481552
}

core/common/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
#[cfg(any(
22
feature = "buffer-reader",
33
feature = "network-structs",
4-
feature = "monitoring-structs"
4+
feature = "monitoring-structs",
55
))]
66
pub mod buffer_type;
77
pub mod constants;
88
pub mod formatters;
99
pub mod logger;
1010
#[cfg(feature = "map-handlers")]
1111
pub mod map_handlers;
12+
#[cfg(feature = "monitoring-structs")]
13+
pub mod otel_metrics;
1214
#[cfg(feature = "program-handlers")]
1315
pub mod program_handlers;

core/common/src/otel_metrics.rs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//! OpenTelemetry metric instruments for eBPF perf-buffer events.
2+
//!
3+
//! This module centralises every [`Meter`]-backed instrument that the
4+
//! `metrics` crate uses to observe raw eBPF events. It provides a single
5+
//! [`Metrics`] handle that is cheap to [`Arc`]-clone and safe to use from
6+
//! multiple asynchronous tasks concurrently.
7+
//!
8+
//! - An [`Arc<Metrics>`] is moved into each Tokio
9+
//! task that reads a perf buffer. All instrument operations are lock-free.
10+
//! - Every observation is tagged with `tgid` and `comm`
11+
//! extracted from the eBPF struct, allowing downstream collectors to group
12+
//! telemetry by process.
13+
14+
use crate::buffer_type::{NetworkMetrics, TimeStampMetrics};
15+
use opentelemetry::KeyValue;
16+
use opentelemetry::metrics::{Counter, Gauge, Histogram, Meter};
17+
pub struct Metrics {
18+
/// Total number of eBPF events processed across all perf buffers.
19+
pub events_total: Counter<u64>,
20+
21+
/// Total number of network-related events produced by the `net_metrics`
22+
/// eBPF map.
23+
pub packets_total: Counter<u64>,
24+
25+
/// Observed socket drop count (`sk_drops`) from the kernel sock struct.
26+
pub sk_drops: Gauge<i64>,
27+
28+
/// Observed socket error count (`sk_err`) from the kernel sock struct.
29+
pub sk_err: Gauge<i64>,
30+
31+
/// Histogram of `delta_us` values supplied by the `time_stamp_events`
32+
/// perf buffer.
33+
pub delta_us: Histogram<u64>,
34+
35+
/// Histogram of `ts_us` values seen in both `net_metrics` and
36+
/// `time_stamp_events`.
37+
pub ts_us: Histogram<u64>,
38+
}
39+
40+
impl Metrics {
41+
/// Initialise all instruments backed by the supplied [`Meter`].
42+
pub fn new(meter: &Meter) -> Self {
43+
// total events
44+
let events_total = meter
45+
.u64_counter("cortexbrain_events_total")
46+
.with_description("Total number of eBPF events processed")
47+
.build();
48+
49+
// total packets
50+
let packets_total = meter
51+
.u64_counter("cortexbrain_packets_total")
52+
.with_description("Total number of network events processed")
53+
.build();
54+
55+
// socket drops
56+
let sk_drops = meter
57+
.i64_gauge("cortexbrain_sk_drops")
58+
.with_description("Socket drop count per event")
59+
.build();
60+
61+
// socket errors
62+
let sk_err = meter
63+
.i64_gauge("cortexbrain_sk_err")
64+
.with_description("Socket error count per event")
65+
.build();
66+
67+
// delta microseconds
68+
let delta_us = meter
69+
.u64_histogram("cortexbrain_delta_us")
70+
.with_description("Distribution of delta_us values from timestamp events")
71+
.build();
72+
73+
// timestamp microseconds grouped
74+
let ts_us = meter
75+
.u64_histogram("cortexbrain_ts_us")
76+
.with_description("Distribution of timestamp values from eBPF events")
77+
.build();
78+
79+
Self {
80+
events_total,
81+
packets_total,
82+
sk_drops,
83+
sk_err,
84+
delta_us,
85+
ts_us,
86+
}
87+
}
88+
89+
/// Record a single [`NetworkMetrics`] event.
90+
///
91+
/// Increments `events_total` and `packets_total`, records `sk_drops` and
92+
/// `sk_err` as gauges, and observes `ts_us` in the timestamp histogram.
93+
///
94+
/// Every observation carries:
95+
///
96+
/// -`tgid` – task group ID.
97+
/// - `comm` – command name (null-terminated bytes converted to a UTF-8
98+
/// string and trimmed).
99+
pub fn record_network_metrics(&self, m: &NetworkMetrics) {
100+
let comm = String::from_utf8_lossy(&m.comm);
101+
let comm_trimmed = comm.trim_end_matches('\0').to_string();
102+
let attrs = &[
103+
KeyValue::new("tgid", m.tgid as i64),
104+
KeyValue::new("comm", comm_trimmed),
105+
];
106+
107+
self.events_total.add(1, attrs);
108+
self.packets_total.add(1, attrs);
109+
self.sk_drops.record(m.sk_drops as i64, attrs);
110+
self.sk_err.record(m.sk_err as i64, attrs);
111+
self.ts_us.record(m.ts_us, attrs);
112+
}
113+
114+
/// Record a single [`TimeStampMetrics`] event.
115+
///
116+
/// Increments `events_total`, and records `delta_us` and `ts_us` in their
117+
/// respective histograms.
118+
///
119+
/// Every observation carries `tgid` and `comm` (see
120+
/// [`record_network_metrics`]).
121+
pub fn record_timestamp_metrics(&self, m: &TimeStampMetrics) {
122+
let comm = String::from_utf8_lossy(&m.comm);
123+
let comm_trimmed = comm.trim_end_matches('\0').to_string();
124+
let attrs = &[
125+
KeyValue::new("tgid", m.tgid as i64),
126+
KeyValue::new("comm", comm_trimmed),
127+
];
128+
129+
self.events_total.add(1, attrs);
130+
self.delta_us.record(m.delta_us, attrs);
131+
self.ts_us.record(m.ts_us, attrs);
132+
}
133+
}

0 commit comments

Comments
 (0)