Struct usdt_impl::UniqueId

source ·
pub struct UniqueId { /* private fields */ }
Expand description

A unique identifier that can be used to correlate multiple USDT probes together.

It’s a common pattern in DTrace scripts to correlate multiple probes. For example, one can time system calls by storing a timestamp on the syscall:::entry probe and then computing the elapsed time in the syscall:::return probe. This requires some way to “match up” these two probes, to ensure that the elapsed time is correctly attributed to a single system call. Doing so requires an identifier. User code may already have an ID appropriate for this use case, but the UniqueId type may be used when one is not already available. These unique IDs can be used to correlate multiple probes occurring in a section or span of user code.

A probe function may accept a UniqueId, which appears in a D as a u64. The value is guaranteed to be unique, even if multiple threads run the same traced section of code. (See the [notes] for caveats.) The value may be shared between threads by calling clone() on a constructed span – in this case, the cloned object shares the same value, so that a traced span running in multiple threads (or asynchronous tasks) shares the same identifier.

A UniqueId is very cheap to construct. The internal value is “materialized” in two situations:

  • When an enabled probe fires
  • When the value is cloned (e.g., for sharing with another thread)

This minimizes the disabled-probe effect, but still allows sharing a consistent ID in the case of multithreaded work.

§Example

#[usdt::provider]
mod with_id {
    fn work_started(_: &usdt::UniqueId) {}
    fn halfway_there(_: &usdt::UniqueId, msg: &str) {}
    fn work_completed(_: &usdt::UniqueId, result: u64) {}
}

// Constructing an ID is very cheap.
let id = usdt::UniqueId::new();

// The ID will only be materialized if this probe is enabled.
with_id_work_started!(|| &id);

// If the ID has been materialized above, this simply clone the internal value. If the ID has
// _not_ yet been materialized, say because the `work_started` probe was not enabled, this will
// do so now.
let id2 = id.clone();
let handle = std::thread::spawn(move || {
    for i in 0..10 {
        // Do our work.
        if i == 5 {
            with_id_halfway_there!(|| (&id2, "work is half completed"));
        }
    }
    10
});

let result = handle.join().unwrap();
with_id_work_completed!(|| (&id, result));

Note that this type is not Sync, which means we cannot accidentally share the value between threads. The only way to track the same ID in work spanning threads is to first clone the type, which materializes the internal value. For example, this will fail to compile:

#[usdt::provider]
mod with_id {
    fn work_started(_: &usdt::UniqueId) {}
    fn halfway_there(_: &usdt::UniqueId, msg: &str) {}
    fn work_completed(_: &usdt::UniqueId, result: u64) {}
}

let id = usdt::UniqueId::new();
with_id_work_started!(|| &id);
let handle = std::thread::spawn(move || {
    for i in 0..10 {
        // Do our work.
        if i == 5 {
            // Note that we're using `id`, not a clone as the previous example.
            with_id_halfway_there!(|| (&id, "work is half completed"));
        }
    }
    10
});
let result = handle.join().unwrap();
with_id_work_completed!(|| (&id, result));

§Notes

In any practical situation, the generated ID is unique. Its value is assigned on the basis of the thread that creates the UniqueId object, plus a monotonic thread-local counter. However, the counter is 32 bits, and so wraps around after about 4 billion unique values. So theoretically, multiple UniqueIds could manifest as the same value to DTrace, if they are exceptionally long-lived or generated very often.

Implementations§

source§

impl UniqueId

source

pub const fn new() -> Self

Construct a new identifier.

A UniqueId is cheap to create, and is not materialized into an actual value until it’s needed, either by a probe function or during cloneing to share the value between threads.

Trait Implementations§

source§

impl Clone for UniqueId

source§

fn clone(&self) -> Self

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for UniqueId

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> ToOwned for T
where T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.