Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Observe SDK support #47

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 48 additions & 12 deletions extism.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@ import (
"os"
"time"

observe "github.com/dylibso/observe-sdk/go"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
"github.com/tetratelabs/wazero/sys"
)

type module struct {
module api.Module
wasm []byte
}

//go:embed extism-runtime.wasm
var extismRuntimeWasm []byte

Expand All @@ -34,10 +40,12 @@ type Runtime struct {

// PluginConfig contains configuration options for the Extism plugin.
type PluginConfig struct {
ModuleConfig wazero.ModuleConfig
RuntimeConfig wazero.RuntimeConfig
EnableWasi bool
LogLevel LogLevel
ModuleConfig wazero.ModuleConfig
RuntimeConfig wazero.RuntimeConfig
EnableWasi bool
LogLevel LogLevel
ObserveAdapter *observe.AdapterBase
mhmd-azeez marked this conversation as resolved.
Show resolved Hide resolved
ObserveOptions *observe.Options
}

// HttpRequest represents an HTTP request to be made by the plugin.
Expand Down Expand Up @@ -80,8 +88,8 @@ func (l LogLevel) String() string {
// Plugin is used to call WASM functions
type Plugin struct {
Runtime *Runtime
Modules map[string]api.Module
Main api.Module
Modules map[string]module
Main module
Timeout time.Duration
Config map[string]string
// NOTE: maybe we can have some nice methods for getting/setting vars
Expand All @@ -92,6 +100,8 @@ type Plugin struct {
log func(LogLevel, string)
logLevel LogLevel
guestRuntime guestRuntime
Adapter *observe.AdapterBase
TraceCtx *observe.TraceCtx
}

func logStd(level LogLevel, message string) {
Expand Down Expand Up @@ -276,6 +286,12 @@ func (m *Manifest) UnmarshalJSON(data []byte) error {

// Close closes the plugin by freeing the underlying resources.
func (p *Plugin) Close() error {
// TODO: Since we don't start the adapter in the ctor, should we stop it here?
if p.Adapter != nil {
mhmd-azeez marked this conversation as resolved.
Show resolved Hide resolved
a := *p.Adapter
a.Stop(true)
}

return p.Runtime.Wazero.Close(p.Runtime.ctx)
}

Expand Down Expand Up @@ -344,7 +360,7 @@ func NewPlugin(
return nil, fmt.Errorf("Manifest can't be empty.")
}

modules := map[string]api.Module{}
modules := map[string]module{}

// NOTE: this is only necessary for guest modules because
// host modules have the same access privileges as the host itself
Expand All @@ -371,6 +387,7 @@ func NewPlugin(
// - If there is only one module in the manifest then that is the main module by default
// - Otherwise the last module listed is the main module

var trace *observe.TraceCtx
for i, wasm := range manifest.Wasm {
data, err := wasm.ToWasmData(ctx)
if err != nil {
Expand All @@ -382,6 +399,18 @@ func NewPlugin(
data.Name = "main"
}

if data.Name == "main" && config.ObserveAdapter != nil {
// TODO: Since we can extract the names out of a api.Module,
// maybe we can have a new overload that accepts that instead of []byte?
trace, err = config.ObserveAdapter.NewTraceCtx(ctx, c.Wazero, data.Data, config.ObserveOptions)
mhmd-azeez marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, fmt.Errorf("Failed to initialize Observe Adapter: %v", err)
}

// TODO: Do we actually need to finish the trace?
trace.Finish()
mhmd-azeez marked this conversation as resolved.
Show resolved Hide resolved
}

_, okh := hostModules[data.Name]
_, okm := modules[data.Name]

Expand All @@ -401,7 +430,7 @@ func NewPlugin(
return nil, err
}

modules[data.Name] = m
modules[data.Name] = module{module: m, wasm: data.Data}
}

logLevel := LogLevelWarn
Expand All @@ -411,7 +440,7 @@ func NewPlugin(

i := 0
for _, m := range modules {
if m.Name() == "main" {
if m.module.Name() == "main" {
p := &Plugin{
Runtime: &c,
Modules: modules,
Expand All @@ -423,7 +452,9 @@ func NewPlugin(
LastStatusCode: 0,
Timeout: time.Duration(manifest.Timeout) * time.Millisecond,
log: logStd,
logLevel: logLevel}
logLevel: logLevel,
Adapter: config.ObserveAdapter,
TraceCtx: trace}

p.guestRuntime = detectGuestRuntime(p)
return p, nil
Expand Down Expand Up @@ -499,7 +530,7 @@ func (plugin *Plugin) GetError() string {

// FunctionExists returns true when the named function is present in the plugin's main module
func (plugin *Plugin) FunctionExists(name string) bool {
return plugin.Main.ExportedFunction(name) != nil
return plugin.Main.module.ExportedFunction(name) != nil
}

// Call a function by name with the given input, returning the output
Expand All @@ -521,7 +552,7 @@ func (plugin *Plugin) Call(name string, data []byte) (uint32, []byte, error) {

ctx = context.WithValue(ctx, "inputOffset", intputOffset)

var f = plugin.Main.ExportedFunction(name)
var f = plugin.Main.module.ExportedFunction(name)

if f == nil {
return 1, []byte{}, errors.New(fmt.Sprintf("Unknown function: %s", name))
Expand All @@ -542,6 +573,11 @@ func (plugin *Plugin) Call(name string, data []byte) (uint32, []byte, error) {

res, err := f.Call(ctx)

// TODO: Should we finish the trace now, or defer it?
if plugin.TraceCtx != nil {
defer plugin.TraceCtx.Finish()
mhmd-azeez marked this conversation as resolved.
Show resolved Hide resolved
}

// Try to extact WASI exit code
if exitErr, ok := err.(*sys.ExitError); ok {
exitCode := exitErr.ExitCode()
Expand Down
51 changes: 51 additions & 0 deletions extism_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"testing"
"time"

observe "github.com/dylibso/observe-sdk/go"
"github.com/dylibso/observe-sdk/go/adapter/stdout"
"github.com/stretchr/testify/assert"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/sys"
Expand Down Expand Up @@ -701,6 +703,55 @@ func TestJsonManifest(t *testing.T) {
}
}

func TestObserve(t *testing.T) {
mhmd-azeez marked this conversation as resolved.
Show resolved Hide resolved
ctx := context.Background()

var buf bytes.Buffer
log.SetOutput(&buf)

adapter := stdout.NewStdoutAdapter()
adapter.Start(ctx)

manifest := manifest("nested.c.instr.wasm")

config := PluginConfig{
ModuleConfig: wazero.NewModuleConfig().WithSysWalltime(),
EnableWasi: true,
ObserveAdapter: adapter.AdapterBase,
ObserveOptions: &observe.Options{
SpanFilter: &observe.SpanFilter{MinDuration: 1 * time.Nanosecond},
ChannelBufferSize: 1024,
},
}

plugin, err := NewPlugin(ctx, manifest, config, []HostFunction{})
if err != nil {
panic(err)
}

meta := map[string]string{
"http.url": "https://example.com/my-endpoint",
"http.status_code": "200",
"http.client_ip": "192.168.1.0",
}

plugin.TraceCtx.Metadata(meta)

_, _, _ = plugin.Call("_start", []byte("hello world"))
plugin.Close()

// HACK: make sure we give enough time for the events to get flushed
time.Sleep(100 * time.Millisecond)

actual := buf.String()
assert.Contains(t, actual, " Call to _start took")
assert.Contains(t, actual, " Call to main took")
assert.Contains(t, actual, " Call to one took")
assert.Contains(t, actual, " Call to two took")
assert.Contains(t, actual, " Call to three took")
assert.Contains(t, actual, " Call to printf took")
}

func BenchmarkInitialize(b *testing.B) {
ctx := context.Background()
cache := wazero.NewCompilationCache()
Expand Down
14 changes: 11 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@ module github.com/extism/go-sdk

go 1.20

require github.com/tetratelabs/wazero v1.3.0
require (
github.com/dylibso/observe-sdk/go v0.0.0-20231201014635-141351c24659
github.com/gobwas/glob v0.2.3
github.com/stretchr/testify v1.8.4
github.com/tetratelabs/wazero v1.3.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

)
17 changes: 17 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dylibso/observe-sdk/go v0.0.0-20231201014635-141351c24659 h1:RGgHymaENttkVRf0YEzly0Cr2q8xB56WuDEsn8oFXHE=
github.com/dylibso/observe-sdk/go v0.0.0-20231201014635-141351c24659/go.mod h1:7h4vx/+0cUjKN2f+ynM4tcC8kIjJqP6W2cLcn7buXl4=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab h1:BA4a7pe6ZTd9F8kXETBoijjFJ/ntaa//1wiH9BZu4zU=
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q=
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk=
github.com/tetratelabs/wazero v1.3.0 h1:nqw7zCldxE06B8zSZAY0ACrR9OH5QCcPwYmYlwtcwtE=
github.com/tetratelabs/wazero v1.3.0/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
2 changes: 1 addition & 1 deletion runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type guestRuntime struct {
}

func detectGuestRuntime(p *Plugin) guestRuntime {
m := p.Main
m := p.Main.module

runtime, ok := haskellRuntime(p, m)
if ok {
Expand Down
Binary file added wasm/nested.c.instr.wasm
Binary file not shown.