opentelemetry-collector/service/telemetry_test.go

377 lines
11 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package service
import (
"context"
"net/http"
"net/http/httptest"
"testing"
io_prometheus_client "github.com/prometheus/client_model/go"
"github.com/prometheus/common/expfmt"
"github.com/stretchr/testify/require"
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
"go.opentelemetry.io/contrib/config"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configtelemetry"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/internal/globalgates"
"go.opentelemetry.io/collector/internal/testutil"
semconv "go.opentelemetry.io/collector/semconv/v1.18.0"
"go.opentelemetry.io/collector/service/internal/proctelemetry"
"go.opentelemetry.io/collector/service/internal/resource"
"go.opentelemetry.io/collector/service/telemetry"
)
const (
metricPrefix = "otelcol_"
otelPrefix = "otel_sdk_"
ocPrefix = "oc_sdk_"
grpcPrefix = "gprc_"
httpPrefix = "http_"
counterName = "test_counter"
)
func TestTelemetryInit(t *testing.T) {
type metricValue struct {
value float64
labels map[string]string
}
for _, tc := range []struct {
name string
disableHighCard bool
disableCensusBridge bool
expectedMetrics map[string]metricValue
extendedConfig bool
cfg *telemetry.Config
}{
{
name: "UseOpenTelemetryForInternalMetrics",
expectedMetrics: map[string]metricValue{
metricPrefix + ocPrefix + counterName: {
value: 13,
labels: map[string]string{
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
metricPrefix + otelPrefix + counterName: {
value: 13,
labels: map[string]string{
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
metricPrefix + grpcPrefix + counterName: {
value: 11,
labels: map[string]string{
"net_sock_peer_addr": "",
"net_sock_peer_name": "",
"net_sock_peer_port": "",
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
metricPrefix + httpPrefix + counterName: {
value: 10,
labels: map[string]string{
"net_host_name": "",
"net_host_port": "",
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
"target_info": {
value: 0,
labels: map[string]string{
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
},
},
{
name: "DisableHighCardinalityWithOtel",
disableHighCard: true,
expectedMetrics: map[string]metricValue{
metricPrefix + ocPrefix + counterName: {
value: 13,
labels: map[string]string{
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
metricPrefix + otelPrefix + counterName: {
value: 13,
labels: map[string]string{
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
metricPrefix + grpcPrefix + counterName: {
value: 11,
labels: map[string]string{
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
metricPrefix + httpPrefix + counterName: {
value: 10,
labels: map[string]string{
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
"target_info": {
value: 0,
labels: map[string]string{
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
},
},
{
name: "UseOTelWithSDKConfiguration",
extendedConfig: true,
cfg: &telemetry.Config{
Metrics: telemetry.MetricsConfig{
Level: configtelemetry.LevelDetailed,
},
Traces: telemetry.TracesConfig{
Processors: []config.SpanProcessor{
{
Batch: &config.BatchSpanProcessor{
Exporter: config.SpanExporter{
Console: config.Console{},
},
},
},
},
},
Resource: map[string]*string{
semconv.AttributeServiceInstanceID: &testInstanceID,
},
},
expectedMetrics: map[string]metricValue{
metricPrefix + ocPrefix + counterName: {
value: 13,
labels: map[string]string{
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
metricPrefix + otelPrefix + counterName: {
value: 13,
labels: map[string]string{
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
metricPrefix + grpcPrefix + counterName: {
value: 11,
labels: map[string]string{
"net_sock_peer_addr": "",
"net_sock_peer_name": "",
"net_sock_peer_port": "",
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
metricPrefix + httpPrefix + counterName: {
value: 10,
labels: map[string]string{
"net_host_name": "",
"net_host_port": "",
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
"target_info": {
value: 0,
labels: map[string]string{
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
},
},
{
name: "DisableOpenCensusBridge",
expectedMetrics: map[string]metricValue{
metricPrefix + otelPrefix + counterName: {
value: 13,
labels: map[string]string{
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
metricPrefix + grpcPrefix + counterName: {
value: 11,
labels: map[string]string{
"net_sock_peer_addr": "",
"net_sock_peer_name": "",
"net_sock_peer_port": "",
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
metricPrefix + httpPrefix + counterName: {
value: 10,
labels: map[string]string{
"net_host_name": "",
"net_host_port": "",
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
"target_info": {
value: 0,
labels: map[string]string{
"service_name": "otelcol",
"service_version": "latest",
"service_instance_id": testInstanceID,
},
},
},
disableCensusBridge: true,
},
} {
t.Run(tc.name, func(t *testing.T) {
require.NoError(t, featuregate.GlobalRegistry().Set(globalgates.DisableOpenCensusBridge.ID(), tc.disableCensusBridge))
t.Cleanup(func() {
require.NoError(t, featuregate.GlobalRegistry().Set(globalgates.DisableOpenCensusBridge.ID(), true))
})
if tc.extendedConfig {
tc.cfg.Metrics.Readers = []config.MetricReader{
{
Pull: &config.PullMetricReader{
Exporter: config.MetricExporter{
Prometheus: testutil.GetAvailableLocalAddressPrometheus(t),
},
},
},
}
}
if tc.cfg == nil {
tc.cfg = &telemetry.Config{
Resource: map[string]*string{
semconv.AttributeServiceInstanceID: &testInstanceID,
},
Metrics: telemetry.MetricsConfig{
Level: configtelemetry.LevelDetailed,
Address: testutil.GetAvailableLocalAddress(t),
},
}
}
set := meterProviderSettings{
res: resource.New(component.NewDefaultBuildInfo(), tc.cfg.Resource),
cfg: tc.cfg.Metrics,
asyncErrorChannel: make(chan error),
}
mp, err := newMeterProvider(set, tc.disableHighCard)
require.NoError(t, err)
defer func() {
if prov, ok := mp.(interface{ Shutdown(context.Context) error }); ok {
require.NoError(t, prov.Shutdown(context.Background()))
}
}()
v := createTestMetrics(t, mp)
defer func() {
view.Unregister(v)
}()
metrics := getMetricsFromPrometheus(t, mp.(*meterProvider).servers[0].Handler)
require.Equal(t, len(tc.expectedMetrics), len(metrics))
for metricName, metricValue := range tc.expectedMetrics {
mf, present := metrics[metricName]
require.True(t, present, "expected metric %q was not present", metricName)
require.Len(t, mf.Metric, 1, "only one measure should exist for metric %q", metricName)
labels := make(map[string]string)
for _, pair := range mf.Metric[0].Label {
labels[pair.GetName()] = pair.GetValue()
}
require.Equal(t, metricValue.labels, labels, "labels for metric %q was different than expected", metricName)
require.Equal(t, metricValue.value, mf.Metric[0].Counter.GetValue(), "value for metric %q was different than expected", metricName)
}
})
}
}
func createTestMetrics(t *testing.T, mp metric.MeterProvider) *view.View {
// Creates a OTel Go counter
counter, err := mp.Meter("collector_test").Int64Counter(otelPrefix+counterName, metric.WithUnit("ms"))
require.NoError(t, err)
counter.Add(context.Background(), 13)
grpcExampleCounter, err := mp.Meter(proctelemetry.GRPCInstrumentation).Int64Counter(grpcPrefix + counterName)
require.NoError(t, err)
grpcExampleCounter.Add(context.Background(), 11, metric.WithAttributes(proctelemetry.GRPCUnacceptableKeyValues...))
httpExampleCounter, err := mp.Meter(proctelemetry.HTTPInstrumentation).Int64Counter(httpPrefix + counterName)
require.NoError(t, err)
httpExampleCounter.Add(context.Background(), 10, metric.WithAttributes(proctelemetry.HTTPUnacceptableKeyValues...))
// Creates a OpenCensus measure
ocCounter := stats.Int64(ocPrefix+counterName, counterName, stats.UnitDimensionless)
v := &view.View{
Name: ocPrefix + counterName,
Description: ocCounter.Description(),
Measure: ocCounter,
Aggregation: view.Sum(),
}
err = view.Register(v)
require.NoError(t, err)
stats.Record(context.Background(), stats.Int64(ocPrefix+counterName, counterName, stats.UnitDimensionless).M(13))
// Forces a flush for the view data.
_, _ = view.RetrieveData(ocPrefix + counterName)
return v
}
func getMetricsFromPrometheus(t *testing.T, handler http.Handler) map[string]*io_prometheus_client.MetricFamily {
req, err := http.NewRequest(http.MethodGet, "/metrics", nil)
require.NoError(t, err)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
var parser expfmt.TextParser
parsed, err := parser.TextToMetricFamilies(rr.Body)
require.NoError(t, err)
return parsed
}