cuviper / probe-rs

probe: Static probes for Rust
Apache License 2.0
92 stars 9 forks source link

Support conditional probes with semaphores #16

Closed nbaksalyar closed 1 year ago

nbaksalyar commented 2 years ago

Currently, the SystemTap semaphore functionality is not used. It's very useful for probes that require expensive computation.

As suggested in the fixme comment, we can introduce a new macro, probe_enabled!(provider, name) -> bool, which will automatically generate code for a conditional probe. Example use case:

use probe::{probe_enabled, probe};
...
if probe_enabled!(foo, bar) {
  let result = heavy_compute_fn();
  probe!(foo, bar, result);
}

It'd be also good to support a shorthand conditional probe macro, e.g.: probe_if_enabled!(provider, name, arg1, arg2) in which case args will be computed only if the corresponding probe is enabled, e.g.:

probe_if_enabled!(foo, bar, heavy_compute_fn())

This feature should be fairly straightforward to implement. For each semaphore, we'll need to check if there is a corresponding symbol in the .probes section. If it does not exist, we'll need to create it. Then, in the .note.stapsdt section, when we define a probe, we'll just need to refer to this symbol's address when we define a semaphore for a probe. For probe_enabled, it should suffice to read the memory at the corresponding .probes symbol offset.

Or, alternatively, even a simpler approach: define the semaphore var in Rust using #[link_section(".probes")] static FOO_SEMAPHORE = ...;.

nbaksalyar commented 2 years ago

define the semaphore in Rust using [static var]

I can confirm that this approach works perfectly in the following example:

#[no_mangle]
#[link_section = ".probes"]
pub static SEMAPHORE: u16 = 0;

println!("semaphore {}", if ::std::ptr::read_volatile(&SEMAPHORE) == 1 {
    "enabled"
} else {
    "disabled"
});

::core::arch::asm!(concat!(r#"
990:    nop
        .pushsection .note.stapsdt,"?","note"
...
993:    ."#, $size, r#"byte 990b
        ."#, $size, r#"byte _.stapsdt.base
        ."#, $size, r#"byte SEMAPHORE
... "#
)

However, the probe definition needs to be decoupled from the actual probe instruction (990: nop) for this to work properly with arguments evaluation.

cuviper commented 1 year ago

This was released in 0.4.0, and then moved to probe_lazy! in 0.5.0, also with link_section added.

The original example can now look like:

    probe_lazy!(foo, bar, heavy_compute_fn());

You could also write more complicated block expressions there, even filling multiple args:

    {
        let len;
        probe_lazy!(foo, bar, {
            let vec = heavy_compute_fn();
            len = vec.len();
            vec.as_ptr()
        }, len);
    }

That's not be as convenient as a separate probe_enabled!() would be, but I don't have any idea how to make that actually work, unless we use some explicit mangling that's not very "rustic".