mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-01-07 23:02:15 +02:00
81b2a33e1b
Resolve https://github.com/open-telemetry/opentelemetry-go/issues/5249 ### Spec > exemplar_reservoir: A functional type that generates an exemplar reservoir a MeterProvider will use when storing exemplars. This functional type needs to be a factory or callback similar to aggregation selection functionality which allows different reservoirs to be chosen by the aggregation. > Users can provide an exemplar_reservoir, but it is up to their discretion. Therefore, the stream configuration parameter needs to be structured to accept an exemplar_reservoir, but MUST NOT obligate a user to provide one. If the user does not provide an exemplar_reservoir value, the MeterProvider MUST apply a [default exemplar reservoir](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#exemplar-defaults). Also, > the reservoir MUST be given the Attributes associated with its timeseries point either at construction so that additional sampling performed by the reservoir has access to all attributes from a measurement in the "offer" method. ### Changes In sdk/metric/exemplar, add: * `exemplar.ReservoirProvider` * `exemplar.HistogramReservoirProvider` * `exemplar.FixedSizeReservoirProvider` In sdk/metric, add: * `metric.ExemplarReservoirProviderSelector` (func Aggregation -> ReservoirProvider) * `metric.DefaultExemplarReservoirProviderSelector` (our default implementation) * `ExemplarReservoirProviderSelector` added to `metric.Stream` Note: the only required public types are `metric.ExemplarReservoirProviderSelector` and `ExemplarReservoirProviderSelector` in `metric.Stream`. Others are for convenience and readability. ### Alternatives considered #### Add ExemplarReservoirProvider directly to metric.Stream, instead of ExemplarReservoirProviderSelector This would mean users can configure a `func() exemplar.Reservoir` instead of a `func(Aggregation) func() exemplar.Reservoir`. I don't think this complies with the statement: `This functional type needs to be a factory or callback similar to aggregation selection functionality which allows different reservoirs to be chosen by the aggregation.`. I'm interpreting "allows different reservoirs to be chosen by the aggregation" as meaning "allows different reservoirs to be chosen **based on the** aggregation", rather than meaning that the aggregation is somehow choosing the reservoir. ### Future work There is some refactoring I plan to do after this to simplify the interaction between the internal/aggregate and exemplar package. I've omitted that from this PR to keep the diff smaller. --------- Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com> Co-authored-by: Robert Pająk <pellared@hotmail.com>
287 lines
8.2 KiB
Go
287 lines
8.2 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package metric_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"regexp"
|
|
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/sdk/instrumentation"
|
|
"go.opentelemetry.io/otel/sdk/metric"
|
|
"go.opentelemetry.io/otel/sdk/metric/exemplar"
|
|
"go.opentelemetry.io/otel/sdk/resource"
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
|
)
|
|
|
|
// To enable metrics in your application using the SDK,
|
|
// you'll need to have an initialized [MeterProvider]
|
|
// that will let you create a [go.opentelemetry.io/otel/metric.Meter].
|
|
//
|
|
// Here's how you might initialize a metrics provider.
|
|
func Example() {
|
|
// Create resource.
|
|
res, err := resource.Merge(resource.Default(),
|
|
resource.NewWithAttributes(semconv.SchemaURL,
|
|
semconv.ServiceName("my-service"),
|
|
semconv.ServiceVersion("0.1.0"),
|
|
))
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
// This reader is used as a stand-in for a reader that will actually export
|
|
// data. See https://pkg.go.dev/go.opentelemetry.io/otel/exporters for
|
|
// exporters that can be used as or with readers.
|
|
reader := metric.NewManualReader()
|
|
|
|
// Create a meter provider.
|
|
// You can pass this instance directly to your instrumented code if it
|
|
// accepts a MeterProvider instance.
|
|
meterProvider := metric.NewMeterProvider(
|
|
metric.WithResource(res),
|
|
metric.WithReader(reader),
|
|
)
|
|
|
|
// Handle shutdown properly so that nothing leaks.
|
|
defer func() {
|
|
err := meterProvider.Shutdown(context.Background())
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
}()
|
|
|
|
// Register as global meter provider so that it can be used via otel.Meter
|
|
// and accessed using otel.GetMeterProvider.
|
|
// Most instrumentation libraries use the global meter provider as default.
|
|
// If the global meter provider is not set then a no-op implementation
|
|
// is used, which fails to generate data.
|
|
otel.SetMeterProvider(meterProvider)
|
|
}
|
|
|
|
func ExampleView() {
|
|
// The NewView function provides convenient creation of common Views
|
|
// construction. However, it is limited in what it can create.
|
|
//
|
|
// When NewView is not able to provide the functionally needed, a custom
|
|
// View can be constructed directly. Here a custom View is constructed that
|
|
// uses Go's regular expression matching to ensure all data stream names
|
|
// have a suffix of the units it uses.
|
|
|
|
re := regexp.MustCompile(`[._](ms|byte)$`)
|
|
var view metric.View = func(i metric.Instrument) (metric.Stream, bool) {
|
|
// In a custom View function, you need to explicitly copy
|
|
// the name, description, and unit.
|
|
s := metric.Stream{Name: i.Name, Description: i.Description, Unit: i.Unit}
|
|
// Any instrument that does not have a unit suffix defined, but has a
|
|
// dimensional unit defined, update the name with a unit suffix.
|
|
if re.MatchString(i.Name) {
|
|
return s, false
|
|
}
|
|
switch i.Unit {
|
|
case "ms":
|
|
s.Name += ".ms"
|
|
case "By":
|
|
s.Name += ".byte"
|
|
default:
|
|
return s, false
|
|
}
|
|
return s, true
|
|
}
|
|
|
|
// The created view can then be registered with the OpenTelemetry metric
|
|
// SDK using the WithView option.
|
|
_ = metric.NewMeterProvider(
|
|
metric.WithView(view),
|
|
)
|
|
|
|
// Below is an example of how the view will
|
|
// function in the SDK for certain instruments.
|
|
stream, _ := view(metric.Instrument{
|
|
Name: "computation.time.ms",
|
|
Unit: "ms",
|
|
})
|
|
fmt.Println("name:", stream.Name)
|
|
|
|
stream, _ = view(metric.Instrument{
|
|
Name: "heap.size",
|
|
Unit: "By",
|
|
})
|
|
fmt.Println("name:", stream.Name)
|
|
// Output:
|
|
// name: computation.time.ms
|
|
// name: heap.size.byte
|
|
}
|
|
|
|
func ExampleNewView() {
|
|
// Create a view that renames the "latency" instrument from the v0.34.0
|
|
// version of the "http" instrumentation library as "request.latency".
|
|
view := metric.NewView(metric.Instrument{
|
|
Name: "latency",
|
|
Scope: instrumentation.Scope{
|
|
Name: "http",
|
|
Version: "0.34.0",
|
|
},
|
|
}, metric.Stream{Name: "request.latency"})
|
|
|
|
// The created view can then be registered with the OpenTelemetry metric
|
|
// SDK using the WithView option.
|
|
_ = metric.NewMeterProvider(
|
|
metric.WithView(view),
|
|
)
|
|
|
|
// Below is an example of how the view will
|
|
// function in the SDK for certain instruments.
|
|
stream, _ := view(metric.Instrument{
|
|
Name: "latency",
|
|
Description: "request latency",
|
|
Unit: "ms",
|
|
Kind: metric.InstrumentKindCounter,
|
|
Scope: instrumentation.Scope{
|
|
Name: "http",
|
|
Version: "0.34.0",
|
|
SchemaURL: "https://opentelemetry.io/schemas/1.0.0",
|
|
},
|
|
})
|
|
fmt.Println("name:", stream.Name)
|
|
fmt.Println("description:", stream.Description)
|
|
fmt.Println("unit:", stream.Unit)
|
|
// Output:
|
|
// name: request.latency
|
|
// description: request latency
|
|
// unit: ms
|
|
}
|
|
|
|
func ExampleNewView_wildcard() {
|
|
// Create a view that sets unit to milliseconds for any instrument with a
|
|
// name suffix of ".ms".
|
|
view := metric.NewView(
|
|
metric.Instrument{Name: "*.ms"},
|
|
metric.Stream{Unit: "ms"},
|
|
)
|
|
|
|
// The created view can then be registered with the OpenTelemetry metric
|
|
// SDK using the WithView option.
|
|
_ = metric.NewMeterProvider(
|
|
metric.WithView(view),
|
|
)
|
|
|
|
// Below is an example of how the view will
|
|
// function in the SDK for certain instruments.
|
|
stream, _ := view(metric.Instrument{
|
|
Name: "computation.time.ms",
|
|
Unit: "1",
|
|
})
|
|
fmt.Println("name:", stream.Name)
|
|
fmt.Println("unit:", stream.Unit)
|
|
// Output:
|
|
// name: computation.time.ms
|
|
// unit: ms
|
|
}
|
|
|
|
func ExampleNewView_drop() {
|
|
// Create a view that drops the "latency" instrument from the "http"
|
|
// instrumentation library.
|
|
view := metric.NewView(
|
|
metric.Instrument{
|
|
Name: "latency",
|
|
Scope: instrumentation.Scope{Name: "http"},
|
|
},
|
|
metric.Stream{Aggregation: metric.AggregationDrop{}},
|
|
)
|
|
|
|
// The created view can then be registered with the OpenTelemetry metric
|
|
// SDK using the WithView option.
|
|
_ = metric.NewMeterProvider(
|
|
metric.WithView(view),
|
|
)
|
|
}
|
|
|
|
func ExampleNewView_attributeFilter() {
|
|
// Create a view that removes the "http.request.method" attribute recorded
|
|
// by the "latency" instrument from the "http" instrumentation library.
|
|
view := metric.NewView(
|
|
metric.Instrument{
|
|
Name: "latency",
|
|
Scope: instrumentation.Scope{Name: "http"},
|
|
},
|
|
metric.Stream{AttributeFilter: attribute.NewDenyKeysFilter("http.request.method")},
|
|
)
|
|
|
|
// The created view can then be registered with the OpenTelemetry metric
|
|
// SDK using the WithView option.
|
|
_ = metric.NewMeterProvider(
|
|
metric.WithView(view),
|
|
)
|
|
}
|
|
|
|
func ExampleNewView_exponentialHistogram() {
|
|
// Create a view that makes the "latency" instrument from the "http"
|
|
// instrumentation library to be reported as an exponential histogram.
|
|
view := metric.NewView(
|
|
metric.Instrument{
|
|
Name: "latency",
|
|
Scope: instrumentation.Scope{Name: "http"},
|
|
},
|
|
metric.Stream{
|
|
Aggregation: metric.AggregationBase2ExponentialHistogram{
|
|
MaxSize: 160,
|
|
MaxScale: 20,
|
|
},
|
|
},
|
|
)
|
|
|
|
// The created view can then be registered with the OpenTelemetry metric
|
|
// SDK using the WithView option.
|
|
_ = metric.NewMeterProvider(
|
|
metric.WithView(view),
|
|
)
|
|
}
|
|
|
|
func ExampleNewView_exemplarreservoirproviderselector() {
|
|
// Create a view that makes all metrics use a different exemplar reservoir.
|
|
view := metric.NewView(
|
|
metric.Instrument{Name: "*"},
|
|
metric.Stream{
|
|
ExemplarReservoirProviderSelector: func(agg metric.Aggregation) exemplar.ReservoirProvider {
|
|
// This example uses a fixed-size reservoir with a size of 10
|
|
// for explicit bucket histograms instead of the default
|
|
// bucket-aligned reservoir.
|
|
if _, ok := agg.(metric.AggregationExplicitBucketHistogram); ok {
|
|
return exemplar.FixedSizeReservoirProvider(10)
|
|
}
|
|
// Fall back to the default reservoir otherwise.
|
|
return metric.DefaultExemplarReservoirProviderSelector(agg)
|
|
},
|
|
},
|
|
)
|
|
|
|
// The created view can then be registered with the OpenTelemetry metric
|
|
// SDK using the WithView option.
|
|
_ = metric.NewMeterProvider(
|
|
metric.WithView(view),
|
|
)
|
|
}
|
|
|
|
func ExampleWithExemplarFilter_disabled() {
|
|
// Use exemplar.AlwaysOffFilter to disable exemplar collection.
|
|
_ = metric.NewMeterProvider(
|
|
metric.WithExemplarFilter(exemplar.AlwaysOffFilter),
|
|
)
|
|
}
|
|
|
|
func ExampleWithExemplarFilter_custom() {
|
|
// Create a custom filter function that only offers measurements if the
|
|
// context has an error.
|
|
customFilter := func(ctx context.Context) bool {
|
|
return ctx.Err() != nil
|
|
}
|
|
_ = metric.NewMeterProvider(
|
|
metric.WithExemplarFilter(customFilter),
|
|
)
|
|
}
|