Skip to content

Commit db9c91f

Browse files
committed
feat(wasi-observe): A WIP WASI Observe host component
Signed-off-by: Caleb Schoepp <[email protected]>
1 parent 2cd5826 commit db9c91f

File tree

25 files changed

+1235
-36
lines changed

25 files changed

+1235
-36
lines changed

Cargo.lock

Lines changed: 134 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ test-components = { path = "tests/test-components" }
9797
test-environment = { workspace = true }
9898
testing-framework = { path = "tests/testing-framework" }
9999
which = "4.2.5"
100+
fake-opentelemetry-collector = "0.19.0"
100101

101102
[build-dependencies]
102103
cargo-target-dep = { git = "https://github.com/fermyon/cargo-target-dep", rev = "482f269eceb7b1a7e8fc618bf8c082dd24979cf1" }

crates/factor-observe/Cargo.toml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
[package]
2+
name = "spin-factor-observe"
3+
version = { workspace = true }
4+
authors = { workspace = true }
5+
edition = { workspace = true }
6+
7+
[dependencies]
8+
anyhow = "1.0"
9+
async-trait = "0.1"
10+
dotenvy = "0.15"
11+
futures-executor = "0.3"
12+
indexmap = "2.2.6"
13+
once_cell = "1"
14+
opentelemetry = { version = "0.22.0", features = [ "metrics", "trace"] }
15+
opentelemetry_sdk = { version = "0.22.1", features = ["rt-tokio"] }
16+
opentelemetry-otlp = { version = "0.15.0", default-features=false, features = ["http-proto", "trace", "http", "reqwest-client", "metrics", "grpc-tonic"] }
17+
pin-project-lite = "0.2"
18+
serde = "1.0.188"
19+
spin-app = { path = "../app" }
20+
spin-core = { path = "../core" }
21+
spin-expressions = { path = "../expressions" }
22+
spin-factors = { path = "../factors" }
23+
spin-telemetry = { path = "../telemetry" }
24+
spin-world = { path = "../world" }
25+
table = { path = "../table" }
26+
thiserror = "1"
27+
tokio = { version = "1", features = ["rt-multi-thread"] }
28+
tracing = "0.1.40"
29+
tracing-opentelemetry = "0.23.0"
30+
vaultrs = "0.6.2"
31+
32+
[dev-dependencies]
33+
toml = "0.5"
34+
35+
[lints]
36+
workspace = true
37+
38+
# TODO(Caleb): Cleanup these dependencies

crates/factor-observe/src/future.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
use pin_project_lite::pin_project;
2+
use std::{
3+
future::Future,
4+
sync::{Arc, RwLock},
5+
};
6+
7+
use crate::State;
8+
9+
pin_project! {
10+
struct Instrumented<F> {
11+
#[pin]
12+
inner: F,
13+
observe_context: ObserveContext,
14+
}
15+
16+
impl<F> PinnedDrop for Instrumented<F> {
17+
fn drop(this: Pin<&mut Self>) {
18+
this.project().observe_context.drop_all();
19+
}
20+
}
21+
}
22+
23+
pub trait FutureExt: Future + Sized {
24+
/// Manage WASI Observe guest spans.
25+
fn manage_wasi_observe_spans(
26+
self,
27+
observe_context: ObserveContext,
28+
) -> impl Future<Output = Self::Output>;
29+
}
30+
31+
impl<F: Future> FutureExt for F {
32+
fn manage_wasi_observe_spans(
33+
self,
34+
observe_context: ObserveContext,
35+
) -> impl Future<Output = Self::Output> {
36+
Instrumented {
37+
inner: self,
38+
observe_context,
39+
}
40+
}
41+
}
42+
43+
impl<F: Future> Future for Instrumented<F> {
44+
type Output = F::Output;
45+
46+
/// Maintains the invariant that all active spans are entered before polling the inner future
47+
/// and exited otherwise. If we don't do this then the timing (among many other things) of the
48+
/// spans becomes wildly incorrect.
49+
fn poll(
50+
self: std::pin::Pin<&mut Self>,
51+
cx: &mut std::task::Context<'_>,
52+
) -> std::task::Poll<Self::Output> {
53+
let this = self.project();
54+
55+
// Enter the active spans before entering the inner poll
56+
{
57+
this.observe_context.state.write().unwrap().enter_all();
58+
}
59+
60+
let ret = this.inner.poll(cx);
61+
62+
// Exit the active spans after exiting the inner poll
63+
{
64+
this.observe_context.state.write().unwrap().exit_all();
65+
}
66+
67+
ret
68+
}
69+
}
70+
71+
/// The context necessary for the observe host component to function.
72+
pub struct ObserveContext {
73+
pub(crate) state: Arc<RwLock<State>>,
74+
}
75+
76+
impl ObserveContext {
77+
fn drop_all(&self) {
78+
self.state.write().unwrap().close_from_back_to(0);
79+
}
80+
}

0 commit comments

Comments
 (0)