Skip to content

Commit

Permalink
Add exporters/otlp/otlpmetric/internal/otest (#3125)
Browse files Browse the repository at this point in the history
* Add otlpmetric package doc

* Add Client interface

* Add the Exporter

Have the Exporter ensure synchronous access to all client methods.

* Add race detection test for Exporter

* Expand New godocs

* Fix lint

* Restore otlpmetrictest from main

* Rename otlpmetrictest to otest

* Remove data.go

The functions and types it contains are no longer relevant to the SDK.

* Update client context error tests

Remove multiple shutdown tests. The Client interface states this should
never happen.

* Remove collector.go and otlptest.go

* Expand client tests with ctx and force-flush

* Add UploadMetrics tests

* Test the tests with a trivial client

* Condense all to client.go

* Example of how to run RunClientTests

* Add client integration testing

* Add GRPCCollector

* Remove GRPCCollector to limit scope of PR
  • Loading branch information
MrAlias committed Sep 8, 2022
1 parent 29c9c40 commit 40319d9
Show file tree
Hide file tree
Showing 5 changed files with 372 additions and 1 deletion.
4 changes: 3 additions & 1 deletion exporters/otlp/otlpmetric/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module go.opentelemetry.io/otel/exporters/otlp/otlpmetric
go 1.18

require (
github.com/google/go-cmp v0.5.8
github.com/stretchr/testify v1.7.1
go.opentelemetry.io/otel v1.9.0
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.9.0
Expand All @@ -11,6 +12,7 @@ require (
go.opentelemetry.io/otel/sdk/metric v0.0.0-00010101000000-000000000000
go.opentelemetry.io/proto/otlp v0.19.0
google.golang.org/grpc v1.42.0
google.golang.org/protobuf v1.27.1
)

require (
Expand All @@ -19,13 +21,13 @@ require (
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/otel/trace v1.9.0 // indirect
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect
golang.org/x/text v0.3.5 // indirect
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)

Expand Down
3 changes: 3 additions & 0 deletions exporters/otlp/otlpmetric/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV
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/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
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=
Expand Down Expand Up @@ -110,6 +111,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
Expand All @@ -124,6 +126,7 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
Expand Down
254 changes: 254 additions & 0 deletions exporters/otlp/otlpmetric/internal/otest/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build go1.18
// +build go1.18

package otest // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/otest"

import (
"context"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"

"go.opentelemetry.io/otel/exporters/otlp/otlpmetric"
"go.opentelemetry.io/otel/metric/unit"
semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
cpb "go.opentelemetry.io/proto/otlp/common/v1"
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
rpb "go.opentelemetry.io/proto/otlp/resource/v1"
)

var (
// Sat Jan 01 2000 00:00:00 GMT+0000.
start = time.Date(2000, time.January, 01, 0, 0, 0, 0, time.FixedZone("GMT", 0))
end = start.Add(30 * time.Second)

kvAlice = &cpb.KeyValue{Key: "user", Value: &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{StringValue: "alice"},
}}
kvBob = &cpb.KeyValue{Key: "user", Value: &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{StringValue: "bob"},
}}
kvSrvName = &cpb.KeyValue{Key: "service.name", Value: &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{StringValue: "test server"},
}}
kvSrvVer = &cpb.KeyValue{Key: "service.version", Value: &cpb.AnyValue{
Value: &cpb.AnyValue_StringValue{StringValue: "v0.1.0"},
}}

min, max, sum = 2.0, 4.0, 90.0
hdp = []*mpb.HistogramDataPoint{{
Attributes: []*cpb.KeyValue{kvAlice},
StartTimeUnixNano: uint64(start.UnixNano()),
TimeUnixNano: uint64(end.UnixNano()),
Count: 30,
Sum: &sum,
ExplicitBounds: []float64{1, 5},
BucketCounts: []uint64{0, 30, 0},
Min: &min,
Max: &max,
}}

hist = &mpb.Histogram{
AggregationTemporality: mpb.AggregationTemporality_AGGREGATION_TEMPORALITY_DELTA,
DataPoints: hdp,
}

dPtsInt64 = []*mpb.NumberDataPoint{
{
Attributes: []*cpb.KeyValue{kvAlice},
StartTimeUnixNano: uint64(start.UnixNano()),
TimeUnixNano: uint64(end.UnixNano()),
Value: &mpb.NumberDataPoint_AsInt{AsInt: 1},
},
{
Attributes: []*cpb.KeyValue{kvBob},
StartTimeUnixNano: uint64(start.UnixNano()),
TimeUnixNano: uint64(end.UnixNano()),
Value: &mpb.NumberDataPoint_AsInt{AsInt: 2},
},
}
dPtsFloat64 = []*mpb.NumberDataPoint{
{
Attributes: []*cpb.KeyValue{kvAlice},
StartTimeUnixNano: uint64(start.UnixNano()),
TimeUnixNano: uint64(end.UnixNano()),
Value: &mpb.NumberDataPoint_AsDouble{AsDouble: 1.0},
},
{
Attributes: []*cpb.KeyValue{kvBob},
StartTimeUnixNano: uint64(start.UnixNano()),
TimeUnixNano: uint64(end.UnixNano()),
Value: &mpb.NumberDataPoint_AsDouble{AsDouble: 2.0},
},
}

sumInt64 = &mpb.Sum{
AggregationTemporality: mpb.AggregationTemporality_AGGREGATION_TEMPORALITY_CUMULATIVE,
IsMonotonic: true,
DataPoints: dPtsInt64,
}
sumFloat64 = &mpb.Sum{
AggregationTemporality: mpb.AggregationTemporality_AGGREGATION_TEMPORALITY_DELTA,
IsMonotonic: false,
DataPoints: dPtsFloat64,
}

gaugeInt64 = &mpb.Gauge{DataPoints: dPtsInt64}
gaugeFloat64 = &mpb.Gauge{DataPoints: dPtsFloat64}

metrics = []*mpb.Metric{
{
Name: "int64-gauge",
Description: "Gauge with int64 values",
Unit: string(unit.Dimensionless),
Data: &mpb.Metric_Gauge{Gauge: gaugeInt64},
},
{
Name: "float64-gauge",
Description: "Gauge with float64 values",
Unit: string(unit.Dimensionless),
Data: &mpb.Metric_Gauge{Gauge: gaugeFloat64},
},
{
Name: "int64-sum",
Description: "Sum with int64 values",
Unit: string(unit.Dimensionless),
Data: &mpb.Metric_Sum{Sum: sumInt64},
},
{
Name: "float64-sum",
Description: "Sum with float64 values",
Unit: string(unit.Dimensionless),
Data: &mpb.Metric_Sum{Sum: sumFloat64},
},
{
Name: "histogram",
Description: "Histogram",
Unit: string(unit.Dimensionless),
Data: &mpb.Metric_Histogram{Histogram: hist},
},
}

scope = &cpb.InstrumentationScope{
Name: "test/code/path",
Version: "v0.1.0",
}
scopeMetrics = []*mpb.ScopeMetrics{{
Scope: scope,
Metrics: metrics,
SchemaUrl: semconv.SchemaURL,
}}

res = &rpb.Resource{
Attributes: []*cpb.KeyValue{kvSrvName, kvSrvVer},
}
resourceMetrics = &mpb.ResourceMetrics{
Resource: res,
ScopeMetrics: scopeMetrics,
SchemaUrl: semconv.SchemaURL,
}
)

