Skip to content

Commit

Permalink
Add support to configure views with config.NewSdk (open-telemetry#5654)
Browse files Browse the repository at this point in the history
  • Loading branch information
bogdandrutu authored Jun 4, 2024
1 parent fbbdb68 commit e8540b2
Show file tree
Hide file tree
Showing 3 changed files with 618 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added

- The `go.opentelemetry.io/contrib/config` add support to configure periodic reader interval and timeout. (#5661)
- Add support to configure views when creating MeterProvider using the config package. (#5654)

### Fixed

Expand Down
162 changes: 161 additions & 1 deletion config/metric.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,22 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
otelprom "go.opentelemetry.io/otel/exporters/prometheus"
"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/noop"
"go.opentelemetry.io/otel/sdk/instrumentation"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
)

var zeroScope instrumentation.Scope

const instrumentKindUndefined = sdkmetric.InstrumentKind(0)

func meterProvider(cfg configOptions, res *resource.Resource) (metric.MeterProvider, shutdownFunc, error) {
if cfg.opentelemetryConfig.MeterProvider == nil {
return noop.NewMeterProvider(), noopShutdown, nil
Expand All @@ -45,6 +51,15 @@ func meterProvider(cfg configOptions, res *resource.Resource) (metric.MeterProvi
errs = append(errs, err)
}
}
for _, vw := range cfg.opentelemetryConfig.MeterProvider.Views {
v, err := view(vw)
if err == nil {
opts = append(opts, sdkmetric.WithView(v))
} else {
errs = append(errs, err)
}
}

if len(errs) > 0 {
return noop.NewMeterProvider(), noopShutdown, errors.Join(errs...)
}
Expand Down Expand Up @@ -156,7 +171,7 @@ func otlpHTTPMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmet
}

func otlpGRPCMetricExporter(ctx context.Context, otlpConfig *OTLPMetric) (sdkmetric.Exporter, error) {
opts := []otlpmetricgrpc.Option{}
var opts []otlpmetricgrpc.Option

if len(otlpConfig.Endpoint) > 0 {
u, err := url.ParseRequestURI(otlpConfig.Endpoint)
Expand Down Expand Up @@ -266,3 +281,148 @@ func (rws readerWithServer) Shutdown(ctx context.Context) error {
rws.server.Shutdown(ctx),
)
}

func view(v View) (sdkmetric.View, error) {
if v.Selector == nil {
return nil, errors.New("view: no selector provided")
}

inst, err := instrument(*v.Selector)
if err != nil {
return nil, err
}

return sdkmetric.NewView(inst, stream(v.Stream)), nil
}

func instrument(vs ViewSelector) (sdkmetric.Instrument, error) {
kind, err := instrumentKind(vs.InstrumentType)
if err != nil {
return sdkmetric.Instrument{}, fmt.Errorf("view_selector: %w", err)
}
inst := sdkmetric.Instrument{
Name: strOrEmpty(vs.InstrumentName),
Unit: strOrEmpty(vs.Unit),
Kind: kind,
Scope: instrumentation.Scope{
Name: strOrEmpty(vs.MeterName),
Version: strOrEmpty(vs.MeterVersion),
SchemaURL: strOrEmpty(vs.MeterSchemaUrl),
},
}

if instrumentIsEmpty(inst) {
return sdkmetric.Instrument{}, errors.New("view_selector: empty selector not supporter")
}
return inst, nil
}

func stream(vs *ViewStream) sdkmetric.Stream {
if vs == nil {
return sdkmetric.Stream{}
}

return sdkmetric.Stream{
Name: strOrEmpty(vs.Name),
Description: strOrEmpty(vs.Description),
Aggregation: aggregation(vs.Aggregation),
AttributeFilter: attributeFilter(vs.AttributeKeys),
}
}

func attributeFilter(attributeKeys []string) attribute.Filter {
var attrKeys []attribute.Key
for _, attrStr := range attributeKeys {
attrKeys = append(attrKeys, attribute.Key(attrStr))
}
return attribute.NewAllowKeysFilter(attrKeys...)
}

func aggregation(aggr *ViewStreamAggregation) sdkmetric.Aggregation {
if aggr == nil {
return nil
}

if aggr.Base2ExponentialBucketHistogram != nil {
return sdkmetric.AggregationBase2ExponentialHistogram{
MaxSize: int32(intOrZero(aggr.Base2ExponentialBucketHistogram.MaxSize)),
MaxScale: int32(intOrZero(aggr.Base2ExponentialBucketHistogram.MaxScale)),
// Need to negate because config has the positive action RecordMinMax.
NoMinMax: !boolOrFalse(aggr.Base2ExponentialBucketHistogram.RecordMinMax),
}
}
if aggr.Default != nil {
// TODO: Understand what to set here.
return nil
}
if aggr.Drop != nil {
return sdkmetric.AggregationDrop{}
}
if aggr.ExplicitBucketHistogram != nil {
return sdkmetric.AggregationExplicitBucketHistogram{
Boundaries: aggr.ExplicitBucketHistogram.Boundaries,
// Need to negate because config has the positive action RecordMinMax.
NoMinMax: !boolOrFalse(aggr.ExplicitBucketHistogram.RecordMinMax),
}
}
if aggr.LastValue != nil {
return sdkmetric.AggregationLastValue{}
}
if aggr.Sum != nil {
return sdkmetric.AggregationSum{}
}
return nil
}

func instrumentKind(vsit *ViewSelectorInstrumentType) (sdkmetric.InstrumentKind, error) {
if vsit == nil {
// Equivalent to instrumentKindUndefined.
return instrumentKindUndefined, nil
}

switch *vsit {
case ViewSelectorInstrumentTypeCounter:
return sdkmetric.InstrumentKindCounter, nil
case ViewSelectorInstrumentTypeUpDownCounter:
return sdkmetric.InstrumentKindUpDownCounter, nil
case ViewSelectorInstrumentTypeHistogram:
return sdkmetric.InstrumentKindHistogram, nil
case ViewSelectorInstrumentTypeObservableCounter:
return sdkmetric.InstrumentKindObservableCounter, nil
case ViewSelectorInstrumentTypeObservableUpDownCounter:
return sdkmetric.InstrumentKindObservableUpDownCounter, nil
case ViewSelectorInstrumentTypeObservableGauge:
return sdkmetric.InstrumentKindObservableGauge, nil
}

return instrumentKindUndefined, errors.New("instrument_type: invalid value")
}

func instrumentIsEmpty(i sdkmetric.Instrument) bool {
return i.Name == "" &&
i.Description == "" &&
i.Kind == instrumentKindUndefined &&
i.Unit == "" &&
i.Scope == zeroScope
}

func boolOrFalse(pBool *bool) bool {
if pBool == nil {
return false
}
return *pBool
}

func intOrZero(pInt *int) int {
if pInt == nil {
return 0
}
return *pInt
}

func strOrEmpty(pStr *string) string {
if pStr == nil {
return ""
}
return *pStr
}
Loading

0 comments on commit e8540b2

Please sign in to comment.