aya icon indicating copy to clipboard operation
aya copied to clipboard

aya+ebpf: Implement read+write methods for PerfEventArray

Open TheElectronWill opened this issue 11 months ago β€’ 15 comments

This allows to read and write a PerfEventArray, from userspace and kernel. Thanks to these modifications, one can read perf events from ebpf code!

Usage

  1. open the perf event from userspace
    • Note: this could be done in aya if perf_event_open was public, for now I'm using perf_event_open_sys
  2. put the event's file descriptor into a map PerfEventArray<i32>
  3. load the ebpf program
  4. from ebpf, read the file descriptor to get the perf event's value, and put it in another map PerfEventArray<MyValue> (you can create a struct to carry the info you need, for instance)
  5. from userspace, read the value from the PerfEventArray using a PerfEventArrayBuffer

See the integration test for more details.

What is it for?

This can be useful to read performance counters from the kernel space, a method which I have evaluated in my research work.


This change is Reviewable

TheElectronWill avatar Jul 12 '23 15:07 TheElectronWill

Deploy Preview for aya-rs-docs failed.

Built without sensitive environment variables

Name Link
Latest commit 0aeb379bebde2a7c1b87ec8e0e66713a877daef0
Latest deploy log https://app.netlify.com/sites/aya-rs-docs/deploys/659976617952250008625870

netlify[bot] avatar Jul 12 '23 15:07 netlify[bot]

Could you write an integration test?

Yes, but how do I open the perf event? Do I make the corresponding functions of aya public, or do I add a dependency?

TheElectronWill avatar Jul 26 '23 15:07 TheElectronWill

Sorry, a dependency on what? I think you should make whatever you need public so that your test is representative of real usage.

tamird avatar Jul 26 '23 15:07 tamird

Sorry, a dependency on what? I think you should make whatever you need public so that your test is representative of real usage.

I've used the crate perf_event_open_sys for step 1 (see the PR's description) to test what I've implemented.

I'll make what I need public :)

TheElectronWill avatar Jul 28 '23 04:07 TheElectronWill

@TheElectronWill, this pull request is now in conflict and requires a rebase.

mergify[bot] avatar Sep 14 '23 23:09 mergify[bot]

Okay, I've added integration tests! I've made a user-friendly version of perf_event_open public, for the purpose of testing and to make the feature available to the users without an additional dependency.

I've failed to run the tests on my machine, with a rather weird error

``` warning: error: could not compile `integration-ebpf` (bin "bpf_probe_read") due to 2 previous errors

error: failed to run custom build command for integration-test v0.1.0 (/home/guillaume/Documents/Contrib/aya/test/integration-test) process didn't exit successfully: /home/guillaume/Documents/Contrib/aya/target/debug/build/integration-test-fd7f5096efa9800e/build-script-build (exit status: 101) --- stderr thread 'main' panicked at test/integration-test/build.rs:258:9: assertion left == right failed: cd "[...]/aya/test/integration-ebpf" && CARGO_CFG_BPF_TARGET_ARCH="x86_64" "cargo" "build" "-Z" "build-std=core" "--bins" "--message-format=json" "--release" "--target" "bpfel-unknown-none" "--target-dir" "[...]/aya/target/debug/build/integration-test-8b78b2389a639211/out/integration-ebpf" failed: ExitStatus(unix_wait_status(25856)) left: Some(101) right: Some(0) note: run with RUST_BACKTRACE=1 environment variable to display a backtrace Error: AYA_BUILD_INTEGRATION_BPF="true" "cargo" "build" "--message-format=json" "--package" "integration-test" "--tests" "--profile" "dev" failed: ExitStatus(unix_wait_status(25856))

Running mentioned `cd "..."` command manually reveals the underlying error:

= note: 16:13:28 [ERROR] fatal error: "Inline asm not supported by this streamer because we don't have an asm parser for this target\n" PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace. Stack dump: 0. Running pass 'Function Pass Manager' on module 'bpf_probe_read-8e261d0427f4b668'. 1. Running pass 'BPF Assembly Printer' on function '@test_bpf_probe_read_user_str_bytes'

but I'm stuck here :S Any idea?
</details>

TheElectronWill avatar Oct 30 '23 16:10 TheElectronWill

do the integration tests run for you on main? how are you running them? what versions of rustc and bpf-linker are you using?

tamird avatar Oct 31 '23 16:10 tamird

Solution to my aforementioned problem: update bpf-linker by running cargo install bpf-linker. I've updated the PR to make the new test pass :)

TheElectronWill avatar Oct 31 '23 19:10 TheElectronWill

/// Returns [`MapError::OutOfBounds`] if `index` is out of bounds, [`MapError::SyscallError`]
/// if `bpf_map_update_elem` fails.
pub fn set(&mut self, index: u32, value: i32) -> Result<(), MapError> {

is value a file descriptor? can we use AsFd instead of a raw i32?

It could be any i32, but that would make no sense for a PerfEventArray because only an event file descriptor would work with read. I've used AsFd.


        let mut buf = MaybeUninit::<bpf_perf_event_value>::uninit();
        unsafe {
            // According to the Linux manual, `bpf_perf_event_read_value` is preferred over `bpf_perf_event_read`.

citation?

man bpf-helpers reads:

u64 bpf_perf_event_read(...) Also, be aware that the newer helper bpf_perf_event_read_value() is recommended over bpf_perf_event_read() in general. The latter has some ABI quirks where error and counter value are used as a return code (which is wrong to do since ranges may overlap). This issue is fixed with bpf_perf_event_read_value(), which at the same time provides more features over the bpf_perf_event_read() interface.

long bpf_perf_event_read_value(...) In general, bpf_perf_event_read_value() is recommended over bpf_perf_event_read(), which has some ABI issues and provides fewer functionalities.


    // sleep a little bit, then poll the values from the buffer
    std::thread::sleep(Duration::from_secs(2));

this is more than a little bit. why do we need to sleep so long? can we poll the fd instead?

I've changed the test to wait for buf.readable() to become true. I've also added an async version.


    // read the events and check that the returned data is correct
    let mut events_data: [BytesMut; BUF_PAGE_COUNT] = std::array::from_fn(|_| BytesMut::new());

why does this need more than one of these? i would imagine a single buffer that is reused.

We could use only one, here I wanted to test reading potentially multiple events at once.

TheElectronWill avatar Nov 02 '23 13:11 TheElectronWill

Hey @alessandrod, this pull request changes the Aya Public API and requires your review.

mergify[bot] avatar Nov 02 '23 18:11 mergify[bot]

@TheElectronWill, this pull request is now in conflict and requires a rebase.

mergify[bot] avatar Nov 28 '23 21:11 mergify[bot]

@TheElectronWill, this pull request is now in conflict and requires a rebase.

mergify[bot] avatar Jan 29 '24 20:01 mergify[bot]

My hunch is that we should have two separate types - I don't think it's ever desiderable to call open() and set() on the same map, and having both in the same type seems confusing. I'm not sure what the best names would be, maybe PerfEventByteArray and PerfEventArray? Not sure

I agree with the idea. There's already a PerfEventByteArray at bpf/aya-bpf/src/maps/perf/perf_event_byte_array.rs, but I'm not sure what is its intended usage.

TheElectronWill avatar Jan 30 '24 08:01 TheElectronWill

Hey @alessandrod, this pull request changes the Aya Public API and requires your review.

mergify[bot] avatar Feb 06 '24 12:02 mergify[bot]

@TheElectronWill, this pull request is now in conflict and requires a rebase.

mergify[bot] avatar Feb 06 '24 12:02 mergify[bot]