Skip to content

Commit

Permalink
doc: add component model integration guide
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisdickinson committed Oct 10, 2023
1 parent b51beda commit 404b551
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 18 deletions.
2 changes: 2 additions & 0 deletions proto/opentelemetry/proto/trace/v1/trace.proto
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,12 @@ message Span {
// attributes is a collection of key/value pairs. Note, global attributes
// like server name can be set using the resource API. Examples of attributes:
//
// ```text
// "/http/user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"
// "/http/server_latency": 300
// "example.com/myattribute": true
// "example.com/score": 10.239
// ```
//
// The OpenTelemetry API specification further restricts the allowed value types:
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/common/README.md#attribute
Expand Down
8 changes: 3 additions & 5 deletions rust/examples/otel-stdout-components.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use dylibso_observe_sdk::{adapter::otelstdout::OtelStdoutAdapter, context::component::{ObserveSdk, ObserveSdkView}};

use wasmtime::component::Val;
use wasmtime_wasi::preview2::{WasiView, WasiCtx, Table};

struct State {
Expand Down Expand Up @@ -57,9 +56,6 @@ pub async fn main() -> anyhow::Result<()> {
let mut linker = wasmtime::component::Linker::new(&engine);

let adapter = OtelStdoutAdapter::create();
// Provide the observability functions to the `Linker` to be made available
// to the instrumented guest code. These are safe to add and are a no-op
// if guest code is uninstrumented.
let observe_sdk = adapter.build_observe_sdk(&data, Default::default())?;

let state = State {
Expand All @@ -75,7 +71,9 @@ pub async fn main() -> anyhow::Result<()> {
// get the function and run it, the events pop into the queue as the function is running
let (cmd, _) = wasmtime_wasi::preview2::command::Command::instantiate_async(&mut store, &component, &linker).await?;
let run = cmd.wasi_cli_run();
run.call_run(&mut store).await?;
if let Err(()) = run.call_run(&mut store).await? {
println!("encountered error");
};

let state = store.into_data();
state.observe_sdk.shutdown().await?;
Expand Down
1 change: 1 addition & 0 deletions rust/src/adapter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ impl AdapterHandle {
}

#[cfg(feature = "component-model")]
/// Given a slice representing a Wasm component and [`Options`], create an [`ObserveSdk`].
pub fn build_observe_sdk(&self, data: &[u8], options: Options) -> Result<ObserveSdk> {
let (ctx, collector, collector_rx) = InstrumentationContext::new(options);
let wasm_instr_info = WasmInstrInfo::new(data)?;
Expand Down
145 changes: 132 additions & 13 deletions rust/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,14 +529,105 @@ pub fn add_to_linker<T: 'static>(

#[cfg(feature = "component-model")]
pub mod component {
//! # Component Model Observability
//!
//! Available via `feature = "component-model"`.
//!
//! This module exposes host observability capabilities to the component model. By using this
//! module, hosts can provide support for guest components which depend on the
//! `dylibso:observe/api` and `dylibso:observe/instrument` WIT interfaces.
//!
//! ## Integrating
//!
//! Like Wasmtime's [`wasmtime_wasi::preview2`] module, Observability support involves three steps:
//!
//! 1. Adding an [`ObserveSdk`] member to your host's [`wasmtime::Store`] state struct.
//! 2. Providing access to that member by implementing [`ObserveSdkView`] for your host's state
//! struct.
//! 3. Adding the host bindings to the [`wasmtime::component::Linker`].
//!
//! ```rust
//! use dylibso_observe_sdk::context::component::{ ObserveSdk, ObserveSdkView };
//!
//! struct MyState {
//! // Step 1: add a member to your state...
//! sdk: ObserveSdk,
//! }
//!
//! // Step 2: implement the view trait for your state.
//! impl ObserveSdkView for MyState {
//! fn sdk_mut(&mut self) -> &mut ObserveSdk {
//! &mut self.sdk
//! }
//! }
//! ```
//!
//! Once you've completed step 1 and 2, you can add host bindings like so:
//!
//! ```no_run
//! # use dylibso_observe_sdk::context::component::{ ObserveSdk, ObserveSdkView };
//! # struct MyState {
//! # sdk: ObserveSdk,
//! # }
//! # impl ObserveSdkView for MyState {
//! # fn sdk_mut(&mut self) -> &mut ObserveSdk {
//! # &mut self.sdk
//! # }
//! # }
//! # #[tokio::main]
//! # async fn main() -> anyhow::Result<()> {
//! use dylibso_observe_sdk::adapter::otelstdout::OtelStdoutAdapter;
//!
//! // (Setup: Read a Wasm module from stdin.)
//! let args: Vec<_> = std::env::args().skip(1).collect();
//! let wasm_data = std::fs::read(&args[0])?;
//! let mut config = wasmtime::Config::new();
//!
//! config.async_support(true);
//! config.wasm_component_model(true);
//! let engine = wasmtime::Engine::new(&config)?;
//! let component = wasmtime::component::Component::new(&engine, &wasm_data)?;
//! let mut linker = wasmtime::component::Linker::new(&engine);
//!
//! // All adapters have component support, OtelStdoutAdapter just happens to be
//! // easiest to use as an example:
//! let adapter = OtelStdoutAdapter::create();
//!
//! // Use the adapter to create observe_sdk bindings which you can then pass to your state.
//! let observe_sdk = adapter.build_observe_sdk(&wasm_data, Default::default())?;
//!
//! // Create your state and wrap it in a wasmtime::Store.
//! let state = MyState {
//! sdk: observe_sdk
//! };
//! let mut store = wasmtime::Store::new(&engine, state);
//!
//! // ...Then add it to the linker.
//! dylibso_observe_sdk::context::component::add_to_linker(&mut linker)?;
//!
//! # let (cmd, _) = wasmtime_wasi::preview2::command::Command::instantiate_async(&mut store, &component, &linker).await?;
//! # let run = cmd.wasi_cli_run();
//! // Once you're done with wasm, call `shutdown()`, which shuts down the collector associated
//! // with the Observe SDK.
//! let state = store.into_data();
//! state.sdk.shutdown().await?;
//! # Ok(())
//! # }
//! ```
//!
//! To see an example integrating both Wasi preview 2 and the Observe SDK, see
//! `rust/examples/otel-stdout-components.rs` in the [observe sdk
//! repo](https://github.com/dylibso/observe-sdk/).
use crate::adapter::TraceContext;

use super::*;
use wasmtime::component::Linker;

/// Provide access to [`ObserveSdk`] from a [`wasmtime::Store`]'s inner object.
///
/// ```example
/// ```rust
/// use dylibso_observe_sdk::context::component::{ ObserveSdk, ObserveSdkView };
///
/// struct MyState {
/// sdk: ObserveSdk,
/// }
Expand All @@ -551,18 +642,43 @@ pub mod component {
fn sdk_mut(&mut self) -> &mut ObserveSdk;
}

wasmtime::component::bindgen!({
interfaces: r#"
import dylibso:observe/api;
import dylibso:observe/instrument;
"#,
path: "../wit",
async: false
});
// Hide the bindgen-generated modules from rustdoc by using an "internal" module.
mod internal {
wasmtime::component::bindgen!({
interfaces: r#"
import dylibso:observe/api;
import dylibso:observe/instrument;
"#,
path: "../wit",
async: false
});
}

use dylibso::observe::api::{ MetricFormat, LogLevel, Host as ApiHost };
use dylibso::observe::instrument::Host as InstrumentHost;
use internal::dylibso::observe::api::{ MetricFormat, LogLevel, Host as ApiHost };
use internal::dylibso::observe::instrument::Host as InstrumentHost;

/// A data structure backing ObserveSdk bindings: contains private information mapping
/// function ids to names and contexts for collector channels.
///
/// Instantiate by calling
/// [`AdapterHandle::build_observe_sdk`](crate::adapter::AdapterHandle::build_observe_sdk)
/// using any adapter type:
///
/// ```no_run
/// # use dylibso_observe_sdk::context::component::ObserveSdk;
/// use dylibso_observe_sdk::adapter::zipkin::ZipkinAdapter;
/// # fn main() -> anyhow::Result<()> {
/// let args: Vec<_> = std::env::args().skip(1).collect();
/// let wasm_data = std::fs::read(&args[0])?;
/// let zipkin = ZipkinAdapter::create();
///
/// // NB: the ": ObserveSdk" type annotation here isn't necessary, it's just to indicate the
/// // return type.
/// let observe_sdk: ObserveSdk = zipkin.build_observe_sdk(&wasm_data,
/// Default::default()).unwrap();
/// # Ok(())
/// # }
/// ```
pub struct ObserveSdk {
pub(crate) instr_context: Arc<Mutex<InstrumentationContext>>,
pub(crate) wasm_instr_info: WasmInstrInfo,
Expand Down Expand Up @@ -653,14 +769,17 @@ pub mod component {
}
}

/// Make ObserveSdk host bindings available to the component model
/// [`wasmtime::component::Linker`]. Assumes that [`ObserveSdkView`] has been implemented to
/// map from the [`wasmtime::Store`] to an instance of [`ObserveSdk`].
pub fn add_to_linker<T>(
linker: &mut Linker<T>,
) -> Result<()> where T: ObserveSdkView + 'static {
dylibso::observe::api::add_to_linker(linker, |s| -> &mut ObserveSdk {
internal::dylibso::observe::api::add_to_linker(linker, |s| -> &mut ObserveSdk {
s.sdk_mut()
})?;

dylibso::observe::instrument::add_to_linker(linker, |s| -> &mut ObserveSdk {
internal::dylibso::observe::instrument::add_to_linker(linker, |s| -> &mut ObserveSdk {
s.sdk_mut()
})?;
Ok(())
Expand Down

0 comments on commit 404b551

Please sign in to comment.