From 404b551c656dafb0f1ffaa1ff73af8a65cf27f15 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Tue, 10 Oct 2023 16:38:08 -0700 Subject: [PATCH] doc: add component model integration guide --- .../opentelemetry/proto/trace/v1/trace.proto | 2 + rust/examples/otel-stdout-components.rs | 8 +- rust/src/adapter/mod.rs | 1 + rust/src/context.rs | 145 ++++++++++++++++-- 4 files changed, 138 insertions(+), 18 deletions(-) diff --git a/proto/opentelemetry/proto/trace/v1/trace.proto b/proto/opentelemetry/proto/trace/v1/trace.proto index b2869edc..3d50d60a 100644 --- a/proto/opentelemetry/proto/trace/v1/trace.proto +++ b/proto/opentelemetry/proto/trace/v1/trace.proto @@ -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 diff --git a/rust/examples/otel-stdout-components.rs b/rust/examples/otel-stdout-components.rs index 500251e9..d44faa05 100644 --- a/rust/examples/otel-stdout-components.rs +++ b/rust/examples/otel-stdout-components.rs @@ -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 { @@ -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 { @@ -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?; diff --git a/rust/src/adapter/mod.rs b/rust/src/adapter/mod.rs index 6aead615..29fee18d 100644 --- a/rust/src/adapter/mod.rs +++ b/rust/src/adapter/mod.rs @@ -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 { let (ctx, collector, collector_rx) = InstrumentationContext::new(options); let wasm_instr_info = WasmInstrInfo::new(data)?; diff --git a/rust/src/context.rs b/rust/src/context.rs index ba60ce17..4a80a268 100644 --- a/rust/src/context.rs +++ b/rust/src/context.rs @@ -529,6 +529,95 @@ pub fn add_to_linker( #[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::*; @@ -536,7 +625,9 @@ pub mod component { /// 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, /// } @@ -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>, pub(crate) wasm_instr_info: WasmInstrInfo, @@ -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( linker: &mut Linker, ) -> 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(())