Skip to content

Commit 0194aae

Browse files
committed
refactor(metrics): untangle metrics from exporter
The `Exporter` type used to own the userspace `Metrics` object and all other objects would grab the metrics they needed from there. However, because `Exporter` requires the `Bpf` object to take the kernel metrics map from it and the `Bpf` object requires the `Exporter` to get its userspace metrics from, it created a pretty naste chicken and egg problem. With the addition of the replay feature, this became a bit of a mess. To clean up the code a bit, `Metrics` is no longer owned by `Exporter`, instead an independent instance of `Metrics` is created and the bits needed by other components are passed to them, then an `Exporter` is created which registers all metrics from this same object. With this, the new `setup_input` method can return a tuple with `Option<KernelMetrics>` and the receiver end of the generated events, which is cleaner than returning the entire exporter.
1 parent ac43c7f commit 0194aae

5 files changed

Lines changed: 77 additions & 55 deletions

File tree

fact/src/bpf/mod.rs

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,17 @@ pub struct Bpf {
3636
paths_globset: GlobSet,
3737

3838
links: Vec<LsmLink>,
39+
40+
running: watch::Receiver<bool>,
41+
metrics: EventCounter,
3942
}
4043

4144
impl Bpf {
4245
pub fn new(
4346
paths_config: watch::Receiver<Vec<PathBuf>>,
4447
bpf_config: &BpfConfig,
48+
running: watch::Receiver<bool>,
49+
metrics: EventCounter,
4550
) -> anyhow::Result<(Self, mpsc::Receiver<Event>)> {
4651
Bpf::bump_memlock_rlimit()?;
4752

@@ -71,6 +76,8 @@ impl Bpf {
7176
paths_config,
7277
paths_globset: GlobSet::empty(),
7378
links: Vec::new(),
79+
running,
80+
metrics,
7481
};
7582

7683
bpf.load_progs(&btf)?;
@@ -223,12 +230,7 @@ impl Bpf {
223230
}
224231

225232
// Gather events from the ring buffer and print them out.
226-
pub fn start(
227-
mut self,
228-
task_set: &mut JoinSet<anyhow::Result<()>>,
229-
mut running: watch::Receiver<bool>,
230-
event_counter: EventCounter,
231-
) {
233+
pub fn start(mut self, task_set: &mut JoinSet<anyhow::Result<()>>) {
232234
info!("Starting BPF worker...");
233235

234236
task_set.spawn(async move {
@@ -251,19 +253,19 @@ impl Bpf {
251253
// decision there.
252254
if !event.is_monitored_by_parent() &&
253255
event.is_ignored(&self.paths_globset) {
254-
event_counter.dropped();
256+
self.metrics.dropped();
255257
continue;
256258
}
257259
event
258260
},
259261
Err(e) => {
260262
error!("Failed to parse event: '{e}'");
261-
event_counter.dropped();
263+
self.metrics.dropped();
262264
continue;
263265
}
264266
};
265267

266-
event_counter.added();
268+
self.metrics.added();
267269
if self.tx.send(event).await.is_err() {
268270
info!("No BPF consumers left, stopping...");
269271
break;
@@ -274,8 +276,8 @@ impl Bpf {
274276
_ = self.paths_config.changed() => {
275277
self.load_paths().context("Failed to load paths")?;
276278
},
277-
_ = running.changed() => {
278-
if !*running.borrow() {
279+
_ = self.running.changed() => {
280+
if !*self.running.borrow() {
279281
info!("Stopping BPF worker...");
280282
break;
281283
}
@@ -299,7 +301,7 @@ mod bpf_tests {
299301
config::{FactConfig, reloader::Reloader},
300302
event::{EventTestData, process::Process},
301303
host_info,
302-
metrics::exporter::Exporter,
304+
metrics::Metrics,
303305
};
304306

305307
use super::*;
@@ -319,14 +321,18 @@ mod bpf_tests {
319321
let mut config = FactConfig::default();
320322
config.set_paths(paths);
321323
let reloader = Reloader::from(config);
322-
let (mut bpf, mut rx) =
323-
Bpf::new(reloader.paths(), &reloader.config().bpf).expect("Failed to load BPF code");
324+
let metrics = Metrics::new();
324325
let (run_tx, run_rx) = watch::channel(true);
325-
// Create a metrics exporter, but don't start it
326-
let exporter = Exporter::new(Some(bpf.take_metrics().unwrap()));
326+
let (bpf, mut rx) = Bpf::new(
327+
reloader.paths(),
328+
&reloader.config().bpf,
329+
run_rx,
330+
metrics.bpf_worker.clone(),
331+
)
332+
.expect("Failed to load BPF code");
327333
let mut task_set = JoinSet::new();
328334

329-
bpf.start(&mut task_set, run_rx, exporter.metrics.bpf_worker.clone());
335+
bpf.start(&mut task_set);
330336

331337
tokio::time::sleep(Duration::from_millis(500)).await;
332338

fact/src/lib.rs

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ mod replay;
2828
use config::FactConfig;
2929
use pre_flight::pre_flight;
3030

31-
use crate::event::Event;
31+
use crate::{
32+
event::Event,
33+
metrics::{Metrics, kernel_metrics::KernelMetrics},
34+
};
3235

3336
pub fn init_log() -> anyhow::Result<()> {
3437
let log_level = std::env::var("FACT_LOGLEVEL").unwrap_or("info".to_owned());
@@ -85,27 +88,34 @@ pub async fn run(config: FactConfig) -> anyhow::Result<()> {
8588
let config_trigger = reloader.get_trigger();
8689
let mut task_set = JoinSet::new();
8790
let is_replay = reloader.config().replay().is_some();
91+
let metrics_userspace = Metrics::new();
8892

89-
let (exporter, rx) = setup_input(&mut task_set, &reloader, running.subscribe())?;
93+
let (metrics_kernelspace, rx) = setup_input(
94+
&mut task_set,
95+
&reloader,
96+
&metrics_userspace,
97+
running.subscribe(),
98+
)?;
9099
let (rate_limiter, rx) = RateLimiter::new(
91100
rx,
92101
reloader.rate_limit(),
93102
running.subscribe(),
94-
exporter.metrics.rate_limiter.clone(),
103+
metrics_userspace.rate_limiter.clone(),
95104
)?;
96105

97106
output::start(
98107
&mut task_set,
99108
rx,
100109
running.subscribe(),
101-
exporter.metrics.output.clone(),
110+
metrics_userspace.output.clone(),
102111
reloader.grpc(),
103112
reloader.otel(),
104113
reloader.config().json(),
105114
);
106115

107116
rate_limiter.start(&mut task_set);
108-
endpoints::Server::new(exporter.clone(), reloader.endpoint(), running.subscribe()).start();
117+
let exporter = Exporter::new(&metrics_userspace, metrics_kernelspace);
118+
endpoints::Server::new(exporter, reloader.endpoint(), running.subscribe()).start();
109119
reloader.start(running.subscribe());
110120

111121
let mut sigterm = signal(SignalKind::terminate())?;
@@ -140,12 +150,13 @@ pub async fn run(config: FactConfig) -> anyhow::Result<()> {
140150
fn setup_input(
141151
task_set: &mut JoinSet<anyhow::Result<()>>,
142152
reloader: &config::reloader::Reloader,
153+
metrics: &Metrics,
143154
running: watch::Receiver<bool>,
144-
) -> anyhow::Result<(Exporter, mpsc::Receiver<Event>)> {
155+
) -> anyhow::Result<(Option<KernelMetrics>, mpsc::Receiver<Event>)> {
145156
match reloader.config().replay() {
146157
Some(replay_file) => {
147158
let rx = replay::start(task_set, replay_file, running)?;
148-
Ok((Exporter::new(None), rx))
159+
Ok((None, rx))
149160
}
150161
None => {
151162
if !reloader.config().skip_pre_flight() {
@@ -155,7 +166,7 @@ fn setup_input(
155166
debug!("Skipping pre-flight checks");
156167
}
157168

158-
bpf_input(task_set, reloader, running)
169+
bpf_input(task_set, reloader, running, metrics)
159170
}
160171
}
161172
}
@@ -164,20 +175,26 @@ fn bpf_input(
164175
task_set: &mut JoinSet<anyhow::Result<()>>,
165176
reloader: &config::reloader::Reloader,
166177
running: watch::Receiver<bool>,
167-
) -> anyhow::Result<(Exporter, mpsc::Receiver<Event>)> {
168-
let (mut bpf, rx) = Bpf::new(reloader.paths(), &reloader.config().bpf)?;
169-
let exporter = Exporter::new(Some(bpf.take_metrics()?));
178+
metrics_userspace: &Metrics,
179+
) -> anyhow::Result<(Option<KernelMetrics>, mpsc::Receiver<Event>)> {
180+
let (mut bpf, rx) = Bpf::new(
181+
reloader.paths(),
182+
&reloader.config().bpf,
183+
running.clone(),
184+
metrics_userspace.bpf_worker.clone(),
185+
)?;
186+
let metrics_kernelspace = KernelMetrics::new(bpf.take_metrics()?);
170187

171188
let (host_scanner, rx) = HostScanner::new(
172189
&mut bpf,
173190
rx,
174191
reloader.paths(),
175192
reloader.scan_interval(),
176-
running.clone(),
177-
exporter.metrics.host_scanner.clone(),
193+
running,
194+
metrics_userspace.host_scanner.clone(),
178195
)?;
179196

180-
bpf.start(task_set, running, exporter.metrics.bpf_worker.clone());
197+
bpf.start(task_set);
181198
host_scanner.start(task_set);
182-
Ok((exporter, rx))
199+
Ok((Some(metrics_kernelspace), rx))
183200
}

fact/src/metrics/exporter.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,27 @@
11
use std::sync::Arc;
22

3-
use aya::maps::{MapData, PerCpuArray};
43
use log::warn;
54
use prometheus_client::{encoding::text::encode, registry::Registry};
65

7-
use fact_ebpf::metrics_t;
8-
96
use super::{Metrics, kernel_metrics::KernelMetrics};
107

118
#[derive(Clone)]
129
pub struct Exporter {
1310
registry: Arc<Registry>,
14-
pub metrics: Arc<Metrics>,
1511
kernel_metrics: Option<Arc<KernelMetrics>>,
1612
}
1713

1814
impl Exporter {
19-
pub fn new(kernel_metrics: Option<PerCpuArray<MapData, metrics_t>>) -> Self {
15+
pub fn new(metrics_user: &Metrics, metrics_kernel: Option<KernelMetrics>) -> Self {
2016
let mut registry = Registry::with_prefix("stackrox_fact");
21-
let metrics = Arc::new(Metrics::new(&mut registry));
22-
let kernel_metrics =
23-
kernel_metrics.map(|km| Arc::new(KernelMetrics::new(&mut registry, km)));
17+
metrics_user.register(&mut registry);
18+
if let Some(metrics_kernel) = &metrics_kernel {
19+
metrics_kernel.register(&mut registry);
20+
}
21+
let kernel_metrics = metrics_kernel.map(Arc::new);
2422
let registry = Arc::new(registry);
2523
Exporter {
2624
registry,
27-
metrics,
2825
kernel_metrics,
2926
}
3027
}

fact/src/metrics/kernel_metrics.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,13 @@ macro_rules! define_kernel_metrics {
1515
}
1616

1717
impl KernelMetrics {
18-
pub fn new(reg: &mut Registry, kernel_metrics: PerCpuArray<MapData, metrics_t>) -> Self {
18+
pub fn new(kernel_metrics: PerCpuArray<MapData, metrics_t>) -> Self {
1919
$(
2020
let $hook = EventCounter::new(
2121
concat!("kernel_", stringify!($hook), "_events"),
2222
concat!("Events processed by the ", stringify!($hook), " LSM hook"),
2323
&[],
2424
);
25-
$hook.register(reg);
2625
)+
2726

2827
KernelMetrics {
@@ -31,6 +30,10 @@ macro_rules! define_kernel_metrics {
3130
}
3231
}
3332

33+
pub fn register(&self, reg: &mut Registry) {
34+
$(self.$hook.register(reg);)+
35+
}
36+
3437
pub fn collect(&self) -> anyhow::Result<()> {
3538
let metrics = self
3639
.map

fact/src/metrics/mod.rs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use host_scanner::HostScannerMetrics;
88

99
pub mod exporter;
1010
pub mod host_scanner;
11-
mod kernel_metrics;
11+
pub mod kernel_metrics;
1212

1313
#[derive(Clone, Hash, Eq, Debug, PartialEq, EncodeLabelValue, Copy)]
1414
enum LabelValues {
@@ -145,7 +145,7 @@ pub struct Metrics {
145145
}
146146

147147
impl Metrics {
148-
fn new(registry: &mut Registry) -> Self {
148+
pub fn new() -> Self {
149149
let bpf_worker = EventCounter::new(
150150
"bpf_events",
151151
"Events processed by the BPF worker",
@@ -155,26 +155,25 @@ impl Metrics {
155155
LabelValues::Ignored,
156156
],
157157
);
158-
bpf_worker.register(registry);
159158

160159
let rate_limiter = EventCounter::new(
161160
"rate_limiter_events",
162161
"Events processed by the rate limiter",
163162
&[LabelValues::Added, LabelValues::Dropped, LabelValues::Error],
164163
);
165-
rate_limiter.register(registry);
166-
167-
let output_metrics = OutputMetrics::new();
168-
output_metrics.register(registry);
169-
170-
let host_scanner = HostScannerMetrics::new();
171-
host_scanner.register(registry);
172164

173165
Metrics {
174166
bpf_worker,
175167
rate_limiter,
176-
output: output_metrics,
177-
host_scanner,
168+
output: OutputMetrics::new(),
169+
host_scanner: HostScannerMetrics::new(),
178170
}
179171
}
172+
173+
fn register(&self, reg: &mut Registry) {
174+
self.bpf_worker.register(reg);
175+
self.rate_limiter.register(reg);
176+
self.output.register(reg);
177+
self.host_scanner.register(reg);
178+
}
180179
}

0 commit comments

Comments
 (0)