Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ tokio = { workspace = true, features = ["full"]}
[[example]]
name = "http_server"
path = "http_server/main.rs"

[[example]]
name = "span_with_probe"
path = "span_with_probe/main.rs"
30 changes: 30 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,35 @@ cargo run --example http_server -- --config http_server/example_conf.yaml
```


## `span_with_probe`


Demo workload for per-span USDT probes. Runs spans instrumented with the
`span_with_probe!` macro and `span_fn`'s `end_probe = true` option in a loop;
attach with bpftrace to get duration histograms:

```
cargo run --example span_with_probe
sudo bpftrace examples/span_with_probe/span_durations.bt -p <pid>
```

Sample output:

```
Attaching to span end probes, durations in milliseconds...
Hit Ctrl-C to end and print histograms.
^C

@long_task_ms:
[32, 64) 15 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[64, 128) 12 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |

@short_task_ms:
[4, 8) 2 |@@@@@ |
[8, 16) 8 |@@@@@@@@@@@@@@@@@@@@@@@ |
[16, 32) 18 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
```




69 changes: 69 additions & 0 deletions examples/span_with_probe/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! Demo workload for the `span_with_probe!` macro and `span_fn`'s
//! `end_probe = true` option.
//!
//! Instrumented spans with distinct delay ranges run in a loop. Attach
//! with bpftrace to get duration histograms for all spans (from the repo
//! root, so the probe path resolves):
//!
//! ```text
//! bpftrace examples/span_with_probe/span_durations.bt -p <pid>
//! ```
//!
//! No telemetry context is installed: every span is unsampled and tracing is
//! effectively disabled. This is intentional — the USDT probe must fire
//! regardless of span sampling.

use foundations::telemetry::tracing::{span_fn, span_with_probe};
use std::io::Write as _;
use std::time::Duration;

/// Span with a 5-25ms delay range.
async fn short_task(iter: u64) {
let work_fut = async move {
// Simulate variable work so durations show up as a histogram.
tokio::time::sleep(Duration::from_millis(5 + iter % 20)).await;
};

span_with_probe!("example::short_task")
.into_context()
.apply(work_fut)
.await;
}

/// Span with a distinct delay range (50-70ms) so the two can be told apart
/// in a duration histogram.
async fn long_task(iter: u64) {
let work_fut = async move {
tokio::time::sleep(Duration::from_millis(50 + iter % 20)).await;
};

span_with_probe!("example::long_task")
.into_context()
.apply(work_fut)
.await;
}

/// Same probing via `span_fn`'s `end_probe = true` option, with its own
/// delay range (100-120ms).
#[span_fn("example::attr_task", end_probe = true)]
async fn attr_task(iter: u64) {
tokio::time::sleep(Duration::from_millis(100 + iter % 20)).await;
}

#[tokio::main(flavor = "current_thread")]
async fn main() {
println!("pid {}", std::process::id());

let mut iter = 0;
loop {
short_task(iter).await;
long_task(iter).await;
attr_task(iter).await;

print!("\riteration {iter}");
std::io::stdout().flush().unwrap();

iter += 1;
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
29 changes: 29 additions & 0 deletions examples/span_with_probe/span_durations.bt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Duration histograms for the span_with_probe example's spans.
* `attr_task` is instrumented with `span_fn`'s `end_probe = true`
* option, the other two with the `span_with_probe!` macro.
*
* Run from the repository root (the probe path is relative to the cwd):
*
* cargo run --example span_with_probe
* sudo bpftrace examples/span_with_probe/span_durations.bt -p <pid>
*
* Probes receive the span duration in nanoseconds as arg0; recorded here in
* milliseconds.
*/
BEGIN {
printf("Attaching to span end probes, durations in milliseconds...\n");
printf("Hit Ctrl-C to end and print histograms.\n");
}

usdt:target/debug/examples/span_with_probe:foundations:span_end__example__short_task {
@short_task_ms = hist((uint64)arg0 / 1000000);
}

usdt:target/debug/examples/span_with_probe:foundations:span_end__example__long_task {
@long_task_ms = hist((uint64)arg0 / 1000000);
}

usdt:target/debug/examples/span_with_probe:foundations:span_end__example__attr_task {
@attr_task_ms = hist((uint64)arg0 / 1000000);
}
43 changes: 43 additions & 0 deletions foundations-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod info_metric;
mod metrics;
mod settings;
mod span_fn;
mod span_with_probe;
mod with_test_telemetry;

use proc_macro::TokenStream;
Expand All @@ -27,6 +28,48 @@ pub fn span_fn(args: TokenStream, item: TokenStream) -> TokenStream {
span_fn::expand(args, item)
}

/// Like `foundations::telemetry::tracing::span`, plus a per-span USDT probe
/// fired at span end.
///
/// Probes are only emitted on linux/x86_64; on any other platform the macro
/// degrades to a plain `foundations::telemetry::tracing::span` call.
///
/// The probe shows up to tracers as
/// `<binary>:<usdt_provider>:span_end__<sanitized span name>`, where
/// sanitization replaces `::` with `__` and any other non-alphanumeric
/// character with `_`.
///
/// Expands to a dedicated probe semaphore: a `static` in the `.probes` ELF
/// section that the tracer (like bpftrace) increments on attach. When the
/// semaphore is non-zero, the span start timestamp is recorded in the span
/// state (regardless of span sampling), and the per-span `probe_end` function's
/// address is stored alongside it. When the last clone of the span drops,
/// `probe_end` is called with the span duration in nanoseconds, executing the
/// NOP whose address the `stapsdt` ELF note publishes as the
/// `span_end__<sanitized span name>` probe location.
///
/// # Example
///
/// ```rust,ignore
/// use foundations::telemetry::tracing::span_with_probe;
///
/// span_with_probe!("http::client::send_request", usdt_provider = "myapp")
/// .into_context()
/// .apply(do_exchange())
/// .await
/// ```
///
/// Options:
/// - `crate_path = "..."` (defaults to `::foundations`)
/// - `usdt_provider = "..."` (defaults to the `FOUNDATIONS_USDT_PROVIDER`
/// environment variable at compile time — settable per project via `[env]`
/// in `.cargo/config.toml` — or `"foundations"` when unset); must be
/// non-empty and must not contain `:`
#[proc_macro]
pub fn span_with_probe(input: TokenStream) -> TokenStream {
span_with_probe::expand(input)
}

#[proc_macro_attribute]
pub fn with_test_telemetry(args: TokenStream, item: TokenStream) -> TokenStream {
with_test_telemetry::expand(args, item)
Expand Down
Loading