538 lines
17 KiB
Go
538 lines
17 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package batchprocessor
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.opencensus.io/stats/view"
|
|
"go.uber.org/zap"
|
|
|
|
"go.opentelemetry.io/collector/component"
|
|
"go.opentelemetry.io/collector/consumer/pdata"
|
|
"go.opentelemetry.io/collector/consumer/pdatautil"
|
|
"go.opentelemetry.io/collector/internal/data"
|
|
"go.opentelemetry.io/collector/internal/data/testdata"
|
|
)
|
|
|
|
func TestBatchProcessorSpansDelivered(t *testing.T) {
|
|
sender := newTestSender()
|
|
cfg := generateDefaultConfig()
|
|
cfg.SendBatchSize = 128
|
|
creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()}
|
|
batcher := newBatchTracesProcessor(creationParams, sender, cfg)
|
|
requestCount := 1000
|
|
spansPerRequest := 100
|
|
waitForCn := sender.waitFor(requestCount*spansPerRequest, 5*time.Second)
|
|
traceDataSlice := make([]pdata.Traces, 0, requestCount)
|
|
for requestNum := 0; requestNum < requestCount; requestNum++ {
|
|
td := testdata.GenerateTraceDataManySpansSameResource(spansPerRequest)
|
|
spans := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans()
|
|
for spanIndex := 0; spanIndex < spansPerRequest; spanIndex++ {
|
|
spans.At(spanIndex).SetName(getTestSpanName(requestNum, spanIndex))
|
|
}
|
|
traceDataSlice = append(traceDataSlice, td.Clone())
|
|
go batcher.ConsumeTraces(context.Background(), td)
|
|
}
|
|
|
|
// Added to test logic that check for empty resources.
|
|
td := testdata.GenerateTraceDataEmpty()
|
|
go batcher.ConsumeTraces(context.Background(), td)
|
|
|
|
err := <-waitForCn
|
|
if err != nil {
|
|
t.Errorf("failed to wait for sender %s", err)
|
|
}
|
|
|
|
require.Equal(t, requestCount*spansPerRequest, sender.spansReceived)
|
|
|
|
for requestNum := 0; requestNum < requestCount; requestNum++ {
|
|
spans := traceDataSlice[requestNum].ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans()
|
|
for spanIndex := 0; spanIndex < spansPerRequest; spanIndex++ {
|
|
require.EqualValues(t,
|
|
spans.At(spanIndex),
|
|
sender.spansReceivedByName[getTestSpanName(requestNum, spanIndex)])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBatchProcessorSentBySize(t *testing.T) {
|
|
views := MetricViews()
|
|
view.Register(views...)
|
|
defer view.Unregister(views...)
|
|
|
|
sender := newTestSender()
|
|
cfg := generateDefaultConfig()
|
|
sendBatchSize := 20
|
|
cfg.SendBatchSize = uint32(sendBatchSize)
|
|
cfg.Timeout = 500 * time.Millisecond
|
|
creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()}
|
|
batcher := newBatchTracesProcessor(creationParams, sender, cfg)
|
|
requestCount := 100
|
|
spansPerRequest := 5
|
|
waitForCn := sender.waitFor(requestCount*spansPerRequest, time.Second)
|
|
|
|
start := time.Now()
|
|
for requestNum := 0; requestNum < requestCount; requestNum++ {
|
|
td := testdata.GenerateTraceDataManySpansSameResource(spansPerRequest)
|
|
go batcher.ConsumeTraces(context.Background(), td)
|
|
}
|
|
|
|
err := <-waitForCn
|
|
if err != nil {
|
|
t.Errorf("failed to wait for sender %s", err)
|
|
}
|
|
|
|
elapsed := time.Since(start)
|
|
require.LessOrEqual(t, elapsed.Nanoseconds(), cfg.Timeout.Nanoseconds())
|
|
|
|
sender.mtx.RLock()
|
|
|
|
expectedBatchesNum := requestCount * spansPerRequest / sendBatchSize
|
|
expectedBatchingFactor := sendBatchSize / spansPerRequest
|
|
|
|
require.Equal(t, requestCount*spansPerRequest, sender.spansReceived)
|
|
|
|
require.EqualValues(t, expectedBatchesNum, len(sender.traceDataReceived))
|
|
for _, td := range sender.traceDataReceived {
|
|
rss := td.ResourceSpans()
|
|
require.Equal(t, expectedBatchingFactor, rss.Len())
|
|
for i := 0; i < expectedBatchingFactor; i++ {
|
|
require.Equal(t, spansPerRequest, rss.At(i).InstrumentationLibrarySpans().At(0).Spans().Len())
|
|
}
|
|
}
|
|
|
|
data, err := view.RetrieveData(statBatchSendSize.Name())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, len(data))
|
|
distData := data[0].Data.(*view.DistributionData)
|
|
assert.Equal(t, int64(expectedBatchesNum), distData.Count)
|
|
assert.Equal(t, sender.spansReceived, int(distData.Sum()))
|
|
assert.Equal(t, sendBatchSize, int(distData.Min))
|
|
assert.Equal(t, sendBatchSize, int(distData.Max))
|
|
|
|
sender.mtx.RUnlock()
|
|
}
|
|
|
|
func TestBatchProcessorSentByTimeout(t *testing.T) {
|
|
sender := newTestSender()
|
|
cfg := generateDefaultConfig()
|
|
sendBatchSize := 100
|
|
cfg.SendBatchSize = uint32(sendBatchSize)
|
|
cfg.Timeout = 100 * time.Millisecond
|
|
creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()}
|
|
|
|
requestCount := 5
|
|
spansPerRequest := 10
|
|
waitForCn := sender.waitFor(requestCount*spansPerRequest, time.Second)
|
|
|
|
start := time.Now()
|
|
|
|
batcher := newBatchTracesProcessor(creationParams, sender, cfg)
|
|
for requestNum := 0; requestNum < requestCount; requestNum++ {
|
|
td := testdata.GenerateTraceDataManySpansSameResource(spansPerRequest)
|
|
go batcher.ConsumeTraces(context.Background(), td)
|
|
}
|
|
|
|
err := <-waitForCn
|
|
if err != nil {
|
|
t.Errorf("failed to wait for sender %s", err)
|
|
}
|
|
|
|
elapsed := time.Since(start)
|
|
require.LessOrEqual(t, cfg.Timeout.Nanoseconds(), elapsed.Nanoseconds())
|
|
|
|
sender.mtx.RLock()
|
|
|
|
expectedBatchesNum := 1
|
|
expectedBatchingFactor := 5
|
|
|
|
require.Equal(t, sender.spansReceived, requestCount*spansPerRequest)
|
|
|
|
require.EqualValues(t, len(sender.traceDataReceived), expectedBatchesNum)
|
|
for _, td := range sender.traceDataReceived {
|
|
rss := td.ResourceSpans()
|
|
require.Equal(t, expectedBatchingFactor, rss.Len())
|
|
for i := 0; i < expectedBatchingFactor; i++ {
|
|
require.Equal(t, spansPerRequest, rss.At(i).InstrumentationLibrarySpans().At(0).Spans().Len())
|
|
}
|
|
}
|
|
|
|
sender.mtx.RUnlock()
|
|
}
|
|
|
|
func TestBatchProcessorTraceSendWhenClosing(t *testing.T) {
|
|
cfg := Config{
|
|
Timeout: 3 * time.Second,
|
|
SendBatchSize: 1000,
|
|
}
|
|
sender := newTestSender()
|
|
|
|
creationParams := component.ProcessorCreateParams{Logger: zap.NewNop()}
|
|
batcher := newBatchTracesProcessor(creationParams, sender, &cfg)
|
|
|
|
requestCount := 10
|
|
spansPerRequest := 10
|
|
waitForCn := sender.waitFor(requestCount*spansPerRequest, 2*time.Second)
|
|
for requestNum := 0; requestNum < requestCount; requestNum++ {
|
|
td := testdata.GenerateTraceDataManySpansSameResource(spansPerRequest)
|
|
batcher.ConsumeTraces(context.Background(), td)
|
|
}
|
|
|
|
batcher.Shutdown(context.Background())
|
|
|
|
// TODO (Issue 1070) - - To check return value when fixing tests.
|
|
<-waitForCn
|
|
|
|
sender.mtx.RLock()
|
|
|
|
// TODO (Issue 1070) - Fix the tests so it can check that all 100 items went through.
|
|
require.Greater(t, sender.spansReceived, 0)
|
|
require.Equal(t, len(sender.traceDataReceived), 1)
|
|
|
|
sender.mtx.RUnlock()
|
|
}
|
|
|
|
func getTestSpanName(requestNum, index int) string {
|
|
return fmt.Sprintf("test-span-%d-%d", requestNum, index)
|
|
}
|
|
|
|
type testSender struct {
|
|
reqChan chan pdata.Traces
|
|
spansReceived int
|
|
traceDataReceived []pdata.Traces
|
|
spansReceivedByName map[string]pdata.Span
|
|
mtx sync.RWMutex
|
|
}
|
|
|
|
func newTestSender() *testSender {
|
|
return &testSender{
|
|
reqChan: make(chan pdata.Traces, 100),
|
|
traceDataReceived: make([]pdata.Traces, 0),
|
|
spansReceivedByName: make(map[string]pdata.Span),
|
|
}
|
|
}
|
|
|
|
func (ts *testSender) ConsumeTraces(_ context.Context, td pdata.Traces) error {
|
|
ts.reqChan <- td
|
|
return nil
|
|
}
|
|
|
|
func (ts *testSender) waitFor(spans int, timeout time.Duration) chan error {
|
|
errorCn := make(chan error)
|
|
go func() {
|
|
for {
|
|
select {
|
|
case td := <-ts.reqChan:
|
|
ts.mtx.Lock()
|
|
ts.traceDataReceived = append(ts.traceDataReceived, td)
|
|
ts.spansReceived = ts.spansReceived + td.SpanCount()
|
|
|
|
rss := td.ResourceSpans()
|
|
for i := 0; i < rss.Len(); i++ {
|
|
rs := rss.At(i)
|
|
if rs.IsNil() {
|
|
continue
|
|
}
|
|
|
|
ilss := rs.InstrumentationLibrarySpans()
|
|
for j := 0; j < ilss.Len(); j++ {
|
|
ils := ilss.At(j)
|
|
if ils.IsNil() {
|
|
continue
|
|
}
|
|
|
|
spans := ils.Spans()
|
|
for k := 0; k < spans.Len(); k++ {
|
|
span := spans.At(k)
|
|
ts.spansReceivedByName[spans.At(k).Name()] = span
|
|
}
|
|
}
|
|
}
|
|
ts.mtx.Unlock()
|
|
if ts.spansReceived == spans {
|
|
errorCn <- nil
|
|
}
|
|
case <-time.After(timeout):
|
|
errorCn <- fmt.Errorf("timed out waiting for spans")
|
|
}
|
|
}
|
|
}()
|
|
return errorCn
|
|
}
|
|
|
|
type testMetricsSender struct {
|
|
reqChan chan pdata.Metrics
|
|
metricsReceived int
|
|
metricsDataReceiver []data.MetricData
|
|
metricsReceivedByName map[string]pdata.Metric
|
|
mtx sync.RWMutex
|
|
}
|
|
|
|
func newTestMetricsSender() *testMetricsSender {
|
|
return &testMetricsSender{
|
|
reqChan: make(chan pdata.Metrics, 100),
|
|
metricsDataReceiver: make([]data.MetricData, 0),
|
|
metricsReceivedByName: make(map[string]pdata.Metric),
|
|
}
|
|
}
|
|
|
|
func (tms *testMetricsSender) ConsumeMetrics(_ context.Context, md pdata.Metrics) error {
|
|
tms.reqChan <- md
|
|
return nil
|
|
}
|
|
|
|
func (tms *testMetricsSender) waitFor(metrics int, timeout time.Duration) chan error {
|
|
errorCn := make(chan error)
|
|
go func() {
|
|
for {
|
|
select {
|
|
case md := <-tms.reqChan:
|
|
tms.mtx.Lock()
|
|
im := pdatautil.MetricsToInternalMetrics(md)
|
|
tms.metricsDataReceiver = append(tms.metricsDataReceiver, im)
|
|
tms.metricsReceived = tms.metricsReceived + im.MetricCount()
|
|
|
|
rms := im.ResourceMetrics()
|
|
for i := 0; i < rms.Len(); i++ {
|
|
rm := rms.At(i)
|
|
if rm.IsNil() {
|
|
continue
|
|
}
|
|
ilms := rm.InstrumentationLibraryMetrics()
|
|
for j := 0; j < ilms.Len(); j++ {
|
|
ilm := ilms.At(j)
|
|
if ilm.IsNil() {
|
|
continue
|
|
}
|
|
metrics := ilm.Metrics()
|
|
for k := 0; k < metrics.Len(); k++ {
|
|
metric := metrics.At(k)
|
|
tms.metricsReceivedByName[metric.MetricDescriptor().Name()] = metric
|
|
}
|
|
}
|
|
}
|
|
tms.mtx.Unlock()
|
|
if tms.metricsReceived == metrics {
|
|
errorCn <- nil
|
|
}
|
|
case <-time.After(timeout):
|
|
errorCn <- fmt.Errorf("timed out waiting for metrics")
|
|
}
|
|
}
|
|
}()
|
|
return errorCn
|
|
}
|
|
|
|
func getTestMetricName(requestNum, index int) string {
|
|
return fmt.Sprintf("test-metric-int-%d-%d", requestNum, index)
|
|
}
|
|
|
|
func TestBatchMetricProcessor_ReceivingData(t *testing.T) {
|
|
// Instantiate the batch processor with low config values to test data
|
|
// gets sent through the processor.
|
|
cfg := Config{
|
|
Timeout: 200 * time.Millisecond,
|
|
SendBatchSize: 50,
|
|
}
|
|
|
|
requestCount := 100
|
|
metricsPerRequest := 5
|
|
// Instantiate upstream component to receive data after the batch processor.
|
|
tms := newTestMetricsSender()
|
|
|
|
createParams := component.ProcessorCreateParams{Logger: zap.NewNop()}
|
|
batcher := newBatchMetricsProcessor(createParams, tms, &cfg)
|
|
waitForCn := tms.waitFor(requestCount*metricsPerRequest, 5*time.Second)
|
|
metricDataSlice := make([]data.MetricData, 0, requestCount)
|
|
|
|
for requestNum := 0; requestNum < requestCount; requestNum++ {
|
|
md := testdata.GenerateMetricDataManyMetricsSameResource(metricsPerRequest)
|
|
metrics := md.ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics()
|
|
for metricIndex := 0; metricIndex < metricsPerRequest; metricIndex++ {
|
|
metrics.At(metricIndex).MetricDescriptor().SetName(getTestMetricName(requestNum, metricIndex))
|
|
}
|
|
metricDataSlice = append(metricDataSlice, md.Clone())
|
|
pd := pdatautil.MetricsFromInternalMetrics(md)
|
|
go batcher.ConsumeMetrics(context.Background(), pd)
|
|
}
|
|
|
|
// Added to test case with empty resources sent.
|
|
md := testdata.GenerateMetricDataEmpty()
|
|
go batcher.ConsumeMetrics(context.Background(), pdatautil.MetricsFromInternalMetrics(md))
|
|
|
|
err := <-waitForCn
|
|
if err != nil {
|
|
t.Errorf("faild to wait for sender %s", err)
|
|
}
|
|
|
|
tms.mtx.RLock()
|
|
|
|
require.Equal(t, requestCount*metricsPerRequest, tms.metricsReceived)
|
|
for requestNum := 0; requestNum < requestCount; requestNum++ {
|
|
metrics := metricDataSlice[requestNum].ResourceMetrics().At(0).InstrumentationLibraryMetrics().At(0).Metrics()
|
|
for metricIndex := 0; metricIndex < metricsPerRequest; metricIndex++ {
|
|
require.EqualValues(t,
|
|
metrics.At(metricIndex),
|
|
tms.metricsReceivedByName[getTestMetricName(requestNum, metricIndex)])
|
|
}
|
|
}
|
|
tms.mtx.RUnlock()
|
|
}
|
|
|
|
func TestBatchMetricProcessor_BatchSize(t *testing.T) {
|
|
views := MetricViews()
|
|
view.Register(views...)
|
|
defer view.Unregister(views...)
|
|
|
|
// Instantiate the batch processor with low config values to test data
|
|
// gets sent through the processor.
|
|
cfg := Config{
|
|
Timeout: 100 * time.Millisecond,
|
|
SendBatchSize: 50,
|
|
}
|
|
|
|
requestCount := 100
|
|
metricsPerRequest := 5
|
|
|
|
// Instantiate upstream component to receive data after the batch processor.
|
|
tms := newTestMetricsSender()
|
|
|
|
createParams := component.ProcessorCreateParams{Logger: zap.NewNop()}
|
|
batcher := newBatchMetricsProcessor(createParams, tms, &cfg)
|
|
waitForCn := tms.waitFor(requestCount*metricsPerRequest, time.Second)
|
|
start := time.Now()
|
|
for requestNum := 0; requestNum < requestCount; requestNum++ {
|
|
md := testdata.GenerateMetricDataManyMetricsSameResource(metricsPerRequest)
|
|
pd := pdatautil.MetricsFromInternalMetrics(md)
|
|
go batcher.ConsumeMetrics(context.Background(), pd)
|
|
}
|
|
err := <-waitForCn
|
|
if err != nil {
|
|
t.Errorf("faild to wait for sender %s", err)
|
|
}
|
|
|
|
elapsed := time.Since(start)
|
|
require.LessOrEqual(t, elapsed.Nanoseconds(), cfg.Timeout.Nanoseconds())
|
|
|
|
tms.mtx.RLock()
|
|
|
|
expectedBatchesNum := requestCount * metricsPerRequest / int(cfg.SendBatchSize)
|
|
expectedBatchingFactor := int(cfg.SendBatchSize) / metricsPerRequest
|
|
|
|
require.Equal(t, requestCount*metricsPerRequest, tms.metricsReceived)
|
|
require.Equal(t, expectedBatchesNum, len(tms.metricsDataReceiver))
|
|
for _, md := range tms.metricsDataReceiver {
|
|
require.Equal(t, expectedBatchingFactor, md.ResourceMetrics().Len())
|
|
for i := 0; i < expectedBatchingFactor; i++ {
|
|
require.Equal(t, metricsPerRequest, md.ResourceMetrics().At(i).InstrumentationLibraryMetrics().At(0).Metrics().Len())
|
|
}
|
|
}
|
|
|
|
data, err := view.RetrieveData(statBatchSendSize.Name())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, len(data))
|
|
distData := data[0].Data.(*view.DistributionData)
|
|
assert.Equal(t, int64(expectedBatchesNum), distData.Count)
|
|
assert.Equal(t, tms.metricsReceived, int(distData.Sum()))
|
|
assert.Equal(t, cfg.SendBatchSize, uint32(distData.Min))
|
|
assert.Equal(t, cfg.SendBatchSize, uint32(distData.Max))
|
|
tms.mtx.RUnlock()
|
|
}
|
|
|
|
func TestBatchMetricsProcessor_Timeout(t *testing.T) {
|
|
cfg := Config{
|
|
Timeout: 100 * time.Millisecond,
|
|
SendBatchSize: 100,
|
|
}
|
|
requestCount := 5
|
|
metricsPerRequest := 10
|
|
// Instantiate upstream component to receive data after the batch processor.
|
|
tms := newTestMetricsSender()
|
|
|
|
createParams := component.ProcessorCreateParams{Logger: zap.NewNop()}
|
|
batcher := newBatchMetricsProcessor(createParams, tms, &cfg)
|
|
waitForCn := tms.waitFor(requestCount*metricsPerRequest, time.Second)
|
|
start := time.Now()
|
|
for requestNum := 0; requestNum < requestCount; requestNum++ {
|
|
md := testdata.GenerateMetricDataManyMetricsSameResource(metricsPerRequest)
|
|
pd := pdatautil.MetricsFromInternalMetrics(md)
|
|
go batcher.ConsumeMetrics(context.Background(), pd)
|
|
}
|
|
err := <-waitForCn
|
|
if err != nil {
|
|
t.Errorf("faild to wait for sender %s", err)
|
|
}
|
|
|
|
elapsed := time.Since(start)
|
|
require.LessOrEqual(t, cfg.Timeout.Nanoseconds(), elapsed.Nanoseconds())
|
|
|
|
tms.mtx.RLock()
|
|
|
|
expectedBatchesNum := 1
|
|
expectedBatchingFactor := 5
|
|
|
|
require.Equal(t, requestCount*metricsPerRequest, tms.metricsReceived)
|
|
require.Equal(t, expectedBatchesNum, len(tms.metricsDataReceiver))
|
|
for _, md := range tms.metricsDataReceiver {
|
|
require.Equal(t, expectedBatchingFactor, md.ResourceMetrics().Len())
|
|
for i := 0; i < expectedBatchingFactor; i++ {
|
|
require.Equal(t, metricsPerRequest, md.ResourceMetrics().At(i).InstrumentationLibraryMetrics().At(0).Metrics().Len())
|
|
}
|
|
}
|
|
tms.mtx.RUnlock()
|
|
}
|
|
|
|
func TestBatchMetricProcessor_Shutdown(t *testing.T) {
|
|
cfg := Config{
|
|
Timeout: 3 * time.Second,
|
|
SendBatchSize: 1000,
|
|
}
|
|
requestCount := 5
|
|
metricsPerRequest := 10
|
|
|
|
// Instantiate upstream component to receive data after the batch processor.
|
|
tms := newTestMetricsSender()
|
|
|
|
createParams := component.ProcessorCreateParams{Logger: zap.NewNop()}
|
|
batcher := newBatchMetricsProcessor(createParams, tms, &cfg)
|
|
waitForCn := tms.waitFor(requestCount*metricsPerRequest, 2*time.Second)
|
|
|
|
for requestNum := 0; requestNum < requestCount; requestNum++ {
|
|
md := testdata.GenerateMetricDataManyMetricsSameResource(metricsPerRequest)
|
|
pd := pdatautil.MetricsFromInternalMetrics(md)
|
|
batcher.ConsumeMetrics(context.Background(), pd)
|
|
}
|
|
|
|
batcher.Shutdown(context.Background())
|
|
|
|
// TODO (Issue 1070) - To check return value when fixing tests.
|
|
<-waitForCn
|
|
|
|
tms.mtx.RLock()
|
|
|
|
// TODO (Issue 1070) - Fix the tests so it can check that all 50 items went through.
|
|
require.Greater(t, tms.metricsReceived, 0)
|
|
require.Equal(t, 1, len(tms.metricsDataReceiver))
|
|
|
|
tms.mtx.RUnlock()
|
|
}
|