You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2026-06-03 18:35:08 +02:00
Merge branch 'master' into otlp-example.
Sync the local branch with upstream changes
This commit is contained in:
@@ -18,3 +18,4 @@ coverage.*
|
||||
/example/otlp/otel-test
|
||||
/example/prometheus/prometheus
|
||||
/example/zipkin/zipkin
|
||||
/example/otel-collector/otel-collector
|
||||
|
||||
+1
-1
@@ -12,6 +12,6 @@
|
||||
# https://help.github.com/en/articles/about-code-owners
|
||||
#
|
||||
|
||||
* @jmacd @paivagustavo @krnowak @lizthegrey @MrAlias @Aneurysm9 @evantorrie
|
||||
* @jmacd @paivagustavo @lizthegrey @MrAlias @Aneurysm9 @evantorrie
|
||||
|
||||
CODEOWNERS @MrAlias @jmacd
|
||||
|
||||
@@ -139,7 +139,6 @@ https://github.com/open-telemetry/opentelemetry-specification/issues/165
|
||||
|
||||
Approvers:
|
||||
|
||||
- [Krzesimir Nowak](https://github.com/krnowak), Kinvolk
|
||||
- [Liz Fong-Jones](https://github.com/lizthegrey), Honeycomb
|
||||
- [Gustavo Silva Paiva](https://github.com/paivagustavo), Stilingue
|
||||
- [Anthony Mirabella](https://github.com/Aneurysm9), Centene
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ depending on the master. So it is not a concern.
|
||||
4. Create a PR on github and merge the PR once approved.
|
||||
|
||||
```
|
||||
./pre-release.sh -t <new tag>
|
||||
./pre_release.sh -t <new tag>
|
||||
git diff master
|
||||
git push
|
||||
```
|
||||
|
||||
@@ -23,7 +23,9 @@ import (
|
||||
"go.opentelemetry.io/otel/api/propagation"
|
||||
)
|
||||
|
||||
const correlationContextHeader = "Correlation-Context"
|
||||
// Temporary header name until W3C finalizes format.
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/18b2752ebe6c7f0cdd8c7b2bcbdceb0ae3f5ad95/specification/correlationcontext/api.md#header-name
|
||||
const correlationContextHeader = "otcorrelations"
|
||||
|
||||
// CorrelationContext propagates Key:Values in W3C CorrelationContext
|
||||
// format.
|
||||
|
||||
@@ -89,7 +89,7 @@ func TestExtractValidDistributedContextFromHTTPReq(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "http://example.com", nil)
|
||||
req.Header.Set("Correlation-Context", tt.header)
|
||||
req.Header.Set("otcorrelations", tt.header)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = propagation.ExtractHTTP(ctx, props, req.Header)
|
||||
@@ -133,7 +133,7 @@ func TestExtractInvalidDistributedContextFromHTTPReq(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "http://example.com", nil)
|
||||
req.Header.Set("Correlation-Context", tt.header)
|
||||
req.Header.Set("otcorrelations", tt.header)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = propagation.ExtractHTTP(ctx, props, req.Header)
|
||||
@@ -202,17 +202,17 @@ func TestInjectCorrelationContextToHTTPReq(t *testing.T) {
|
||||
ctx := correlation.ContextWithMap(context.Background(), correlation.NewMap(correlation.MapUpdate{MultiKV: tt.kvs}))
|
||||
propagation.InjectHTTP(ctx, props, req.Header)
|
||||
|
||||
gotHeader := req.Header.Get("Correlation-Context")
|
||||
gotHeader := req.Header.Get("otcorrelations")
|
||||
wantedLen := len(strings.Join(tt.wantInHeader, ","))
|
||||
if wantedLen != len(gotHeader) {
|
||||
t.Errorf(
|
||||
"%s: Inject Correlation-Context incorrect length %d != %d.", tt.name, tt.wantedLen, len(gotHeader),
|
||||
"%s: Inject otcorrelations incorrect length %d != %d.", tt.name, tt.wantedLen, len(gotHeader),
|
||||
)
|
||||
}
|
||||
for _, inHeader := range tt.wantInHeader {
|
||||
if !strings.Contains(gotHeader, inHeader) {
|
||||
t.Errorf(
|
||||
"%s: Inject Correlation-Context missing part of header: %s in %s", tt.name, inHeader, gotHeader,
|
||||
"%s: Inject otcorrelations missing part of header: %s in %s", tt.name, inHeader, gotHeader,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -222,7 +222,7 @@ func TestInjectCorrelationContextToHTTPReq(t *testing.T) {
|
||||
|
||||
func TestTraceContextPropagator_GetAllKeys(t *testing.T) {
|
||||
var propagator correlation.CorrelationContext
|
||||
want := []string{"Correlation-Context"}
|
||||
want := []string{"otcorrelations"}
|
||||
got := propagator.GetAllKeys()
|
||||
if diff := cmp.Diff(got, want); diff != "" {
|
||||
t.Errorf("GetAllKeys: -got +want %s", diff)
|
||||
|
||||
@@ -59,13 +59,13 @@ func (*benchFixture) AggregatorFor(descriptor *metric.Descriptor) export.Aggrega
|
||||
switch descriptor.MetricKind() {
|
||||
case metric.CounterKind:
|
||||
return sum.New()
|
||||
case metric.MeasureKind:
|
||||
case metric.ValueRecorderKind:
|
||||
if strings.HasSuffix(descriptor.Name(), "minmaxsumcount") {
|
||||
return minmaxsumcount.New(descriptor)
|
||||
} else if strings.HasSuffix(descriptor.Name(), "ddsketch") {
|
||||
return ddsketch.New(ddsketch.NewDefaultConfig(), descriptor)
|
||||
return ddsketch.New(descriptor, ddsketch.NewDefaultConfig())
|
||||
} else if strings.HasSuffix(descriptor.Name(), "array") {
|
||||
return ddsketch.New(ddsketch.NewDefaultConfig(), descriptor)
|
||||
return ddsketch.New(descriptor, ddsketch.NewDefaultConfig())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -82,21 +82,21 @@ func TestDirect(t *testing.T) {
|
||||
counter.Add(ctx, 1, labels1...)
|
||||
counter.Add(ctx, 1, labels1...)
|
||||
|
||||
measure := Must(meter1).NewFloat64Measure("test.measure")
|
||||
measure.Record(ctx, 1, labels1...)
|
||||
measure.Record(ctx, 2, labels1...)
|
||||
valuerecorder := Must(meter1).NewFloat64ValueRecorder("test.valuerecorder")
|
||||
valuerecorder.Record(ctx, 1, labels1...)
|
||||
valuerecorder.Record(ctx, 2, labels1...)
|
||||
|
||||
_ = Must(meter1).RegisterFloat64Observer("test.observer.float", func(result metric.Float64ObserverResult) {
|
||||
_ = Must(meter1).NewFloat64ValueObserver("test.valueobserver.float", func(_ context.Context, result metric.Float64ObserverResult) {
|
||||
result.Observe(1., labels1...)
|
||||
result.Observe(2., labels2...)
|
||||
})
|
||||
|
||||
_ = Must(meter1).RegisterInt64Observer("test.observer.int", func(result metric.Int64ObserverResult) {
|
||||
_ = Must(meter1).NewInt64ValueObserver("test.valueobserver.int", func(_ context.Context, result metric.Int64ObserverResult) {
|
||||
result.Observe(1, labels1...)
|
||||
result.Observe(2, labels2...)
|
||||
})
|
||||
|
||||
second := Must(meter2).NewFloat64Measure("test.second")
|
||||
second := Must(meter2).NewFloat64ValueRecorder("test.second")
|
||||
second.Record(ctx, 1, labels3...)
|
||||
second.Record(ctx, 2, labels3...)
|
||||
|
||||
@@ -104,7 +104,7 @@ func TestDirect(t *testing.T) {
|
||||
global.SetMeterProvider(provider)
|
||||
|
||||
counter.Add(ctx, 1, labels1...)
|
||||
measure.Record(ctx, 3, labels1...)
|
||||
valuerecorder.Record(ctx, 3, labels1...)
|
||||
second.Record(ctx, 3, labels3...)
|
||||
|
||||
mock.RunAsyncInstruments()
|
||||
@@ -120,7 +120,7 @@ func TestDirect(t *testing.T) {
|
||||
Number: asInt(1),
|
||||
},
|
||||
{
|
||||
Name: "test.measure",
|
||||
Name: "test.valuerecorder",
|
||||
LibraryName: "test1",
|
||||
Labels: asMap(labels1...),
|
||||
Number: asFloat(3),
|
||||
@@ -132,25 +132,25 @@ func TestDirect(t *testing.T) {
|
||||
Number: asFloat(3),
|
||||
},
|
||||
{
|
||||
Name: "test.observer.float",
|
||||
Name: "test.valueobserver.float",
|
||||
LibraryName: "test1",
|
||||
Labels: asMap(labels1...),
|
||||
Number: asFloat(1),
|
||||
},
|
||||
{
|
||||
Name: "test.observer.float",
|
||||
Name: "test.valueobserver.float",
|
||||
LibraryName: "test1",
|
||||
Labels: asMap(labels2...),
|
||||
Number: asFloat(2),
|
||||
},
|
||||
{
|
||||
Name: "test.observer.int",
|
||||
Name: "test.valueobserver.int",
|
||||
LibraryName: "test1",
|
||||
Labels: asMap(labels1...),
|
||||
Number: asInt(1),
|
||||
},
|
||||
{
|
||||
Name: "test.observer.int",
|
||||
Name: "test.valueobserver.int",
|
||||
LibraryName: "test1",
|
||||
Labels: asMap(labels2...),
|
||||
Number: asInt(2),
|
||||
@@ -174,8 +174,8 @@ func TestBound(t *testing.T) {
|
||||
boundC.Add(ctx, 1)
|
||||
boundC.Add(ctx, 1)
|
||||
|
||||
measure := Must(glob).NewInt64Measure("test.measure")
|
||||
boundM := measure.Bind(labels1...)
|
||||
valuerecorder := Must(glob).NewInt64ValueRecorder("test.valuerecorder")
|
||||
boundM := valuerecorder.Bind(labels1...)
|
||||
boundM.Record(ctx, 1)
|
||||
boundM.Record(ctx, 2)
|
||||
|
||||
@@ -194,7 +194,7 @@ func TestBound(t *testing.T) {
|
||||
Number: asFloat(1),
|
||||
},
|
||||
{
|
||||
Name: "test.measure",
|
||||
Name: "test.valuerecorder",
|
||||
LibraryName: "test",
|
||||
Labels: asMap(labels1...),
|
||||
Number: asInt(3),
|
||||
@@ -216,8 +216,8 @@ func TestUnbind(t *testing.T) {
|
||||
counter := Must(glob).NewFloat64Counter("test.counter")
|
||||
boundC := counter.Bind(labels1...)
|
||||
|
||||
measure := Must(glob).NewInt64Measure("test.measure")
|
||||
boundM := measure.Bind(labels1...)
|
||||
valuerecorder := Must(glob).NewInt64ValueRecorder("test.valuerecorder")
|
||||
boundM := valuerecorder.Bind(labels1...)
|
||||
|
||||
boundC.Unbind()
|
||||
boundM.Unbind()
|
||||
@@ -331,12 +331,12 @@ func TestImplementationIndirection(t *testing.T) {
|
||||
require.False(t, ok)
|
||||
|
||||
// Async: no SDK yet
|
||||
observer := Must(meter1).RegisterFloat64Observer(
|
||||
"interface.observer",
|
||||
func(result metric.Float64ObserverResult) {},
|
||||
valueobserver := Must(meter1).NewFloat64ValueObserver(
|
||||
"interface.valueobserver",
|
||||
func(_ context.Context, result metric.Float64ObserverResult) {},
|
||||
)
|
||||
|
||||
ival = observer.AsyncImpl().Implementation()
|
||||
ival = valueobserver.AsyncImpl().Implementation()
|
||||
require.NotNil(t, ival)
|
||||
|
||||
_, ok = ival.(*metrictest.Async)
|
||||
@@ -356,7 +356,7 @@ func TestImplementationIndirection(t *testing.T) {
|
||||
require.True(t, ok)
|
||||
|
||||
// Async
|
||||
ival = observer.AsyncImpl().Implementation()
|
||||
ival = valueobserver.AsyncImpl().Implementation()
|
||||
require.NotNil(t, ival)
|
||||
|
||||
_, ok = ival.(*metrictest.Async)
|
||||
@@ -407,7 +407,7 @@ func TestRecordBatchRealSDK(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
global.SetMeterProvider(pusher)
|
||||
global.SetMeterProvider(pusher.Provider())
|
||||
|
||||
meter.RecordBatch(context.Background(), nil, counter.Measurement(1))
|
||||
pusher.Stop()
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
@@ -36,17 +37,17 @@ var (
|
||||
"counter.float64": func(name, libraryName string) (metric.InstrumentImpl, error) {
|
||||
return unwrap(MeterProvider().Meter(libraryName).NewFloat64Counter(name))
|
||||
},
|
||||
"measure.int64": func(name, libraryName string) (metric.InstrumentImpl, error) {
|
||||
return unwrap(MeterProvider().Meter(libraryName).NewInt64Measure(name))
|
||||
"valuerecorder.int64": func(name, libraryName string) (metric.InstrumentImpl, error) {
|
||||
return unwrap(MeterProvider().Meter(libraryName).NewInt64ValueRecorder(name))
|
||||
},
|
||||
"measure.float64": func(name, libraryName string) (metric.InstrumentImpl, error) {
|
||||
return unwrap(MeterProvider().Meter(libraryName).NewFloat64Measure(name))
|
||||
"valuerecorder.float64": func(name, libraryName string) (metric.InstrumentImpl, error) {
|
||||
return unwrap(MeterProvider().Meter(libraryName).NewFloat64ValueRecorder(name))
|
||||
},
|
||||
"observer.int64": func(name, libraryName string) (metric.InstrumentImpl, error) {
|
||||
return unwrap(MeterProvider().Meter(libraryName).RegisterInt64Observer(name, func(metric.Int64ObserverResult) {}))
|
||||
"valueobserver.int64": func(name, libraryName string) (metric.InstrumentImpl, error) {
|
||||
return unwrap(MeterProvider().Meter(libraryName).NewInt64ValueObserver(name, func(context.Context, metric.Int64ObserverResult) {}))
|
||||
},
|
||||
"observer.float64": func(name, libraryName string) (metric.InstrumentImpl, error) {
|
||||
return unwrap(MeterProvider().Meter(libraryName).RegisterFloat64Observer(name, func(metric.Float64ObserverResult) {}))
|
||||
"valueobserver.float64": func(name, libraryName string) (metric.InstrumentImpl, error) {
|
||||
return unwrap(MeterProvider().Meter(libraryName).NewFloat64ValueObserver(name, func(context.Context, metric.Float64ObserverResult) {}))
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
+2
-2
@@ -128,7 +128,7 @@ func Infer(k string, value interface{}) KeyValue {
|
||||
case reflect.Float64:
|
||||
return Float64(k, rv.Float())
|
||||
case reflect.String:
|
||||
return String(k, rv.Interface().(string))
|
||||
return String(k, rv.String())
|
||||
}
|
||||
return String(k, fmt.Sprint(rv.Interface()))
|
||||
return String(k, fmt.Sprint(value))
|
||||
}
|
||||
|
||||
@@ -25,6 +25,21 @@ type Iterator struct {
|
||||
idx int
|
||||
}
|
||||
|
||||
// MergeIterator supports iterating over two sets of labels while
|
||||
// eliminating duplicate values from the combined set. The first
|
||||
// iterator value takes precedence.
|
||||
type MergeItererator struct {
|
||||
one oneIterator
|
||||
two oneIterator
|
||||
current kv.KeyValue
|
||||
}
|
||||
|
||||
type oneIterator struct {
|
||||
iter Iterator
|
||||
done bool
|
||||
label kv.KeyValue
|
||||
}
|
||||
|
||||
// Next moves the iterator to the next position. Returns false if there
|
||||
// are no more labels.
|
||||
func (i *Iterator) Next() bool {
|
||||
@@ -75,3 +90,63 @@ func (i *Iterator) ToSlice() []kv.KeyValue {
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// NewMergeIterator returns a MergeIterator for merging two label sets
|
||||
// Duplicates are resolved by taking the value from the first set.
|
||||
func NewMergeIterator(s1, s2 *Set) MergeItererator {
|
||||
mi := MergeItererator{
|
||||
one: makeOne(s1.Iter()),
|
||||
two: makeOne(s2.Iter()),
|
||||
}
|
||||
return mi
|
||||
}
|
||||
|
||||
func makeOne(iter Iterator) oneIterator {
|
||||
oi := oneIterator{
|
||||
iter: iter,
|
||||
}
|
||||
oi.advance()
|
||||
return oi
|
||||
}
|
||||
|
||||
func (oi *oneIterator) advance() {
|
||||
if oi.done = !oi.iter.Next(); !oi.done {
|
||||
oi.label = oi.iter.Label()
|
||||
}
|
||||
}
|
||||
|
||||
// Next returns true if there is another label available.
|
||||
func (m *MergeItererator) Next() bool {
|
||||
if m.one.done && m.two.done {
|
||||
return false
|
||||
}
|
||||
if m.one.done {
|
||||
m.current = m.two.label
|
||||
m.two.advance()
|
||||
return true
|
||||
}
|
||||
if m.two.done {
|
||||
m.current = m.one.label
|
||||
m.one.advance()
|
||||
return true
|
||||
}
|
||||
if m.one.label.Key == m.two.label.Key {
|
||||
m.current = m.one.label // first iterator label value wins
|
||||
m.one.advance()
|
||||
m.two.advance()
|
||||
return true
|
||||
}
|
||||
if m.one.label.Key < m.two.label.Key {
|
||||
m.current = m.one.label
|
||||
m.one.advance()
|
||||
return true
|
||||
}
|
||||
m.current = m.two.label
|
||||
m.two.advance()
|
||||
return true
|
||||
}
|
||||
|
||||
// Label returns the current value after Next() returns true.
|
||||
func (m *MergeItererator) Label() kv.KeyValue {
|
||||
return m.current
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package label_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
@@ -55,3 +56,96 @@ func TestEmptyIterator(t *testing.T) {
|
||||
require.Equal(t, 0, iter.Len())
|
||||
require.False(t, iter.Next())
|
||||
}
|
||||
|
||||
func TestMergedIterator(t *testing.T) {
|
||||
|
||||
type inputs struct {
|
||||
name string
|
||||
keys1 []string
|
||||
keys2 []string
|
||||
expect []string
|
||||
}
|
||||
|
||||
makeLabels := func(keys []string, num int) (result []kv.KeyValue) {
|
||||
for _, k := range keys {
|
||||
result = append(result, kv.Int(k, num))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for _, input := range []inputs{
|
||||
{
|
||||
name: "one overlap",
|
||||
keys1: []string{"A", "B"},
|
||||
keys2: []string{"B", "C"},
|
||||
expect: []string{"A/1", "B/1", "C/2"},
|
||||
},
|
||||
{
|
||||
name: "reversed one overlap",
|
||||
keys1: []string{"B", "A"},
|
||||
keys2: []string{"C", "B"},
|
||||
expect: []string{"A/1", "B/1", "C/2"},
|
||||
},
|
||||
{
|
||||
name: "one empty",
|
||||
keys1: nil,
|
||||
keys2: []string{"C", "B"},
|
||||
expect: []string{"B/2", "C/2"},
|
||||
},
|
||||
{
|
||||
name: "two empty",
|
||||
keys1: []string{"C", "B"},
|
||||
keys2: nil,
|
||||
expect: []string{"B/1", "C/1"},
|
||||
},
|
||||
{
|
||||
name: "no overlap both",
|
||||
keys1: []string{"C"},
|
||||
keys2: []string{"B"},
|
||||
expect: []string{"B/2", "C/1"},
|
||||
},
|
||||
{
|
||||
name: "one empty single two",
|
||||
keys1: nil,
|
||||
keys2: []string{"B"},
|
||||
expect: []string{"B/2"},
|
||||
},
|
||||
{
|
||||
name: "two empty single one",
|
||||
keys1: []string{"A"},
|
||||
keys2: nil,
|
||||
expect: []string{"A/1"},
|
||||
},
|
||||
{
|
||||
name: "all empty",
|
||||
keys1: nil,
|
||||
keys2: nil,
|
||||
expect: nil,
|
||||
},
|
||||
{
|
||||
name: "full overlap",
|
||||
keys1: []string{"A", "B", "C", "D"},
|
||||
keys2: []string{"A", "B", "C", "D"},
|
||||
expect: []string{"A/1", "B/1", "C/1", "D/1"},
|
||||
},
|
||||
} {
|
||||
t.Run(input.name, func(t *testing.T) {
|
||||
labels1 := makeLabels(input.keys1, 1)
|
||||
labels2 := makeLabels(input.keys2, 2)
|
||||
|
||||
set1 := label.NewSet(labels1...)
|
||||
set2 := label.NewSet(labels2...)
|
||||
|
||||
merge := label.NewMergeIterator(&set1, &set2)
|
||||
|
||||
var result []string
|
||||
|
||||
for merge.Next() {
|
||||
label := merge.Label()
|
||||
result = append(result, fmt.Sprint(label.Key, "/", label.Value.Emit()))
|
||||
}
|
||||
|
||||
require.Equal(t, input.expect, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+145
-65
@@ -93,84 +93,162 @@ func TestOptions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCounter(t *testing.T) {
|
||||
{
|
||||
// N.B. the API does not check for negative
|
||||
// values, that's the SDK's responsibility.
|
||||
t.Run("float64 counter", func(t *testing.T) {
|
||||
mockSDK, meter := mockTest.NewMeter()
|
||||
c := Must(meter).NewFloat64Counter("test.counter.float")
|
||||
ctx := context.Background()
|
||||
labels := []kv.KeyValue{kv.String("A", "B")}
|
||||
c.Add(ctx, 42, labels...)
|
||||
c.Add(ctx, 1994.1, labels...)
|
||||
boundInstrument := c.Bind(labels...)
|
||||
boundInstrument.Add(ctx, 42)
|
||||
boundInstrument.Add(ctx, -742)
|
||||
meter.RecordBatch(ctx, labels, c.Measurement(42))
|
||||
t.Log("Testing float counter")
|
||||
checkBatches(t, ctx, labels, mockSDK, metric.Float64NumberKind, c.SyncImpl())
|
||||
}
|
||||
{
|
||||
checkSyncBatches(t, ctx, labels, mockSDK, metric.Float64NumberKind, metric.CounterKind, c.SyncImpl(),
|
||||
1994.1, -742, 42,
|
||||
)
|
||||
})
|
||||
t.Run("int64 counter", func(t *testing.T) {
|
||||
mockSDK, meter := mockTest.NewMeter()
|
||||
c := Must(meter).NewInt64Counter("test.counter.int")
|
||||
ctx := context.Background()
|
||||
labels := []kv.KeyValue{kv.String("A", "B"), kv.String("C", "D")}
|
||||
c.Add(ctx, 42, labels...)
|
||||
boundInstrument := c.Bind(labels...)
|
||||
boundInstrument.Add(ctx, 42)
|
||||
boundInstrument.Add(ctx, 4200)
|
||||
meter.RecordBatch(ctx, labels, c.Measurement(420000))
|
||||
checkSyncBatches(t, ctx, labels, mockSDK, metric.Int64NumberKind, metric.CounterKind, c.SyncImpl(),
|
||||
42, 4200, 420000,
|
||||
)
|
||||
|
||||
})
|
||||
t.Run("int64 updowncounter", func(t *testing.T) {
|
||||
mockSDK, meter := mockTest.NewMeter()
|
||||
c := Must(meter).NewInt64UpDownCounter("test.updowncounter.int")
|
||||
ctx := context.Background()
|
||||
labels := []kv.KeyValue{kv.String("A", "B"), kv.String("C", "D")}
|
||||
c.Add(ctx, 100, labels...)
|
||||
boundInstrument := c.Bind(labels...)
|
||||
boundInstrument.Add(ctx, -100)
|
||||
meter.RecordBatch(ctx, labels, c.Measurement(42))
|
||||
t.Log("Testing int counter")
|
||||
checkBatches(t, ctx, labels, mockSDK, metric.Int64NumberKind, c.SyncImpl())
|
||||
}
|
||||
checkSyncBatches(t, ctx, labels, mockSDK, metric.Int64NumberKind, metric.UpDownCounterKind, c.SyncImpl(),
|
||||
100, -100, 42,
|
||||
)
|
||||
})
|
||||
t.Run("float64 updowncounter", func(t *testing.T) {
|
||||
mockSDK, meter := mockTest.NewMeter()
|
||||
c := Must(meter).NewFloat64UpDownCounter("test.updowncounter.float")
|
||||
ctx := context.Background()
|
||||
labels := []kv.KeyValue{kv.String("A", "B"), kv.String("C", "D")}
|
||||
c.Add(ctx, 100.1, labels...)
|
||||
boundInstrument := c.Bind(labels...)
|
||||
boundInstrument.Add(ctx, -76)
|
||||
meter.RecordBatch(ctx, labels, c.Measurement(-100.1))
|
||||
checkSyncBatches(t, ctx, labels, mockSDK, metric.Float64NumberKind, metric.UpDownCounterKind, c.SyncImpl(),
|
||||
100.1, -76, -100.1,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMeasure(t *testing.T) {
|
||||
{
|
||||
func TestValueRecorder(t *testing.T) {
|
||||
t.Run("float64 valuerecorder", func(t *testing.T) {
|
||||
mockSDK, meter := mockTest.NewMeter()
|
||||
m := Must(meter).NewFloat64Measure("test.measure.float")
|
||||
m := Must(meter).NewFloat64ValueRecorder("test.valuerecorder.float")
|
||||
ctx := context.Background()
|
||||
labels := []kv.KeyValue{}
|
||||
m.Record(ctx, 42, labels...)
|
||||
boundInstrument := m.Bind(labels...)
|
||||
boundInstrument.Record(ctx, 42)
|
||||
meter.RecordBatch(ctx, labels, m.Measurement(42))
|
||||
t.Log("Testing float measure")
|
||||
checkBatches(t, ctx, labels, mockSDK, metric.Float64NumberKind, m.SyncImpl())
|
||||
}
|
||||
{
|
||||
boundInstrument.Record(ctx, 0)
|
||||
meter.RecordBatch(ctx, labels, m.Measurement(-100.5))
|
||||
checkSyncBatches(t, ctx, labels, mockSDK, metric.Float64NumberKind, metric.ValueRecorderKind, m.SyncImpl(),
|
||||
42, 0, -100.5,
|
||||
)
|
||||
})
|
||||
t.Run("int64 valuerecorder", func(t *testing.T) {
|
||||
mockSDK, meter := mockTest.NewMeter()
|
||||
m := Must(meter).NewInt64Measure("test.measure.int")
|
||||
m := Must(meter).NewInt64ValueRecorder("test.valuerecorder.int")
|
||||
ctx := context.Background()
|
||||
labels := []kv.KeyValue{kv.Int("I", 1)}
|
||||
m.Record(ctx, 42, labels...)
|
||||
m.Record(ctx, 173, labels...)
|
||||
boundInstrument := m.Bind(labels...)
|
||||
boundInstrument.Record(ctx, 42)
|
||||
meter.RecordBatch(ctx, labels, m.Measurement(42))
|
||||
t.Log("Testing int measure")
|
||||
checkBatches(t, ctx, labels, mockSDK, metric.Int64NumberKind, m.SyncImpl())
|
||||
}
|
||||
boundInstrument.Record(ctx, 80)
|
||||
meter.RecordBatch(ctx, labels, m.Measurement(0))
|
||||
checkSyncBatches(t, ctx, labels, mockSDK, metric.Int64NumberKind, metric.ValueRecorderKind, m.SyncImpl(),
|
||||
173, 80, 0,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func TestObserver(t *testing.T) {
|
||||
{
|
||||
func TestObserverInstruments(t *testing.T) {
|
||||
t.Run("float valueobserver", func(t *testing.T) {
|
||||
labels := []kv.KeyValue{kv.String("O", "P")}
|
||||
mockSDK, meter := mockTest.NewMeter()
|
||||
o := Must(meter).RegisterFloat64Observer("test.observer.float", func(result metric.Float64ObserverResult) {
|
||||
result.Observe(42, labels...)
|
||||
o := Must(meter).NewFloat64ValueObserver("test.valueobserver.float", func(_ context.Context, result metric.Float64ObserverResult) {
|
||||
result.Observe(42.1, labels...)
|
||||
})
|
||||
t.Log("Testing float observer")
|
||||
|
||||
mockSDK.RunAsyncInstruments()
|
||||
checkObserverBatch(t, labels, mockSDK, metric.Float64NumberKind, o.AsyncImpl())
|
||||
}
|
||||
{
|
||||
checkObserverBatch(t, labels, mockSDK, metric.Float64NumberKind, metric.ValueObserverKind, o.AsyncImpl(),
|
||||
42.1,
|
||||
)
|
||||
})
|
||||
t.Run("int valueobserver", func(t *testing.T) {
|
||||
labels := []kv.KeyValue{}
|
||||
mockSDK, meter := mockTest.NewMeter()
|
||||
o := Must(meter).RegisterInt64Observer("test.observer.int", func(result metric.Int64ObserverResult) {
|
||||
result.Observe(42, labels...)
|
||||
o := Must(meter).NewInt64ValueObserver("test.observer.int", func(_ context.Context, result metric.Int64ObserverResult) {
|
||||
result.Observe(-142, labels...)
|
||||
})
|
||||
t.Log("Testing int observer")
|
||||
mockSDK.RunAsyncInstruments()
|
||||
checkObserverBatch(t, labels, mockSDK, metric.Int64NumberKind, o.AsyncImpl())
|
||||
}
|
||||
checkObserverBatch(t, labels, mockSDK, metric.Int64NumberKind, metric.ValueObserverKind, o.AsyncImpl(),
|
||||
-142,
|
||||
)
|
||||
})
|
||||
t.Run("float sumobserver", func(t *testing.T) {
|
||||
labels := []kv.KeyValue{kv.String("O", "P")}
|
||||
mockSDK, meter := mockTest.NewMeter()
|
||||
o := Must(meter).NewFloat64SumObserver("test.sumobserver.float", func(_ context.Context, result metric.Float64ObserverResult) {
|
||||
result.Observe(42.1, labels...)
|
||||
})
|
||||
mockSDK.RunAsyncInstruments()
|
||||
checkObserverBatch(t, labels, mockSDK, metric.Float64NumberKind, metric.SumObserverKind, o.AsyncImpl(),
|
||||
42.1,
|
||||
)
|
||||
})
|
||||
t.Run("int sumobserver", func(t *testing.T) {
|
||||
labels := []kv.KeyValue{}
|
||||
mockSDK, meter := mockTest.NewMeter()
|
||||
o := Must(meter).NewInt64SumObserver("test.observer.int", func(_ context.Context, result metric.Int64ObserverResult) {
|
||||
result.Observe(-142, labels...)
|
||||
})
|
||||
mockSDK.RunAsyncInstruments()
|
||||
checkObserverBatch(t, labels, mockSDK, metric.Int64NumberKind, metric.SumObserverKind, o.AsyncImpl(),
|
||||
-142,
|
||||
)
|
||||
})
|
||||
t.Run("float updownsumobserver", func(t *testing.T) {
|
||||
labels := []kv.KeyValue{kv.String("O", "P")}
|
||||
mockSDK, meter := mockTest.NewMeter()
|
||||
o := Must(meter).NewFloat64UpDownSumObserver("test.updownsumobserver.float", func(_ context.Context, result metric.Float64ObserverResult) {
|
||||
result.Observe(42.1, labels...)
|
||||
})
|
||||
mockSDK.RunAsyncInstruments()
|
||||
checkObserverBatch(t, labels, mockSDK, metric.Float64NumberKind, metric.UpDownSumObserverKind, o.AsyncImpl(),
|
||||
42.1,
|
||||
)
|
||||
})
|
||||
t.Run("int updownsumobserver", func(t *testing.T) {
|
||||
labels := []kv.KeyValue{}
|
||||
mockSDK, meter := mockTest.NewMeter()
|
||||
o := Must(meter).NewInt64UpDownSumObserver("test.observer.int", func(_ context.Context, result metric.Int64ObserverResult) {
|
||||
result.Observe(-142, labels...)
|
||||
})
|
||||
mockSDK.RunAsyncInstruments()
|
||||
checkObserverBatch(t, labels, mockSDK, metric.Int64NumberKind, metric.UpDownSumObserverKind, o.AsyncImpl(),
|
||||
-142,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func checkBatches(t *testing.T, ctx context.Context, labels []kv.KeyValue, mock *mockTest.MeterImpl, kind metric.NumberKind, instrument metric.InstrumentImpl) {
|
||||
func checkSyncBatches(t *testing.T, ctx context.Context, labels []kv.KeyValue, mock *mockTest.MeterImpl, nkind metric.NumberKind, mkind metric.Kind, instrument metric.InstrumentImpl, expected ...float64) {
|
||||
t.Helper()
|
||||
if len(mock.MeasurementBatches) != 3 {
|
||||
t.Errorf("Expected 3 recorded measurement batches, got %d", len(mock.MeasurementBatches))
|
||||
@@ -195,6 +273,8 @@ func checkBatches(t *testing.T, ctx context.Context, labels []kv.KeyValue, mock
|
||||
}
|
||||
for j := 0; j < minMLen; j++ {
|
||||
measurement := got.Measurements[j]
|
||||
require.Equal(t, mkind, measurement.Instrument.Descriptor().MetricKind())
|
||||
|
||||
if measurement.Instrument.Implementation() != ourInstrument {
|
||||
d := func(iface interface{}) string {
|
||||
i := iface.(*mockTest.Instrument)
|
||||
@@ -202,19 +282,19 @@ func checkBatches(t *testing.T, ctx context.Context, labels []kv.KeyValue, mock
|
||||
}
|
||||
t.Errorf("Wrong recorded instrument in measurement %d in batch %d, expected %s, got %s", j, i, d(ourInstrument), d(measurement.Instrument.Implementation()))
|
||||
}
|
||||
ft := fortyTwo(t, kind)
|
||||
if measurement.Number.CompareNumber(kind, ft) != 0 {
|
||||
t.Errorf("Wrong recorded value in measurement %d in batch %d, expected %s, got %s", j, i, ft.Emit(kind), measurement.Number.Emit(kind))
|
||||
expect := number(t, nkind, expected[i])
|
||||
if measurement.Number.CompareNumber(nkind, expect) != 0 {
|
||||
t.Errorf("Wrong recorded value in measurement %d in batch %d, expected %s, got %s", j, i, expect.Emit(nkind), measurement.Number.Emit(nkind))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchObserver(t *testing.T) {
|
||||
func TestBatchObserverInstruments(t *testing.T) {
|
||||
mockSDK, meter := mockTest.NewMeter()
|
||||
|
||||
var obs1 metric.Int64Observer
|
||||
var obs2 metric.Float64Observer
|
||||
var obs1 metric.Int64ValueObserver
|
||||
var obs2 metric.Float64ValueObserver
|
||||
|
||||
labels := []kv.KeyValue{
|
||||
kv.String("A", "B"),
|
||||
@@ -222,15 +302,15 @@ func TestBatchObserver(t *testing.T) {
|
||||
}
|
||||
|
||||
cb := Must(meter).NewBatchObserver(
|
||||
func(result metric.BatchObserverResult) {
|
||||
func(_ context.Context, result metric.BatchObserverResult) {
|
||||
result.Observe(labels,
|
||||
obs1.Observation(42),
|
||||
obs2.Observation(42.0),
|
||||
)
|
||||
},
|
||||
)
|
||||
obs1 = cb.RegisterInt64Observer("test.observer.int")
|
||||
obs2 = cb.RegisterFloat64Observer("test.observer.float")
|
||||
obs1 = cb.NewInt64ValueObserver("test.observer.int")
|
||||
obs2 = cb.NewFloat64ValueObserver("test.observer.float")
|
||||
|
||||
mockSDK.RunAsyncInstruments()
|
||||
|
||||
@@ -248,14 +328,14 @@ func TestBatchObserver(t *testing.T) {
|
||||
|
||||
m1 := got.Measurements[0]
|
||||
require.Equal(t, impl1, m1.Instrument.Implementation().(*mockTest.Async))
|
||||
require.Equal(t, 0, m1.Number.CompareNumber(metric.Int64NumberKind, fortyTwo(t, metric.Int64NumberKind)))
|
||||
require.Equal(t, 0, m1.Number.CompareNumber(metric.Int64NumberKind, number(t, metric.Int64NumberKind, 42)))
|
||||
|
||||
m2 := got.Measurements[1]
|
||||
require.Equal(t, impl2, m2.Instrument.Implementation().(*mockTest.Async))
|
||||
require.Equal(t, 0, m2.Number.CompareNumber(metric.Float64NumberKind, fortyTwo(t, metric.Float64NumberKind)))
|
||||
require.Equal(t, 0, m2.Number.CompareNumber(metric.Float64NumberKind, number(t, metric.Float64NumberKind, 42)))
|
||||
}
|
||||
|
||||
func checkObserverBatch(t *testing.T, labels []kv.KeyValue, mock *mockTest.MeterImpl, kind metric.NumberKind, observer metric.AsyncImpl) {
|
||||
func checkObserverBatch(t *testing.T, labels []kv.KeyValue, mock *mockTest.MeterImpl, nkind metric.NumberKind, mkind metric.Kind, observer metric.AsyncImpl, expected float64) {
|
||||
t.Helper()
|
||||
assert.Len(t, mock.MeasurementBatches, 1)
|
||||
if len(mock.MeasurementBatches) < 1 {
|
||||
@@ -272,21 +352,21 @@ func checkObserverBatch(t *testing.T, labels []kv.KeyValue, mock *mockTest.Meter
|
||||
return
|
||||
}
|
||||
measurement := got.Measurements[0]
|
||||
require.Equal(t, mkind, measurement.Instrument.Descriptor().MetricKind())
|
||||
assert.Equal(t, o, measurement.Instrument.Implementation().(*mockTest.Async))
|
||||
ft := fortyTwo(t, kind)
|
||||
assert.Equal(t, 0, measurement.Number.CompareNumber(kind, ft))
|
||||
ft := number(t, nkind, expected)
|
||||
assert.Equal(t, 0, measurement.Number.CompareNumber(nkind, ft))
|
||||
}
|
||||
|
||||
func fortyTwo(t *testing.T, kind metric.NumberKind) metric.Number {
|
||||
func number(t *testing.T, kind metric.NumberKind, value float64) metric.Number {
|
||||
t.Helper()
|
||||
switch kind {
|
||||
case metric.Int64NumberKind:
|
||||
return metric.NewInt64Number(42)
|
||||
return metric.NewInt64Number(int64(value))
|
||||
case metric.Float64NumberKind:
|
||||
return metric.NewFloat64Number(42)
|
||||
return metric.NewFloat64Number(value)
|
||||
}
|
||||
t.Errorf("Invalid value kind %q", kind)
|
||||
return metric.NewInt64Number(0)
|
||||
panic("invalid number kind")
|
||||
}
|
||||
|
||||
type testWrappedMeter struct {
|
||||
@@ -309,12 +389,12 @@ func TestWrappedInstrumentError(t *testing.T) {
|
||||
impl := &testWrappedMeter{}
|
||||
meter := metric.WrapMeterImpl(impl, "test")
|
||||
|
||||
measure, err := meter.NewInt64Measure("test.measure")
|
||||
valuerecorder, err := meter.NewInt64ValueRecorder("test.valuerecorder")
|
||||
|
||||
require.Equal(t, err, metric.ErrSDKReturnedNilImpl)
|
||||
require.NotNil(t, measure.SyncImpl())
|
||||
require.NotNil(t, valuerecorder.SyncImpl())
|
||||
|
||||
observer, err := meter.RegisterInt64Observer("test.observer", func(result metric.Int64ObserverResult) {})
|
||||
observer, err := meter.NewInt64ValueObserver("test.observer", func(_ context.Context, result metric.Int64ObserverResult) {})
|
||||
|
||||
require.NotNil(t, err)
|
||||
require.NotNil(t, observer.AsyncImpl())
|
||||
@@ -324,7 +404,7 @@ func TestNilCallbackObserverNoop(t *testing.T) {
|
||||
// Tests that a nil callback yields a no-op observer without error.
|
||||
_, meter := mockTest.NewMeter()
|
||||
|
||||
observer := Must(meter).RegisterInt64Observer("test.observer", nil)
|
||||
observer := Must(meter).NewInt64ValueObserver("test.observer", nil)
|
||||
|
||||
_, ok := observer.AsyncImpl().(metric.NoopAsync)
|
||||
require.True(t, ok)
|
||||
|
||||
+53
-13
@@ -14,7 +14,11 @@
|
||||
|
||||
package metric
|
||||
|
||||
import "go.opentelemetry.io/otel/api/kv"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
)
|
||||
|
||||
// The file is organized as follows:
|
||||
//
|
||||
@@ -29,7 +33,7 @@ import "go.opentelemetry.io/otel/api/kv"
|
||||
|
||||
// Observation is used for reporting an asynchronous batch of metric
|
||||
// values. Instances of this type should be created by asynchronous
|
||||
// instruments (e.g., Int64Observer.Observation()).
|
||||
// instruments (e.g., Int64ValueObserver.Observation()).
|
||||
type Observation struct {
|
||||
// number needs to be aligned for 64-bit atomic operations.
|
||||
number Number
|
||||
@@ -38,16 +42,16 @@ type Observation struct {
|
||||
|
||||
// Int64ObserverCallback is a type of callback that integral
|
||||
// observers run.
|
||||
type Int64ObserverCallback func(Int64ObserverResult)
|
||||
type Int64ObserverCallback func(context.Context, Int64ObserverResult)
|
||||
|
||||
// Float64ObserverCallback is a type of callback that floating point
|
||||
// observers run.
|
||||
type Float64ObserverCallback func(Float64ObserverResult)
|
||||
type Float64ObserverCallback func(context.Context, Float64ObserverResult)
|
||||
|
||||
// BatchObserverCallback is a callback argument for use with any
|
||||
// Observer instrument that will be reported as a batch of
|
||||
// observations.
|
||||
type BatchObserverCallback func(BatchObserverResult)
|
||||
type BatchObserverCallback func(context.Context, BatchObserverResult)
|
||||
|
||||
// Int64ObserverResult is passed to an observer callback to capture
|
||||
// observations for one asynchronous integer metric instrument.
|
||||
@@ -110,7 +114,7 @@ type AsyncSingleRunner interface {
|
||||
// receives one captured observation. (The function accepts
|
||||
// multiple observations so the same implementation can be
|
||||
// used for batch runners.)
|
||||
Run(single AsyncImpl, capture func([]kv.KeyValue, ...Observation))
|
||||
Run(ctx context.Context, single AsyncImpl, capture func([]kv.KeyValue, ...Observation))
|
||||
|
||||
AsyncRunner
|
||||
}
|
||||
@@ -120,7 +124,7 @@ type AsyncSingleRunner interface {
|
||||
type AsyncBatchRunner interface {
|
||||
// Run accepts a function for capturing observations of
|
||||
// multiple instruments.
|
||||
Run(capture func([]kv.KeyValue, ...Observation))
|
||||
Run(ctx context.Context, capture func([]kv.KeyValue, ...Observation))
|
||||
|
||||
AsyncRunner
|
||||
}
|
||||
@@ -154,24 +158,60 @@ func (*Float64ObserverCallback) AnyRunner() {}
|
||||
func (*BatchObserverCallback) AnyRunner() {}
|
||||
|
||||
// Run implements AsyncSingleRunner.
|
||||
func (i *Int64ObserverCallback) Run(impl AsyncImpl, function func([]kv.KeyValue, ...Observation)) {
|
||||
(*i)(Int64ObserverResult{
|
||||
func (i *Int64ObserverCallback) Run(ctx context.Context, impl AsyncImpl, function func([]kv.KeyValue, ...Observation)) {
|
||||
(*i)(ctx, Int64ObserverResult{
|
||||
instrument: impl,
|
||||
function: function,
|
||||
})
|
||||
}
|
||||
|
||||
// Run implements AsyncSingleRunner.
|
||||
func (f *Float64ObserverCallback) Run(impl AsyncImpl, function func([]kv.KeyValue, ...Observation)) {
|
||||
(*f)(Float64ObserverResult{
|
||||
func (f *Float64ObserverCallback) Run(ctx context.Context, impl AsyncImpl, function func([]kv.KeyValue, ...Observation)) {
|
||||
(*f)(ctx, Float64ObserverResult{
|
||||
instrument: impl,
|
||||
function: function,
|
||||
})
|
||||
}
|
||||
|
||||
// Run implements AsyncBatchRunner.
|
||||
func (b *BatchObserverCallback) Run(function func([]kv.KeyValue, ...Observation)) {
|
||||
(*b)(BatchObserverResult{
|
||||
func (b *BatchObserverCallback) Run(ctx context.Context, function func([]kv.KeyValue, ...Observation)) {
|
||||
(*b)(ctx, BatchObserverResult{
|
||||
function: function,
|
||||
})
|
||||
}
|
||||
|
||||
// wrapInt64ValueObserverInstrument converts an AsyncImpl into Int64ValueObserver.
|
||||
func wrapInt64ValueObserverInstrument(asyncInst AsyncImpl, err error) (Int64ValueObserver, error) {
|
||||
common, err := checkNewAsync(asyncInst, err)
|
||||
return Int64ValueObserver{asyncInstrument: common}, err
|
||||
}
|
||||
|
||||
// wrapFloat64ValueObserverInstrument converts an AsyncImpl into Float64ValueObserver.
|
||||
func wrapFloat64ValueObserverInstrument(asyncInst AsyncImpl, err error) (Float64ValueObserver, error) {
|
||||
common, err := checkNewAsync(asyncInst, err)
|
||||
return Float64ValueObserver{asyncInstrument: common}, err
|
||||
}
|
||||
|
||||
// wrapInt64SumObserverInstrument converts an AsyncImpl into Int64SumObserver.
|
||||
func wrapInt64SumObserverInstrument(asyncInst AsyncImpl, err error) (Int64SumObserver, error) {
|
||||
common, err := checkNewAsync(asyncInst, err)
|
||||
return Int64SumObserver{asyncInstrument: common}, err
|
||||
}
|
||||
|
||||
// wrapFloat64SumObserverInstrument converts an AsyncImpl into Float64SumObserver.
|
||||
func wrapFloat64SumObserverInstrument(asyncInst AsyncImpl, err error) (Float64SumObserver, error) {
|
||||
common, err := checkNewAsync(asyncInst, err)
|
||||
return Float64SumObserver{asyncInstrument: common}, err
|
||||
}
|
||||
|
||||
// wrapInt64UpDownSumObserverInstrument converts an AsyncImpl into Int64UpDownSumObserver.
|
||||
func wrapInt64UpDownSumObserverInstrument(asyncInst AsyncImpl, err error) (Int64UpDownSumObserver, error) {
|
||||
common, err := checkNewAsync(asyncInst, err)
|
||||
return Int64UpDownSumObserver{asyncInstrument: common}, err
|
||||
}
|
||||
|
||||
// wrapFloat64UpDownSumObserverInstrument converts an AsyncImpl into Float64UpDownSumObserver.
|
||||
func wrapFloat64UpDownSumObserverInstrument(asyncInst AsyncImpl, err error) (Float64UpDownSumObserver, error) {
|
||||
common, err := checkNewAsync(asyncInst, err)
|
||||
return Float64UpDownSumObserver{asyncInstrument: common}, err
|
||||
}
|
||||
|
||||
+25
-45
@@ -13,57 +13,37 @@
|
||||
// limitations under the License.
|
||||
|
||||
// metric package provides an API for reporting diagnostic
|
||||
// measurements using four basic kinds of instruments.
|
||||
// measurements using instruments categorized as follows:
|
||||
//
|
||||
// The three basic kinds are:
|
||||
// Synchronous instruments are called by the user with a Context.
|
||||
// Asynchronous instruments are called by the SDK during collection.
|
||||
//
|
||||
// - counters
|
||||
// - measures
|
||||
// - observers
|
||||
// Additive instruments are semantically intended for capturing a sum.
|
||||
// Non-additive instruments are intended for capturing a distribution.
|
||||
//
|
||||
// All instruments report either float64 or int64 values.
|
||||
// Additive instruments may be monotonic, in which case they are
|
||||
// non-descreasing and naturally define a rate.
|
||||
//
|
||||
// The primary object that handles metrics is Meter. Meter can be
|
||||
// obtained from Provider. The implementations of the Meter and
|
||||
// Provider are provided by SDK. Normally, the Meter is used directly
|
||||
// only for the instrument creation and batch recording.
|
||||
// The synchronous instrument names are:
|
||||
//
|
||||
// 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 WithMonotonic option
|
||||
// (passing false as a parameter) passed to the Meter.New*Counter
|
||||
// function - this allows reporting negative values. To report the new
|
||||
// value, use an Add function.
|
||||
// Counter: additive, monotonic
|
||||
// UpDownCounter: additive
|
||||
// ValueRecorder: non-additive
|
||||
//
|
||||
// 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 WithAbsolute option (passing false as a parameter) passed to
|
||||
// the New*Measure function - this allows reporting negative values
|
||||
// too. To report a new value, use the Record function.
|
||||
// and the asynchronous instruments are:
|
||||
//
|
||||
// Observers are instruments that are reporting a current state of a
|
||||
// set of values. An example could be voltage or
|
||||
// temperature. Observers can be created with either
|
||||
// RegisterFloat64Observer or RegisterInt64Observer. Observers 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 Register*Observer
|
||||
// function - this permits the reported values only to go
|
||||
// up. Reporting of the new values happens asynchronously, with the
|
||||
// use of a callback passed to the Register*Observer function. The
|
||||
// callback can report multiple values. There is no unregister function.
|
||||
// SumObserver: additive, monotonic
|
||||
// UpDownSumObserver: additive
|
||||
// ValueObserver: non-additive
|
||||
//
|
||||
// Counters and measures support creating bound instruments for a
|
||||
// potentially more efficient reporting. The bound instruments have
|
||||
// the same function names as the instruments (so a Counter bound
|
||||
// instrument has Add, and a Measure bound instrument has Record).
|
||||
// Bound Instruments can be created with the Bind function of the
|
||||
// respective instrument. When done with the bound instrument, call
|
||||
// Unbind on it.
|
||||
// All instruments are provided with support for either float64 or
|
||||
// int64 input values.
|
||||
//
|
||||
// The Meter interface supports allocating new instruments as well as
|
||||
// interfaces for recording batches of synchronous measurements or
|
||||
// asynchronous observations. To obtain a Meter, use a Provider.
|
||||
//
|
||||
// The Provider interface supports obtaining a named Meter interface.
|
||||
// To obtain a Provider implementation, initialize and configure any
|
||||
// compatible SDK.
|
||||
package metric // import "go.opentelemetry.io/otel/api/metric"
|
||||
|
||||
+12
-4
@@ -20,10 +20,18 @@ package metric
|
||||
type Kind int8
|
||||
|
||||
const (
|
||||
// MeasureKind indicates a Measure instrument.
|
||||
MeasureKind Kind = iota
|
||||
// ObserverKind indicates an Observer instrument.
|
||||
ObserverKind
|
||||
// ValueRecorderKind indicates a ValueRecorder instrument.
|
||||
ValueRecorderKind Kind = iota
|
||||
// ValueObserverKind indicates an ValueObserver instrument.
|
||||
ValueObserverKind
|
||||
|
||||
// CounterKind indicates a Counter instrument.
|
||||
CounterKind
|
||||
// UpDownCounterKind indicates a UpDownCounter instrument.
|
||||
UpDownCounterKind
|
||||
|
||||
// SumObserverKind indicates a SumObserver instrument.
|
||||
SumObserverKind
|
||||
// UpDownSumObserverKind indicates a UpDownSumObserver instrument.
|
||||
UpDownSumObserverKind
|
||||
)
|
||||
|
||||
@@ -8,14 +8,17 @@ 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[MeasureKind-0]
|
||||
_ = x[ObserverKind-1]
|
||||
_ = x[ValueRecorderKind-0]
|
||||
_ = x[ValueObserverKind-1]
|
||||
_ = x[CounterKind-2]
|
||||
_ = x[UpDownCounterKind-3]
|
||||
_ = x[SumObserverKind-4]
|
||||
_ = x[UpDownSumObserverKind-5]
|
||||
}
|
||||
|
||||
const _Kind_name = "MeasureKindObserverKindCounterKind"
|
||||
const _Kind_name = "ValueRecorderKindValueObserverKindCounterKindUpDownCounterKindSumObserverKindUpDownSumObserverKind"
|
||||
|
||||
var _Kind_index = [...]uint8{0, 11, 23, 34}
|
||||
var _Kind_index = [...]uint8{0, 17, 34, 45, 62, 77, 98}
|
||||
|
||||
func (i Kind) String() string {
|
||||
if i < 0 || i >= Kind(len(_Kind_index)-1) {
|
||||
|
||||
+148
-28
@@ -82,72 +82,192 @@ func (m Meter) NewFloat64Counter(name string, options ...Option) (Float64Counter
|
||||
m.newSync(name, CounterKind, Float64NumberKind, options))
|
||||
}
|
||||
|
||||
// NewInt64Measure creates a new integer Measure instrument with the
|
||||
// NewInt64UpDownCounter creates a new integer UpDownCounter instrument with the
|
||||
// given name, customized with options. May return an error if the
|
||||
// name is invalid (e.g., empty) or improperly registered (e.g.,
|
||||
// duplicate registration).
|
||||
func (m Meter) NewInt64Measure(name string, opts ...Option) (Int64Measure, error) {
|
||||
return wrapInt64MeasureInstrument(
|
||||
m.newSync(name, MeasureKind, Int64NumberKind, opts))
|
||||
func (m Meter) NewInt64UpDownCounter(name string, options ...Option) (Int64UpDownCounter, error) {
|
||||
return wrapInt64UpDownCounterInstrument(
|
||||
m.newSync(name, UpDownCounterKind, Int64NumberKind, options))
|
||||
}
|
||||
|
||||
// NewFloat64Measure creates a new floating point Measure with the
|
||||
// NewFloat64UpDownCounter creates a new floating point UpDownCounter with the
|
||||
// given name, customized with options. May return an error if the
|
||||
// name is invalid (e.g., empty) or improperly registered (e.g.,
|
||||
// duplicate registration).
|
||||
func (m Meter) NewFloat64Measure(name string, opts ...Option) (Float64Measure, error) {
|
||||
return wrapFloat64MeasureInstrument(
|
||||
m.newSync(name, MeasureKind, Float64NumberKind, opts))
|
||||
func (m Meter) NewFloat64UpDownCounter(name string, options ...Option) (Float64UpDownCounter, error) {
|
||||
return wrapFloat64UpDownCounterInstrument(
|
||||
m.newSync(name, UpDownCounterKind, Float64NumberKind, options))
|
||||
}
|
||||
|
||||
// RegisterInt64Observer creates a new integer Observer instrument
|
||||
// NewInt64ValueRecorder creates a new integer ValueRecorder instrument with the
|
||||
// given name, customized with options. May return an error if the
|
||||
// name is invalid (e.g., empty) or improperly registered (e.g.,
|
||||
// duplicate registration).
|
||||
func (m Meter) NewInt64ValueRecorder(name string, opts ...Option) (Int64ValueRecorder, error) {
|
||||
return wrapInt64ValueRecorderInstrument(
|
||||
m.newSync(name, ValueRecorderKind, Int64NumberKind, opts))
|
||||
}
|
||||
|
||||
// NewFloat64ValueRecorder creates a new floating point ValueRecorder with the
|
||||
// given name, customized with options. May return an error if the
|
||||
// name is invalid (e.g., empty) or improperly registered (e.g.,
|
||||
// duplicate registration).
|
||||
func (m Meter) NewFloat64ValueRecorder(name string, opts ...Option) (Float64ValueRecorder, error) {
|
||||
return wrapFloat64ValueRecorderInstrument(
|
||||
m.newSync(name, ValueRecorderKind, Float64NumberKind, opts))
|
||||
}
|
||||
|
||||
// NewInt64ValueObserver creates a new integer ValueObserver instrument
|
||||
// with the given name, running a given callback, and customized with
|
||||
// options. May return an error if the name is invalid (e.g., empty)
|
||||
// or improperly registered (e.g., duplicate registration).
|
||||
func (m Meter) RegisterInt64Observer(name string, callback Int64ObserverCallback, opts ...Option) (Int64Observer, error) {
|
||||
func (m Meter) NewInt64ValueObserver(name string, callback Int64ObserverCallback, opts ...Option) (Int64ValueObserver, error) {
|
||||
if callback == nil {
|
||||
return wrapInt64ObserverInstrument(NoopAsync{}, nil)
|
||||
return wrapInt64ValueObserverInstrument(NoopAsync{}, nil)
|
||||
}
|
||||
return wrapInt64ObserverInstrument(
|
||||
m.newAsync(name, ObserverKind, Int64NumberKind, opts,
|
||||
return wrapInt64ValueObserverInstrument(
|
||||
m.newAsync(name, ValueObserverKind, Int64NumberKind, opts,
|
||||
newInt64AsyncRunner(callback)))
|
||||
}
|
||||
|
||||
// RegisterFloat64Observer creates a new floating point Observer with
|
||||
// NewFloat64ValueObserver creates a new floating point ValueObserver with
|
||||
// the given name, running a given callback, and customized with
|
||||
// options. May return an error if the name is invalid (e.g., empty)
|
||||
// or improperly registered (e.g., duplicate registration).
|
||||
func (m Meter) RegisterFloat64Observer(name string, callback Float64ObserverCallback, opts ...Option) (Float64Observer, error) {
|
||||
func (m Meter) NewFloat64ValueObserver(name string, callback Float64ObserverCallback, opts ...Option) (Float64ValueObserver, error) {
|
||||
if callback == nil {
|
||||
return wrapFloat64ObserverInstrument(NoopAsync{}, nil)
|
||||
return wrapFloat64ValueObserverInstrument(NoopAsync{}, nil)
|
||||
}
|
||||
return wrapFloat64ObserverInstrument(
|
||||
m.newAsync(name, ObserverKind, Float64NumberKind, opts,
|
||||
return wrapFloat64ValueObserverInstrument(
|
||||
m.newAsync(name, ValueObserverKind, Float64NumberKind, opts,
|
||||
newFloat64AsyncRunner(callback)))
|
||||
}
|
||||
|
||||
// RegisterInt64Observer creates a new integer Observer instrument
|
||||
// NewInt64SumObserver creates a new integer SumObserver instrument
|
||||
// with the given name, running a given callback, and customized with
|
||||
// options. May return an error if the name is invalid (e.g., empty)
|
||||
// or improperly registered (e.g., duplicate registration).
|
||||
func (m Meter) NewInt64SumObserver(name string, callback Int64ObserverCallback, opts ...Option) (Int64SumObserver, error) {
|
||||
if callback == nil {
|
||||
return wrapInt64SumObserverInstrument(NoopAsync{}, nil)
|
||||
}
|
||||
return wrapInt64SumObserverInstrument(
|
||||
m.newAsync(name, SumObserverKind, Int64NumberKind, opts,
|
||||
newInt64AsyncRunner(callback)))
|
||||
}
|
||||
|
||||
// NewFloat64SumObserver creates a new floating point SumObserver with
|
||||
// the given name, running a given callback, and customized with
|
||||
// options. May return an error if the name is invalid (e.g., empty)
|
||||
// or improperly registered (e.g., duplicate registration).
|
||||
func (m Meter) NewFloat64SumObserver(name string, callback Float64ObserverCallback, opts ...Option) (Float64SumObserver, error) {
|
||||
if callback == nil {
|
||||
return wrapFloat64SumObserverInstrument(NoopAsync{}, nil)
|
||||
}
|
||||
return wrapFloat64SumObserverInstrument(
|
||||
m.newAsync(name, SumObserverKind, Float64NumberKind, opts,
|
||||
newFloat64AsyncRunner(callback)))
|
||||
}
|
||||
|
||||
// NewInt64UpDownSumObserver creates a new integer UpDownSumObserver instrument
|
||||
// with the given name, running a given callback, and customized with
|
||||
// options. May return an error if the name is invalid (e.g., empty)
|
||||
// or improperly registered (e.g., duplicate registration).
|
||||
func (m Meter) NewInt64UpDownSumObserver(name string, callback Int64ObserverCallback, opts ...Option) (Int64UpDownSumObserver, error) {
|
||||
if callback == nil {
|
||||
return wrapInt64UpDownSumObserverInstrument(NoopAsync{}, nil)
|
||||
}
|
||||
return wrapInt64UpDownSumObserverInstrument(
|
||||
m.newAsync(name, UpDownSumObserverKind, Int64NumberKind, opts,
|
||||
newInt64AsyncRunner(callback)))
|
||||
}
|
||||
|
||||
// NewFloat64UpDownSumObserver creates a new floating point UpDownSumObserver with
|
||||
// the given name, running a given callback, and customized with
|
||||
// options. May return an error if the name is invalid (e.g., empty)
|
||||
// or improperly registered (e.g., duplicate registration).
|
||||
func (m Meter) NewFloat64UpDownSumObserver(name string, callback Float64ObserverCallback, opts ...Option) (Float64UpDownSumObserver, error) {
|
||||
if callback == nil {
|
||||
return wrapFloat64UpDownSumObserverInstrument(NoopAsync{}, nil)
|
||||
}
|
||||
return wrapFloat64UpDownSumObserverInstrument(
|
||||
m.newAsync(name, UpDownSumObserverKind, Float64NumberKind, opts,
|
||||
newFloat64AsyncRunner(callback)))
|
||||
}
|
||||
|
||||
// NewInt64ValueObserver creates a new integer ValueObserver instrument
|
||||
// with the given name, running in a batch callback, and customized with
|
||||
// options. May return an error if the name is invalid (e.g., empty)
|
||||
// or improperly registered (e.g., duplicate registration).
|
||||
func (b BatchObserver) RegisterInt64Observer(name string, opts ...Option) (Int64Observer, error) {
|
||||
func (b BatchObserver) NewInt64ValueObserver(name string, opts ...Option) (Int64ValueObserver, error) {
|
||||
if b.runner == nil {
|
||||
return wrapInt64ObserverInstrument(NoopAsync{}, nil)
|
||||
return wrapInt64ValueObserverInstrument(NoopAsync{}, nil)
|
||||
}
|
||||
return wrapInt64ObserverInstrument(
|
||||
b.meter.newAsync(name, ObserverKind, Int64NumberKind, opts, b.runner))
|
||||
return wrapInt64ValueObserverInstrument(
|
||||
b.meter.newAsync(name, ValueObserverKind, Int64NumberKind, opts, b.runner))
|
||||
}
|
||||
|
||||
// RegisterFloat64Observer creates a new floating point Observer with
|
||||
// NewFloat64ValueObserver creates a new floating point ValueObserver with
|
||||
// the given name, running in a batch callback, and customized with
|
||||
// options. May return an error if the name is invalid (e.g., empty)
|
||||
// or improperly registered (e.g., duplicate registration).
|
||||
func (b BatchObserver) RegisterFloat64Observer(name string, opts ...Option) (Float64Observer, error) {
|
||||
func (b BatchObserver) NewFloat64ValueObserver(name string, opts ...Option) (Float64ValueObserver, error) {
|
||||
if b.runner == nil {
|
||||
return wrapFloat64ObserverInstrument(NoopAsync{}, nil)
|
||||
return wrapFloat64ValueObserverInstrument(NoopAsync{}, nil)
|
||||
}
|
||||
return wrapFloat64ObserverInstrument(
|
||||
b.meter.newAsync(name, ObserverKind, Float64NumberKind, opts,
|
||||
return wrapFloat64ValueObserverInstrument(
|
||||
b.meter.newAsync(name, ValueObserverKind, Float64NumberKind, opts,
|
||||
b.runner))
|
||||
}
|
||||
|
||||
// NewInt64SumObserver creates a new integer SumObserver instrument
|
||||
// with the given name, running in a batch callback, and customized with
|
||||
// options. May return an error if the name is invalid (e.g., empty)
|
||||
// or improperly registered (e.g., duplicate registration).
|
||||
func (b BatchObserver) NewInt64SumObserver(name string, opts ...Option) (Int64SumObserver, error) {
|
||||
if b.runner == nil {
|
||||
return wrapInt64SumObserverInstrument(NoopAsync{}, nil)
|
||||
}
|
||||
return wrapInt64SumObserverInstrument(
|
||||
b.meter.newAsync(name, SumObserverKind, Int64NumberKind, opts, b.runner))
|
||||
}
|
||||
|
||||
// NewFloat64SumObserver creates a new floating point SumObserver with
|
||||
// the given name, running in a batch callback, and customized with
|
||||
// options. May return an error if the name is invalid (e.g., empty)
|
||||
// or improperly registered (e.g., duplicate registration).
|
||||
func (b BatchObserver) NewFloat64SumObserver(name string, opts ...Option) (Float64SumObserver, error) {
|
||||
if b.runner == nil {
|
||||
return wrapFloat64SumObserverInstrument(NoopAsync{}, nil)
|
||||
}
|
||||
return wrapFloat64SumObserverInstrument(
|
||||
b.meter.newAsync(name, SumObserverKind, Float64NumberKind, opts,
|
||||
b.runner))
|
||||
}
|
||||
|
||||
// NewInt64UpDownSumObserver creates a new integer UpDownSumObserver instrument
|
||||
// with the given name, running in a batch callback, and customized with
|
||||
// options. May return an error if the name is invalid (e.g., empty)
|
||||
// or improperly registered (e.g., duplicate registration).
|
||||
func (b BatchObserver) NewInt64UpDownSumObserver(name string, opts ...Option) (Int64UpDownSumObserver, error) {
|
||||
if b.runner == nil {
|
||||
return wrapInt64UpDownSumObserverInstrument(NoopAsync{}, nil)
|
||||
}
|
||||
return wrapInt64UpDownSumObserverInstrument(
|
||||
b.meter.newAsync(name, UpDownSumObserverKind, Int64NumberKind, opts, b.runner))
|
||||
}
|
||||
|
||||
// NewFloat64UpDownSumObserver creates a new floating point UpDownSumObserver with
|
||||
// the given name, running in a batch callback, and customized with
|
||||
// options. May return an error if the name is invalid (e.g., empty)
|
||||
// or improperly registered (e.g., duplicate registration).
|
||||
func (b BatchObserver) NewFloat64UpDownSumObserver(name string, opts ...Option) (Float64UpDownSumObserver, error) {
|
||||
if b.runner == nil {
|
||||
return wrapFloat64UpDownSumObserverInstrument(NoopAsync{}, nil)
|
||||
}
|
||||
return wrapFloat64UpDownSumObserverInstrument(
|
||||
b.meter.newAsync(name, UpDownSumObserverKind, Float64NumberKind, opts,
|
||||
b.runner))
|
||||
}
|
||||
|
||||
|
||||
+119
-19
@@ -53,40 +53,100 @@ func (mm MeterMust) NewFloat64Counter(name string, cos ...Option) Float64Counter
|
||||
}
|
||||
}
|
||||
|
||||
// NewInt64Measure calls `Meter.NewInt64Measure` and returns the
|
||||
// NewInt64UpDownCounter calls `Meter.NewInt64UpDownCounter` and returns the
|
||||
// instrument, panicking if it encounters an error.
|
||||
func (mm MeterMust) NewInt64Measure(name string, mos ...Option) Int64Measure {
|
||||
if inst, err := mm.meter.NewInt64Measure(name, mos...); err != nil {
|
||||
func (mm MeterMust) NewInt64UpDownCounter(name string, cos ...Option) Int64UpDownCounter {
|
||||
if inst, err := mm.meter.NewInt64UpDownCounter(name, cos...); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return inst
|
||||
}
|
||||
}
|
||||
|
||||
// NewFloat64Measure calls `Meter.NewFloat64Measure` and returns the
|
||||
// NewFloat64UpDownCounter calls `Meter.NewFloat64UpDownCounter` and returns the
|
||||
// instrument, panicking if it encounters an error.
|
||||
func (mm MeterMust) NewFloat64Measure(name string, mos ...Option) Float64Measure {
|
||||
if inst, err := mm.meter.NewFloat64Measure(name, mos...); err != nil {
|
||||
func (mm MeterMust) NewFloat64UpDownCounter(name string, cos ...Option) Float64UpDownCounter {
|
||||
if inst, err := mm.meter.NewFloat64UpDownCounter(name, cos...); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return inst
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterInt64Observer calls `Meter.RegisterInt64Observer` and
|
||||
// returns the instrument, panicking if it encounters an error.
|
||||
func (mm MeterMust) RegisterInt64Observer(name string, callback Int64ObserverCallback, oos ...Option) Int64Observer {
|
||||
if inst, err := mm.meter.RegisterInt64Observer(name, callback, oos...); err != nil {
|
||||
// NewInt64ValueRecorder calls `Meter.NewInt64ValueRecorder` and returns the
|
||||
// instrument, panicking if it encounters an error.
|
||||
func (mm MeterMust) NewInt64ValueRecorder(name string, mos ...Option) Int64ValueRecorder {
|
||||
if inst, err := mm.meter.NewInt64ValueRecorder(name, mos...); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return inst
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterFloat64Observer calls `Meter.RegisterFloat64Observer` and
|
||||
// NewFloat64ValueRecorder calls `Meter.NewFloat64ValueRecorder` and returns the
|
||||
// instrument, panicking if it encounters an error.
|
||||
func (mm MeterMust) NewFloat64ValueRecorder(name string, mos ...Option) Float64ValueRecorder {
|
||||
if inst, err := mm.meter.NewFloat64ValueRecorder(name, mos...); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return inst
|
||||
}
|
||||
}
|
||||
|
||||
// NewInt64ValueObserver calls `Meter.NewInt64ValueObserver` and
|
||||
// returns the instrument, panicking if it encounters an error.
|
||||
func (mm MeterMust) RegisterFloat64Observer(name string, callback Float64ObserverCallback, oos ...Option) Float64Observer {
|
||||
if inst, err := mm.meter.RegisterFloat64Observer(name, callback, oos...); err != nil {
|
||||
func (mm MeterMust) NewInt64ValueObserver(name string, callback Int64ObserverCallback, oos ...Option) Int64ValueObserver {
|
||||
if inst, err := mm.meter.NewInt64ValueObserver(name, callback, oos...); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return inst
|
||||
}
|
||||
}
|
||||
|
||||
// NewFloat64ValueObserver calls `Meter.NewFloat64ValueObserver` and
|
||||
// returns the instrument, panicking if it encounters an error.
|
||||
func (mm MeterMust) NewFloat64ValueObserver(name string, callback Float64ObserverCallback, oos ...Option) Float64ValueObserver {
|
||||
if inst, err := mm.meter.NewFloat64ValueObserver(name, callback, oos...); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return inst
|
||||
}
|
||||
}
|
||||
|
||||
// NewInt64SumObserver calls `Meter.NewInt64SumObserver` and
|
||||
// returns the instrument, panicking if it encounters an error.
|
||||
func (mm MeterMust) NewInt64SumObserver(name string, callback Int64ObserverCallback, oos ...Option) Int64SumObserver {
|
||||
if inst, err := mm.meter.NewInt64SumObserver(name, callback, oos...); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return inst
|
||||
}
|
||||
}
|
||||
|
||||
// NewFloat64SumObserver calls `Meter.NewFloat64SumObserver` and
|
||||
// returns the instrument, panicking if it encounters an error.
|
||||
func (mm MeterMust) NewFloat64SumObserver(name string, callback Float64ObserverCallback, oos ...Option) Float64SumObserver {
|
||||
if inst, err := mm.meter.NewFloat64SumObserver(name, callback, oos...); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return inst
|
||||
}
|
||||
}
|
||||
|
||||
// NewInt64UpDownSumObserver calls `Meter.NewInt64UpDownSumObserver` and
|
||||
// returns the instrument, panicking if it encounters an error.
|
||||
func (mm MeterMust) NewInt64UpDownSumObserver(name string, callback Int64ObserverCallback, oos ...Option) Int64UpDownSumObserver {
|
||||
if inst, err := mm.meter.NewInt64UpDownSumObserver(name, callback, oos...); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return inst
|
||||
}
|
||||
}
|
||||
|
||||
// NewFloat64UpDownSumObserver calls `Meter.NewFloat64UpDownSumObserver` and
|
||||
// returns the instrument, panicking if it encounters an error.
|
||||
func (mm MeterMust) NewFloat64UpDownSumObserver(name string, callback Float64ObserverCallback, oos ...Option) Float64UpDownSumObserver {
|
||||
if inst, err := mm.meter.NewFloat64UpDownSumObserver(name, callback, oos...); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return inst
|
||||
@@ -101,20 +161,60 @@ func (mm MeterMust) NewBatchObserver(callback BatchObserverCallback) BatchObserv
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterInt64Observer calls `BatchObserver.RegisterInt64Observer` and
|
||||
// NewInt64ValueObserver calls `BatchObserver.NewInt64ValueObserver` and
|
||||
// returns the instrument, panicking if it encounters an error.
|
||||
func (bm BatchObserverMust) RegisterInt64Observer(name string, oos ...Option) Int64Observer {
|
||||
if inst, err := bm.batch.RegisterInt64Observer(name, oos...); err != nil {
|
||||
func (bm BatchObserverMust) NewInt64ValueObserver(name string, oos ...Option) Int64ValueObserver {
|
||||
if inst, err := bm.batch.NewInt64ValueObserver(name, oos...); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return inst
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterFloat64Observer calls `BatchObserver.RegisterFloat64Observer` and
|
||||
// NewFloat64ValueObserver calls `BatchObserver.NewFloat64ValueObserver` and
|
||||
// returns the instrument, panicking if it encounters an error.
|
||||
func (bm BatchObserverMust) RegisterFloat64Observer(name string, oos ...Option) Float64Observer {
|
||||
if inst, err := bm.batch.RegisterFloat64Observer(name, oos...); err != nil {
|
||||
func (bm BatchObserverMust) NewFloat64ValueObserver(name string, oos ...Option) Float64ValueObserver {
|
||||
if inst, err := bm.batch.NewFloat64ValueObserver(name, oos...); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return inst
|
||||
}
|
||||
}
|
||||
|
||||
// NewInt64SumObserver calls `BatchObserver.NewInt64SumObserver` and
|
||||
// returns the instrument, panicking if it encounters an error.
|
||||
func (bm BatchObserverMust) NewInt64SumObserver(name string, oos ...Option) Int64SumObserver {
|
||||
if inst, err := bm.batch.NewInt64SumObserver(name, oos...); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return inst
|
||||
}
|
||||
}
|
||||
|
||||
// NewFloat64SumObserver calls `BatchObserver.NewFloat64SumObserver` and
|
||||
// returns the instrument, panicking if it encounters an error.
|
||||
func (bm BatchObserverMust) NewFloat64SumObserver(name string, oos ...Option) Float64SumObserver {
|
||||
if inst, err := bm.batch.NewFloat64SumObserver(name, oos...); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return inst
|
||||
}
|
||||
}
|
||||
|
||||
// NewInt64UpDownSumObserver calls `BatchObserver.NewInt64UpDownSumObserver` and
|
||||
// returns the instrument, panicking if it encounters an error.
|
||||
func (bm BatchObserverMust) NewInt64UpDownSumObserver(name string, oos ...Option) Int64UpDownSumObserver {
|
||||
if inst, err := bm.batch.NewInt64UpDownSumObserver(name, oos...); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return inst
|
||||
}
|
||||
}
|
||||
|
||||
// NewFloat64UpDownSumObserver calls `BatchObserver.NewFloat64UpDownSumObserver` and
|
||||
// returns the instrument, panicking if it encounters an error.
|
||||
func (bm BatchObserverMust) NewFloat64UpDownSumObserver(name string, oos ...Option) Float64UpDownSumObserver {
|
||||
if inst, err := bm.batch.NewFloat64UpDownSumObserver(name, oos...); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return inst
|
||||
|
||||
+74
-6
@@ -21,15 +21,39 @@ type BatchObserver struct {
|
||||
runner AsyncBatchRunner
|
||||
}
|
||||
|
||||
// Int64Observer is a metric that captures a set of int64 values at a
|
||||
// Int64ValueObserver is a metric that captures a set of int64 values at a
|
||||
// point in time.
|
||||
type Int64Observer struct {
|
||||
type Int64ValueObserver struct {
|
||||
asyncInstrument
|
||||
}
|
||||
|
||||
// Float64Observer is a metric that captures a set of float64 values
|
||||
// Float64ValueObserver is a metric that captures a set of float64 values
|
||||
// at a point in time.
|
||||
type Float64Observer struct {
|
||||
type Float64ValueObserver struct {
|
||||
asyncInstrument
|
||||
}
|
||||
|
||||
// Int64SumObserver is a metric that captures a precomputed sum of
|
||||
// int64 values at a point in time.
|
||||
type Int64SumObserver struct {
|
||||
asyncInstrument
|
||||
}
|
||||
|
||||
// Float64SumObserver is a metric that captures a precomputed sum of
|
||||
// float64 values at a point in time.
|
||||
type Float64SumObserver struct {
|
||||
asyncInstrument
|
||||
}
|
||||
|
||||
// Int64UpDownSumObserver is a metric that captures a precomputed sum of
|
||||
// int64 values at a point in time.
|
||||
type Int64UpDownSumObserver struct {
|
||||
asyncInstrument
|
||||
}
|
||||
|
||||
// Float64UpDownSumObserver is a metric that captures a precomputed sum of
|
||||
// float64 values at a point in time.
|
||||
type Float64UpDownSumObserver struct {
|
||||
asyncInstrument
|
||||
}
|
||||
|
||||
@@ -37,7 +61,7 @@ type Float64Observer struct {
|
||||
// argument, for an asynchronous integer instrument.
|
||||
// This returns an implementation-level object for use by the SDK,
|
||||
// users should not refer to this.
|
||||
func (i Int64Observer) Observation(v int64) Observation {
|
||||
func (i Int64ValueObserver) Observation(v int64) Observation {
|
||||
return Observation{
|
||||
number: NewInt64Number(v),
|
||||
instrument: i.instrument,
|
||||
@@ -48,7 +72,51 @@ func (i Int64Observer) Observation(v int64) Observation {
|
||||
// argument, for an asynchronous integer instrument.
|
||||
// This returns an implementation-level object for use by the SDK,
|
||||
// users should not refer to this.
|
||||
func (f Float64Observer) Observation(v float64) Observation {
|
||||
func (f Float64ValueObserver) Observation(v float64) Observation {
|
||||
return Observation{
|
||||
number: NewFloat64Number(v),
|
||||
instrument: f.instrument,
|
||||
}
|
||||
}
|
||||
|
||||
// Observation returns an Observation, a BatchObserverCallback
|
||||
// argument, for an asynchronous integer instrument.
|
||||
// This returns an implementation-level object for use by the SDK,
|
||||
// users should not refer to this.
|
||||
func (i Int64SumObserver) Observation(v int64) Observation {
|
||||
return Observation{
|
||||
number: NewInt64Number(v),
|
||||
instrument: i.instrument,
|
||||
}
|
||||
}
|
||||
|
||||
// Observation returns an Observation, a BatchObserverCallback
|
||||
// argument, for an asynchronous integer instrument.
|
||||
// This returns an implementation-level object for use by the SDK,
|
||||
// users should not refer to this.
|
||||
func (f Float64SumObserver) Observation(v float64) Observation {
|
||||
return Observation{
|
||||
number: NewFloat64Number(v),
|
||||
instrument: f.instrument,
|
||||
}
|
||||
}
|
||||
|
||||
// Observation returns an Observation, a BatchObserverCallback
|
||||
// argument, for an asynchronous integer instrument.
|
||||
// This returns an implementation-level object for use by the SDK,
|
||||
// users should not refer to this.
|
||||
func (i Int64UpDownSumObserver) Observation(v int64) Observation {
|
||||
return Observation{
|
||||
number: NewInt64Number(v),
|
||||
instrument: i.instrument,
|
||||
}
|
||||
}
|
||||
|
||||
// Observation returns an Observation, a BatchObserverCallback
|
||||
// argument, for an asynchronous integer instrument.
|
||||
// This returns an implementation-level object for use by the SDK,
|
||||
// users should not refer to this.
|
||||
func (f Float64UpDownSumObserver) Observation(v float64) Observation {
|
||||
return Observation{
|
||||
number: NewFloat64Number(v),
|
||||
instrument: f.instrument,
|
||||
|
||||
@@ -23,6 +23,13 @@ import (
|
||||
"go.opentelemetry.io/otel/api/metric"
|
||||
)
|
||||
|
||||
// Provider is a standard metric.Provider for wrapping `MeterImpl`
|
||||
type Provider struct {
|
||||
impl metric.MeterImpl
|
||||
}
|
||||
|
||||
var _ metric.Provider = (*Provider)(nil)
|
||||
|
||||
// uniqueInstrumentMeterImpl implements the metric.MeterImpl interface, adding
|
||||
// uniqueness checking for instrument descriptors. Use NewUniqueInstrumentMeter
|
||||
// to wrap an implementation with uniqueness checking.
|
||||
@@ -39,6 +46,19 @@ type key struct {
|
||||
libraryName string
|
||||
}
|
||||
|
||||
// NewProvider returns a new provider that implements instrument
|
||||
// name-uniqueness checking.
|
||||
func NewProvider(impl metric.MeterImpl) *Provider {
|
||||
return &Provider{
|
||||
impl: NewUniqueInstrumentMeterImpl(impl),
|
||||
}
|
||||
}
|
||||
|
||||
// Meter implements metric.Provider.
|
||||
func (p *Provider) Meter(name string) metric.Meter {
|
||||
return metric.WrapMeterImpl(p.impl, name)
|
||||
}
|
||||
|
||||
// ErrMetricKindMismatch is the standard error for mismatched metric
|
||||
// instrument definitions.
|
||||
var ErrMetricKindMismatch = fmt.Errorf(
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package registry_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
@@ -37,17 +38,17 @@ var (
|
||||
"counter.float64": func(m metric.Meter, name string) (metric.InstrumentImpl, error) {
|
||||
return unwrap(m.NewFloat64Counter(name))
|
||||
},
|
||||
"measure.int64": func(m metric.Meter, name string) (metric.InstrumentImpl, error) {
|
||||
return unwrap(m.NewInt64Measure(name))
|
||||
"valuerecorder.int64": func(m metric.Meter, name string) (metric.InstrumentImpl, error) {
|
||||
return unwrap(m.NewInt64ValueRecorder(name))
|
||||
},
|
||||
"measure.float64": func(m metric.Meter, name string) (metric.InstrumentImpl, error) {
|
||||
return unwrap(m.NewFloat64Measure(name))
|
||||
"valuerecorder.float64": func(m metric.Meter, name string) (metric.InstrumentImpl, error) {
|
||||
return unwrap(m.NewFloat64ValueRecorder(name))
|
||||
},
|
||||
"observer.int64": func(m metric.Meter, name string) (metric.InstrumentImpl, error) {
|
||||
return unwrap(m.RegisterInt64Observer(name, func(metric.Int64ObserverResult) {}))
|
||||
"valueobserver.int64": func(m metric.Meter, name string) (metric.InstrumentImpl, error) {
|
||||
return unwrap(m.NewInt64ValueObserver(name, func(context.Context, metric.Int64ObserverResult) {}))
|
||||
},
|
||||
"observer.float64": func(m metric.Meter, name string) (metric.InstrumentImpl, error) {
|
||||
return unwrap(m.RegisterFloat64Observer(name, func(metric.Float64ObserverResult) {}))
|
||||
"valueobserver.float64": func(m metric.Meter, name string) (metric.InstrumentImpl, error) {
|
||||
return unwrap(m.NewFloat64ValueObserver(name, func(context.Context, metric.Float64ObserverResult) {}))
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -118,3 +119,14 @@ func TestRegistryDiffInstruments(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider(t *testing.T) {
|
||||
impl, _ := mockTest.NewMeter()
|
||||
p := registry.NewProvider(impl)
|
||||
m1 := p.Meter("m1")
|
||||
m1p := p.Meter("m1")
|
||||
m2 := p.Meter("m2")
|
||||
|
||||
require.Equal(t, m1, m1p)
|
||||
require.NotEqual(t, m1, m2)
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ type InstrumentImpl interface {
|
||||
}
|
||||
|
||||
// SyncImpl is the implementation-level interface to a generic
|
||||
// synchronous instrument (e.g., Measure and Counter instruments).
|
||||
// synchronous instrument (e.g., ValueRecorder and Counter instruments).
|
||||
type SyncImpl interface {
|
||||
InstrumentImpl
|
||||
|
||||
|
||||
+16
-34
@@ -156,56 +156,38 @@ func newMeasurement(instrument SyncImpl, number Number) Measurement {
|
||||
}
|
||||
}
|
||||
|
||||
// wrapInt64CounterInstrument returns an `Int64Counter` from a
|
||||
// `SyncImpl`. An error will be generated if the
|
||||
// `SyncImpl` is nil (in which case a No-op is substituted),
|
||||
// otherwise the error passes through.
|
||||
// wrapInt64CounterInstrument converts a SyncImpl into Int64Counter.
|
||||
func wrapInt64CounterInstrument(syncInst SyncImpl, err error) (Int64Counter, error) {
|
||||
common, err := checkNewSync(syncInst, err)
|
||||
return Int64Counter{syncInstrument: common}, err
|
||||
}
|
||||
|
||||
// wrapFloat64CounterInstrument returns an `Float64Counter` from a
|
||||
// `SyncImpl`. An error will be generated if the
|
||||
// `SyncImpl` is nil (in which case a No-op is substituted),
|
||||
// otherwise the error passes through.
|
||||
// wrapFloat64CounterInstrument converts a SyncImpl into Float64Counter.
|
||||
func wrapFloat64CounterInstrument(syncInst SyncImpl, err error) (Float64Counter, error) {
|
||||
common, err := checkNewSync(syncInst, err)
|
||||
return Float64Counter{syncInstrument: common}, err
|
||||
}
|
||||
|
||||
// wrapInt64MeasureInstrument returns an `Int64Measure` from a
|
||||
// `SyncImpl`. An error will be generated if the
|
||||
// `SyncImpl` is nil (in which case a No-op is substituted),
|
||||
// otherwise the error passes through.
|
||||
func wrapInt64MeasureInstrument(syncInst SyncImpl, err error) (Int64Measure, error) {
|
||||
// wrapInt64UpDownCounterInstrument converts a SyncImpl into Int64UpDownCounter.
|
||||
func wrapInt64UpDownCounterInstrument(syncInst SyncImpl, err error) (Int64UpDownCounter, error) {
|
||||
common, err := checkNewSync(syncInst, err)
|
||||
return Int64Measure{syncInstrument: common}, err
|
||||
return Int64UpDownCounter{syncInstrument: common}, err
|
||||
}
|
||||
|
||||
// wrapFloat64MeasureInstrument returns an `Float64Measure` from a
|
||||
// `SyncImpl`. An error will be generated if the
|
||||
// `SyncImpl` is nil (in which case a No-op is substituted),
|
||||
// otherwise the error passes through.
|
||||
func wrapFloat64MeasureInstrument(syncInst SyncImpl, err error) (Float64Measure, error) {
|
||||
// wrapFloat64UpDownCounterInstrument converts a SyncImpl into Float64UpDownCounter.
|
||||
func wrapFloat64UpDownCounterInstrument(syncInst SyncImpl, err error) (Float64UpDownCounter, error) {
|
||||
common, err := checkNewSync(syncInst, err)
|
||||
return Float64Measure{syncInstrument: common}, err
|
||||
return Float64UpDownCounter{syncInstrument: common}, err
|
||||
}
|
||||
|
||||
// wrapInt64ObserverInstrument returns an `Int64Observer` from a
|
||||
// `AsyncImpl`. An error will be generated if the
|
||||
// `AsyncImpl` is nil (in which case a No-op is substituted),
|
||||
// otherwise the error passes through.
|
||||
func wrapInt64ObserverInstrument(asyncInst AsyncImpl, err error) (Int64Observer, error) {
|
||||
common, err := checkNewAsync(asyncInst, err)
|
||||
return Int64Observer{asyncInstrument: common}, err
|
||||
// wrapInt64ValueRecorderInstrument converts a SyncImpl into Int64ValueRecorder.
|
||||
func wrapInt64ValueRecorderInstrument(syncInst SyncImpl, err error) (Int64ValueRecorder, error) {
|
||||
common, err := checkNewSync(syncInst, err)
|
||||
return Int64ValueRecorder{syncInstrument: common}, err
|
||||
}
|
||||
|
||||
// wrapFloat64ObserverInstrument returns an `Float64Observer` from a
|
||||
// `AsyncImpl`. An error will be generated if the
|
||||
// `AsyncImpl` is nil (in which case a No-op is substituted),
|
||||
// otherwise the error passes through.
|
||||
func wrapFloat64ObserverInstrument(asyncInst AsyncImpl, err error) (Float64Observer, error) {
|
||||
common, err := checkNewAsync(asyncInst, err)
|
||||
return Float64Observer{asyncInstrument: common}, err
|
||||
// wrapFloat64ValueRecorderInstrument converts a SyncImpl into Float64ValueRecorder.
|
||||
func wrapFloat64ValueRecorderInstrument(syncInst SyncImpl, err error) (Float64ValueRecorder, error) {
|
||||
common, err := checkNewSync(syncInst, err)
|
||||
return Float64ValueRecorder{syncInstrument: common}, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package metric
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
)
|
||||
|
||||
// Float64UpDownCounter is a metric instrument that sums floating
|
||||
// point values.
|
||||
type Float64UpDownCounter struct {
|
||||
syncInstrument
|
||||
}
|
||||
|
||||
// Int64UpDownCounter is a metric instrument that sums integer values.
|
||||
type Int64UpDownCounter struct {
|
||||
syncInstrument
|
||||
}
|
||||
|
||||
// BoundFloat64UpDownCounter is a bound instrument for Float64UpDownCounter.
|
||||
//
|
||||
// It inherits the Unbind function from syncBoundInstrument.
|
||||
type BoundFloat64UpDownCounter struct {
|
||||
syncBoundInstrument
|
||||
}
|
||||
|
||||
// BoundInt64UpDownCounter is a boundInstrument for Int64UpDownCounter.
|
||||
//
|
||||
// It inherits the Unbind function from syncBoundInstrument.
|
||||
type BoundInt64UpDownCounter struct {
|
||||
syncBoundInstrument
|
||||
}
|
||||
|
||||
// Bind creates a bound instrument for this counter. The labels are
|
||||
// associated with values recorded via subsequent calls to Record.
|
||||
func (c Float64UpDownCounter) Bind(labels ...kv.KeyValue) (h BoundFloat64UpDownCounter) {
|
||||
h.syncBoundInstrument = c.bind(labels)
|
||||
return
|
||||
}
|
||||
|
||||
// Bind creates a bound instrument for this counter. The labels are
|
||||
// associated with values recorded via subsequent calls to Record.
|
||||
func (c Int64UpDownCounter) Bind(labels ...kv.KeyValue) (h BoundInt64UpDownCounter) {
|
||||
h.syncBoundInstrument = c.bind(labels)
|
||||
return
|
||||
}
|
||||
|
||||
// Measurement creates a Measurement object to use with batch
|
||||
// recording.
|
||||
func (c Float64UpDownCounter) Measurement(value float64) Measurement {
|
||||
return c.float64Measurement(value)
|
||||
}
|
||||
|
||||
// Measurement creates a Measurement object to use with batch
|
||||
// recording.
|
||||
func (c Int64UpDownCounter) 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 to be associated with this value.
|
||||
func (c Float64UpDownCounter) Add(ctx context.Context, value float64, labels ...kv.KeyValue) {
|
||||
c.directRecord(ctx, NewFloat64Number(value), labels)
|
||||
}
|
||||
|
||||
// Add adds the value to the counter's sum. The labels should contain
|
||||
// the keys and values to be associated with this value.
|
||||
func (c Int64UpDownCounter) Add(ctx context.Context, value int64, labels ...kv.KeyValue) {
|
||||
c.directRecord(ctx, NewInt64Number(value), labels)
|
||||
}
|
||||
|
||||
// Add adds the value to the counter's sum using the labels
|
||||
// previously bound to this counter via Bind()
|
||||
func (b BoundFloat64UpDownCounter) Add(ctx context.Context, value float64) {
|
||||
b.directRecord(ctx, NewFloat64Number(value))
|
||||
}
|
||||
|
||||
// Add adds the value to the counter's sum using the labels
|
||||
// previously bound to this counter via Bind()
|
||||
func (b BoundInt64UpDownCounter) Add(ctx context.Context, value int64) {
|
||||
b.directRecord(ctx, NewInt64Number(value))
|
||||
}
|
||||
@@ -20,78 +20,78 @@ import (
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
)
|
||||
|
||||
// Float64Measure is a metric that records float64 values.
|
||||
type Float64Measure struct {
|
||||
// Float64ValueRecorder is a metric that records float64 values.
|
||||
type Float64ValueRecorder struct {
|
||||
syncInstrument
|
||||
}
|
||||
|
||||
// Int64Measure is a metric that records int64 values.
|
||||
type Int64Measure struct {
|
||||
// Int64ValueRecorder is a metric that records int64 values.
|
||||
type Int64ValueRecorder struct {
|
||||
syncInstrument
|
||||
}
|
||||
|
||||
// BoundFloat64Measure is a bound instrument for Float64Measure.
|
||||
// BoundFloat64ValueRecorder is a bound instrument for Float64ValueRecorder.
|
||||
//
|
||||
// It inherits the Unbind function from syncBoundInstrument.
|
||||
type BoundFloat64Measure struct {
|
||||
type BoundFloat64ValueRecorder struct {
|
||||
syncBoundInstrument
|
||||
}
|
||||
|
||||
// BoundInt64Measure is a bound instrument for Int64Measure.
|
||||
// BoundInt64ValueRecorder is a bound instrument for Int64ValueRecorder.
|
||||
//
|
||||
// It inherits the Unbind function from syncBoundInstrument.
|
||||
type BoundInt64Measure struct {
|
||||
type BoundInt64ValueRecorder struct {
|
||||
syncBoundInstrument
|
||||
}
|
||||
|
||||
// Bind creates a bound instrument for this measure. The labels are
|
||||
// Bind creates a bound instrument for this ValueRecorder. The labels are
|
||||
// associated with values recorded via subsequent calls to Record.
|
||||
func (c Float64Measure) Bind(labels ...kv.KeyValue) (h BoundFloat64Measure) {
|
||||
func (c Float64ValueRecorder) Bind(labels ...kv.KeyValue) (h BoundFloat64ValueRecorder) {
|
||||
h.syncBoundInstrument = c.bind(labels)
|
||||
return
|
||||
}
|
||||
|
||||
// Bind creates a bound instrument for this measure. The labels are
|
||||
// Bind creates a bound instrument for this ValueRecorder. The labels are
|
||||
// associated with values recorded via subsequent calls to Record.
|
||||
func (c Int64Measure) Bind(labels ...kv.KeyValue) (h BoundInt64Measure) {
|
||||
func (c Int64ValueRecorder) Bind(labels ...kv.KeyValue) (h BoundInt64ValueRecorder) {
|
||||
h.syncBoundInstrument = c.bind(labels)
|
||||
return
|
||||
}
|
||||
|
||||
// Measurement creates a Measurement object to use with batch
|
||||
// recording.
|
||||
func (c Float64Measure) Measurement(value float64) Measurement {
|
||||
func (c Float64ValueRecorder) 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 {
|
||||
func (c Int64ValueRecorder) Measurement(value int64) Measurement {
|
||||
return c.int64Measurement(value)
|
||||
}
|
||||
|
||||
// Record adds a new value to the list of measure's records. The
|
||||
// Record adds a new value to the list of ValueRecorder's records. The
|
||||
// labels should contain the keys and values to be associated with
|
||||
// this value.
|
||||
func (c Float64Measure) Record(ctx context.Context, value float64, labels ...kv.KeyValue) {
|
||||
func (c Float64ValueRecorder) Record(ctx context.Context, value float64, labels ...kv.KeyValue) {
|
||||
c.directRecord(ctx, NewFloat64Number(value), labels)
|
||||
}
|
||||
|
||||
// Record adds a new value to the list of measure's records. The
|
||||
// Record adds a new value to the ValueRecorder's distribution. The
|
||||
// labels should contain the keys and values to be associated with
|
||||
// this value.
|
||||
func (c Int64Measure) Record(ctx context.Context, value int64, labels ...kv.KeyValue) {
|
||||
func (c Int64ValueRecorder) Record(ctx context.Context, value int64, labels ...kv.KeyValue) {
|
||||
c.directRecord(ctx, NewInt64Number(value), labels)
|
||||
}
|
||||
|
||||
// Record adds a new value to the list of measure's records using the labels
|
||||
// previously bound to the measure via Bind()
|
||||
func (b BoundFloat64Measure) Record(ctx context.Context, value float64) {
|
||||
// Record adds a new value to the ValueRecorder's distribution using the labels
|
||||
// previously bound to the ValueRecorder via Bind().
|
||||
func (b BoundFloat64ValueRecorder) Record(ctx context.Context, value float64) {
|
||||
b.directRecord(ctx, NewFloat64Number(value))
|
||||
}
|
||||
|
||||
// Record adds a new value to the list of measure's records using the labels
|
||||
// previously bound to the measure via Bind()
|
||||
func (b BoundInt64Measure) Record(ctx context.Context, value int64) {
|
||||
// Record adds a new value to the ValueRecorder's distribution using the labels
|
||||
// previously bound to the ValueRecorder via Bind().
|
||||
func (b BoundInt64ValueRecorder) Record(ctx context.Context, value int64) {
|
||||
b.directRecord(ctx, NewInt64Number(value))
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package standard contains keys and values that have been standardized for
|
||||
// use in OpenTelemetry. These standardizations are specified in the
|
||||
// OpenTelemetry specification:
|
||||
//
|
||||
// - https://github.com/open-telemetry/opentelemetry-specification/tree/v0.4.0/specification/resource/semantic_conventions
|
||||
// - https://github.com/open-telemetry/opentelemetry-specification/tree/v0.4.0/specification/trace/semantic_conventions
|
||||
// - https://github.com/open-telemetry/opentelemetry-specification/tree/v0.4.0/specification/metrics/semantic_conventions
|
||||
package standard
|
||||
@@ -0,0 +1,303 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package standard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
)
|
||||
|
||||
// NetAttributesFromHTTPRequest generates attributes of the net
|
||||
// namespace as specified by the OpenTelemetry specification for a
|
||||
// span. The network parameter is a string that net.Dial function
|
||||
// from standard library can understand.
|
||||
func NetAttributesFromHTTPRequest(network string, request *http.Request) []kv.KeyValue {
|
||||
attrs := []kv.KeyValue{}
|
||||
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
attrs = append(attrs, NetTransportTCP)
|
||||
case "udp", "udp4", "udp6":
|
||||
attrs = append(attrs, NetTransportUDP)
|
||||
case "ip", "ip4", "ip6":
|
||||
attrs = append(attrs, NetTransportIP)
|
||||
case "unix", "unixgram", "unixpacket":
|
||||
attrs = append(attrs, NetTransportUnix)
|
||||
default:
|
||||
attrs = append(attrs, NetTransportOther)
|
||||
}
|
||||
|
||||
peerName, peerIP, peerPort := "", "", 0
|
||||
{
|
||||
hostPart := request.RemoteAddr
|
||||
portPart := ""
|
||||
if idx := strings.LastIndex(hostPart, ":"); idx >= 0 {
|
||||
hostPart = request.RemoteAddr[:idx]
|
||||
portPart = request.RemoteAddr[idx+1:]
|
||||
}
|
||||
if hostPart != "" {
|
||||
if ip := net.ParseIP(hostPart); ip != nil {
|
||||
peerIP = ip.String()
|
||||
} else {
|
||||
peerName = hostPart
|
||||
}
|
||||
|
||||
if portPart != "" {
|
||||
numPort, err := strconv.ParseUint(portPart, 10, 16)
|
||||
if err == nil {
|
||||
peerPort = (int)(numPort)
|
||||
} else {
|
||||
peerName, peerIP = "", ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if peerName != "" {
|
||||
attrs = append(attrs, NetPeerNameKey.String(peerName))
|
||||
}
|
||||
if peerIP != "" {
|
||||
attrs = append(attrs, NetPeerIPKey.String(peerIP))
|
||||
}
|
||||
if peerPort != 0 {
|
||||
attrs = append(attrs, NetPeerPortKey.Int(peerPort))
|
||||
}
|
||||
|
||||
hostIP, hostName, hostPort := "", "", 0
|
||||
for _, someHost := range []string{request.Host, request.Header.Get("Host"), request.URL.Host} {
|
||||
hostPart := ""
|
||||
if idx := strings.LastIndex(someHost, ":"); idx >= 0 {
|
||||
strPort := someHost[idx+1:]
|
||||
numPort, err := strconv.ParseUint(strPort, 10, 16)
|
||||
if err == nil {
|
||||
hostPort = (int)(numPort)
|
||||
}
|
||||
hostPart = someHost[:idx]
|
||||
} else {
|
||||
hostPart = someHost
|
||||
}
|
||||
if hostPart != "" {
|
||||
ip := net.ParseIP(hostPart)
|
||||
if ip != nil {
|
||||
hostIP = ip.String()
|
||||
} else {
|
||||
hostName = hostPart
|
||||
}
|
||||
break
|
||||
} else {
|
||||
hostPort = 0
|
||||
}
|
||||
}
|
||||
if hostIP != "" {
|
||||
attrs = append(attrs, NetHostIPKey.String(hostIP))
|
||||
}
|
||||
if hostName != "" {
|
||||
attrs = append(attrs, NetHostNameKey.String(hostName))
|
||||
}
|
||||
if hostPort != 0 {
|
||||
attrs = append(attrs, NetHostPortKey.Int(hostPort))
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
// EndUserAttributesFromHTTPRequest generates attributes of the
|
||||
// enduser namespace as specified by the OpenTelemetry specification
|
||||
// for a span.
|
||||
func EndUserAttributesFromHTTPRequest(request *http.Request) []kv.KeyValue {
|
||||
if username, _, ok := request.BasicAuth(); ok {
|
||||
return []kv.KeyValue{EnduserIDKey.String(username)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HTTPClientAttributesFromHTTPRequest generates attributes of the
|
||||
// http namespace as specified by the OpenTelemetry specification for
|
||||
// a span on the client side.
|
||||
func HTTPClientAttributesFromHTTPRequest(request *http.Request) []kv.KeyValue {
|
||||
attrs := []kv.KeyValue{}
|
||||
|
||||
if request.Method != "" {
|
||||
attrs = append(attrs, HTTPMethodKey.String(request.Method))
|
||||
} else {
|
||||
attrs = append(attrs, HTTPMethodKey.String(http.MethodGet))
|
||||
}
|
||||
|
||||
attrs = append(attrs, HTTPUrlKey.String(request.URL.String()))
|
||||
|
||||
return append(attrs, httpCommonAttributesFromHTTPRequest(request)...)
|
||||
}
|
||||
|
||||
func httpCommonAttributesFromHTTPRequest(request *http.Request) []kv.KeyValue {
|
||||
attrs := []kv.KeyValue{}
|
||||
|
||||
if request.TLS != nil {
|
||||
attrs = append(attrs, HTTPSchemeHTTPS)
|
||||
} else {
|
||||
attrs = append(attrs, HTTPSchemeHTTP)
|
||||
}
|
||||
|
||||
if request.Host != "" {
|
||||
attrs = append(attrs, HTTPHostKey.String(request.Host))
|
||||
}
|
||||
|
||||
if ua := request.UserAgent(); ua != "" {
|
||||
attrs = append(attrs, HTTPUserAgentKey.String(ua))
|
||||
}
|
||||
|
||||
flavor := ""
|
||||
if request.ProtoMajor == 1 {
|
||||
flavor = fmt.Sprintf("1.%d", request.ProtoMinor)
|
||||
} else if request.ProtoMajor == 2 {
|
||||
flavor = "2"
|
||||
}
|
||||
if flavor != "" {
|
||||
attrs = append(attrs, HTTPFlavorKey.String(flavor))
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
// HTTPServerAttributesFromHTTPRequest generates attributes of the
|
||||
// http namespace as specified by the OpenTelemetry specification for
|
||||
// a span on the server side. Currently, only basic authentication is
|
||||
// supported.
|
||||
func HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []kv.KeyValue {
|
||||
attrs := []kv.KeyValue{
|
||||
HTTPMethodKey.String(request.Method),
|
||||
HTTPTargetKey.String(request.RequestURI),
|
||||
}
|
||||
|
||||
if serverName != "" {
|
||||
attrs = append(attrs, HTTPServerNameKey.String(serverName))
|
||||
}
|
||||
if route != "" {
|
||||
attrs = append(attrs, HTTPRouteKey.String(route))
|
||||
}
|
||||
if values, ok := request.Header["X-Forwarded-For"]; ok && len(values) > 0 {
|
||||
attrs = append(attrs, HTTPClientIPKey.String(values[0]))
|
||||
}
|
||||
|
||||
return append(attrs, httpCommonAttributesFromHTTPRequest(request)...)
|
||||
}
|
||||
|
||||
// HTTPAttributesFromHTTPStatusCode generates attributes of the http
|
||||
// namespace as specified by the OpenTelemetry specification for a
|
||||
// span.
|
||||
func HTTPAttributesFromHTTPStatusCode(code int) []kv.KeyValue {
|
||||
attrs := []kv.KeyValue{
|
||||
HTTPStatusCodeKey.Int(code),
|
||||
}
|
||||
text := http.StatusText(code)
|
||||
if text != "" {
|
||||
attrs = append(attrs, HTTPStatusTextKey.String(text))
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
type codeRange struct {
|
||||
fromInclusive int
|
||||
toInclusive int
|
||||
}
|
||||
|
||||
func (r codeRange) contains(code int) bool {
|
||||
return r.fromInclusive <= code && code <= r.toInclusive
|
||||
}
|
||||
|
||||
var validRangesPerCategory = map[int][]codeRange{
|
||||
1: {
|
||||
{http.StatusContinue, http.StatusEarlyHints},
|
||||
},
|
||||
2: {
|
||||
{http.StatusOK, http.StatusAlreadyReported},
|
||||
{http.StatusIMUsed, http.StatusIMUsed},
|
||||
},
|
||||
3: {
|
||||
{http.StatusMultipleChoices, http.StatusUseProxy},
|
||||
{http.StatusTemporaryRedirect, http.StatusPermanentRedirect},
|
||||
},
|
||||
4: {
|
||||
{http.StatusBadRequest, http.StatusTeapot}, // yes, teapot is so useful…
|
||||
{http.StatusMisdirectedRequest, http.StatusUpgradeRequired},
|
||||
{http.StatusPreconditionRequired, http.StatusTooManyRequests},
|
||||
{http.StatusRequestHeaderFieldsTooLarge, http.StatusRequestHeaderFieldsTooLarge},
|
||||
{http.StatusUnavailableForLegalReasons, http.StatusUnavailableForLegalReasons},
|
||||
},
|
||||
5: {
|
||||
{http.StatusInternalServerError, http.StatusLoopDetected},
|
||||
{http.StatusNotExtended, http.StatusNetworkAuthenticationRequired},
|
||||
},
|
||||
}
|
||||
|
||||
// SpanStatusFromHTTPStatusCode generates a status code and a message
|
||||
// as specified by the OpenTelemetry specification for a span.
|
||||
func SpanStatusFromHTTPStatusCode(code int) (codes.Code, string) {
|
||||
spanCode := func() codes.Code {
|
||||
category := code / 100
|
||||
ranges, ok := validRangesPerCategory[category]
|
||||
if !ok {
|
||||
return codes.Unknown
|
||||
}
|
||||
ok = false
|
||||
for _, crange := range ranges {
|
||||
ok = crange.contains(code)
|
||||
if ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return codes.Unknown
|
||||
}
|
||||
switch code {
|
||||
case http.StatusUnauthorized:
|
||||
return codes.Unauthenticated
|
||||
case http.StatusForbidden:
|
||||
return codes.PermissionDenied
|
||||
case http.StatusNotFound:
|
||||
return codes.NotFound
|
||||
case http.StatusTooManyRequests:
|
||||
return codes.ResourceExhausted
|
||||
case http.StatusNotImplemented:
|
||||
return codes.Unimplemented
|
||||
case http.StatusServiceUnavailable:
|
||||
return codes.Unavailable
|
||||
case http.StatusGatewayTimeout:
|
||||
return codes.DeadlineExceeded
|
||||
}
|
||||
if category > 0 && category < 4 {
|
||||
return codes.OK
|
||||
}
|
||||
if category == 4 {
|
||||
return codes.InvalidArgument
|
||||
}
|
||||
if category == 5 {
|
||||
return codes.Internal
|
||||
}
|
||||
// this really should not happen, if we get there then
|
||||
// it means that the code got out of sync with
|
||||
// validRangesPerCategory map
|
||||
return codes.Unknown
|
||||
}()
|
||||
if spanCode == codes.Unknown {
|
||||
return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code)
|
||||
}
|
||||
return spanCode, fmt.Sprintf("HTTP status code: %d", code)
|
||||
}
|
||||
@@ -0,0 +1,777 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package standard
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
otelkv "go.opentelemetry.io/otel/api/kv"
|
||||
)
|
||||
|
||||
type tlsOption int
|
||||
|
||||
const (
|
||||
noTLS tlsOption = iota
|
||||
withTLS
|
||||
)
|
||||
|
||||
func TestNetAttributesFromHTTPRequest(t *testing.T) {
|
||||
type testcase struct {
|
||||
name string
|
||||
|
||||
network string
|
||||
|
||||
method string
|
||||
requestURI string
|
||||
proto string
|
||||
remoteAddr string
|
||||
host string
|
||||
url *url.URL
|
||||
header http.Header
|
||||
|
||||
expected []otelkv.KeyValue
|
||||
}
|
||||
testcases := []testcase{
|
||||
{
|
||||
name: "stripped, tcp",
|
||||
network: "tcp",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "",
|
||||
host: "",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "IP.TCP"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "stripped, udp",
|
||||
network: "udp",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "",
|
||||
host: "",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "IP.UDP"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "stripped, ip",
|
||||
network: "ip",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "",
|
||||
host: "",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "IP"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "stripped, unix",
|
||||
network: "unix",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "",
|
||||
host: "",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "Unix"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "stripped, other",
|
||||
network: "nih",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "",
|
||||
host: "",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "other"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with remote ip and port",
|
||||
network: "tcp",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "1.2.3.4:56",
|
||||
host: "",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "IP.TCP"),
|
||||
otelkv.String("net.peer.ip", "1.2.3.4"),
|
||||
otelkv.Int("net.peer.port", 56),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with remote name and port",
|
||||
network: "tcp",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "example.com:56",
|
||||
host: "",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "IP.TCP"),
|
||||
otelkv.String("net.peer.name", "example.com"),
|
||||
otelkv.Int("net.peer.port", 56),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with remote ip only",
|
||||
network: "tcp",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "1.2.3.4",
|
||||
host: "",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "IP.TCP"),
|
||||
otelkv.String("net.peer.ip", "1.2.3.4"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with remote name only",
|
||||
network: "tcp",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "example.com",
|
||||
host: "",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "IP.TCP"),
|
||||
otelkv.String("net.peer.name", "example.com"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with remote port only",
|
||||
network: "tcp",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: ":56",
|
||||
host: "",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "IP.TCP"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with host name only",
|
||||
network: "tcp",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "1.2.3.4:56",
|
||||
host: "example.com",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "IP.TCP"),
|
||||
otelkv.String("net.peer.ip", "1.2.3.4"),
|
||||
otelkv.Int("net.peer.port", 56),
|
||||
otelkv.String("net.host.name", "example.com"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with host ip only",
|
||||
network: "tcp",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "1.2.3.4:56",
|
||||
host: "4.3.2.1",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "IP.TCP"),
|
||||
otelkv.String("net.peer.ip", "1.2.3.4"),
|
||||
otelkv.Int("net.peer.port", 56),
|
||||
otelkv.String("net.host.ip", "4.3.2.1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with host name and port",
|
||||
network: "tcp",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "1.2.3.4:56",
|
||||
host: "example.com:78",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "IP.TCP"),
|
||||
otelkv.String("net.peer.ip", "1.2.3.4"),
|
||||
otelkv.Int("net.peer.port", 56),
|
||||
otelkv.String("net.host.name", "example.com"),
|
||||
otelkv.Int("net.host.port", 78),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with host ip and port",
|
||||
network: "tcp",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "1.2.3.4:56",
|
||||
host: "4.3.2.1:78",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "IP.TCP"),
|
||||
otelkv.String("net.peer.ip", "1.2.3.4"),
|
||||
otelkv.Int("net.peer.port", 56),
|
||||
otelkv.String("net.host.ip", "4.3.2.1"),
|
||||
otelkv.Int("net.host.port", 78),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with host name and bogus port",
|
||||
network: "tcp",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "1.2.3.4:56",
|
||||
host: "example.com:qwerty",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "IP.TCP"),
|
||||
otelkv.String("net.peer.ip", "1.2.3.4"),
|
||||
otelkv.Int("net.peer.port", 56),
|
||||
otelkv.String("net.host.name", "example.com"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with host ip and bogus port",
|
||||
network: "tcp",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "1.2.3.4:56",
|
||||
host: "4.3.2.1:qwerty",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "IP.TCP"),
|
||||
otelkv.String("net.peer.ip", "1.2.3.4"),
|
||||
otelkv.Int("net.peer.port", 56),
|
||||
otelkv.String("net.host.ip", "4.3.2.1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with empty host and port",
|
||||
network: "tcp",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "1.2.3.4:56",
|
||||
host: ":80",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "IP.TCP"),
|
||||
otelkv.String("net.peer.ip", "1.2.3.4"),
|
||||
otelkv.Int("net.peer.port", 56),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with host ip and port in headers",
|
||||
network: "tcp",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "1.2.3.4:56",
|
||||
host: "",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: http.Header{
|
||||
"Host": []string{"4.3.2.1:78"},
|
||||
},
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "IP.TCP"),
|
||||
otelkv.String("net.peer.ip", "1.2.3.4"),
|
||||
otelkv.Int("net.peer.port", 56),
|
||||
otelkv.String("net.host.ip", "4.3.2.1"),
|
||||
otelkv.Int("net.host.port", 78),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with host ip and port in url",
|
||||
network: "tcp",
|
||||
method: "GET",
|
||||
requestURI: "http://4.3.2.1:78/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "1.2.3.4:56",
|
||||
host: "",
|
||||
url: &url.URL{
|
||||
Host: "4.3.2.1:78",
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("net.transport", "IP.TCP"),
|
||||
otelkv.String("net.peer.ip", "1.2.3.4"),
|
||||
otelkv.Int("net.peer.port", 56),
|
||||
otelkv.String("net.host.ip", "4.3.2.1"),
|
||||
otelkv.Int("net.host.port", 78),
|
||||
},
|
||||
},
|
||||
}
|
||||
for idx, tc := range testcases {
|
||||
r := testRequest(tc.method, tc.requestURI, tc.proto, tc.remoteAddr, tc.host, tc.url, tc.header, noTLS)
|
||||
got := NetAttributesFromHTTPRequest(tc.network, r)
|
||||
assertElementsMatch(t, tc.expected, got, "testcase %d - %s", idx, tc.name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndUserAttributesFromHTTPRequest(t *testing.T) {
|
||||
r := testRequest("GET", "/user/123", "HTTP/1.1", "", "", nil, http.Header{}, withTLS)
|
||||
var expected []otelkv.KeyValue
|
||||
got := EndUserAttributesFromHTTPRequest(r)
|
||||
assert.ElementsMatch(t, expected, got)
|
||||
r.SetBasicAuth("admin", "password")
|
||||
expected = []otelkv.KeyValue{otelkv.String("enduser.id", "admin")}
|
||||
got = EndUserAttributesFromHTTPRequest(r)
|
||||
assert.ElementsMatch(t, expected, got)
|
||||
}
|
||||
|
||||
func TestHTTPServerAttributesFromHTTPRequest(t *testing.T) {
|
||||
type testcase struct {
|
||||
name string
|
||||
|
||||
serverName string
|
||||
route string
|
||||
|
||||
method string
|
||||
requestURI string
|
||||
proto string
|
||||
remoteAddr string
|
||||
host string
|
||||
url *url.URL
|
||||
header http.Header
|
||||
tls tlsOption
|
||||
|
||||
expected []otelkv.KeyValue
|
||||
}
|
||||
testcases := []testcase{
|
||||
{
|
||||
name: "stripped",
|
||||
serverName: "",
|
||||
route: "",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "",
|
||||
host: "",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
tls: noTLS,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("http.method", "GET"),
|
||||
otelkv.String("http.target", "/user/123"),
|
||||
otelkv.String("http.scheme", "http"),
|
||||
otelkv.String("http.flavor", "1.0"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with server name",
|
||||
serverName: "my-server-name",
|
||||
route: "",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "",
|
||||
host: "",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
tls: noTLS,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("http.method", "GET"),
|
||||
otelkv.String("http.target", "/user/123"),
|
||||
otelkv.String("http.scheme", "http"),
|
||||
otelkv.String("http.flavor", "1.0"),
|
||||
otelkv.String("http.server_name", "my-server-name"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with tls",
|
||||
serverName: "my-server-name",
|
||||
route: "",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "",
|
||||
host: "",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
tls: withTLS,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("http.method", "GET"),
|
||||
otelkv.String("http.target", "/user/123"),
|
||||
otelkv.String("http.scheme", "https"),
|
||||
otelkv.String("http.flavor", "1.0"),
|
||||
otelkv.String("http.server_name", "my-server-name"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with route",
|
||||
serverName: "my-server-name",
|
||||
route: "/user/:id",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "",
|
||||
host: "",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
tls: withTLS,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("http.method", "GET"),
|
||||
otelkv.String("http.target", "/user/123"),
|
||||
otelkv.String("http.scheme", "https"),
|
||||
otelkv.String("http.flavor", "1.0"),
|
||||
otelkv.String("http.server_name", "my-server-name"),
|
||||
otelkv.String("http.route", "/user/:id"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with host",
|
||||
serverName: "my-server-name",
|
||||
route: "/user/:id",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "",
|
||||
host: "example.com",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: nil,
|
||||
tls: withTLS,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("http.method", "GET"),
|
||||
otelkv.String("http.target", "/user/123"),
|
||||
otelkv.String("http.scheme", "https"),
|
||||
otelkv.String("http.flavor", "1.0"),
|
||||
otelkv.String("http.server_name", "my-server-name"),
|
||||
otelkv.String("http.route", "/user/:id"),
|
||||
otelkv.String("http.host", "example.com"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with user agent",
|
||||
serverName: "my-server-name",
|
||||
route: "/user/:id",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "",
|
||||
host: "example.com",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: http.Header{
|
||||
"User-Agent": []string{"foodownloader"},
|
||||
},
|
||||
tls: withTLS,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("http.method", "GET"),
|
||||
otelkv.String("http.target", "/user/123"),
|
||||
otelkv.String("http.scheme", "https"),
|
||||
otelkv.String("http.flavor", "1.0"),
|
||||
otelkv.String("http.server_name", "my-server-name"),
|
||||
otelkv.String("http.route", "/user/:id"),
|
||||
otelkv.String("http.host", "example.com"),
|
||||
otelkv.String("http.user_agent", "foodownloader"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with proxy info",
|
||||
serverName: "my-server-name",
|
||||
route: "/user/:id",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.0",
|
||||
remoteAddr: "",
|
||||
host: "example.com",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: http.Header{
|
||||
"User-Agent": []string{"foodownloader"},
|
||||
"X-Forwarded-For": []string{"1.2.3.4"},
|
||||
},
|
||||
tls: withTLS,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("http.method", "GET"),
|
||||
otelkv.String("http.target", "/user/123"),
|
||||
otelkv.String("http.scheme", "https"),
|
||||
otelkv.String("http.flavor", "1.0"),
|
||||
otelkv.String("http.server_name", "my-server-name"),
|
||||
otelkv.String("http.route", "/user/:id"),
|
||||
otelkv.String("http.host", "example.com"),
|
||||
otelkv.String("http.user_agent", "foodownloader"),
|
||||
otelkv.String("http.client_ip", "1.2.3.4"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with http 1.1",
|
||||
serverName: "my-server-name",
|
||||
route: "/user/:id",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/1.1",
|
||||
remoteAddr: "",
|
||||
host: "example.com",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: http.Header{
|
||||
"User-Agent": []string{"foodownloader"},
|
||||
"X-Forwarded-For": []string{"1.2.3.4"},
|
||||
},
|
||||
tls: withTLS,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("http.method", "GET"),
|
||||
otelkv.String("http.target", "/user/123"),
|
||||
otelkv.String("http.scheme", "https"),
|
||||
otelkv.String("http.flavor", "1.1"),
|
||||
otelkv.String("http.server_name", "my-server-name"),
|
||||
otelkv.String("http.route", "/user/:id"),
|
||||
otelkv.String("http.host", "example.com"),
|
||||
otelkv.String("http.user_agent", "foodownloader"),
|
||||
otelkv.String("http.client_ip", "1.2.3.4"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with http 2",
|
||||
serverName: "my-server-name",
|
||||
route: "/user/:id",
|
||||
method: "GET",
|
||||
requestURI: "/user/123",
|
||||
proto: "HTTP/2.0",
|
||||
remoteAddr: "",
|
||||
host: "example.com",
|
||||
url: &url.URL{
|
||||
Path: "/user/123",
|
||||
},
|
||||
header: http.Header{
|
||||
"User-Agent": []string{"foodownloader"},
|
||||
"X-Forwarded-For": []string{"1.2.3.4"},
|
||||
},
|
||||
tls: withTLS,
|
||||
expected: []otelkv.KeyValue{
|
||||
otelkv.String("http.method", "GET"),
|
||||
otelkv.String("http.target", "/user/123"),
|
||||
otelkv.String("http.scheme", "https"),
|
||||
otelkv.String("http.flavor", "2"),
|
||||
otelkv.String("http.server_name", "my-server-name"),
|
||||
otelkv.String("http.route", "/user/:id"),
|
||||
otelkv.String("http.host", "example.com"),
|
||||
otelkv.String("http.user_agent", "foodownloader"),
|
||||
otelkv.String("http.client_ip", "1.2.3.4"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for idx, tc := range testcases {
|
||||
r := testRequest(tc.method, tc.requestURI, tc.proto, tc.remoteAddr, tc.host, tc.url, tc.header, tc.tls)
|
||||
got := HTTPServerAttributesFromHTTPRequest(tc.serverName, tc.route, r)
|
||||
assertElementsMatch(t, tc.expected, got, "testcase %d - %s", idx, tc.name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPAttributesFromHTTPStatusCode(t *testing.T) {
|
||||
expected := []otelkv.KeyValue{
|
||||
otelkv.Int("http.status_code", 404),
|
||||
otelkv.String("http.status_text", "Not Found"),
|
||||
}
|
||||
got := HTTPAttributesFromHTTPStatusCode(http.StatusNotFound)
|
||||
assertElementsMatch(t, expected, got, "with valid HTTP status code")
|
||||
assert.ElementsMatch(t, expected, got)
|
||||
expected = []otelkv.KeyValue{
|
||||
otelkv.Int("http.status_code", 499),
|
||||
}
|
||||
got = HTTPAttributesFromHTTPStatusCode(499)
|
||||
assertElementsMatch(t, expected, got, "with invalid HTTP status code")
|
||||
}
|
||||
|
||||
func TestSpanStatusFromHTTPStatusCode(t *testing.T) {
|
||||
for code := 0; code < 1000; code++ {
|
||||
expected := getExpectedGRPCCodeForHTTPCode(code)
|
||||
got, _ := SpanStatusFromHTTPStatusCode(code)
|
||||
assert.Equalf(t, expected, got, "%s vs %s", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func getExpectedGRPCCodeForHTTPCode(code int) codes.Code {
|
||||
if http.StatusText(code) == "" {
|
||||
return codes.Unknown
|
||||
}
|
||||
switch code {
|
||||
case http.StatusUnauthorized:
|
||||
return codes.Unauthenticated
|
||||
case http.StatusForbidden:
|
||||
return codes.PermissionDenied
|
||||
case http.StatusNotFound:
|
||||
return codes.NotFound
|
||||
case http.StatusTooManyRequests:
|
||||
return codes.ResourceExhausted
|
||||
case http.StatusNotImplemented:
|
||||
return codes.Unimplemented
|
||||
case http.StatusServiceUnavailable:
|
||||
return codes.Unavailable
|
||||
case http.StatusGatewayTimeout:
|
||||
return codes.DeadlineExceeded
|
||||
}
|
||||
category := code / 100
|
||||
if category < 4 {
|
||||
return codes.OK
|
||||
}
|
||||
if category < 5 {
|
||||
return codes.InvalidArgument
|
||||
}
|
||||
return codes.Internal
|
||||
}
|
||||
|
||||
func assertElementsMatch(t *testing.T, expected, got []otelkv.KeyValue, format string, args ...interface{}) {
|
||||
if !assert.ElementsMatchf(t, expected, got, format, args...) {
|
||||
t.Log("expected:", kvStr(expected))
|
||||
t.Log("got:", kvStr(got))
|
||||
}
|
||||
}
|
||||
|
||||
func testRequest(method, requestURI, proto, remoteAddr, host string, u *url.URL, header http.Header, tlsopt tlsOption) *http.Request {
|
||||
major, minor := protoToInts(proto)
|
||||
var tlsConn *tls.ConnectionState
|
||||
switch tlsopt {
|
||||
case noTLS:
|
||||
case withTLS:
|
||||
tlsConn = &tls.ConnectionState{}
|
||||
}
|
||||
return &http.Request{
|
||||
Method: method,
|
||||
URL: u,
|
||||
Proto: proto,
|
||||
ProtoMajor: major,
|
||||
ProtoMinor: minor,
|
||||
Header: header,
|
||||
Host: host,
|
||||
RemoteAddr: remoteAddr,
|
||||
RequestURI: requestURI,
|
||||
TLS: tlsConn,
|
||||
}
|
||||
}
|
||||
|
||||
func protoToInts(proto string) (int, int) {
|
||||
switch proto {
|
||||
case "HTTP/1.0":
|
||||
return 1, 0
|
||||
case "HTTP/1.1":
|
||||
return 1, 1
|
||||
case "HTTP/2.0":
|
||||
return 2, 0
|
||||
}
|
||||
// invalid proto
|
||||
return 13, 42
|
||||
}
|
||||
|
||||
func kvStr(kvs []otelkv.KeyValue) string {
|
||||
sb := strings.Builder{}
|
||||
sb.WriteRune('[')
|
||||
for idx, kv := range kvs {
|
||||
if idx > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString((string)(kv.Key))
|
||||
sb.WriteString(": ")
|
||||
sb.WriteString(kv.Value.Emit())
|
||||
}
|
||||
sb.WriteRune(']')
|
||||
return sb.String()
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package standard // import "go.opentelemetry.io/otel/api/standard"
|
||||
|
||||
import "go.opentelemetry.io/otel/api/kv"
|
||||
|
||||
// Standard service resource attribute keys.
|
||||
const (
|
||||
// Name of the service.
|
||||
ServiceNameKey = kv.Key("service.name")
|
||||
|
||||
// A namespace for `service.name`. This needs to have meaning that helps
|
||||
// to distinguish a group of services. For example, the team name that
|
||||
// owns a group of services. `service.name` is expected to be unique
|
||||
// within the same namespace.
|
||||
ServiceNamespaceKey = kv.Key("service.namespace")
|
||||
|
||||
// A unique identifier of the service instance. In conjunction with the
|
||||
// `service.name` and `service.namespace` this must be unique.
|
||||
ServiceInstanceIDKey = kv.Key("service.instance.id")
|
||||
|
||||
// The version of the service API.
|
||||
ServiceVersionKey = kv.Key("service.version")
|
||||
)
|
||||
|
||||
// Standard telemetry SDK resource attribute keys.
|
||||
const (
|
||||
// The name of the telemetry SDK.
|
||||
//
|
||||
// The default OpenTelemetry SDK provided by the OpenTelemetry project
|
||||
// MUST set telemetry.sdk.name to the value `opentelemetry`.
|
||||
//
|
||||
// If another SDK is used, this attribute MUST be set to the import path
|
||||
// of that SDK's package.
|
||||
//
|
||||
// The value `opentelemetry` is reserved and MUST NOT be used by
|
||||
// non-OpenTelemetry SDKs.
|
||||
TelemetrySDKNameKey = kv.Key("telemetry.sdk.name")
|
||||
|
||||
// The language of the telemetry SDK.
|
||||
TelemetrySDKLanguageKey = kv.Key("telemetry.sdk.language")
|
||||
|
||||
// The version string of the telemetry SDK.
|
||||
TelemetrySDKVersionKey = kv.Key("telemetry.sdk.version")
|
||||
)
|
||||
|
||||
// Standard telemetry SDK resource attributes.
|
||||
var (
|
||||
TelemetrySDKLanguageGo = TelemetrySDKLanguageKey.String("go")
|
||||
)
|
||||
|
||||
// Standard container resource attribute keys.
|
||||
const (
|
||||
// A uniquely identifying name for the Container.
|
||||
ContainerNameKey = kv.Key("container.name")
|
||||
|
||||
// Name of the image the container was built on.
|
||||
ContainerImageNameKey = kv.Key("container.image.name")
|
||||
|
||||
// Container image tag.
|
||||
ContainerImageTagKey = kv.Key("container.image.tag")
|
||||
)
|
||||
|
||||
// Standard Function-as-a-Service resource attribute keys.
|
||||
const (
|
||||
// A uniquely identifying name for the FaaS.
|
||||
FaaSName = kv.Key("faas.name")
|
||||
|
||||
// The unique name of the function being executed.
|
||||
FaaSID = kv.Key("faas.id")
|
||||
|
||||
// The version of the function being executed.
|
||||
FaaSVersion = kv.Key("faas.version")
|
||||
|
||||
// The execution environment identifier.
|
||||
FaaSInstance = kv.Key("faas.instance")
|
||||
)
|
||||
|
||||
// Standard Kubernetes resource attribute keys.
|
||||
const (
|
||||
// A uniquely identifying name for the Kubernetes cluster. Kubernetes
|
||||
// does not have cluster names as an internal concept so this may be
|
||||
// set to any meaningful value within the environment. For example,
|
||||
// GKE clusters have a name which can be used for this label.
|
||||
K8SClusterNameKey = kv.Key("k8s.cluster.name")
|
||||
|
||||
// The name of the namespace that the pod is running in.
|
||||
K8SNamespaceNameKey = kv.Key("k8s.namespace.name")
|
||||
|
||||
// The name of the pod.
|
||||
K8SPodNameKey = kv.Key("k8s.pod.name")
|
||||
|
||||
// The name of the deployment.
|
||||
K8SDeploymentNameKey = kv.Key("k8s.deployment.name")
|
||||
)
|
||||
|
||||
// Standard host resource attribute keys.
|
||||
const (
|
||||
// A uniquely identifying name for the host.
|
||||
HostNameKey = kv.Key("host.name")
|
||||
|
||||
// A hostname as returned by the 'hostname' command on host machine.
|
||||
HostHostNameKey = kv.Key("host.hostname")
|
||||
|
||||
// Unique host ID. For cloud environments this will be the instance ID.
|
||||
HostIDKey = kv.Key("host.id")
|
||||
|
||||
// Type of host. For cloud environments this will be the machine type.
|
||||
HostTypeKey = kv.Key("host.type")
|
||||
|
||||
// Name of the OS or VM image the host is running.
|
||||
HostImageNameKey = kv.Key("host.image.name")
|
||||
|
||||
// Identifier of the image the host is running.
|
||||
HostImageIDKey = kv.Key("host.image.id")
|
||||
|
||||
// Version of the image the host is running.
|
||||
HostImageVersionKey = kv.Key("host.image.version")
|
||||
)
|
||||
|
||||
// Standard cloud environment resource attribute keys.
|
||||
const (
|
||||
// Name of the cloud provider.
|
||||
CloudProviderKey = kv.Key("cloud.provider")
|
||||
|
||||
// The account ID from the cloud provider used for authorization.
|
||||
CloudAccountIDKey = kv.Key("cloud.account.id")
|
||||
|
||||
// Geographical region where this resource is.
|
||||
CloudRegionKey = kv.Key("cloud.region")
|
||||
|
||||
// Zone of the region where this resource is.
|
||||
CloudZoneKey = kv.Key("cloud.zone")
|
||||
)
|
||||
|
||||
var (
|
||||
CloudProviderAWS = CloudProviderKey.String("aws")
|
||||
CloudProviderAzure = CloudProviderKey.String("azure")
|
||||
CloudProviderGCP = CloudProviderKey.String("gcp")
|
||||
)
|
||||
@@ -0,0 +1,262 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package standard
|
||||
|
||||
import "go.opentelemetry.io/otel/api/kv"
|
||||
|
||||
// Standard attribute keys used for network related operations.
|
||||
const (
|
||||
// Transport protocol used.
|
||||
NetTransportKey = kv.Key("net.transport")
|
||||
|
||||
// Remote address of the peer.
|
||||
NetPeerIPKey = kv.Key("net.peer.ip")
|
||||
|
||||
// Remote port number.
|
||||
NetPeerPortKey = kv.Key("net.peer.port")
|
||||
|
||||
// Remote hostname or similar.
|
||||
NetPeerNameKey = kv.Key("net.peer.name")
|
||||
|
||||
// Local host IP. Useful in case of a multi-IP host.
|
||||
NetHostIPKey = kv.Key("net.host.ip")
|
||||
|
||||
// Local host port.
|
||||
NetHostPortKey = kv.Key("net.host.port")
|
||||
|
||||
// Local hostname or similar.
|
||||
NetHostNameKey = kv.Key("net.host.name")
|
||||
)
|
||||
|
||||
var (
|
||||
NetTransportTCP = NetTransportKey.String("IP.TCP")
|
||||
NetTransportUDP = NetTransportKey.String("IP.UDP")
|
||||
NetTransportIP = NetTransportKey.String("IP")
|
||||
NetTransportUnix = NetTransportKey.String("Unix")
|
||||
NetTransportPipe = NetTransportKey.String("pipe")
|
||||
NetTransportInProc = NetTransportKey.String("inproc")
|
||||
NetTransportOther = NetTransportKey.String("other")
|
||||
)
|
||||
|
||||
// Standard attribute keys used to identify an authorized enduser.
|
||||
const (
|
||||
// Username or the client identifier extracted from the access token or
|
||||
// authorization header in the inbound request from outside the system.
|
||||
EnduserIDKey = kv.Key("enduser.id")
|
||||
|
||||
// Actual or assumed role the client is making the request with.
|
||||
EnduserRoleKey = kv.Key("enduser.role")
|
||||
|
||||
// Scopes or granted authorities the client currently possesses.
|
||||
EnduserScopeKey = kv.Key("enduser.scope")
|
||||
)
|
||||
|
||||
// Standard attribute keys for HTTP.
|
||||
const (
|
||||
// HTTP request method.
|
||||
HTTPMethodKey = kv.Key("http.method")
|
||||
|
||||
// Full HTTP request URL in the form:
|
||||
// scheme://host[:port]/path?query[#fragment].
|
||||
HTTPUrlKey = kv.Key("http.url")
|
||||
|
||||
// The full request target as passed in a HTTP request line or
|
||||
// equivalent, e.g. "/path/12314/?q=ddds#123".
|
||||
HTTPTargetKey = kv.Key("http.target")
|
||||
|
||||
// The value of the HTTP host header.
|
||||
HTTPHostKey = kv.Key("http.host")
|
||||
|
||||
// The URI scheme identifying the used protocol.
|
||||
HTTPSchemeKey = kv.Key("http.scheme")
|
||||
|
||||
// HTTP response status code.
|
||||
HTTPStatusCodeKey = kv.Key("http.status_code")
|
||||
|
||||
// HTTP reason phrase.
|
||||
HTTPStatusTextKey = kv.Key("http.status_text")
|
||||
|
||||
// Kind of HTTP protocol used.
|
||||
HTTPFlavorKey = kv.Key("http.flavor")
|
||||
|
||||
// Value of the HTTP User-Agent header sent by the client.
|
||||
HTTPUserAgentKey = kv.Key("http.user_agent")
|
||||
|
||||
// The primary server name of the matched virtual host.
|
||||
HTTPServerNameKey = kv.Key("http.server_name")
|
||||
|
||||
// The matched route served (path template). For example,
|
||||
// "/users/:userID?".
|
||||
HTTPRouteKey = kv.Key("http.route")
|
||||
|
||||
// The IP address of the original client behind all proxies, if known
|
||||
// (e.g. from X-Forwarded-For).
|
||||
HTTPClientIPKey = kv.Key("http.client_ip")
|
||||
)
|
||||
|
||||
var (
|
||||
HTTPSchemeHTTP = HTTPSchemeKey.String("http")
|
||||
HTTPSchemeHTTPS = HTTPSchemeKey.String("https")
|
||||
|
||||
HTTPFlavor1_0 = HTTPFlavorKey.String("1.0")
|
||||
HTTPFlavor1_1 = HTTPFlavorKey.String("1.1")
|
||||
HTTPFlavor2 = HTTPFlavorKey.String("2")
|
||||
HTTPFlavorSPDY = HTTPFlavorKey.String("SPDY")
|
||||
HTTPFlavorQUIC = HTTPFlavorKey.String("QUIC")
|
||||
)
|
||||
|
||||
// Standard attribute keys for database clients.
|
||||
const (
|
||||
// Database type. For any SQL database, "sql". For others, the
|
||||
// lower-case database category, e.g. "cassandra", "hbase", or "redis".
|
||||
DBTypeKey = kv.Key("db.type")
|
||||
|
||||
// Database instance name.
|
||||
DBInstanceKey = kv.Key("db.instance")
|
||||
|
||||
// A database statement for the given database type.
|
||||
DBStatementKey = kv.Key("db.statement")
|
||||
|
||||
// Username for accessing database.
|
||||
DBUserKey = kv.Key("db.user")
|
||||
|
||||
// Database URL.
|
||||
DBUrlKey = kv.Key("db.url")
|
||||
)
|
||||
|
||||
// Standard attribute keys for RPC.
|
||||
const (
|
||||
// The RPC service name.
|
||||
RPCServiceKey = kv.Key("rpc.service")
|
||||
|
||||
// Name of message transmitted or received.
|
||||
RPCNameKey = kv.Key("name")
|
||||
|
||||
// Type of message transmitted or received.
|
||||
RPCMessageTypeKey = kv.Key("message.type")
|
||||
|
||||
// Identifier of message transmitted or received.
|
||||
RPCMessageIDKey = kv.Key("message.id")
|
||||
|
||||
// The compressed size of the message transmitted or received in bytes.
|
||||
RPCMessageCompressedSizeKey = kv.Key("message.compressed_size")
|
||||
|
||||
// The uncompressed size of the message transmitted or received in
|
||||
// bytes.
|
||||
RPCMessageUncompressedSizeKey = kv.Key("message.uncompressed_size")
|
||||
)
|
||||
|
||||
var (
|
||||
RPCNameMessage = RPCNameKey.String("message")
|
||||
|
||||
RPCMessageTypeSent = RPCMessageTypeKey.String("SENT")
|
||||
RPCMessageTypeReceived = RPCMessageTypeKey.String("RECEIVED")
|
||||
)
|
||||
|
||||
// Standard attribute keys for messaging systems.
|
||||
const (
|
||||
// A unique identifier describing the messaging system. For example,
|
||||
// kafka, rabbitmq or activemq.
|
||||
MessagingSystemKey = kv.Key("messaging.system")
|
||||
|
||||
// The message destination name, e.g. MyQueue or MyTopic.
|
||||
MessagingDestinationKey = kv.Key("messaging.destination")
|
||||
|
||||
// The kind of message destination.
|
||||
MessagingDestinationKindKey = kv.Key("messaging.destination_kind")
|
||||
|
||||
// Describes if the destination is temporary or not.
|
||||
MessagingTempDestinationKey = kv.Key("messaging.temp_destination")
|
||||
|
||||
// The name of the transport protocol.
|
||||
MessagingProtocolKey = kv.Key("messaging.protocol")
|
||||
|
||||
// The version of the transport protocol.
|
||||
MessagingProtocolVersionKey = kv.Key("messaging.protocol_version")
|
||||
|
||||
// Messaging service URL.
|
||||
MessagingURLKey = kv.Key("messaging.url")
|
||||
|
||||
// Identifier used by the messaging system for a message.
|
||||
MessagingMessageIDKey = kv.Key("messaging.message_id")
|
||||
|
||||
// Identifier used by the messaging system for a conversation.
|
||||
MessagingConversationIDKey = kv.Key("messaging.conversation_id")
|
||||
|
||||
// The (uncompressed) size of the message payload in bytes.
|
||||
MessagingMessagePayloadSizeBytesKey = kv.Key("messaging.message_payload_size_bytes")
|
||||
|
||||
// The compressed size of the message payload in bytes.
|
||||
MessagingMessagePayloadCompressedSizeBytesKey = kv.Key("messaging.message_payload_compressed_size_bytes")
|
||||
|
||||
// Identifies which part and kind of message consumption is being
|
||||
// preformed.
|
||||
MessagingOperationKey = kv.Key("messaging.operation")
|
||||
|
||||
// RabbitMQ specific attribute describing the destination routing key.
|
||||
MessagingRabbitMQRoutingKeyKey = kv.Key("messaging.rabbitmq.routing_key")
|
||||
)
|
||||
|
||||
var (
|
||||
MessagingDestinationKindKeyQueue = MessagingDestinationKindKey.String("queue")
|
||||
MessagingDestinationKindKeyTopic = MessagingDestinationKindKey.String("topic")
|
||||
|
||||
MessagingTempDestination = MessagingTempDestinationKey.Bool(true)
|
||||
|
||||
MessagingOperationReceive = MessagingOperationKey.String("receive")
|
||||
MessagingOperationProcess = MessagingOperationKey.String("process")
|
||||
)
|
||||
|
||||
// Standard attribute keys for FaaS systems.
|
||||
const (
|
||||
|
||||
// Type of the trigger on which the function is executed.
|
||||
FaaSTriggerKey = kv.Key("faas.trigger")
|
||||
|
||||
// String containing the execution identifier of the function.
|
||||
FaaSExecutionKey = kv.Key("faas.execution")
|
||||
|
||||
// The name of the source on which the operation was performed.
|
||||
// For example, in Cloud Storage or S3 corresponds to the bucket name,
|
||||
// and in Cosmos DB to the database name.
|
||||
FaaSDocumentCollectionKey = kv.Key("faas.document.collection")
|
||||
|
||||
// The type of the operation that was performed on the data.
|
||||
FaaSDocumentOperationKey = kv.Key("faas.document.operation")
|
||||
|
||||
// A string containing the time when the data was accessed.
|
||||
FaaSDocumentTimeKey = kv.Key("faas.document.time")
|
||||
|
||||
// The document name/table subjected to the operation.
|
||||
FaaSDocumentNameKey = kv.Key("faas.document.name")
|
||||
|
||||
// The function invocation time.
|
||||
FaaSTimeKey = kv.Key("faas.time")
|
||||
|
||||
// The schedule period as Cron Expression.
|
||||
FaaSCronKey = kv.Key("faas.cron")
|
||||
)
|
||||
|
||||
var (
|
||||
FaasTriggerDatasource = FaaSTriggerKey.String("datasource")
|
||||
FaasTriggerHTTP = FaaSTriggerKey.String("http")
|
||||
FaasTriggerPubSub = FaaSTriggerKey.String("pubsub")
|
||||
FaasTriggerTimer = FaaSTriggerKey.String("timer")
|
||||
FaasTriggerOther = FaaSTriggerKey.String("other")
|
||||
|
||||
FaaSDocumentOperationInsert = FaaSDocumentOperationKey.String("insert")
|
||||
FaaSDocumentOperationEdit = FaaSDocumentOperationKey.String("edit")
|
||||
FaaSDocumentOperationDelete = FaaSDocumentOperationKey.String("delete")
|
||||
)
|
||||
@@ -33,7 +33,6 @@ func (ns alwaysOffSampler) ShouldSample(
|
||||
_ SpanContext,
|
||||
_ bool,
|
||||
_ ID,
|
||||
_ SpanID,
|
||||
_ string,
|
||||
_ SpanKind,
|
||||
_ []kv.KeyValue,
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
func TestNeverSamperShouldSample(t *testing.T) {
|
||||
gotD := AlwaysOffSampler().ShouldSample(
|
||||
SpanContext{}, false, ID{}, SpanID{}, "span", SpanKindClient, []kv.KeyValue{}, []Link{})
|
||||
SpanContext{}, false, ID{}, "span", SpanKindClient, []kv.KeyValue{}, []Link{})
|
||||
wantD := Decision{Sampled: false}
|
||||
if diff := cmp.Diff(wantD, gotD); diff != "" {
|
||||
t.Errorf("Decision: +got, -want%v", diff)
|
||||
|
||||
@@ -33,7 +33,6 @@ func (as alwaysOnSampler) ShouldSample(
|
||||
_ SpanContext,
|
||||
_ bool,
|
||||
_ ID,
|
||||
_ SpanID,
|
||||
_ string,
|
||||
_ SpanKind,
|
||||
_ []kv.KeyValue,
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
func TestAlwaysOnSamplerShouldSample(t *testing.T) {
|
||||
gotD := AlwaysOnSampler().ShouldSample(
|
||||
SpanContext{}, false, ID{}, SpanID{}, "span", SpanKindClient, []kv.KeyValue{}, []Link{})
|
||||
SpanContext{}, false, ID{}, "span", SpanKindClient, []kv.KeyValue{}, []Link{})
|
||||
wantD := Decision{Sampled: true}
|
||||
if diff := cmp.Diff(wantD, gotD); diff != "" {
|
||||
t.Errorf("Decision: +got, -want%v", diff)
|
||||
|
||||
@@ -24,7 +24,6 @@ type Sampler interface {
|
||||
sc SpanContext,
|
||||
remote bool,
|
||||
traceID ID,
|
||||
spanID SpanID,
|
||||
spanName string,
|
||||
spanKind SpanKind,
|
||||
attributes []kv.KeyValue,
|
||||
|
||||
@@ -4,4 +4,4 @@ go 1.13
|
||||
|
||||
replace go.opentelemetry.io/otel => ../..
|
||||
|
||||
require go.opentelemetry.io/otel v0.5.0
|
||||
require go.opentelemetry.io/otel v0.6.0
|
||||
|
||||
+10
-9
@@ -26,6 +26,7 @@ import (
|
||||
metricstdout "go.opentelemetry.io/otel/exporters/metric/stdout"
|
||||
tracestdout "go.opentelemetry.io/otel/exporters/trace/stdout"
|
||||
"go.opentelemetry.io/otel/sdk/metric/controller/push"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
@@ -46,7 +47,7 @@ func initTracer() {
|
||||
}
|
||||
tp, err := sdktrace.NewProvider(sdktrace.WithSyncer(exp),
|
||||
sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
|
||||
sdktrace.WithResourceAttributes(kv.String("rk1", "rv11"), kv.Int64("rk2", 5)))
|
||||
sdktrace.WithResource(resource.New(kv.String("rk1", "rv11"), kv.Int64("rk2", 5))))
|
||||
if err != nil {
|
||||
log.Panicf("failed to initialize trace provider %v", err)
|
||||
}
|
||||
@@ -73,14 +74,14 @@ func main() {
|
||||
|
||||
commonLabels := []kv.KeyValue{lemonsKey.Int(10), kv.String("A", "1"), kv.String("B", "2"), kv.String("C", "3")}
|
||||
|
||||
oneMetricCB := func(result metric.Float64ObserverResult) {
|
||||
oneMetricCB := func(_ context.Context, result metric.Float64ObserverResult) {
|
||||
result.Observe(1, commonLabels...)
|
||||
}
|
||||
_ = metric.Must(meter).RegisterFloat64Observer("ex.com.one", oneMetricCB,
|
||||
metric.WithDescription("An observer set to 1.0"),
|
||||
_ = metric.Must(meter).NewFloat64ValueObserver("ex.com.one", oneMetricCB,
|
||||
metric.WithDescription("A ValueObserver set to 1.0"),
|
||||
)
|
||||
|
||||
measureTwo := metric.Must(meter).NewFloat64Measure("ex.com.two")
|
||||
valuerecorderTwo := metric.Must(meter).NewFloat64ValueRecorder("ex.com.two")
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -89,8 +90,8 @@ func main() {
|
||||
barKey.String("bar1"),
|
||||
)
|
||||
|
||||
measure := measureTwo.Bind(commonLabels...)
|
||||
defer measure.Unbind()
|
||||
valuerecorder := valuerecorderTwo.Bind(commonLabels...)
|
||||
defer valuerecorder.Unbind()
|
||||
|
||||
err := tracer.WithSpan(ctx, "operation", func(ctx context.Context) error {
|
||||
|
||||
@@ -103,7 +104,7 @@ func main() {
|
||||
correlation.NewContext(ctx, anotherKey.String("xyz")),
|
||||
commonLabels,
|
||||
|
||||
measureTwo.Measurement(2.0),
|
||||
valuerecorderTwo.Measurement(2.0),
|
||||
)
|
||||
|
||||
return tracer.WithSpan(
|
||||
@@ -114,7 +115,7 @@ func main() {
|
||||
|
||||
trace.SpanFromContext(ctx).AddEvent(ctx, "Sub span event")
|
||||
|
||||
measure.Record(ctx, 1.3)
|
||||
valuerecorder.Record(ctx, 1.3)
|
||||
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/example/grpc/api"
|
||||
"go.opentelemetry.io/otel/example/grpc/config"
|
||||
"go.opentelemetry.io/otel/plugin/grpctrace"
|
||||
"go.opentelemetry.io/otel/instrumentation/grpctrace"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ replace go.opentelemetry.io/otel => ../..
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.3.2
|
||||
go.opentelemetry.io/otel v0.5.0
|
||||
go.opentelemetry.io/otel v0.6.0
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a
|
||||
google.golang.org/grpc v1.27.1
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/example/grpc/api"
|
||||
"go.opentelemetry.io/otel/example/grpc/config"
|
||||
"go.opentelemetry.io/otel/plugin/grpctrace"
|
||||
"go.opentelemetry.io/otel/instrumentation/grpctrace"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
# 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.
|
||||
FROM golang:alpine AS base
|
||||
FROM golang:1.14-alpine AS base
|
||||
COPY . /go/src/github.com/open-telemetry/opentelemetry-go/
|
||||
WORKDIR /go/src/github.com/open-telemetry/opentelemetry-go/example/http/
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
"go.opentelemetry.io/otel/api/correlation"
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/exporters/trace/stdout"
|
||||
"go.opentelemetry.io/otel/plugin/httptrace"
|
||||
"go.opentelemetry.io/otel/instrumentation/httptrace"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -4,4 +4,4 @@ go 1.13
|
||||
|
||||
replace go.opentelemetry.io/otel => ../..
|
||||
|
||||
require go.opentelemetry.io/otel v0.5.0
|
||||
require go.opentelemetry.io/otel v0.6.0
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
"go.opentelemetry.io/otel/exporters/trace/stdout"
|
||||
"go.opentelemetry.io/otel/plugin/httptrace"
|
||||
"go.opentelemetry.io/otel/instrumentation/httptrace"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
|
||||
@@ -8,6 +8,6 @@ replace (
|
||||
)
|
||||
|
||||
require (
|
||||
go.opentelemetry.io/otel v0.5.0
|
||||
go.opentelemetry.io/otel/exporters/trace/jaeger v0.5.0
|
||||
go.opentelemetry.io/otel v0.6.0
|
||||
go.opentelemetry.io/otel/exporters/trace/jaeger v0.6.0
|
||||
)
|
||||
|
||||
@@ -4,4 +4,4 @@ go 1.13
|
||||
|
||||
replace go.opentelemetry.io/otel => ../..
|
||||
|
||||
require go.opentelemetry.io/otel v0.5.0
|
||||
require go.opentelemetry.io/otel v0.6.0
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace-k8s:
|
||||
kubectl apply -f k8s/namespace.yaml
|
||||
|
||||
jaeger-operator-k8s:
|
||||
# Create the jaeger operator and necessary artifacts in ns observability
|
||||
kubectl create -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/crds/jaegertracing.io_jaegers_crd.yaml
|
||||
kubectl create -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/service_account.yaml
|
||||
kubectl create -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/role.yaml
|
||||
kubectl create -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/role_binding.yaml
|
||||
kubectl create -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/operator.yaml
|
||||
|
||||
# Create the cluster role & bindings
|
||||
kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/cluster_role.yaml
|
||||
kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/cluster_role_binding.yaml
|
||||
|
||||
jaeger-k8s:
|
||||
kubectl apply -f k8s/jaeger.yaml
|
||||
|
||||
otel-collector-k8s:
|
||||
kubectl apply -f k8s/otel-collector.yaml
|
||||
|
||||
clean-k8s:
|
||||
- kubectl delete -f k8s/otel-collector.yaml
|
||||
|
||||
- kubectl delete -f k8s/jaeger.yaml
|
||||
|
||||
- kubectl delete -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/cluster_role.yaml
|
||||
- kubectl delete -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/cluster_role_binding.yaml
|
||||
|
||||
- kubectl delete -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/crds/jaegertracing.io_jaegers_crd.yaml
|
||||
- kubectl delete -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/service_account.yaml
|
||||
- kubectl delete -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/role.yaml
|
||||
- kubectl delete -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/role_binding.yaml
|
||||
- kubectl delete -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/operator.yaml
|
||||
@@ -0,0 +1,180 @@
|
||||
# OpenTelemetry Collector Traces Example
|
||||
|
||||
This example illustrates how to export traces from the OpenTelemetry-Go SDK to the OpenTelemetry Collector, and from there to a Jaeger instance.
|
||||
The complete flow is:
|
||||
|
||||
`Demo App -> OpenTelemetry Collector -> Jaeger`
|
||||
|
||||
# Prerequisites
|
||||
|
||||
The demo is built on Kubernetes, and uses a local instance of [microk8s](https://microk8s.io/). You will need access to a cluster in order to deploy the OpenTelemetry Collector and Jaeger components from this demo.
|
||||
|
||||
For simplicity, the demo application is not part of the k8s cluster, and will access the OpenTelemetry Collector through a NodePort on the cluster. Note that the NodePort opened by this demo is not secured.
|
||||
|
||||
Ideally you'd want to either have your application running as part of the kubernetes cluster, or use a secured connection (NodePort/LoadBalancer with TLS or an ingress extension).
|
||||
|
||||
# Deploying Jaeger and OpenTelemetry Collector
|
||||
The first step of this demo is to deploy a Jaeger instance and a Collector to your cluster. All the necessary Kubernetes deployment files are available in this demo, in the [k8s](./k8s) folder.
|
||||
There are two ways to create the necessary deployments for this demo: using the [makefile](./Makefile) or manually applying the k8s files.
|
||||
|
||||
## Using the makefile
|
||||
|
||||
For using the [makefile](./Makefile), run the following commands in order:
|
||||
```bash
|
||||
# Create the namespace
|
||||
make namespace-k8s
|
||||
|
||||
# Deploy Jaeger operator
|
||||
make jaeger-operator-k8s
|
||||
|
||||
# After the operator is deployed, create the Jaeger instance
|
||||
make jaeger-k8s
|
||||
|
||||
# Finally, deploy the OpenTelemetry Collector
|
||||
make otel-collector-k8s
|
||||
```
|
||||
|
||||
If you want to clean up after this, you can use the `make clean-k8s` to delete all the resources created above. Note that this will not remove the namespace. Because Kubernetes sometimes gets stuck when removing namespaces, please remove this namespace manually after all the resources inside have been deleted.
|
||||
|
||||
## Manual deployment
|
||||
|
||||
For manual deployments, follow the same steps as above, but instead run the `kubectl apply` yourself.
|
||||
|
||||
First, the namespace needs to be created:
|
||||
```bash
|
||||
k apply -f k8s/namespace.yaml
|
||||
```
|
||||
|
||||
Jaeger is then deployed via the operator, and the demo follows [these steps](https://github.com/jaegertracing/jaeger-operator#getting-started) to create it:
|
||||
```bash
|
||||
# Create the jaeger operator and necessary artifacts in ns observability
|
||||
kubectl create -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/crds/jaegertracing.io_jaegers_crd.yaml
|
||||
kubectl create -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/service_account.yaml
|
||||
kubectl create -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/role.yaml
|
||||
kubectl create -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/role_binding.yaml
|
||||
kubectl create -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/operator.yaml
|
||||
|
||||
# Create the cluster role & bindings
|
||||
kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/cluster_role.yaml
|
||||
kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/cluster_role_binding.yaml
|
||||
|
||||
# Create the Jaeger instance itself:
|
||||
kubectl apply -f k8s/jaeger/jaeger.yaml
|
||||
```
|
||||
|
||||
The OpenTelemetry Collector is contained in a single k8s file, which can be deployed with one command:
|
||||
```bash
|
||||
k8s apply -f k8s/otel-collector.yaml
|
||||
```
|
||||
|
||||
|
||||
# Configuring the OpenTelemetry Collector
|
||||
|
||||
Although the above steps should deploy and configure both Jaeger and the OpenTelemetry Collector, it might be worth spending some time on the [configuration](./k8s/otel-collector.yaml) of the Collector.
|
||||
|
||||
One important part here is that, in order to enable our application to send traces to the OpenTelemetry Collector, we need to first configure the otlp receiver:
|
||||
|
||||
```yml
|
||||
...
|
||||
otel-collector-config: |
|
||||
receivers:
|
||||
# Make sure to add the otlp receiver.
|
||||
# This will open up the receiver on port 55680.
|
||||
otlp:
|
||||
endpoint: 0.0.0.0:55680
|
||||
processors:
|
||||
...
|
||||
```
|
||||
|
||||
This will create the receiver on the Collector side, and open up port `55680` for receiving traces.
|
||||
|
||||
The rest of the configuration is quite standard, with the only mention that we need to create the Jaeger exporter:
|
||||
|
||||
```yml
|
||||
...
|
||||
exporters:
|
||||
jaeger_grpc:
|
||||
endpoint: "jaeger-collector.observability.svc.cluster.local:14250"
|
||||
...
|
||||
```
|
||||
|
||||
## OpenTelemetry Collector service
|
||||
|
||||
One more aspect in the OpenTelemetry Collector [configuration](./k8s/otel-collector.yaml) worth looking at is the NodePort service used for accessing it:
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
...
|
||||
spec:
|
||||
ports:
|
||||
- name: otlp # Default endpoint for otlp receiver.
|
||||
port: 55680
|
||||
protocol: TCP
|
||||
targetPort: 55680
|
||||
nodePort: 30080
|
||||
- name: metrics # Default endpoint for metrics.
|
||||
port: 8888
|
||||
protocol: TCP
|
||||
targetPort: 8888
|
||||
selector:
|
||||
component: otel-collector
|
||||
type:
|
||||
NodePort
|
||||
```
|
||||
|
||||
This service will bind the `55680` port used to access the otlp receiver to port `30080` on your cluster's node. By doing so, it makes it possible for us to access the Collector by using the static address `<node-ip>:30080`. In case you are running a local cluster, this will be `localhost:30080`. Note that you can also change this to a LoadBalancer or have an ingress extension for accessing the service.
|
||||
|
||||
|
||||
# Writing the demo
|
||||
|
||||
Having the OpenTelemetry Collector started with the otlp port open for traces, and connected to Jaeger, let's look at the go app that will send traces to the Collector.
|
||||
|
||||
First, we need to create an exporter using the [otlp](https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp?tab=doc) package:
|
||||
```go
|
||||
exp, err := otlp.NewExporter(otlp.WithInsecure(),
|
||||
// use the address of the NodePort service created above
|
||||
// <node-ip>:30080
|
||||
otlp.WithAddress("localhost:30080"),
|
||||
otlp.WithGRPCDialOption(grpc.WithBlock()))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create the collector exporter: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
err := exp.Stop()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to stop the exporter: %v", err)
|
||||
}
|
||||
}()
|
||||
```
|
||||
This will initialize the exporter and connect to the otlp receiver at the address that we set for the [NodePort](#opentelemetry-collector-service): `localhost:30080`.
|
||||
|
||||
Feel free to remove the blocking operation, but it might come in handy when testing the connection.
|
||||
Also, make sure to close the exporter before the app exits.
|
||||
|
||||
The next step is to create the TraceProvider:
|
||||
```go
|
||||
tp, err := sdktrace.NewProvider(
|
||||
sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
|
||||
sdktrace.WithResource(resource.New(
|
||||
// the service name used to display traces in Jaeger
|
||||
kv.Key(conventions.AttributeServiceName).String("test-service"),
|
||||
)),
|
||||
sdktrace.WithSyncer(exp))
|
||||
if err != nil {
|
||||
log.Fatalf("error creating trace provider: %v\n", err)
|
||||
}
|
||||
```
|
||||
|
||||
It is important here to set the [AttributeServiceName](https://github.com/open-telemetry/opentelemetry-collector/blob/master/translator/conventions/opentelemetry.go#L20) from the `github.com/open-telemetry/opentelemetry-collector/translator/conventions` package on the resource level. This will be passed to the OpenTelemetry Collector, and used as ServiceName when exporting the traces to Jaeger.
|
||||
|
||||
After this, you can simply start sending traces:
|
||||
```go
|
||||
tracer := tp.Tracer("test-tracer")
|
||||
ctx, span := tracer.Start(context.Background(), "CollectorExporter-Example")
|
||||
defer span.End()
|
||||
```
|
||||
|
||||
The traces should now be visible from the Jaeger UI (if you have it installed), or thorough the jaeger-query service, under the name `test-service`.
|
||||
|
||||
You can find the complete code for this example in the [main.go](./main.go) file.
|
||||
@@ -0,0 +1,15 @@
|
||||
module go.opentelemetry.io/otel/example/otel-collector
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/open-telemetry/opentelemetry-collector v0.3.0
|
||||
go.opentelemetry.io/otel v0.6.0
|
||||
go.opentelemetry.io/otel/exporters/otlp v0.6.0
|
||||
google.golang.org/grpc v1.29.1
|
||||
)
|
||||
|
||||
replace (
|
||||
go.opentelemetry.io/otel => ../..
|
||||
go.opentelemetry.io/otel/exporters/otlp => ../../exporters/otlp
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: jaegertracing.io/v1
|
||||
kind: Jaeger
|
||||
metadata:
|
||||
name: jaeger
|
||||
namespace: observability
|
||||
@@ -0,0 +1,18 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: observability
|
||||
@@ -0,0 +1,144 @@
|
||||
# Copyright The OpenTelemetry Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Configuration based on otel-collector k8s demo:
|
||||
# https://github.com/open-telemetry/opentelemetry-collector/blob/master/examples/k8s.yaml
|
||||
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: otel-collector-conf
|
||||
namespace: observability
|
||||
labels:
|
||||
app: opentelemetry
|
||||
component: otel-collector-conf
|
||||
data:
|
||||
otel-collector-config: |
|
||||
receivers:
|
||||
# Make sure to add the otlp receiver.
|
||||
# This will open up the receiver on port 55680
|
||||
otlp:
|
||||
endpoint: 0.0.0.0:55680
|
||||
processors:
|
||||
extensions:
|
||||
health_check: {}
|
||||
exporters:
|
||||
jaeger_grpc:
|
||||
endpoint: "jaeger-collector.observability.svc.cluster.local:14250"
|
||||
service:
|
||||
extensions: [health_check]
|
||||
pipelines:
|
||||
traces:
|
||||
receivers: [otlp]
|
||||
processors: []
|
||||
exporters: [jaeger_grpc]
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: otel-collector
|
||||
namespace: observability
|
||||
labels:
|
||||
app: opentelemetry
|
||||
component: otel-collector
|
||||
spec:
|
||||
ports:
|
||||
- name: otlp # Default endpoint for otlp receiver.
|
||||
port: 55680
|
||||
protocol: TCP
|
||||
targetPort: 55680
|
||||
nodePort: 30080
|
||||
- name: metrics # Default endpoint for metrics.
|
||||
port: 8888
|
||||
protocol: TCP
|
||||
targetPort: 8888
|
||||
selector:
|
||||
component: otel-collector
|
||||
type:
|
||||
NodePort
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: otel-collector
|
||||
namespace: observability
|
||||
labels:
|
||||
app: opentelemetry
|
||||
component: otel-collector
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: opentelemetry
|
||||
component: otel-collector
|
||||
minReadySeconds: 5
|
||||
progressDeadlineSeconds: 120
|
||||
replicas: 1 #TODO - adjust this to your own requirements
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
prometheus.io/path: "/metrics"
|
||||
prometheus.io/port: "8888"
|
||||
prometheus.io/scrape: "true"
|
||||
labels:
|
||||
app: opentelemetry
|
||||
component: otel-collector
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- "/otelcol"
|
||||
- "--config=/conf/otel-collector-config.yaml"
|
||||
# Memory Ballast size should be max 1/3 to 1/2 of memory.
|
||||
- "--mem-ballast-size-mib=683"
|
||||
env:
|
||||
- name: GOGC
|
||||
value: "80"
|
||||
image: otel/opentelemetry-collector:0.3.0
|
||||
name: otel-collector
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1
|
||||
memory: 2Gi
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 400Mi
|
||||
ports:
|
||||
- containerPort: 55680 # Default endpoint for otlp receiver.
|
||||
- containerPort: 8888 # Default endpoint for querying metrics.
|
||||
volumeMounts:
|
||||
- name: otel-collector-config-vol
|
||||
mountPath: /conf
|
||||
# - name: otel-collector-secrets
|
||||
# mountPath: /secrets
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 13133 # Health Check extension default port.
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 13133 # Health Check extension default port.
|
||||
volumes:
|
||||
- configMap:
|
||||
name: otel-collector-conf
|
||||
items:
|
||||
- key: otel-collector-config
|
||||
path: otel-collector-config.yaml
|
||||
name: otel-collector-config-vol
|
||||
# - secret:
|
||||
# name: otel-collector-secrets
|
||||
# items:
|
||||
# - key: cert.pem
|
||||
# path: cert.pem
|
||||
# - key: key.pem
|
||||
# path: key.pem
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Example from otlp package: https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp?tab=doc#example-package-Insecure
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/exporters/otlp"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
|
||||
"github.com/open-telemetry/opentelemetry-collector/translator/conventions"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// If the OpenTelemetry Collector is running on a local cluster (minikube or microk8s),
|
||||
// it should be accessible through the NodePort service at the `localhost:30080` address.
|
||||
// Otherwise, replace `localhost` with the address of your cluster.
|
||||
// If you run the app inside k8s, then you can probably connect directly to the service through dns
|
||||
exp, err := otlp.NewExporter(otlp.WithInsecure(),
|
||||
otlp.WithAddress("localhost:30080"),
|
||||
otlp.WithGRPCDialOption(grpc.WithBlock()))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create the collector exporter: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
err := exp.Stop()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to stop the exporter: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
tp, err := sdktrace.NewProvider(
|
||||
sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
|
||||
sdktrace.WithResource(resource.New(
|
||||
// the service name used to display traces in Jaeger
|
||||
kv.Key(conventions.AttributeServiceName).String("test-service"),
|
||||
)),
|
||||
sdktrace.WithSyncer(exp))
|
||||
if err != nil {
|
||||
log.Fatalf("error creating trace provider: %v\n", err)
|
||||
}
|
||||
|
||||
tracer := tp.Tracer("test-tracer")
|
||||
|
||||
// Then use the OpenTelemetry tracing library, like we normally would.
|
||||
ctx, span := tracer.Start(context.Background(), "CollectorExporter-Example")
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_, iSpan := tracer.Start(ctx, fmt.Sprintf("Sample-%d", i))
|
||||
<-time.After(time.Second)
|
||||
iSpan.End()
|
||||
}
|
||||
|
||||
span.End()
|
||||
}
|
||||
@@ -8,6 +8,6 @@ replace (
|
||||
)
|
||||
|
||||
require (
|
||||
go.opentelemetry.io/otel v0.5.0
|
||||
go.opentelemetry.io/otel/exporters/metric/prometheus v0.5.0
|
||||
go.opentelemetry.io/otel v0.6.0
|
||||
go.opentelemetry.io/otel/exporters/metric/prometheus v0.6.0
|
||||
)
|
||||
|
||||
+15
-18
@@ -25,46 +25,43 @@ import (
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/metric"
|
||||
"go.opentelemetry.io/otel/exporters/metric/prometheus"
|
||||
"go.opentelemetry.io/otel/sdk/metric/controller/push"
|
||||
)
|
||||
|
||||
var (
|
||||
lemonsKey = kv.Key("ex.com/lemons")
|
||||
)
|
||||
|
||||
func initMeter() *push.Controller {
|
||||
pusher, hf, err := prometheus.InstallNewPipeline(prometheus.Config{})
|
||||
func initMeter() {
|
||||
exporter, err := prometheus.InstallNewPipeline(prometheus.Config{})
|
||||
if err != nil {
|
||||
log.Panicf("failed to initialize prometheus exporter %v", err)
|
||||
}
|
||||
http.HandleFunc("/", hf)
|
||||
http.HandleFunc("/", exporter.ServeHTTP)
|
||||
go func() {
|
||||
_ = http.ListenAndServe(":2222", nil)
|
||||
}()
|
||||
|
||||
return pusher
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer initMeter().Stop()
|
||||
initMeter()
|
||||
|
||||
meter := global.Meter("ex.com/basic")
|
||||
observerLock := new(sync.RWMutex)
|
||||
observerValueToReport := new(float64)
|
||||
observerLabelsToReport := new([]kv.KeyValue)
|
||||
cb := func(result metric.Float64ObserverResult) {
|
||||
cb := func(_ context.Context, result metric.Float64ObserverResult) {
|
||||
(*observerLock).RLock()
|
||||
value := *observerValueToReport
|
||||
labels := *observerLabelsToReport
|
||||
(*observerLock).RUnlock()
|
||||
result.Observe(value, labels...)
|
||||
}
|
||||
_ = metric.Must(meter).RegisterFloat64Observer("ex.com.one", cb,
|
||||
metric.WithDescription("A measure set to 1.0"),
|
||||
_ = metric.Must(meter).NewFloat64ValueObserver("ex.com.one", cb,
|
||||
metric.WithDescription("A ValueObserver set to 1.0"),
|
||||
)
|
||||
|
||||
measureTwo := metric.Must(meter).NewFloat64Measure("ex.com.two")
|
||||
measureThree := metric.Must(meter).NewFloat64Counter("ex.com.three")
|
||||
valuerecorder := metric.Must(meter).NewFloat64ValueRecorder("ex.com.two")
|
||||
counter := metric.Must(meter).NewFloat64Counter("ex.com.three")
|
||||
|
||||
commonLabels := []kv.KeyValue{lemonsKey.Int(10), kv.String("A", "1"), kv.String("B", "2"), kv.String("C", "3")}
|
||||
notSoCommonLabels := []kv.KeyValue{lemonsKey.Int(13)}
|
||||
@@ -78,8 +75,8 @@ func main() {
|
||||
meter.RecordBatch(
|
||||
ctx,
|
||||
commonLabels,
|
||||
measureTwo.Measurement(2.0),
|
||||
measureThree.Measurement(12.0),
|
||||
valuerecorder.Measurement(2.0),
|
||||
counter.Measurement(12.0),
|
||||
)
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
@@ -91,8 +88,8 @@ func main() {
|
||||
meter.RecordBatch(
|
||||
ctx,
|
||||
notSoCommonLabels,
|
||||
measureTwo.Measurement(2.0),
|
||||
measureThree.Measurement(22.0),
|
||||
valuerecorder.Measurement(2.0),
|
||||
counter.Measurement(22.0),
|
||||
)
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
@@ -104,8 +101,8 @@ func main() {
|
||||
meter.RecordBatch(
|
||||
ctx,
|
||||
commonLabels,
|
||||
measureTwo.Measurement(12.0),
|
||||
measureThree.Measurement(13.0),
|
||||
valuerecorder.Measurement(12.0),
|
||||
counter.Measurement(13.0),
|
||||
)
|
||||
|
||||
time.Sleep(100 * time.Second)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
# 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.
|
||||
FROM golang:alpine
|
||||
FROM golang:1.14-alpine
|
||||
COPY . /go/src/github.com/open-telemetry/opentelemetry-go/
|
||||
WORKDIR /go/src/github.com/open-telemetry/opentelemetry-go/example/zipkin/
|
||||
RUN go install ./main.go
|
||||
|
||||
@@ -8,6 +8,6 @@ replace (
|
||||
)
|
||||
|
||||
require (
|
||||
go.opentelemetry.io/otel v0.5.0
|
||||
go.opentelemetry.io/otel/exporters/trace/zipkin v0.5.0
|
||||
go.opentelemetry.io/otel v0.6.0
|
||||
go.opentelemetry.io/otel/exporters/trace/zipkin v0.6.0
|
||||
)
|
||||
|
||||
@@ -49,7 +49,7 @@ func initTracer(url string) {
|
||||
tp, err := sdktrace.NewProvider(
|
||||
sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
|
||||
sdktrace.WithBatcher(exporter,
|
||||
sdktrace.WithScheduleDelayMillis(5),
|
||||
sdktrace.WithBatchTimeout(5),
|
||||
sdktrace.WithMaxExportBatchSize(10),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package prometheus_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/metric"
|
||||
"go.opentelemetry.io/otel/exporters/metric/prometheus"
|
||||
"go.opentelemetry.io/otel/sdk/metric/controller/pull"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
// This test demonstrates that it is relatively difficult to setup a
|
||||
// Prometheus export pipeline:
|
||||
//
|
||||
// 1. The default boundaries are difficult to pass, should be []float instead of []metric.Number
|
||||
//
|
||||
// TODO: Address this issue.
|
||||
|
||||
func ExampleNewExportPipeline() {
|
||||
// Create a meter
|
||||
exporter, err := prometheus.NewExportPipeline(
|
||||
prometheus.Config{},
|
||||
pull.WithResource(resource.New(kv.String("R", "V"))),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
meter := exporter.Provider().Meter("example")
|
||||
ctx := context.Background()
|
||||
|
||||
// Use two instruments
|
||||
counter := metric.Must(meter).NewInt64Counter(
|
||||
"a.counter",
|
||||
metric.WithDescription("Counts things"),
|
||||
)
|
||||
recorder := metric.Must(meter).NewInt64ValueRecorder(
|
||||
"a.valuerecorder",
|
||||
metric.WithDescription("Records values"),
|
||||
)
|
||||
|
||||
counter.Add(ctx, 100, kv.String("key", "value"))
|
||||
recorder.Record(ctx, 100, kv.String("key", "value"))
|
||||
|
||||
// GET the HTTP endpoint
|
||||
var input bytes.Buffer
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", &input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
exporter.ServeHTTP(resp, req)
|
||||
data, err := ioutil.ReadAll(resp.Result().Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Print(string(data))
|
||||
|
||||
// Output:
|
||||
// # HELP a_counter Counts things
|
||||
// # TYPE a_counter counter
|
||||
// a_counter{R="V",key="value"} 100
|
||||
// # HELP a_valuerecorder Records values
|
||||
// # TYPE a_valuerecorder histogram
|
||||
// a_valuerecorder_bucket{R="V",key="value",le="+Inf"} 1
|
||||
// a_valuerecorder_sum{R="V",key="value"} 100
|
||||
// a_valuerecorder_count{R="V",key="value"} 1
|
||||
}
|
||||
@@ -9,6 +9,6 @@ require (
|
||||
github.com/prometheus/client_golang v1.5.0
|
||||
github.com/prometheus/procfs v0.0.10 // indirect
|
||||
github.com/stretchr/testify v1.4.0
|
||||
go.opentelemetry.io/otel v0.5.0
|
||||
go.opentelemetry.io/otel v0.6.0
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect
|
||||
)
|
||||
|
||||
@@ -18,39 +18,44 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/api/metric"
|
||||
"sync"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/label"
|
||||
"go.opentelemetry.io/otel/api/metric"
|
||||
export "go.opentelemetry.io/otel/sdk/export/metric"
|
||||
"go.opentelemetry.io/otel/sdk/export/metric/aggregator"
|
||||
"go.opentelemetry.io/otel/sdk/metric/controller/push"
|
||||
integrator "go.opentelemetry.io/otel/sdk/metric/integrator/simple"
|
||||
"go.opentelemetry.io/otel/sdk/metric/controller/pull"
|
||||
"go.opentelemetry.io/otel/sdk/metric/selector/simple"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
// Exporter is an implementation of metric.Exporter that sends metrics to
|
||||
// Prometheus.
|
||||
//
|
||||
// This exporter supports Prometheus pulls, as such it does not
|
||||
// implement the export.Exporter interface.
|
||||
type Exporter struct {
|
||||
handler http.Handler
|
||||
|
||||
registerer prometheus.Registerer
|
||||
gatherer prometheus.Gatherer
|
||||
|
||||
snapshot export.CheckpointSet
|
||||
onError func(error)
|
||||
// lock protects access to the controller. The controller
|
||||
// exposes its own lock, but using a dedicated lock in this
|
||||
// struct allows the exporter to potentially support multiple
|
||||
// controllers (e.g., with different resources).
|
||||
lock sync.RWMutex
|
||||
controller *pull.Controller
|
||||
|
||||
onError func(error)
|
||||
|
||||
defaultSummaryQuantiles []float64
|
||||
defaultHistogramBoundaries []metric.Number
|
||||
defaultHistogramBoundaries []float64
|
||||
}
|
||||
|
||||
var _ export.Exporter = &Exporter{}
|
||||
var _ http.Handler = &Exporter{}
|
||||
|
||||
// Config is a set of configs for the tally reporter.
|
||||
@@ -79,16 +84,16 @@ type Config struct {
|
||||
|
||||
// DefaultHistogramBoundaries defines the default histogram bucket
|
||||
// boundaries.
|
||||
DefaultHistogramBoundaries []metric.Number
|
||||
DefaultHistogramBoundaries []float64
|
||||
|
||||
// OnError is a function that handle errors that may occur while exporting metrics.
|
||||
// TODO: This should be refactored or even removed once we have a better error handling mechanism.
|
||||
OnError func(error)
|
||||
}
|
||||
|
||||
// NewRawExporter returns a new prometheus exporter for prometheus metrics
|
||||
// for use in a pipeline.
|
||||
func NewRawExporter(config Config) (*Exporter, error) {
|
||||
// NewExportPipeline sets up a complete export pipeline with the recommended setup,
|
||||
// using the recommended selector and standard integrator. See the pull.Options.
|
||||
func NewExportPipeline(config Config, options ...pull.Option) (*Exporter, error) {
|
||||
if config.Registry == nil {
|
||||
config.Registry = prometheus.NewRegistry()
|
||||
}
|
||||
@@ -116,9 +121,12 @@ func NewRawExporter(config Config) (*Exporter, error) {
|
||||
onError: config.OnError,
|
||||
}
|
||||
|
||||
c := newCollector(e)
|
||||
c := &collector{
|
||||
exp: e,
|
||||
}
|
||||
e.SetController(config, options...)
|
||||
if err := config.Registerer.Register(c); err != nil {
|
||||
config.OnError(fmt.Errorf("cannot register the collector: %w", err))
|
||||
return nil, fmt.Errorf("cannot register the collector: %w", err)
|
||||
}
|
||||
|
||||
return e, nil
|
||||
@@ -127,7 +135,7 @@ func NewRawExporter(config Config) (*Exporter, error) {
|
||||
// InstallNewPipeline instantiates a NewExportPipeline and registers it globally.
|
||||
// Typically called as:
|
||||
//
|
||||
// pipeline, hf, err := prometheus.InstallNewPipeline(prometheus.Config{...})
|
||||
// hf, err := prometheus.InstallNewPipeline(prometheus.Config{...})
|
||||
//
|
||||
// if err != nil {
|
||||
// ...
|
||||
@@ -135,44 +143,51 @@ func NewRawExporter(config Config) (*Exporter, error) {
|
||||
// http.HandleFunc("/metrics", hf)
|
||||
// defer pipeline.Stop()
|
||||
// ... Done
|
||||
func InstallNewPipeline(config Config) (*push.Controller, http.HandlerFunc, error) {
|
||||
controller, hf, err := NewExportPipeline(config, time.Minute)
|
||||
func InstallNewPipeline(config Config, options ...pull.Option) (*Exporter, error) {
|
||||
exp, err := NewExportPipeline(config, options...)
|
||||
if err != nil {
|
||||
return controller, hf, err
|
||||
return nil, err
|
||||
}
|
||||
global.SetMeterProvider(controller)
|
||||
return controller, hf, err
|
||||
global.SetMeterProvider(exp.Provider())
|
||||
return exp, nil
|
||||
}
|
||||
|
||||
// NewExportPipeline sets up a complete export pipeline with the recommended setup,
|
||||
// chaining a NewRawExporter into the recommended selectors and integrators.
|
||||
func NewExportPipeline(config Config, period time.Duration) (*push.Controller, http.HandlerFunc, error) {
|
||||
selector := simple.NewWithHistogramMeasure(config.DefaultHistogramBoundaries)
|
||||
exporter, err := NewRawExporter(config)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Prometheus needs to use a stateful integrator since counters (and histogram since they are a collection of Counters)
|
||||
// are cumulative (i.e., monotonically increasing values) and should not be resetted after each export.
|
||||
// SetController sets up a standard *pull.Controller as the metric provider
|
||||
// for this exporter.
|
||||
func (e *Exporter) SetController(config Config, options ...pull.Option) {
|
||||
e.lock.Lock()
|
||||
defer e.lock.Unlock()
|
||||
// Prometheus uses a stateful pull controller since instruments are
|
||||
// cumulative and should not be reset after each collection interval.
|
||||
//
|
||||
// Prometheus uses this approach to be resilient to scrape failures.
|
||||
// If a Prometheus server tries to scrape metrics from a host and fails for some reason,
|
||||
// it could try again on the next scrape and no data would be lost, only resolution.
|
||||
//
|
||||
// Gauges (or LastValues) and Summaries are an exception to this and have different behaviors.
|
||||
integrator := integrator.New(selector, true)
|
||||
pusher := push.New(integrator, exporter, period)
|
||||
pusher.Start()
|
||||
|
||||
return pusher, exporter.ServeHTTP, nil
|
||||
//
|
||||
// TODO: Prometheus supports "Gauge Histogram" which are
|
||||
// expressed as delta histograms.
|
||||
e.controller = pull.New(
|
||||
simple.NewWithHistogramDistribution(config.DefaultHistogramBoundaries),
|
||||
append(options, pull.WithStateful(true))...,
|
||||
)
|
||||
}
|
||||
|
||||
// Export exports the provide metric record to prometheus.
|
||||
func (e *Exporter) Export(_ context.Context, _ *resource.Resource, checkpointSet export.CheckpointSet) error {
|
||||
// TODO: Use the resource value in this exporter.
|
||||
e.snapshot = checkpointSet
|
||||
return nil
|
||||
// Provider returns the metric.Provider of this exporter.
|
||||
func (e *Exporter) Provider() metric.Provider {
|
||||
return e.controller.Provider()
|
||||
}
|
||||
|
||||
// Controller returns the controller object that coordinates collection for the SDK.
|
||||
func (e *Exporter) Controller() *pull.Controller {
|
||||
e.lock.RLock()
|
||||
defer e.lock.RUnlock()
|
||||
return e.controller
|
||||
}
|
||||
|
||||
func (e *Exporter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
e.handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// collector implements prometheus.Collector interface.
|
||||
@@ -182,19 +197,14 @@ type collector struct {
|
||||
|
||||
var _ prometheus.Collector = (*collector)(nil)
|
||||
|
||||
func newCollector(exporter *Exporter) *collector {
|
||||
return &collector{
|
||||
exp: exporter,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *collector) Describe(ch chan<- *prometheus.Desc) {
|
||||
if c.exp.snapshot == nil {
|
||||
return
|
||||
}
|
||||
c.exp.lock.RLock()
|
||||
defer c.exp.lock.RUnlock()
|
||||
|
||||
_ = c.exp.snapshot.ForEach(func(record export.Record) error {
|
||||
ch <- c.toDesc(&record)
|
||||
_ = c.exp.Controller().ForEach(func(record export.Record) error {
|
||||
var labelKeys []string
|
||||
mergeLabels(record, &labelKeys, nil)
|
||||
ch <- c.toDesc(record, labelKeys)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -204,15 +214,20 @@ func (c *collector) Describe(ch chan<- *prometheus.Desc) {
|
||||
// Collect is invoked whenever prometheus.Gatherer is also invoked.
|
||||
// For example, when the HTTP endpoint is invoked by Prometheus.
|
||||
func (c *collector) Collect(ch chan<- prometheus.Metric) {
|
||||
if c.exp.snapshot == nil {
|
||||
return
|
||||
}
|
||||
c.exp.lock.RLock()
|
||||
defer c.exp.lock.RUnlock()
|
||||
|
||||
err := c.exp.snapshot.ForEach(func(record export.Record) error {
|
||||
ctrl := c.exp.Controller()
|
||||
ctrl.Collect(context.Background())
|
||||
|
||||
err := ctrl.ForEach(func(record export.Record) error {
|
||||
agg := record.Aggregator()
|
||||
numberKind := record.Descriptor().NumberKind()
|
||||
labels := labelValues(record.Labels())
|
||||
desc := c.toDesc(&record)
|
||||
|
||||
var labelKeys, labels []string
|
||||
mergeLabels(record, &labelKeys, &labels)
|
||||
|
||||
desc := c.toDesc(record, labelKeys)
|
||||
|
||||
if hist, ok := agg.(aggregator.Histogram); ok {
|
||||
if err := c.exportHistogram(ch, hist, numberKind, desc, labels); err != nil {
|
||||
@@ -220,7 +235,7 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {
|
||||
}
|
||||
} else if dist, ok := agg.(aggregator.Distribution); ok {
|
||||
// TODO: summaries values are never being resetted.
|
||||
// As measures are recorded, new records starts to have less impact on these summaries.
|
||||
// As measurements are recorded, new records starts to have less impact on these summaries.
|
||||
// We should implement an solution that is similar to the Prometheus Clients
|
||||
// using a rolling window for summaries could be a solution.
|
||||
//
|
||||
@@ -318,12 +333,12 @@ func (c *collector) exportHistogram(ch chan<- prometheus.Metric, hist aggregator
|
||||
// The bucket with upper-bound +inf is not included.
|
||||
counts := make(map[float64]uint64, len(buckets.Boundaries))
|
||||
for i := range buckets.Boundaries {
|
||||
boundary := buckets.Boundaries[i].CoerceToFloat64(kind)
|
||||
totalCount += buckets.Counts[i].AsUint64()
|
||||
boundary := buckets.Boundaries[i]
|
||||
totalCount += uint64(buckets.Counts[i])
|
||||
counts[boundary] = totalCount
|
||||
}
|
||||
// Include the +inf bucket in the total count.
|
||||
totalCount += buckets.Counts[len(buckets.Counts)-1].AsUint64()
|
||||
totalCount += uint64(buckets.Counts[len(buckets.Counts)-1])
|
||||
|
||||
m, err := prometheus.NewConstHistogram(desc, totalCount, sum.CoerceToFloat64(kind), counts, labels...)
|
||||
if err != nil {
|
||||
@@ -334,34 +349,34 @@ func (c *collector) exportHistogram(ch chan<- prometheus.Metric, hist aggregator
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *collector) toDesc(record *export.Record) *prometheus.Desc {
|
||||
func (c *collector) toDesc(record export.Record, labelKeys []string) *prometheus.Desc {
|
||||
desc := record.Descriptor()
|
||||
labels := labelsKeys(record.Labels())
|
||||
return prometheus.NewDesc(sanitize(desc.Name()), desc.Description(), labels, nil)
|
||||
return prometheus.NewDesc(sanitize(desc.Name()), desc.Description(), labelKeys, nil)
|
||||
}
|
||||
|
||||
func (e *Exporter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
e.handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func labelsKeys(labels *label.Set) []string {
|
||||
iter := labels.Iter()
|
||||
keys := make([]string, 0, iter.Len())
|
||||
for iter.Next() {
|
||||
kv := iter.Label()
|
||||
keys = append(keys, sanitize(string(kv.Key)))
|
||||
// mergeLabels merges the export.Record's labels and resources into a
|
||||
// single set, giving precedence to the record's labels in case of
|
||||
// duplicate keys. This outputs one or both of the keys and the
|
||||
// values as a slice, and either argument may be nil to avoid
|
||||
// allocating an unnecessary slice.
|
||||
func mergeLabels(record export.Record, keys, values *[]string) {
|
||||
if keys != nil {
|
||||
*keys = make([]string, 0, record.Labels().Len()+record.Resource().Len())
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func labelValues(labels *label.Set) []string {
|
||||
// TODO(paivagustavo): parse the labels.Encoded() instead of calling `Emit()` directly
|
||||
// this would avoid unnecessary allocations.
|
||||
iter := labels.Iter()
|
||||
values := make([]string, 0, iter.Len())
|
||||
for iter.Next() {
|
||||
label := iter.Label()
|
||||
values = append(values, label.Value.Emit())
|
||||
if values != nil {
|
||||
*values = make([]string, 0, record.Labels().Len()+record.Resource().Len())
|
||||
}
|
||||
|
||||
// Duplicate keys are resolved by taking the record label value over
|
||||
// the resource value.
|
||||
mi := label.NewMergeIterator(record.Labels(), record.Resource().LabelSet())
|
||||
for mi.Next() {
|
||||
label := mi.Label()
|
||||
if keys != nil {
|
||||
*keys = append(*keys, sanitize(string(label.Key)))
|
||||
}
|
||||
if values != nil {
|
||||
*values = append(*values, label.Value.Emit())
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
package prometheus_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"log"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -27,105 +29,56 @@ import (
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/metric"
|
||||
"go.opentelemetry.io/otel/exporters/metric/prometheus"
|
||||
"go.opentelemetry.io/otel/exporters/metric/test"
|
||||
"go.opentelemetry.io/otel/sdk/metric/controller/pull"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
func TestPrometheusExporter(t *testing.T) {
|
||||
exporter, err := prometheus.NewRawExporter(prometheus.Config{
|
||||
DefaultSummaryQuantiles: []float64{0.5, 0.9, 0.99},
|
||||
})
|
||||
if err != nil {
|
||||
log.Panicf("failed to initialize prometheus exporter %v", err)
|
||||
}
|
||||
exporter, err := prometheus.NewExportPipeline(prometheus.Config{
|
||||
DefaultHistogramBoundaries: []float64{-0.5, 1},
|
||||
}, pull.WithResource(resource.New(kv.String("R", "V"))))
|
||||
require.NoError(t, err)
|
||||
|
||||
var expected []string
|
||||
checkpointSet := test.NewCheckpointSet()
|
||||
meter := exporter.Provider().Meter("test")
|
||||
|
||||
counter := metric.NewDescriptor(
|
||||
"counter", metric.CounterKind, metric.Float64NumberKind)
|
||||
lastValue := metric.NewDescriptor(
|
||||
"lastvalue", metric.ObserverKind, metric.Float64NumberKind)
|
||||
measure := metric.NewDescriptor(
|
||||
"measure", metric.MeasureKind, metric.Float64NumberKind)
|
||||
histogramMeasure := metric.NewDescriptor(
|
||||
"histogram_measure", metric.MeasureKind, metric.Float64NumberKind)
|
||||
counter := metric.Must(meter).NewFloat64Counter("counter")
|
||||
valuerecorder := metric.Must(meter).NewFloat64ValueRecorder("valuerecorder")
|
||||
|
||||
labels := []kv.KeyValue{
|
||||
kv.Key("A").String("B"),
|
||||
kv.Key("C").String("D"),
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
||||
checkpointSet.AddCounter(&counter, 15.3, labels...)
|
||||
expected = append(expected, `counter{A="B",C="D"} 15.3`)
|
||||
var expected []string
|
||||
|
||||
checkpointSet.AddLastValue(&lastValue, 13.2, labels...)
|
||||
expected = append(expected, `lastvalue{A="B",C="D"} 13.2`)
|
||||
counter.Add(ctx, 10, labels...)
|
||||
counter.Add(ctx, 5.3, labels...)
|
||||
|
||||
checkpointSet.AddMeasure(&measure, 13, labels...)
|
||||
checkpointSet.AddMeasure(&measure, 15, labels...)
|
||||
checkpointSet.AddMeasure(&measure, 17, labels...)
|
||||
expected = append(expected, `measure{A="B",C="D",quantile="0.5"} 15`)
|
||||
expected = append(expected, `measure{A="B",C="D",quantile="0.9"} 17`)
|
||||
expected = append(expected, `measure{A="B",C="D",quantile="0.99"} 17`)
|
||||
expected = append(expected, `measure_sum{A="B",C="D"} 45`)
|
||||
expected = append(expected, `measure_count{A="B",C="D"} 3`)
|
||||
expected = append(expected, `counter{A="B",C="D",R="V"} 15.3`)
|
||||
|
||||
boundaries := []metric.Number{metric.NewFloat64Number(-0.5), metric.NewFloat64Number(1)}
|
||||
checkpointSet.AddHistogramMeasure(&histogramMeasure, boundaries, -0.6, labels...)
|
||||
checkpointSet.AddHistogramMeasure(&histogramMeasure, boundaries, -0.4, labels...)
|
||||
checkpointSet.AddHistogramMeasure(&histogramMeasure, boundaries, 0.6, labels...)
|
||||
checkpointSet.AddHistogramMeasure(&histogramMeasure, boundaries, 20, labels...)
|
||||
valuerecorder.Record(ctx, -0.6, labels...)
|
||||
valuerecorder.Record(ctx, -0.4, labels...)
|
||||
valuerecorder.Record(ctx, 0.6, labels...)
|
||||
valuerecorder.Record(ctx, 20, labels...)
|
||||
|
||||
expected = append(expected, `histogram_measure_bucket{A="B",C="D",le="+Inf"} 4`)
|
||||
expected = append(expected, `histogram_measure_bucket{A="B",C="D",le="-0.5"} 1`)
|
||||
expected = append(expected, `histogram_measure_bucket{A="B",C="D",le="1"} 3`)
|
||||
expected = append(expected, `histogram_measure_count{A="B",C="D"} 4`)
|
||||
expected = append(expected, `histogram_measure_sum{A="B",C="D"} 19.6`)
|
||||
expected = append(expected, `valuerecorder_bucket{A="B",C="D",R="V",le="+Inf"} 4`)
|
||||
expected = append(expected, `valuerecorder_bucket{A="B",C="D",R="V",le="-0.5"} 1`)
|
||||
expected = append(expected, `valuerecorder_bucket{A="B",C="D",R="V",le="1"} 3`)
|
||||
expected = append(expected, `valuerecorder_count{A="B",C="D",R="V"} 4`)
|
||||
expected = append(expected, `valuerecorder_sum{A="B",C="D",R="V"} 19.6`)
|
||||
|
||||
missingLabels := []kv.KeyValue{
|
||||
kv.Key("A").String("E"),
|
||||
kv.Key("C").String(""),
|
||||
}
|
||||
|
||||
checkpointSet.AddCounter(&counter, 12, missingLabels...)
|
||||
expected = append(expected, `counter{A="E",C=""} 12`)
|
||||
|
||||
checkpointSet.AddLastValue(&lastValue, 32, missingLabels...)
|
||||
expected = append(expected, `lastvalue{A="E",C=""} 32`)
|
||||
|
||||
checkpointSet.AddMeasure(&measure, 19, missingLabels...)
|
||||
expected = append(expected, `measure{A="E",C="",quantile="0.5"} 19`)
|
||||
expected = append(expected, `measure{A="E",C="",quantile="0.9"} 19`)
|
||||
expected = append(expected, `measure{A="E",C="",quantile="0.99"} 19`)
|
||||
expected = append(expected, `measure_count{A="E",C=""} 1`)
|
||||
expected = append(expected, `measure_sum{A="E",C=""} 19`)
|
||||
|
||||
boundaries = []metric.Number{metric.NewFloat64Number(0), metric.NewFloat64Number(1)}
|
||||
checkpointSet.AddHistogramMeasure(&histogramMeasure, boundaries, -0.6, missingLabels...)
|
||||
checkpointSet.AddHistogramMeasure(&histogramMeasure, boundaries, -0.4, missingLabels...)
|
||||
checkpointSet.AddHistogramMeasure(&histogramMeasure, boundaries, -0.1, missingLabels...)
|
||||
checkpointSet.AddHistogramMeasure(&histogramMeasure, boundaries, 15, missingLabels...)
|
||||
checkpointSet.AddHistogramMeasure(&histogramMeasure, boundaries, 15, missingLabels...)
|
||||
|
||||
expected = append(expected, `histogram_measure_bucket{A="E",C="",le="+Inf"} 5`)
|
||||
expected = append(expected, `histogram_measure_bucket{A="E",C="",le="0"} 3`)
|
||||
expected = append(expected, `histogram_measure_bucket{A="E",C="",le="1"} 3`)
|
||||
expected = append(expected, `histogram_measure_count{A="E",C=""} 5`)
|
||||
expected = append(expected, `histogram_measure_sum{A="E",C=""} 28.9`)
|
||||
|
||||
compareExport(t, exporter, checkpointSet, expected)
|
||||
compareExport(t, exporter, expected)
|
||||
}
|
||||
|
||||
func compareExport(t *testing.T, exporter *prometheus.Exporter, checkpointSet *test.CheckpointSet, expected []string) {
|
||||
err := exporter.Export(context.Background(), nil, checkpointSet)
|
||||
require.Nil(t, err)
|
||||
|
||||
func compareExport(t *testing.T, exporter *prometheus.Exporter, expected []string) {
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/metrics", nil)
|
||||
exporter.ServeHTTP(rec, req)
|
||||
|
||||
output := rec.Body.String()
|
||||
lines := strings.Split(output, "\n")
|
||||
|
||||
var metricsOnly []string
|
||||
for _, line := range lines {
|
||||
if !strings.HasPrefix(line, "#") && line != "" {
|
||||
@@ -138,3 +91,50 @@ func compareExport(t *testing.T, exporter *prometheus.Exporter, checkpointSet *t
|
||||
|
||||
require.Equal(t, strings.Join(expected, "\n"), strings.Join(metricsOnly, "\n"))
|
||||
}
|
||||
|
||||
func TestPrometheusStatefulness(t *testing.T) {
|
||||
// Create a meter
|
||||
exporter, err := prometheus.NewExportPipeline(
|
||||
prometheus.Config{},
|
||||
pull.WithCachePeriod(0),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
meter := exporter.Provider().Meter("test")
|
||||
|
||||
// GET the HTTP endpoint
|
||||
scrape := func() string {
|
||||
var input bytes.Buffer
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", &input)
|
||||
require.NoError(t, err)
|
||||
|
||||
exporter.ServeHTTP(resp, req)
|
||||
data, err := ioutil.ReadAll(resp.Result().Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
return string(data)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
counter := metric.Must(meter).NewInt64Counter(
|
||||
"a.counter",
|
||||
metric.WithDescription("Counts things"),
|
||||
)
|
||||
|
||||
counter.Add(ctx, 100, kv.String("key", "value"))
|
||||
|
||||
require.Equal(t, `# HELP a_counter Counts things
|
||||
# TYPE a_counter counter
|
||||
a_counter{key="value"} 100
|
||||
`, scrape())
|
||||
|
||||
counter.Add(ctx, 100, kv.String("key", "value"))
|
||||
|
||||
require.Equal(t, `# HELP a_counter Counts things
|
||||
# TYPE a_counter counter
|
||||
a_counter{key="value"} 200
|
||||
`, scrape())
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ package stdout_test
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/metric"
|
||||
@@ -29,7 +28,7 @@ func ExampleNewExportPipeline() {
|
||||
pusher, err := stdout.NewExportPipeline(stdout.Config{
|
||||
PrettyPrint: true,
|
||||
DoNotPrintTime: true,
|
||||
}, time.Minute)
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal("Could not initialize stdout exporter:", err)
|
||||
}
|
||||
@@ -38,7 +37,7 @@ func ExampleNewExportPipeline() {
|
||||
ctx := context.Background()
|
||||
|
||||
key := kv.Key("key")
|
||||
meter := pusher.Meter("example")
|
||||
meter := pusher.Provider().Meter("example")
|
||||
|
||||
// Create and update a single counter:
|
||||
counter := metric.Must(meter).NewInt64Counter("a.counter")
|
||||
|
||||
@@ -25,12 +25,10 @@ import (
|
||||
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/label"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
|
||||
export "go.opentelemetry.io/otel/sdk/export/metric"
|
||||
"go.opentelemetry.io/otel/sdk/export/metric/aggregator"
|
||||
"go.opentelemetry.io/otel/sdk/metric/controller/push"
|
||||
integrator "go.opentelemetry.io/otel/sdk/metric/integrator/simple"
|
||||
"go.opentelemetry.io/otel/sdk/metric/selector/simple"
|
||||
)
|
||||
|
||||
@@ -53,8 +51,8 @@ type Config struct {
|
||||
// useful to create deterministic test conditions.
|
||||
DoNotPrintTime bool
|
||||
|
||||
// Quantiles are the desired aggregation quantiles for measure
|
||||
// metric data, used when the configured aggregator supports
|
||||
// Quantiles are the desired aggregation quantiles for distribution
|
||||
// summaries, used when the configured aggregator supports
|
||||
// quantiles.
|
||||
//
|
||||
// Note: this exporter is meant as a demonstration; a real
|
||||
@@ -121,42 +119,48 @@ func NewRawExporter(config Config) (*Exporter, error) {
|
||||
// }
|
||||
// defer pipeline.Stop()
|
||||
// ... Done
|
||||
func InstallNewPipeline(config Config, opts ...push.Option) (*push.Controller, error) {
|
||||
controller, err := NewExportPipeline(config, time.Minute, opts...)
|
||||
func InstallNewPipeline(config Config, options ...push.Option) (*push.Controller, error) {
|
||||
controller, err := NewExportPipeline(config, options...)
|
||||
if err != nil {
|
||||
return controller, err
|
||||
}
|
||||
global.SetMeterProvider(controller)
|
||||
global.SetMeterProvider(controller.Provider())
|
||||
return controller, err
|
||||
}
|
||||
|
||||
// NewExportPipeline sets up a complete export pipeline with the recommended setup,
|
||||
// chaining a NewRawExporter into the recommended selectors and integrators.
|
||||
func NewExportPipeline(config Config, period time.Duration, opts ...push.Option) (*push.Controller, error) {
|
||||
selector := simple.NewWithExactMeasure()
|
||||
// NewExportPipeline sets up a complete export pipeline with the
|
||||
// recommended setup, chaining a NewRawExporter into the recommended
|
||||
// selectors and integrators.
|
||||
//
|
||||
// The pipeline is configured with a stateful integrator unless the
|
||||
// push.WithStateful(false) option is used.
|
||||
func NewExportPipeline(config Config, options ...push.Option) (*push.Controller, error) {
|
||||
exporter, err := NewRawExporter(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
integrator := integrator.New(selector, true)
|
||||
pusher := push.New(integrator, exporter, period, opts...)
|
||||
pusher := push.New(
|
||||
simple.NewWithExactDistribution(),
|
||||
exporter,
|
||||
append([]push.Option{push.WithStateful(true)}, options...)...,
|
||||
)
|
||||
pusher.Start()
|
||||
|
||||
return pusher, nil
|
||||
}
|
||||
|
||||
func (e *Exporter) Export(_ context.Context, resource *resource.Resource, checkpointSet export.CheckpointSet) error {
|
||||
func (e *Exporter) Export(_ context.Context, checkpointSet export.CheckpointSet) error {
|
||||
var aggError error
|
||||
var batch expoBatch
|
||||
if !e.config.DoNotPrintTime {
|
||||
ts := time.Now()
|
||||
batch.Timestamp = &ts
|
||||
}
|
||||
encodedResource := resource.Encoded(e.config.LabelEncoder)
|
||||
aggError = checkpointSet.ForEach(func(record export.Record) error {
|
||||
desc := record.Descriptor()
|
||||
agg := record.Aggregator()
|
||||
kind := desc.NumberKind()
|
||||
encodedResource := record.Resource().Encoded(e.config.LabelEncoder)
|
||||
|
||||
var expose expoLine
|
||||
|
||||
|
||||
@@ -44,10 +44,11 @@ type testFixture struct {
|
||||
ctx context.Context
|
||||
exporter *stdout.Exporter
|
||||
output *bytes.Buffer
|
||||
resource *resource.Resource
|
||||
}
|
||||
|
||||
func newFixture(t *testing.T, resource *resource.Resource, config stdout.Config) testFixture {
|
||||
var testResource = resource.New(kv.String("R", "V"))
|
||||
|
||||
func newFixture(t *testing.T, config stdout.Config) testFixture {
|
||||
buf := &bytes.Buffer{}
|
||||
config.Writer = buf
|
||||
config.DoNotPrintTime = true
|
||||
@@ -60,7 +61,6 @@ func newFixture(t *testing.T, resource *resource.Resource, config stdout.Config)
|
||||
ctx: context.Background(),
|
||||
exporter: exp,
|
||||
output: buf,
|
||||
resource: resource,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ func (fix testFixture) Output() string {
|
||||
}
|
||||
|
||||
func (fix testFixture) Export(checkpointSet export.CheckpointSet) {
|
||||
err := fix.exporter.Export(fix.ctx, fix.resource, checkpointSet)
|
||||
err := fix.exporter.Export(fix.ctx, checkpointSet)
|
||||
if err != nil {
|
||||
fix.t.Error("export failed: ", err)
|
||||
}
|
||||
@@ -95,17 +95,17 @@ func TestStdoutTimestamp(t *testing.T) {
|
||||
|
||||
before := time.Now()
|
||||
|
||||
checkpointSet := test.NewCheckpointSet()
|
||||
checkpointSet := test.NewCheckpointSet(testResource)
|
||||
|
||||
ctx := context.Background()
|
||||
desc := metric.NewDescriptor("test.name", metric.ObserverKind, metric.Int64NumberKind)
|
||||
desc := metric.NewDescriptor("test.name", metric.ValueObserverKind, metric.Int64NumberKind)
|
||||
lvagg := lastvalue.New()
|
||||
aggtest.CheckedUpdate(t, lvagg, metric.NewInt64Number(321), &desc)
|
||||
lvagg.Checkpoint(ctx, &desc)
|
||||
|
||||
checkpointSet.Add(&desc, lvagg)
|
||||
|
||||
if err := exporter.Export(ctx, nil, checkpointSet); err != nil {
|
||||
if err := exporter.Export(ctx, checkpointSet); err != nil {
|
||||
t.Fatal("Unexpected export error: ", err)
|
||||
}
|
||||
|
||||
@@ -139,9 +139,9 @@ func TestStdoutTimestamp(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStdoutCounterFormat(t *testing.T) {
|
||||
fix := newFixture(t, nil, stdout.Config{})
|
||||
fix := newFixture(t, stdout.Config{})
|
||||
|
||||
checkpointSet := test.NewCheckpointSet()
|
||||
checkpointSet := test.NewCheckpointSet(testResource)
|
||||
|
||||
desc := metric.NewDescriptor("test.name", metric.CounterKind, metric.Int64NumberKind)
|
||||
cagg := sum.New()
|
||||
@@ -152,15 +152,15 @@ func TestStdoutCounterFormat(t *testing.T) {
|
||||
|
||||
fix.Export(checkpointSet)
|
||||
|
||||
require.Equal(t, `{"updates":[{"name":"test.name{A=B,C=D}","sum":123}]}`, fix.Output())
|
||||
require.Equal(t, `{"updates":[{"name":"test.name{R=V,A=B,C=D}","sum":123}]}`, fix.Output())
|
||||
}
|
||||
|
||||
func TestStdoutLastValueFormat(t *testing.T) {
|
||||
fix := newFixture(t, nil, stdout.Config{})
|
||||
fix := newFixture(t, stdout.Config{})
|
||||
|
||||
checkpointSet := test.NewCheckpointSet()
|
||||
checkpointSet := test.NewCheckpointSet(testResource)
|
||||
|
||||
desc := metric.NewDescriptor("test.name", metric.ObserverKind, metric.Float64NumberKind)
|
||||
desc := metric.NewDescriptor("test.name", metric.ValueObserverKind, metric.Float64NumberKind)
|
||||
lvagg := lastvalue.New()
|
||||
aggtest.CheckedUpdate(fix.t, lvagg, metric.NewFloat64Number(123.456), &desc)
|
||||
lvagg.Checkpoint(fix.ctx, &desc)
|
||||
@@ -169,15 +169,15 @@ func TestStdoutLastValueFormat(t *testing.T) {
|
||||
|
||||
fix.Export(checkpointSet)
|
||||
|
||||
require.Equal(t, `{"updates":[{"name":"test.name{A=B,C=D}","last":123.456}]}`, fix.Output())
|
||||
require.Equal(t, `{"updates":[{"name":"test.name{R=V,A=B,C=D}","last":123.456}]}`, fix.Output())
|
||||
}
|
||||
|
||||
func TestStdoutMinMaxSumCount(t *testing.T) {
|
||||
fix := newFixture(t, nil, stdout.Config{})
|
||||
fix := newFixture(t, stdout.Config{})
|
||||
|
||||
checkpointSet := test.NewCheckpointSet()
|
||||
checkpointSet := test.NewCheckpointSet(testResource)
|
||||
|
||||
desc := metric.NewDescriptor("test.name", metric.MeasureKind, metric.Float64NumberKind)
|
||||
desc := metric.NewDescriptor("test.name", metric.ValueRecorderKind, metric.Float64NumberKind)
|
||||
magg := minmaxsumcount.New(&desc)
|
||||
aggtest.CheckedUpdate(fix.t, magg, metric.NewFloat64Number(123.456), &desc)
|
||||
aggtest.CheckedUpdate(fix.t, magg, metric.NewFloat64Number(876.543), &desc)
|
||||
@@ -187,17 +187,17 @@ func TestStdoutMinMaxSumCount(t *testing.T) {
|
||||
|
||||
fix.Export(checkpointSet)
|
||||
|
||||
require.Equal(t, `{"updates":[{"name":"test.name{A=B,C=D}","min":123.456,"max":876.543,"sum":999.999,"count":2}]}`, fix.Output())
|
||||
require.Equal(t, `{"updates":[{"name":"test.name{R=V,A=B,C=D}","min":123.456,"max":876.543,"sum":999.999,"count":2}]}`, fix.Output())
|
||||
}
|
||||
|
||||
func TestStdoutMeasureFormat(t *testing.T) {
|
||||
fix := newFixture(t, nil, stdout.Config{
|
||||
func TestStdoutValueRecorderFormat(t *testing.T) {
|
||||
fix := newFixture(t, stdout.Config{
|
||||
PrettyPrint: true,
|
||||
})
|
||||
|
||||
checkpointSet := test.NewCheckpointSet()
|
||||
checkpointSet := test.NewCheckpointSet(testResource)
|
||||
|
||||
desc := metric.NewDescriptor("test.name", metric.MeasureKind, metric.Float64NumberKind)
|
||||
desc := metric.NewDescriptor("test.name", metric.ValueRecorderKind, metric.Float64NumberKind)
|
||||
magg := array.New()
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
@@ -213,7 +213,7 @@ func TestStdoutMeasureFormat(t *testing.T) {
|
||||
require.Equal(t, `{
|
||||
"updates": [
|
||||
{
|
||||
"name": "test.name{A=B,C=D}",
|
||||
"name": "test.name{R=V,A=B,C=D}",
|
||||
"min": 0.5,
|
||||
"max": 999.5,
|
||||
"sum": 500000,
|
||||
@@ -238,18 +238,18 @@ func TestStdoutMeasureFormat(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStdoutNoData(t *testing.T) {
|
||||
desc := metric.NewDescriptor("test.name", metric.MeasureKind, metric.Float64NumberKind)
|
||||
desc := metric.NewDescriptor("test.name", metric.ValueRecorderKind, metric.Float64NumberKind)
|
||||
for name, tc := range map[string]export.Aggregator{
|
||||
"ddsketch": ddsketch.New(ddsketch.NewDefaultConfig(), &desc),
|
||||
"ddsketch": ddsketch.New(&desc, ddsketch.NewDefaultConfig()),
|
||||
"minmaxsumcount": minmaxsumcount.New(&desc),
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fix := newFixture(t, nil, stdout.Config{})
|
||||
fix := newFixture(t, stdout.Config{})
|
||||
|
||||
checkpointSet := test.NewCheckpointSet()
|
||||
checkpointSet := test.NewCheckpointSet(testResource)
|
||||
|
||||
magg := tc
|
||||
magg.Checkpoint(fix.ctx, &desc)
|
||||
@@ -264,11 +264,11 @@ func TestStdoutNoData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStdoutLastValueNotSet(t *testing.T) {
|
||||
fix := newFixture(t, nil, stdout.Config{})
|
||||
fix := newFixture(t, stdout.Config{})
|
||||
|
||||
checkpointSet := test.NewCheckpointSet()
|
||||
checkpointSet := test.NewCheckpointSet(testResource)
|
||||
|
||||
desc := metric.NewDescriptor("test.name", metric.ObserverKind, metric.Float64NumberKind)
|
||||
desc := metric.NewDescriptor("test.name", metric.ValueObserverKind, metric.Float64NumberKind)
|
||||
lvagg := lastvalue.New()
|
||||
lvagg.Checkpoint(fix.ctx, &desc)
|
||||
|
||||
@@ -314,11 +314,11 @@ func TestStdoutResource(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
fix := newFixture(t, tc.res, stdout.Config{})
|
||||
fix := newFixture(t, stdout.Config{})
|
||||
|
||||
checkpointSet := test.NewCheckpointSet()
|
||||
checkpointSet := test.NewCheckpointSet(tc.res)
|
||||
|
||||
desc := metric.NewDescriptor("test.name", metric.ObserverKind, metric.Float64NumberKind)
|
||||
desc := metric.NewDescriptor("test.name", metric.ValueObserverKind, metric.Float64NumberKind)
|
||||
lvagg := lastvalue.New()
|
||||
aggtest.CheckedUpdate(fix.t, lvagg, metric.NewFloat64Number(123.456), &desc)
|
||||
lvagg.Checkpoint(fix.ctx, &desc)
|
||||
|
||||
@@ -17,6 +17,7 @@ package test
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/label"
|
||||
@@ -27,6 +28,7 @@ import (
|
||||
"go.opentelemetry.io/otel/sdk/metric/aggregator/histogram"
|
||||
"go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue"
|
||||
"go.opentelemetry.io/otel/sdk/metric/aggregator/sum"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
type mapkey struct {
|
||||
@@ -35,15 +37,18 @@ type mapkey struct {
|
||||
}
|
||||
|
||||
type CheckpointSet struct {
|
||||
records map[mapkey]export.Record
|
||||
updates []export.Record
|
||||
sync.RWMutex
|
||||
records map[mapkey]export.Record
|
||||
updates []export.Record
|
||||
resource *resource.Resource
|
||||
}
|
||||
|
||||
// NewCheckpointSet returns a test CheckpointSet that new records could be added.
|
||||
// Records are grouped by their encoded labels.
|
||||
func NewCheckpointSet() *CheckpointSet {
|
||||
func NewCheckpointSet(resource *resource.Resource) *CheckpointSet {
|
||||
return &CheckpointSet{
|
||||
records: make(map[mapkey]export.Record),
|
||||
records: make(map[mapkey]export.Record),
|
||||
resource: resource,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +72,7 @@ func (p *CheckpointSet) Add(desc *metric.Descriptor, newAgg export.Aggregator, l
|
||||
return record.Aggregator(), false
|
||||
}
|
||||
|
||||
rec := export.NewRecord(desc, &elabels, newAgg)
|
||||
rec := export.NewRecord(desc, &elabels, p.resource, newAgg)
|
||||
p.updates = append(p.updates, rec)
|
||||
p.records[key] = rec
|
||||
return newAgg, true
|
||||
@@ -88,11 +93,11 @@ func (p *CheckpointSet) AddCounter(desc *metric.Descriptor, v float64, labels ..
|
||||
p.updateAggregator(desc, sum.New(), v, labels...)
|
||||
}
|
||||
|
||||
func (p *CheckpointSet) AddMeasure(desc *metric.Descriptor, v float64, labels ...kv.KeyValue) {
|
||||
func (p *CheckpointSet) AddValueRecorder(desc *metric.Descriptor, v float64, labels ...kv.KeyValue) {
|
||||
p.updateAggregator(desc, array.New(), v, labels...)
|
||||
}
|
||||
|
||||
func (p *CheckpointSet) AddHistogramMeasure(desc *metric.Descriptor, boundaries []metric.Number, v float64, labels ...kv.KeyValue) {
|
||||
func (p *CheckpointSet) AddHistogramValueRecorder(desc *metric.Descriptor, boundaries []float64, v float64, labels ...kv.KeyValue) {
|
||||
p.updateAggregator(desc, histogram.New(desc, boundaries), v, labels...)
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ func Example_insecure() {
|
||||
tp, _ := sdktrace.NewProvider(
|
||||
sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
|
||||
sdktrace.WithBatcher(exp, // add following two options to ensure flush
|
||||
sdktrace.WithScheduleDelayMillis(5),
|
||||
sdktrace.WithBatchTimeout(5),
|
||||
sdktrace.WithMaxExportBatchSize(10),
|
||||
))
|
||||
if err != nil {
|
||||
@@ -80,7 +80,7 @@ func Example_withTLS() {
|
||||
tp, err := sdktrace.NewProvider(
|
||||
sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
|
||||
sdktrace.WithBatcher(exp, // add following two options to ensure flush
|
||||
sdktrace.WithScheduleDelayMillis(5),
|
||||
sdktrace.WithBatchTimeout(5),
|
||||
sdktrace.WithMaxExportBatchSize(10),
|
||||
))
|
||||
if err != nil {
|
||||
|
||||
@@ -9,7 +9,7 @@ require (
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.14.3 // indirect
|
||||
github.com/open-telemetry/opentelemetry-proto v0.3.0
|
||||
github.com/stretchr/testify v1.4.0
|
||||
go.opentelemetry.io/otel v0.5.0
|
||||
go.opentelemetry.io/otel v0.6.0
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
google.golang.org/grpc v1.27.1
|
||||
)
|
||||
|
||||
@@ -61,7 +61,7 @@ type result struct {
|
||||
|
||||
// CheckpointSet transforms all records contained in a checkpoint into
|
||||
// batched OTLP ResourceMetrics.
|
||||
func CheckpointSet(ctx context.Context, resource *resource.Resource, cps export.CheckpointSet, numWorkers uint) ([]*metricpb.ResourceMetrics, error) {
|
||||
func CheckpointSet(ctx context.Context, cps export.CheckpointSet, numWorkers uint) ([]*metricpb.ResourceMetrics, error) {
|
||||
records, errc := source(ctx, cps)
|
||||
|
||||
// Start a fixed number of goroutines to transform records.
|
||||
@@ -71,7 +71,7 @@ func CheckpointSet(ctx context.Context, resource *resource.Resource, cps export.
|
||||
for i := uint(0); i < numWorkers; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
transformer(ctx, resource, records, transformed)
|
||||
transformer(ctx, records, transformed)
|
||||
}()
|
||||
}
|
||||
go func() {
|
||||
@@ -116,7 +116,7 @@ func source(ctx context.Context, cps export.CheckpointSet) (<-chan export.Record
|
||||
|
||||
// transformer transforms records read from the passed in chan into
|
||||
// OTLP Metrics which are sent on the out chan.
|
||||
func transformer(ctx context.Context, resource *resource.Resource, in <-chan export.Record, out chan<- result) {
|
||||
func transformer(ctx context.Context, in <-chan export.Record, out chan<- result) {
|
||||
for r := range in {
|
||||
m, err := Record(r)
|
||||
// Propagate errors, but do not send empty results.
|
||||
@@ -124,7 +124,7 @@ func transformer(ctx context.Context, resource *resource.Resource, in <-chan exp
|
||||
continue
|
||||
}
|
||||
res := result{
|
||||
Resource: resource,
|
||||
Resource: r.Resource(),
|
||||
Library: r.Descriptor().LibraryName(),
|
||||
Metric: m,
|
||||
Err: err,
|
||||
|
||||
@@ -111,7 +111,7 @@ func TestMinMaxSumCountMetricDescriptor(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
"mmsc-test-a",
|
||||
metric.MeasureKind,
|
||||
metric.ValueRecorderKind,
|
||||
"test-a-description",
|
||||
unit.Dimensionless,
|
||||
metric.Int64NumberKind,
|
||||
@@ -160,7 +160,7 @@ func TestMinMaxSumCountMetricDescriptor(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMinMaxSumCountDatapoints(t *testing.T) {
|
||||
desc := metric.NewDescriptor("", metric.MeasureKind, metric.Int64NumberKind)
|
||||
desc := metric.NewDescriptor("", metric.ValueRecorderKind, metric.Int64NumberKind)
|
||||
labels := label.NewSet()
|
||||
mmsc := minmaxsumcount.New(&desc)
|
||||
assert.NoError(t, mmsc.Update(context.Background(), 1, &desc))
|
||||
@@ -228,7 +228,7 @@ func TestSumMetricDescriptor(t *testing.T) {
|
||||
},
|
||||
{
|
||||
"sum-test-b",
|
||||
metric.MeasureKind, // This shouldn't change anything.
|
||||
metric.ValueRecorderKind, // This shouldn't change anything.
|
||||
"test-b-description",
|
||||
unit.Milliseconds,
|
||||
metric.Float64NumberKind,
|
||||
@@ -257,7 +257,7 @@ func TestSumMetricDescriptor(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSumInt64DataPoints(t *testing.T) {
|
||||
desc := metric.NewDescriptor("", metric.MeasureKind, metric.Int64NumberKind)
|
||||
desc := metric.NewDescriptor("", metric.ValueRecorderKind, metric.Int64NumberKind)
|
||||
labels := label.NewSet()
|
||||
s := sumAgg.New()
|
||||
assert.NoError(t, s.Update(context.Background(), metric.Number(1), &desc))
|
||||
@@ -271,7 +271,7 @@ func TestSumInt64DataPoints(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSumFloat64DataPoints(t *testing.T) {
|
||||
desc := metric.NewDescriptor("", metric.MeasureKind, metric.Float64NumberKind)
|
||||
desc := metric.NewDescriptor("", metric.ValueRecorderKind, metric.Float64NumberKind)
|
||||
labels := label.NewSet()
|
||||
s := sumAgg.New()
|
||||
assert.NoError(t, s.Update(context.Background(), metric.NewFloat64Number(1), &desc))
|
||||
@@ -285,7 +285,7 @@ func TestSumFloat64DataPoints(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSumErrUnknownValueType(t *testing.T) {
|
||||
desc := metric.NewDescriptor("", metric.MeasureKind, metric.NumberKind(-1))
|
||||
desc := metric.NewDescriptor("", metric.ValueRecorderKind, metric.NumberKind(-1))
|
||||
labels := label.NewSet()
|
||||
s := sumAgg.New()
|
||||
_, err := sum(&desc, &labels, s)
|
||||
|
||||
@@ -31,7 +31,6 @@ import (
|
||||
"go.opentelemetry.io/otel/exporters/otlp/internal/transform"
|
||||
metricsdk "go.opentelemetry.io/otel/sdk/export/metric"
|
||||
tracesdk "go.opentelemetry.io/otel/sdk/export/trace"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
type Exporter struct {
|
||||
@@ -212,7 +211,7 @@ func (e *Exporter) Stop() error {
|
||||
// Export implements the "go.opentelemetry.io/otel/sdk/export/metric".Exporter
|
||||
// interface. It transforms and batches metric Records into OTLP Metrics and
|
||||
// transmits them to the configured collector.
|
||||
func (e *Exporter) Export(parent context.Context, resource *resource.Resource, cps metricsdk.CheckpointSet) error {
|
||||
func (e *Exporter) Export(parent context.Context, cps metricsdk.CheckpointSet) error {
|
||||
// Unify the parent context Done signal with the exporter stopCh.
|
||||
ctx, cancel := context.WithCancel(parent)
|
||||
defer cancel()
|
||||
@@ -224,7 +223,7 @@ func (e *Exporter) Export(parent context.Context, resource *resource.Resource, c
|
||||
}
|
||||
}(ctx, cancel)
|
||||
|
||||
rms, err := transform.CheckpointSet(ctx, resource, cps, e.c.numWorkers)
|
||||
rms, err := transform.CheckpointSet(ctx, cps, e.c.numWorkers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
"go.opentelemetry.io/otel/sdk/metric/controller/push"
|
||||
integrator "go.opentelemetry.io/otel/sdk/metric/integrator/simple"
|
||||
"go.opentelemetry.io/otel/sdk/metric/selector/simple"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
@@ -81,18 +82,22 @@ func newExporterEndToEndTest(t *testing.T, additionalOpts []otlp.ExporterOption)
|
||||
pOpts := []sdktrace.ProviderOption{
|
||||
sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
|
||||
sdktrace.WithBatcher(exp, // add following two options to ensure flush
|
||||
sdktrace.WithScheduleDelayMillis(15),
|
||||
sdktrace.WithBatchTimeout(15),
|
||||
sdktrace.WithMaxExportBatchSize(10),
|
||||
),
|
||||
}
|
||||
tp1, err := sdktrace.NewProvider(append(pOpts,
|
||||
sdktrace.WithResourceAttributes(kv.String("rk1", "rv11)"),
|
||||
kv.Int64("rk2", 5)))...)
|
||||
sdktrace.WithResource(resource.New(
|
||||
kv.String("rk1", "rv11)"),
|
||||
kv.Int64("rk2", 5),
|
||||
)))...)
|
||||
assert.NoError(t, err)
|
||||
|
||||
tp2, err := sdktrace.NewProvider(append(pOpts,
|
||||
sdktrace.WithResourceAttributes(kv.String("rk1", "rv12)"),
|
||||
kv.Float32("rk3", 6.5)))...)
|
||||
sdktrace.WithResource(resource.New(
|
||||
kv.String("rk1", "rv12)"),
|
||||
kv.Float32("rk3", 6.5),
|
||||
)))...)
|
||||
assert.NoError(t, err)
|
||||
|
||||
tr1 := tp1.Tracer("test-tracer1")
|
||||
@@ -109,13 +114,13 @@ func newExporterEndToEndTest(t *testing.T, additionalOpts []otlp.ExporterOption)
|
||||
span.End()
|
||||
}
|
||||
|
||||
selector := simple.NewWithExactMeasure()
|
||||
selector := simple.NewWithExactDistribution()
|
||||
integrator := integrator.New(selector, true)
|
||||
pusher := push.New(integrator, exp, 60*time.Second)
|
||||
pusher := push.New(integrator, exp)
|
||||
pusher.Start()
|
||||
|
||||
ctx := context.Background()
|
||||
meter := pusher.Meter("test-meter")
|
||||
meter := pusher.Provider().Meter("test-meter")
|
||||
labels := []kv.KeyValue{kv.Bool("test", true)}
|
||||
|
||||
type data struct {
|
||||
@@ -124,12 +129,12 @@ func newExporterEndToEndTest(t *testing.T, additionalOpts []otlp.ExporterOption)
|
||||
val int64
|
||||
}
|
||||
instruments := map[string]data{
|
||||
"test-int64-counter": {metric.CounterKind, metricapi.Int64NumberKind, 1},
|
||||
"test-float64-counter": {metric.CounterKind, metricapi.Float64NumberKind, 1},
|
||||
"test-int64-measure": {metric.MeasureKind, metricapi.Int64NumberKind, 2},
|
||||
"test-float64-measure": {metric.MeasureKind, metricapi.Float64NumberKind, 2},
|
||||
"test-int64-observer": {metric.ObserverKind, metricapi.Int64NumberKind, 3},
|
||||
"test-float64-observer": {metric.ObserverKind, metricapi.Float64NumberKind, 3},
|
||||
"test-int64-counter": {metric.CounterKind, metricapi.Int64NumberKind, 1},
|
||||
"test-float64-counter": {metric.CounterKind, metricapi.Float64NumberKind, 1},
|
||||
"test-int64-valuerecorder": {metric.ValueRecorderKind, metricapi.Int64NumberKind, 2},
|
||||
"test-float64-valuerecorder": {metric.ValueRecorderKind, metricapi.Float64NumberKind, 2},
|
||||
"test-int64-valueobserver": {metric.ValueObserverKind, metricapi.Int64NumberKind, 3},
|
||||
"test-float64-valueobserver": {metric.ValueObserverKind, metricapi.Float64NumberKind, 3},
|
||||
}
|
||||
for name, data := range instruments {
|
||||
switch data.iKind {
|
||||
@@ -142,27 +147,27 @@ func newExporterEndToEndTest(t *testing.T, additionalOpts []otlp.ExporterOption)
|
||||
default:
|
||||
assert.Failf(t, "unsupported number testing kind", data.nKind.String())
|
||||
}
|
||||
case metric.MeasureKind:
|
||||
case metric.ValueRecorderKind:
|
||||
switch data.nKind {
|
||||
case metricapi.Int64NumberKind:
|
||||
metricapi.Must(meter).NewInt64Measure(name).Record(ctx, data.val, labels...)
|
||||
metricapi.Must(meter).NewInt64ValueRecorder(name).Record(ctx, data.val, labels...)
|
||||
case metricapi.Float64NumberKind:
|
||||
metricapi.Must(meter).NewFloat64Measure(name).Record(ctx, float64(data.val), labels...)
|
||||
metricapi.Must(meter).NewFloat64ValueRecorder(name).Record(ctx, float64(data.val), labels...)
|
||||
default:
|
||||
assert.Failf(t, "unsupported number testing kind", data.nKind.String())
|
||||
}
|
||||
case metric.ObserverKind:
|
||||
case metric.ValueObserverKind:
|
||||
switch data.nKind {
|
||||
case metricapi.Int64NumberKind:
|
||||
callback := func(v int64) metricapi.Int64ObserverCallback {
|
||||
return metricapi.Int64ObserverCallback(func(result metricapi.Int64ObserverResult) { result.Observe(v, labels...) })
|
||||
return metricapi.Int64ObserverCallback(func(_ context.Context, result metricapi.Int64ObserverResult) { result.Observe(v, labels...) })
|
||||
}(data.val)
|
||||
metricapi.Must(meter).RegisterInt64Observer(name, callback)
|
||||
metricapi.Must(meter).NewInt64ValueObserver(name, callback)
|
||||
case metricapi.Float64NumberKind:
|
||||
callback := func(v float64) metricapi.Float64ObserverCallback {
|
||||
return metricapi.Float64ObserverCallback(func(result metricapi.Float64ObserverResult) { result.Observe(v, labels...) })
|
||||
return metricapi.Float64ObserverCallback(func(_ context.Context, result metricapi.Float64ObserverResult) { result.Observe(v, labels...) })
|
||||
}(float64(data.val))
|
||||
metricapi.Must(meter).RegisterFloat64Observer(name, callback)
|
||||
metricapi.Must(meter).NewFloat64ValueObserver(name, callback)
|
||||
default:
|
||||
assert.Failf(t, "unsupported number testing kind", data.nKind.String())
|
||||
}
|
||||
@@ -246,7 +251,7 @@ func newExporterEndToEndTest(t *testing.T, additionalOpts []otlp.ExporterOption)
|
||||
default:
|
||||
assert.Failf(t, "invalid number kind", data.nKind.String())
|
||||
}
|
||||
case metric.MeasureKind, metric.ObserverKind:
|
||||
case metric.ValueRecorderKind, metric.ValueObserverKind:
|
||||
assert.Equal(t, metricpb.MetricDescriptor_SUMMARY.String(), desc.GetType().String())
|
||||
m.GetSummaryDataPoints()
|
||||
if dp := m.GetSummaryDataPoints(); assert.Len(t, dp, 1) {
|
||||
|
||||
@@ -16,6 +16,7 @@ package otlp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
colmetricpb "github.com/open-telemetry/opentelemetry-proto/gen/go/collector/metrics/v1"
|
||||
@@ -60,10 +61,11 @@ func (m *metricsServiceClientStub) Reset() {
|
||||
}
|
||||
|
||||
type checkpointSet struct {
|
||||
sync.RWMutex
|
||||
records []metricsdk.Record
|
||||
}
|
||||
|
||||
func (m checkpointSet) ForEach(fn func(metricsdk.Record) error) error {
|
||||
func (m *checkpointSet) ForEach(fn func(metricsdk.Record) error) error {
|
||||
for _, r := range m.records {
|
||||
if err := fn(r); err != nil && err != aggregator.ErrNoData {
|
||||
return err
|
||||
@@ -188,10 +190,10 @@ func TestNoGroupingExport(t *testing.T) {
|
||||
)
|
||||
}
|
||||
|
||||
func TestMeasureMetricGroupingExport(t *testing.T) {
|
||||
func TestValuerecorderMetricGroupingExport(t *testing.T) {
|
||||
r := record{
|
||||
"measure",
|
||||
metric.MeasureKind,
|
||||
"valuerecorder",
|
||||
metric.ValueRecorderKind,
|
||||
metric.Int64NumberKind,
|
||||
nil,
|
||||
nil,
|
||||
@@ -205,7 +207,7 @@ func TestMeasureMetricGroupingExport(t *testing.T) {
|
||||
Metrics: []*metricpb.Metric{
|
||||
{
|
||||
MetricDescriptor: &metricpb.MetricDescriptor{
|
||||
Name: "measure",
|
||||
Name: "valuerecorder",
|
||||
Type: metricpb.MetricDescriptor_SUMMARY,
|
||||
Labels: []*commonpb.StringKeyValue{
|
||||
{
|
||||
@@ -659,11 +661,10 @@ func runMetricExportTest(t *testing.T, exp *Exporter, rs []record, expected []me
|
||||
|
||||
equiv := r.resource.Equivalent()
|
||||
resources[equiv] = r.resource
|
||||
recs[equiv] = append(recs[equiv], metricsdk.NewRecord(&desc, &labs, agg))
|
||||
recs[equiv] = append(recs[equiv], metricsdk.NewRecord(&desc, &labs, r.resource, agg))
|
||||
}
|
||||
for equiv, records := range recs {
|
||||
resource := resources[equiv]
|
||||
assert.NoError(t, exp.Export(context.Background(), resource, checkpointSet{records: records}))
|
||||
for _, records := range recs {
|
||||
assert.NoError(t, exp.Export(context.Background(), &checkpointSet{records: records}))
|
||||
}
|
||||
|
||||
// assert.ElementsMatch does not equate nested slices of different order,
|
||||
@@ -713,8 +714,6 @@ func TestEmptyMetricExport(t *testing.T) {
|
||||
exp.metricExporter = msc
|
||||
exp.started = true
|
||||
|
||||
resource := resource.New(kv.String("R", "S"))
|
||||
|
||||
for _, test := range []struct {
|
||||
records []metricsdk.Record
|
||||
want []metricpb.ResourceMetrics
|
||||
@@ -729,7 +728,7 @@ func TestEmptyMetricExport(t *testing.T) {
|
||||
},
|
||||
} {
|
||||
msc.Reset()
|
||||
require.NoError(t, exp.Export(context.Background(), resource, checkpointSet{records: test.records}))
|
||||
require.NoError(t, exp.Export(context.Background(), &checkpointSet{records: test.records}))
|
||||
assert.Equal(t, test.want, msc.ResourceMetrics())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ require (
|
||||
github.com/apache/thrift v0.13.0
|
||||
github.com/google/go-cmp v0.4.0
|
||||
github.com/stretchr/testify v1.4.0
|
||||
go.opentelemetry.io/otel v0.5.0
|
||||
go.opentelemetry.io/otel v0.6.0
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
||||
google.golang.org/api v0.20.0
|
||||
google.golang.org/grpc v1.27.1
|
||||
|
||||
@@ -7,6 +7,6 @@ replace go.opentelemetry.io/otel => ../../..
|
||||
require (
|
||||
github.com/openzipkin/zipkin-go v0.2.2
|
||||
github.com/stretchr/testify v1.4.0
|
||||
go.opentelemetry.io/otel v0.5.0
|
||||
go.opentelemetry.io/otel v0.6.0
|
||||
google.golang.org/grpc v1.27.1
|
||||
)
|
||||
|
||||
@@ -105,6 +105,7 @@ func (e *Exporter) ExportSpans(ctx context.Context, batch []*export.SpanData) {
|
||||
e.logf("failed to create request to %s: %v", e.url, err)
|
||||
return
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := e.client.Do(req)
|
||||
if err != nil {
|
||||
e.logf("request to %s failed: %v", e.url, err)
|
||||
|
||||
@@ -26,6 +26,29 @@ import (
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
)
|
||||
|
||||
// Option is a function that allows configuration of the grpctrace Extract()
|
||||
// and Inject() functions
|
||||
type Option func(*config)
|
||||
|
||||
type config struct {
|
||||
propagators propagation.Propagators
|
||||
}
|
||||
|
||||
func newConfig(opts []Option) *config {
|
||||
c := &config{propagators: global.Propagators()}
|
||||
for _, o := range opts {
|
||||
o(c)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// WithPropagators sets the propagators to use for Extraction and Injection
|
||||
func WithPropagators(props propagation.Propagators) Option {
|
||||
return func(c *config) {
|
||||
c.propagators = props
|
||||
}
|
||||
}
|
||||
|
||||
type metadataSupplier struct {
|
||||
metadata *metadata.MD
|
||||
}
|
||||
@@ -45,8 +68,9 @@ func (s *metadataSupplier) Set(key string, value string) {
|
||||
// Inject injects correlation context and span context into the gRPC
|
||||
// metadata object. This function is meant to be used on outgoing
|
||||
// requests.
|
||||
func Inject(ctx context.Context, metadata *metadata.MD) {
|
||||
propagation.InjectHTTP(ctx, global.Propagators(), &metadataSupplier{
|
||||
func Inject(ctx context.Context, metadata *metadata.MD, opts ...Option) {
|
||||
c := newConfig(opts)
|
||||
propagation.InjectHTTP(ctx, c.propagators, &metadataSupplier{
|
||||
metadata: metadata,
|
||||
})
|
||||
}
|
||||
@@ -54,8 +78,9 @@ func Inject(ctx context.Context, metadata *metadata.MD) {
|
||||
// Extract returns the correlation context and span context that
|
||||
// another service encoded in the gRPC metadata object with Inject.
|
||||
// This function is meant to be used on incoming requests.
|
||||
func Extract(ctx context.Context, metadata *metadata.MD) ([]kv.KeyValue, trace.SpanContext) {
|
||||
ctx = propagation.ExtractHTTP(ctx, global.Propagators(), &metadataSupplier{
|
||||
func Extract(ctx context.Context, metadata *metadata.MD, opts ...Option) ([]kv.KeyValue, trace.SpanContext) {
|
||||
c := newConfig(opts)
|
||||
ctx = propagation.ExtractHTTP(ctx, c.propagators, &metadataSupplier{
|
||||
metadata: metadata,
|
||||
})
|
||||
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"net"
|
||||
"regexp"
|
||||
|
||||
"go.opentelemetry.io/otel/api/standard"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
@@ -33,19 +35,29 @@ import (
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
)
|
||||
|
||||
type messageType kv.KeyValue
|
||||
|
||||
// Event adds an event of the messageType to the span associated with the
|
||||
// passed context with id and size (if message is a proto message).
|
||||
func (m messageType) Event(ctx context.Context, id int, message interface{}) {
|
||||
span := trace.SpanFromContext(ctx)
|
||||
if p, ok := message.(proto.Message); ok {
|
||||
span.AddEvent(ctx, "message",
|
||||
kv.KeyValue(m),
|
||||
standard.RPCMessageIDKey.Int(id),
|
||||
standard.RPCMessageUncompressedSizeKey.Int(proto.Size(p)),
|
||||
)
|
||||
} else {
|
||||
span.AddEvent(ctx, "message",
|
||||
kv.KeyValue(m),
|
||||
standard.RPCMessageIDKey.Int(id),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
rpcServiceKey = kv.Key("rpc.service")
|
||||
netPeerIPKey = kv.Key("net.peer.ip")
|
||||
netPeerPortKey = kv.Key("net.peer.port")
|
||||
|
||||
messageTypeKey = kv.Key("message.type")
|
||||
messageIDKey = kv.Key("message.id")
|
||||
messageUncompressedSizeKey = kv.Key("message.uncompressed_size")
|
||||
)
|
||||
|
||||
const (
|
||||
messageTypeSent = "SENT"
|
||||
messageTypeReceived = "RECEIVED"
|
||||
messageSent = messageType(standard.RPCMessageTypeSent)
|
||||
messageReceived = messageType(standard.RPCMessageTypeReceived)
|
||||
)
|
||||
|
||||
// UnaryClientInterceptor returns a grpc.UnaryClientInterceptor suitable
|
||||
@@ -73,18 +85,18 @@ func UnaryClientInterceptor(tracer trace.Tracer) grpc.UnaryClientInterceptor {
|
||||
ctx, method,
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(peerInfoFromTarget(cc.Target())...),
|
||||
trace.WithAttributes(rpcServiceKey.String(serviceFromFullMethod(method))),
|
||||
trace.WithAttributes(standard.RPCServiceKey.String(serviceFromFullMethod(method))),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
Inject(ctx, &metadataCopy)
|
||||
ctx = metadata.NewOutgoingContext(ctx, metadataCopy)
|
||||
|
||||
addEventForMessageSent(ctx, 1, req)
|
||||
messageSent.Event(ctx, 1, req)
|
||||
|
||||
err := invoker(ctx, method, req, reply, cc, opts...)
|
||||
|
||||
addEventForMessageReceived(ctx, 1, reply)
|
||||
messageReceived.Event(ctx, 1, reply)
|
||||
|
||||
if err != nil {
|
||||
s, _ := status.FromError(err)
|
||||
@@ -113,9 +125,10 @@ const (
|
||||
type clientStream struct {
|
||||
grpc.ClientStream
|
||||
|
||||
desc *grpc.StreamDesc
|
||||
events chan streamEvent
|
||||
finished chan error
|
||||
desc *grpc.StreamDesc
|
||||
events chan streamEvent
|
||||
eventsDone chan struct{}
|
||||
finished chan error
|
||||
|
||||
receivedMessageID int
|
||||
sentMessageID int
|
||||
@@ -127,14 +140,14 @@ func (w *clientStream) RecvMsg(m interface{}) error {
|
||||
err := w.ClientStream.RecvMsg(m)
|
||||
|
||||
if err == nil && !w.desc.ServerStreams {
|
||||
w.events <- streamEvent{receiveEndEvent, nil}
|
||||
w.sendStreamEvent(receiveEndEvent, nil)
|
||||
} else if err == io.EOF {
|
||||
w.events <- streamEvent{receiveEndEvent, nil}
|
||||
w.sendStreamEvent(receiveEndEvent, nil)
|
||||
} else if err != nil {
|
||||
w.events <- streamEvent{errorEvent, err}
|
||||
w.sendStreamEvent(errorEvent, err)
|
||||
} else {
|
||||
w.receivedMessageID++
|
||||
addEventForMessageReceived(w.Context(), w.receivedMessageID, m)
|
||||
messageReceived.Event(w.Context(), w.receivedMessageID, m)
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -144,10 +157,10 @@ func (w *clientStream) SendMsg(m interface{}) error {
|
||||
err := w.ClientStream.SendMsg(m)
|
||||
|
||||
w.sentMessageID++
|
||||
addEventForMessageSent(w.Context(), w.sentMessageID, m)
|
||||
messageSent.Event(w.Context(), w.sentMessageID, m)
|
||||
|
||||
if err != nil {
|
||||
w.events <- streamEvent{errorEvent, err}
|
||||
w.sendStreamEvent(errorEvent, err)
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -157,7 +170,7 @@ func (w *clientStream) Header() (metadata.MD, error) {
|
||||
md, err := w.ClientStream.Header()
|
||||
|
||||
if err != nil {
|
||||
w.events <- streamEvent{errorEvent, err}
|
||||
w.sendStreamEvent(errorEvent, err)
|
||||
}
|
||||
|
||||
return md, err
|
||||
@@ -167,9 +180,9 @@ func (w *clientStream) CloseSend() error {
|
||||
err := w.ClientStream.CloseSend()
|
||||
|
||||
if err != nil {
|
||||
w.events <- streamEvent{errorEvent, err}
|
||||
w.sendStreamEvent(errorEvent, err)
|
||||
} else {
|
||||
w.events <- streamEvent{closeEvent, nil}
|
||||
w.sendStreamEvent(closeEvent, nil)
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -181,10 +194,13 @@ const (
|
||||
)
|
||||
|
||||
func wrapClientStream(s grpc.ClientStream, desc *grpc.StreamDesc) *clientStream {
|
||||
events := make(chan streamEvent, 1)
|
||||
events := make(chan streamEvent)
|
||||
eventsDone := make(chan struct{})
|
||||
finished := make(chan error)
|
||||
|
||||
go func() {
|
||||
defer close(eventsDone)
|
||||
|
||||
// Both streams have to be closed
|
||||
state := byte(0)
|
||||
|
||||
@@ -196,12 +212,12 @@ func wrapClientStream(s grpc.ClientStream, desc *grpc.StreamDesc) *clientStream
|
||||
state |= receiveEndedState
|
||||
case errorEvent:
|
||||
finished <- event.Err
|
||||
close(events)
|
||||
return
|
||||
}
|
||||
|
||||
if state == clientClosedState|receiveEndedState {
|
||||
finished <- nil
|
||||
close(events)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -210,10 +226,18 @@ func wrapClientStream(s grpc.ClientStream, desc *grpc.StreamDesc) *clientStream
|
||||
ClientStream: s,
|
||||
desc: desc,
|
||||
events: events,
|
||||
eventsDone: eventsDone,
|
||||
finished: finished,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *clientStream) sendStreamEvent(eventType streamEventType, err error) {
|
||||
select {
|
||||
case <-w.eventsDone:
|
||||
case w.events <- streamEvent{Type: eventType, Err: err}:
|
||||
}
|
||||
}
|
||||
|
||||
// StreamClientInterceptor returns a grpc.StreamClientInterceptor suitable
|
||||
// for use in a grpc.Dial call.
|
||||
//
|
||||
@@ -239,7 +263,7 @@ func StreamClientInterceptor(tracer trace.Tracer) grpc.StreamClientInterceptor {
|
||||
ctx, method,
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(peerInfoFromTarget(cc.Target())...),
|
||||
trace.WithAttributes(rpcServiceKey.String(serviceFromFullMethod(method))),
|
||||
trace.WithAttributes(standard.RPCServiceKey.String(serviceFromFullMethod(method))),
|
||||
)
|
||||
|
||||
Inject(ctx, &metadataCopy)
|
||||
@@ -293,19 +317,19 @@ func UnaryServerInterceptor(tracer trace.Tracer) grpc.UnaryServerInterceptor {
|
||||
info.FullMethod,
|
||||
trace.WithSpanKind(trace.SpanKindServer),
|
||||
trace.WithAttributes(peerInfoFromContext(ctx)...),
|
||||
trace.WithAttributes(rpcServiceKey.String(serviceFromFullMethod(info.FullMethod))),
|
||||
trace.WithAttributes(standard.RPCServiceKey.String(serviceFromFullMethod(info.FullMethod))),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
addEventForMessageReceived(ctx, 1, req)
|
||||
messageReceived.Event(ctx, 1, req)
|
||||
|
||||
resp, err := handler(ctx, req)
|
||||
|
||||
addEventForMessageSent(ctx, 1, resp)
|
||||
|
||||
if err != nil {
|
||||
s, _ := status.FromError(err)
|
||||
span.SetStatus(s.Code(), s.Message())
|
||||
messageSent.Event(ctx, 1, s.Proto())
|
||||
} else {
|
||||
messageSent.Event(ctx, 1, resp)
|
||||
}
|
||||
|
||||
return resp, err
|
||||
@@ -331,7 +355,7 @@ func (w *serverStream) RecvMsg(m interface{}) error {
|
||||
|
||||
if err == nil {
|
||||
w.receivedMessageID++
|
||||
addEventForMessageReceived(w.Context(), w.receivedMessageID, m)
|
||||
messageReceived.Event(w.Context(), w.receivedMessageID, m)
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -341,7 +365,7 @@ func (w *serverStream) SendMsg(m interface{}) error {
|
||||
err := w.ServerStream.SendMsg(m)
|
||||
|
||||
w.sentMessageID++
|
||||
addEventForMessageSent(w.Context(), w.sentMessageID, m)
|
||||
messageSent.Event(w.Context(), w.sentMessageID, m)
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -383,7 +407,7 @@ func StreamServerInterceptor(tracer trace.Tracer) grpc.StreamServerInterceptor {
|
||||
info.FullMethod,
|
||||
trace.WithSpanKind(trace.SpanKindServer),
|
||||
trace.WithAttributes(peerInfoFromContext(ctx)...),
|
||||
trace.WithAttributes(rpcServiceKey.String(serviceFromFullMethod(info.FullMethod))),
|
||||
trace.WithAttributes(standard.RPCServiceKey.String(serviceFromFullMethod(info.FullMethod))),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
@@ -410,8 +434,8 @@ func peerInfoFromTarget(target string) []kv.KeyValue {
|
||||
}
|
||||
|
||||
return []kv.KeyValue{
|
||||
netPeerIPKey.String(host),
|
||||
netPeerPortKey.String(port),
|
||||
standard.NetPeerIPKey.String(host),
|
||||
standard.NetPeerPortKey.String(port),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,25 +459,3 @@ func serviceFromFullMethod(method string) string {
|
||||
|
||||
return match[1]
|
||||
}
|
||||
|
||||
func addEventForMessageReceived(ctx context.Context, id int, m interface{}) {
|
||||
size := proto.Size(m.(proto.Message))
|
||||
|
||||
span := trace.SpanFromContext(ctx)
|
||||
span.AddEvent(ctx, "message",
|
||||
messageTypeKey.String(messageTypeReceived),
|
||||
messageIDKey.Int(id),
|
||||
messageUncompressedSizeKey.Int(size),
|
||||
)
|
||||
}
|
||||
|
||||
func addEventForMessageSent(ctx context.Context, id int, m interface{}) {
|
||||
size := proto.Size(m.(proto.Message))
|
||||
|
||||
span := trace.SpanFromContext(ctx)
|
||||
span.AddEvent(ctx, "message",
|
||||
messageTypeKey.String(messageTypeSent),
|
||||
messageIDKey.Int(id),
|
||||
messageUncompressedSizeKey.Int(size),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package grpctrace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/api/standard"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/kv/value"
|
||||
export "go.opentelemetry.io/otel/sdk/export/trace"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
type testExporter struct {
|
||||
mu sync.Mutex
|
||||
spanMap map[string]*export.SpanData
|
||||
}
|
||||
|
||||
func (t *testExporter) ExportSpan(ctx context.Context, s *export.SpanData) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.spanMap[s.Name] = s
|
||||
}
|
||||
|
||||
type mockUICInvoker struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (mcuici *mockUICInvoker) invoker(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, opts ...grpc.CallOption) error {
|
||||
mcuici.ctx = ctx
|
||||
return nil
|
||||
}
|
||||
|
||||
type mockProtoMessage struct{}
|
||||
|
||||
func (mm *mockProtoMessage) Reset() {
|
||||
}
|
||||
|
||||
func (mm *mockProtoMessage) String() string {
|
||||
return "mock"
|
||||
}
|
||||
|
||||
func (mm *mockProtoMessage) ProtoMessage() {
|
||||
}
|
||||
|
||||
func TestUnaryClientInterceptor(t *testing.T) {
|
||||
exp := &testExporter{spanMap: make(map[string]*export.SpanData)}
|
||||
tp, _ := sdktrace.NewProvider(sdktrace.WithSyncer(exp),
|
||||
sdktrace.WithConfig(sdktrace.Config{
|
||||
DefaultSampler: sdktrace.AlwaysSample(),
|
||||
},
|
||||
))
|
||||
|
||||
clientConn, err := grpc.Dial("fake:connection", grpc.WithInsecure())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create client connection: %v", err)
|
||||
}
|
||||
|
||||
tracer := tp.Tracer("grpctrace/client")
|
||||
unaryInterceptor := UnaryClientInterceptor(tracer)
|
||||
|
||||
req := &mockProtoMessage{}
|
||||
reply := &mockProtoMessage{}
|
||||
uniInterceptorInvoker := &mockUICInvoker{}
|
||||
|
||||
checks := []struct {
|
||||
name string
|
||||
expectedAttr map[kv.Key]value.Value
|
||||
eventsAttr []map[kv.Key]value.Value
|
||||
}{
|
||||
{
|
||||
name: "/github.com.serviceName/bar",
|
||||
expectedAttr: map[kv.Key]value.Value{
|
||||
standard.RPCServiceKey: value.String("serviceName"),
|
||||
standard.NetPeerIPKey: value.String("fake"),
|
||||
standard.NetPeerPortKey: value.String("connection"),
|
||||
},
|
||||
eventsAttr: []map[kv.Key]value.Value{
|
||||
{
|
||||
standard.RPCMessageTypeKey: value.String("SENT"),
|
||||
standard.RPCMessageIDKey: value.Int(1),
|
||||
standard.RPCMessageUncompressedSizeKey: value.Int(proto.Size(proto.Message(req))),
|
||||
},
|
||||
{
|
||||
standard.RPCMessageTypeKey: value.String("RECEIVED"),
|
||||
standard.RPCMessageIDKey: value.Int(1),
|
||||
standard.RPCMessageUncompressedSizeKey: value.Int(proto.Size(proto.Message(reply))),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "/serviceName/bar",
|
||||
expectedAttr: map[kv.Key]value.Value{
|
||||
standard.RPCServiceKey: value.String("serviceName"),
|
||||
standard.NetPeerIPKey: value.String("fake"),
|
||||
standard.NetPeerPortKey: value.String("connection"),
|
||||
},
|
||||
eventsAttr: []map[kv.Key]value.Value{
|
||||
{
|
||||
standard.RPCMessageTypeKey: value.String("SENT"),
|
||||
standard.RPCMessageIDKey: value.Int(1),
|
||||
standard.RPCMessageUncompressedSizeKey: value.Int(proto.Size(proto.Message(req))),
|
||||
},
|
||||
{
|
||||
standard.RPCMessageTypeKey: value.String("RECEIVED"),
|
||||
standard.RPCMessageIDKey: value.Int(1),
|
||||
standard.RPCMessageUncompressedSizeKey: value.Int(proto.Size(proto.Message(reply))),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "serviceName/bar",
|
||||
expectedAttr: map[kv.Key]value.Value{
|
||||
standard.RPCServiceKey: value.String("serviceName"),
|
||||
standard.NetPeerIPKey: value.String("fake"),
|
||||
standard.NetPeerPortKey: value.String("connection"),
|
||||
},
|
||||
eventsAttr: []map[kv.Key]value.Value{
|
||||
{
|
||||
standard.RPCMessageTypeKey: value.String("SENT"),
|
||||
standard.RPCMessageIDKey: value.Int(1),
|
||||
standard.RPCMessageUncompressedSizeKey: value.Int(proto.Size(proto.Message(req))),
|
||||
},
|
||||
{
|
||||
standard.RPCMessageTypeKey: value.String("RECEIVED"),
|
||||
standard.RPCMessageIDKey: value.Int(1),
|
||||
standard.RPCMessageUncompressedSizeKey: value.Int(proto.Size(proto.Message(reply))),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalidName",
|
||||
expectedAttr: map[kv.Key]value.Value{
|
||||
standard.RPCServiceKey: value.String(""),
|
||||
standard.NetPeerIPKey: value.String("fake"),
|
||||
standard.NetPeerPortKey: value.String("connection"),
|
||||
},
|
||||
eventsAttr: []map[kv.Key]value.Value{
|
||||
{
|
||||
standard.RPCMessageTypeKey: value.String("SENT"),
|
||||
standard.RPCMessageIDKey: value.Int(1),
|
||||
standard.RPCMessageUncompressedSizeKey: value.Int(proto.Size(proto.Message(req))),
|
||||
},
|
||||
{
|
||||
standard.RPCMessageTypeKey: value.String("RECEIVED"),
|
||||
standard.RPCMessageIDKey: value.Int(1),
|
||||
standard.RPCMessageUncompressedSizeKey: value.Int(proto.Size(proto.Message(reply))),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "/github.com.foo.serviceName_123/method",
|
||||
expectedAttr: map[kv.Key]value.Value{
|
||||
standard.RPCServiceKey: value.String("serviceName_123"),
|
||||
standard.NetPeerIPKey: value.String("fake"),
|
||||
standard.NetPeerPortKey: value.String("connection"),
|
||||
},
|
||||
eventsAttr: []map[kv.Key]value.Value{
|
||||
{
|
||||
standard.RPCMessageTypeKey: value.String("SENT"),
|
||||
standard.RPCMessageIDKey: value.Int(1),
|
||||
standard.RPCMessageUncompressedSizeKey: value.Int(proto.Size(proto.Message(req))),
|
||||
},
|
||||
{
|
||||
standard.RPCMessageTypeKey: value.String("RECEIVED"),
|
||||
standard.RPCMessageIDKey: value.Int(1),
|
||||
standard.RPCMessageUncompressedSizeKey: value.Int(proto.Size(proto.Message(reply))),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, check := range checks {
|
||||
err = unaryInterceptor(context.Background(), check.name, req, reply, clientConn, uniInterceptorInvoker.invoker)
|
||||
if err != nil {
|
||||
t.Errorf("failed to run unary interceptor: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
spanData, ok := exp.spanMap[check.name]
|
||||
if !ok {
|
||||
t.Errorf("no span data found for name < %s >", check.name)
|
||||
continue
|
||||
}
|
||||
|
||||
attrs := spanData.Attributes
|
||||
if len(check.expectedAttr) > len(attrs) {
|
||||
t.Errorf("attributes received are less than expected attributes, received %d, expected %d",
|
||||
len(attrs), len(check.expectedAttr))
|
||||
}
|
||||
for _, attr := range attrs {
|
||||
expectedAttr, ok := check.expectedAttr[attr.Key]
|
||||
if ok {
|
||||
if expectedAttr != attr.Value {
|
||||
t.Errorf("name: %s invalid %s found. expected %s, actual %s", check.name, string(attr.Key),
|
||||
expectedAttr.AsString(), attr.Value.AsString())
|
||||
}
|
||||
delete(check.expectedAttr, attr.Key)
|
||||
} else {
|
||||
t.Errorf("attribute %s not found in expected attributes map", string(attr.Key))
|
||||
}
|
||||
}
|
||||
|
||||
// Check if any expected attr not seen
|
||||
if len(check.expectedAttr) > 0 {
|
||||
for attr := range check.expectedAttr {
|
||||
t.Errorf("missing attribute %s in span", string(attr))
|
||||
}
|
||||
}
|
||||
|
||||
events := spanData.MessageEvents
|
||||
if len(check.eventsAttr) > len(events) {
|
||||
t.Errorf("events received are less than expected events, received %d, expected %d",
|
||||
len(events), len(check.eventsAttr))
|
||||
}
|
||||
for event := 0; event < len(check.eventsAttr); event++ {
|
||||
for _, attr := range events[event].Attributes {
|
||||
expectedAttr, ok := check.eventsAttr[event][attr.Key]
|
||||
if ok {
|
||||
if attr.Value != expectedAttr {
|
||||
t.Errorf("invalid value for attribute %s in events, expected %s actual %s",
|
||||
string(attr.Key), attr.Value.AsString(), expectedAttr.AsString())
|
||||
}
|
||||
delete(check.eventsAttr[event], attr.Key)
|
||||
} else {
|
||||
t.Errorf("attribute in event %s not found in expected attributes map", string(attr.Key))
|
||||
}
|
||||
}
|
||||
if len(check.eventsAttr[event]) > 0 {
|
||||
for attr := range check.eventsAttr[event] {
|
||||
t.Errorf("missing attribute %s in span event", string(attr))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type mockClientStream struct {
|
||||
Desc *grpc.StreamDesc
|
||||
Ctx context.Context
|
||||
}
|
||||
|
||||
func (mockClientStream) SendMsg(m interface{}) error { return nil }
|
||||
func (mockClientStream) RecvMsg(m interface{}) error { return nil }
|
||||
func (mockClientStream) CloseSend() error { return nil }
|
||||
func (c mockClientStream) Context() context.Context { return c.Ctx }
|
||||
func (mockClientStream) Header() (metadata.MD, error) { return nil, nil }
|
||||
func (mockClientStream) Trailer() metadata.MD { return nil }
|
||||
|
||||
func TestStreamClientInterceptor(t *testing.T) {
|
||||
exp := &testExporter{spanMap: make(map[string]*export.SpanData)}
|
||||
tp, _ := sdktrace.NewProvider(sdktrace.WithSyncer(exp),
|
||||
sdktrace.WithConfig(sdktrace.Config{
|
||||
DefaultSampler: sdktrace.AlwaysSample(),
|
||||
},
|
||||
))
|
||||
clientConn, err := grpc.Dial("fake:connection", grpc.WithInsecure())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create client connection: %v", err)
|
||||
}
|
||||
|
||||
// tracer
|
||||
tracer := tp.Tracer("grpctrace/Server")
|
||||
streamCI := StreamClientInterceptor(tracer)
|
||||
|
||||
var mockClStr mockClientStream
|
||||
methodName := "/github.com.serviceName/bar"
|
||||
|
||||
streamClient, err := streamCI(context.Background(),
|
||||
&grpc.StreamDesc{ServerStreams: true},
|
||||
clientConn,
|
||||
methodName,
|
||||
func(ctx context.Context,
|
||||
desc *grpc.StreamDesc,
|
||||
cc *grpc.ClientConn,
|
||||
method string,
|
||||
opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
mockClStr = mockClientStream{Desc: desc, Ctx: ctx}
|
||||
return mockClStr, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("failed to initialize grpc stream client: %v", err)
|
||||
}
|
||||
|
||||
// no span exported while stream is open
|
||||
if _, ok := exp.spanMap[methodName]; ok {
|
||||
t.Fatalf("span shouldn't end while stream is open")
|
||||
}
|
||||
|
||||
req := &mockProtoMessage{}
|
||||
reply := &mockProtoMessage{}
|
||||
|
||||
// send and receive fake data
|
||||
for i := 0; i < 10; i++ {
|
||||
_ = streamClient.SendMsg(req)
|
||||
_ = streamClient.RecvMsg(reply)
|
||||
}
|
||||
|
||||
// close client and server stream
|
||||
_ = streamClient.CloseSend()
|
||||
mockClStr.Desc.ServerStreams = false
|
||||
_ = streamClient.RecvMsg(reply)
|
||||
|
||||
// added retry because span end is called in separate go routine
|
||||
var spanData *export.SpanData
|
||||
for retry := 0; retry < 5; retry++ {
|
||||
ok := false
|
||||
exp.mu.Lock()
|
||||
spanData, ok = exp.spanMap[methodName]
|
||||
exp.mu.Unlock()
|
||||
if ok {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second * 1)
|
||||
}
|
||||
if spanData == nil {
|
||||
t.Fatalf("no span data found for name < %s >", methodName)
|
||||
}
|
||||
|
||||
attrs := spanData.Attributes
|
||||
expectedAttr := map[kv.Key]string{
|
||||
standard.RPCServiceKey: "serviceName",
|
||||
standard.NetPeerIPKey: "fake",
|
||||
standard.NetPeerPortKey: "connection",
|
||||
}
|
||||
|
||||
for _, attr := range attrs {
|
||||
expected, ok := expectedAttr[attr.Key]
|
||||
if ok {
|
||||
if expected != attr.Value.AsString() {
|
||||
t.Errorf("name: %s invalid %s found. expected %s, actual %s", methodName, string(attr.Key),
|
||||
expected, attr.Value.AsString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
events := spanData.MessageEvents
|
||||
if len(events) != 20 {
|
||||
t.Fatalf("incorrect number of events expected 20 got %d", len(events))
|
||||
}
|
||||
for i := 0; i < 20; i += 2 {
|
||||
msgID := i/2 + 1
|
||||
validate := func(eventName string, attrs []kv.KeyValue) {
|
||||
for _, attr := range attrs {
|
||||
if attr.Key == standard.RPCMessageTypeKey && attr.Value.AsString() != eventName {
|
||||
t.Errorf("invalid event on index: %d expecting %s event, receive %s event", i, eventName, attr.Value.AsString())
|
||||
}
|
||||
if attr.Key == standard.RPCMessageIDKey && attr.Value != value.Int(msgID) {
|
||||
t.Errorf("invalid id for message event expected %d received %d", msgID, attr.Value.AsInt32())
|
||||
}
|
||||
}
|
||||
}
|
||||
validate("SENT", events[i].Attributes)
|
||||
validate("RECEIVED", events[i+1].Attributes)
|
||||
}
|
||||
|
||||
// ensure CloseSend can be subsequently called
|
||||
_ = streamClient.CloseSend()
|
||||
}
|
||||
|
||||
func TestServerInterceptorError(t *testing.T) {
|
||||
exp := &testExporter{spanMap: make(map[string]*export.SpanData)}
|
||||
tp, err := sdktrace.NewProvider(
|
||||
sdktrace.WithSyncer(exp),
|
||||
sdktrace.WithConfig(sdktrace.Config{
|
||||
DefaultSampler: sdktrace.AlwaysSample(),
|
||||
}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
tracer := tp.Tracer("grpctrace/Server")
|
||||
usi := UnaryServerInterceptor(tracer)
|
||||
deniedErr := status.Error(codes.PermissionDenied, "PERMISSION_DENIED_TEXT")
|
||||
handler := func(_ context.Context, _ interface{}) (interface{}, error) {
|
||||
return nil, deniedErr
|
||||
}
|
||||
_, err = usi(context.Background(), &mockProtoMessage{}, &grpc.UnaryServerInfo{}, handler)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, err, deniedErr)
|
||||
|
||||
span, ok := exp.spanMap[""]
|
||||
if !ok {
|
||||
t.Fatalf("failed to export error span")
|
||||
}
|
||||
assert.Equal(t, span.StatusCode, codes.PermissionDenied)
|
||||
assert.Contains(t, deniedErr.Error(), span.StatusMessage)
|
||||
assert.Len(t, span.MessageEvents, 2)
|
||||
assert.Equal(t, []kv.KeyValue{
|
||||
kv.String("message.type", "SENT"),
|
||||
kv.Int("message.id", 1),
|
||||
kv.Int("message.uncompressed_size", 26),
|
||||
}, span.MessageEvents[1].Attributes)
|
||||
}
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"go.opentelemetry.io/otel/api/standard"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
@@ -67,7 +69,7 @@ func NewClientTrace(ctx context.Context) *httptrace.ClientTrace {
|
||||
activeHooks: make(map[string]context.Context),
|
||||
}
|
||||
|
||||
ct.tr = global.Tracer("go.opentelemetry.io/otel/plugin/httptrace")
|
||||
ct.tr = global.Tracer("go.opentelemetry.io/otel/instrumentation/httptrace")
|
||||
|
||||
return &httptrace.ClientTrace{
|
||||
GetConn: ct.getConn,
|
||||
@@ -150,7 +152,7 @@ func (ct *clientTracer) span(hook string) trace.Span {
|
||||
}
|
||||
|
||||
func (ct *clientTracer) getConn(host string) {
|
||||
ct.start("http.getconn", "http.getconn", HostKey.String(host))
|
||||
ct.start("http.getconn", "http.getconn", standard.HTTPHostKey.String(host))
|
||||
}
|
||||
|
||||
func (ct *clientTracer) gotConn(info httptrace.GotConnInfo) {
|
||||
@@ -170,7 +172,7 @@ func (ct *clientTracer) gotFirstResponseByte() {
|
||||
}
|
||||
|
||||
func (ct *clientTracer) dnsStart(info httptrace.DNSStartInfo) {
|
||||
ct.start("http.dns", "http.dns", HostKey.String(info.Host))
|
||||
ct.start("http.dns", "http.dns", standard.HTTPHostKey.String(info.Host))
|
||||
}
|
||||
|
||||
func (ct *clientTracer) dnsDone(info httptrace.DNSDoneInfo) {
|
||||
+1
-1
@@ -25,7 +25,7 @@ import (
|
||||
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/plugin/httptrace"
|
||||
"go.opentelemetry.io/otel/instrumentation/httptrace"
|
||||
export "go.opentelemetry.io/otel/sdk/export/trace"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
@@ -22,22 +22,42 @@ import (
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/propagation"
|
||||
"go.opentelemetry.io/otel/api/standard"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
HostKey = kv.Key("http.host")
|
||||
URLKey = kv.Key("http.url")
|
||||
)
|
||||
// Option is a function that allows configuration of the httptrace Extract()
|
||||
// and Inject() functions
|
||||
type Option func(*config)
|
||||
|
||||
type config struct {
|
||||
propagators propagation.Propagators
|
||||
}
|
||||
|
||||
func newConfig(opts []Option) *config {
|
||||
c := &config{propagators: global.Propagators()}
|
||||
for _, o := range opts {
|
||||
o(c)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// WithPropagators sets the propagators to use for Extraction and Injection
|
||||
func WithPropagators(props propagation.Propagators) Option {
|
||||
return func(c *config) {
|
||||
c.propagators = props
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the Attributes, Context Entries, and SpanContext that were encoded by Inject.
|
||||
func Extract(ctx context.Context, req *http.Request) ([]kv.KeyValue, []kv.KeyValue, trace.SpanContext) {
|
||||
ctx = propagation.ExtractHTTP(ctx, global.Propagators(), req.Header)
|
||||
func Extract(ctx context.Context, req *http.Request, opts ...Option) ([]kv.KeyValue, []kv.KeyValue, trace.SpanContext) {
|
||||
c := newConfig(opts)
|
||||
ctx = propagation.ExtractHTTP(ctx, c.propagators, req.Header)
|
||||
|
||||
attrs := []kv.KeyValue{
|
||||
URLKey.String(req.URL.String()),
|
||||
// Etc.
|
||||
}
|
||||
attrs := append(
|
||||
standard.HTTPServerAttributesFromHTTPRequest("", "", req),
|
||||
standard.NetAttributesFromHTTPRequest("tcp", req)...,
|
||||
)
|
||||
|
||||
var correlationCtxKVs []kv.KeyValue
|
||||
correlation.MapFromContext(ctx).Foreach(func(kv kv.KeyValue) bool {
|
||||
@@ -48,6 +68,7 @@ func Extract(ctx context.Context, req *http.Request) ([]kv.KeyValue, []kv.KeyVal
|
||||
return attrs, correlationCtxKVs, trace.RemoteSpanContextFromContext(ctx)
|
||||
}
|
||||
|
||||
func Inject(ctx context.Context, req *http.Request) {
|
||||
propagation.InjectHTTP(ctx, global.Propagators(), req.Header)
|
||||
func Inject(ctx context.Context, req *http.Request, opts ...Option) {
|
||||
c := newConfig(opts)
|
||||
propagation.InjectHTTP(ctx, c.propagators, req.Header)
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package httptrace_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"go.opentelemetry.io/otel/api/correlation"
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/propagation"
|
||||
"go.opentelemetry.io/otel/api/standard"
|
||||
"go.opentelemetry.io/otel/instrumentation/httptrace"
|
||||
export "go.opentelemetry.io/otel/sdk/export/trace"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
func TestRoundtrip(t *testing.T) {
|
||||
exp := &testExporter{
|
||||
spanMap: make(map[string][]*export.SpanData),
|
||||
}
|
||||
tp, _ := sdktrace.NewProvider(sdktrace.WithSyncer(exp), sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}))
|
||||
global.SetTraceProvider(tp)
|
||||
|
||||
tr := tp.Tracer("httptrace/client")
|
||||
|
||||
var expectedAttrs map[kv.Key]string
|
||||
expectedCorrs := map[kv.Key]string{kv.Key("foo"): "bar"}
|
||||
|
||||
// Mock http server
|
||||
ts := httptest.NewServer(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
attrs, corrs, span := httptrace.Extract(r.Context(), r)
|
||||
|
||||
actualAttrs := make(map[kv.Key]string)
|
||||
for _, attr := range attrs {
|
||||
if attr.Key == standard.NetPeerPortKey {
|
||||
// Peer port will be non-deterministic
|
||||
continue
|
||||
}
|
||||
actualAttrs[attr.Key] = attr.Value.Emit()
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(actualAttrs, expectedAttrs); diff != "" {
|
||||
t.Fatalf("[TestRoundtrip] Attributes are different: %v", diff)
|
||||
}
|
||||
|
||||
actualCorrs := make(map[kv.Key]string)
|
||||
for _, corr := range corrs {
|
||||
actualCorrs[corr.Key] = corr.Value.Emit()
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(actualCorrs, expectedCorrs); diff != "" {
|
||||
t.Fatalf("[TestRoundtrip] Correlations are different: %v", diff)
|
||||
}
|
||||
|
||||
if !span.IsValid() {
|
||||
t.Fatalf("[TestRoundtrip] Invalid span extracted: %v", span)
|
||||
}
|
||||
|
||||
_, err := w.Write([]byte("OK"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}),
|
||||
)
|
||||
defer ts.Close()
|
||||
|
||||
address := ts.Listener.Addr()
|
||||
hp := strings.Split(address.String(), ":")
|
||||
expectedAttrs = map[kv.Key]string{
|
||||
standard.HTTPFlavorKey: "1.1",
|
||||
standard.HTTPHostKey: address.String(),
|
||||
standard.HTTPMethodKey: "GET",
|
||||
standard.HTTPSchemeKey: "http",
|
||||
standard.HTTPTargetKey: "/",
|
||||
standard.HTTPUserAgentKey: "Go-http-client/1.1",
|
||||
standard.NetHostIPKey: hp[0],
|
||||
standard.NetHostPortKey: hp[1],
|
||||
standard.NetPeerIPKey: "127.0.0.1",
|
||||
standard.NetTransportKey: "IP.TCP",
|
||||
}
|
||||
|
||||
client := ts.Client()
|
||||
err := tr.WithSpan(context.Background(), "test",
|
||||
func(ctx context.Context) error {
|
||||
ctx = correlation.ContextWithMap(ctx, correlation.NewMap(correlation.MapUpdate{SingleKV: kv.Key("foo").String("bar")}))
|
||||
req, _ := http.NewRequest("GET", ts.URL, nil)
|
||||
httptrace.Inject(ctx, req)
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %s", err.Error())
|
||||
}
|
||||
_ = res.Body.Close()
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
panic("unexpected error in http request: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpecifyPropagators(t *testing.T) {
|
||||
exp := &testExporter{
|
||||
spanMap: make(map[string][]*export.SpanData),
|
||||
}
|
||||
tp, _ := sdktrace.NewProvider(sdktrace.WithSyncer(exp), sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}))
|
||||
global.SetTraceProvider(tp)
|
||||
|
||||
tr := tp.Tracer("httptrace/client")
|
||||
|
||||
expectedCorrs := map[kv.Key]string{kv.Key("foo"): "bar"}
|
||||
|
||||
// Mock http server
|
||||
ts := httptest.NewServer(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, corrs, span := httptrace.Extract(r.Context(), r, httptrace.WithPropagators(propagation.New(propagation.WithExtractors(correlation.DefaultHTTPPropagator()))))
|
||||
|
||||
actualCorrs := make(map[kv.Key]string)
|
||||
for _, corr := range corrs {
|
||||
actualCorrs[corr.Key] = corr.Value.Emit()
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(actualCorrs, expectedCorrs); diff != "" {
|
||||
t.Fatalf("[TestRoundtrip] Correlations are different: %v", diff)
|
||||
}
|
||||
|
||||
if span.IsValid() {
|
||||
t.Fatalf("[TestRoundtrip] valid span extracted, expected none: %v", span)
|
||||
}
|
||||
|
||||
_, err := w.Write([]byte("OK"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}),
|
||||
)
|
||||
defer ts.Close()
|
||||
|
||||
client := ts.Client()
|
||||
err := tr.WithSpan(context.Background(), "test",
|
||||
func(ctx context.Context) error {
|
||||
ctx = correlation.ContextWithMap(ctx, correlation.NewMap(correlation.MapUpdate{SingleKV: kv.Key("foo").String("bar")}))
|
||||
req, _ := http.NewRequest("GET", ts.URL, nil)
|
||||
httptrace.Inject(ctx, req, httptrace.WithPropagators(propagation.New(propagation.WithInjectors(correlation.DefaultHTTPPropagator()))))
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %s", err.Error())
|
||||
}
|
||||
_ = res.Body.Close()
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
panic("unexpected error in http request: " + err.Error())
|
||||
}
|
||||
}
|
||||
@@ -18,20 +18,10 @@ import (
|
||||
"net/http"
|
||||
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
)
|
||||
|
||||
// Attribute keys that can be added to a span.
|
||||
const (
|
||||
HostKey = kv.Key("http.host") // the HTTP host (http.Request.Host)
|
||||
MethodKey = kv.Key("http.method") // the HTTP method (http.Request.Method)
|
||||
PathKey = kv.Key("http.path") // the HTTP path (http.Request.URL.Path)
|
||||
URLKey = kv.Key("http.url") // the HTTP URL (http.Request.URL.String())
|
||||
UserAgentKey = kv.Key("http.user_agent") // the HTTP user agent (http.Request.UserAgent())
|
||||
RouteKey = kv.Key("http.route") // the HTTP route (ex: /users/:id)
|
||||
RemoteAddrKey = kv.Key("http.remote_addr") // the network address of the client that sent the HTTP request (http.Request.RemoteAddr)
|
||||
StatusCodeKey = kv.Key("http.status_code") // if set, the HTTP status
|
||||
ReadBytesKey = kv.Key("http.read_bytes") // if anything was read from the request body, the total number of bytes read
|
||||
ReadErrorKey = kv.Key("http.read_error") // If an error occurred while reading a request, the string of the error (io.EOF is not recorded)
|
||||
WroteBytesKey = kv.Key("http.wrote_bytes") // if anything was written to the response writer, the total number of bytes written
|
||||
@@ -41,15 +31,3 @@ const (
|
||||
// Filter is a predicate used to determine whether a given http.request should
|
||||
// be traced. A Filter must return true if the request should be traced.
|
||||
type Filter func(*http.Request) bool
|
||||
|
||||
// Setup basic span attributes before so that they
|
||||
// are available to be mutated if needed.
|
||||
func setBasicAttributes(span trace.Span, r *http.Request) {
|
||||
span.SetAttributes(
|
||||
HostKey.String(r.Host),
|
||||
MethodKey.String(r.Method),
|
||||
PathKey.String(r.URL.Path),
|
||||
URLKey.String(r.URL.String()),
|
||||
UserAgentKey.String(r.UserAgent()),
|
||||
)
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/plugin/othttp"
|
||||
"go.opentelemetry.io/otel/instrumentation/othttp"
|
||||
)
|
||||
|
||||
// Any takes a list of Filters and returns a Filter that
|
||||
+1
-1
@@ -19,7 +19,7 @@ import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"go.opentelemetry.io/otel/plugin/othttp"
|
||||
"go.opentelemetry.io/otel/instrumentation/othttp"
|
||||
)
|
||||
|
||||
type scenario struct {
|
||||
+1
-1
@@ -20,7 +20,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/plugin/othttp"
|
||||
"go.opentelemetry.io/otel/instrumentation/othttp"
|
||||
)
|
||||
|
||||
// Header returns a Filter that returns true if the request
|
||||
+1
-1
@@ -21,7 +21,7 @@ import (
|
||||
"net/textproto"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/plugin/othttp"
|
||||
"go.opentelemetry.io/otel/instrumentation/othttp"
|
||||
)
|
||||
|
||||
// Header returns a Filter that returns true if the request
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/kv"
|
||||
"go.opentelemetry.io/otel/api/propagation"
|
||||
"go.opentelemetry.io/otel/api/standard"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
)
|
||||
|
||||
@@ -56,7 +57,7 @@ func NewHandler(handler http.Handler, operation string, opts ...Option) http.Han
|
||||
}
|
||||
|
||||
defaultOpts := []Option{
|
||||
WithTracer(global.Tracer("go.opentelemetry.io/plugin/othttp")),
|
||||
WithTracer(global.Tracer("go.opentelemetry.io/otel/instrumentation/othttp")),
|
||||
WithPropagators(global.Propagators()),
|
||||
WithSpanOptions(trace.WithSpanKind(trace.SpanKindServer)),
|
||||
WithSpanNameFormatter(defaultHandlerFormatter),
|
||||
@@ -88,7 +89,11 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
opts := append([]trace.StartOption{}, h.spanStartOptions...) // start with the configured options
|
||||
opts := append([]trace.StartOption{
|
||||
trace.WithAttributes(standard.NetAttributesFromHTTPRequest("tcp", r)...),
|
||||
trace.WithAttributes(standard.EndUserAttributesFromHTTPRequest(r)...),
|
||||
trace.WithAttributes(standard.HTTPServerAttributesFromHTTPRequest(h.operation, "", r)...),
|
||||
}, h.spanStartOptions...) // start with the configured options
|
||||
|
||||
ctx := propagation.ExtractHTTP(r.Context(), h.propagators, r.Header)
|
||||
ctx, span := h.tracer.Start(ctx, h.spanNameFormatter(h.operation, r), opts...)
|
||||
@@ -112,16 +117,14 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
rww := &respWriterWrapper{ResponseWriter: w, record: writeRecordFunc, ctx: ctx, props: h.propagators}
|
||||
|
||||
setBasicAttributes(span, r)
|
||||
span.SetAttributes(RemoteAddrKey.String(r.RemoteAddr))
|
||||
|
||||
h.handler.ServeHTTP(rww, r.WithContext(ctx))
|
||||
|
||||
setAfterServeAttributes(span, bw.read, rww.written, int64(rww.statusCode), bw.err, rww.err)
|
||||
setAfterServeAttributes(span, bw.read, rww.written, rww.statusCode, bw.err, rww.err)
|
||||
}
|
||||
|
||||
func setAfterServeAttributes(span trace.Span, read, wrote, statusCode int64, rerr, werr error) {
|
||||
kv := make([]kv.KeyValue, 0, 5)
|
||||
func setAfterServeAttributes(span trace.Span, read, wrote int64, statusCode int, rerr, werr error) {
|
||||
kv := []kv.KeyValue{}
|
||||
|
||||
// TODO: Consider adding an event after each read and write, possibly as an
|
||||
// option (defaulting to off), so as to not create needlessly verbose spans.
|
||||
if read > 0 {
|
||||
@@ -134,7 +137,7 @@ func setAfterServeAttributes(span trace.Span, read, wrote, statusCode int64, rer
|
||||
kv = append(kv, WroteBytesKey.Int64(wrote))
|
||||
}
|
||||
if statusCode > 0 {
|
||||
kv = append(kv, StatusCodeKey.Int64(statusCode))
|
||||
kv = append(kv, standard.HTTPAttributesFromHTTPStatusCode(statusCode)...)
|
||||
}
|
||||
if werr != nil && werr != io.EOF {
|
||||
kv = append(kv, WriteErrorKey.String(werr.Error()))
|
||||
@@ -147,7 +150,7 @@ func setAfterServeAttributes(span trace.Span, read, wrote, statusCode int64, rer
|
||||
func WithRouteTag(route string, h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
span := trace.SpanFromContext(r.Context())
|
||||
span.SetAttributes(RouteKey.String(route))
|
||||
span.SetAttributes(standard.HTTPRouteKey.String(route))
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
+1
-1
@@ -27,7 +27,7 @@ import (
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
"go.opentelemetry.io/otel/exporters/trace/stdout"
|
||||
"go.opentelemetry.io/otel/plugin/othttp"
|
||||
"go.opentelemetry.io/otel/instrumentation/othttp"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
|
||||
"go.opentelemetry.io/otel/api/global"
|
||||
"go.opentelemetry.io/otel/api/propagation"
|
||||
"go.opentelemetry.io/otel/api/standard"
|
||||
"go.opentelemetry.io/otel/api/trace"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -48,7 +49,7 @@ func NewTransport(base http.RoundTripper, opts ...Option) *Transport {
|
||||
}
|
||||
|
||||
defaultOpts := []Option{
|
||||
WithTracer(global.Tracer("go.opentelemetry.io/plugin/othttp")),
|
||||
WithTracer(global.Tracer("go.opentelemetry.io/otel/instrumentation/othttp")),
|
||||
WithPropagators(global.Propagators()),
|
||||
WithSpanOptions(trace.WithSpanKind(trace.SpanKindClient)),
|
||||
WithSpanNameFormatter(defaultTransportFormatter),
|
||||
@@ -88,7 +89,7 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
ctx, span := t.tracer.Start(r.Context(), t.spanNameFormatter("", r), opts...)
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
setBasicAttributes(span, r)
|
||||
span.SetAttributes(standard.HTTPClientAttributesFromHTTPRequest(r)...)
|
||||
propagation.InjectHTTP(ctx, t.propagators, r.Header)
|
||||
|
||||
res, err := t.rt.RoundTrip(r)
|
||||
@@ -98,7 +99,8 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
return res, err
|
||||
}
|
||||
|
||||
span.SetAttributes(StatusCodeKey.Int(res.StatusCode))
|
||||
span.SetAttributes(standard.HTTPAttributesFromHTTPStatusCode(res.StatusCode)...)
|
||||
span.SetStatus(standard.SpanStatusFromHTTPStatusCode(res.StatusCode))
|
||||
res.Body = &wrappedBody{ctx: ctx, span: span, body: res.Body}
|
||||
|
||||
return res, err
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user