1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2024-12-22 20:06:07 +02:00
opentelemetry-go/bridge/opencensus/exporter.go
Tyler Yahn 1f5b159161
Use already enabled revive linter and add depguard (#2883)
* Refactor golangci-lint conf

Order settings alphabetically.

* Add revive settings to golangci conf

* Check blank imports

* Check bool-literal-in-expr

* Check constant-logical-expr

* Check context-as-argument

* Check context-key-type

* Check deep-exit

* Check defer

* Check dot-imports

* Check duplicated-imports

* Check early-return

* Check empty-block

* Check empty-lines

* Check error-naming

* Check error-return

* Check error-strings

* Check errorf

* Stop ignoring context first arg in tests

* Check exported comments

* Check flag-parameter

* Check identical branches

* Check if-return

* Check increment-decrement

* Check indent-error-flow

* Check deny list of go imports

* Check import shadowing

* Check package comments

* Check range

* Check range val in closure

* Check range val address

* Check redefines builtin id

* Check string-format

* Check struct tag

* Check superfluous else

* Check time equal

* Check var naming

* Check var declaration

* Check unconditional recursion

* Check unexported return

* Check unhandled errors

* Check unnecessary stmt

* Check unnecessary break

* Check waitgroup by value

* Exclude deep-exit check in example*_test.go files
2022-05-19 15:15:07 -05:00

187 lines
5.8 KiB
Go

// 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.
package opencensus // import "go.opentelemetry.io/otel/bridge/opencensus"
import (
"context"
"errors"
"fmt"
"sync"
"time"
"go.opencensus.io/metric/metricdata"
"go.opencensus.io/metric/metricexport"
ocresource "go.opencensus.io/resource"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric/instrument"
"go.opentelemetry.io/otel/metric/unit"
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/metric/export"
"go.opentelemetry.io/otel/sdk/metric/export/aggregation"
"go.opentelemetry.io/otel/sdk/metric/number"
"go.opentelemetry.io/otel/sdk/metric/sdkapi"
"go.opentelemetry.io/otel/sdk/resource"
)
var errConversion = errors.New("unable to convert from OpenCensus to OpenTelemetry")
// NewMetricExporter returns an OpenCensus exporter that exports to an
// OpenTelemetry exporter.
func NewMetricExporter(base export.Exporter) metricexport.Exporter {
return &exporter{base: base}
}
// exporter implements the OpenCensus metric Exporter interface using an
// OpenTelemetry base exporter.
type exporter struct {
base export.Exporter
}
// ExportMetrics implements the OpenCensus metric Exporter interface.
func (e *exporter) ExportMetrics(ctx context.Context, metrics []*metricdata.Metric) error {
res := resource.Empty()
if len(metrics) != 0 {
res = convertResource(metrics[0].Resource)
}
return e.base.Export(ctx, res, &censusLibraryReader{metrics: metrics})
}
type censusLibraryReader struct {
metrics []*metricdata.Metric
}
func (r censusLibraryReader) ForEach(readerFunc func(instrumentation.Library, export.Reader) error) error {
return readerFunc(instrumentation.Library{
Name: "OpenCensus Bridge",
}, &metricReader{metrics: r.metrics})
}
type metricReader struct {
// RWMutex implements locking for the `Reader` interface.
sync.RWMutex
metrics []*metricdata.Metric
}
var _ export.Reader = &metricReader{}
// ForEach iterates through the metrics data, synthesizing an
// export.Record with the appropriate aggregation for the exporter.
func (d *metricReader) ForEach(_ aggregation.TemporalitySelector, f func(export.Record) error) error {
for _, m := range d.metrics {
descriptor, err := convertDescriptor(m.Descriptor)
if err != nil {
otel.Handle(err)
continue
}
for _, ts := range m.TimeSeries {
if len(ts.Points) == 0 {
continue
}
attrs, err := convertAttrs(m.Descriptor.LabelKeys, ts.LabelValues)
if err != nil {
otel.Handle(err)
continue
}
err = recordAggregationsFromPoints(
ts.Points,
func(agg aggregation.Aggregation, end time.Time) error {
return f(export.NewRecord(
&descriptor,
&attrs,
agg,
ts.StartTime,
end,
))
})
if err != nil && !errors.Is(err, aggregation.ErrNoData) {
return err
}
}
}
return nil
}
// convertAttrs converts from OpenCensus attribute keys and values to an
// OpenTelemetry attribute Set.
func convertAttrs(keys []metricdata.LabelKey, values []metricdata.LabelValue) (attribute.Set, error) {
if len(keys) != len(values) {
return attribute.NewSet(), fmt.Errorf("%w different number of label keys (%d) and values (%d)", errConversion, len(keys), len(values))
}
attrs := []attribute.KeyValue{}
for i, lv := range values {
if !lv.Present {
continue
}
attrs = append(attrs, attribute.KeyValue{
Key: attribute.Key(keys[i].Key),
Value: attribute.StringValue(lv.Value),
})
}
return attribute.NewSet(attrs...), nil
}
// convertResource converts an OpenCensus Resource to an OpenTelemetry Resource
// Note: the ocresource.Resource Type field is not used.
func convertResource(res *ocresource.Resource) *resource.Resource {
attrs := []attribute.KeyValue{}
if res == nil {
return nil
}
for k, v := range res.Labels {
attrs = append(attrs, attribute.KeyValue{Key: attribute.Key(k), Value: attribute.StringValue(v)})
}
return resource.NewSchemaless(attrs...)
}
// convertDescriptor converts an OpenCensus Descriptor to an OpenTelemetry Descriptor.
func convertDescriptor(ocDescriptor metricdata.Descriptor) (sdkapi.Descriptor, error) {
var (
nkind number.Kind
ikind sdkapi.InstrumentKind
)
switch ocDescriptor.Type {
case metricdata.TypeGaugeInt64:
nkind = number.Int64Kind
ikind = sdkapi.GaugeObserverInstrumentKind
case metricdata.TypeGaugeFloat64:
nkind = number.Float64Kind
ikind = sdkapi.GaugeObserverInstrumentKind
case metricdata.TypeCumulativeInt64:
nkind = number.Int64Kind
ikind = sdkapi.CounterObserverInstrumentKind
case metricdata.TypeCumulativeFloat64:
nkind = number.Float64Kind
ikind = sdkapi.CounterObserverInstrumentKind
default:
// Includes TypeGaugeDistribution, TypeCumulativeDistribution, TypeSummary
return sdkapi.Descriptor{}, fmt.Errorf("%w; descriptor type: %v", errConversion, ocDescriptor.Type)
}
opts := []instrument.Option{
instrument.WithDescription(ocDescriptor.Description),
}
switch ocDescriptor.Unit {
case metricdata.UnitDimensionless:
opts = append(opts, instrument.WithUnit(unit.Dimensionless))
case metricdata.UnitBytes:
opts = append(opts, instrument.WithUnit(unit.Bytes))
case metricdata.UnitMilliseconds:
opts = append(opts, instrument.WithUnit(unit.Milliseconds))
}
cfg := instrument.NewConfig(opts...)
return sdkapi.NewDescriptor(ocDescriptor.Name, ikind, nkind, cfg.Description(), cfg.Unit()), nil
}