forked from open-telemetry/opentelemetry-rust
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
opentelemetry-contrib api enhancements with new_span benchmark (open-…
- Loading branch information
Showing
10 changed files
with
536 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
/.vscode/ | ||
/target/ | ||
*/target/ | ||
**/*.rs.bk | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; | ||
use futures_util::future::BoxFuture; | ||
use opentelemetry::{ | ||
global::BoxedTracer, | ||
trace::{ | ||
mark_span_as_active, noop::NoopTracer, SpanBuilder, SpanContext, SpanId, | ||
TraceContextExt as _, TraceFlags, TraceId, TraceState, Tracer as _, TracerProvider as _, | ||
}, | ||
Context, ContextGuard, | ||
}; | ||
use opentelemetry_contrib::trace::{ | ||
new_span_if_parent_sampled, new_span_if_recording, TracerSource, | ||
}; | ||
use opentelemetry_sdk::{ | ||
export::trace::{ExportResult, SpanData, SpanExporter}, | ||
trace::{config, Sampler, TracerProvider}, | ||
}; | ||
#[cfg(not(target_os = "windows"))] | ||
use pprof::criterion::{Output, PProfProfiler}; | ||
use std::fmt::Display; | ||
|
||
fn criterion_benchmark(c: &mut Criterion) { | ||
let mut group = c.benchmark_group("new_span"); | ||
group.throughput(Throughput::Elements(1)); | ||
for env in [ | ||
Environment::InContext, | ||
Environment::NoContext, | ||
Environment::NoSdk, | ||
] { | ||
let (_provider, tracer, _guard) = env.setup(); | ||
|
||
for api in [Api::Alt, Api::Spec] { | ||
let param = format!("{env}/{api}"); | ||
group.bench_function( | ||
BenchmarkId::new("if_parent_sampled", param.clone()), | ||
// m2max, in-cx/alt: 530ns | ||
// m2max, no-cx/alt: 5.9ns | ||
// m2max, no-sdk/alt: 5.9ns | ||
// m2max, in-cx/spec: 505ns | ||
// m2max, no-cx/spec: 255ns | ||
// m2max, no-sdk/spec: 170ns | ||
|b| match api { | ||
Api::Alt => b.iter(|| { | ||
new_span_if_parent_sampled( | ||
|| SpanBuilder::from_name("new_span"), | ||
TracerSource::borrowed(&tracer), | ||
) | ||
.map(|cx| cx.attach()) | ||
}), | ||
Api::Spec => b.iter(|| mark_span_as_active(tracer.start("new_span"))), | ||
}, | ||
); | ||
group.bench_function( | ||
BenchmarkId::new("if_recording", param.clone()), | ||
// m2max, in-cx/alt: 8ns | ||
// m2max, no-cx/alt: 5.9ns | ||
// m2max, no-sdk/alt: 5.9ns | ||
// m2max, in-cx/spec: 31ns | ||
// m2max, no-cx/spec: 5.8ns | ||
// m2max, no-sdk/spec: 5.7ns | ||
|b| match api { | ||
Api::Alt => b.iter(|| { | ||
new_span_if_recording( | ||
|| SpanBuilder::from_name("new_span"), | ||
TracerSource::borrowed(&tracer), | ||
) | ||
.map(|cx| cx.attach()) | ||
}), | ||
Api::Spec => b.iter(|| { | ||
Context::current() | ||
.span() | ||
.is_recording() | ||
.then(|| mark_span_as_active(tracer.start("new_span"))) | ||
}), | ||
}, | ||
); | ||
} | ||
} | ||
} | ||
|
||
#[derive(Copy, Clone)] | ||
enum Api { | ||
/// An alternative way which may be faster than what the spec recommends. | ||
Alt, | ||
/// The recommended way as proposed by the current opentelemetry specification. | ||
Spec, | ||
} | ||
|
||
impl Api { | ||
const fn as_str(self) -> &'static str { | ||
match self { | ||
Api::Alt => "alt", | ||
Api::Spec => "spec", | ||
} | ||
} | ||
} | ||
|
||
impl Display for Api { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!(f, "{}", self.as_str()) | ||
} | ||
} | ||
|
||
#[derive(Copy, Clone)] | ||
enum Environment { | ||
/// There is an active span being sampled in the current context. | ||
InContext, | ||
/// There is no span in context (or there is not context). | ||
NoContext, | ||
/// An SDK has not been configured, so instrumentation should be noop. | ||
NoSdk, | ||
} | ||
|
||
impl Environment { | ||
const fn as_str(self) -> &'static str { | ||
match self { | ||
Environment::InContext => "in-cx", | ||
Environment::NoContext => "no-cx", | ||
Environment::NoSdk => "no-sdk", | ||
} | ||
} | ||
|
||
fn setup(&self) -> (Option<TracerProvider>, BoxedTracer, Option<ContextGuard>) { | ||
match self { | ||
Environment::InContext => { | ||
let guard = Context::current() | ||
.with_remote_span_context(SpanContext::new( | ||
TraceId::from(0x09251969), | ||
SpanId::from(0x08171969), | ||
TraceFlags::SAMPLED, | ||
true, | ||
TraceState::default(), | ||
)) | ||
.attach(); | ||
let (provider, tracer) = parent_sampled_tracer(Sampler::AlwaysOff); | ||
(Some(provider), tracer, Some(guard)) | ||
} | ||
Environment::NoContext => { | ||
let (provider, tracer) = parent_sampled_tracer(Sampler::AlwaysOff); | ||
(Some(provider), tracer, None) | ||
} | ||
Environment::NoSdk => (None, BoxedTracer::new(Box::new(NoopTracer::new())), None), | ||
} | ||
} | ||
} | ||
|
||
impl Display for Environment { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!(f, "{}", self.as_str()) | ||
} | ||
} | ||
|
||
fn parent_sampled_tracer(inner_sampler: Sampler) -> (TracerProvider, BoxedTracer) { | ||
let provider = TracerProvider::builder() | ||
.with_config(config().with_sampler(Sampler::ParentBased(Box::new(inner_sampler)))) | ||
.with_simple_exporter(NoopExporter) | ||
.build(); | ||
let tracer = provider.tracer(module_path!()); | ||
(provider, BoxedTracer::new(Box::new(tracer))) | ||
} | ||
|
||
#[derive(Debug)] | ||
struct NoopExporter; | ||
|
||
impl SpanExporter for NoopExporter { | ||
fn export(&mut self, _spans: Vec<SpanData>) -> BoxFuture<'static, ExportResult> { | ||
Box::pin(futures_util::future::ready(Ok(()))) | ||
} | ||
} | ||
|
||
#[cfg(not(target_os = "windows"))] | ||
criterion_group! { | ||
name = benches; | ||
config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); | ||
targets = criterion_benchmark | ||
} | ||
#[cfg(target_os = "windows")] | ||
criterion_group! { | ||
name = benches; | ||
config = Criterion::default(); | ||
targets = criterion_benchmark | ||
} | ||
criterion_main!(benches); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
use super::TracerSource; | ||
use opentelemetry::{ | ||
trace::{SpanBuilder, TraceContextExt as _, Tracer as _}, | ||
Context, | ||
}; | ||
use std::{ | ||
fmt::{Debug, Formatter}, | ||
ops::{Deref, DerefMut}, | ||
}; | ||
|
||
/// Lazily creates a new span only if the current context has an active span, | ||
/// which will used as the new span's parent. | ||
/// | ||
/// This is useful for instrumenting library crates whose activities would be | ||
/// undesirable to see as root spans, by themselves, outside of any application | ||
/// context. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use opentelemetry::trace::{SpanBuilder}; | ||
/// use opentelemetry_contrib::trace::{new_span_if_parent_sampled, TracerSource}; | ||
/// | ||
/// fn my_lib_fn() { | ||
/// let _guard = new_span_if_parent_sampled( | ||
/// || SpanBuilder::from_name("my span"), | ||
/// TracerSource::lazy(&|| opentelemetry::global::tracer(module_path!())), | ||
/// ) | ||
/// .map(|cx| cx.attach()); | ||
/// } | ||
/// ``` | ||
pub fn new_span_if_parent_sampled( | ||
builder_fn: impl Fn() -> SpanBuilder, | ||
tracer: TracerSource<'_>, | ||
) -> Option<Context> { | ||
Context::map_current(|current| { | ||
current.span().span_context().is_sampled().then(|| { | ||
let builder = builder_fn(); | ||
let span = tracer.get().build_with_context(builder, current); | ||
current.with_span(span) | ||
}) | ||
}) | ||
} | ||
|
||
/// Lazily creates a new span only if the current context has a recording span, | ||
/// which will used as the new span's parent. | ||
/// | ||
/// This is useful for instrumenting library crates whose activities would be | ||
/// undesirable to see as root spans, by themselves, outside of any application | ||
/// context. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use opentelemetry::trace::{SpanBuilder}; | ||
/// use opentelemetry_contrib::trace::{new_span_if_recording, TracerSource}; | ||
/// | ||
/// fn my_lib_fn() { | ||
/// let _guard = new_span_if_recording( | ||
/// || SpanBuilder::from_name("my span"), | ||
/// TracerSource::lazy(&|| opentelemetry::global::tracer(module_path!())), | ||
/// ) | ||
/// .map(|cx| cx.attach()); | ||
/// } | ||
/// ``` | ||
pub fn new_span_if_recording( | ||
builder_fn: impl Fn() -> SpanBuilder, | ||
tracer: TracerSource<'_>, | ||
) -> Option<Context> { | ||
Context::map_current(|current| { | ||
current.span().is_recording().then(|| { | ||
let builder = builder_fn(); | ||
let span = tracer.get().build_with_context(builder, current); | ||
current.with_span(span) | ||
}) | ||
}) | ||
} | ||
|
||
/// Carries anything with an optional `opentelemetry::Context`. | ||
/// | ||
/// A `Contextualized<T>` is a smart pointer which owns and instance of `T` and | ||
/// dereferences to it automatically. The instance of `T` and its associated | ||
/// optional `Context` can be reacquired using the `Into` trait for the associated | ||
/// tuple type. | ||
/// | ||
/// This type is mostly useful when sending `T`'s through channels with logical | ||
/// context propagation. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use opentelemetry::trace::{SpanBuilder, TraceContextExt as _}; | ||
/// use opentelemetry_contrib::trace::{new_span_if_parent_sampled, Contextualized, TracerSource}; | ||
/// enum Message{Command}; | ||
/// let (tx, rx) = std::sync::mpsc::channel(); | ||
/// | ||
/// let cx = new_span_if_parent_sampled( | ||
/// || SpanBuilder::from_name("my command"), | ||
/// TracerSource::lazy(&|| opentelemetry::global::tracer(module_path!())), | ||
/// ); | ||
/// tx.send(Contextualized::new(Message::Command, cx)); | ||
/// | ||
/// let msg = rx.recv().unwrap(); | ||
/// let (msg, cx) = msg.into_inner(); | ||
/// let _guard = cx.filter(|cx| cx.has_active_span()).map(|cx| { | ||
/// cx.span().add_event("command received", vec![]); | ||
/// cx.attach() | ||
/// }); | ||
/// ``` | ||
pub struct Contextualized<T>(T, Option<Context>); | ||
|
||
impl<T> Contextualized<T> { | ||
/// Creates a new instance using the specified value and optional context. | ||
pub fn new(value: T, cx: Option<Context>) -> Self { | ||
Self(value, cx) | ||
} | ||
|
||
/// Creates a new instance using the specified value and current context if | ||
/// it has an active span. | ||
pub fn pass_thru(value: T) -> Self { | ||
Self::new( | ||
value, | ||
Context::map_current(|current| current.has_active_span().then(|| current.clone())), | ||
) | ||
} | ||
|
||
/// Convert self into its constituent parts, returning a tuple. | ||
pub fn into_inner(self) -> (T, Option<Context>) { | ||
(self.0, self.1) | ||
} | ||
} | ||
|
||
impl<T: Clone> Clone for Contextualized<T> { | ||
fn clone(&self) -> Self { | ||
Self(self.0.clone(), self.1.clone()) | ||
} | ||
} | ||
|
||
impl<T: Debug> Debug for Contextualized<T> { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | ||
f.debug_tuple("Contextualized") | ||
.field(&self.0) | ||
.field(&self.1) | ||
.finish() | ||
} | ||
} | ||
|
||
impl<T> Deref for Contextualized<T> { | ||
type Target = T; | ||
|
||
fn deref(&self) -> &Self::Target { | ||
&self.0 | ||
} | ||
} | ||
|
||
impl<T> DerefMut for Contextualized<T> { | ||
fn deref_mut(&mut self) -> &mut Self::Target { | ||
&mut self.0 | ||
} | ||
} |
Oops, something went wrong.