Skip to content

Commit 251b795

Browse files
committed
Documentation
Signed-off-by: Caleb Schoepp <caleb.schoepp@fermyon.com>
1 parent 9ba58e7 commit 251b795

File tree

2 files changed

+51
-22
lines changed

2 files changed

+51
-22
lines changed

crates/factor-observe/src/lib.rs

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ impl Factor for ObserveFactor {
3838
&self,
3939
ctx: spin_factors::PrepareContext<T, Self>,
4040
) -> anyhow::Result<Self::InstanceBuilder> {
41-
// TODO(Lann): Is it fine that we're using BoxedTracer and BoxedSpan? Are there perf tradeoffs?
42-
// TODO(Lann): Would it make sense to put the tracer on the app state and just hold a reference to it in the instance state?
4341
let tracer = global::tracer(ctx.app_component().app.id().to_string());
4442
Ok(InstanceState {
4543
state: Arc::new(RwLock::new(State {
@@ -73,11 +71,10 @@ pub(crate) struct State {
7371
/// A resource table that holds the guest spans.
7472
pub(crate) guest_spans: table::Table<GuestSpan>,
7573

76-
/// A LIFO stack of guest spans that are currently active.
74+
/// A stack of resource ids for all the active guest spans. The topmost span is the active span.
7775
///
78-
/// Only a reference ID to the guest span is held here. The actual guest span must be looked up
79-
/// in the `guest_spans` table using the reference ID.
80-
/// TODO(Caleb): Fix comment
76+
/// When a guest span is ended it is removed from this stack (regardless of whether is the
77+
/// active span) and all other spans are shifted back to retain relative order.
8178
pub(crate) active_spans: IndexSet<u32>,
8279

8380
/// Id of the last span emitted from within the host before entering the guest.
@@ -93,13 +90,17 @@ pub struct GuestSpan {
9390
pub inner: opentelemetry::global::BoxedSpan,
9491
}
9592

96-
/// TODO(Caleb): comment
93+
/// Manages access to the ObserveFactor state for the purpose of maintaining proper span
94+
/// parent/child relationships when WASI Observe spans are being created.
9795
pub struct ObserveContext {
9896
pub(crate) state: Option<Arc<RwLock<State>>>,
9997
}
10098

10199
impl ObserveContext {
102-
/// TODO(Caleb): Comment
100+
/// Creates an [`ObserveContext`] from a [`PrepareContext`].
101+
///
102+
/// If [`RuntimeFactors`] does not contain an [`ObserveFactor`], then calling
103+
/// [`ObserveContext::reparent_tracing_span`] will be a no-op.
103104
pub fn from_prepare_context<T: RuntimeFactors, F: Factor>(
104105
prepare_context: &mut PrepareContext<T, F>,
105106
) -> anyhow::Result<Self> {
@@ -111,21 +112,45 @@ impl ObserveContext {
111112
Ok(Self { state })
112113
}
113114

114-
/// TODO(Caleb): comment
115-
/// Make sure to mention this should only be called from an instrumented function in a factor.
116-
/// Make sure this is called before any awaits
115+
/// Reparents the current [tracing] span to be a child of the last active guest span.
116+
///
117+
/// The observe factor enables guests to emit spans that should be part of the same trace as the
118+
/// host is producing for a request. Below is an example trace. A request is made to an app, a
119+
/// guest span is created and then the host is re-entered to fetch a key value.
120+
///
121+
/// ```text
122+
/// | GET /... _________________________________|
123+
/// | execute_wasm_component foo ___________|
124+
/// | my_guest_span ___________________|
125+
/// | spin_key_value.get |
126+
/// ```
127+
///
128+
/// Setting the guest spans parent as the host is trivially done. However, the more difficult
129+
/// task is having the host factor spans be children of the guest span.
130+
/// [`ObserveContext::reparent_tracing_span`] handles this by reparenting the current span to be
131+
/// a child of the last active guest span (which is tracked internally in the observe factor).
132+
///
133+
/// Note that if the observe factor is not in your [`RuntimeFactors`] than this is effectively a
134+
/// no-op.
135+
///
136+
/// This MUST only be called from a factor host implementation function that is instrumented.
137+
///
138+
/// This MUST be called at the very start of the function before any awaits.
117139
pub fn reparent_tracing_span(&self) {
118-
// TODO(Caleb): Refactor to be similar to start
140+
// If state is None then we want to return early b/c the factor doesn't depend on the
141+
// Observe factor and therefore there is nothing to do
119142
let state = if let Some(state) = self.state.as_ref() {
120143
state.read().unwrap()
121144
} else {
122145
return;
123146
};
124147

148+
// If there are no active guest spans then there is nothing to do
125149
if state.active_spans.is_empty() {
126150
return;
127151
}
128152

153+
// Ensure that we are not reparenting the original host span
129154
if let Some(original_host_span_id) = state.original_host_span_id {
130155
if tracing::Span::current()
131156
.context()
@@ -134,19 +159,20 @@ impl ObserveContext {
134159
.span_id()
135160
.eq(&original_host_span_id)
136161
{
137-
panic!("TODO(Caleb): This should not happen")
162+
// TODO(Caleb): Figure out why this occurs under load
163+
panic!("The original host span was incorrectly reparented. Likely `reparent_tracing_span` was called in an incorrect location.")
138164
}
139165
}
140166

141-
let parent_context = Context::new().with_remote_span_context(
142-
state
143-
.guest_spans
144-
.get(*state.active_spans.last().unwrap())
145-
.unwrap()
146-
.inner
147-
.span_context()
148-
.clone(),
149-
);
167+
// Now reparent the current span to the last active guest span
168+
let span_context = state
169+
.guest_spans
170+
.get(*state.active_spans.last().unwrap())
171+
.unwrap()
172+
.inner
173+
.span_context()
174+
.clone();
175+
let parent_context = Context::new().with_remote_span_context(span_context);
150176
tracing::Span::current().set_parent(parent_context);
151177
}
152178
}

wit/deps/observe/world.wit

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ package wasi:observe@0.2.0-draft;
33
world imports {
44
import tracer;
55
}
6+
7+
// TODO(Reviewer): Should we make this an experimental wasi package or a Spin package
8+
// TODO(Reviewer): Would adding a metrics interface to this in a future version be a breaking change?

0 commit comments

Comments
 (0)