Skip to content
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

Allow eBPF map sizes and general buffer size for profiler to be changed via CLI options #54

Merged
merged 10 commits into from
Aug 13, 2024
Prev Previous commit
Next Next commit
Sane defaults for ProfilerConfig; tests for perf buffer arg
gmarler committed Aug 13, 2024
commit 8570d8a02a24b9d45f35dde7577aa5bc859cea01
105 changes: 84 additions & 21 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -63,20 +63,31 @@ fn sample_freq_in_range(s: &str) -> Result<u16, String> {
Ok(sample_freq as u16)
}

// Return a value if it's a power of 2, otherwise Error
// Clap value_parser() in the form of: Fn(&str) -> Result<T,E>
// Convert a &str into a usize, if possible, and return the result if it's a
// power of 2, otherwise Error
fn value_is_power_of_two(s: &str) -> Result<usize, String> {
gmarler marked this conversation as resolved.
Show resolved Hide resolved
let value: usize = s
.parse()
.map_err(|_| format!("`{s}' isn't a valid usize"))?;
// Now we have a value, test whether it's a power of 2
// NOTE: Neither 0 nor 1 are a power of 2, so rule them out
if (value != 0) && (value != 1) && ((value & (value - 1)) == 0) {
if is_power_of_two(value) {
Ok(value)
} else {
Err(format!("{} is not a power of 2", value))
}
}

fn is_power_of_two(v: usize) -> bool {
// NOTE: Neither 0 nor 1 are a power of 2 (ignoring 2^0 for this use case),
// so rule them out
if (v != 0) && (v != 1) && ((v & (v - 1)) == 0) {
true
} else {
false
}
}

/// Given a non-prime unsigned int, return the prime number that precedes it
/// as well as the prime that succeeds it
fn primes_before_after(non_prime: usize) -> Result<(usize, usize), String> {
@@ -375,7 +386,7 @@ mod tests {
use assert_cmd::Command;
use clap::Parser;
use rand::distributions::{Distribution, Uniform};
use rstest::rstest;
use rstest::{fixture, rstest};
use std::collections::HashSet;

#[test]
@@ -466,40 +477,92 @@ mod tests {
}
}

#[rstest]
fn should_be_powers_of_two() {
let mut test_uint_strings = vec![];
for shift in 1..63 {
// Powers of 2 in usize range
#[fixture]
fn power_of_two_usize() -> Vec<usize> {
let mut test_usizes = vec![];
for shift in 0..63 {
let val: usize = 2 << shift;
test_usizes.push(val);
}
test_usizes
}

// Powers of 2 represented as Strings
#[fixture]
fn power_of_two_strings(power_of_two_usize: Vec<usize>) -> Vec<String> {
let mut test_uint_strings = vec![];
for val in power_of_two_usize {
let val_str = val.to_string();
test_uint_strings.push(val_str);
}
for val_string in test_uint_strings {
assert!(value_is_power_of_two(val_string.as_str()).is_ok())
}
test_uint_strings
}

#[rstest]
fn should_not_be_powers_of_two() {
let mut test_uint_stringset: HashSet<String> = HashSet::new();
// This fixture produces 5 million random results from the range of usize
// integers that are NOT powers of 2
#[fixture]
fn all_but_power_of_two_usize(power_of_two_usize: Vec<usize>) -> Vec<usize> {
let mut test_usize_set: HashSet<usize> = HashSet::new();
let mut test_usize_not_p2: Vec<usize> = vec![];
// usizes that ARE powers of two, for later exclusion
for shift in 0..63 {
let val: usize = 2 << shift;
let val_string = val.to_string();
test_uint_stringset.insert(val_string);
for val in power_of_two_usize {
test_usize_set.insert(val);
}
// Now, for a random sampling of 500000 integers in the range of usize,
// excluding any that are known to be powers of 2
let between = Uniform::from(0..=usize::MAX);
let mut rng = rand::thread_rng();
for _ in 0..500000 {
let usize_int: usize = between.sample(&mut rng);
let usize_int_string = usize_int.to_string();
if test_uint_stringset.contains(&usize_int_string) {
if test_usize_set.contains(&usize_int) {
// We know this is a power of 2, already tested separately, skip
continue;
}
let result = value_is_power_of_two(usize_int_string.as_str());
test_usize_not_p2.push(usize_int);
}
test_usize_not_p2
}

// all_but_power_of_two_usize, but as Strings
#[fixture]
fn all_but_power_of_two_strings(all_but_power_of_two_usize: Vec<usize>) -> Vec<String> {
let mut test_uint_strings: Vec<String> = vec![];
for val in all_but_power_of_two_usize {
let val_str = val.to_string();
test_uint_strings.push(val_str);
}
test_uint_strings
}

// Testing is_power_of_two predicate used by perf_buffer_bytes
// value_parser()
#[rstest]
fn test_should_be_powers_of_two(power_of_two_usize: Vec<usize>) {
for val in power_of_two_usize {
assert!(is_power_of_two(val))
}
}

#[rstest]
fn test_should_not_be_powers_of_two(all_but_power_of_two_usize: Vec<usize>) {
for val in all_but_power_of_two_usize {
assert!(!is_power_of_two(val))
}
}

// Testing the value_parser() implementation for perf_buffer_bytes
#[rstest]
fn args_should_be_powers_of_two(power_of_two_strings: Vec<String>) {
for val_string in power_of_two_strings {
assert!(value_is_power_of_two(val_string.as_str()).is_ok())
}
}

#[rstest]
fn args_should_not_be_powers_of_two(all_but_power_of_two_strings: Vec<String>) {
for non_p2_string in all_but_power_of_two_strings {
let result = value_is_power_of_two(&non_p2_string.as_str());
assert!(result.is_err());
}
}
28 changes: 14 additions & 14 deletions src/profiler.rs
Original file line number Diff line number Diff line change
@@ -300,24 +300,24 @@ pub struct ProfilerConfig {
pub mapsize_rate_limits: u32,
}

// Note that we zero out most of the fields in the default ProfilerConfig here,
// as we're normally going to pass in the defaults from Clap, and we don't want
// Note that we normally pass in the defaults from Clap, and we don't want
// to be in the business of keeping the default values defined in Clap in sync
// with the defaults defined here.
// with the defaults defined here. So these are some defaults that will
// almost always be overridden.
impl Default for ProfilerConfig {
fn default() -> Self {
Self {
libbpf_debug: false,
bpf_logging: false,
duration: Duration::MAX,
sample_freq: 0,
perf_buffer_bytes: 0,
sample_freq: 19,
perf_buffer_bytes: 512 * 1024,
mapsize_info: false,
gmarler marked this conversation as resolved.
Show resolved Hide resolved
mapsize_stacks: 0,
mapsize_aggregated_stacks: 0,
mapsize_unwind_info_chunks: 0,
mapsize_unwind_tables: 0,
mapsize_rate_limits: 0,
mapsize_stacks: 100000,
mapsize_aggregated_stacks: 10000,
mapsize_unwind_info_chunks: 5000,
mapsize_unwind_tables: 65,
mapsize_rate_limits: 5000,
}
}
}
@@ -501,8 +501,8 @@ impl Profiler<'_> {
})
.lost_cb(Self::handle_lost_events)
.build()
// TODO: Instead of unwrap, consume and emit any error - usually
// about buffer bytes not being a power of 2
// TODO: Instead of unwrap, consume and emit any error, with
// .expect() perhaps?
.unwrap();

let _poll_thread = thread::spawn(move || loop {
@@ -525,8 +525,8 @@ impl Profiler<'_> {
warn!("lost {} events from the tracers", lost_count);
})
.build()
// TODO: Instead of unwrap, consume and emit any error - usually
// about buffer bytes not being a power of 2
// TODO: Instead of unwrap, consume and emit any error, with
// .expect() perhaps?
.unwrap();

let _tracers_poll_thread = thread::spawn(move || loop {
16 changes: 5 additions & 11 deletions tests/integration_test.rs
Original file line number Diff line number Diff line change
@@ -101,17 +101,11 @@ fn test_integration() {
));

let profiler_config = ProfilerConfig {
libbpf_debug: bpf_test_debug, // changed from default
bpf_logging: bpf_test_debug, // changed from default
duration: Duration::from_secs(5), // changed from default
sample_freq: 999, // changed from default
perf_buffer_bytes: 512 * 1024,
mapsize_info: false,
mapsize_stacks: 100000,
mapsize_aggregated_stacks: 10000,
mapsize_unwind_info_chunks: 5000,
mapsize_unwind_tables: 65,
mapsize_rate_limits: 5000,
libbpf_debug: bpf_test_debug,
bpf_logging: bpf_test_debug,
duration: Duration::from_secs(5),
sample_freq: 999,
..Default::default()
};
let (_stop_signal_send, stop_signal_receive) = bounded(1);
let mut p = Profiler::new(profiler_config, stop_signal_receive);