// ClientFactory is a function that when called returns a
// otlpmetric.Client implementation that is connected to also returned
// Collector implementation. The Client is ready to upload metric data to the
// Collector which is ready to store that data.
type ClientFactory func() (otlpmetric.Client, Collector)

// RunClientTests runs a suite of Client integration tests. For example:
//
// t.Run("Integration", RunClientTests(factory))
func RunClientTests(f ClientFactory) func(*testing.T) {
return func(t *testing.T) {
t.Run("ClientHonorsContextErrors", func(t *testing.T) {
t.Run("Shutdown", testCtxErrs(func() func(context.Context) error {
c, _ := f()
return c.Shutdown
}))

t.Run("ForceFlush", testCtxErrs(func() func(context.Context) error {
c, _ := f()
return c.ForceFlush
}))

t.Run("UploadMetrics", testCtxErrs(func() func(context.Context) error {
c, _ := f()
return func(ctx context.Context) error {
return c.UploadMetrics(ctx, nil)
}
}))
})

t.Run("ForceFlushFlushes", func(t *testing.T) {
ctx := context.Background()
client, collector := f()
require.NoError(t, client.UploadMetrics(ctx, resourceMetrics))

require.NoError(t, client.ForceFlush(ctx))
rm := collector.Collect().dump()
// Data correctness is not important, just it was received.
require.Greater(t, len(rm), 0, "no data uploaded")

require.NoError(t, client.Shutdown(ctx))
rm = collector.Collect().dump()
assert.Len(t, rm, 0, "client did not flush all data")
})

t.Run("UploadMetrics", func(t *testing.T) {
ctx := context.Background()
client, coll := f()

require.NoError(t, client.UploadMetrics(ctx, resourceMetrics))
require.NoError(t, client.Shutdown(ctx))
got := coll.Collect().dump()
require.Len(t, got, 1, "upload of one ResourceMetrics")
diff := cmp.Diff(got[0], resourceMetrics, cmp.Comparer(proto.Equal))
if diff != "" {
t.Fatalf("unexpected ResourceMetrics:\n%s", diff)
}
})
}
}

func testCtxErrs(factory func() func(context.Context) error) func(t *testing.T) {
return func(t *testing.T) {
t.Helper()
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)

t.Run("DeadlineExceeded", func(t *testing.T) {
innerCtx, innerCancel := context.WithTimeout(ctx, time.Nanosecond)
t.Cleanup(innerCancel)
<-innerCtx.Done()

f := factory()
assert.ErrorIs(t, f(innerCtx), context.DeadlineExceeded)
})

t.Run("Canceled", func(t *testing.T) {
innerCtx, innerCancel := context.WithCancel(ctx)
innerCancel()

f := factory()
assert.ErrorIs(t, f(innerCtx), context.Canceled)
})
}
}
54 changes: 54 additions & 0 deletions exporters/otlp/otlpmetric/internal/otest/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build go1.18
// +build go1.18

package otest // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/otest"

import (
"context"
"testing"

"go.opentelemetry.io/otel/exporters/otlp/otlpmetric"
cpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1"
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
)

type client struct {
storage *Storage
}

func (c *client) Collect() *Storage {
return c.storage
}

func (c *client) UploadMetrics(ctx context.Context, rm *mpb.ResourceMetrics) error {
c.storage.Add(&cpb.ExportMetricsServiceRequest{
ResourceMetrics: []*mpb.ResourceMetrics{rm},
})
return ctx.Err()
}

func (c *client) ForceFlush(ctx context.Context) error { return ctx.Err() }
func (c *client) Shutdown(ctx context.Context) error { return ctx.Err() }

func TestClientTests(t *testing.T) {
factory := func() (otlpmetric.Client, Collector) {
c := &client{storage: NewStorage()}
return c, c
}

t.Run("Integration", RunClientTests(factory))
}

0 comments on commit 40319d9

Please sign in to comment.