Skip to content

Commit

Permalink
update OpenCensus metric bridge to use the metric.Producer interface
Browse files Browse the repository at this point in the history
  • Loading branch information
dashpole committed Dec 15, 2022
1 parent 14a17b3 commit 7e521eb
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 16 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Expand Up @@ -15,6 +15,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added

- Add `Producer` interface and `Reader.RegisterProducer(Producer)` to `go.opentelemetry.io/otel/sdk/metric` to enable external metric Producers. (#3524)
- Add `NewMetricProducer` to `go.opentelemetry.io/otel/bridge/opencensus`, which can be used to pass OpenCensus metrics to an OpenTelemetry Reader. (#3541)

### Deprecated

- The `NewMetricExporter` in `go.opentelemetry.io/otel/bridge/opencensus` is deprecated. Use `NewMetricProducer` instead. (#3541)

## [1.11.2/0.34.0] 2022-12-05

Expand Down
29 changes: 29 additions & 0 deletions bridge/opencensus/metric.go
Expand Up @@ -22,6 +22,7 @@ import (

ocmetricdata "go.opencensus.io/metric/metricdata"
"go.opencensus.io/metric/metricexport"
"go.opencensus.io/metric/metricproducer"

"go.opentelemetry.io/otel"
internal "go.opentelemetry.io/otel/bridge/opencensus/internal/ocmetric"
Expand All @@ -33,6 +34,33 @@ import (

const scopeName = "go.opentelemetry.io/otel/bridge/opencensus"

type producer struct {
manager *metricproducer.Manager
}

// NewMetricExporter returns a metric.Producer that fetches metrics from
// OpenCensus.
func NewMetricProducer() metric.Producer {
return &producer{
manager: metricproducer.GlobalManager(),
}
}

func (p *producer) Produce(_ context.Context) ([]metricdata.ScopeMetrics, error) {
producers := p.manager.GetAll()
data := []*ocmetricdata.Metric{}
for _, ocProducer := range producers {
data = append(data, ocProducer.Read()...)
}
otelmetrics, err := internal.ConvertMetrics(data)
return []metricdata.ScopeMetrics{{
Scope: instrumentation.Scope{
Name: scopeName,
},
Metrics: otelmetrics,
}}, err
}

// exporter implements the OpenCensus metric Exporter interface using an
// OpenTelemetry base exporter.
type exporter struct {
Expand All @@ -42,6 +70,7 @@ type exporter struct {

// NewMetricExporter returns an OpenCensus exporter that exports to an
// OpenTelemetry (push) exporter.
// Deprecated: Use NewMetricProducer instead.
func NewMetricExporter(base metric.Exporter, res *resource.Resource) metricexport.Exporter {
return &exporter{base: base, res: res}
}
Expand Down
131 changes: 131 additions & 0 deletions bridge/opencensus/metric_test.go
Expand Up @@ -22,6 +22,7 @@ import (

"github.com/stretchr/testify/require"
ocmetricdata "go.opencensus.io/metric/metricdata"
"go.opencensus.io/metric/metricproducer"
ocresource "go.opencensus.io/resource"

"go.opentelemetry.io/otel/attribute"
Expand All @@ -32,6 +33,136 @@ import (
"go.opentelemetry.io/otel/sdk/resource"
)

func TestMetricProducer(t *testing.T) {
now := time.Now()
for _, tc := range []struct {
desc string
input []*ocmetricdata.Metric
expected metricdata.ScopeMetrics
expectErr bool
}{
{
desc: "empty",
expected: metricdata.ScopeMetrics{
Scope: instrumentation.Scope{
Name: scopeName,
},
},
},
{
desc: "success",
input: []*ocmetricdata.Metric{
{
Resource: &ocresource.Resource{
Labels: map[string]string{
"R1": "V1",
"R2": "V2",
},
},
TimeSeries: []*ocmetricdata.TimeSeries{
{
StartTime: now,
Points: []ocmetricdata.Point{
{Value: int64(123), Time: now},
},
},
},
},
},
expected: metricdata.ScopeMetrics{
Scope: instrumentation.Scope{
Name: scopeName,
},
Metrics: []metricdata.Metrics{
{
Data: metricdata.Gauge[int64]{
DataPoints: []metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(),
StartTime: now,
Time: now,
Value: 123,
},
},
},
},
},
},
},
{
desc: "partial success",
input: []*ocmetricdata.Metric{
{
Descriptor: ocmetricdata.Descriptor{
Name: "foo.com/bad-point",
Description: "a bad type",
Unit: ocmetricdata.UnitDimensionless,
Type: ocmetricdata.TypeGaugeDistribution,
},
},
{
Resource: &ocresource.Resource{
Labels: map[string]string{
"R1": "V1",
"R2": "V2",
},
},
TimeSeries: []*ocmetricdata.TimeSeries{
{
StartTime: now,
Points: []ocmetricdata.Point{
{Value: int64(123), Time: now},
},
},
},
},
},
expected: metricdata.ScopeMetrics{
Scope: instrumentation.Scope{
Name: scopeName,
},
Metrics: []metricdata.Metrics{
{
Data: metricdata.Gauge[int64]{
DataPoints: []metricdata.DataPoint[int64]{
{
Attributes: attribute.NewSet(),
StartTime: now,
Time: now,
Value: 123,
},
},
},
},
},
},
expectErr: true,
},
} {
t.Run(tc.desc, func(t *testing.T) {
fakeProducer := &fakeOCProducer{metrics: tc.input}
metricproducer.GlobalManager().AddProducer(fakeProducer)
defer metricproducer.GlobalManager().DeleteProducer(fakeProducer)
output, err := NewMetricProducer().Produce(context.Background())
if tc.expectErr {
require.Error(t, err)
} else {
require.Nil(t, err)
}
require.Equal(t, len(output), 1)
metricdatatest.AssertEqual(t, tc.expected, output[0])
})
}
}

type fakeOCProducer struct {
metrics []*ocmetricdata.Metric
}

func (f *fakeOCProducer) Read() []*ocmetricdata.Metric {
return f.metrics
}

func TestPushMetricsExporter(t *testing.T) {
now := time.Now()
for _, tc := range []struct {
Expand Down
22 changes: 6 additions & 16 deletions example/opencensus/main.go
Expand Up @@ -22,7 +22,6 @@ import (

ocmetric "go.opencensus.io/metric"
"go.opencensus.io/metric/metricdata"
"go.opencensus.io/metric/metricexport"
"go.opencensus.io/metric/metricproducer"
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
Expand All @@ -34,7 +33,6 @@ import (
"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)

Expand Down Expand Up @@ -103,20 +101,12 @@ func tracing(otExporter sdktrace.SpanExporter) {
// monitoring demonstrates creating an IntervalReader using the OpenTelemetry
// exporter to send metrics to the exporter by using either an OpenCensus
// registry or an OpenCensus view.
func monitoring(otExporter metric.Exporter) error {
log.Println("Using the OpenTelemetry stdoutmetric exporter to export OpenCensus metrics. This allows routing telemetry from both OpenTelemetry and OpenCensus to a single exporter.")
ocExporter := opencensus.NewMetricExporter(otExporter, resource.Default())
intervalReader, err := metricexport.NewIntervalReader(&metricexport.Reader{}, ocExporter)
if err != nil {
return fmt.Errorf("failed to create interval reader: %w", err)
}
intervalReader.ReportingInterval = 10 * time.Second
log.Println("Emitting metrics using OpenCensus APIs. These should be printed out using the OpenTelemetry stdoutmetric exporter.")
err = intervalReader.Start()
if err != nil {
return fmt.Errorf("failed to start interval reader: %w", err)
}
defer intervalReader.Stop()
func monitoring(exporter metric.Exporter) error {
log.Println("Adding the OpenCensus metric Producer to an OpenTelemetry Reader to export OpenCensus metrics using the OpenTelemetry stdout exporter.")
reader := metric.NewPeriodicReader(exporter)
// Register the OpenCensus metric Producer to add metrics from OpenCensus to the output.
reader.RegisterProducer(opencensus.NewMetricProducer())
metric.NewMeterProvider(metric.WithReader(reader))

log.Println("Registering a gauge metric using an OpenCensus registry.")
r := ocmetric.NewRegistry()
Expand Down

0 comments on commit 7e521eb

Please sign in to comment.