1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2024-12-20 19:52:56 +02:00
opentelemetry-go/exporter/metric/internal/statsd/conn_test.go

308 lines
7.3 KiB
Go
Raw Normal View History

Dogstatsd metrics exporter (#326) * Add MetricAggregator.Merge() implementations * Update from feedback * Type * Ckpt * Ckpt * Add push controller * Ckpt * Add aggregator interfaces, stdout encoder * Modify basic main.go * Main is working * Batch stdout output * Sum udpate * Rename stdout * Add stateless/stateful Batcher options * Undo a for-loop in the example, remove a done TODO * Update imports * Add note * Rename defaultkeys * Support variable label encoder to speed OpenMetrics/Statsd export * Lint * Checkpoint * Checkpoint * Doc * Precommit/lint * Simplify Aggregator API * Record->Identifier * Remove export.Record a.k.a. Identifier * Checkpoint * Propagate errors to the SDK, remove a bunch of 'TODO warn' * Checkpoint * Introduce export.Labels * Comments in export/metric.go * Comment * More merge * More doc * Complete example * Lint fixes * Add a testable example * Lint * Dogstats * Let Export return an error * Checkpoint * add a basic stdout exporter test * Add measure test; fix aggregator APIs * Use JSON numbers, not strings * Test stdout exporter error * Add a test for the call to RangeTest * Add error handler API to improve correctness test; return errors from RecordOne * Undo the previous -- do not expose errors * Add simple selector variations, test * Repair examples * Test push controller error handling * Add SDK label encoder tests * Add a defaultkeys batcher test * Add an ungrouped batcher test * Lint new tests * Respond to krnowak's feedback * Checkpoint * Funciontal example using unixgram * Tidy the example * Add a packet-split test * More tests * Undo comment * Use concrete receivers for export records and labels, since the constructors return structs not pointers * Bug fix for stateful batchers; clone an aggregator for long term storage * Remove TODO addressed in #318 * Add errors to all aggregator interfaces * Handle ErrNoLastValue case in stdout exporter * Move aggregator API into sdk/export/metric/aggregator * Update all aggregator exported-method comments * Document the aggregator APIs * More aggregator comments * Add multiple updates to the ungrouped test * Fixes for feedback from Gustavo and Liz * Producer->CheckpointSet; add FinishedCollection * Process takes an export.Record * ReadCheckpoint->CheckpointSet * EncodeLabels->Encode * Format a better inconsistent type error; add more aggregator API tests * More RangeTest test coverage * Make benbjohnson/clock a test-only dependency * Handle ErrNoLastValue in stress_test * Update comments; use a pipe vs a unix socket in the example test * Update test * Spelling * Typo fix * Rename DefaultLabelEncoder to NewDefaultLabelEncoder for clarity * Rename DefaultLabelEncoder to NewDefaultLabelEncoder for clarity * Test different adapters; add ForceEncode to statsd label encoder
2019-11-22 06:46:05 +02:00
// Copyright 2019, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package statsd_test
import (
"bytes"
"context"
"fmt"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/api/core"
"go.opentelemetry.io/otel/api/key"
"go.opentelemetry.io/otel/api/unit"
"go.opentelemetry.io/otel/exporter/metric/internal/statsd"
"go.opentelemetry.io/otel/exporter/metric/test"
export "go.opentelemetry.io/otel/sdk/export/metric"
sdk "go.opentelemetry.io/otel/sdk/metric"
)
// withTagsAdapter tests a dogstatsd-style statsd exporter.
type withTagsAdapter struct {
*statsd.LabelEncoder
}
func (*withTagsAdapter) AppendName(rec export.Record, buf *bytes.Buffer) {
_, _ = buf.WriteString(rec.Descriptor().Name())
}
func (ta *withTagsAdapter) AppendTags(rec export.Record, buf *bytes.Buffer) {
encoded, _ := ta.LabelEncoder.ForceEncode(rec.Labels())
_, _ = buf.WriteString(encoded)
}
func newWithTagsAdapter() *withTagsAdapter {
return &withTagsAdapter{
statsd.NewLabelEncoder(),
}
}
// noTagsAdapter simulates a plain-statsd exporter that appends tag
// values to the metric name.
type noTagsAdapter struct {
}
func (*noTagsAdapter) AppendName(rec export.Record, buf *bytes.Buffer) {
_, _ = buf.WriteString(rec.Descriptor().Name())
for _, tag := range rec.Labels().Ordered() {
_, _ = buf.WriteString(".")
_, _ = buf.WriteString(tag.Value.Emit())
}
}
func (*noTagsAdapter) AppendTags(rec export.Record, buf *bytes.Buffer) {
}
func newNoTagsAdapter() *noTagsAdapter {
return &noTagsAdapter{}
}
type testWriter struct {
vec []string
}
func (w *testWriter) Write(b []byte) (int, error) {
w.vec = append(w.vec, string(b))
return len(b), nil
}
func TestBasicFormat(t *testing.T) {
type adapterOutput struct {
adapter statsd.Adapter
expected string
}
for _, ao := range []adapterOutput{{
adapter: newWithTagsAdapter(),
expected: `counter:%s|c|#A:B,C:D
gauge:%s|g|#A:B,C:D
measure:%s|h|#A:B,C:D
timer:%s|ms|#A:B,C:D
`}, {
adapter: newNoTagsAdapter(),
expected: `counter.B.D:%s|c
gauge.B.D:%s|g
measure.B.D:%s|h
timer.B.D:%s|ms
`},
} {
adapter := ao.adapter
expected := ao.expected
t.Run(fmt.Sprintf("%T", adapter), func(t *testing.T) {
for _, nkind := range []core.NumberKind{
core.Float64NumberKind,
core.Int64NumberKind,
} {
t.Run(nkind.String(), func(t *testing.T) {
ctx := context.Background()
writer := &testWriter{}
config := statsd.Config{
Writer: writer,
MaxPacketSize: 1024,
}
exp, err := statsd.NewExporter(config, adapter)
if err != nil {
t.Fatal("New error: ", err)
}
checkpointSet := test.NewCheckpointSet(sdk.NewDefaultLabelEncoder())
cdesc := export.NewDescriptor(
"counter", export.CounterKind, nil, "", "", nkind, false)
gdesc := export.NewDescriptor(
"gauge", export.GaugeKind, nil, "", "", nkind, false)
mdesc := export.NewDescriptor(
"measure", export.MeasureKind, nil, "", "", nkind, false)
tdesc := export.NewDescriptor(
"timer", export.MeasureKind, nil, "", unit.Milliseconds, nkind, false)
labels := []core.KeyValue{
key.New("A").String("B"),
key.New("C").String("D"),
}
const value = 123.456
checkpointSet.AddCounter(cdesc, value, labels...)
checkpointSet.AddGauge(gdesc, value, labels...)
checkpointSet.AddMeasure(mdesc, value, labels...)
checkpointSet.AddMeasure(tdesc, value, labels...)
Dogstatsd metrics exporter (#326) * Add MetricAggregator.Merge() implementations * Update from feedback * Type * Ckpt * Ckpt * Add push controller * Ckpt * Add aggregator interfaces, stdout encoder * Modify basic main.go * Main is working * Batch stdout output * Sum udpate * Rename stdout * Add stateless/stateful Batcher options * Undo a for-loop in the example, remove a done TODO * Update imports * Add note * Rename defaultkeys * Support variable label encoder to speed OpenMetrics/Statsd export * Lint * Checkpoint * Checkpoint * Doc * Precommit/lint * Simplify Aggregator API * Record->Identifier * Remove export.Record a.k.a. Identifier * Checkpoint * Propagate errors to the SDK, remove a bunch of 'TODO warn' * Checkpoint * Introduce export.Labels * Comments in export/metric.go * Comment * More merge * More doc * Complete example * Lint fixes * Add a testable example * Lint * Dogstats * Let Export return an error * Checkpoint * add a basic stdout exporter test * Add measure test; fix aggregator APIs * Use JSON numbers, not strings * Test stdout exporter error * Add a test for the call to RangeTest * Add error handler API to improve correctness test; return errors from RecordOne * Undo the previous -- do not expose errors * Add simple selector variations, test * Repair examples * Test push controller error handling * Add SDK label encoder tests * Add a defaultkeys batcher test * Add an ungrouped batcher test * Lint new tests * Respond to krnowak's feedback * Checkpoint * Funciontal example using unixgram * Tidy the example * Add a packet-split test * More tests * Undo comment * Use concrete receivers for export records and labels, since the constructors return structs not pointers * Bug fix for stateful batchers; clone an aggregator for long term storage * Remove TODO addressed in #318 * Add errors to all aggregator interfaces * Handle ErrNoLastValue case in stdout exporter * Move aggregator API into sdk/export/metric/aggregator * Update all aggregator exported-method comments * Document the aggregator APIs * More aggregator comments * Add multiple updates to the ungrouped test * Fixes for feedback from Gustavo and Liz * Producer->CheckpointSet; add FinishedCollection * Process takes an export.Record * ReadCheckpoint->CheckpointSet * EncodeLabels->Encode * Format a better inconsistent type error; add more aggregator API tests * More RangeTest test coverage * Make benbjohnson/clock a test-only dependency * Handle ErrNoLastValue in stress_test * Update comments; use a pipe vs a unix socket in the example test * Update test * Spelling * Typo fix * Rename DefaultLabelEncoder to NewDefaultLabelEncoder for clarity * Rename DefaultLabelEncoder to NewDefaultLabelEncoder for clarity * Test different adapters; add ForceEncode to statsd label encoder
2019-11-22 06:46:05 +02:00
err = exp.Export(ctx, checkpointSet)
require.Nil(t, err)
var vfmt string
if nkind == core.Int64NumberKind {
fv := float64(value)
vfmt = strconv.FormatInt(int64(fv), 10)
} else {
vfmt = strconv.FormatFloat(value, 'g', -1, 64)
}
require.Equal(t, 1, len(writer.vec))
require.Equal(t, fmt.Sprintf(expected, vfmt, vfmt, vfmt, vfmt), writer.vec[0])
})
}
})
}
}
func makeLabels(offset, nkeys int) []core.KeyValue {
r := make([]core.KeyValue, nkeys)
for i := range r {
r[i] = key.New(fmt.Sprint("k", offset+i)).String(fmt.Sprint("v", offset+i))
}
return r
}
type splitTestCase struct {
name string
setup func(add func(int))
check func(expected, got []string, t *testing.T)
}
var splitTestCases = []splitTestCase{
// These test use the number of keys to control where packets
// are split.
{"Simple",
func(add func(int)) {
add(1)
add(1000)
add(1)
},
func(expected, got []string, t *testing.T) {
require.EqualValues(t, expected, got)
},
},
{"LastBig",
func(add func(int)) {
add(1)
add(1)
add(1000)
},
func(expected, got []string, t *testing.T) {
require.Equal(t, 2, len(got))
require.EqualValues(t, []string{
expected[0] + expected[1],
expected[2],
}, got)
},
},
{"FirstBig",
func(add func(int)) {
add(1000)
add(1)
add(1)
add(1000)
add(1)
add(1)
},
func(expected, got []string, t *testing.T) {
require.Equal(t, 4, len(got))
require.EqualValues(t, []string{
expected[0],
expected[1] + expected[2],
expected[3],
expected[4] + expected[5],
}, got)
},
},
{"OneBig",
func(add func(int)) {
add(1000)
},
func(expected, got []string, t *testing.T) {
require.EqualValues(t, expected, got)
},
},
{"LastSmall",
func(add func(int)) {
add(1000)
add(1)
},
func(expected, got []string, t *testing.T) {
require.EqualValues(t, expected, got)
},
},
{"Overflow",
func(add func(int)) {
for i := 0; i < 1000; i++ {
add(1)
}
},
func(expected, got []string, t *testing.T) {
require.Less(t, 1, len(got))
require.Equal(t, strings.Join(expected, ""), strings.Join(got, ""))
},
},
{"Empty",
func(add func(int)) {
},
func(expected, got []string, t *testing.T) {
require.Equal(t, 0, len(got))
},
},
{"AllBig",
func(add func(int)) {
add(1000)
add(1000)
add(1000)
},
func(expected, got []string, t *testing.T) {
require.EqualValues(t, expected, got)
},
},
}
func TestPacketSplit(t *testing.T) {
for _, tcase := range splitTestCases {
t.Run(tcase.name, func(t *testing.T) {
ctx := context.Background()
writer := &testWriter{}
config := statsd.Config{
Writer: writer,
MaxPacketSize: 1024,
}
adapter := newWithTagsAdapter()
exp, err := statsd.NewExporter(config, adapter)
if err != nil {
t.Fatal("New error: ", err)
}
checkpointSet := test.NewCheckpointSet(adapter.LabelEncoder)
desc := export.NewDescriptor("counter", export.CounterKind, nil, "", "", core.Int64NumberKind, false)
var expected []string
offset := 0
tcase.setup(func(nkeys int) {
labels := makeLabels(offset, nkeys)
offset += nkeys
expect := fmt.Sprint("counter:100|c", adapter.LabelEncoder.Encode(labels), "\n")
expected = append(expected, expect)
checkpointSet.AddCounter(desc, 100, labels...)
Dogstatsd metrics exporter (#326) * Add MetricAggregator.Merge() implementations * Update from feedback * Type * Ckpt * Ckpt * Add push controller * Ckpt * Add aggregator interfaces, stdout encoder * Modify basic main.go * Main is working * Batch stdout output * Sum udpate * Rename stdout * Add stateless/stateful Batcher options * Undo a for-loop in the example, remove a done TODO * Update imports * Add note * Rename defaultkeys * Support variable label encoder to speed OpenMetrics/Statsd export * Lint * Checkpoint * Checkpoint * Doc * Precommit/lint * Simplify Aggregator API * Record->Identifier * Remove export.Record a.k.a. Identifier * Checkpoint * Propagate errors to the SDK, remove a bunch of 'TODO warn' * Checkpoint * Introduce export.Labels * Comments in export/metric.go * Comment * More merge * More doc * Complete example * Lint fixes * Add a testable example * Lint * Dogstats * Let Export return an error * Checkpoint * add a basic stdout exporter test * Add measure test; fix aggregator APIs * Use JSON numbers, not strings * Test stdout exporter error * Add a test for the call to RangeTest * Add error handler API to improve correctness test; return errors from RecordOne * Undo the previous -- do not expose errors * Add simple selector variations, test * Repair examples * Test push controller error handling * Add SDK label encoder tests * Add a defaultkeys batcher test * Add an ungrouped batcher test * Lint new tests * Respond to krnowak's feedback * Checkpoint * Funciontal example using unixgram * Tidy the example * Add a packet-split test * More tests * Undo comment * Use concrete receivers for export records and labels, since the constructors return structs not pointers * Bug fix for stateful batchers; clone an aggregator for long term storage * Remove TODO addressed in #318 * Add errors to all aggregator interfaces * Handle ErrNoLastValue case in stdout exporter * Move aggregator API into sdk/export/metric/aggregator * Update all aggregator exported-method comments * Document the aggregator APIs * More aggregator comments * Add multiple updates to the ungrouped test * Fixes for feedback from Gustavo and Liz * Producer->CheckpointSet; add FinishedCollection * Process takes an export.Record * ReadCheckpoint->CheckpointSet * EncodeLabels->Encode * Format a better inconsistent type error; add more aggregator API tests * More RangeTest test coverage * Make benbjohnson/clock a test-only dependency * Handle ErrNoLastValue in stress_test * Update comments; use a pipe vs a unix socket in the example test * Update test * Spelling * Typo fix * Rename DefaultLabelEncoder to NewDefaultLabelEncoder for clarity * Rename DefaultLabelEncoder to NewDefaultLabelEncoder for clarity * Test different adapters; add ForceEncode to statsd label encoder
2019-11-22 06:46:05 +02:00
})
err = exp.Export(ctx, checkpointSet)
require.Nil(t, err)
tcase.check(expected, writer.vec, t)
})
}
}