mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2024-12-14 10:13:10 +02:00
06f833e2ae
* Add skeleton uniqueness checker * Fix the build w/ new code in place * Add sync tests * More test * Implement global uniqueness checking * Set the library name * Ensure ordered global initialization * Use proper require statement for errors * Comment * Apply feedback fixes * Comment and rename from feedback
223 lines
5.2 KiB
Go
223 lines
5.2 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 push // import "go.opentelemetry.io/otel/sdk/metric/controller/push"
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
"go.opentelemetry.io/otel/api/metric"
|
|
"go.opentelemetry.io/otel/api/metric/registry"
|
|
export "go.opentelemetry.io/otel/sdk/export/metric"
|
|
sdk "go.opentelemetry.io/otel/sdk/metric"
|
|
)
|
|
|
|
// Controller organizes a periodic push of metric data.
|
|
type Controller struct {
|
|
lock sync.Mutex
|
|
collectLock sync.Mutex
|
|
sdk *sdk.SDK
|
|
uniq metric.MeterImpl
|
|
named map[string]metric.Meter
|
|
errorHandler sdk.ErrorHandler
|
|
batcher export.Batcher
|
|
exporter export.Exporter
|
|
wg sync.WaitGroup
|
|
ch chan struct{}
|
|
period time.Duration
|
|
ticker Ticker
|
|
clock Clock
|
|
}
|
|
|
|
var _ metric.Provider = &Controller{}
|
|
|
|
// Several types below are created to match "github.com/benbjohnson/clock"
|
|
// so that it remains a test-only dependency.
|
|
|
|
type Clock interface {
|
|
Now() time.Time
|
|
Ticker(time.Duration) Ticker
|
|
}
|
|
|
|
type Ticker interface {
|
|
Stop()
|
|
C() <-chan time.Time
|
|
}
|
|
|
|
type realClock struct {
|
|
}
|
|
|
|
type realTicker struct {
|
|
ticker *time.Ticker
|
|
}
|
|
|
|
var _ Clock = realClock{}
|
|
var _ Ticker = realTicker{}
|
|
|
|
// New constructs a Controller, an implementation of metric.Provider,
|
|
// using the provided batcher, exporter, collection period, and SDK
|
|
// configuration options to configure an SDK with periodic collection.
|
|
// The batcher itself is configured with the aggregation selector policy.
|
|
func New(batcher export.Batcher, exporter export.Exporter, period time.Duration, opts ...Option) *Controller {
|
|
c := &Config{ErrorHandler: sdk.DefaultErrorHandler}
|
|
for _, opt := range opts {
|
|
opt.Apply(c)
|
|
}
|
|
|
|
impl := sdk.New(batcher, sdk.WithResource(c.Resource), sdk.WithErrorHandler(c.ErrorHandler))
|
|
return &Controller{
|
|
sdk: impl,
|
|
uniq: registry.NewUniqueInstrumentMeterImpl(impl),
|
|
named: map[string]metric.Meter{},
|
|
errorHandler: c.ErrorHandler,
|
|
batcher: batcher,
|
|
exporter: exporter,
|
|
ch: make(chan struct{}),
|
|
period: period,
|
|
clock: realClock{},
|
|
}
|
|
}
|
|
|
|
// SetClock supports setting a mock clock for testing. This must be
|
|
// called before Start().
|
|
func (c *Controller) SetClock(clock Clock) {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
c.clock = clock
|
|
}
|
|
|
|
func (c *Controller) SetErrorHandler(errorHandler sdk.ErrorHandler) {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
c.errorHandler = errorHandler
|
|
c.sdk.SetErrorHandler(errorHandler)
|
|
}
|
|
|
|
// Meter returns a named Meter, satisifying the metric.Provider
|
|
// interface.
|
|
func (c *Controller) Meter(name string) metric.Meter {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
|
|
if meter, ok := c.named[name]; ok {
|
|
return meter
|
|
}
|
|
|
|
meter := metric.WrapMeterImpl(c.uniq, name)
|
|
c.named[name] = meter
|
|
return meter
|
|
}
|
|
|
|
// Start begins a ticker that periodically collects and exports
|
|
// metrics with the configured interval.
|
|
func (c *Controller) Start() {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
|
|
if c.ticker != nil {
|
|
return
|
|
}
|
|
|
|
c.ticker = c.clock.Ticker(c.period)
|
|
c.wg.Add(1)
|
|
go c.run(c.ch)
|
|
}
|
|
|
|
// Stop waits for the background goroutine to return and then collects
|
|
// and exports metrics one last time before returning.
|
|
func (c *Controller) Stop() {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
|
|
if c.ch == nil {
|
|
return
|
|
}
|
|
|
|
close(c.ch)
|
|
c.ch = nil
|
|
c.wg.Wait()
|
|
c.ticker.Stop()
|
|
|
|
c.tick()
|
|
}
|
|
|
|
func (c *Controller) run(ch chan struct{}) {
|
|
for {
|
|
select {
|
|
case <-ch:
|
|
c.wg.Done()
|
|
return
|
|
case <-c.ticker.C():
|
|
c.tick()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Controller) tick() {
|
|
// TODO: either remove the context argument from Export() or
|
|
// configure a timeout here?
|
|
ctx := context.Background()
|
|
c.collect(ctx)
|
|
checkpointSet := syncCheckpointSet{
|
|
mtx: &c.collectLock,
|
|
delegate: c.batcher.CheckpointSet(),
|
|
}
|
|
err := c.exporter.Export(ctx, checkpointSet)
|
|
c.batcher.FinishedCollection()
|
|
|
|
if err != nil {
|
|
c.errorHandler(err)
|
|
}
|
|
}
|
|
|
|
func (c *Controller) collect(ctx context.Context) {
|
|
c.collectLock.Lock()
|
|
defer c.collectLock.Unlock()
|
|
|
|
c.sdk.Collect(ctx)
|
|
}
|
|
|
|
// syncCheckpointSet is a wrapper for a CheckpointSet to synchronize
|
|
// SDK's collection and reads of a CheckpointSet by an exporter.
|
|
type syncCheckpointSet struct {
|
|
mtx *sync.Mutex
|
|
delegate export.CheckpointSet
|
|
}
|
|
|
|
var _ export.CheckpointSet = (*syncCheckpointSet)(nil)
|
|
|
|
func (c syncCheckpointSet) ForEach(fn func(export.Record) error) error {
|
|
c.mtx.Lock()
|
|
defer c.mtx.Unlock()
|
|
return c.delegate.ForEach(fn)
|
|
}
|
|
|
|
func (realClock) Now() time.Time {
|
|
return time.Now()
|
|
}
|
|
|
|
func (realClock) Ticker(period time.Duration) Ticker {
|
|
return realTicker{time.NewTicker(period)}
|
|
}
|
|
|
|
func (t realTicker) Stop() {
|
|
t.ticker.Stop()
|
|
}
|
|
|
|
func (t realTicker) C() <-chan time.Time {
|
|
return t.ticker.C
|
|
}
|