Repository: open-telemetry/opentelemetry-go-contrib (go)

Issue:
Fix `isServer` control flag
The code comments that `isServer` is not a control flag.

https://github.com/open-telemetry/opentelemetry-go-contrib/blob/2adc6806f06367c08440fa491ab2197b584c3aff/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler.go#L147

However, it, [by definition](https://en.wikipedia.org/wiki/Coupling_(computer_programming)#Procedural_programming), is:

https://github.com/open-telemetry/opentelemetry-go-contrib/blob/2adc6806f06367c08440fa491ab2197b584c3aff/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler.go#L209-L214

The function should be refactored to avoid this coupling. Possibly something similar to [this](https://github.com/MrAlias/opentelemetry-go-contrib/blob/191ac0dd06f576ec9abf8c432d5df17b51e97e15/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler.go#L257-L264).

Pull request:
Refactor `otelgrpc` to not use control parameters
Fix #7258 

Patch:
diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/config.go b/instrumentation/google.golang.org/grpc/otelgrpc/config.go
index b95df177e..768160b0c 100644
--- a/instrumentation/google.golang.org/grpc/otelgrpc/config.go
+++ b/instrumentation/google.golang.org/grpc/otelgrpc/config.go
@@ -9,9 +9,7 @@ import (
 	"go.opentelemetry.io/otel"
 	"go.opentelemetry.io/otel/attribute"
 	"go.opentelemetry.io/otel/metric"
-	"go.opentelemetry.io/otel/metric/noop"
 	"go.opentelemetry.io/otel/propagation"
-	semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
 	"go.opentelemetry.io/otel/trace"
 )
 
@@ -47,15 +45,6 @@ type config struct {
 
 	ReceivedEvent bool
 	SentEvent     bool
-
-	tracer trace.Tracer
-	meter  metric.Meter
-
-	rpcDuration    metric.Float64Histogram
-	rpcInBytes     metric.Int64Histogram
-	rpcOutBytes    metric.Int64Histogram
-	rpcInMessages  metric.Int64Histogram
-	rpcOutMessages metric.Int64Histogram
 }
 
 // Option applies an option value for a config.
@@ -64,7 +53,7 @@ type Option interface {
 }
 
 // newConfig returns a config configured with all the passed Options.
-func newConfig(opts []Option, role string) *config {
+func newConfig(opts []Option) *config {
 	c := &config{
 		Propagators:    otel.GetTextMapPropagator(),
 		TracerProvider: otel.GetTracerProvider(),
@@ -73,87 +62,6 @@ func newConfig(opts []Option, role string) *config {
 	for _, o := range opts {
 		o.apply(c)
 	}
-
-	c.tracer = c.TracerProvider.Tracer(
-		ScopeName,
-		trace.WithInstrumentationVersion(Version()),
-	)
-
-	c.meter = c.MeterProvider.Meter(
-		ScopeName,
-		metric.WithInstrumentationVersion(Version()),
-		metric.WithSchemaURL(semconv.SchemaURL),
-	)
-
-	var err error
-	c.rpcDuration, err = c.meter.Float64Histogram("rpc."+role+".duration",
-		metric.WithDescription("Measures the duration of inbound RPC."),
-		metric.WithUnit("ms"))
-	if err != nil {
-		otel.Handle(err)
-		if c.rpcDuration == nil {
-			c.rpcDuration = noop.Float64Histogram{}
-		}
-	}
-
-	rpcRequestSize, err := c.meter.Int64Histogram("rpc."+role+".request.size",
-		metric.WithDescription("Measures size of RPC request messages (uncompressed)."),
-		metric.WithUnit("By"))
-	if err != nil {
-		otel.Handle(err)
-		if rpcRequestSize == nil {
-			rpcRequestSize = noop.Int64Histogram{}
-		}
-	}
-
-	rpcResponseSize, err := c.meter.Int64Histogram("rpc."+role+".response.size",
-		metric.WithDescription("Measures size of RPC response messages (uncompressed)."),
-		metric.WithUnit("By"))
-	if err != nil {
-		otel.Handle(err)
-		if rpcResponseSize == nil {
-			rpcResponseSize = noop.Int64Histogram{}
-		}
-	}
-
-	rpcRequestsPerRPC, err := c.meter.Int64Histogram("rpc."+role+".requests_per_rpc",
-		metric.WithDescription("Measures the number of messages received per RPC. Should be 1 for all non-streaming RPCs."),
-		metric.WithUnit("{count}"))
-	if err != nil {
-		otel.Handle(err)
-		if rpcRequestsPerRPC == nil {
-			rpcRequestsPerRPC = noop.Int64Histogram{}
-		}
-	}
-
-	rpcResponsesPerRPC, err := c.meter.Int64Histogram("rpc."+role+".responses_per_rpc",
-		metric.WithDescription("Measures the number of messages received per RPC. Should be 1 for all non-streaming RPCs."),
-		metric.WithUnit("{count}"))
-	if err != nil {
-		otel.Handle(err)
-		if rpcResponsesPerRPC == nil {
-			rpcResponsesPerRPC = noop.Int64Histogram{}
-		}
-	}
-
-	switch role {
-	case "client":
-		c.rpcInBytes = rpcResponseSize
-		c.rpcInMessages = rpcResponsesPerRPC
-		c.rpcOutBytes = rpcRequestSize
-		c.rpcOutMessages = rpcRequestsPerRPC
-	case "server":
-		c.rpcInBytes = rpcRequestSize
-		c.rpcInMessages = rpcRequestsPerRPC
-		c.rpcOutBytes = rpcResponseSize
-		c.rpcOutMessages = rpcResponsesPerRPC
-	default:
-		c.rpcInBytes = noop.Int64Histogram{}
-		c.rpcInMessages = noop.Int64Histogram{}
-		c.rpcOutBytes = noop.Int64Histogram{}
-		c.rpcOutMessages = noop.Int64Histogram{}
-	}
-
 	return c
 }
 
diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go b/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go
index 19b45a949..820ce1837 100644
--- a/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go
+++ b/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go
@@ -144,7 +144,7 @@ func (w *clientStream) endSpan(err error) {
 //
 // Deprecated: Use [NewClientHandler] instead.
 func StreamClientInterceptor(opts ...Option) grpc.StreamClientInterceptor {
-	cfg := newConfig(opts, "client")
+	cfg := newConfig(opts)
 	tracer := cfg.TracerProvider.Tracer(
 		ScopeName,
 		trace.WithInstrumentationVersion(Version()),
@@ -251,7 +251,7 @@ func wrapServerStream(ctx context.Context, ss grpc.ServerStream, cfg *config) *s
 //
 // Deprecated: Use [NewServerHandler] instead.
 func StreamServerInterceptor(opts ...Option) grpc.StreamServerInterceptor {
-	cfg := newConfig(opts, "server")
+	cfg := newConfig(opts)
 	tracer := cfg.TracerProvider.Tracer(
 		ScopeName,
 		trace.WithInstrumentationVersion(Version()),
diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/metadata_supplier.go b/instrumentation/google.golang.org/grpc/otelgrpc/metadata_supplier.go
index 3aa37915d..6e67f0216 100644
--- a/instrumentation/google.golang.org/grpc/otelgrpc/metadata_supplier.go
+++ b/instrumentation/google.golang.org/grpc/otelgrpc/metadata_supplier.go
@@ -45,7 +45,7 @@ func (s *metadataSupplier) Keys() []string {
 // requests.
 // Deprecated: Unnecessary public func.
 func Inject(ctx context.Context, md *metadata.MD, opts ...Option) {
-	c := newConfig(opts, "")
+	c := newConfig(opts)
 	c.Propagators.Inject(ctx, &metadataSupplier{
 		metadata: md,
 	})
@@ -67,7 +67,7 @@ func inject(ctx context.Context, propagators propagation.TextMapPropagator) cont
 // This function is meant to be used on incoming requests.
 // Deprecated: Unnecessary public func.
 func Extract(ctx context.Context, md *metadata.MD, opts ...Option) (baggage.Baggage, trace.SpanContext) {
-	c := newConfig(opts, "")
+	c := newConfig(opts)
 	ctx = c.Propagators.Extract(ctx, &metadataSupplier{
 		metadata: md,
 	})
diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler.go b/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler.go
index 704c19c5b..a1686ef8b 100644
--- a/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler.go
+++ b/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler.go
@@ -13,9 +13,11 @@ import (
 	"google.golang.org/grpc/stats"
 	"google.golang.org/grpc/status"
 
+	"go.opentelemetry.io/otel"
 	"go.opentelemetry.io/otel/attribute"
 	"go.opentelemetry.io/otel/codes"
 	"go.opentelemetry.io/otel/metric"
+	"go.opentelemetry.io/otel/metric/noop"
 	semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
 	"go.opentelemetry.io/otel/trace"
 
@@ -33,12 +35,91 @@ type gRPCContext struct {
 
 type serverHandler struct {
 	*config
+
+	tracer trace.Tracer
+
+	duration metric.Float64Histogram
+	inSize   metric.Int64Histogram
+	outSize  metric.Int64Histogram
+	inMsg    metric.Int64Histogram
+	outMsg   metric.Int64Histogram
 }
 
 // NewServerHandler creates a stats.Handler for a gRPC server.
 func NewServerHandler(opts ...Option) stats.Handler {
-	h := &serverHandler{
-		config: newConfig(opts, "server"),
+	c := newConfig(opts)
+	h := &serverHandler{config: c}
+
+	h.tracer = c.TracerProvider.Tracer(
+		ScopeName,
+		trace.WithInstrumentationVersion(Version()),
+	)
+
+	meter := c.MeterProvider.Meter(
+		ScopeName,
+		metric.WithInstrumentationVersion(Version()),
+		metric.WithSchemaURL(semconv.SchemaURL),
+	)
+
+	var err error
+	h.duration, err = meter.Float64Histogram(
+		"rpc.server.duration",
+		metric.WithDescription("Measures the duration of inbound RPC."),
+		metric.WithUnit("ms"),
+	)
+	if err != nil {
+		otel.Handle(err)
+		if h.duration == nil {
+			h.duration = noop.Float64Histogram{}
+		}
+	}
+
+	h.inSize, err = meter.Int64Histogram(
+		"rpc.server.request.size",
+		metric.WithDescription("Measures size of RPC request messages (uncompressed)."),
+		metric.WithUnit("By"),
+	)
+	if err != nil {
+		otel.Handle(err)
+		if h.inSize == nil {
+			h.inSize = noop.Int64Histogram{}
+		}
+	}
+
+	h.outSize, err = meter.Int64Histogram(
+		"rpc.server.response.size",
+		metric.WithDescription("Measures size of RPC response messages (uncompressed)."),
+		metric.WithUnit("By"),
+	)
+	if err != nil {
+		otel.Handle(err)
+		if h.outSize == nil {
+			h.outSize = noop.Int64Histogram{}
+		}
+	}
+
+	h.inMsg, err = meter.Int64Histogram(
+		"rpc.server.requests_per_rpc",
+		metric.WithDescription("Measures the number of messages received per RPC. Should be 1 for all non-streaming RPCs."),
+		metric.WithUnit("{count}"),
+	)
+	if err != nil {
+		otel.Handle(err)
+		if h.inMsg == nil {
+			h.inMsg = noop.Int64Histogram{}
+		}
+	}
+
+	h.outMsg, err = meter.Int64Histogram(
+		"rpc.server.responses_per_rpc",
+		metric.WithDescription("Measures the number of messages received per RPC. Should be 1 for all non-streaming RPCs."),
+		metric.WithUnit("{count}"),
+	)
+	if err != nil {
+		otel.Handle(err)
+		if h.outMsg == nil {
+			h.outMsg = noop.Int64Histogram{}
+		}
 	}
 
 	return h
@@ -84,18 +165,96 @@ func (h *serverHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) cont
 
 // HandleRPC processes the RPC stats.
 func (h *serverHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
-	isServer := true
-	h.handleRPC(ctx, rs, isServer)
+	h.handleRPC(ctx, rs, h.duration, h.inSize, h.outSize, h.inMsg, h.outMsg, serverStatus)
 }
 
 type clientHandler struct {
 	*config
+
+	tracer trace.Tracer
+
+	duration metric.Float64Histogram
+	inSize   metric.Int64Histogram
+	outSize  metric.Int64Histogram
+	inMsg    metric.Int64Histogram
+	outMsg   metric.Int64Histogram
 }
 
 // NewClientHandler creates a stats.Handler for a gRPC client.
 func NewClientHandler(opts ...Option) stats.Handler {
-	h := &clientHandler{
-		config: newConfig(opts, "client"),
+	c := newConfig(opts)
+	h := &clientHandler{config: c}
+
+	h.tracer = c.TracerProvider.Tracer(
+		ScopeName,
+		trace.WithInstrumentationVersion(Version()),
+	)
+
+	meter := c.MeterProvider.Meter(
+		ScopeName,
+		metric.WithInstrumentationVersion(Version()),
+		metric.WithSchemaURL(semconv.SchemaURL),
+	)
+
+	var err error
+	h.duration, err = meter.Float64Histogram(
+		"rpc.client.duration",
+		metric.WithDescription("Measures the duration of inbound RPC."),
+		metric.WithUnit("ms"),
+	)
+	if err != nil {
+		otel.Handle(err)
+		if h.duration == nil {
+			h.duration = noop.Float64Histogram{}
+		}
+	}
+
+	h.outSize, err = meter.Int64Histogram(
+		"rpc.client.request.size",
+		metric.WithDescription("Measures size of RPC request messages (uncompressed)."),
+		metric.WithUnit("By"),
+	)
+	if err != nil {
+		otel.Handle(err)
+		if h.outSize == nil {
+			h.outSize = noop.Int64Histogram{}
+		}
+	}
+
+	h.inSize, err = meter.Int64Histogram(
+		"rpc.client.response.size",
+		metric.WithDescription("Measures size of RPC response messages (uncompressed)."),
+		metric.WithUnit("By"),
+	)
+	if err != nil {
+		otel.Handle(err)
+		if h.inSize == nil {
+			h.inSize = noop.Int64Histogram{}
+		}
+	}
+
+	h.outMsg, err = meter.Int64Histogram(
+		"rpc.client.requests_per_rpc",
+		metric.WithDescription("Measures the number of messages received per RPC. Should be 1 for all non-streaming RPCs."),
+		metric.WithUnit("{count}"),
+	)
+	if err != nil {
+		otel.Handle(err)
+		if h.outMsg == nil {
+			h.outMsg = noop.Int64Histogram{}
+		}
+	}
+
+	h.inMsg, err = meter.Int64Histogram(
+		"rpc.client.responses_per_rpc",
+		metric.WithDescription("Measures the number of messages received per RPC. Should be 1 for all non-streaming RPCs."),
+		metric.WithUnit("{count}"),
+	)
+	if err != nil {
+		otel.Handle(err)
+		if h.inMsg == nil {
+			h.inMsg = noop.Int64Histogram{}
+		}
 	}
 
 	return h
@@ -130,8 +289,12 @@ func (h *clientHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) cont
 
 // HandleRPC processes the RPC stats.
 func (h *clientHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
-	isServer := false
-	h.handleRPC(ctx, rs, isServer)
+	h.handleRPC(
+		ctx, rs, h.duration, h.inSize, h.outSize, h.inMsg, h.outMsg,
+		func(s *status.Status) (codes.Code, string) {
+			return codes.Error, s.Message()
+		},
+	)
 }
 
 // TagConn can attach some information to the given context.
@@ -144,7 +307,13 @@ func (h *clientHandler) HandleConn(context.Context, stats.ConnStats) {
 	// no-op
 }
 
-func (c *config) handleRPC(ctx context.Context, rs stats.RPCStats, isServer bool) { // nolint: revive  // isServer is not a control flag.
+func (c *config) handleRPC(
+	ctx context.Context,
+	rs stats.RPCStats,
+	duration metric.Float64Histogram,
+	inSize, outSize, inMsg, outMsg metric.Int64Histogram,
+	recordStatus func(*status.Status) (codes.Code, string),
+) {
 	gctx, _ := ctx.Value(gRPCContextKey{}).(*gRPCContext)
 	if gctx != nil && !gctx.record {
 		return
@@ -158,7 +327,7 @@ func (c *config) handleRPC(ctx context.Context, rs stats.RPCStats, isServer bool
 	case *stats.InPayload:
 		if gctx != nil {
 			messageId = atomic.AddInt64(&gctx.inMessages, 1)
-			c.rpcInBytes.Record(ctx, int64(rs.Length), metric.WithAttributes(gctx.metricAttrs...))
+			inSize.Record(ctx, int64(rs.Length), metric.WithAttributes(gctx.metricAttrs...))
 		}
 
 		if c.ReceivedEvent && span.IsRecording() {
@@ -174,7 +343,7 @@ func (c *config) handleRPC(ctx context.Context, rs stats.RPCStats, isServer bool
 	case *stats.OutPayload:
 		if gctx != nil {
 			messageId = atomic.AddInt64(&gctx.outMessages, 1)
-			c.rpcOutBytes.Record(ctx, int64(rs.Length), metric.WithAttributes(gctx.metricAttrs...))
+			outSize.Record(ctx, int64(rs.Length), metric.WithAttributes(gctx.metricAttrs...))
 		}
 
 		if c.SentEvent && span.IsRecording() {
@@ -206,12 +375,8 @@ func (c *config) handleRPC(ctx context.Context, rs stats.RPCStats, isServer bool
 		}
 		if span.IsRecording() {
 			if s != nil {
-				if isServer {
-					statusCode, msg := serverStatus(s)
-					span.SetStatus(statusCode, msg)
-				} else {
-					span.SetStatus(codes.Error, s.Message())
-				}
+				c, m := recordStatus(s)
+				span.SetStatus(c, m)
 			}
 			span.SetAttributes(rpcStatusAttr)
 			span.End()
@@ -230,10 +395,10 @@ func (c *config) handleRPC(ctx context.Context, rs stats.RPCStats, isServer bool
 		// Measure right before calling Record() to capture as much elapsed time as possible.
 		elapsedTime := float64(rs.EndTime.Sub(rs.BeginTime)) / float64(time.Millisecond)
 
-		c.rpcDuration.Record(ctx, elapsedTime, recordOpts...)
+		duration.Record(ctx, elapsedTime, recordOpts...)
 		if gctx != nil {
-			c.rpcInMessages.Record(ctx, atomic.LoadInt64(&gctx.inMessages), recordOpts...)
-			c.rpcOutMessages.Record(ctx, atomic.LoadInt64(&gctx.outMessages), recordOpts...)
+			inMsg.Record(ctx, atomic.LoadInt64(&gctx.inMessages), recordOpts...)
+			outMsg.Record(ctx, atomic.LoadInt64(&gctx.outMessages), recordOpts...)
 		}
 	default:
 		return


Test patch:
diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/config_test.go b/instrumentation/google.golang.org/grpc/otelgrpc/config_test.go
deleted file mode 100644
index 4d6192dcd..000000000
--- a/instrumentation/google.golang.org/grpc/otelgrpc/config_test.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright The OpenTelemetry Authors
-// SPDX-License-Identifier: Apache-2.0
-
-package otelgrpc
-
-import (
-	"context"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-
-	"go.opentelemetry.io/otel/metric"
-	"go.opentelemetry.io/otel/metric/embedded"
-)
-
-func TestNilInstruments(t *testing.T) {
-	mp := meterProvider{}
-	c := newConfig([]Option{WithMeterProvider(mp)}, "test")
-
-	ctx := context.Background()
-	assert.NotPanics(t, func() { c.rpcDuration.Record(ctx, 0) }, "rpcDuration")
-	assert.NotPanics(t, func() { c.rpcInBytes.Record(ctx, 0) }, "rpcInBytes")
-	assert.NotPanics(t, func() { c.rpcOutBytes.Record(ctx, 0) }, "rpcOutBytes")
-	assert.NotPanics(t, func() { c.rpcInMessages.Record(ctx, 0) }, "rpcInMessages")
-	assert.NotPanics(t, func() { c.rpcOutMessages.Record(ctx, 0) }, "rpcOutMessages")
-}
-
-type meterProvider struct {
-	embedded.MeterProvider
-}
-
-func (meterProvider) Meter(string, ...metric.MeterOption) metric.Meter {
-	return meter{}
-}
-
-type meter struct {
-	// Panic for non-implemented methods.
-	metric.Meter
-}
-
-func (meter) Int64Histogram(string, ...metric.Int64HistogramOption) (metric.Int64Histogram, error) {
-	return nil, assert.AnError
-}
-
-func (meter) Float64Histogram(string, ...metric.Float64HistogramOption) (metric.Float64Histogram, error) {
-	return nil, assert.AnError
-}
diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler_test.go b/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler_test.go
new file mode 100644
index 000000000..d6f71a5c0
--- /dev/null
+++ b/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler_test.go
@@ -0,0 +1,71 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package otelgrpc
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"go.opentelemetry.io/otel/metric"
+	"go.opentelemetry.io/otel/metric/embedded"
+)
+
+func TestNilInstruments(t *testing.T) {
+	mp := meterProvider{}
+	opts := []Option{WithMeterProvider(mp)}
+
+	ctx := context.Background()
+
+	t.Run("ServerHandler", func(t *testing.T) {
+		hIface := NewServerHandler(opts...)
+		require.NotNil(t, hIface, "handler")
+		require.IsType(t, (*serverHandler)(nil), hIface)
+
+		h := hIface.(*serverHandler)
+
+		assert.NotPanics(t, func() { h.duration.Record(ctx, 0) }, "duration")
+		assert.NotPanics(t, func() { h.inSize.Record(ctx, 0) }, "inSize")
+		assert.NotPanics(t, func() { h.outSize.Record(ctx, 0) }, "outSize")
+		assert.NotPanics(t, func() { h.inMsg.Record(ctx, 0) }, "inMsg")
+		assert.NotPanics(t, func() { h.outMsg.Record(ctx, 0) }, "outMsg")
+	})
+
+	t.Run("ClientHandler", func(t *testing.T) {
+		hIface := NewClientHandler(opts...)
+		require.NotNil(t, hIface, "handler")
+		require.IsType(t, (*clientHandler)(nil), hIface)
+
+		h := hIface.(*clientHandler)
+
+		assert.NotPanics(t, func() { h.duration.Record(ctx, 0) }, "duration")
+		assert.NotPanics(t, func() { h.inSize.Record(ctx, 0) }, "inSize")
+		assert.NotPanics(t, func() { h.outSize.Record(ctx, 0) }, "outSize")
+		assert.NotPanics(t, func() { h.inMsg.Record(ctx, 0) }, "inMsg")
+		assert.NotPanics(t, func() { h.outMsg.Record(ctx, 0) }, "outMsg")
+	})
+}
+
+type meterProvider struct {
+	embedded.MeterProvider
+}
+
+func (meterProvider) Meter(string, ...metric.MeterOption) metric.Meter {
+	return meter{}
+}
+
+type meter struct {
+	// Panic for non-implemented methods.
+	metric.Meter
+}
+
+func (meter) Int64Histogram(string, ...metric.Int64HistogramOption) (metric.Int64Histogram, error) {
+	return nil, assert.AnError
+}
+
+func (meter) Float64Histogram(string, ...metric.Float64HistogramOption) (metric.Float64Histogram, error) {
+	return nil, assert.AnError
+}