You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-07-15 01:04:25 +02:00
Metrics stdout export pipeline (#265)
* Add MetricAggregator.Merge() implementations * Update from feedback * Type * Ckpt * Ckpt * Add push controller * Ckpt * Add aggregator interfaces, stdout encoder * Modify basic main.go * Main is working * Batch stdout output * Sum udpate * Rename stdout * Add stateless/stateful Batcher options * Undo a for-loop in the example, remove a done TODO * Update imports * Add note * Rename defaultkeys * Support variable label encoder to speed OpenMetrics/Statsd export * Lint * Doc * Precommit/lint * Simplify Aggregator API * Record->Identifier * Remove export.Record a.k.a. Identifier * Checkpoint * Propagate errors to the SDK, remove a bunch of 'TODO warn' * Checkpoint * Introduce export.Labels * Comments in export/metric.go * Comment * More merge * More doc * Complete example * Lint fixes * Add a testable example * Lint * Let Export return an error * add a basic stdout exporter test * Add measure test; fix aggregator APIs * Use JSON numbers, not strings * Test stdout exporter error * Add a test for the call to RangeTest * Add error handler API to improve correctness test; return errors from RecordOne * Undo the previous -- do not expose errors * Add simple selector variations, test * Repair examples * Test push controller error handling * Add SDK label encoder tests * Add a defaultkeys batcher test * Add an ungrouped batcher test * Lint new tests * Respond to krnowak's feedback * Undo comment * Use concrete receivers for export records and labels, since the constructors return structs not pointers * Bug fix for stateful batchers; clone an aggregator for long term storage * Remove TODO addressed in #318 * Add errors to all aggregator interfaces * Handle ErrNoLastValue case in stdout exporter * Move aggregator API into sdk/export/metric/aggregator * Update all aggregator exported-method comments * Document the aggregator APIs * More aggregator comments * Add multiple updates to the ungrouped test * Fixes for feedback from Gustavo and Liz * Producer->CheckpointSet; add FinishedCollection * Process takes an export.Record * ReadCheckpoint->CheckpointSet * EncodeLabels->Encode * Format a better inconsistent type error; add more aggregator API tests * More RangeTest test coverage * Make benbjohnson/clock a test-only dependency * Handle ErrNoLastValue in stress_test
This commit is contained in:
committed by
rghetia
parent
c3d5b7b16d
commit
9878f3b700
@ -23,7 +23,7 @@ import (
|
||||
|
||||
"go.opentelemetry.io/otel/api/core"
|
||||
export "go.opentelemetry.io/otel/sdk/export/metric"
|
||||
"go.opentelemetry.io/otel/sdk/metric/aggregator"
|
||||
"go.opentelemetry.io/otel/sdk/export/metric/aggregator"
|
||||
)
|
||||
|
||||
type (
|
||||
@ -38,44 +38,55 @@ type (
|
||||
)
|
||||
|
||||
var _ export.Aggregator = &Aggregator{}
|
||||
var _ aggregator.MaxSumCount = &Aggregator{}
|
||||
var _ aggregator.Distribution = &Aggregator{}
|
||||
|
||||
// New returns a new array aggregator, which aggregates recorded
|
||||
// measurements by storing them in an array. This type uses a mutex
|
||||
// for Update() and Checkpoint() concurrency.
|
||||
func New() *Aggregator {
|
||||
return &Aggregator{}
|
||||
}
|
||||
|
||||
// Sum returns the sum of the checkpoint.
|
||||
func (c *Aggregator) Sum() core.Number {
|
||||
return c.ckptSum
|
||||
// Sum returns the sum of values in the checkpoint.
|
||||
func (c *Aggregator) Sum() (core.Number, error) {
|
||||
return c.ckptSum, nil
|
||||
}
|
||||
|
||||
// Count returns the count of the checkpoint.
|
||||
func (c *Aggregator) Count() int64 {
|
||||
return int64(len(c.checkpoint))
|
||||
// Count returns the number of values in the checkpoint.
|
||||
func (c *Aggregator) Count() (int64, error) {
|
||||
return int64(len(c.checkpoint)), nil
|
||||
}
|
||||
|
||||
// Max returns the max of the checkpoint.
|
||||
// Max returns the maximum value in the checkpoint.
|
||||
func (c *Aggregator) Max() (core.Number, error) {
|
||||
return c.checkpoint.Quantile(1)
|
||||
}
|
||||
|
||||
// Min returns the min of the checkpoint.
|
||||
// Min returns the mininum value in the checkpoint.
|
||||
func (c *Aggregator) Min() (core.Number, error) {
|
||||
return c.checkpoint.Quantile(0)
|
||||
}
|
||||
|
||||
// Quantile returns the estimated quantile of the checkpoint.
|
||||
// Quantile returns the estimated quantile of data in the checkpoint.
|
||||
// It is an error if `q` is less than 0 or greated than 1.
|
||||
func (c *Aggregator) Quantile(q float64) (core.Number, error) {
|
||||
return c.checkpoint.Quantile(q)
|
||||
}
|
||||
|
||||
func (c *Aggregator) Collect(ctx context.Context, rec export.Record, exp export.Batcher) {
|
||||
// Checkpoint saves the current state and resets the current state to
|
||||
// the empty set, taking a lock to prevent concurrent Update() calls.
|
||||
func (c *Aggregator) Checkpoint(ctx context.Context, desc *export.Descriptor) {
|
||||
c.lock.Lock()
|
||||
c.checkpoint, c.current = c.current, nil
|
||||
c.lock.Unlock()
|
||||
|
||||
desc := rec.Descriptor()
|
||||
kind := desc.NumberKind()
|
||||
|
||||
// TODO: This sort should be done lazily, only when quantiles
|
||||
// are requested. The SDK specification says you can use this
|
||||
// aggregator to simply list values in the order they were
|
||||
// received as an alternative to requesting quantile information.
|
||||
c.sort(kind)
|
||||
|
||||
c.ckptSum = core.Number(0)
|
||||
@ -83,39 +94,28 @@ func (c *Aggregator) Collect(ctx context.Context, rec export.Record, exp export.
|
||||
for _, v := range c.checkpoint {
|
||||
c.ckptSum.AddNumber(kind, v)
|
||||
}
|
||||
|
||||
exp.Export(ctx, rec, c)
|
||||
}
|
||||
|
||||
func (c *Aggregator) Update(_ context.Context, number core.Number, rec export.Record) {
|
||||
desc := rec.Descriptor()
|
||||
kind := desc.NumberKind()
|
||||
|
||||
if kind == core.Float64NumberKind && math.IsNaN(number.AsFloat64()) {
|
||||
// TODO warn
|
||||
// NOTE: add this to the specification.
|
||||
return
|
||||
}
|
||||
|
||||
if !desc.Alternate() && number.IsNegative(kind) {
|
||||
// TODO warn
|
||||
return
|
||||
}
|
||||
|
||||
// Update adds the recorded measurement to the current data set.
|
||||
// Update takes a lock to prevent concurrent Update() and Checkpoint()
|
||||
// calls.
|
||||
func (c *Aggregator) Update(_ context.Context, number core.Number, desc *export.Descriptor) error {
|
||||
c.lock.Lock()
|
||||
c.current = append(c.current, number)
|
||||
c.lock.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Aggregator) Merge(oa export.Aggregator, desc *export.Descriptor) {
|
||||
// Merge combines two data sets into one.
|
||||
func (c *Aggregator) Merge(oa export.Aggregator, desc *export.Descriptor) error {
|
||||
o, _ := oa.(*Aggregator)
|
||||
if o == nil {
|
||||
// TODO warn
|
||||
return
|
||||
return aggregator.NewInconsistentMergeError(c, oa)
|
||||
}
|
||||
|
||||
c.ckptSum.AddNumber(desc.NumberKind(), o.ckptSum)
|
||||
c.checkpoint = combine(c.checkpoint, o.checkpoint, desc.NumberKind())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Aggregator) sort(kind core.NumberKind) {
|
||||
@ -166,7 +166,8 @@ func (p *Points) Swap(i, j int) {
|
||||
}
|
||||
|
||||
// Quantile returns the least X such that Pr(x<X)>=q, where X is an
|
||||
// element of the data set.
|
||||
// element of the data set. This uses the "Nearest-Rank" definition
|
||||
// of a quantile.
|
||||
func (p *Points) Quantile(q float64) (core.Number, error) {
|
||||
if len(*p) == 0 {
|
||||
return core.Number(0), aggregator.ErrEmptyDataSet
|
||||
@ -182,9 +183,6 @@ func (p *Points) Quantile(q float64) (core.Number, error) {
|
||||
return (*p)[len(*p)-1], nil
|
||||
}
|
||||
|
||||
// Note: There's no interpolation being done here. There are
|
||||
// many definitions for "quantile", some interpolate, some do
|
||||
// not. What is expected?
|
||||
position := float64(len(*p)-1) * q
|
||||
ceil := int(math.Ceil(position))
|
||||
return (*p)[ceil], nil
|
||||
|
Reference in New Issue
Block a user