Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
de8e7f9
migrate runtimevar to utilize opentelemetry
pitabwire Apr 11, 2025
adba7ec
run go mod tidy
pitabwire Apr 11, 2025
47330e9
minor improvements to otel common code
pitabwire Apr 26, 2025
672b9c1
tidy up modules via go mod tidy
pitabwire Apr 26, 2025
11a5731
add bytes read and bytes written views for summations
pitabwire Apr 26, 2025
7183bc8
Merge branch 'otel-migration-internal-update' of github.com:pitabwire…
pitabwire Apr 26, 2025
453d2c4
reuse gcdkotel functions
pitabwire Apr 26, 2025
77be8c3
add a reusable dimensionless counter getter
pitabwire Apr 26, 2025
9526c37
Merge branch 'otel-migration-internal-update' of github.com:pitabwire…
pitabwire Apr 26, 2025
970ccc1
add resources when setting up metrics counter
pitabwire Apr 26, 2025
7dbfca9
initialize meter with provider too
pitabwire Apr 26, 2025
866921b
Merge branch 'otel-migration-internal-update' of github.com:pitabwire…
pitabwire Apr 26, 2025
218be9c
update counter to work with metrics
pitabwire Apr 26, 2025
2072f85
remove need for setting provider name on addinging metrics
pitabwire Apr 26, 2025
2ab0e00
run tidy scripts
pitabwire Apr 26, 2025
3bbc25b
minor improvement to avoid race conditions
pitabwire Apr 26, 2025
d725986
resolve merge conflicts with master
pitabwire Jul 3, 2025
36b3218
runtimevar changes
pitabwire Jul 3, 2025
0f4bb0e
expose the diff functionality for Span & Metrics
pitabwire Jul 4, 2025
aedefd7
go mod tidy scripts run
pitabwire Jul 4, 2025
44db86d
make diffSpan private again
pitabwire Jul 5, 2025
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
40 changes: 39 additions & 1 deletion internal/otel/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ import (
// Units are encoded according to the case-sensitive abbreviations from the
// Unified Code for Units of Measure: http://unitsofmeasure.org/ucum.html.
const (
unitMilliseconds = "ms"
unitDimensionless = "1"
unitMilliseconds = "ms"
)

var (
Expand Down Expand Up @@ -108,3 +109,40 @@ func LatencyMeasure(pkg string, provider string) metric.Float64Histogram {

return m
}

func DimensionlessMeasure(pkg string, provider string, meterName string, description string) metric.Int64Counter {

attrs := []attribute.KeyValue{
packageKey.String(pkg),
providerKey.String(provider),
}

pkgMeter := otel.Meter(pkg, metric.WithInstrumentationAttributes(attrs...))

m, err := pkgMeter.Int64Counter(pkg+meterName, metric.WithDescription(description), metric.WithUnit(unitDimensionless))

if err != nil {
// The only possible errors are from invalid key or value names,
// and those are programming errors that will be found during testing.
panic(fmt.Sprintf("fullName=%q, provider=%q: %v", pkg, pkgMeter, err))
}
return m
}

func CounterView(pkg string, meterName string, description string) []sdkmetric.View {
return []sdkmetric.View{
// View for gauge counts.
func(inst sdkmetric.Instrument) (sdkmetric.Stream, bool) {
if inst.Kind == sdkmetric.InstrumentKindCounter {
if inst.Name == pkg+meterName {
return sdkmetric.Stream{
Name: inst.Name,
Description: description,
Aggregation: sdkmetric.DefaultAggregationSelector(sdkmetric.InstrumentKindCounter),
}, true
}
}
return sdkmetric.Stream{}, false
},
}
}
94 changes: 59 additions & 35 deletions internal/testing/oteltest/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ func formatCall(c *Call) string {
// provider is the name of the provider used (e.g., "aws").
// want is the list of expected calls.
func Diff(gotSpans []sdktrace.ReadOnlySpan, gotMetrics []metricdata.ScopeMetrics, namePrefix, provider string, want []Call) string {
ds := diffSpans(gotSpans, namePrefix, want)
dc := diffCounts(gotMetrics, namePrefix, provider, want)
ds := DiffSpans(gotSpans, namePrefix, want)
dc := DiffMetrics(gotMetrics, namePrefix, provider, want)
if len(ds) > 0 {
ds = "trace: " + ds + "\n"
}
Expand All @@ -86,7 +86,7 @@ func mapStatusCode(code gcerrors.ErrorCode) codes.Code {
return codes.Error
}

func diffSpans(got []sdktrace.ReadOnlySpan, prefix string, want []Call) string {
func DiffSpans(got []sdktrace.ReadOnlySpan, prefix string, want []Call) string {
var diffs []string
add := func(i int, g sdktrace.ReadOnlySpan, w *Call) {
diffs = append(diffs, fmt.Sprintf("#%d: got %s, want %s", i, formatSpanData(g), formatCall(w)))
Expand Down Expand Up @@ -117,15 +117,15 @@ func diffSpans(got []sdktrace.ReadOnlySpan, prefix string, want []Call) string {
return strings.Join(diffs, "\n")
}

func diffCounts(got []metricdata.ScopeMetrics, prefix, provider string, wantCalls []Call) string {
func DiffMetrics(got []metricdata.ScopeMetrics, prefix, provider string, wantCalls []Call) string {
// OTel metric data is structured. We need to iterate through it to find the
// relevant metric data points and their attributes.
var diffs []string
gotTags := map[string]bool{} // map of canonicalized data point attributes

// Helper to convert attribute.Set to a canonical string key
attrSetToCanonicalString := func(set attribute.Set) string {
// Get key-value pairs, sort them, and format into a stable string
// Get key-value pairs, sort them, and format into a stable string.
attrs := make([]attribute.KeyValue, 0, set.Len())
iter := set.Iter()
for iter.Next() {
Expand All @@ -143,45 +143,69 @@ func diffCounts(got []metricdata.ScopeMetrics, prefix, provider string, wantCall
return strings.Join(parts, ",")
}

// Iterate through all collected metrics to find relevant data points
for _, sm := range got {
// Helper function to collect relevant attributes for tag comparison.
processAtrributes := func(attrSets ...attribute.Set) {

var requiredAttributes []attribute.KeyValue
for _, attrSet := range attrSets {
for _, a := range attrSet.ToSlice() {
if a.Key == providerKey {
requiredAttributes = append(requiredAttributes, a)
}

if a.Key == methodKey {
requiredAttributes = append(requiredAttributes, a)
}

providerVal, providerOK := sm.Scope.Attributes.Value(providerKey)
if a.Key == statusKey {
requiredAttributes = append(requiredAttributes, a)
}
}
}

if len(requiredAttributes) > 0 {
gotTags[attrSetToCanonicalString(attribute.NewSet(requiredAttributes...))] = true
}

}

// Iterate through all collected metrics to find relevant data points.
for _, sm := range got {

for _, m := range sm.Metrics {
// gocloud usually records counts. Check for Sum metrics.
if sum, ok := m.Data.(metricdata.Sum[float64]); ok {
for _, dp := range sum.DataPoints {
methodVal, methodOK := dp.Attributes.Value(methodKey)
statusVal, statusOK := dp.Attributes.Value(statusKey)

if providerOK && methodOK && statusOK {

attrSet := attribute.NewSet(
providerKey.String(providerVal.AsString()),
methodKey.String(methodVal.AsString()),
statusKey.String(statusVal.AsString()),
)

gotTags[attrSetToCanonicalString(attrSet)] = true
}

// Using a switch will allow us accommodate other types of metrics.
switch v := m.Data.(type) {
case metricdata.Sum[int64]:
// Handle int64 Sum metrics.
for _, dp := range v.DataPoints {
processAtrributes(sm.Scope.Attributes, dp.Attributes)
}
case metricdata.Sum[float64]:
// gocloud usually records counts. Check for Sum metrics.
for _, dp := range v.DataPoints {
processAtrributes(sm.Scope.Attributes, dp.Attributes)
}
default:
// Handle any other types of metrics.
processAtrributes(sm.Scope.Attributes)
}
}
}

// Check that each wanted call has a corresponding metric data point with the correct attributes
// Check that each wanted call has a corresponding metric data point with the correct attributes.
for _, wc := range wantCalls {
// Construct the expected set of attributes for the wanted call
expectedAttributes := attribute.NewSet(
methodKey.String(prefix+"."+wc.Method),
providerKey.String(provider),
// gcerrors code is usually formatted as a string status in the attribute
statusKey.String(fmt.Sprint(wc.Code)),
)

// Canonicalize the expected attributes to check against the collected ones
expectedKey := attrSetToCanonicalString(expectedAttributes)
// Construct the expected set of attributes for the wanted call.
expectedAttributes := []attribute.KeyValue{providerKey.String(provider)}

if wc.Method != "" {
expectedAttributes = append(expectedAttributes,
methodKey.String(prefix+"."+wc.Method),
statusKey.String(fmt.Sprint(wc.Code)))
}

// Canonicalize the expected attributes to check against the collected ones.
expectedKey := attrSetToCanonicalString(attribute.NewSet(expectedAttributes...))

if !gotTags[expectedKey] {
diffs = append(diffs, fmt.Sprintf("missing metric data point with attributes %q", expectedKey))
Expand Down
2 changes: 0 additions & 2 deletions runtimevar/etcdvar/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ require (
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-replayers/grpcreplay v1.3.0 // indirect
Expand All @@ -62,7 +61,6 @@ require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
go.etcd.io/etcd/api/v3 v3.5.21 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.21 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
Expand Down
9 changes: 0 additions & 9 deletions runtimevar/etcdvar/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
Expand Down Expand Up @@ -252,14 +250,9 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
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/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand All @@ -285,8 +278,6 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
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/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
Expand Down
42 changes: 20 additions & 22 deletions runtimevar/oc_test.go → runtimevar/otel_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2019 The Go Cloud Development Kit Authors
// Copyright 2019-2025 The Go Cloud Development Kit Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -16,19 +16,22 @@ package runtimevar_test

import (
"context"
"testing"

"go.opencensus.io/stats/view"
"gocloud.dev/internal/oc"
"gocloud.dev/internal/testing/octest"
"gocloud.dev/gcerrors"
"gocloud.dev/internal/testing/oteltest"
"gocloud.dev/runtimevar"
"gocloud.dev/runtimevar/constantvar"
"testing"
)

func TestOpenCensus(t *testing.T) {
const (
pkgName = "gocloud.dev/runtimevar"
driver = "gocloud.dev/runtimevar/constantvar"
)

func TestOpenTelemetry(t *testing.T) {
ctx := context.Background()
te := octest.NewTestExporter(runtimevar.OpenCensusViews)
defer te.Unregister()
te := oteltest.NewTestExporter(t, runtimevar.OpenTelemetryViews)
defer te.Shutdown(ctx)

v := constantvar.New(1)
defer v.Close()
Expand All @@ -39,18 +42,13 @@ func TestOpenCensus(t *testing.T) {
cancel()
_, _ = v.Watch(cctx)

seen := false
const driver = "gocloud.dev/runtimevar/constantvar"
for _, row := range te.Counts() {
if _, ok := row.Data.(*view.CountData); !ok {
continue
}
if row.Tags[0].Key == oc.ProviderKey && row.Tags[0].Value == driver {
seen = true
break
}
}
if !seen {
t.Errorf("did not see count row with provider=%s", driver)
// Check metrics - during migration, we may need to look for different metric names.
metrics := te.GetMetrics(ctx)

diff := oteltest.DiffMetrics(metrics, pkgName, driver, []oteltest.Call{
{Method: "", Code: gcerrors.OK},
})
if diff != "" {
t.Error(diff)
}
}
Loading