From 22daea3bbbbac4f9b0cbb5f1191bce214af6e224 Mon Sep 17 00:00:00 2001 From: William Dumont Date: Thu, 23 Nov 2023 09:53:10 +0100 Subject: [PATCH] Improve otel integration tests (#5753) * improve otel integration tests * improve otlp integration test code * apply same test refactor for prom tests * lint --- .github/workflows/integration-tests.yml | 2 +- .../otel-collector-contrib.yaml | 27 --- .../configs/otel-gen-client/main.go | 205 ------------------ .../configs/otel-gen-server/Dockerfile | 11 - .../configs/otel-gen-server/main.go | 155 ------------- .../Dockerfile | 4 +- .../configs/otel-metrics-gen/main.go | 91 ++++++++ integration-tests/docker-compose.yaml | 38 +--- .../tests/otlp-metrics/config.river | 4 +- .../tests/otlp-metrics/otlp_metrics_test.go | 73 ++++++- .../scrape_prom_metrics_test.go | 41 ++-- integration-tests/utils.go | 29 +-- 12 files changed, 207 insertions(+), 473 deletions(-) delete mode 100644 integration-tests/configs/otel-collector-contrib/otel-collector-contrib.yaml delete mode 100644 integration-tests/configs/otel-gen-client/main.go delete mode 100644 integration-tests/configs/otel-gen-server/Dockerfile delete mode 100644 integration-tests/configs/otel-gen-server/main.go rename integration-tests/configs/{otel-gen-client => otel-metrics-gen}/Dockerfile (60%) create mode 100644 integration-tests/configs/otel-metrics-gen/main.go diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index b27dfa59319e..80540ae4848e 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -18,6 +18,6 @@ jobs: with: go-version: "1.21" - name: Set OTEL Exporter Endpoint - run: echo "OTEL_EXPORTER_ENDPOINT=http://172.17.0.1:8080" >> $GITHUB_ENV + run: echo "OTEL_EXPORTER_ENDPOINT=172.17.0.1:4318" >> $GITHUB_ENV - name: Run tests run: make integration-test diff --git a/integration-tests/configs/otel-collector-contrib/otel-collector-contrib.yaml b/integration-tests/configs/otel-collector-contrib/otel-collector-contrib.yaml deleted file mode 100644 index 7359cfb35215..000000000000 --- a/integration-tests/configs/otel-collector-contrib/otel-collector-contrib.yaml +++ /dev/null @@ -1,27 +0,0 @@ -receivers: - otlp: - protocols: - grpc: - -exporters: - logging: - - otlphttp: - endpoint: ${OTEL_EXPORTER_ENDPOINT} - - -connectors: - spanmetrics: - namespace: span.metrics - exemplars: - enabled: true - metrics_flush_interval: 1s - -service: - pipelines: - traces: - receivers: [otlp] - exporters: [spanmetrics] - metrics: - receivers: [spanmetrics] - exporters: [otlphttp] diff --git a/integration-tests/configs/otel-gen-client/main.go b/integration-tests/configs/otel-gen-client/main.go deleted file mode 100644 index 7066daccf26d..000000000000 --- a/integration-tests/configs/otel-gen-client/main.go +++ /dev/null @@ -1,205 +0,0 @@ -// This file was copied with minor modifications from https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/b0be5c98325ec71f35b82e278a3fc3e6f3fe4954/examples/demo/client/main.go - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -// Sample contains a simple client that periodically makes a simple http request -// to a server and exports to the OpenTelemetry service. -package main - -import ( - "context" - "log" - "math/rand" - "net/http" - "os" - "time" - - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/baggage" - "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/propagation" - sdkmetric "go.opentelemetry.io/otel/sdk/metric" - "go.opentelemetry.io/otel/sdk/resource" - sdktrace "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.4.0" - "google.golang.org/grpc" -) - -const ( - otelExporterOtlpEndpoint = "OTEL_EXPORTER_OTLP_ENDPOINT" - demoServerEndpoint = "DEMO_SERVER_ENDPOINT" -) - -// Initializes an OTLP exporter, and configures the corresponding trace and -// metric providers. -func initProvider() func() { - ctx := context.Background() - - res, err := resource.New(ctx, - resource.WithFromEnv(), - resource.WithProcess(), - resource.WithTelemetrySDK(), - resource.WithHost(), - resource.WithAttributes( - // the service name used to display traces in backends - semconv.ServiceNameKey.String("demo-client"), - ), - ) - handleErr(err, "failed to create resource") - - otelAgentAddr, ok := os.LookupEnv(otelExporterOtlpEndpoint) - if !ok { - otelAgentAddr = "0.0.0.0:4317" - } - - metricExp, err := otlpmetricgrpc.New( - ctx, - otlpmetricgrpc.WithInsecure(), - otlpmetricgrpc.WithEndpoint(otelAgentAddr), - ) - handleErr(err, "Failed to create the collector metric exporter") - - meterProvider := sdkmetric.NewMeterProvider( - sdkmetric.WithResource(res), - sdkmetric.WithReader( - sdkmetric.NewPeriodicReader( - metricExp, - sdkmetric.WithInterval(2*time.Second), - ), - ), - ) - otel.SetMeterProvider(meterProvider) - - traceClient := otlptracegrpc.NewClient( - otlptracegrpc.WithInsecure(), - otlptracegrpc.WithEndpoint(otelAgentAddr), - otlptracegrpc.WithDialOption(grpc.WithBlock())) - sctx, cancel := context.WithTimeout(ctx, time.Second) - defer cancel() - traceExp, err := otlptrace.New(sctx, traceClient) - handleErr(err, "Failed to create the collector trace exporter") - - bsp := sdktrace.NewBatchSpanProcessor(traceExp) - tracerProvider := sdktrace.NewTracerProvider( - sdktrace.WithSampler(sdktrace.AlwaysSample()), - sdktrace.WithResource(res), - sdktrace.WithSpanProcessor(bsp), - ) - - // set global propagator to tracecontext (the default is no-op). - otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) - otel.SetTracerProvider(tracerProvider) - - return func() { - cxt, cancel := context.WithTimeout(ctx, time.Second) - defer cancel() - if err := traceExp.Shutdown(cxt); err != nil { - otel.Handle(err) - } - // pushes any last exports to the receiver - if err := meterProvider.Shutdown(cxt); err != nil { - otel.Handle(err) - } - } -} - -func handleErr(err error, message string) { - if err != nil { - log.Fatalf("%s: %v", message, err) - } -} - -func main() { - shutdown := initProvider() - defer shutdown() - - tracer := otel.Tracer("demo-client-tracer") - meter := otel.Meter("demo-client-meter") - - method, _ := baggage.NewMember("method", "repl") - client, _ := baggage.NewMember("client", "cli") - bag, _ := baggage.New(method, client) - - // labels represent additional key-value descriptors that can be bound to a - // metric observer or recorder. - // TODO: Use baggage when supported to extract labels from baggage. - commonLabels := []attribute.KeyValue{ - attribute.String("method", "repl"), - attribute.String("client", "cli"), - } - - // Recorder metric example - requestLatency, _ := meter.Float64Histogram( - "demo_client/request_latency", - metric.WithDescription("The latency of requests processed"), - ) - - // TODO: Use a view to just count number of measurements for requestLatency when available. - requestCount, _ := meter.Int64Counter( - "demo_client/request_counts", - metric.WithDescription("The number of requests processed"), - ) - - lineLengths, _ := meter.Int64Histogram( - "demo_client/line_lengths", - metric.WithDescription("The lengths of the various lines in"), - ) - - // TODO: Use a view to just count number of measurements for lineLengths when available. - lineCounts, _ := meter.Int64Counter( - "demo_client/line_counts", - metric.WithDescription("The counts of the lines in"), - ) - - defaultCtx := baggage.ContextWithBaggage(context.Background(), bag) - rng := rand.New(rand.NewSource(time.Now().UnixNano())) - for { - startTime := time.Now() - ctx, span := tracer.Start(defaultCtx, "ExecuteRequest") - makeRequest(ctx) - span.End() - latencyMs := float64(time.Since(startTime)) / 1e6 - nr := int(rng.Int31n(7)) - for i := 0; i < nr; i++ { - randLineLength := rng.Int63n(999) - lineCounts.Add(ctx, 1, metric.WithAttributes(commonLabels...)) - lineLengths.Record(ctx, randLineLength, metric.WithAttributes(commonLabels...)) - } - - requestLatency.Record(ctx, latencyMs, metric.WithAttributes(commonLabels...)) - requestCount.Add(ctx, 1, metric.WithAttributes(commonLabels...)) - - time.Sleep(time.Duration(1) * time.Second) - } -} - -func makeRequest(ctx context.Context) { - demoServerAddr, ok := os.LookupEnv(demoServerEndpoint) - if !ok { - demoServerAddr = "http://0.0.0.0:7080/hello" - } - - // Trace an HTTP client by wrapping the transport - client := http.Client{ - Transport: otelhttp.NewTransport(http.DefaultTransport), - } - - // Make sure we pass the context to the request to avoid broken traces. - req, err := http.NewRequestWithContext(ctx, "GET", demoServerAddr, nil) - if err != nil { - handleErr(err, "failed to http request") - } - - // All requests made with this client will create spans. - res, err := client.Do(req) - if err != nil { - panic(err) - } - res.Body.Close() -} diff --git a/integration-tests/configs/otel-gen-server/Dockerfile b/integration-tests/configs/otel-gen-server/Dockerfile deleted file mode 100644 index bc6bc1c6d136..000000000000 --- a/integration-tests/configs/otel-gen-server/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright The OpenTelemetry Authors -# SPDX-License-Identifier: Apache-2.0 -FROM golang:1.21 as build -WORKDIR /app/ -COPY go.mod go.sum ./ -RUN go mod download -COPY ./integration-tests/configs/otel-gen-server/ ./ -RUN CGO_ENABLED=0 go build -o main main.go -FROM alpine:3.18 -COPY --from=build /app/main /app/main -CMD ["/app/main"] diff --git a/integration-tests/configs/otel-gen-server/main.go b/integration-tests/configs/otel-gen-server/main.go deleted file mode 100644 index 97bfb1ca2ef7..000000000000 --- a/integration-tests/configs/otel-gen-server/main.go +++ /dev/null @@ -1,155 +0,0 @@ -// This file was copied with minor modifications from https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/b0be5c98325ec71f35b82e278a3fc3e6f3fe4954/examples/demo/server/main.go - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -// Sample contains a simple http server that exports to the OpenTelemetry agent. - -package main - -import ( - "context" - "log" - "net/http" - "os" - "time" - - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/baggage" - "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/propagation" - sdkmetric "go.opentelemetry.io/otel/sdk/metric" - "go.opentelemetry.io/otel/sdk/resource" - sdktrace "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.4.0" - "go.opentelemetry.io/otel/trace" - "google.golang.org/grpc" -) - -const otelExporterOtlpEndpoint = "OTEL_EXPORTER_OTLP_ENDPOINT" - -// Initializes an OTLP exporter, and configures the corresponding trace and -// metric providers. -func initProvider() func() { - ctx := context.Background() - - res, err := resource.New(ctx, - resource.WithFromEnv(), - resource.WithProcess(), - resource.WithTelemetrySDK(), - resource.WithHost(), - resource.WithAttributes( - // the service name used to display traces in backends - semconv.ServiceNameKey.String("demo-server"), - ), - ) - handleErr(err, "failed to create resource") - - otelAgentAddr, ok := os.LookupEnv(otelExporterOtlpEndpoint) - if !ok { - otelAgentAddr = "0.0.0.0:4317" - } - - metricExp, err := otlpmetricgrpc.New( - ctx, - otlpmetricgrpc.WithInsecure(), - otlpmetricgrpc.WithEndpoint(otelAgentAddr)) - handleErr(err, "Failed to create the collector metric exporter") - - meterProvider := sdkmetric.NewMeterProvider( - sdkmetric.WithResource(res), - sdkmetric.WithReader( - sdkmetric.NewPeriodicReader( - metricExp, - sdkmetric.WithInterval(2*time.Second), - ), - ), - ) - otel.SetMeterProvider(meterProvider) - - traceClient := otlptracegrpc.NewClient( - otlptracegrpc.WithInsecure(), - otlptracegrpc.WithEndpoint(otelAgentAddr), - otlptracegrpc.WithDialOption(grpc.WithBlock())) - traceExp, err := otlptrace.New(ctx, traceClient) - handleErr(err, "Failed to create the collector trace exporter") - - bsp := sdktrace.NewBatchSpanProcessor(traceExp) - tracerProvider := sdktrace.NewTracerProvider( - sdktrace.WithSampler(sdktrace.AlwaysSample()), - sdktrace.WithResource(res), - sdktrace.WithSpanProcessor(bsp), - ) - - // set global propagator to tracecontext (the default is no-op). - otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) - otel.SetTracerProvider(tracerProvider) - - return func() { - cxt, cancel := context.WithTimeout(ctx, time.Second) - defer cancel() - if err := traceExp.Shutdown(cxt); err != nil { - otel.Handle(err) - } - // pushes any last exports to the receiver - if err := meterProvider.Shutdown(cxt); err != nil { - otel.Handle(err) - } - } -} - -func handleErr(err error, message string) { - if err != nil { - log.Fatalf("%s: %v", message, err) - } -} - -func main() { - shutdown := initProvider() - defer shutdown() - - meter := otel.Meter("demo-server-meter") - serverAttribute := attribute.String("server-attribute", "foo") - commonLabels := []attribute.KeyValue{serverAttribute} - requestCount, _ := meter.Int64Counter( - "demo_server/request_counts", - metric.WithDescription("The number of requests received"), - ) - - // create a handler wrapped in OpenTelemetry instrumentation - handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - time.Sleep(time.Duration(100) * time.Millisecond) - ctx := req.Context() - requestCount.Add(ctx, 1, metric.WithAttributes(commonLabels...)) - span := trace.SpanFromContext(ctx) - bag := baggage.FromContext(ctx) - - var baggageAttributes []attribute.KeyValue - baggageAttributes = append(baggageAttributes, serverAttribute) - for _, member := range bag.Members() { - baggageAttributes = append(baggageAttributes, attribute.String("baggage key:"+member.Key(), member.Value())) - } - span.SetAttributes(baggageAttributes...) - - if _, err := w.Write([]byte("Hello World")); err != nil { - http.Error(w, "write operation failed.", http.StatusInternalServerError) - return - } - }) - - mux := http.NewServeMux() - mux.Handle("/hello", otelhttp.NewHandler(handler, "/hello")) - server := &http.Server{ - Addr: ":7080", - Handler: mux, - ReadHeaderTimeout: 20 * time.Second, - } - if err := server.ListenAndServe(); err != http.ErrServerClosed { - handleErr(err, "server failed to serve") - } -} diff --git a/integration-tests/configs/otel-gen-client/Dockerfile b/integration-tests/configs/otel-metrics-gen/Dockerfile similarity index 60% rename from integration-tests/configs/otel-gen-client/Dockerfile rename to integration-tests/configs/otel-metrics-gen/Dockerfile index aae9f646067b..8bf00387660e 100644 --- a/integration-tests/configs/otel-gen-client/Dockerfile +++ b/integration-tests/configs/otel-metrics-gen/Dockerfile @@ -1,10 +1,8 @@ -# Copyright The OpenTelemetry Authors -# SPDX-License-Identifier: Apache-2.0 FROM golang:1.21 as build WORKDIR /app/ COPY go.mod go.sum ./ RUN go mod download -COPY ./integration-tests/configs/otel-gen-client/ ./ +COPY ./integration-tests/configs/otel-metrics-gen/ ./ RUN CGO_ENABLED=0 go build -o main main.go FROM alpine:3.18 COPY --from=build /app/main /app/main diff --git a/integration-tests/configs/otel-metrics-gen/main.go b/integration-tests/configs/otel-metrics-gen/main.go new file mode 100644 index 000000000000..6f88d7725b46 --- /dev/null +++ b/integration-tests/configs/otel-metrics-gen/main.go @@ -0,0 +1,91 @@ +package main + +import ( + "context" + "log" + "os" + "time" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + "go.opentelemetry.io/otel/sdk/metric" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" + semconv "go.opentelemetry.io/otel/semconv/v1.7.0" +) + +const otelExporterOtlpEndpoint = "OTEL_EXPORTER_ENDPOINT" + +func main() { + ctx := context.Background() + otlpExporterEndpoint, ok := os.LookupEnv(otelExporterOtlpEndpoint) + if !ok { + otlpExporterEndpoint = "localhost:4318" + } + exporter, err := otlpmetrichttp.New(ctx, + otlpmetrichttp.WithInsecure(), + otlpmetrichttp.WithEndpoint(otlpExporterEndpoint), + ) + if err != nil { + log.Fatalf("failed to create exporter: %v", err) + } + + resource, err := resource.New(ctx, + resource.WithAttributes( + semconv.ServiceNameKey.String("otel-metrics-gen"), + ), + ) + if err != nil { + log.Fatalf("failed to create resource: %v", err) + } + + exponentialHistogramView := metric.NewView( + metric.Instrument{ + Name: "example_exponential_*", + }, + metric.Stream{ + Aggregation: metric.AggregationBase2ExponentialHistogram{ + MaxSize: 160, + MaxScale: 20, + }, + }, + ) + + provider := sdkmetric.NewMeterProvider( + sdkmetric.WithReader(sdkmetric.NewPeriodicReader(exporter, sdkmetric.WithInterval(1*time.Second))), + sdkmetric.WithResource(resource), + metric.WithView(exponentialHistogramView), + ) + otel.SetMeterProvider(provider) + defer func() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if err := provider.Shutdown(ctx); err != nil { + log.Fatalf("Server shutdown error: %v", err) + } + }() + + meter := otel.Meter("example-meter") + counter, _ := meter.Int64Counter("example_counter") + floatCounter, _ := meter.Float64Counter("example_float_counter") + upDownCounter, _ := meter.Int64UpDownCounter("example_updowncounter") + floatUpDownCounter, _ := meter.Float64UpDownCounter("example_float_updowncounter") + histogram, _ := meter.Int64Histogram("example_histogram") + floatHistogram, _ := meter.Float64Histogram("example_float_histogram") + exponentialHistogram, _ := meter.Int64Histogram("example_exponential_histogram") + exponentialFloatHistogram, _ := meter.Float64Histogram("example_exponential_float_histogram") + + for { + counter.Add(ctx, 10) + floatCounter.Add(ctx, 2.5) + upDownCounter.Add(ctx, -5) + floatUpDownCounter.Add(ctx, 3.5) + histogram.Record(ctx, 2) + floatHistogram.Record(ctx, 6.5) + exponentialHistogram.Record(ctx, 5) + exponentialFloatHistogram.Record(ctx, 1.5) + + time.Sleep(200 * time.Millisecond) + } +} diff --git a/integration-tests/docker-compose.yaml b/integration-tests/docker-compose.yaml index b576a425fa68..a94a05db21d9 100644 --- a/integration-tests/docker-compose.yaml +++ b/integration-tests/docker-compose.yaml @@ -2,7 +2,7 @@ version: "3" services: mimir: - image: grafana/mimir:latest + image: grafana/mimir:2.10.4 volumes: - ./configs/mimir:/etc/mimir-config entrypoint: @@ -12,45 +12,17 @@ services: - "9009:9009" loki: - image: grafana/loki:2.8.3 + image: grafana/loki:latest command: -config.file=/etc/loki/local-config.yaml ports: - "3100:3100" - otel-collector: - image: otel/opentelemetry-collector-contrib:0.85.0 - restart: always - command: ["--config=/etc/otel-collector-contrib.yaml", ""] - volumes: - - ./configs/otel-collector-contrib/otel-collector-contrib.yaml:/etc/otel-collector-contrib.yaml - ports: - - "4317:4317" # OTLP gRPC receiver - - "4318:4318" # OTLP HTTP exporter - environment: - - OTEL_EXPORTER_ENDPOINT=${OTEL_EXPORTER_ENDPOINT:-http://host.docker.internal:8080} - - demo-client: + otel-metrics-gen: build: - dockerfile: ./integration-tests/configs/otel-gen-client/Dockerfile + dockerfile: ./integration-tests/configs/otel-metrics-gen/Dockerfile context: .. - restart: always environment: - - OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317 - - DEMO_SERVER_ENDPOINT=http://demo-server:7080/hello - depends_on: - - demo-server - - demo-server: - build: - dockerfile: ./integration-tests/configs/otel-gen-server/Dockerfile - context: .. - restart: always - environment: - - OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317 - ports: - - "7080" - depends_on: - - otel-collector + - OTEL_EXPORTER_ENDPOINT=${OTEL_EXPORTER_ENDPOINT:-host.docker.internal:4318} prom-gen: build: diff --git a/integration-tests/tests/otlp-metrics/config.river b/integration-tests/tests/otlp-metrics/config.river index 8c4f0ebc96e6..26a6212a3c11 100644 --- a/integration-tests/tests/otlp-metrics/config.river +++ b/integration-tests/tests/otlp-metrics/config.river @@ -1,7 +1,5 @@ otelcol.receiver.otlp "otlp_metrics" { - http { - endpoint="0.0.0.0:8080" - } + http {} output { metrics = [otelcol.processor.attributes.otlp_metrics.input] diff --git a/integration-tests/tests/otlp-metrics/otlp_metrics_test.go b/integration-tests/tests/otlp-metrics/otlp_metrics_test.go index a4bdd887c50c..03575e742b2d 100644 --- a/integration-tests/tests/otlp-metrics/otlp_metrics_test.go +++ b/integration-tests/tests/otlp-metrics/otlp_metrics_test.go @@ -1,23 +1,90 @@ package main import ( + "fmt" + "strconv" "testing" "github.com/grafana/agent/integration-tests/common" "github.com/stretchr/testify/assert" ) -const query = "http://localhost:9009/prometheus/api/v1/query?query=span_metrics_duration_bucket{test_name='otlp_metrics'}" +const promURL = "http://localhost:9009/prometheus/api/v1/query?query=" -func TestOtlpMetrics(t *testing.T) { +func metricQuery(metricName string) string { + return fmt.Sprintf("%s%s{test_name='otlp_metrics'}", promURL, metricName) +} + +func TestOTLPMetrics(t *testing.T) { + tests := []struct { + metric string + }{ + // TODO: better differentiate these metric types? + {"example_counter"}, + {"example_float_counter"}, + {"example_updowncounter"}, + {"example_float_updowncounter"}, + {"example_histogram_bucket"}, + {"example_float_histogram_bucket"}, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.metric, func(t *testing.T) { + t.Parallel() + assertMetricData(t, metricQuery(tt.metric), tt.metric) + }) + } + + histogramTests := []string{ + "example_exponential_histogram", + "example_exponential_float_histogram", + } + + for _, metric := range histogramTests { + metric := metric + t.Run(metric, func(t *testing.T) { + t.Parallel() + assertHistogramData(t, metricQuery(metric), metric) + }) + } +} + +func assertHistogramData(t *testing.T, query string, expectedMetric string) { + var metricResponse common.MetricResponse + assert.EventuallyWithT(t, func(c *assert.CollectT) { + err := common.FetchDataFromURL(query, &metricResponse) + assert.NoError(c, err) + if assert.NotEmpty(c, metricResponse.Data.Result) { + assert.Equal(c, metricResponse.Data.Result[0].Metric.Name, expectedMetric) + assert.Equal(c, metricResponse.Data.Result[0].Metric.TestName, "otlp_metrics") + if assert.NotNil(c, metricResponse.Data.Result[0].Histogram) { + histogram := metricResponse.Data.Result[0].Histogram + if assert.NotEmpty(c, histogram.Data.Count) { + count, _ := strconv.Atoi(histogram.Data.Count) + assert.Greater(c, count, 10, "Count should be at some point greater than 10.") + } + if assert.NotEmpty(c, histogram.Data.Sum) { + sum, _ := strconv.Atoi(histogram.Data.Sum) + assert.Greater(c, sum, 10, "Sum should be at some point greater than 10.") + } + assert.NotEmpty(c, histogram.Data.Buckets) + assert.Nil(c, metricResponse.Data.Result[0].Value) + } + } + }, common.DefaultTimeout, common.DefaultRetryInterval, "Histogram data did not satisfy the conditions within the time limit") +} + +func assertMetricData(t *testing.T, query, expectedMetric string) { var metricResponse common.MetricResponse assert.EventuallyWithT(t, func(c *assert.CollectT) { err := common.FetchDataFromURL(query, &metricResponse) assert.NoError(c, err) if assert.NotEmpty(c, metricResponse.Data.Result) { - assert.Equal(c, metricResponse.Data.Result[0].Metric.Name, "span_metrics_duration_bucket") + assert.Equal(c, metricResponse.Data.Result[0].Metric.Name, expectedMetric) assert.Equal(c, metricResponse.Data.Result[0].Metric.TestName, "otlp_metrics") assert.NotEmpty(c, metricResponse.Data.Result[0].Value.Value) + assert.Nil(c, metricResponse.Data.Result[0].Histogram) } }, common.DefaultTimeout, common.DefaultRetryInterval, "Data did not satisfy the conditions within the time limit") } diff --git a/integration-tests/tests/scrape-prom-metrics/scrape_prom_metrics_test.go b/integration-tests/tests/scrape-prom-metrics/scrape_prom_metrics_test.go index f57e1ca37b3c..467147846e67 100644 --- a/integration-tests/tests/scrape-prom-metrics/scrape_prom_metrics_test.go +++ b/integration-tests/tests/scrape-prom-metrics/scrape_prom_metrics_test.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "strconv" "testing" "github.com/grafana/agent/integration-tests/common" @@ -15,28 +16,26 @@ func metricQuery(metricName string) string { } func TestScrapePromMetrics(t *testing.T) { - tests := []struct { - query string - metric string - }{ + metrics := []string{ // TODO: better differentiate these metric types? - {metricQuery("golang_counter"), "golang_counter"}, - {metricQuery("golang_gauge"), "golang_gauge"}, - {metricQuery("golang_histogram_bucket"), "golang_histogram_bucket"}, - {metricQuery("golang_summary"), "golang_summary"}, + "golang_counter", + "golang_gauge", + "golang_histogram_bucket", + "golang_summary", + "golang_native_histogram", } - for _, tt := range tests { - tt := tt - t.Run(tt.metric, func(t *testing.T) { + for _, metric := range metrics { + metric := metric + t.Run(metric, func(t *testing.T) { t.Parallel() - assertMetricData(t, tt.query, tt.metric) + if metric == "golang_native_histogram" { + assertHistogramData(t, metricQuery(metric), metric) + } else { + assertMetricData(t, metricQuery(metric), metric) + } }) } - t.Run("golang_native_histogram", func(t *testing.T) { - t.Parallel() - assertHistogramData(t, metricQuery("golang_native_histogram"), "golang_native_histogram") - }) } func assertHistogramData(t *testing.T, query string, expectedMetric string) { @@ -49,8 +48,14 @@ func assertHistogramData(t *testing.T, query string, expectedMetric string) { assert.Equal(c, metricResponse.Data.Result[0].Metric.TestName, "scrape_prom_metrics") if assert.NotNil(c, metricResponse.Data.Result[0].Histogram) { histogram := metricResponse.Data.Result[0].Histogram - assert.NotEmpty(c, histogram.Data.Count) - assert.NotEmpty(c, histogram.Data.Sum) + if assert.NotEmpty(c, histogram.Data.Count) { + count, _ := strconv.Atoi(histogram.Data.Count) + assert.Greater(c, count, 10, "Count should be at some point greater than 10.") + } + if assert.NotEmpty(c, histogram.Data.Sum) { + sum, _ := strconv.ParseFloat(histogram.Data.Sum, 64) + assert.Greater(c, sum, 10., "Sum should be at some point greater than 10.") + } assert.NotEmpty(c, histogram.Data.Buckets) assert.Nil(c, metricResponse.Data.Result[0].Value) } diff --git a/integration-tests/utils.go b/integration-tests/utils.go index 06aba877203c..d723a235235c 100644 --- a/integration-tests/utils.go +++ b/integration-tests/utils.go @@ -3,6 +3,7 @@ package main import ( "bytes" "fmt" + "log" "os" "os/exec" "path/filepath" @@ -10,9 +11,7 @@ import ( ) const ( - agentBinaryPath = "../../../build/grafana-agent-flow" - dockerComposeCmd = "docker-compose" - makeCmd = "make" + agentBinaryPath = "../../../build/grafana-agent-flow" ) type TestLog struct { @@ -23,20 +22,22 @@ type TestLog struct { var logChan chan TestLog -func buildAgent() { - fmt.Println("Building agent...") - cmd := exec.Command(makeCmd, "-C", "..", "agent-flow") +func executeCommand(command string, args []string, taskDescription string) { + fmt.Printf("%s...\n", taskDescription) + cmd := exec.Command(command, args...) + var stderr bytes.Buffer + cmd.Stderr = &stderr if err := cmd.Run(); err != nil { - panic(err) + log.Fatalf("Error: %s\n", stderr.String()) } } +func buildAgent() { + executeCommand("make", []string{"-C", "..", "agent-flow"}, "Building agent") +} + func setupEnvironment() { - fmt.Println("Setting up environment with Docker Compose...") - cmd := exec.Command(dockerComposeCmd, "up", "-d") - if err := cmd.Run(); err != nil { - panic(err) - } + executeCommand("docker-compose", []string{"up", "-d"}, "Setting up environment with Docker Compose") } func runSingleTest(testDir string) { @@ -105,18 +106,18 @@ func runAllTests() { }(testDir) } wg.Wait() + close(logChan) } func cleanUpEnvironment() { fmt.Println("Cleaning up Docker environment...") - err := exec.Command(dockerComposeCmd, "down", "--volumes", "--rmi", "all").Run() + err := exec.Command("docker-compose", "down", "--volumes", "--rmi", "all").Run() if err != nil { panic(err) } } func reportResults() { - close(logChan) testsFailed := 0 for log := range logChan { fmt.Printf("Failure detected in %s:\n", log.TestDir)