You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-07-03 00:27:03 +02:00
Golang metrics prototype (#100)
* initial metrics work * rename cumulative to counter * rename bidirectional to nonmonotonic * rename unidirectional to monotonic * rename nonnegative to signed this changes the default semantics a bit - before the change measure could record negative values by default, now it can't. The specification draft currently specifies both NonNegative and Signed, but I think it's a mistake. * rename instrument to descriptor * license * rework measurement values * make measurement value a tagged union * simplify to one kind of metrics * add observers * change some interfaces to match the spec * keep integral measurement separate from floating ones * remove duplicated measurement type * add checking for options * reorder some fields and functions * rename a function to avoid confusion between the Handle type and the Measure type * drop disabled field from descriptor * add back typed API for metrics * make metric options type safe * merge alternatives into a single bool * make value kind name less stuttery * fix observation callback prototype * drop context parameter from NewHandle * drop useless parameter names * make descriptor an opaque struct * use a store helper * handle comment fixes * reword Alternate comment * drop the "any value" metrics * make measurement value simpler * document value stuff * add tests for values * docs * do not panic if there is no span ID in the event
This commit is contained in:
committed by
rghetia
parent
c2d5c66990
commit
be8fb0b4e2
@ -25,6 +25,7 @@ type Value struct {
|
|||||||
String string
|
String string
|
||||||
Bytes []byte
|
Bytes []byte
|
||||||
|
|
||||||
|
// TODO See how segmentio/stats handles this type, it's much smaller.
|
||||||
// TODO Lazy value type?
|
// TODO Lazy value type?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:generate stringer -type=Kind,ValueKind
|
||||||
|
|
||||||
package metric
|
package metric
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -21,62 +23,317 @@ import (
|
|||||||
"go.opentelemetry.io/api/unit"
|
"go.opentelemetry.io/api/unit"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MetricType int
|
// Kind categorizes different kinds of metric.
|
||||||
|
type Kind int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Invalid MetricType = iota
|
// Invalid describes an invalid metric.
|
||||||
Gauge // Supports Set()
|
Invalid Kind = iota
|
||||||
Cumulative // Supports Inc()
|
// CounterKind describes a metric that supports Add().
|
||||||
|
CounterKind
|
||||||
|
// GaugeKind describes a metric that supports Set().
|
||||||
|
GaugeKind
|
||||||
|
// MeasureKind describes a metric that supports Record().
|
||||||
|
MeasureKind
|
||||||
|
// ObserverKind describes a metric that reports measurement on
|
||||||
|
// demand.
|
||||||
|
ObserverKind
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Handle is the implementation-level interface to Set/Add/Record
|
||||||
|
// individual metrics.
|
||||||
|
type Handle interface {
|
||||||
|
// RecordOne allows the SDK to observe a single metric event
|
||||||
|
RecordOne(ctx context.Context, value MeasurementValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO this belongs outside the metrics API, in some sense, but that
|
||||||
|
// might create a dependency. Putting this here means we can't re-use
|
||||||
|
// a LabelSet between metrics and tracing, even when they are the same
|
||||||
|
// SDK.
|
||||||
|
|
||||||
|
// LabelSet represents a []core.KeyValue for use as pre-defined labels
|
||||||
|
// in the metrics API.
|
||||||
|
type LabelSet interface {
|
||||||
|
Meter() Meter
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObservationCallback defines a type of the callback the observer
|
||||||
|
// will use to report the measurement
|
||||||
|
type ObservationCallback func(LabelSet, MeasurementValue)
|
||||||
|
|
||||||
|
// ObserverCallback defines a type of the callback SDK will call for
|
||||||
|
// the registered observers.
|
||||||
|
type ObserverCallback func(Meter, Observer, ObservationCallback)
|
||||||
|
|
||||||
|
// Meter is an interface to the metrics portion of the OpenTelemetry SDK.
|
||||||
type Meter interface {
|
type Meter interface {
|
||||||
// TODO more Metric types
|
// DefineLabels returns a reference to a set of labels that
|
||||||
GetFloat64Gauge(ctx context.Context, gauge *Float64GaugeHandle, labels ...core.KeyValue) Float64Gauge
|
// cannot be read by the application.
|
||||||
|
DefineLabels(context.Context, ...core.KeyValue) LabelSet
|
||||||
|
|
||||||
|
// RecordBatch atomically records a batch of measurements.
|
||||||
|
RecordBatch(context.Context, LabelSet, ...Measurement)
|
||||||
|
|
||||||
|
// NewHandle creates a Handle that contains the passed
|
||||||
|
// key-value pairs. This should not be used directly - prefer
|
||||||
|
// using GetHandle function of a metric.
|
||||||
|
NewHandle(*Descriptor, LabelSet) Handle
|
||||||
|
// DeleteHandle destroys the Handle and does a cleanup of the
|
||||||
|
// underlying resources.
|
||||||
|
DeleteHandle(Handle)
|
||||||
|
|
||||||
|
// RegisterObserver registers the observer with callback
|
||||||
|
// returning a measurement. When and how often the callback
|
||||||
|
// will be called is defined by SDK. This should not be used
|
||||||
|
// directly - prefer either RegisterInt64Observer or
|
||||||
|
// RegisterFloat64Observer, depending on the type of the
|
||||||
|
// observer to be registered.
|
||||||
|
RegisterObserver(Observer, ObserverCallback)
|
||||||
|
// UnregisterObserver removes the observer from registered
|
||||||
|
// observers. This should not be used directly - prefer either
|
||||||
|
// UnregisterInt64Observer or UnregisterFloat64Observer,
|
||||||
|
// depending on the type of the observer to be registered.
|
||||||
|
UnregisterObserver(Observer)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Float64Gauge interface {
|
// DescriptorID is a unique identifier of a metric.
|
||||||
Set(ctx context.Context, value float64, labels ...core.KeyValue)
|
type DescriptorID uint64
|
||||||
|
|
||||||
|
// ValueKind describes the data type of the measurement value the
|
||||||
|
// metric generates.
|
||||||
|
type ValueKind int8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Int64ValueKind means that the metric generates values of
|
||||||
|
// type int64.
|
||||||
|
Int64ValueKind ValueKind = iota
|
||||||
|
// Float64ValueKind means that the metric generates values of
|
||||||
|
// type float64.
|
||||||
|
Float64ValueKind
|
||||||
|
)
|
||||||
|
|
||||||
|
// Descriptor represents a named metric with recommended
|
||||||
|
// local-aggregation keys.
|
||||||
|
type Descriptor struct {
|
||||||
|
name string
|
||||||
|
kind Kind
|
||||||
|
keys []core.Key
|
||||||
|
id DescriptorID
|
||||||
|
description string
|
||||||
|
unit unit.Unit
|
||||||
|
valueKind ValueKind
|
||||||
|
alternate bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Handle struct {
|
// Name is a required field describing this metric descriptor, should
|
||||||
Name string
|
// have length > 0.
|
||||||
Description string
|
func (d *Descriptor) Name() string {
|
||||||
Unit unit.Unit
|
return d.name
|
||||||
|
|
||||||
Type MetricType
|
|
||||||
Keys []core.Key
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Option func(*Handle)
|
// Kind is the metric kind of this descriptor.
|
||||||
|
func (d *Descriptor) Kind() Kind {
|
||||||
|
return d.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys are recommended keys determined in the handles obtained for
|
||||||
|
// this metric.
|
||||||
|
func (d *Descriptor) Keys() []core.Key {
|
||||||
|
return d.keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID is uniquely assigned to support per-SDK registration.
|
||||||
|
func (d *Descriptor) ID() DescriptorID {
|
||||||
|
return d.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description is an optional field describing this metric descriptor.
|
||||||
|
func (d *Descriptor) Description() string {
|
||||||
|
return d.description
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unit is an optional field describing this metric descriptor.
|
||||||
|
func (d *Descriptor) Unit() unit.Unit {
|
||||||
|
return d.unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValueKind describes the type of values the metric produces.
|
||||||
|
func (d *Descriptor) ValueKind() ValueKind {
|
||||||
|
return d.valueKind
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alternate defines the property of metric value dependent on a
|
||||||
|
// metric type.
|
||||||
|
//
|
||||||
|
// - for Counter, true implies that the metric is an up-down Counter
|
||||||
|
//
|
||||||
|
// - for Gauge/Observer, true implies that the metric is a
|
||||||
|
// non-descending Gauge/Observer
|
||||||
|
//
|
||||||
|
// - for Measure, true implies that the metric supports positive and
|
||||||
|
// negative values
|
||||||
|
func (d *Descriptor) Alternate() bool {
|
||||||
|
return d.alternate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measurement is used for reporting a batch of metric values.
|
||||||
|
type Measurement struct {
|
||||||
|
Descriptor *Descriptor
|
||||||
|
Value MeasurementValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option supports specifying the various metric options.
|
||||||
|
type Option func(*Descriptor)
|
||||||
|
|
||||||
|
// OptionApplier is an interface for applying metric options that are
|
||||||
|
// valid for all the kinds of metrics.
|
||||||
|
type OptionApplier interface {
|
||||||
|
CounterOptionApplier
|
||||||
|
GaugeOptionApplier
|
||||||
|
MeasureOptionApplier
|
||||||
|
// ApplyOption is used to make some changes in the Descriptor.
|
||||||
|
ApplyOption(*Descriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
type optionWrapper struct {
|
||||||
|
F Option
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ OptionApplier = optionWrapper{}
|
||||||
|
|
||||||
|
func (o optionWrapper) ApplyCounterOption(d *Descriptor) {
|
||||||
|
o.ApplyOption(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o optionWrapper) ApplyGaugeOption(d *Descriptor) {
|
||||||
|
o.ApplyOption(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o optionWrapper) ApplyMeasureOption(d *Descriptor) {
|
||||||
|
o.ApplyOption(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o optionWrapper) ApplyOption(d *Descriptor) {
|
||||||
|
o.F(d)
|
||||||
|
}
|
||||||
|
|
||||||
// WithDescription applies provided description.
|
// WithDescription applies provided description.
|
||||||
func WithDescription(desc string) Option {
|
func WithDescription(desc string) OptionApplier {
|
||||||
return func(m *Handle) {
|
return optionWrapper{
|
||||||
m.Description = desc
|
F: func(d *Descriptor) {
|
||||||
|
d.description = desc
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithUnit applies provided unit.
|
// WithUnit applies provided unit.
|
||||||
func WithUnit(unit unit.Unit) Option {
|
func WithUnit(unit unit.Unit) OptionApplier {
|
||||||
return func(m *Handle) {
|
return optionWrapper{
|
||||||
m.Unit = unit
|
F: func(d *Descriptor) {
|
||||||
|
d.unit = unit
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithKeys applies the provided dimension keys.
|
// WithKeys applies required label keys. Multiple `WithKeys` options
|
||||||
func WithKeys(keys ...core.Key) Option {
|
// accumulate.
|
||||||
return func(m *Handle) {
|
func WithKeys(keys ...core.Key) OptionApplier {
|
||||||
m.Keys = keys
|
return optionWrapper{
|
||||||
|
F: func(d *Descriptor) {
|
||||||
|
d.keys = append(d.keys, keys...)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mtype MetricType) String() string {
|
// WithNonMonotonic sets whether a counter is permitted to go up AND
|
||||||
switch mtype {
|
// down.
|
||||||
case Gauge:
|
func WithNonMonotonic(nm bool) CounterOptionApplier {
|
||||||
return "gauge"
|
return counterOptionWrapper{
|
||||||
case Cumulative:
|
F: func(d *Descriptor) {
|
||||||
return "cumulative"
|
d.alternate = nm
|
||||||
default:
|
},
|
||||||
return "unknown"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithMonotonic sets whether a gauge is not permitted to go down.
|
||||||
|
func WithMonotonic(m bool) GaugeOptionApplier {
|
||||||
|
return gaugeOptionWrapper{
|
||||||
|
F: func(d *Descriptor) {
|
||||||
|
d.alternate = m
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSigned sets whether a measure is permitted to be negative.
|
||||||
|
func WithSigned(s bool) MeasureOptionApplier {
|
||||||
|
return measureOptionWrapper{
|
||||||
|
F: func(d *Descriptor) {
|
||||||
|
d.alternate = s
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defined returns true when the descriptor has been registered.
|
||||||
|
func (d Descriptor) Defined() bool {
|
||||||
|
return len(d.name) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordBatch reports to the global Meter.
|
||||||
|
func RecordBatch(ctx context.Context, labels LabelSet, batch ...Measurement) {
|
||||||
|
GlobalMeter().RecordBatch(ctx, labels, batch...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64ObservationCallback defines a type of the callback the
|
||||||
|
// observer will use to report the int64 measurement.
|
||||||
|
type Int64ObservationCallback func(LabelSet, int64)
|
||||||
|
|
||||||
|
// Int64ObserverCallback defines a type of the callback SDK will call
|
||||||
|
// for the registered int64 observers.
|
||||||
|
type Int64ObserverCallback func(Meter, Int64Observer, Int64ObservationCallback)
|
||||||
|
|
||||||
|
// RegisterInt64Observer is a convenience wrapper around
|
||||||
|
// Meter.RegisterObserver that provides a type-safe callback for
|
||||||
|
// Int64Observer.
|
||||||
|
func RegisterInt64Observer(meter Meter, observer Int64Observer, callback Int64ObserverCallback) {
|
||||||
|
cb := func(m Meter, o Observer, ocb ObservationCallback) {
|
||||||
|
iocb := func(l LabelSet, i int64) {
|
||||||
|
ocb(l, NewInt64MeasurementValue(i))
|
||||||
|
}
|
||||||
|
callback(m, Int64Observer{o}, iocb)
|
||||||
|
}
|
||||||
|
meter.RegisterObserver(observer.Observer, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnregisterInt64Observer is a convenience wrapper around
|
||||||
|
// Meter.UnregisterObserver for Int64Observer.
|
||||||
|
func UnregisterInt64Observer(meter Meter, observer Int64Observer) {
|
||||||
|
meter.UnregisterObserver(observer.Observer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64ObservationCallback defines a type of the callback the
|
||||||
|
// observer will use to report the float64 measurement.
|
||||||
|
type Float64ObservationCallback func(LabelSet, float64)
|
||||||
|
|
||||||
|
// Float64ObserverCallback defines a type of the callback SDK will
|
||||||
|
// call for the registered float64 observers.
|
||||||
|
type Float64ObserverCallback func(Meter, Float64Observer, Float64ObservationCallback)
|
||||||
|
|
||||||
|
// RegisterFloat64Observer is a convenience wrapper around
|
||||||
|
// Meter.RegisterObserver that provides a type-safe callback for
|
||||||
|
// Float64Observer.
|
||||||
|
func RegisterFloat64Observer(meter Meter, observer Float64Observer, callback Float64ObserverCallback) {
|
||||||
|
cb := func(m Meter, o Observer, ocb ObservationCallback) {
|
||||||
|
focb := func(l LabelSet, f float64) {
|
||||||
|
ocb(l, NewFloat64MeasurementValue(f))
|
||||||
|
}
|
||||||
|
callback(m, Float64Observer{o}, focb)
|
||||||
|
}
|
||||||
|
meter.RegisterObserver(observer.Observer, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnregisterFloat64Observer is a convenience wrapper around
|
||||||
|
// Meter.UnregisterObserver for Float64Observer.
|
||||||
|
func UnregisterFloat64Observer(meter Meter, observer Float64Observer) {
|
||||||
|
meter.UnregisterObserver(observer.Observer)
|
||||||
|
}
|
||||||
|
@ -14,11 +14,60 @@
|
|||||||
|
|
||||||
package metric
|
package metric
|
||||||
|
|
||||||
func registerMetric(name string, mtype MetricType, opts []Option, metric *Handle) {
|
import (
|
||||||
for _, opt := range opts {
|
"context"
|
||||||
opt(metric)
|
"sync/atomic"
|
||||||
}
|
)
|
||||||
|
|
||||||
metric.Name = name
|
var (
|
||||||
metric.Type = mtype
|
descriptorID uint64
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: Maybe unexport that and document very _very_ clearly, that
|
||||||
|
// you can still get a descriptor with NewInt64Counter(…).Descriptor
|
||||||
|
|
||||||
|
// CommonMetric holds a descriptor. It is used mostly to implement the
|
||||||
|
// common parts for every metric kind.
|
||||||
|
type CommonMetric struct {
|
||||||
|
*Descriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m CommonMetric) getHandle(labels LabelSet) Handle {
|
||||||
|
return labels.Meter().NewHandle(m.Descriptor, labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m CommonMetric) float64Measurement(value float64) Measurement {
|
||||||
|
return Measurement{
|
||||||
|
Descriptor: m.Descriptor,
|
||||||
|
Value: NewFloat64MeasurementValue(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m CommonMetric) int64Measurement(value int64) Measurement {
|
||||||
|
return Measurement{
|
||||||
|
Descriptor: m.Descriptor,
|
||||||
|
Value: NewInt64MeasurementValue(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m CommonMetric) recordOne(ctx context.Context, value MeasurementValue, labels LabelSet) {
|
||||||
|
labels.Meter().RecordBatch(ctx, labels, Measurement{
|
||||||
|
Descriptor: m.Descriptor,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerCommonMetric(name string, kind Kind, valueKind ValueKind) CommonMetric {
|
||||||
|
return CommonMetric{
|
||||||
|
Descriptor: registerDescriptor(name, kind, valueKind),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerDescriptor(name string, kind Kind, valueKind ValueKind) *Descriptor {
|
||||||
|
return &Descriptor{
|
||||||
|
name: name,
|
||||||
|
kind: kind,
|
||||||
|
valueKind: valueKind,
|
||||||
|
id: DescriptorID(atomic.AddUint64(&descriptorID, 1)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
129
api/metric/counter.go
Normal file
129
api/metric/counter.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
// Copyright 2019, 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 metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Float64Counter is a metric that accumulates float64 values.
|
||||||
|
type Float64Counter struct {
|
||||||
|
CommonMetric
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Counter is a metric that accumulates int64 values.
|
||||||
|
type Int64Counter struct {
|
||||||
|
CommonMetric
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64CounterHandle is a handle for Float64Counter.
|
||||||
|
type Float64CounterHandle struct {
|
||||||
|
Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64CounterHandle is a handle for Int64Counter.
|
||||||
|
type Int64CounterHandle struct {
|
||||||
|
Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// CounterOptionApplier is an interface for applying metric options
|
||||||
|
// that are valid only for counter metrics.
|
||||||
|
type CounterOptionApplier interface {
|
||||||
|
// ApplyCounterOption is used to make some counter-specific
|
||||||
|
// changes in the Descriptor.
|
||||||
|
ApplyCounterOption(*Descriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
type counterOptionWrapper struct {
|
||||||
|
F Option
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ CounterOptionApplier = counterOptionWrapper{}
|
||||||
|
|
||||||
|
func (o counterOptionWrapper) ApplyCounterOption(d *Descriptor) {
|
||||||
|
o.F(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCounter(name string, valueKind ValueKind, mos ...CounterOptionApplier) CommonMetric {
|
||||||
|
m := registerCommonMetric(name, CounterKind, valueKind)
|
||||||
|
for _, opt := range mos {
|
||||||
|
opt.ApplyCounterOption(m.Descriptor)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64Counter creates a new counter for float64.
|
||||||
|
func NewFloat64Counter(name string, mos ...CounterOptionApplier) (c Float64Counter) {
|
||||||
|
c.CommonMetric = newCounter(name, Float64ValueKind, mos...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64Counter creates a new counter for int64.
|
||||||
|
func NewInt64Counter(name string, mos ...CounterOptionApplier) (c Int64Counter) {
|
||||||
|
c.CommonMetric = newCounter(name, Int64ValueKind, mos...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHandle creates a handle for this counter. The labels should
|
||||||
|
// contain the keys and values specified in the counter with the
|
||||||
|
// WithKeys option.
|
||||||
|
func (c *Float64Counter) GetHandle(labels LabelSet) (h Float64CounterHandle) {
|
||||||
|
h.Handle = c.getHandle(labels)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHandle creates a handle for this counter. The labels should
|
||||||
|
// contain the keys and values specified in the counter with the
|
||||||
|
// WithKeys option.
|
||||||
|
func (c *Int64Counter) GetHandle(labels LabelSet) (h Int64CounterHandle) {
|
||||||
|
h.Handle = c.getHandle(labels)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measurement creates a Measurement object to use with batch
|
||||||
|
// recording.
|
||||||
|
func (c *Float64Counter) Measurement(value float64) Measurement {
|
||||||
|
return c.float64Measurement(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measurement creates a Measurement object to use with batch
|
||||||
|
// recording.
|
||||||
|
func (c *Int64Counter) Measurement(value int64) Measurement {
|
||||||
|
return c.int64Measurement(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds the value to the counter's sum. The labels should contain
|
||||||
|
// the keys and values specified in the counter with the WithKeys
|
||||||
|
// option.
|
||||||
|
func (c *Float64Counter) Add(ctx context.Context, value float64, labels LabelSet) {
|
||||||
|
c.recordOne(ctx, NewFloat64MeasurementValue(value), labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds the value to the counter's sum. The labels should contain
|
||||||
|
// the keys and values specified in the counter with the WithKeys
|
||||||
|
// option.
|
||||||
|
func (c *Int64Counter) Add(ctx context.Context, value int64, labels LabelSet) {
|
||||||
|
c.recordOne(ctx, NewInt64MeasurementValue(value), labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds the value to the counter's sum.
|
||||||
|
func (h *Float64CounterHandle) Add(ctx context.Context, value float64) {
|
||||||
|
h.RecordOne(ctx, NewFloat64MeasurementValue(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds the value to the counter's sum.
|
||||||
|
func (h *Int64CounterHandle) Add(ctx context.Context, value int64) {
|
||||||
|
h.RecordOne(ctx, NewInt64MeasurementValue(value))
|
||||||
|
}
|
@ -12,4 +12,68 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
// metric package provides an API for reporting diagnostic
|
||||||
|
// measurements using three basic kinds of instruments (or four, if
|
||||||
|
// calling one special case a separate one).
|
||||||
|
//
|
||||||
|
// The three basic kinds are:
|
||||||
|
//
|
||||||
|
// - counters
|
||||||
|
// - gauges
|
||||||
|
// - measures
|
||||||
|
//
|
||||||
|
// There is also a special case of a gauge instrument called
|
||||||
|
// observer. It will be discussed later.
|
||||||
|
//
|
||||||
|
// All instruments report either float64 or int64 values.
|
||||||
|
//
|
||||||
|
// The primary object that handles metrics is Meter. The
|
||||||
|
// implementation of the Meter is provided by SDK. Normally, the Meter
|
||||||
|
// is used directly only for the LabelSet generation, batch recording
|
||||||
|
// and the handle destruction.
|
||||||
|
//
|
||||||
|
// LabelSet is a set of keys and values that are in a suitable,
|
||||||
|
// optimized form to be used by Meter.
|
||||||
|
//
|
||||||
|
// Counters are instruments that are reporting a quantity or a sum. An
|
||||||
|
// example could be bank account balance or bytes downloaded. Counters
|
||||||
|
// can be created with either NewFloat64Counter or
|
||||||
|
// NewInt64Counter. Counters expect non-negative values by default to
|
||||||
|
// be reported. This can be changed with the WithNonMonotonic option
|
||||||
|
// passed to the New*Counter function - this allows reporting negative
|
||||||
|
// values. To report the new value, use an Add function.
|
||||||
|
//
|
||||||
|
// Gauges are instruments that are reporting a current state of a
|
||||||
|
// value. An example could be voltage or temperature. Gauges can be
|
||||||
|
// created with either NewFloat64Gauge or NewInt64Gauge. Gauges by
|
||||||
|
// default have no limitations about reported values - they can be
|
||||||
|
// less or greater than the last reported value. This can be changed
|
||||||
|
// with the WithMonotonic option passed to the New*Gauge function -
|
||||||
|
// this permits the reported values only to go up. To report a new
|
||||||
|
// value, use the Set function.
|
||||||
|
//
|
||||||
|
// Measures are instruments that are reporting values that are
|
||||||
|
// recorded separately to figure out some statistical properties from
|
||||||
|
// those values (like average). An example could be temperature over
|
||||||
|
// time or lines of code in the project over time. Measures can be
|
||||||
|
// created with either NewFloat64Measure or NewInt64Measure. Measures
|
||||||
|
// by default take only non-negative values. This can be changed with
|
||||||
|
// the WithSigned option passed to the New*Measure function - this
|
||||||
|
// allows reporting negative values too. To report a new value, use
|
||||||
|
// the Record function.
|
||||||
|
//
|
||||||
|
// A special case of a gauge is observer. It has the same role as
|
||||||
|
// gauge, but reports values in a different way. The observer can be
|
||||||
|
// created with NewFloat64Observer or NewInt64Observer and then
|
||||||
|
// registered within a Meter with either RegisterInt64Observer or
|
||||||
|
// RegisterFloat64Observer functions. These take a callback for
|
||||||
|
// reporting the values. The callback will be called by the Meter when
|
||||||
|
// it deems it necessary.
|
||||||
|
//
|
||||||
|
// All the basic kinds of instruments (so, not observers) also support
|
||||||
|
// creating handles for a potentially more efficient reporting. The
|
||||||
|
// handles have the same function names as the instruments (so counter
|
||||||
|
// handle has Add, gauge handle has Set and measure handle has
|
||||||
|
// Record). Handles can be created with the GetHandle function of the
|
||||||
|
// respective instrument.
|
||||||
package metric // import "go.opentelemetry.io/api/metric"
|
package metric // import "go.opentelemetry.io/api/metric"
|
||||||
|
@ -14,12 +14,116 @@
|
|||||||
|
|
||||||
package metric
|
package metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Float64Gauge is a metric that stores the last float64 value.
|
||||||
|
type Float64Gauge struct {
|
||||||
|
CommonMetric
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Gauge is a metric that stores the last int64 value.
|
||||||
|
type Int64Gauge struct {
|
||||||
|
CommonMetric
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64GaugeHandle is a handle for Float64Gauge.
|
||||||
type Float64GaugeHandle struct {
|
type Float64GaugeHandle struct {
|
||||||
Handle
|
Handle
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFloat64Gauge(name string, mos ...Option) *Float64GaugeHandle {
|
// Int64GaugeHandle is a handle for Int64Gauge.
|
||||||
g := &Float64GaugeHandle{}
|
type Int64GaugeHandle struct {
|
||||||
registerMetric(name, Gauge, mos, &g.Handle)
|
Handle
|
||||||
return g
|
}
|
||||||
|
|
||||||
|
// GaugeOptionApplier is an interface for applying metric options that
|
||||||
|
// are valid only for gauge metrics.
|
||||||
|
type GaugeOptionApplier interface {
|
||||||
|
// ApplyGaugeOption is used to make some gauge-specific
|
||||||
|
// changes in the Descriptor.
|
||||||
|
ApplyGaugeOption(*Descriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
type gaugeOptionWrapper struct {
|
||||||
|
F Option
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ GaugeOptionApplier = gaugeOptionWrapper{}
|
||||||
|
|
||||||
|
func (o gaugeOptionWrapper) ApplyGaugeOption(d *Descriptor) {
|
||||||
|
o.F(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGauge(name string, valueKind ValueKind, mos ...GaugeOptionApplier) CommonMetric {
|
||||||
|
m := registerCommonMetric(name, GaugeKind, valueKind)
|
||||||
|
for _, opt := range mos {
|
||||||
|
opt.ApplyGaugeOption(m.Descriptor)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64Gauge creates a new gauge for float64.
|
||||||
|
func NewFloat64Gauge(name string, mos ...GaugeOptionApplier) (g Float64Gauge) {
|
||||||
|
g.CommonMetric = newGauge(name, Float64ValueKind, mos...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64Gauge creates a new gauge for int64.
|
||||||
|
func NewInt64Gauge(name string, mos ...GaugeOptionApplier) (g Int64Gauge) {
|
||||||
|
g.CommonMetric = newGauge(name, Int64ValueKind, mos...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHandle creates a handle for this gauge. The labels should
|
||||||
|
// contain the keys and values specified in the gauge with the
|
||||||
|
// WithKeys option.
|
||||||
|
func (g *Float64Gauge) GetHandle(labels LabelSet) (h Float64GaugeHandle) {
|
||||||
|
h.Handle = g.getHandle(labels)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHandle creates a handle for this gauge. The labels should
|
||||||
|
// contain the keys and values specified in the gauge with the
|
||||||
|
// WithKeys option.
|
||||||
|
func (g *Int64Gauge) GetHandle(labels LabelSet) (h Int64GaugeHandle) {
|
||||||
|
h.Handle = g.getHandle(labels)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measurement creates a Measurement object to use with batch
|
||||||
|
// recording.
|
||||||
|
func (g *Float64Gauge) Measurement(value float64) Measurement {
|
||||||
|
return g.float64Measurement(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measurement creates a Measurement object to use with batch
|
||||||
|
// recording.
|
||||||
|
func (g *Int64Gauge) Measurement(value int64) Measurement {
|
||||||
|
return g.int64Measurement(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the value of the gauge to the passed value. The labels
|
||||||
|
// should contain the keys and values specified in the gauge with the
|
||||||
|
// WithKeys option.
|
||||||
|
func (g *Float64Gauge) Set(ctx context.Context, value float64, labels LabelSet) {
|
||||||
|
g.recordOne(ctx, NewFloat64MeasurementValue(value), labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the value of the gauge to the passed value. The labels
|
||||||
|
// should contain the keys and values specified in the gauge with the
|
||||||
|
// WithKeys option.
|
||||||
|
func (g *Int64Gauge) Set(ctx context.Context, value int64, labels LabelSet) {
|
||||||
|
g.recordOne(ctx, NewInt64MeasurementValue(value), labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the value of the gauge to the passed value.
|
||||||
|
func (h *Float64GaugeHandle) Set(ctx context.Context, value float64) {
|
||||||
|
h.RecordOne(ctx, NewFloat64MeasurementValue(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the value of the gauge to the passed value.
|
||||||
|
func (h *Int64GaugeHandle) Set(ctx context.Context, value int64) {
|
||||||
|
h.RecordOne(ctx, NewInt64MeasurementValue(value))
|
||||||
}
|
}
|
||||||
|
@ -18,13 +18,13 @@ import "sync/atomic"
|
|||||||
|
|
||||||
var global atomic.Value
|
var global atomic.Value
|
||||||
|
|
||||||
// GlobalMeter return meter registered with global registry.
|
// GlobalMeter returns a meter registered as a global meter. If no
|
||||||
// If no meter is registered then an instance of noop Meter is returned.
|
// meter is registered then an instance of noop Meter is returned.
|
||||||
func GlobalMeter() Meter {
|
func GlobalMeter() Meter {
|
||||||
if t := global.Load(); t != nil {
|
if t := global.Load(); t != nil {
|
||||||
return t.(Meter)
|
return t.(Meter)
|
||||||
}
|
}
|
||||||
return NoopMeter{}
|
return noopMeter{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetGlobalMeter sets provided meter as a global meter.
|
// SetGlobalMeter sets provided meter as a global meter.
|
||||||
|
45
api/metric/kind_string.go
Normal file
45
api/metric/kind_string.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Code generated by "stringer -type=Kind,ValueKind"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package metric
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[Invalid-0]
|
||||||
|
_ = x[CounterKind-1]
|
||||||
|
_ = x[GaugeKind-2]
|
||||||
|
_ = x[MeasureKind-3]
|
||||||
|
_ = x[ObserverKind-4]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _Kind_name = "InvalidCounterKindGaugeKindMeasureKindObserverKind"
|
||||||
|
|
||||||
|
var _Kind_index = [...]uint8{0, 7, 18, 27, 38, 50}
|
||||||
|
|
||||||
|
func (i Kind) String() string {
|
||||||
|
if i < 0 || i >= Kind(len(_Kind_index)-1) {
|
||||||
|
return "Kind(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _Kind_name[_Kind_index[i]:_Kind_index[i+1]]
|
||||||
|
}
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[Int64ValueKind-0]
|
||||||
|
_ = x[Float64ValueKind-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _ValueKind_name = "Int64ValueKindFloat64ValueKind"
|
||||||
|
|
||||||
|
var _ValueKind_index = [...]uint8{0, 14, 30}
|
||||||
|
|
||||||
|
func (i ValueKind) String() string {
|
||||||
|
if i < 0 || i >= ValueKind(len(_ValueKind_index)-1) {
|
||||||
|
return "ValueKind(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _ValueKind_name[_ValueKind_index[i]:_ValueKind_index[i+1]]
|
||||||
|
}
|
129
api/metric/measure.go
Normal file
129
api/metric/measure.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
// Copyright 2019, 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 metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Float64Measure is a metric that records float64 values.
|
||||||
|
type Float64Measure struct {
|
||||||
|
CommonMetric
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Measure is a metric that records int64 values.
|
||||||
|
type Int64Measure struct {
|
||||||
|
CommonMetric
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64MeasureHandle is a handle for Float64Measure.
|
||||||
|
type Float64MeasureHandle struct {
|
||||||
|
Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64MeasureHandle is a handle for Int64Measure.
|
||||||
|
type Int64MeasureHandle struct {
|
||||||
|
Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// MeasureOptionApplier is an interface for applying metric options
|
||||||
|
// that are valid only for measure metrics.
|
||||||
|
type MeasureOptionApplier interface {
|
||||||
|
// ApplyMeasureOption is used to make some measure-specific
|
||||||
|
// changes in the Descriptor.
|
||||||
|
ApplyMeasureOption(*Descriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
type measureOptionWrapper struct {
|
||||||
|
F Option
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ MeasureOptionApplier = measureOptionWrapper{}
|
||||||
|
|
||||||
|
func (o measureOptionWrapper) ApplyMeasureOption(d *Descriptor) {
|
||||||
|
o.F(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMeasure(name string, valueKind ValueKind, mos ...MeasureOptionApplier) CommonMetric {
|
||||||
|
m := registerCommonMetric(name, MeasureKind, valueKind)
|
||||||
|
for _, opt := range mos {
|
||||||
|
opt.ApplyMeasureOption(m.Descriptor)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64Measure creates a new measure for float64.
|
||||||
|
func NewFloat64Measure(name string, mos ...MeasureOptionApplier) (c Float64Measure) {
|
||||||
|
c.CommonMetric = newMeasure(name, Float64ValueKind, mos...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64Measure creates a new measure for int64.
|
||||||
|
func NewInt64Measure(name string, mos ...MeasureOptionApplier) (c Int64Measure) {
|
||||||
|
c.CommonMetric = newMeasure(name, Int64ValueKind, mos...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHandle creates a handle for this measure. The labels should
|
||||||
|
// contain the keys and values specified in the measure with the
|
||||||
|
// WithKeys option.
|
||||||
|
func (c *Float64Measure) GetHandle(labels LabelSet) (h Float64MeasureHandle) {
|
||||||
|
h.Handle = c.getHandle(labels)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHandle creates a handle for this measure. The labels should
|
||||||
|
// contain the keys and values specified in the measure with the
|
||||||
|
// WithKeys option.
|
||||||
|
func (c *Int64Measure) GetHandle(labels LabelSet) (h Int64MeasureHandle) {
|
||||||
|
h.Handle = c.getHandle(labels)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measurement creates a Measurement object to use with batch
|
||||||
|
// recording.
|
||||||
|
func (c *Float64Measure) Measurement(value float64) Measurement {
|
||||||
|
return c.float64Measurement(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measurement creates a Measurement object to use with batch
|
||||||
|
// recording.
|
||||||
|
func (c *Int64Measure) Measurement(value int64) Measurement {
|
||||||
|
return c.int64Measurement(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record adds a new value to the list of measure's records. The
|
||||||
|
// labels should contain the keys and values specified in the measure
|
||||||
|
// with the WithKeys option.
|
||||||
|
func (c *Float64Measure) Record(ctx context.Context, value float64, labels LabelSet) {
|
||||||
|
c.recordOne(ctx, NewFloat64MeasurementValue(value), labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record adds a new value to the list of measure's records. The
|
||||||
|
// labels should contain the keys and values specified in the measure
|
||||||
|
// with the WithKeys option.
|
||||||
|
func (c *Int64Measure) Record(ctx context.Context, value int64, labels LabelSet) {
|
||||||
|
c.recordOne(ctx, NewInt64MeasurementValue(value), labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record adds a new value to the list of measure's records.
|
||||||
|
func (h *Float64MeasureHandle) Record(ctx context.Context, value float64) {
|
||||||
|
h.RecordOne(ctx, NewFloat64MeasurementValue(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record adds a new value to the list of measure's records.
|
||||||
|
func (h *Int64MeasureHandle) Record(ctx context.Context, value int64) {
|
||||||
|
h.RecordOne(ctx, NewInt64MeasurementValue(value))
|
||||||
|
}
|
@ -6,17 +6,37 @@ import (
|
|||||||
"go.opentelemetry.io/api/core"
|
"go.opentelemetry.io/api/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NoopMeter struct{}
|
type noopMeter struct{}
|
||||||
|
type noopHandle struct{}
|
||||||
|
type noopLabelSet struct{}
|
||||||
|
|
||||||
type noopMetric struct{}
|
var _ Meter = noopMeter{}
|
||||||
|
var _ Handle = noopHandle{}
|
||||||
|
var _ LabelSet = noopLabelSet{}
|
||||||
|
|
||||||
var _ Meter = NoopMeter{}
|
func (noopHandle) RecordOne(context.Context, MeasurementValue) {
|
||||||
|
|
||||||
var _ Float64Gauge = noopMetric{}
|
|
||||||
|
|
||||||
func (NoopMeter) GetFloat64Gauge(ctx context.Context, gauge *Float64GaugeHandle, labels ...core.KeyValue) Float64Gauge {
|
|
||||||
return noopMetric{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (noopMetric) Set(ctx context.Context, value float64, labels ...core.KeyValue) {
|
func (noopLabelSet) Meter() Meter {
|
||||||
|
return noopMeter{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (noopMeter) DefineLabels(context.Context, ...core.KeyValue) LabelSet {
|
||||||
|
return noopLabelSet{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (noopMeter) NewHandle(*Descriptor, LabelSet) Handle {
|
||||||
|
return noopHandle{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (noopMeter) DeleteHandle(Handle) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (noopMeter) RecordBatch(context.Context, LabelSet, ...Measurement) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (noopMeter) RegisterObserver(Observer, ObserverCallback) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (noopMeter) UnregisterObserver(Observer) {
|
||||||
}
|
}
|
||||||
|
50
api/metric/observer.go
Normal file
50
api/metric/observer.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2019, 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 metric
|
||||||
|
|
||||||
|
// Observer is a base of typed-observers. Shouldn't be used directly.
|
||||||
|
type Observer struct {
|
||||||
|
*Descriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64Observer is an observer that reports float64 values.
|
||||||
|
type Float64Observer struct {
|
||||||
|
Observer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Observer is an observer that reports int64 values.
|
||||||
|
type Int64Observer struct {
|
||||||
|
Observer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newObserver(name string, valueKind ValueKind, mos ...GaugeOptionApplier) (o Observer) {
|
||||||
|
o.Descriptor = registerDescriptor(name, ObserverKind, valueKind)
|
||||||
|
for _, opt := range mos {
|
||||||
|
opt.ApplyGaugeOption(o.Descriptor)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64Observer creates a new observer for float64.
|
||||||
|
func NewFloat64Observer(name string, mos ...GaugeOptionApplier) (o Float64Observer) {
|
||||||
|
o.Observer = newObserver(name, Float64ValueKind, mos...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64Observer creates a new observer for int64.
|
||||||
|
func NewInt64Observer(name string, mos ...GaugeOptionApplier) (o Int64Observer) {
|
||||||
|
o.Observer = newObserver(name, Int64ValueKind, mos...)
|
||||||
|
return
|
||||||
|
}
|
168
api/metric/value.go
Normal file
168
api/metric/value.go
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
// Copyright 2019, 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 metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MeasurementValue represents either an integral or a floating point
|
||||||
|
// value of a measurement. It needs to be accompanied with a
|
||||||
|
// descriptor of a metric that generated this value to decide what
|
||||||
|
// type of value it represents.
|
||||||
|
type MeasurementValue uint64
|
||||||
|
|
||||||
|
// NewInt64MeasurementValue creates an integral MeasurementValue.
|
||||||
|
func NewInt64MeasurementValue(i int64) MeasurementValue {
|
||||||
|
return newFromRaw(int64ToRaw(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloat64MeasurementValue creates a floating point
|
||||||
|
// MeasurementValue.
|
||||||
|
func NewFloat64MeasurementValue(f float64) MeasurementValue {
|
||||||
|
return newFromRaw(float64ToRaw(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFromRaw(raw uint64) MeasurementValue {
|
||||||
|
return MeasurementValue(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsInt64 assumes that the measurement value contains an int64 and
|
||||||
|
// returns it as such. Make sure that metric that generated this value
|
||||||
|
// has indeed Int64ValueKind in its descriptor.
|
||||||
|
func (v MeasurementValue) AsInt64() int64 {
|
||||||
|
return rawToInt64(v.AsRaw())
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsFloat64 assumes that the measurement value contains a float64 and
|
||||||
|
// returns it as such. Make sure that metric that generated this value
|
||||||
|
// has indeed Int64ValueKind in its descriptor.
|
||||||
|
func (v MeasurementValue) AsFloat64() float64 {
|
||||||
|
return rawToFloat64(v.AsRaw())
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsRaw gets the raw, uninterpreted value of the measurement. Might
|
||||||
|
// be useful for some atomic operations.
|
||||||
|
func (v MeasurementValue) AsRaw() uint64 {
|
||||||
|
return uint64(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsRawPtr gets the pointer to the raw, uninterpreted value of the
|
||||||
|
// measurement. Might be useful for some atomic operations.
|
||||||
|
func (v *MeasurementValue) AsRawPtr() *uint64 {
|
||||||
|
return (*uint64)(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit returns a string representation of the actual value of the
|
||||||
|
// MeasurementValue. A %d is used for integral values, %f for floating
|
||||||
|
// point values.
|
||||||
|
func (v MeasurementValue) Emit(kind ValueKind) string {
|
||||||
|
switch kind {
|
||||||
|
case Int64ValueKind:
|
||||||
|
return fmt.Sprintf("%d", v.AsInt64())
|
||||||
|
case Float64ValueKind:
|
||||||
|
return fmt.Sprintf("%f", v.AsFloat64())
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64Compare assumes that the MeasurementValue contains a float64
|
||||||
|
// and performs a comparison between the value and the other value. It
|
||||||
|
// returns the typical result of the compare function: -1 if the value
|
||||||
|
// is less than the other, 0 if both are equal, 1 if the value is
|
||||||
|
// greater than the other.
|
||||||
|
func (v MeasurementValue) Float64Compare(other float64) int {
|
||||||
|
this := v.AsFloat64()
|
||||||
|
if this < other {
|
||||||
|
return -1
|
||||||
|
} else if this > other {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Compare assumes that the MeasurementValue contains an int64
|
||||||
|
// and performs a comparison between the value and the other value. It
|
||||||
|
// returns the typical result of the compare function: -1 if the value
|
||||||
|
// is less than the other, 0 if both are equal, 1 if the value is
|
||||||
|
// greater than the other.
|
||||||
|
func (v MeasurementValue) Int64Compare(other int64) int {
|
||||||
|
this := v.AsInt64()
|
||||||
|
if this < other {
|
||||||
|
return -1
|
||||||
|
} else if this > other {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawCompare calls either Float64Compare or Int64Compare, depending
|
||||||
|
// on the passed kind.
|
||||||
|
func (v MeasurementValue) RawCompare(other uint64, kind ValueKind) int {
|
||||||
|
switch kind {
|
||||||
|
case Int64ValueKind:
|
||||||
|
return v.Int64Compare(rawToInt64(other))
|
||||||
|
case Float64ValueKind:
|
||||||
|
return v.Float64Compare(rawToFloat64(other))
|
||||||
|
default:
|
||||||
|
// you get what you deserve
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPositive returns true if the actual value is greater than zero.
|
||||||
|
func (v MeasurementValue) IsPositive(kind ValueKind) bool {
|
||||||
|
return v.compareWithZero(kind) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNegative returns true if the actual value is less than zero.
|
||||||
|
func (v MeasurementValue) IsNegative(kind ValueKind) bool {
|
||||||
|
return v.compareWithZero(kind) < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns true if the actual value is equal to zero.
|
||||||
|
func (v MeasurementValue) IsZero(kind ValueKind) bool {
|
||||||
|
return v.compareWithZero(kind) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v MeasurementValue) compareWithZero(kind ValueKind) int {
|
||||||
|
switch kind {
|
||||||
|
case Int64ValueKind:
|
||||||
|
return v.Int64Compare(0)
|
||||||
|
case Float64ValueKind:
|
||||||
|
return v.Float64Compare(0.)
|
||||||
|
default:
|
||||||
|
// you get what you deserve
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func rawToFloat64(r uint64) float64 {
|
||||||
|
return math.Float64frombits(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func float64ToRaw(f float64) uint64 {
|
||||||
|
return math.Float64bits(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rawToInt64(r uint64) int64 {
|
||||||
|
return int64(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func int64ToRaw(i int64) uint64 {
|
||||||
|
return uint64(i)
|
||||||
|
}
|
148
api/metric/value_test.go
Normal file
148
api/metric/value_test.go
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// Copyright 2019, 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 metric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMeasurementValue(t *testing.T) {
|
||||||
|
iNeg := NewInt64MeasurementValue(-42)
|
||||||
|
iZero := NewInt64MeasurementValue(0)
|
||||||
|
iPos := NewInt64MeasurementValue(42)
|
||||||
|
i64Values := [3]MeasurementValue{iNeg, iZero, iPos}
|
||||||
|
|
||||||
|
for idx, i := range []int64{-42, 0, 42} {
|
||||||
|
v := i64Values[idx]
|
||||||
|
if got := v.AsInt64(); got != i {
|
||||||
|
t.Errorf("Value %#v (%s) int64 check failed, expected %d, got %d", v, v.Emit(Int64ValueKind), i, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range i64Values {
|
||||||
|
expected := unsafe.Pointer(&v)
|
||||||
|
got := unsafe.Pointer(v.AsRawPtr())
|
||||||
|
if expected != got {
|
||||||
|
t.Errorf("Getting raw pointer failed, got %v, expected %v", got, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fNeg := NewFloat64MeasurementValue(-42.)
|
||||||
|
fZero := NewFloat64MeasurementValue(0.)
|
||||||
|
fPos := NewFloat64MeasurementValue(42.)
|
||||||
|
f64Values := [3]MeasurementValue{fNeg, fZero, fPos}
|
||||||
|
|
||||||
|
for idx, f := range []float64{-42., 0., 42.} {
|
||||||
|
v := f64Values[idx]
|
||||||
|
if got := v.AsFloat64(); got != f {
|
||||||
|
t.Errorf("Value %#v (%s) float64 check failed, expected %f, got %f", v, v.Emit(Int64ValueKind), f, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range f64Values {
|
||||||
|
expected := unsafe.Pointer(&v)
|
||||||
|
got := unsafe.Pointer(v.AsRawPtr())
|
||||||
|
if expected != got {
|
||||||
|
t.Errorf("Getting raw pointer failed, got %v, expected %v", got, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmpsForNeg := [3]int{0, -1, -1}
|
||||||
|
cmpsForZero := [3]int{1, 0, -1}
|
||||||
|
cmpsForPos := [3]int{1, 1, 0}
|
||||||
|
|
||||||
|
type testcase struct {
|
||||||
|
v MeasurementValue
|
||||||
|
kind ValueKind
|
||||||
|
pos bool
|
||||||
|
zero bool
|
||||||
|
neg bool
|
||||||
|
vals [3]MeasurementValue
|
||||||
|
cmps [3]int
|
||||||
|
}
|
||||||
|
testcases := []testcase{
|
||||||
|
{
|
||||||
|
v: iNeg,
|
||||||
|
kind: Int64ValueKind,
|
||||||
|
pos: false,
|
||||||
|
zero: false,
|
||||||
|
neg: true,
|
||||||
|
vals: i64Values,
|
||||||
|
cmps: cmpsForNeg,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
v: iZero,
|
||||||
|
kind: Int64ValueKind,
|
||||||
|
pos: false,
|
||||||
|
zero: true,
|
||||||
|
neg: false,
|
||||||
|
vals: i64Values,
|
||||||
|
cmps: cmpsForZero,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
v: iPos,
|
||||||
|
kind: Int64ValueKind,
|
||||||
|
pos: true,
|
||||||
|
zero: false,
|
||||||
|
neg: false,
|
||||||
|
vals: i64Values,
|
||||||
|
cmps: cmpsForPos,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
v: fNeg,
|
||||||
|
kind: Float64ValueKind,
|
||||||
|
pos: false,
|
||||||
|
zero: false,
|
||||||
|
neg: true,
|
||||||
|
vals: f64Values,
|
||||||
|
cmps: cmpsForNeg,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
v: fZero,
|
||||||
|
kind: Float64ValueKind,
|
||||||
|
pos: false,
|
||||||
|
zero: true,
|
||||||
|
neg: false,
|
||||||
|
vals: f64Values,
|
||||||
|
cmps: cmpsForZero,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
v: fPos,
|
||||||
|
kind: Float64ValueKind,
|
||||||
|
pos: true,
|
||||||
|
zero: false,
|
||||||
|
neg: false,
|
||||||
|
vals: f64Values,
|
||||||
|
cmps: cmpsForPos,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range testcases {
|
||||||
|
if got := tt.v.IsPositive(tt.kind); got != tt.pos {
|
||||||
|
t.Errorf("Value %#v (%s) positive check failed, expected %v, got %v", tt.v, tt.v.Emit(tt.kind), tt.pos, got)
|
||||||
|
}
|
||||||
|
if got := tt.v.IsZero(tt.kind); got != tt.zero {
|
||||||
|
t.Errorf("Value %#v (%s) zero check failed, expected %v, got %v", tt.v, tt.v.Emit(tt.kind), tt.pos, got)
|
||||||
|
}
|
||||||
|
if got := tt.v.IsNegative(tt.kind); got != tt.neg {
|
||||||
|
t.Errorf("Value %#v (%s) negative check failed, expected %v, got %v", tt.v, tt.v.Emit(tt.kind), tt.pos, got)
|
||||||
|
}
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
if got := tt.v.RawCompare(tt.vals[i].AsRaw(), tt.kind); got != tt.cmps[i] {
|
||||||
|
t.Errorf("Value %#v (%s) compare check with %#v (%s) failed, expected %d, got %d", tt.v, tt.v.Emit(tt.kind), tt.vals[i], tt.vals[i].Emit(tt.kind), tt.cmps[i], got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,106 +0,0 @@
|
|||||||
// Copyright 2019, 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 stats
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"go.opentelemetry.io/api/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MeasureHandle struct {
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Measure interface {
|
|
||||||
N() string
|
|
||||||
M(value float64) Measurement
|
|
||||||
}
|
|
||||||
|
|
||||||
type Measurement struct {
|
|
||||||
Measure Measure
|
|
||||||
Value float64
|
|
||||||
}
|
|
||||||
|
|
||||||
type Recorder interface {
|
|
||||||
// TODO: Note as in rfc 0001, allow raw Measures to have pre-defined labels:
|
|
||||||
GetMeasure(ctx context.Context, measure *MeasureHandle, labels ...core.KeyValue) Measure
|
|
||||||
|
|
||||||
Record(ctx context.Context, m ...Measurement)
|
|
||||||
RecordSingle(ctx context.Context, m Measurement)
|
|
||||||
}
|
|
||||||
|
|
||||||
type noopRecorder struct{}
|
|
||||||
type noopMeasure struct{}
|
|
||||||
|
|
||||||
var global atomic.Value
|
|
||||||
|
|
||||||
// GlobalRecorder return meter registered with global registry.
|
|
||||||
// If no meter is registered then an instance of noop Recorder is returned.
|
|
||||||
func GlobalRecorder() Recorder {
|
|
||||||
if t := global.Load(); t != nil {
|
|
||||||
return t.(Recorder)
|
|
||||||
}
|
|
||||||
return noopRecorder{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetGlobalRecorder sets provided meter as a global meter.
|
|
||||||
func SetGlobalRecorder(t Recorder) {
|
|
||||||
global.Store(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Record(ctx context.Context, m ...Measurement) {
|
|
||||||
GlobalRecorder().Record(ctx, m...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RecordSingle(ctx context.Context, m Measurement) {
|
|
||||||
GlobalRecorder().RecordSingle(ctx, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMeasure(name string) *MeasureHandle {
|
|
||||||
return &MeasureHandle{
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MeasureHandle) M(value float64) Measurement {
|
|
||||||
return Measurement{
|
|
||||||
Measure: m,
|
|
||||||
Value: value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MeasureHandle) N() string {
|
|
||||||
return m.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (noopRecorder) Record(ctx context.Context, m ...Measurement) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (noopRecorder) RecordSingle(ctx context.Context, m Measurement) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (noopRecorder) GetMeasure(ctx context.Context, handle *MeasureHandle, labels ...core.KeyValue) Measure {
|
|
||||||
return noopMeasure{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (noopMeasure) M(float64) Measurement {
|
|
||||||
return Measurement{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (noopMeasure) N() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
@ -19,7 +19,6 @@ import (
|
|||||||
|
|
||||||
"go.opentelemetry.io/api/key"
|
"go.opentelemetry.io/api/key"
|
||||||
"go.opentelemetry.io/api/metric"
|
"go.opentelemetry.io/api/metric"
|
||||||
"go.opentelemetry.io/api/stats"
|
|
||||||
"go.opentelemetry.io/api/tag"
|
"go.opentelemetry.io/api/tag"
|
||||||
"go.opentelemetry.io/api/trace"
|
"go.opentelemetry.io/api/trace"
|
||||||
)
|
)
|
||||||
@ -39,7 +38,7 @@ var (
|
|||||||
metric.WithDescription("A gauge set to 1.0"),
|
metric.WithDescription("A gauge set to 1.0"),
|
||||||
)
|
)
|
||||||
|
|
||||||
measureTwo = stats.NewMeasure("ex.com/two")
|
measureTwo = metric.NewFloat64Measure("ex.com/two")
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -50,11 +49,11 @@ func main() {
|
|||||||
tag.Insert(barKey.String("bar1")),
|
tag.Insert(barKey.String("bar1")),
|
||||||
)
|
)
|
||||||
|
|
||||||
gauge := meter.GetFloat64Gauge(
|
commonLabels := meter.DefineLabels(ctx, lemonsKey.Int(10))
|
||||||
ctx,
|
|
||||||
oneMetric,
|
gauge := oneMetric.GetHandle(commonLabels)
|
||||||
lemonsKey.Int(10),
|
|
||||||
)
|
measure := measureTwo.GetHandle(commonLabels)
|
||||||
|
|
||||||
err := tracer.WithSpan(ctx, "operation", func(ctx context.Context) error {
|
err := tracer.WithSpan(ctx, "operation", func(ctx context.Context) error {
|
||||||
|
|
||||||
@ -64,6 +63,16 @@ func main() {
|
|||||||
|
|
||||||
gauge.Set(ctx, 1)
|
gauge.Set(ctx, 1)
|
||||||
|
|
||||||
|
meter.RecordBatch(
|
||||||
|
// Note: call-site variables added as context tags:
|
||||||
|
tag.NewContext(ctx,
|
||||||
|
tag.Insert(anotherKey.String("xyz"))),
|
||||||
|
commonLabels,
|
||||||
|
|
||||||
|
oneMetric.Measurement(1.0),
|
||||||
|
measureTwo.Measurement(2.0),
|
||||||
|
)
|
||||||
|
|
||||||
return tracer.WithSpan(
|
return tracer.WithSpan(
|
||||||
ctx,
|
ctx,
|
||||||
"Sub operation...",
|
"Sub operation...",
|
||||||
@ -72,7 +81,7 @@ func main() {
|
|||||||
|
|
||||||
trace.CurrentSpan(ctx).AddEvent(ctx, "Sub span event")
|
trace.CurrentSpan(ctx).AddEvent(ctx, "Sub span event")
|
||||||
|
|
||||||
stats.Record(ctx, measureTwo.M(1.3))
|
measure.Record(ctx, 1.3)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -19,7 +19,6 @@ import (
|
|||||||
|
|
||||||
"go.opentelemetry.io/api/key"
|
"go.opentelemetry.io/api/key"
|
||||||
"go.opentelemetry.io/api/metric"
|
"go.opentelemetry.io/api/metric"
|
||||||
"go.opentelemetry.io/api/stats"
|
|
||||||
"go.opentelemetry.io/api/tag"
|
"go.opentelemetry.io/api/tag"
|
||||||
"go.opentelemetry.io/api/trace"
|
"go.opentelemetry.io/api/trace"
|
||||||
|
|
||||||
@ -28,10 +27,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
streaming = sdk.New(spanlog.New())
|
streaming = sdk.New(spanlog.New())
|
||||||
|
tracer trace.Tracer = streaming
|
||||||
tracer trace.Tracer = streaming
|
meter metric.Meter = streaming
|
||||||
meter metric.Meter = metric.NoopMeter{}
|
|
||||||
|
|
||||||
fooKey = key.New("ex.com/foo")
|
fooKey = key.New("ex.com/foo")
|
||||||
barKey = key.New("ex.com/bar")
|
barKey = key.New("ex.com/bar")
|
||||||
@ -43,7 +41,7 @@ var (
|
|||||||
metric.WithDescription("A gauge set to 1.0"),
|
metric.WithDescription("A gauge set to 1.0"),
|
||||||
)
|
)
|
||||||
|
|
||||||
measureTwo = stats.NewMeasure("ex.com/two")
|
measureTwo = metric.NewFloat64Measure("ex.com/two")
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -54,11 +52,11 @@ func main() {
|
|||||||
tag.Insert(barKey.String("bar1")),
|
tag.Insert(barKey.String("bar1")),
|
||||||
)
|
)
|
||||||
|
|
||||||
gauge := meter.GetFloat64Gauge(
|
commonLabels := meter.DefineLabels(ctx, lemonsKey.Int(10))
|
||||||
ctx,
|
|
||||||
oneMetric,
|
gauge := oneMetric.GetHandle(commonLabels)
|
||||||
lemonsKey.Int(10),
|
|
||||||
)
|
measure := measureTwo.GetHandle(commonLabels)
|
||||||
|
|
||||||
err := tracer.WithSpan(ctx, "operation", func(ctx context.Context) error {
|
err := tracer.WithSpan(ctx, "operation", func(ctx context.Context) error {
|
||||||
|
|
||||||
@ -68,6 +66,16 @@ func main() {
|
|||||||
|
|
||||||
gauge.Set(ctx, 1)
|
gauge.Set(ctx, 1)
|
||||||
|
|
||||||
|
meter.RecordBatch(
|
||||||
|
// Note: call-site variables added as context tags:
|
||||||
|
tag.NewContext(ctx,
|
||||||
|
tag.Insert(anotherKey.String("xyz"))),
|
||||||
|
commonLabels,
|
||||||
|
|
||||||
|
oneMetric.Measurement(1.0),
|
||||||
|
measureTwo.Measurement(2.0),
|
||||||
|
)
|
||||||
|
|
||||||
return tracer.WithSpan(
|
return tracer.WithSpan(
|
||||||
ctx,
|
ctx,
|
||||||
"Sub operation...",
|
"Sub operation...",
|
||||||
@ -76,7 +84,7 @@ func main() {
|
|||||||
|
|
||||||
trace.CurrentSpan(ctx).AddEvent(ctx, "Sub span event")
|
trace.CurrentSpan(ctx).AddEvent(ctx, "Sub span event")
|
||||||
|
|
||||||
stats.Record(ctx, measureTwo.M(1.3))
|
measure.Record(ctx, 1.3)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -13,17 +13,16 @@ func _() {
|
|||||||
_ = x[END_SPAN-2]
|
_ = x[END_SPAN-2]
|
||||||
_ = x[ADD_EVENT-3]
|
_ = x[ADD_EVENT-3]
|
||||||
_ = x[NEW_SCOPE-4]
|
_ = x[NEW_SCOPE-4]
|
||||||
_ = x[NEW_MEASURE-5]
|
_ = x[MODIFY_ATTR-5]
|
||||||
_ = x[NEW_METRIC-6]
|
_ = x[SET_STATUS-6]
|
||||||
_ = x[MODIFY_ATTR-7]
|
_ = x[SET_NAME-7]
|
||||||
_ = x[RECORD_STATS-8]
|
_ = x[SINGLE_METRIC-8]
|
||||||
_ = x[SET_STATUS-9]
|
_ = x[BATCH_METRIC-9]
|
||||||
_ = x[SET_NAME-10]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const _EventType_name = "INVALIDSTART_SPANEND_SPANADD_EVENTNEW_SCOPENEW_MEASURENEW_METRICMODIFY_ATTRRECORD_STATSSET_STATUSSET_NAME"
|
const _EventType_name = "INVALIDSTART_SPANEND_SPANADD_EVENTNEW_SCOPEMODIFY_ATTRSET_STATUSSET_NAMESINGLE_METRICBATCH_METRIC"
|
||||||
|
|
||||||
var _EventType_index = [...]uint8{0, 7, 17, 25, 34, 43, 54, 64, 75, 87, 97, 105}
|
var _EventType_index = [...]uint8{0, 7, 17, 25, 34, 43, 54, 64, 72, 85, 97}
|
||||||
|
|
||||||
func (i EventType) String() string {
|
func (i EventType) String() string {
|
||||||
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
|
|
||||||
"go.opentelemetry.io/api/core"
|
"go.opentelemetry.io/api/core"
|
||||||
"go.opentelemetry.io/api/stats"
|
"go.opentelemetry.io/api/metric"
|
||||||
"go.opentelemetry.io/api/tag"
|
"go.opentelemetry.io/api/tag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,11 +40,10 @@ type Event struct {
|
|||||||
Status codes.Code // SET_STATUS
|
Status codes.Code // SET_STATUS
|
||||||
|
|
||||||
// Values
|
// Values
|
||||||
String string // START_SPAN, EVENT, SET_NAME, ...
|
String string // START_SPAN, EVENT, SET_NAME, ...
|
||||||
Float64 float64
|
Parent ScopeID // START_SPAN
|
||||||
Parent ScopeID // START_SPAN
|
Measurement metric.Measurement
|
||||||
Stats []stats.Measurement
|
Measurements []metric.Measurement
|
||||||
Stat stats.Measurement
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Observer interface {
|
type Observer interface {
|
||||||
@ -58,12 +57,11 @@ const (
|
|||||||
END_SPAN
|
END_SPAN
|
||||||
ADD_EVENT
|
ADD_EVENT
|
||||||
NEW_SCOPE
|
NEW_SCOPE
|
||||||
NEW_MEASURE
|
|
||||||
NEW_METRIC
|
|
||||||
MODIFY_ATTR
|
MODIFY_ATTR
|
||||||
RECORD_STATS
|
|
||||||
SET_STATUS
|
SET_STATUS
|
||||||
SET_NAME
|
SET_NAME
|
||||||
|
SINGLE_METRIC // A metric Set(), Add(), Record()
|
||||||
|
BATCH_METRIC // A RecordBatch()
|
||||||
)
|
)
|
||||||
|
|
||||||
type Exporter struct {
|
type Exporter struct {
|
||||||
@ -101,6 +99,9 @@ func (e *Exporter) Foreach(f func(Observer)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Exporter) NewScope(parent ScopeID, attributes ...core.KeyValue) ScopeID {
|
func (e *Exporter) NewScope(parent ScopeID, attributes ...core.KeyValue) ScopeID {
|
||||||
|
if len(attributes) == 0 {
|
||||||
|
return parent
|
||||||
|
}
|
||||||
eventID := e.Record(Event{
|
eventID := e.Record(Event{
|
||||||
Type: NEW_SCOPE,
|
Type: NEW_SCOPE,
|
||||||
Scope: parent,
|
Scope: parent,
|
||||||
|
@ -20,6 +20,8 @@ import (
|
|||||||
|
|
||||||
"go.opentelemetry.io/api/core"
|
"go.opentelemetry.io/api/core"
|
||||||
"go.opentelemetry.io/api/key"
|
"go.opentelemetry.io/api/key"
|
||||||
|
"go.opentelemetry.io/api/metric"
|
||||||
|
"go.opentelemetry.io/api/tag"
|
||||||
"go.opentelemetry.io/experimental/streaming/exporter"
|
"go.opentelemetry.io/experimental/streaming/exporter"
|
||||||
"go.opentelemetry.io/experimental/streaming/exporter/reader"
|
"go.opentelemetry.io/experimental/streaming/exporter/reader"
|
||||||
|
|
||||||
@ -89,27 +91,17 @@ func AppendEvent(buf *strings.Builder, data reader.Event) {
|
|||||||
case exporter.MODIFY_ATTR:
|
case exporter.MODIFY_ATTR:
|
||||||
buf.WriteString("modify attr ")
|
buf.WriteString("modify attr ")
|
||||||
buf.WriteString(data.Type.String())
|
buf.WriteString(data.Type.String())
|
||||||
case exporter.RECORD_STATS:
|
|
||||||
buf.WriteString("record")
|
|
||||||
|
|
||||||
for _, s := range data.Stats {
|
case exporter.SINGLE_METRIC:
|
||||||
f(false)(core.Key{
|
formatMetricUpdate(buf, data.Measurement)
|
||||||
Name: s.Measure.N(),
|
formatMetricLabels(buf, data.Attributes)
|
||||||
}.Float64(s.Value))
|
|
||||||
|
|
||||||
buf.WriteString(" {")
|
case exporter.BATCH_METRIC:
|
||||||
i := 0
|
buf.WriteString("BATCH")
|
||||||
s.Tags.Foreach(func(kv core.KeyValue) bool {
|
formatMetricLabels(buf, data.Attributes)
|
||||||
if i != 0 {
|
for _, m := range data.Measurements {
|
||||||
buf.WriteString(",")
|
formatMetricUpdate(buf, m)
|
||||||
}
|
buf.WriteString(" ")
|
||||||
i++
|
|
||||||
buf.WriteString(kv.Key.Name)
|
|
||||||
buf.WriteString("=")
|
|
||||||
buf.WriteString(kv.Value.Emit())
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
buf.WriteString("}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case exporter.SET_STATUS:
|
case exporter.SET_STATUS:
|
||||||
@ -142,6 +134,30 @@ func AppendEvent(buf *strings.Builder, data reader.Event) {
|
|||||||
buf.WriteString(" ]\n")
|
buf.WriteString(" ]\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatMetricUpdate(buf *strings.Builder, m metric.Measurement) {
|
||||||
|
buf.WriteString(m.Descriptor.Kind().String())
|
||||||
|
buf.WriteString(" ")
|
||||||
|
buf.WriteString(m.Descriptor.Name())
|
||||||
|
buf.WriteString("=")
|
||||||
|
buf.WriteString(m.Value.Emit(m.Descriptor.ValueKind()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatMetricLabels(buf *strings.Builder, l tag.Map) {
|
||||||
|
buf.WriteString(" {")
|
||||||
|
i := 0
|
||||||
|
l.Foreach(func(kv core.KeyValue) bool {
|
||||||
|
if i != 0 {
|
||||||
|
buf.WriteString(",")
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
buf.WriteString(kv.Key.Name)
|
||||||
|
buf.WriteString("=")
|
||||||
|
buf.WriteString(kv.Value.Emit())
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
buf.WriteString("}")
|
||||||
|
}
|
||||||
|
|
||||||
func EventToString(data reader.Event) string {
|
func EventToString(data reader.Event) string {
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
AppendEvent(&buf, data)
|
AppendEvent(&buf, data)
|
||||||
|
@ -22,8 +22,9 @@ import (
|
|||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
|
|
||||||
"go.opentelemetry.io/api/core"
|
"go.opentelemetry.io/api/core"
|
||||||
"go.opentelemetry.io/api/stats"
|
"go.opentelemetry.io/api/metric"
|
||||||
"go.opentelemetry.io/api/tag"
|
"go.opentelemetry.io/api/tag"
|
||||||
|
"go.opentelemetry.io/api/trace"
|
||||||
"go.opentelemetry.io/experimental/streaming/exporter"
|
"go.opentelemetry.io/experimental/streaming/exporter"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,13 +33,14 @@ type Reader interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
Type exporter.EventType
|
Type exporter.EventType
|
||||||
Time time.Time
|
Time time.Time
|
||||||
Sequence exporter.EventID
|
Sequence exporter.EventID
|
||||||
SpanContext core.SpanContext
|
SpanContext core.SpanContext
|
||||||
Tags tag.Map
|
Tags tag.Map // context tags
|
||||||
Attributes tag.Map
|
Attributes tag.Map // span attributes, metric labels
|
||||||
Stats []Measurement
|
Measurement metric.Measurement
|
||||||
|
Measurements []metric.Measurement
|
||||||
|
|
||||||
Parent core.SpanContext
|
Parent core.SpanContext
|
||||||
ParentAttributes tag.Map
|
ParentAttributes tag.Map
|
||||||
@ -49,23 +51,11 @@ type Event struct {
|
|||||||
Status codes.Code
|
Status codes.Code
|
||||||
}
|
}
|
||||||
|
|
||||||
type Measurement struct {
|
|
||||||
Measure stats.Measure
|
|
||||||
Value float64
|
|
||||||
Tags tag.Map
|
|
||||||
}
|
|
||||||
|
|
||||||
type readerObserver struct {
|
type readerObserver struct {
|
||||||
readers []Reader
|
readers []Reader
|
||||||
|
|
||||||
// core.EventID -> *readerSpan or *readerScope
|
// core.EventID -> *readerSpan or *readerScope
|
||||||
scopes sync.Map
|
scopes sync.Map
|
||||||
|
|
||||||
// core.EventID -> *readerMeasure
|
|
||||||
measures sync.Map
|
|
||||||
|
|
||||||
// core.EventID -> *readerMetric
|
|
||||||
metrics sync.Map
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type readerSpan struct {
|
type readerSpan struct {
|
||||||
@ -78,14 +68,6 @@ type readerSpan struct {
|
|||||||
*readerScope
|
*readerScope
|
||||||
}
|
}
|
||||||
|
|
||||||
type readerMeasure struct {
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
type readerMetric struct {
|
|
||||||
*readerMeasure
|
|
||||||
}
|
|
||||||
|
|
||||||
type readerScope struct {
|
type readerScope struct {
|
||||||
span *readerSpan
|
span *readerSpan
|
||||||
parent exporter.EventID
|
parent exporter.EventID
|
||||||
@ -222,24 +204,6 @@ func (ro *readerObserver) orderedObserve(event exporter.Event) {
|
|||||||
read.Tags = span.startTags
|
read.Tags = span.startTags
|
||||||
}
|
}
|
||||||
|
|
||||||
case exporter.NEW_MEASURE:
|
|
||||||
measure := &readerMeasure{
|
|
||||||
name: event.String,
|
|
||||||
}
|
|
||||||
ro.measures.Store(event.Sequence, measure)
|
|
||||||
return
|
|
||||||
|
|
||||||
case exporter.NEW_METRIC:
|
|
||||||
measureI, has := ro.measures.Load(event.Scope.EventID)
|
|
||||||
if !has {
|
|
||||||
panic("metric measure not found")
|
|
||||||
}
|
|
||||||
metric := &readerMetric{
|
|
||||||
readerMeasure: measureI.(*readerMeasure),
|
|
||||||
}
|
|
||||||
ro.metrics.Store(event.Sequence, metric)
|
|
||||||
return
|
|
||||||
|
|
||||||
case exporter.ADD_EVENT:
|
case exporter.ADD_EVENT:
|
||||||
read.Type = exporter.ADD_EVENT
|
read.Type = exporter.ADD_EVENT
|
||||||
read.Message = event.String
|
read.Message = event.String
|
||||||
@ -252,20 +216,34 @@ func (ro *readerObserver) orderedObserve(event exporter.Event) {
|
|||||||
read.SpanContext = span.spanContext
|
read.SpanContext = span.spanContext
|
||||||
}
|
}
|
||||||
|
|
||||||
case exporter.RECORD_STATS:
|
case exporter.SINGLE_METRIC:
|
||||||
read.Type = exporter.RECORD_STATS
|
read.Type = exporter.SINGLE_METRIC
|
||||||
|
|
||||||
_, span := ro.readScope(event.Scope)
|
if event.Context != nil {
|
||||||
if span != nil {
|
span := trace.CurrentSpan(event.Context)
|
||||||
read.SpanContext = span.spanContext
|
if span != nil {
|
||||||
|
read.SpanContext = span.SpanContext()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, es := range event.Stats {
|
attrs, _ := ro.readScope(event.Scope)
|
||||||
ro.addMeasurement(&read, es)
|
read.Attributes = attrs
|
||||||
}
|
read.Measurement = event.Measurement
|
||||||
if event.Stat.Measure != nil {
|
|
||||||
ro.addMeasurement(&read, event.Stat)
|
case exporter.BATCH_METRIC:
|
||||||
|
read.Type = event.Type
|
||||||
|
|
||||||
|
if event.Context != nil {
|
||||||
|
span := trace.CurrentSpan(event.Context)
|
||||||
|
if span != nil {
|
||||||
|
read.SpanContext = span.SpanContext()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attrs, _ := ro.readScope(event.Scope)
|
||||||
|
read.Attributes = attrs
|
||||||
|
read.Measurements = make([]metric.Measurement, len(event.Measurements))
|
||||||
|
copy(read.Measurements, event.Measurements)
|
||||||
|
|
||||||
case exporter.SET_STATUS:
|
case exporter.SET_STATUS:
|
||||||
read.Type = exporter.SET_STATUS
|
read.Type = exporter.SET_STATUS
|
||||||
read.Status = event.Status
|
read.Status = event.Status
|
||||||
@ -292,20 +270,6 @@ func (ro *readerObserver) orderedObserve(event exporter.Event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ro *readerObserver) addMeasurement(e *Event, m stats.Measurement) {
|
|
||||||
attrs, _ := ro.readMeasureScope(m.Measure)
|
|
||||||
e.Stats = append(e.Stats, Measurement{
|
|
||||||
Measure: m.Measure,
|
|
||||||
Value: m.Value,
|
|
||||||
Tags: attrs,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ro *readerObserver) readMeasureScope(m stats.Measure) (tag.Map, *readerSpan) {
|
|
||||||
// TODO
|
|
||||||
return tag.NewEmptyMap(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ro *readerObserver) readScope(id exporter.ScopeID) (tag.Map, *readerSpan) {
|
func (ro *readerObserver) readScope(id exporter.ScopeID) (tag.Map, *readerSpan) {
|
||||||
if id.EventID == 0 {
|
if id.EventID == 0 {
|
||||||
return tag.NewEmptyMap(), nil
|
return tag.NewEmptyMap(), nil
|
||||||
|
@ -25,7 +25,8 @@ type Reader interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Span struct {
|
type Span struct {
|
||||||
Events []reader.Event
|
Events []reader.Event
|
||||||
|
Aggregates map[string]float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type spanReader struct {
|
type spanReader struct {
|
||||||
@ -41,27 +42,37 @@ func NewReaderObserver(readers ...Reader) exporter.Observer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *spanReader) Read(data reader.Event) {
|
func (s *spanReader) Read(data reader.Event) {
|
||||||
if !data.SpanContext.HasSpanID() {
|
|
||||||
panic("How is this?")
|
|
||||||
}
|
|
||||||
var span *Span
|
var span *Span
|
||||||
if data.Type == exporter.START_SPAN {
|
if data.SpanContext.HasSpanID() {
|
||||||
span = &Span{Events: make([]reader.Event, 0, 4)}
|
if data.Type == exporter.START_SPAN {
|
||||||
s.spans[data.SpanContext] = span
|
span = &Span{Events: make([]reader.Event, 0, 4)}
|
||||||
} else {
|
s.spans[data.SpanContext] = span
|
||||||
span = s.spans[data.SpanContext]
|
} else {
|
||||||
if span == nil {
|
span = s.spans[data.SpanContext]
|
||||||
// TODO count and report this.
|
if span == nil {
|
||||||
return
|
// TODO count and report this.
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
span.Events = append(span.Events, data)
|
switch data.Type {
|
||||||
|
case exporter.SINGLE_METRIC:
|
||||||
|
s.updateMetric(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if data.Type == exporter.END_SPAN {
|
if span != nil {
|
||||||
for _, r := range s.readers {
|
span.Events = append(span.Events, data)
|
||||||
r.Read(span)
|
if data.Type == exporter.END_SPAN {
|
||||||
|
for _, r := range s.readers {
|
||||||
|
r.Read(span)
|
||||||
|
}
|
||||||
|
delete(s.spans, data.SpanContext)
|
||||||
}
|
}
|
||||||
delete(s.spans, data.SpanContext)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *spanReader) updateMetric(data reader.Event) {
|
||||||
|
// TODO aggregate
|
||||||
|
}
|
||||||
|
175
experimental/streaming/sdk/metric.go
Normal file
175
experimental/streaming/sdk/metric.go
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
// Copyright 2019, 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 sdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/api/core"
|
||||||
|
"go.opentelemetry.io/api/metric"
|
||||||
|
"go.opentelemetry.io/experimental/streaming/exporter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type metricHandle struct {
|
||||||
|
descriptor *metric.Descriptor
|
||||||
|
labels metricLabels
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ metric.Handle = &metricHandle{}
|
||||||
|
|
||||||
|
type metricLabels struct {
|
||||||
|
sdk *sdk
|
||||||
|
scope exporter.ScopeID
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ metric.LabelSet = &metricLabels{}
|
||||||
|
|
||||||
|
func (h *metricHandle) RecordOne(ctx context.Context, value metric.MeasurementValue) {
|
||||||
|
h.labels.sdk.exporter.Record(exporter.Event{
|
||||||
|
Type: exporter.SINGLE_METRIC,
|
||||||
|
Context: ctx,
|
||||||
|
Scope: h.labels.scope,
|
||||||
|
Measurement: metric.Measurement{
|
||||||
|
Descriptor: h.descriptor,
|
||||||
|
Value: value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m metricLabels) Meter() metric.Meter {
|
||||||
|
return m.sdk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sdk) DefineLabels(ctx context.Context, labels ...core.KeyValue) metric.LabelSet {
|
||||||
|
return metricLabels{
|
||||||
|
sdk: s,
|
||||||
|
scope: s.exporter.NewScope(exporter.ScopeID{}, labels...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sdk) NewHandle(descriptor *metric.Descriptor, labels metric.LabelSet) metric.Handle {
|
||||||
|
mlabels, _ := labels.(metricLabels)
|
||||||
|
|
||||||
|
return &metricHandle{
|
||||||
|
descriptor: descriptor,
|
||||||
|
labels: mlabels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sdk) DeleteHandle(handle metric.Handle) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sdk) RecordBatch(ctx context.Context, labels metric.LabelSet, ms ...metric.Measurement) {
|
||||||
|
eventType := exporter.BATCH_METRIC
|
||||||
|
if len(ms) == 1 {
|
||||||
|
eventType = exporter.SINGLE_METRIC
|
||||||
|
}
|
||||||
|
oms := make([]metric.Measurement, len(ms))
|
||||||
|
mlabels, _ := labels.(metricLabels)
|
||||||
|
|
||||||
|
copy(oms, ms)
|
||||||
|
|
||||||
|
s.exporter.Record(exporter.Event{
|
||||||
|
Type: eventType,
|
||||||
|
Context: ctx,
|
||||||
|
Scope: mlabels.scope,
|
||||||
|
Measurements: oms,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sdk) RegisterObserver(observer metric.Observer, callback metric.ObserverCallback) {
|
||||||
|
if s.insertNewObserver(observer, callback) {
|
||||||
|
go s.observersRoutine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sdk) insertNewObserver(observer metric.Observer, callback metric.ObserverCallback) bool {
|
||||||
|
s.observersLock.Lock()
|
||||||
|
defer s.observersLock.Unlock()
|
||||||
|
old := s.loadObserversMap()
|
||||||
|
id := observer.Descriptor.ID()
|
||||||
|
if _, ok := old[id]; ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
observers := make(observersMap)
|
||||||
|
for oid, data := range old {
|
||||||
|
observers[oid] = data
|
||||||
|
}
|
||||||
|
observers[id] = observerData{
|
||||||
|
observer: observer,
|
||||||
|
callback: callback,
|
||||||
|
}
|
||||||
|
s.storeObserversMap(observers)
|
||||||
|
return old == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sdk) UnregisterObserver(observer metric.Observer) {
|
||||||
|
s.observersLock.Lock()
|
||||||
|
defer s.observersLock.Unlock()
|
||||||
|
old := s.loadObserversMap()
|
||||||
|
id := observer.Descriptor.ID()
|
||||||
|
if _, ok := old[id]; !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(old) == 1 {
|
||||||
|
s.storeObserversMap(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
observers := make(observersMap)
|
||||||
|
for oid, data := range old {
|
||||||
|
if oid != id {
|
||||||
|
observers[oid] = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.storeObserversMap(observers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sdk) observersRoutine() {
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for range ticker.C {
|
||||||
|
m := s.loadObserversMap()
|
||||||
|
if m == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, data := range m {
|
||||||
|
ocb := s.getObservationCallback(data.observer.Descriptor)
|
||||||
|
data.callback(s, data.observer, ocb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sdk) getObservationCallback(descriptor *metric.Descriptor) metric.ObservationCallback {
|
||||||
|
return func(l metric.LabelSet, v metric.MeasurementValue) {
|
||||||
|
s.RecordBatch(context.Background(), l, metric.Measurement{
|
||||||
|
Descriptor: descriptor,
|
||||||
|
Value: v,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sdk) loadObserversMap() observersMap {
|
||||||
|
i := s.observers.Load()
|
||||||
|
if i == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m := i.(observersMap)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sdk) storeObserversMap(m observersMap) {
|
||||||
|
s.observers.Store(m)
|
||||||
|
}
|
52
experimental/streaming/sdk/package.go
Normal file
52
experimental/streaming/sdk/package.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright 2019, 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 sdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/api/metric"
|
||||||
|
"go.opentelemetry.io/api/trace"
|
||||||
|
"go.opentelemetry.io/experimental/streaming/exporter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type observerData struct {
|
||||||
|
observer metric.Observer
|
||||||
|
callback metric.ObserverCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
type observersMap map[metric.DescriptorID]observerData
|
||||||
|
|
||||||
|
type sdk struct {
|
||||||
|
exporter *exporter.Exporter
|
||||||
|
resources exporter.EventID
|
||||||
|
|
||||||
|
observersLock sync.Mutex
|
||||||
|
observers atomic.Value // observersMap
|
||||||
|
}
|
||||||
|
|
||||||
|
type SDK interface {
|
||||||
|
trace.Tracer
|
||||||
|
metric.Meter
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ SDK = &sdk{}
|
||||||
|
|
||||||
|
func New(observers ...exporter.Observer) SDK {
|
||||||
|
return &sdk{
|
||||||
|
exporter: exporter.NewExporter(observers...),
|
||||||
|
}
|
||||||
|
}
|
@ -27,7 +27,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type span struct {
|
type span struct {
|
||||||
tracer *tracer
|
sdk *sdk
|
||||||
initial exporter.ScopeID
|
initial exporter.ScopeID
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ func (sp *span) IsRecordingEvents() bool {
|
|||||||
|
|
||||||
// SetStatus sets the status of the span.
|
// SetStatus sets the status of the span.
|
||||||
func (sp *span) SetStatus(status codes.Code) {
|
func (sp *span) SetStatus(status codes.Code) {
|
||||||
sp.tracer.exporter.Record(exporter.Event{
|
sp.sdk.exporter.Record(exporter.Event{
|
||||||
Type: exporter.SET_STATUS,
|
Type: exporter.SET_STATUS,
|
||||||
Scope: sp.ScopeID(),
|
Scope: sp.ScopeID(),
|
||||||
Status: status,
|
Status: status,
|
||||||
@ -56,7 +56,7 @@ func (sp *span) ScopeID() exporter.ScopeID {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sp *span) SetAttribute(attribute core.KeyValue) {
|
func (sp *span) SetAttribute(attribute core.KeyValue) {
|
||||||
sp.tracer.exporter.Record(exporter.Event{
|
sp.sdk.exporter.Record(exporter.Event{
|
||||||
Type: exporter.MODIFY_ATTR,
|
Type: exporter.MODIFY_ATTR,
|
||||||
Scope: sp.ScopeID(),
|
Scope: sp.ScopeID(),
|
||||||
Attribute: attribute,
|
Attribute: attribute,
|
||||||
@ -64,7 +64,7 @@ func (sp *span) SetAttribute(attribute core.KeyValue) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sp *span) SetAttributes(attributes ...core.KeyValue) {
|
func (sp *span) SetAttributes(attributes ...core.KeyValue) {
|
||||||
sp.tracer.exporter.Record(exporter.Event{
|
sp.sdk.exporter.Record(exporter.Event{
|
||||||
Type: exporter.MODIFY_ATTR,
|
Type: exporter.MODIFY_ATTR,
|
||||||
Scope: sp.ScopeID(),
|
Scope: sp.ScopeID(),
|
||||||
Attributes: attributes,
|
Attributes: attributes,
|
||||||
@ -72,7 +72,7 @@ func (sp *span) SetAttributes(attributes ...core.KeyValue) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sp *span) ModifyAttribute(mutator tag.Mutator) {
|
func (sp *span) ModifyAttribute(mutator tag.Mutator) {
|
||||||
sp.tracer.exporter.Record(exporter.Event{
|
sp.sdk.exporter.Record(exporter.Event{
|
||||||
Type: exporter.MODIFY_ATTR,
|
Type: exporter.MODIFY_ATTR,
|
||||||
Scope: sp.ScopeID(),
|
Scope: sp.ScopeID(),
|
||||||
Mutator: mutator,
|
Mutator: mutator,
|
||||||
@ -80,7 +80,7 @@ func (sp *span) ModifyAttribute(mutator tag.Mutator) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sp *span) ModifyAttributes(mutators ...tag.Mutator) {
|
func (sp *span) ModifyAttributes(mutators ...tag.Mutator) {
|
||||||
sp.tracer.exporter.Record(exporter.Event{
|
sp.sdk.exporter.Record(exporter.Event{
|
||||||
Type: exporter.MODIFY_ATTR,
|
Type: exporter.MODIFY_ATTR,
|
||||||
Scope: sp.ScopeID(),
|
Scope: sp.ScopeID(),
|
||||||
Mutators: mutators,
|
Mutators: mutators,
|
||||||
@ -93,7 +93,7 @@ func (sp *span) End(options ...trace.EndOption) {
|
|||||||
for _, opt := range options {
|
for _, opt := range options {
|
||||||
opt(&opts)
|
opt(&opts)
|
||||||
}
|
}
|
||||||
sp.tracer.exporter.Record(exporter.Event{
|
sp.sdk.exporter.Record(exporter.Event{
|
||||||
Time: opts.EndTime,
|
Time: opts.EndTime,
|
||||||
Type: exporter.END_SPAN,
|
Type: exporter.END_SPAN,
|
||||||
Scope: sp.ScopeID(),
|
Scope: sp.ScopeID(),
|
||||||
@ -105,7 +105,7 @@ func (sp *span) End(options ...trace.EndOption) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sp *span) Tracer() trace.Tracer {
|
func (sp *span) Tracer() trace.Tracer {
|
||||||
return sp.tracer
|
return sp.sdk
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sp *span) AddEvent(ctx context.Context, msg string, attrs ...core.KeyValue) {
|
func (sp *span) AddEvent(ctx context.Context, msg string, attrs ...core.KeyValue) {
|
||||||
@ -117,7 +117,7 @@ func (sp *span) AddEventWithTimestamp(ctx context.Context, timestamp time.Time,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sp *span) addEventWithTime(ctx context.Context, timestamp time.Time, msg string, attrs ...core.KeyValue) {
|
func (sp *span) addEventWithTime(ctx context.Context, timestamp time.Time, msg string, attrs ...core.KeyValue) {
|
||||||
sp.tracer.exporter.Record(exporter.Event{
|
sp.sdk.exporter.Record(exporter.Event{
|
||||||
Time: timestamp,
|
Time: timestamp,
|
||||||
Type: exporter.ADD_EVENT,
|
Type: exporter.ADD_EVENT,
|
||||||
Scope: sp.ScopeID(),
|
Scope: sp.ScopeID(),
|
||||||
@ -128,7 +128,7 @@ func (sp *span) addEventWithTime(ctx context.Context, timestamp time.Time, msg s
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sp *span) SetName(name string) {
|
func (sp *span) SetName(name string) {
|
||||||
sp.tracer.exporter.Record(exporter.Event{
|
sp.sdk.exporter.Record(exporter.Event{
|
||||||
Type: exporter.SET_NAME,
|
Type: exporter.SET_NAME,
|
||||||
String: name,
|
String: name,
|
||||||
})
|
})
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
|
|
||||||
"go.opentelemetry.io/api/core"
|
"go.opentelemetry.io/api/core"
|
||||||
"go.opentelemetry.io/api/key"
|
"go.opentelemetry.io/api/key"
|
||||||
|
"go.opentelemetry.io/api/metric"
|
||||||
"go.opentelemetry.io/api/trace"
|
"go.opentelemetry.io/api/trace"
|
||||||
|
|
||||||
"go.opentelemetry.io/experimental/streaming/exporter"
|
"go.opentelemetry.io/experimental/streaming/exporter"
|
||||||
@ -130,6 +131,26 @@ func checkContext(t *testing.T, ctx context.Context, key, wantValue interface{})
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func measurementCompare(m1, m2 metric.Measurement) bool {
|
||||||
|
// Nil descriptor normally shouldn't happen, unless there is
|
||||||
|
// some struct with the Measurement field that didn't get
|
||||||
|
// initialized.
|
||||||
|
m1Nil := m1.Descriptor == nil
|
||||||
|
m2Nil := m2.Descriptor == nil
|
||||||
|
if m1Nil != m2Nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if m1Nil {
|
||||||
|
return m1.Value.AsRaw() == m2.Value.AsRaw()
|
||||||
|
}
|
||||||
|
if m1.Descriptor.ID() != m2.Descriptor.ID() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
m2Raw := m2.Value.AsRaw()
|
||||||
|
kind := m1.Descriptor.ValueKind()
|
||||||
|
return m1.Value.RawCompare(m2Raw, kind) == 0
|
||||||
|
}
|
||||||
|
|
||||||
func diffEvents(t *testing.T, got, want []exporter.Event, extraIgnoredFields ...string) bool {
|
func diffEvents(t *testing.T, got, want []exporter.Event, extraIgnoredFields ...string) bool {
|
||||||
ignoredPaths := map[string]struct{}{
|
ignoredPaths := map[string]struct{}{
|
||||||
"Sequence": struct{}{},
|
"Sequence": struct{}{},
|
||||||
@ -143,6 +164,7 @@ func diffEvents(t *testing.T, got, want []exporter.Event, extraIgnoredFields ...
|
|||||||
_, found := ignoredPaths[path.String()]
|
_, found := ignoredPaths[path.String()]
|
||||||
return found
|
return found
|
||||||
}, cmp.Ignore()),
|
}, cmp.Ignore()),
|
||||||
|
cmp.Comparer(measurementCompare),
|
||||||
}
|
}
|
||||||
if diff := cmp.Diff(got, want, opts...); diff != "" {
|
if diff := cmp.Diff(got, want, opts...); diff != "" {
|
||||||
t.Errorf("Events: -got +want %s", diff)
|
t.Errorf("Events: -got +want %s", diff)
|
||||||
|
@ -24,11 +24,6 @@ import (
|
|||||||
"go.opentelemetry.io/experimental/streaming/exporter"
|
"go.opentelemetry.io/experimental/streaming/exporter"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tracer struct {
|
|
||||||
exporter *exporter.Exporter
|
|
||||||
resources exporter.EventID
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// TODO These should move somewhere in the api, right?
|
// TODO These should move somewhere in the api, right?
|
||||||
ErrorKey = key.New("error")
|
ErrorKey = key.New("error")
|
||||||
@ -37,16 +32,10 @@ var (
|
|||||||
MessageKey = key.New("message")
|
MessageKey = key.New("message")
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(observers ...exporter.Observer) trace.Tracer {
|
func (s *sdk) WithSpan(ctx context.Context, name string, body func(context.Context) error) error {
|
||||||
return &tracer{
|
// TODO: use runtime/trace.WithRegion for execution sdk support
|
||||||
exporter: exporter.NewExporter(observers...),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *tracer) WithSpan(ctx context.Context, name string, body func(context.Context) error) error {
|
|
||||||
// TODO: use runtime/trace.WithRegion for execution tracer support
|
|
||||||
// TODO: use runtime/pprof.Do for profile tags support
|
// TODO: use runtime/pprof.Do for profile tags support
|
||||||
ctx, span := t.Start(ctx, name)
|
ctx, span := s.Start(ctx, name)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
if err := body(ctx); err != nil {
|
if err := body(ctx); err != nil {
|
||||||
@ -57,7 +46,7 @@ func (t *tracer) WithSpan(ctx context.Context, name string, body func(context.Co
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tracer) Start(ctx context.Context, name string, opts ...trace.SpanOption) (context.Context, trace.Span) {
|
func (s *sdk) Start(ctx context.Context, name string, opts ...trace.SpanOption) (context.Context, trace.Span) {
|
||||||
var child core.SpanContext
|
var child core.SpanContext
|
||||||
|
|
||||||
child.SpanID = rand.Uint64()
|
child.SpanID = rand.Uint64()
|
||||||
@ -87,17 +76,17 @@ func (t *tracer) Start(ctx context.Context, name string, opts ...trace.SpanOptio
|
|||||||
|
|
||||||
childScope := exporter.ScopeID{
|
childScope := exporter.ScopeID{
|
||||||
SpanContext: child,
|
SpanContext: child,
|
||||||
EventID: t.resources,
|
EventID: s.resources,
|
||||||
}
|
}
|
||||||
|
|
||||||
span := &span{
|
span := &span{
|
||||||
tracer: t,
|
sdk: s,
|
||||||
initial: exporter.ScopeID{
|
initial: exporter.ScopeID{
|
||||||
SpanContext: child,
|
SpanContext: child,
|
||||||
EventID: t.exporter.Record(exporter.Event{
|
EventID: s.exporter.Record(exporter.Event{
|
||||||
Time: o.StartTime,
|
Time: o.StartTime,
|
||||||
Type: exporter.START_SPAN,
|
Type: exporter.START_SPAN,
|
||||||
Scope: t.exporter.NewScope(childScope, o.Attributes...),
|
Scope: s.exporter.NewScope(childScope, o.Attributes...),
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Parent: parentScope,
|
Parent: parentScope,
|
||||||
String: name,
|
String: name,
|
||||||
|
Reference in New Issue
Block a user