mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-06-06 23:16:14 +02:00
[metrics] standardize/simplify export pipeline setup (#395)
* Introduce simplified export pipeline setup for stdout * Standardize dogstatsd,stdout,prometheus calling. * Creates NewRawExporter, NewExportPipeline, InstallNewPipeline methods. * Uses Options rather than Config throughout for options. * fix merge conflicts. Co-authored-by: Liz Fong-Jones <elizabeth@ctyalcove.org>
This commit is contained in:
parent
5eb457a119
commit
067aa9e142
@ -5,7 +5,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
@ -15,9 +14,6 @@ import (
|
|||||||
"go.opentelemetry.io/otel/api/key"
|
"go.opentelemetry.io/otel/api/key"
|
||||||
"go.opentelemetry.io/otel/exporter/metric/stdout"
|
"go.opentelemetry.io/otel/exporter/metric/stdout"
|
||||||
metrictest "go.opentelemetry.io/otel/internal/metric"
|
metrictest "go.opentelemetry.io/otel/internal/metric"
|
||||||
"go.opentelemetry.io/otel/sdk/metric/batcher/ungrouped"
|
|
||||||
"go.opentelemetry.io/otel/sdk/metric/controller/push"
|
|
||||||
"go.opentelemetry.io/otel/sdk/metric/selector/simple"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDirect(t *testing.T) {
|
func TestDirect(t *testing.T) {
|
||||||
@ -207,26 +203,13 @@ func TestDefaultSDK(t *testing.T) {
|
|||||||
counter.Add(ctx, 1, labels1)
|
counter.Add(ctx, 1, labels1)
|
||||||
|
|
||||||
in, out := io.Pipe()
|
in, out := io.Pipe()
|
||||||
// TODO this should equal a stdout.NewPipeline(), use it.
|
pusher, err := stdout.InstallNewPipeline(stdout.Options{
|
||||||
// Consider also moving the io.Pipe() and go func() call
|
|
||||||
// below into a test helper somewhere.
|
|
||||||
sdk := func(options stdout.Options) *push.Controller {
|
|
||||||
selector := simple.NewWithInexpensiveMeasure()
|
|
||||||
exporter, err := stdout.New(options)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
batcher := ungrouped.New(selector, true)
|
|
||||||
pusher := push.New(batcher, exporter, time.Second)
|
|
||||||
pusher.Start()
|
|
||||||
|
|
||||||
return pusher
|
|
||||||
}(stdout.Options{
|
|
||||||
Writer: out,
|
Writer: out,
|
||||||
DoNotPrintTime: true,
|
DoNotPrintTime: true,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
global.SetMeterProvider(sdk)
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
counter.Add(ctx, 1, labels1)
|
counter.Add(ctx, 1, labels1)
|
||||||
|
|
||||||
@ -236,9 +219,9 @@ func TestDefaultSDK(t *testing.T) {
|
|||||||
ch <- string(data)
|
ch <- string(data)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
sdk.Stop()
|
pusher.Stop()
|
||||||
out.Close()
|
out.Close()
|
||||||
|
|
||||||
require.Equal(t, `{"updates":[{"name":"test.builtin{A=B}","sum":1}]}
|
require.Equal(t, `{"updates":[{"name":"test.builtin","sum":1}]}
|
||||||
`, <-ch)
|
`, <-ch)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/api/distributedcontext"
|
"go.opentelemetry.io/otel/api/distributedcontext"
|
||||||
"go.opentelemetry.io/otel/api/global"
|
"go.opentelemetry.io/otel/api/global"
|
||||||
@ -26,10 +25,7 @@ import (
|
|||||||
"go.opentelemetry.io/otel/api/trace"
|
"go.opentelemetry.io/otel/api/trace"
|
||||||
metricstdout "go.opentelemetry.io/otel/exporter/metric/stdout"
|
metricstdout "go.opentelemetry.io/otel/exporter/metric/stdout"
|
||||||
tracestdout "go.opentelemetry.io/otel/exporter/trace/stdout"
|
tracestdout "go.opentelemetry.io/otel/exporter/trace/stdout"
|
||||||
metricsdk "go.opentelemetry.io/otel/sdk/metric"
|
|
||||||
"go.opentelemetry.io/otel/sdk/metric/batcher/defaultkeys"
|
|
||||||
"go.opentelemetry.io/otel/sdk/metric/controller/push"
|
"go.opentelemetry.io/otel/sdk/metric/controller/push"
|
||||||
"go.opentelemetry.io/otel/sdk/metric/selector/simple"
|
|
||||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -57,19 +53,13 @@ func initTracer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initMeter() *push.Controller {
|
func initMeter() *push.Controller {
|
||||||
selector := simple.NewWithExactMeasure()
|
pusher, err := metricstdout.InstallNewPipeline(metricstdout.Options{
|
||||||
exporter, err := metricstdout.New(metricstdout.Options{
|
|
||||||
Quantiles: []float64{0.5, 0.9, 0.99},
|
Quantiles: []float64{0.5, 0.9, 0.99},
|
||||||
PrettyPrint: false,
|
PrettyPrint: false,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("failed to initialize metric stdout exporter %v", err)
|
log.Panicf("failed to initialize metric stdout exporter %v", err)
|
||||||
}
|
}
|
||||||
batcher := defaultkeys.New(selector, metricsdk.NewDefaultLabelEncoder(), true)
|
|
||||||
pusher := push.New(batcher, exporter, time.Second)
|
|
||||||
pusher.Start()
|
|
||||||
|
|
||||||
global.SetMeterProvider(pusher)
|
|
||||||
return pusher
|
return pusher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,10 +24,7 @@ import (
|
|||||||
"go.opentelemetry.io/otel/api/key"
|
"go.opentelemetry.io/otel/api/key"
|
||||||
"go.opentelemetry.io/otel/api/metric"
|
"go.opentelemetry.io/otel/api/metric"
|
||||||
"go.opentelemetry.io/otel/exporter/metric/prometheus"
|
"go.opentelemetry.io/otel/exporter/metric/prometheus"
|
||||||
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
|
||||||
"go.opentelemetry.io/otel/sdk/metric/batcher/defaultkeys"
|
|
||||||
"go.opentelemetry.io/otel/sdk/metric/controller/push"
|
"go.opentelemetry.io/otel/sdk/metric/controller/push"
|
||||||
"go.opentelemetry.io/otel/sdk/metric/selector/simple"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -37,29 +34,15 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func initMeter() *push.Controller {
|
func initMeter() *push.Controller {
|
||||||
selector := simple.NewWithExactMeasure()
|
pusher, hf, err := prometheus.InstallNewPipeline(prometheus.Options{})
|
||||||
exporter, err := prometheus.NewExporter(prometheus.Options{})
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("failed to initialize metric stdout exporter %v", err)
|
log.Panicf("failed to initialize prometheus exporter %v", err)
|
||||||
}
|
}
|
||||||
// Prometheus needs to use a stateful batcher since counters (and histogram since they are a collection of Counters)
|
http.HandleFunc("/", hf)
|
||||||
// are cumulative (i.e., monotonically increasing values) and should not be resetted after each export.
|
|
||||||
//
|
|
||||||
// Prometheus uses this approach to be resilient to scrape failures.
|
|
||||||
// If a Prometheus server tries to scrape metrics from a host and fails for some reason,
|
|
||||||
// it could try again on the next scrape and no data would be lost, only resolution.
|
|
||||||
//
|
|
||||||
// Gauges (or LastValues) and Summaries are an exception to this and have different behaviors.
|
|
||||||
batcher := defaultkeys.New(selector, sdkmetric.NewDefaultLabelEncoder(), true)
|
|
||||||
pusher := push.New(batcher, exporter, time.Second)
|
|
||||||
pusher.Start()
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_ = http.ListenAndServe(":2222", exporter)
|
_ = http.ListenAndServe(":2222", nil)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
global.SetMeterProvider(pusher)
|
|
||||||
return pusher
|
return pusher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,13 +16,19 @@ package dogstatsd // import "go.opentelemetry.io/otel/exporter/metric/dogstatsd"
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/api/global"
|
||||||
"go.opentelemetry.io/otel/exporter/metric/internal/statsd"
|
"go.opentelemetry.io/otel/exporter/metric/internal/statsd"
|
||||||
|
|
||||||
export "go.opentelemetry.io/otel/sdk/export/metric"
|
export "go.opentelemetry.io/otel/sdk/export/metric"
|
||||||
|
"go.opentelemetry.io/otel/sdk/metric/batcher/ungrouped"
|
||||||
|
"go.opentelemetry.io/otel/sdk/metric/controller/push"
|
||||||
|
"go.opentelemetry.io/otel/sdk/metric/selector/simple"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Config = statsd.Config
|
Options = statsd.Options
|
||||||
|
|
||||||
// Exporter implements a dogstatsd-format statsd exporter,
|
// Exporter implements a dogstatsd-format statsd exporter,
|
||||||
// which encodes label sets as independent fields in the
|
// which encodes label sets as independent fields in the
|
||||||
@ -45,20 +51,59 @@ var (
|
|||||||
_ export.LabelEncoder = &Exporter{}
|
_ export.LabelEncoder = &Exporter{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// New returns a new Dogstatsd-syntax exporter. This type implements
|
// NewRawExporter returns a new Dogstatsd-syntax exporter for use in a pipeline.
|
||||||
// the metric.LabelEncoder interface, allowing the SDK's unique label
|
// This type implements the metric.LabelEncoder interface,
|
||||||
// encoding to be pre-computed for the exporter and stored in the
|
// allowing the SDK's unique label encoding to be pre-computed
|
||||||
// LabelSet.
|
// for the exporter and stored in the LabelSet.
|
||||||
func New(config Config) (*Exporter, error) {
|
func NewRawExporter(options Options) (*Exporter, error) {
|
||||||
exp := &Exporter{
|
exp := &Exporter{
|
||||||
LabelEncoder: statsd.NewLabelEncoder(),
|
LabelEncoder: statsd.NewLabelEncoder(),
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
exp.Exporter, err = statsd.NewExporter(config, exp)
|
exp.Exporter, err = statsd.NewExporter(options, exp)
|
||||||
return exp, err
|
return exp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InstallNewPipeline instantiates a NewExportPipeline and registers it globally.
|
||||||
|
// Typically called as:
|
||||||
|
// pipeline, err := dogstatsd.InstallNewPipeline(dogstatsd.Options{...})
|
||||||
|
// if err != nil {
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
// defer pipeline.Stop()
|
||||||
|
// ... Done
|
||||||
|
func InstallNewPipeline(options Options) (*push.Controller, error) {
|
||||||
|
controller, err := NewExportPipeline(options)
|
||||||
|
if err != nil {
|
||||||
|
return controller, err
|
||||||
|
}
|
||||||
|
global.SetMeterProvider(controller)
|
||||||
|
return controller, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewExportPipeline sets up a complete export pipeline with the recommended setup,
|
||||||
|
// chaining a NewRawExporter into the recommended selectors and batchers.
|
||||||
|
func NewExportPipeline(options Options) (*push.Controller, error) {
|
||||||
|
selector := simple.NewWithExactMeasure()
|
||||||
|
exporter, err := NewRawExporter(options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The ungrouped batcher ensures that the export sees the full
|
||||||
|
// set of labels as dogstatsd tags.
|
||||||
|
batcher := ungrouped.New(selector, false)
|
||||||
|
|
||||||
|
// The pusher automatically recognizes that the exporter
|
||||||
|
// implements the LabelEncoder interface, which ensures the
|
||||||
|
// export encoding for labels is encoded in the LabelSet.
|
||||||
|
pusher := push.New(batcher, exporter, time.Hour)
|
||||||
|
pusher.Start()
|
||||||
|
|
||||||
|
return pusher, nil
|
||||||
|
}
|
||||||
|
|
||||||
// AppendName is part of the stats-internal adapter interface.
|
// AppendName is part of the stats-internal adapter interface.
|
||||||
func (*Exporter) AppendName(rec export.Record, buf *bytes.Buffer) {
|
func (*Exporter) AppendName(rec export.Record, buf *bytes.Buffer) {
|
||||||
_, _ = buf.WriteString(rec.Descriptor().Name())
|
_, _ = buf.WriteString(rec.Descriptor().Name())
|
||||||
|
@ -52,7 +52,7 @@ func TestDogstatsLabels(t *testing.T) {
|
|||||||
checkpointSet.Add(desc, cagg, key.New("A").String("B"))
|
checkpointSet.Add(desc, cagg, key.New("A").String("B"))
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
exp, err := dogstatsd.New(dogstatsd.Config{
|
exp, err := dogstatsd.NewRawExporter(dogstatsd.Options{
|
||||||
Writer: &buf,
|
Writer: &buf,
|
||||||
})
|
})
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
@ -6,14 +6,10 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/api/key"
|
"go.opentelemetry.io/otel/api/key"
|
||||||
"go.opentelemetry.io/otel/api/metric"
|
"go.opentelemetry.io/otel/api/metric"
|
||||||
"go.opentelemetry.io/otel/exporter/metric/dogstatsd"
|
"go.opentelemetry.io/otel/exporter/metric/dogstatsd"
|
||||||
"go.opentelemetry.io/otel/sdk/metric/batcher/ungrouped"
|
|
||||||
"go.opentelemetry.io/otel/sdk/metric/controller/push"
|
|
||||||
"go.opentelemetry.io/otel/sdk/metric/selector/simple"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleNew() {
|
func ExampleNew() {
|
||||||
@ -42,8 +38,7 @@ func ExampleNew() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Create a meter
|
// Create a meter
|
||||||
selector := simple.NewWithExactMeasure()
|
pusher, err := dogstatsd.NewExportPipeline(dogstatsd.Options{
|
||||||
exporter, err := dogstatsd.New(dogstatsd.Config{
|
|
||||||
// The Writer field provides test support.
|
// The Writer field provides test support.
|
||||||
Writer: writer,
|
Writer: writer,
|
||||||
|
|
||||||
@ -54,15 +49,6 @@ func ExampleNew() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Could not initialize dogstatsd exporter:", err)
|
log.Fatal("Could not initialize dogstatsd exporter:", err)
|
||||||
}
|
}
|
||||||
// The ungrouped batcher ensures that the export sees the full
|
|
||||||
// set of labels as dogstatsd tags.
|
|
||||||
batcher := ungrouped.New(selector, false)
|
|
||||||
|
|
||||||
// The pusher automatically recognizes that the exporter
|
|
||||||
// implements the LabelEncoder interface, which ensures the
|
|
||||||
// export encoding for labels is encoded in the LabelSet.
|
|
||||||
pusher := push.New(batcher, exporter, time.Hour)
|
|
||||||
pusher.Start()
|
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
@ -34,8 +34,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// Config supports common options that apply to statsd exporters.
|
// Options supports common options that apply to statsd exporters.
|
||||||
Config struct {
|
Options struct {
|
||||||
// URL describes the destination for exporting statsd data.
|
// URL describes the destination for exporting statsd data.
|
||||||
// e.g., udp://host:port
|
// e.g., udp://host:port
|
||||||
// tcp://host:port
|
// tcp://host:port
|
||||||
@ -57,7 +57,7 @@ type (
|
|||||||
// exporters.
|
// exporters.
|
||||||
Exporter struct {
|
Exporter struct {
|
||||||
adapter Adapter
|
adapter Adapter
|
||||||
config Config
|
options Options
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
writer io.Writer
|
writer io.Writer
|
||||||
buffer bytes.Buffer
|
buffer bytes.Buffer
|
||||||
@ -88,17 +88,17 @@ var (
|
|||||||
|
|
||||||
// NewExport returns a common implementation for exporters that Export
|
// NewExport returns a common implementation for exporters that Export
|
||||||
// statsd syntax.
|
// statsd syntax.
|
||||||
func NewExporter(config Config, adapter Adapter) (*Exporter, error) {
|
func NewExporter(options Options, adapter Adapter) (*Exporter, error) {
|
||||||
if config.MaxPacketSize <= 0 {
|
if options.MaxPacketSize <= 0 {
|
||||||
config.MaxPacketSize = MaxPacketSize
|
options.MaxPacketSize = MaxPacketSize
|
||||||
}
|
}
|
||||||
var writer io.Writer
|
var writer io.Writer
|
||||||
var conn net.Conn
|
var conn net.Conn
|
||||||
var err error
|
var err error
|
||||||
if config.Writer != nil {
|
if options.Writer != nil {
|
||||||
writer = config.Writer
|
writer = options.Writer
|
||||||
} else {
|
} else {
|
||||||
conn, err = dial(config.URL)
|
conn, err = dial(options.URL)
|
||||||
if conn != nil {
|
if conn != nil {
|
||||||
writer = conn
|
writer = conn
|
||||||
}
|
}
|
||||||
@ -108,7 +108,7 @@ func NewExporter(config Config, adapter Adapter) (*Exporter, error) {
|
|||||||
// Start() and Stop() API.
|
// Start() and Stop() API.
|
||||||
return &Exporter{
|
return &Exporter{
|
||||||
adapter: adapter,
|
adapter: adapter,
|
||||||
config: config,
|
options: options,
|
||||||
conn: conn,
|
conn: conn,
|
||||||
writer: writer,
|
writer: writer,
|
||||||
}, err
|
}, err
|
||||||
@ -171,7 +171,7 @@ func (e *Exporter) Export(_ context.Context, checkpointSet export.CheckpointSet)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if buf.Len() < e.config.MaxPacketSize {
|
if buf.Len() < e.options.MaxPacketSize {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if before == 0 {
|
if before == 0 {
|
||||||
|
@ -113,11 +113,11 @@ timer.B.D:%s|ms
|
|||||||
t.Run(nkind.String(), func(t *testing.T) {
|
t.Run(nkind.String(), func(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
writer := &testWriter{}
|
writer := &testWriter{}
|
||||||
config := statsd.Config{
|
options := statsd.Options{
|
||||||
Writer: writer,
|
Writer: writer,
|
||||||
MaxPacketSize: 1024,
|
MaxPacketSize: 1024,
|
||||||
}
|
}
|
||||||
exp, err := statsd.NewExporter(config, adapter)
|
exp, err := statsd.NewExporter(options, adapter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("New error: ", err)
|
t.Fatal("New error: ", err)
|
||||||
}
|
}
|
||||||
@ -274,12 +274,12 @@ func TestPacketSplit(t *testing.T) {
|
|||||||
t.Run(tcase.name, func(t *testing.T) {
|
t.Run(tcase.name, func(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
writer := &testWriter{}
|
writer := &testWriter{}
|
||||||
config := statsd.Config{
|
options := statsd.Options{
|
||||||
Writer: writer,
|
Writer: writer,
|
||||||
MaxPacketSize: 1024,
|
MaxPacketSize: 1024,
|
||||||
}
|
}
|
||||||
adapter := newWithTagsAdapter()
|
adapter := newWithTagsAdapter()
|
||||||
exp, err := statsd.NewExporter(config, adapter)
|
exp, err := statsd.NewExporter(options, adapter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("New error: ", err)
|
t.Fatal("New error: ", err)
|
||||||
}
|
}
|
||||||
|
@ -18,13 +18,19 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/api/core"
|
"go.opentelemetry.io/otel/api/core"
|
||||||
|
"go.opentelemetry.io/otel/api/global"
|
||||||
export "go.opentelemetry.io/otel/sdk/export/metric"
|
export "go.opentelemetry.io/otel/sdk/export/metric"
|
||||||
"go.opentelemetry.io/otel/sdk/export/metric/aggregator"
|
"go.opentelemetry.io/otel/sdk/export/metric/aggregator"
|
||||||
|
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
||||||
|
"go.opentelemetry.io/otel/sdk/metric/batcher/defaultkeys"
|
||||||
|
"go.opentelemetry.io/otel/sdk/metric/controller/push"
|
||||||
|
"go.opentelemetry.io/otel/sdk/metric/selector/simple"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Exporter is an implementation of metric.Exporter that sends metrics to
|
// Exporter is an implementation of metric.Exporter that sends metrics to
|
||||||
@ -73,8 +79,9 @@ type Options struct {
|
|||||||
OnError func(error)
|
OnError func(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewExporter returns a new prometheus exporter for prometheus metrics.
|
// NewRawExporter returns a new prometheus exporter for prometheus metrics
|
||||||
func NewExporter(opts Options) (*Exporter, error) {
|
// for use in a pipeline.
|
||||||
|
func NewRawExporter(opts Options) (*Exporter, error) {
|
||||||
if opts.Registry == nil {
|
if opts.Registry == nil {
|
||||||
opts.Registry = prometheus.NewRegistry()
|
opts.Registry = prometheus.NewRegistry()
|
||||||
}
|
}
|
||||||
@ -108,6 +115,48 @@ func NewExporter(opts Options) (*Exporter, error) {
|
|||||||
return e, nil
|
return e, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InstallNewPipeline instantiates a NewExportPipeline and registers it globally.
|
||||||
|
// Typically called as:
|
||||||
|
// pipeline, hf, err := prometheus.InstallNewPipeline(prometheus.Options{...})
|
||||||
|
// if err != nil {
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
// http.HandleFunc("/metrics", hf)
|
||||||
|
// defer pipeline.Stop()
|
||||||
|
// ... Done
|
||||||
|
func InstallNewPipeline(options Options) (*push.Controller, http.HandlerFunc, error) {
|
||||||
|
controller, hf, err := NewExportPipeline(options)
|
||||||
|
if err != nil {
|
||||||
|
return controller, hf, err
|
||||||
|
}
|
||||||
|
global.SetMeterProvider(controller)
|
||||||
|
return controller, hf, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewExportPipeline sets up a complete export pipeline with the recommended setup,
|
||||||
|
// chaining a NewRawExporter into the recommended selectors and batchers.
|
||||||
|
func NewExportPipeline(options Options) (*push.Controller, http.HandlerFunc, error) {
|
||||||
|
selector := simple.NewWithExactMeasure()
|
||||||
|
exporter, err := NewRawExporter(options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prometheus needs to use a stateful batcher since counters (and histogram since they are a collection of Counters)
|
||||||
|
// are cumulative (i.e., monotonically increasing values) and should not be resetted after each export.
|
||||||
|
//
|
||||||
|
// Prometheus uses this approach to be resilient to scrape failures.
|
||||||
|
// If a Prometheus server tries to scrape metrics from a host and fails for some reason,
|
||||||
|
// it could try again on the next scrape and no data would be lost, only resolution.
|
||||||
|
//
|
||||||
|
// Gauges (or LastValues) and Summaries are an exception to this and have different behaviors.
|
||||||
|
batcher := defaultkeys.New(selector, sdkmetric.NewDefaultLabelEncoder(), false)
|
||||||
|
pusher := push.New(batcher, exporter, time.Second)
|
||||||
|
pusher.Start()
|
||||||
|
|
||||||
|
return pusher, exporter.ServeHTTP, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Export exports the provide metric record to prometheus.
|
// Export exports the provide metric record to prometheus.
|
||||||
func (e *Exporter) Export(_ context.Context, checkpointSet export.CheckpointSet) error {
|
func (e *Exporter) Export(_ context.Context, checkpointSet export.CheckpointSet) error {
|
||||||
e.snapshot = checkpointSet
|
e.snapshot = checkpointSet
|
||||||
|
@ -19,11 +19,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestPrometheusExporter(t *testing.T) {
|
func TestPrometheusExporter(t *testing.T) {
|
||||||
exporter, err := prometheus.NewExporter(prometheus.Options{
|
exporter, err := prometheus.NewRawExporter(prometheus.Options{
|
||||||
DefaultSummaryQuantiles: []float64{0.5, 0.9, 0.99},
|
DefaultSummaryQuantiles: []float64{0.5, 0.9, 0.99},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("failed to initialize metric stdout exporter %v", err)
|
log.Panicf("failed to initialize prometheus exporter %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var expected []string
|
var expected []string
|
||||||
|
43
exporter/metric/stdout/example_test.go
Normal file
43
exporter/metric/stdout/example_test.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package stdout_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/api/key"
|
||||||
|
"go.opentelemetry.io/otel/api/metric"
|
||||||
|
"go.opentelemetry.io/otel/exporter/metric/stdout"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleNewExportPipeline() {
|
||||||
|
// Create a meter
|
||||||
|
pusher, err := stdout.NewExportPipeline(stdout.Options{
|
||||||
|
PrettyPrint: true,
|
||||||
|
DoNotPrintTime: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Could not initialize stdout exporter:", err)
|
||||||
|
}
|
||||||
|
defer pusher.Stop()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
key := key.New("key")
|
||||||
|
meter := pusher.Meter("example")
|
||||||
|
|
||||||
|
// Create and update a single counter:
|
||||||
|
counter := meter.NewInt64Counter("a.counter", metric.WithKeys(key))
|
||||||
|
labels := meter.Labels(key.String("value"))
|
||||||
|
|
||||||
|
counter.Add(ctx, 100, labels)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// {
|
||||||
|
// "updates": [
|
||||||
|
// {
|
||||||
|
// "name": "a.counter{key=value}",
|
||||||
|
// "sum": 100
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
}
|
@ -23,8 +23,14 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/api/global"
|
||||||
|
|
||||||
export "go.opentelemetry.io/otel/sdk/export/metric"
|
export "go.opentelemetry.io/otel/sdk/export/metric"
|
||||||
"go.opentelemetry.io/otel/sdk/export/metric/aggregator"
|
"go.opentelemetry.io/otel/sdk/export/metric/aggregator"
|
||||||
|
metricsdk "go.opentelemetry.io/otel/sdk/metric"
|
||||||
|
"go.opentelemetry.io/otel/sdk/metric/batcher/defaultkeys"
|
||||||
|
"go.opentelemetry.io/otel/sdk/metric/controller/push"
|
||||||
|
"go.opentelemetry.io/otel/sdk/metric/selector/simple"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Exporter struct {
|
type Exporter struct {
|
||||||
@ -80,7 +86,8 @@ type expoQuantile struct {
|
|||||||
V interface{} `json:"v"`
|
V interface{} `json:"v"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(options Options) (*Exporter, error) {
|
// NewRawExporter creates a stdout Exporter for use in a pipeline.
|
||||||
|
func NewRawExporter(options Options) (*Exporter, error) {
|
||||||
if options.Writer == nil {
|
if options.Writer == nil {
|
||||||
options.Writer = os.Stdout
|
options.Writer = os.Stdout
|
||||||
}
|
}
|
||||||
@ -98,6 +105,38 @@ func New(options Options) (*Exporter, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InstallNewPipeline instantiates a NewExportPipeline and registers it globally.
|
||||||
|
// Typically called as:
|
||||||
|
// pipeline, err := stdout.InstallNewPipeline(stdout.Options{...})
|
||||||
|
// if err != nil {
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
// defer pipeline.Stop()
|
||||||
|
// ... Done
|
||||||
|
func InstallNewPipeline(options Options) (*push.Controller, error) {
|
||||||
|
controller, err := NewExportPipeline(options)
|
||||||
|
if err != nil {
|
||||||
|
return controller, err
|
||||||
|
}
|
||||||
|
global.SetMeterProvider(controller)
|
||||||
|
return controller, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewExportPipeline sets up a complete export pipeline with the recommended setup,
|
||||||
|
// chaining a NewRawExporter into the recommended selectors and batchers.
|
||||||
|
func NewExportPipeline(options Options) (*push.Controller, error) {
|
||||||
|
selector := simple.NewWithExactMeasure()
|
||||||
|
exporter, err := NewRawExporter(options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
batcher := defaultkeys.New(selector, metricsdk.NewDefaultLabelEncoder(), true)
|
||||||
|
pusher := push.New(batcher, exporter, time.Second)
|
||||||
|
pusher.Start()
|
||||||
|
|
||||||
|
return pusher, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Exporter) Export(_ context.Context, checkpointSet export.CheckpointSet) error {
|
func (e *Exporter) Export(_ context.Context, checkpointSet export.CheckpointSet) error {
|
||||||
// N.B. Only return one aggError, if any occur. They're likely
|
// N.B. Only return one aggError, if any occur. They're likely
|
||||||
// to be duplicates of the same error.
|
// to be duplicates of the same error.
|
||||||
|
@ -36,7 +36,7 @@ func newFixture(t *testing.T, options stdout.Options) testFixture {
|
|||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
options.Writer = buf
|
options.Writer = buf
|
||||||
options.DoNotPrintTime = true
|
options.DoNotPrintTime = true
|
||||||
exp, err := stdout.New(options)
|
exp, err := stdout.NewRawExporter(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("Error building fixture: ", err)
|
t.Fatal("Error building fixture: ", err)
|
||||||
}
|
}
|
||||||
@ -60,7 +60,7 @@ func (fix testFixture) Export(checkpointSet export.CheckpointSet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStdoutInvalidQuantile(t *testing.T) {
|
func TestStdoutInvalidQuantile(t *testing.T) {
|
||||||
_, err := stdout.New(stdout.Options{
|
_, err := stdout.NewRawExporter(stdout.Options{
|
||||||
Quantiles: []float64{1.1, 0.9},
|
Quantiles: []float64{1.1, 0.9},
|
||||||
})
|
})
|
||||||
require.Error(t, err, "Invalid quantile error expected")
|
require.Error(t, err, "Invalid quantile error expected")
|
||||||
@ -69,7 +69,7 @@ func TestStdoutInvalidQuantile(t *testing.T) {
|
|||||||
|
|
||||||
func TestStdoutTimestamp(t *testing.T) {
|
func TestStdoutTimestamp(t *testing.T) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
exporter, err := stdout.New(stdout.Options{
|
exporter, err := stdout.NewRawExporter(stdout.Options{
|
||||||
Writer: &buf,
|
Writer: &buf,
|
||||||
DoNotPrintTime: false,
|
DoNotPrintTime: false,
|
||||||
})
|
})
|
||||||
|
@ -17,29 +17,20 @@ package metric_test
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/api/key"
|
"go.opentelemetry.io/otel/api/key"
|
||||||
"go.opentelemetry.io/otel/api/metric"
|
"go.opentelemetry.io/otel/api/metric"
|
||||||
"go.opentelemetry.io/otel/exporter/metric/stdout"
|
"go.opentelemetry.io/otel/exporter/metric/stdout"
|
||||||
sdk "go.opentelemetry.io/otel/sdk/metric"
|
|
||||||
"go.opentelemetry.io/otel/sdk/metric/batcher/defaultkeys"
|
|
||||||
"go.opentelemetry.io/otel/sdk/metric/controller/push"
|
|
||||||
"go.opentelemetry.io/otel/sdk/metric/selector/simple"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleNew() {
|
func ExampleNew() {
|
||||||
selector := simple.NewWithInexpensiveMeasure()
|
pusher, err := stdout.NewExportPipeline(stdout.Options{
|
||||||
exporter, err := stdout.New(stdout.Options{
|
|
||||||
PrettyPrint: true,
|
PrettyPrint: true,
|
||||||
DoNotPrintTime: true, // This makes the output deterministic
|
DoNotPrintTime: true, // This makes the output deterministic
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintln("Could not initialize stdout exporter:", err))
|
panic(fmt.Sprintln("Could not initialize stdout exporter:", err))
|
||||||
}
|
}
|
||||||
batcher := defaultkeys.New(selector, sdk.NewDefaultLabelEncoder(), true)
|
|
||||||
pusher := push.New(batcher, exporter, time.Second)
|
|
||||||
pusher.Start()
|
|
||||||
defer pusher.Stop()
|
defer pusher.Stop()
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user