diff --git a/examples/component/wasitel-metrics/.gitignore b/examples/component/wasitel-metrics/.gitignore new file mode 100644 index 00000000..f9c09e0d --- /dev/null +++ b/examples/component/wasitel-metrics/.gitignore @@ -0,0 +1,3 @@ +gen/* +build/* +wit/deps/* diff --git a/examples/component/wasitel-metrics/go.mod b/examples/component/wasitel-metrics/go.mod new file mode 100644 index 00000000..72bd11bf --- /dev/null +++ b/examples/component/wasitel-metrics/go.mod @@ -0,0 +1,42 @@ +module wasitel-metrics + +go 1.24 + +tool go.bytecodealliance.org/cmd/wit-bindgen-go + +replace go.wasmcloud.dev/component => ../../../component + +replace go.wasmcloud.dev/x/wasitel => ../../../x/wasitel + +require ( + github.com/google/uuid v1.6.0 + go.bytecodealliance.org/cm v0.2.2 + go.opentelemetry.io/otel v1.35.0 + go.opentelemetry.io/otel/metric v1.35.0 + go.opentelemetry.io/otel/sdk/metric v1.35.0 + go.wasmcloud.dev/component v0.0.6 + go.wasmcloud.dev/x/wasitel v0.0.1 +) + +require ( + github.com/coreos/go-semver v0.3.1 // indirect + github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/regclient/regclient v0.8.2 // indirect + github.com/samber/lo v1.49.1 // indirect + github.com/samber/slog-common v0.18.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/tetratelabs/wazero v1.9.0 // indirect + github.com/ulikunitz/xz v0.5.12 // indirect + github.com/urfave/cli/v3 v3.0.0-beta1 // indirect + go.bytecodealliance.org v0.6.2 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/sdk v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect + golang.org/x/mod v0.23.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect +) diff --git a/examples/component/wasitel-metrics/go.sum b/examples/component/wasitel-metrics/go.sum new file mode 100644 index 00000000..bab8ef41 --- /dev/null +++ b/examples/component/wasitel-metrics/go.sum @@ -0,0 +1,75 @@ +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/olareg/olareg v0.1.1 h1:Ui7q93zjcoF+U9U71sgqgZWByDoZOpqHitUXEu2xV+g= +github.com/olareg/olareg v0.1.1/go.mod h1:w8NP4SWrHHtxsFaUiv1lnCnYPm4sN1seCd2h7FK/dc0= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +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/regclient/regclient v0.8.2 h1:23BQ3jWgKYHHIXUhp/S9laVJDHDoOQaQCzXMJ4undVE= +github.com/regclient/regclient v0.8.2/go.mod h1:uGyetv0o6VLyRDjtfeBqp/QBwRLJ3Hcn07/+8QbhNcM= +github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/samber/slog-common v0.18.1 h1:c0EipD/nVY9HG5shgm/XAs67mgpWDMF+MmtptdJNCkQ= +github.com/samber/slog-common v0.18.1/go.mod h1:QNZiNGKakvrfbJ2YglQXLCZauzkI9xZBjOhWFKS3IKk= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= +github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= +github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= +github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg= +github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= +go.bytecodealliance.org v0.6.2 h1:Jy4u5DVmSkXgsnwojBhJ+AD/YsJsR3VzVnxF0xRCqTQ= +go.bytecodealliance.org v0.6.2/go.mod h1:gqjTJm0y9NSksG4py/lSjIQ/SNuIlOQ+hCIEPQwtJgA= +go.bytecodealliance.org/cm v0.2.2 h1:M9iHS6qs884mbQbIjtLX1OifgyPG9DuMs2iwz8G4WQA= +go.bytecodealliance.org/cm v0.2.2/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/component/wasitel-metrics/main.go b/examples/component/wasitel-metrics/main.go new file mode 100644 index 00000000..f6d3929a --- /dev/null +++ b/examples/component/wasitel-metrics/main.go @@ -0,0 +1,75 @@ +//go:generate go tool wit-bindgen-go generate --world metrics --out gen ./wit + +package main + +import ( + "fmt" + "net/http" + "time" + + "github.com/google/uuid" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.wasmcloud.dev/component/log/wasilog" + "go.wasmcloud.dev/component/net/wasihttp" +) + +var logger = wasilog.DefaultLogger + +var meter = otel.Meter("wasitel-metrics") + +func init() { + if err := initMetrics(); err != nil { + logger.Error("Failed to init metrics", "error", err) + } + + router := http.NewServeMux() + router.HandleFunc("/", metricsMiddleware(echoHandler)) + wasihttp.Handle(router) +} + +func echoHandler(w http.ResponseWriter, r *http.Request) { + defer func() { + if err := syncMetrics(); err != nil { + logger.Error("Failed to sync metrics", "error", err) + } + }() + + fmt.Fprintf(w, "Hello Metrics!") +} + +func metricsMiddleware(next func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { + requestCount, err := meter.Int64UpDownCounter("request_count") + if err != nil { + logger.Error("failed to create counter", "error", err) + } + + responseTime, err := meter.Float64Histogram("response_time") + if err != nil { + logger.Error("failed to create histogram", "error", err) + } + + return func(w http.ResponseWriter, r *http.Request) { + defer func() { + if err := syncMetrics(); err != nil { + logger.Error("Failed to sync metrics", "error", err) + } + }() + reqId := uuid.NewString() + + requestCount.Add(r.Context(), 1, metric.WithAttributes( + attribute.String("request_id", reqId), + )) + + startTime := time.Now() + next(w, r) + endTime := time.Now() + + responseTime.Record(r.Context(), endTime.Sub(startTime).Seconds(), metric.WithAttributes( + attribute.String("request_id", reqId), + )) + } +} + +func main() {} diff --git a/examples/component/wasitel-metrics/metrics.go b/examples/component/wasitel-metrics/metrics.go new file mode 100644 index 00000000..9376dffd --- /dev/null +++ b/examples/component/wasitel-metrics/metrics.go @@ -0,0 +1,47 @@ +package main + +import ( + "context" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + "go.wasmcloud.dev/x/wasitel/wasitelmetric" +) + +var ( + metricExporter *wasitelmetric.Exporter + metricReader *metric.ManualReader +) + +func initMetrics() (err error) { + // Setup metricExporter + metricExporter, err = wasitelmetric.New() + if err != nil { + return err + } + + // Setup metricReader + metricReader = metric.NewManualReader() + + // Setup meterProvider + meterProvider := metric.NewMeterProvider( + metric.WithReader(metricReader), + ) + otel.SetMeterProvider(meterProvider) + return nil +} + +func syncMetrics() error { + rm := &metricdata.ResourceMetrics{} + err := metricReader.Collect(context.Background(), rm) + if err != nil { + return err + } + + err = metricExporter.Export(context.Background(), rm) + if err != nil { + return err + } + return nil +} diff --git a/examples/component/wasitel-metrics/wadm.yaml b/examples/component/wasitel-metrics/wadm.yaml new file mode 100644 index 00000000..2fd38e77 --- /dev/null +++ b/examples/component/wasitel-metrics/wadm.yaml @@ -0,0 +1,52 @@ +apiVersion: core.oam.dev/v1beta1 +kind: Application +metadata: + name: wasitel-metrics + annotations: + description: 'Demo for showing the HTTP OpenTelemetry Metrics integration' + wasmcloud.dev/authors: wasmCloud team + wasmcloud.dev/source-url: https://github.com/wasmCloud/go/blob/main/examples/component/wasitel-metrics/wadm.yaml + wasmcloud.dev/readme-md-url: https://github.com/wasmCloud/go/blob/main/examples/component/wasitel-metrics/README.md + wasmcloud.dev/homepage: https://github.com/wasmCloud/go/blob/main/examples/component/wasitel-metrics + wasmcloud.dev/categories: | + opentelemetry,otel,wasitel,http +spec: + components: + - name: http-component + type: component + properties: + image: file://./build/wasitel-metrics_s.wasm + traits: + - type: spreadscaler + properties: + replicas: 1 + - type: link + properties: + target: http-client + namespace: wasi + package: http + interfaces: [outgoing-handler] + - name: http-server + type: capability + properties: + image: ghcr.io/wasmcloud/http-server:0.25.0 + traits: + - type: link + properties: + target: http-component + namespace: wasi + package: http + interfaces: [incoming-handler] + source_config: + - name: default-http + properties: + address: 0.0.0.0:8000 + - name: http-client + type: capability + properties: + image: ghcr.io/wasmcloud/http-client:0.12.1 + traits: + - type: spreadscaler + properties: + replicas: 1 + diff --git a/examples/component/wasitel-metrics/wasmcloud.lock b/examples/component/wasitel-metrics/wasmcloud.lock new file mode 100644 index 00000000..e830db88 --- /dev/null +++ b/examples/component/wasitel-metrics/wasmcloud.lock @@ -0,0 +1,21 @@ +# This file is automatically generated. +# It is not intended for manual editing. +version = 1 + +[[packages]] +name = "wasi:http" +registry = "wasi.dev" + +[[packages.versions]] +requirement = "=0.2.0" +version = "0.2.0" +digest = "sha256:5a568e6e2d60c1ce51220e1833cdd5b88db9f615720edc762a9b4a6f36b383bd" + +[[packages]] +name = "wasmcloud:component-go" +registry = "wasmcloud.com" + +[[packages.versions]] +requirement = "=0.1.0" +version = "0.1.0" +digest = "sha256:de3e9af7dedd0d9e882f68f1bee533c7af9c6a1947eb40cbf1ef6163b7d41784" diff --git a/examples/component/wasitel-metrics/wasmcloud.toml b/examples/component/wasitel-metrics/wasmcloud.toml new file mode 100644 index 00000000..5587e45f --- /dev/null +++ b/examples/component/wasitel-metrics/wasmcloud.toml @@ -0,0 +1,8 @@ +name = "wasitel-metrics" +language = "tinygo" +type = "component" +version = "0.1.0" + +[component] +wit_world = "metrics" +wasm_target = "wasm32-wasi-preview2" diff --git a/examples/component/wasitel-metrics/wit/world.wit b/examples/component/wasitel-metrics/wit/world.wit new file mode 100644 index 00000000..dad2f6c0 --- /dev/null +++ b/examples/component/wasitel-metrics/wit/world.wit @@ -0,0 +1,8 @@ +package example:wasitel-metrics; + +world metrics { + include wasmcloud:component-go/imports@0.1.0; + import wasi:http/outgoing-handler@0.2.0; // required by wasitel to send metrics + + export wasi:http/incoming-handler@0.2.0; +} diff --git a/x/wasitel/wasitelmetric/internal/convert/attribute.go b/x/wasitel/wasitelmetric/internal/convert/attribute.go new file mode 100644 index 00000000..3c788160 --- /dev/null +++ b/x/wasitel/wasitelmetric/internal/convert/attribute.go @@ -0,0 +1,112 @@ +package convert + +import ( + "go.opentelemetry.io/otel/attribute" + "go.wasmcloud.dev/x/wasitel/wasitelmetric/internal/types" +) + +// AttrIter transforms an attribute Iterator into OTLP key-values. +func AttrIter(iter attribute.Iterator) []*types.KeyValue { + l := iter.Len() + if l == 0 { + return nil + } + + out := make([]*types.KeyValue, l) + for i, kv := range iter.ToSlice() { + out[i] = KeyValue(kv) + } + return out +} + +// KeyValues transforms a slice of attribute KeyValues into OTLP key-values. +func KeyValues(attrs []attribute.KeyValue) []*types.KeyValue { + if len(attrs) == 0 { + return nil + } + + out := make([]*types.KeyValue, 0, len(attrs)) + for _, kv := range attrs { + out = append(out, KeyValue(kv)) + } + return out +} + +// KeyValue transforms an attribute KeyValue into an OTLP key-value. +func KeyValue(kv attribute.KeyValue) *types.KeyValue { + return &types.KeyValue{Key: string(kv.Key), Value: Value(kv.Value)} +} + +// Value transforms an attribute Value into an OTLP AnyValue. +func Value(v attribute.Value) *types.AnyValue { + av := new(types.AnyValue) + switch v.Type() { + case attribute.BOOL: + av.BoolValue = v.AsBool() + case attribute.BOOLSLICE: + av.ArrayValue = &types.ArrayValue{ + Values: boolSliceValues(v.AsBoolSlice()), + } + case attribute.INT64: + av.IntValue = v.AsInt64() + case attribute.INT64SLICE: + av.ArrayValue = &types.ArrayValue{ + Values: int64SliceValues(v.AsInt64Slice()), + } + case attribute.FLOAT64: + av.DoubleValue = v.AsFloat64() + case attribute.FLOAT64SLICE: + av.ArrayValue = &types.ArrayValue{ + Values: float64SliceValues(v.AsFloat64Slice()), + } + case attribute.STRING: + av.StringValue = v.AsString() + case attribute.STRINGSLICE: + av.ArrayValue = &types.ArrayValue{ + Values: stringSliceValues(v.AsStringSlice()), + } + default: + av.StringValue = "INVALID" + } + return av +} + +func boolSliceValues(vals []bool) []*types.AnyValue { + converted := make([]*types.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &types.AnyValue{ + BoolValue: v, + } + } + return converted +} + +func int64SliceValues(vals []int64) []*types.AnyValue { + converted := make([]*types.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &types.AnyValue{ + IntValue: v, + } + } + return converted +} + +func float64SliceValues(vals []float64) []*types.AnyValue { + converted := make([]*types.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &types.AnyValue{ + DoubleValue: v, + } + } + return converted +} + +func stringSliceValues(vals []string) []*types.AnyValue { + converted := make([]*types.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &types.AnyValue{ + StringValue: v, + } + } + return converted +} diff --git a/x/wasitel/wasitelmetric/internal/convert/metrics.go b/x/wasitel/wasitelmetric/internal/convert/metrics.go index 1dda9223..489d72b8 100644 --- a/x/wasitel/wasitelmetric/internal/convert/metrics.go +++ b/x/wasitel/wasitelmetric/internal/convert/metrics.go @@ -5,6 +5,228 @@ import ( "go.wasmcloud.dev/x/wasitel/wasitelmetric/internal/types" ) -func ResourceMetrics(data *metricdata.ResourceMetrics) (*types.ResourceMetrics, error) { - return nil, nil +func ResourceMetrics(rm *metricdata.ResourceMetrics) (*types.ResourceMetrics, error) { + return &types.ResourceMetrics{ + Resource: &types.Resource{ + Attributes: AttrIter(rm.Resource.Iter()), + }, + ScopeMetrics: ScopeMetrics(rm.ScopeMetrics), + SchemaUrl: rm.Resource.SchemaURL(), + }, nil +} + +func ScopeMetrics(sms []metricdata.ScopeMetrics) []*types.ScopeMetrics { + out := make([]*types.ScopeMetrics, len(sms)) + for i, sm := range sms { + out[i] = &types.ScopeMetrics{ + Scope: &types.InstrumentationScope{ + Name: sm.Scope.Name, + Version: sm.Scope.Version, + Attributes: AttrIter(sm.Scope.Attributes.Iter()), + }, + Metrics: Metrics(sm.Metrics), + SchemaUrl: sm.Scope.SchemaURL, + } + } + return out +} + +func Metrics(ms []metricdata.Metrics) []*types.Metric { + out := make([]*types.Metric, len(ms)) + for i, m := range ms { + out[i] = metric(m) + } + return out +} + +func metric(m metricdata.Metrics) *types.Metric { + out := &types.Metric{ + Name: m.Name, + Description: m.Description, + Unit: m.Unit, + } + switch a := m.Data.(type) { + case metricdata.Gauge[int64]: + out.Gauge = Gauge(a) + case metricdata.Gauge[float64]: + out.Gauge = Gauge(a) + case metricdata.Sum[int64]: + out.Sum = Sum(a) + case metricdata.Sum[float64]: + out.Sum = Sum(a) + case metricdata.Histogram[int64]: + out.Histogram = Histogram(a) + case metricdata.Histogram[float64]: + out.Histogram = Histogram(a) + case metricdata.ExponentialHistogram[int64]: + out.ExponentialHistogram = ExponentialHistogram(a) + case metricdata.ExponentialHistogram[float64]: + out.ExponentialHistogram = ExponentialHistogram(a) + case metricdata.Summary: + out.Summary = Summary(a) + } + return out +} + +func Gauge[N int64 | float64](g metricdata.Gauge[N]) *types.Gauge { + return &types.Gauge{ + DataPoints: DataPoints(g.DataPoints), + } +} + +func Sum[N int64 | float64](s metricdata.Sum[N]) *types.Sum { + return &types.Sum{ + AggregationTemporality: types.AggregationTemporality(s.Temporality), + IsMonotonic: s.IsMonotonic, + DataPoints: DataPoints(s.DataPoints), + } +} + +func DataPoints[N int64 | float64](pts []metricdata.DataPoint[N]) []*types.NumberDataPoint { + out := make([]*types.NumberDataPoint, len(pts)) + for i, pt := range pts { + dp := &types.NumberDataPoint{ + Attributes: AttrIter(pt.Attributes.Iter()), + StartTimeUnixNano: uint64(pt.StartTime.UnixNano()), + TimeUnixNano: uint64(pt.Time.UnixNano()), + Exemplars: Exemplars(pt.Exemplars), + } + switch v := any(pt.Value).(type) { + case int64: + dp.AsInt = (*types.NumberDataPoint_AsInt)(&v) + case float64: + dp.AsDouble = (*types.NumberDataPoint_AsDouble)(&v) + } + out[i] = dp + } + return out +} + +func Histogram[N int64 | float64](h metricdata.Histogram[N]) *types.Histogram { + return &types.Histogram{ + AggregationTemporality: types.AggregationTemporality(h.Temporality), + DataPoints: HistogramDataPoints(h.DataPoints), + } +} + +func HistogramDataPoints[N int64 | float64](pts []metricdata.HistogramDataPoint[N]) []*types.HistogramDataPoint { + out := make([]*types.HistogramDataPoint, len(pts)) + for i, pt := range pts { + sum := float64(pt.Sum) + hdp := &types.HistogramDataPoint{ + Attributes: AttrIter(pt.Attributes.Iter()), + StartTimeUnixNano: uint64(pt.StartTime.UnixNano()), + TimeUnixNano: uint64(pt.Time.UnixNano()), + Count: pt.Count, + Sum: &sum, + BucketCounts: pt.BucketCounts, + ExplicitBounds: pt.Bounds, + Exemplars: Exemplars(pt.Exemplars), + } + if v, ok := pt.Min.Value(); ok { + vMin := float64(v) + hdp.Min = &vMin + } + if v, ok := pt.Max.Value(); ok { + vMax := float64(v) + hdp.Max = &vMax + } + out[i] = hdp + } + return out +} + +func ExponentialHistogram[N int64 | float64](h metricdata.ExponentialHistogram[N]) *types.ExponentialHistogram { + return &types.ExponentialHistogram{ + DataPoints: ExponentialHistogramDataPoints(h.DataPoints), + AggregationTemporality: types.AggregationTemporality(h.Temporality), + } +} + +func ExponentialHistogramDataPoints[N int64 | float64](pts []metricdata.ExponentialHistogramDataPoint[N]) []*types.ExponentialHistogramDataPoint { + out := make([]*types.ExponentialHistogramDataPoint, len(pts)) + for i, pt := range pts { + sum := float64(pt.Sum) + ehdp := &types.ExponentialHistogramDataPoint{ + Attributes: AttrIter(pt.Attributes.Iter()), + StartTimeUnixNano: uint64(pt.StartTime.UnixNano()), + TimeUnixNano: uint64(pt.Time.UnixNano()), + Count: pt.Count, + Sum: &sum, + Scale: pt.Scale, + Exemplars: Exemplars(pt.Exemplars), + + Positive: ExponentialHistogramDataPointBuckets(pt.PositiveBucket), + Negative: ExponentialHistogramDataPointBuckets(pt.NegativeBucket), + } + if v, ok := pt.Min.Value(); ok { + vMin := float64(v) + ehdp.Min = &vMin + } + if v, ok := pt.Max.Value(); ok { + vMax := float64(v) + ehdp.Max = &vMax + } + out[i] = ehdp + } + return out +} + +func ExponentialHistogramDataPointBuckets(bucket metricdata.ExponentialBucket) *types.ExponentialHistogramDataPoint_Buckets { + return &types.ExponentialHistogramDataPoint_Buckets{ + Offset: bucket.Offset, + BucketCounts: bucket.Counts, + } +} + +func Exemplars[N int64 | float64](exemplars []metricdata.Exemplar[N]) []*types.Exemplar { + out := make([]*types.Exemplar, len(exemplars)) + for i, exemplar := range exemplars { + e := &types.Exemplar{ + FilteredAttributes: KeyValues(exemplar.FilteredAttributes), + TimeUnixNano: uint64(exemplar.Time.UnixNano()), + SpanId: (*types.SpanID)(&exemplar.SpanID), + TraceId: (*types.TraceID)(&exemplar.TraceID), + } + switch v := any(exemplar.Value).(type) { + case int64: + e.AsInt = (*types.Exemplar_AsInt)(&v) + case float64: + e.AsDouble = (*types.Exemplar_AsDouble)(&v) + } + out[i] = e + } + return out +} + +func Summary(s metricdata.Summary) *types.Summary { + return &types.Summary{ + DataPoints: SummaryDataPoints(s.DataPoints), + } +} + +func SummaryDataPoints(pts []metricdata.SummaryDataPoint) []*types.SummaryDataPoint { + out := make([]*types.SummaryDataPoint, len(pts)) + for i, pt := range pts { + out[i] = &types.SummaryDataPoint{ + Attributes: AttrIter(pt.Attributes.Iter()), + StartTimeUnixNano: uint64(pt.StartTime.UnixNano()), + TimeUnixNano: uint64(pt.Time.UnixNano()), + Count: pt.Count, + Sum: pt.Sum, + QuantileValues: QuantileValues(pt.QuantileValues), + } + } + return out +} + +func QuantileValues(quantiles []metricdata.QuantileValue) []*types.SummaryDataPoint_ValueAtQuantile { + out := make([]*types.SummaryDataPoint_ValueAtQuantile, len(quantiles)) + for i, q := range quantiles { + out[i] = &types.SummaryDataPoint_ValueAtQuantile{ + Quantile: q.Quantile, + Value: q.Value, + } + } + return out }