-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Detect system info + available BPF features on startup #70
Merged
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
52e340a
Detect system info
patnebe b01a025
Merge branch 'main' into feature-detection
patnebe 3fe5193
Prevent panic
patnebe 6b8313d
cleanup
patnebe abdb93d
Merge branch 'main' into feature-detection
javierhonduco 73d04a0
Merge branch 'main' into feature-detection
javierhonduco 109ff7f
Hook up to main + some cleanup
patnebe 15def31
Merge branch 'main' into feature-detection
patnebe 371fe01
clippy fix
patnebe 72ebbd6
Merge branch 'feature-detection' of github.com:patnebe/lightswitch in…
patnebe 6d93054
Apply review feedback
patnebe e6c6f24
Merge branch 'main' into feature-detection
patnebe 19c0d0b
Hook into sched_switch event for feat detection
patnebe aebaebd
Merge branch 'feature-detection' of github.com:patnebe/lightswitch in…
patnebe 26eef57
fix mount detection path
patnebe 20f7bde
Create top level lightswitch-sys-probe crate
patnebe 4e9d7db
Create top level lightswitch-sys-probe crate
patnebe ebd57a4
Delete lightswitch-sys-probe/src/bpf/features_skel.rs
patnebe e2ce4d5
Merge branch 'feature-detection' of github.com:patnebe/lightswitch in…
patnebe 048ac80
missing newline
patnebe 3f5cd25
Delete lightswitch-sys-probe/src/bpf/features_skel.rs
patnebe aa68f52
nit
patnebe ac3ae35
symlink vmlinux headers + remove ringbuf from min reqs
patnebe 246459e
Merge branch 'feature-detection' of github.com:patnebe/lightswitch in…
patnebe 9149ce5
Rename crate to lightswitch-capabilities
patnebe File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
/target | ||
flame.svg | ||
profile.pb | ||
src/bpf/*_skel.rs | ||
**/bpf/*_skel.rs | ||
.vmtest.log | ||
/result | ||
/result |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
[package] | ||
name = "lightswitch-capabilities" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
anyhow = "1.0.89" | ||
thiserror = "1.0.63" | ||
libbpf-rs = { version = "0.23.3", features = ["static"] } | ||
perf-event-open-sys = "4.0.0" | ||
libc = "0.2.158" | ||
errno = "0.3.9" | ||
procfs = "0.16.0" | ||
tracing = "0.1.40" | ||
nix = { version = "0.29.0", features = ["user"] } | ||
|
||
[build-dependencies] | ||
bindgen = "0.69.4" | ||
libbpf-cargo = "0.23.3" | ||
glob = "0.3.1" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
use glob::glob; | ||
use libbpf_cargo::SkeletonBuilder; | ||
use std::path::Path; | ||
|
||
const FEATURES_BPF_SOURCE: &str = "./src/bpf/features.bpf.c"; | ||
const FEATURES_SKELETON: &str = "./src/bpf/features_skel.rs"; | ||
|
||
fn main() { | ||
// Inform cargo of when to re build | ||
for path in glob("src/bpf/*[hc]").unwrap().flatten() { | ||
println!("cargo:rerun-if-changed={}", path.display()); | ||
} | ||
|
||
let skel = Path::new(FEATURES_SKELETON); | ||
SkeletonBuilder::new() | ||
.source(FEATURES_BPF_SOURCE) | ||
.clang_args([ | ||
"-Wextra", | ||
"-Wall", | ||
"-Werror", | ||
"-Wno-unused-command-line-argument", | ||
"-Wno-unused-parameter", | ||
]) | ||
.build_and_generate(skel) | ||
.expect("run skeleton builder"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// SPDX-License-Identifier: GPL-2.0-only | ||
|
||
#include "vmlinux.h" | ||
|
||
#include <bpf/bpf_core_read.h> | ||
#include <bpf/bpf_helpers.h> | ||
|
||
bool feature_check_done = false; | ||
|
||
bool has_tail_call = false; | ||
bool has_ringbuf = false; | ||
bool has_map_of_maps = false; | ||
bool has_batch_map_operations = false; | ||
|
||
SEC("tracepoint/sched/sched_switch") | ||
int detect_bpf_features(void *ctx) { | ||
has_tail_call = bpf_core_enum_value_exists( | ||
enum bpf_func_id, BPF_FUNC_tail_call); | ||
|
||
has_ringbuf = bpf_core_enum_value_exists( | ||
enum bpf_map_type, BPF_MAP_TYPE_RINGBUF); | ||
|
||
has_map_of_maps = bpf_core_enum_value_exists( | ||
enum bpf_map_type, BPF_MAP_TYPE_HASH_OF_MAPS); | ||
|
||
has_batch_map_operations = bpf_core_enum_value_exists( | ||
enum bpf_cmd, BPF_MAP_LOOKUP_AND_DELETE_BATCH); | ||
|
||
feature_check_done = true; | ||
|
||
return 0; | ||
} | ||
|
||
char LICENSE[] SEC("license") = "Dual MIT/GPL"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub mod features_skel; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../../src/bpf/vmlinux.h |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../../src/bpf/vmlinux_arm64.h |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../../src/bpf/vmlinux_x86.h |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
pub mod bpf; | ||
pub mod system_info; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
use std::fs::read_to_string; | ||
use std::os::raw::c_int; | ||
use std::path::Path; | ||
use std::thread; | ||
use std::time::Duration; | ||
use thiserror::Error; | ||
use tracing::{error, warn}; | ||
|
||
use crate::bpf::features_skel::FeaturesSkelBuilder; | ||
|
||
use anyhow::Result; | ||
use errno::errno; | ||
use libbpf_rs::skel::{OpenSkel, Skel, SkelBuilder}; | ||
use libc::close; | ||
use nix::sys::utsname; | ||
use perf_event_open_sys as sys; | ||
use perf_event_open_sys::bindings::perf_event_attr; | ||
|
||
const PROCFS_PATH: &str = "/proc"; | ||
const TRACEFS_PATH: &str = "/sys/kernel/debug/tracing"; | ||
|
||
#[derive(Debug, Default)] | ||
pub struct BpfFeatures { | ||
pub can_load_trivial_bpf_program: bool, | ||
pub has_ring_buf: bool, | ||
pub has_tail_call: bool, | ||
pub has_map_of_maps: bool, | ||
pub has_batch_map_operations: bool, | ||
} | ||
|
||
#[derive(Debug)] | ||
pub struct SystemInfo { | ||
pub os_release: String, | ||
pub procfs_mount_detected: bool, | ||
pub tracefs_mount_detected: bool, | ||
pub tracepoints_support_detected: bool, | ||
pub software_perfevents_support_detected: bool, | ||
pub available_bpf_features: BpfFeatures, | ||
} | ||
|
||
#[derive(Debug, Error)] | ||
pub enum SystemInfoError { | ||
#[error("File could not be opened {0}")] | ||
ErrorOpeningFile(String), | ||
|
||
#[error("Failed to detect tracefs mount")] | ||
ErrorTracefsNotMounted, | ||
|
||
#[error("Id for trace event {0} could not be read, err={1}")] | ||
ErrorReadingTraceEventId(String, String), | ||
|
||
#[error("BPF feature detection failed, err={0}")] | ||
ErrorDetectingBpfFeatures(String), | ||
} | ||
|
||
struct DroppableFiledescriptor { | ||
fd: i32, | ||
} | ||
|
||
impl Drop for DroppableFiledescriptor { | ||
fn drop(&mut self) { | ||
if { unsafe { close(self.fd) } } != 0 { | ||
warn!("Failed to close file descriptor={}", self.fd); | ||
} | ||
} | ||
} | ||
|
||
fn tracefs_mount_detected() -> bool { | ||
return Path::new(TRACEFS_PATH).exists(); | ||
} | ||
|
||
fn get_trace_sched_event_id(trace_event: &str) -> Result<u32> { | ||
if !tracefs_mount_detected() { | ||
return Err(SystemInfoError::ErrorTracefsNotMounted.into()); | ||
} | ||
|
||
let event_id_path = format!("{}/events/sched/{}/id", TRACEFS_PATH, trace_event); | ||
let path = Path::new(&event_id_path); | ||
if !path.exists() { | ||
return Err(SystemInfoError::ErrorOpeningFile(event_id_path).into()); | ||
} | ||
|
||
read_to_string(path) | ||
.map_err(|err| { | ||
SystemInfoError::ErrorReadingTraceEventId(trace_event.to_string(), err.to_string()) | ||
})? | ||
.trim() | ||
.parse::<u32>() | ||
.map_err(|err| { | ||
SystemInfoError::ErrorReadingTraceEventId(trace_event.to_string(), err.to_string()) | ||
.into() | ||
}) | ||
} | ||
|
||
fn software_perfevents_detected() -> bool { | ||
let mut attrs: perf_event_attr = perf_event_open_sys::bindings::perf_event_attr { | ||
size: std::mem::size_of::<sys::bindings::perf_event_attr>() as u32, | ||
type_: sys::bindings::PERF_TYPE_SOFTWARE, | ||
config: sys::bindings::PERF_COUNT_SW_CPU_CLOCK as u64, | ||
..Default::default() | ||
}; | ||
attrs.__bindgen_anon_1.sample_freq = 0; | ||
attrs.set_disabled(1); | ||
attrs.set_freq(1); | ||
|
||
let fd = DroppableFiledescriptor { | ||
fd: unsafe { | ||
sys::perf_event_open( | ||
&mut attrs, -1, /* pid */ | ||
/* cpu */ 0, -1, /* group_fd */ | ||
0, /* flags */ | ||
) | ||
} as c_int, | ||
}; | ||
|
||
if fd.fd < 0 { | ||
error!("setup_perf_event failed with errno {}", errno()); | ||
return false; | ||
} | ||
true | ||
} | ||
|
||
fn tracepoints_detected() -> bool { | ||
let mut attrs = sys::bindings::perf_event_attr { | ||
size: std::mem::size_of::<sys::bindings::perf_event_attr>() as u32, | ||
type_: sys::bindings::PERF_TYPE_TRACEPOINT, | ||
..sys::bindings::perf_event_attr::default() | ||
}; | ||
|
||
match get_trace_sched_event_id("sched_process_exec") { | ||
Ok(event_id) => attrs.config = event_id as u64, | ||
Err(err) => { | ||
error!("{}", err); | ||
return false; | ||
} | ||
} | ||
|
||
let fd = DroppableFiledescriptor { | ||
fd: unsafe { | ||
sys::perf_event_open( | ||
&mut attrs, -1, /* pid */ | ||
0, /* cpu */ | ||
-1, /* group_fd */ | ||
0, /* flags */ | ||
) as c_int | ||
}, | ||
}; | ||
|
||
fd.fd >= 0 | ||
} | ||
|
||
fn check_bpf_features() -> Result<BpfFeatures> { | ||
let skel_builder = FeaturesSkelBuilder::default(); | ||
let open_skel = match skel_builder.open() { | ||
Ok(open_skel) => open_skel, | ||
Err(err) => return Err(SystemInfoError::ErrorDetectingBpfFeatures(err.to_string()).into()), | ||
}; | ||
let mut bpf_features = match open_skel.load() { | ||
Ok(bpf_features) => bpf_features, | ||
Err(err) => return Err(SystemInfoError::ErrorDetectingBpfFeatures(err.to_string()).into()), | ||
}; | ||
match bpf_features.attach() { | ||
Ok(_) => {} | ||
Err(err) => return Err(SystemInfoError::ErrorDetectingBpfFeatures(err.to_string()).into()), | ||
}; | ||
|
||
thread::sleep(Duration::from_millis(1)); | ||
|
||
let bpf_features_bss = bpf_features.bss(); | ||
if !bpf_features_bss.feature_check_done { | ||
warn!("Failed to detect available bpf features"); | ||
return Ok(BpfFeatures { | ||
can_load_trivial_bpf_program: true, | ||
..BpfFeatures::default() | ||
}); | ||
} | ||
|
||
let features: BpfFeatures = BpfFeatures { | ||
can_load_trivial_bpf_program: true, | ||
has_tail_call: bpf_features_bss.has_tail_call, | ||
has_ring_buf: bpf_features_bss.has_ringbuf, | ||
has_map_of_maps: bpf_features_bss.has_map_of_maps, | ||
has_batch_map_operations: bpf_features_bss.has_batch_map_operations, | ||
}; | ||
|
||
Ok(features) | ||
} | ||
|
||
impl SystemInfo { | ||
pub fn new() -> Result<SystemInfo> { | ||
let available_bpf_features = match check_bpf_features() { | ||
Ok(features) => features, | ||
Err(err) => { | ||
warn!("Failed to detect available BPF features {}", err); | ||
BpfFeatures::default() | ||
} | ||
}; | ||
Ok(SystemInfo { | ||
os_release: utsname::uname()?.release().to_string_lossy().to_string(), | ||
procfs_mount_detected: Path::new(PROCFS_PATH).exists(), | ||
tracefs_mount_detected: tracefs_mount_detected(), | ||
tracepoints_support_detected: tracepoints_detected(), | ||
software_perfevents_support_detected: software_perfevents_detected(), | ||
available_bpf_features, | ||
}) | ||
} | ||
|
||
pub fn has_minimal_requirements(&self) -> bool { | ||
let bpf_features = &self.available_bpf_features; | ||
self.tracefs_mount_detected | ||
&& self.procfs_mount_detected | ||
&& self.software_perfevents_support_detected | ||
&& self.tracepoints_support_detected | ||
&& bpf_features.can_load_trivial_bpf_program | ||
&& bpf_features.has_tail_call | ||
} | ||
} | ||
|
||
// TODO: How can we make this an integration/system test? | ||
// since it depends on the runtime environment. | ||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_get_system_info() { | ||
let result = SystemInfo::new(); | ||
|
||
assert!(result.is_ok()); | ||
|
||
let system_info = result.unwrap(); | ||
assert!(system_info.procfs_mount_detected); | ||
assert!(system_info.tracefs_mount_detected); | ||
assert!(system_info.tracepoints_support_detected); | ||
assert!(system_info.software_perfevents_support_detected); | ||
|
||
let bpf_features = system_info.available_bpf_features; | ||
assert!(bpf_features.can_load_trivial_bpf_program); | ||
assert!(bpf_features.has_ring_buf); | ||
assert!(bpf_features.has_tail_call); | ||
assert!(bpf_features.has_map_of_maps); | ||
assert!(bpf_features.has_batch_map_operations); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's totally fine to leave it here. Personally I don't think the difference between purely no-IO unittests and integration tests matters that much, unless we need to test a binary, in that case we should use the tool level folder instead. The standard in the Rust world is to add integration tests in
test/
so here would go onlightswitch-capabilities/test
.