diff --git a/test/integration/configs/instrumenter-config-elixir.yml b/test/integration/configs/instrumenter-config-elixir.yml new file mode 100644 index 000000000..c50973115 --- /dev/null +++ b/test/integration/configs/instrumenter-config-elixir.yml @@ -0,0 +1,6 @@ +routes: + patterns: + - /test/:test_id + unmatched: path +otel_metrics_export: + endpoint: http://otelcol:4318 \ No newline at end of file diff --git a/test/integration/docker-compose-elixir.yml b/test/integration/docker-compose-elixir.yml new file mode 100644 index 000000000..ae907c394 --- /dev/null +++ b/test/integration/docker-compose-elixir.yml @@ -0,0 +1,81 @@ +version: '3.8' + +services: + testserver: + build: + context: ../.. + dockerfile: test/integration/components/elixir/Dockerfile + image: hatest-testserver-elixir + ports: + - "4000:4000" + depends_on: + otelcol: + condition: service_started + + autoinstrumenter: + build: + context: ../.. + dockerfile: ./test/integration/components/beyla/Dockerfile + command: + - --config=/configs/instrumenter-config-elixir.yml + volumes: + - ./configs/:/configs + - ./system/sys/kernel/security:/sys/kernel/security + - ../../testoutput:/coverage + - ../../testoutput/run:/var/run/beyla + image: hatest-autoinstrumenter + privileged: true # in some environments (not GH Pull Requests) you can set it to false and then cap_add: [ SYS_ADMIN ] + network_mode: "service:testserver" + pid: "service:testserver" + environment: + GOCOVERDIR: "/coverage" + BEYLA_PRINT_TRACES: "true" + BEYLA_OPEN_PORT: "4000" + BEYLA_DISCOVERY_POLL_INTERVAL: 500ms + BEYLA_SERVICE_NAMESPACE: "integration-test" + BEYLA_METRICS_INTERVAL: "10ms" + BEYLA_BPF_BATCH_TIMEOUT: "10ms" + BEYLA_LOG_LEVEL: "DEBUG" + BEYLA_BPF_DEBUG: "TRUE" + BEYLA_METRICS_REPORT_TARGET: "true" + BEYLA_METRICS_REPORT_PEER: "true" + BEYLA_HOSTNAME: "beyla" + depends_on: + testserver: + condition: service_started + + # OpenTelemetry Collector + otelcol: + image: otel/opentelemetry-collector-contrib:0.85.0 + container_name: otel-col + deploy: + resources: + limits: + memory: 125M + restart: unless-stopped + command: [ "--config=/etc/otelcol-config/otelcol-config.yml" ] + volumes: + - ./configs/:/etc/otelcol-config + ports: + - "4317" # OTLP over gRPC receiver + - "4318:4318" # OTLP over HTTP receiver + - "9464" # Prometheus exporter + - "8888" # metrics endpoint + depends_on: + prometheus: + condition: service_started + + # Prometheus + prometheus: + image: quay.io/prometheus/prometheus:v2.46.0 + container_name: prometheus + command: + - --storage.tsdb.retention.time=1m + - --config.file=/etc/prometheus/prometheus-config.yml + - --storage.tsdb.path=/prometheus + - --web.enable-lifecycle + - --web.route-prefix=/ + volumes: + - ./configs/:/etc/prometheus + ports: + - "9090:9090" diff --git a/test/integration/red_test_elixir.go b/test/integration/red_test_elixir.go new file mode 100644 index 000000000..d9820616c --- /dev/null +++ b/test/integration/red_test_elixir.go @@ -0,0 +1,65 @@ +//go:build integration + +package integration + +import ( + "fmt" + "testing" + + "github.com/mariomac/guara/pkg/test" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/grafana/beyla/test/integration/components/prom" +) + +// does a smoke test to verify that all the components that started +// asynchronously for the Elixir test are up and communicating properly +func waitForElixirTestComponents(t *testing.T, url string) { + waitForTestComponentsSub(t, url, "/smoke") +} + +func testREDMetricsForElixirHTTPLibrary(t *testing.T, url string, comm string) { + path := "/test" + + pq := prom.Client{HostPort: prometheusHostPort} + var results []prom.Result + + // Call 4 times the instrumented service, forcing it to: + // - process multiple calls in a row with, one more than we might need + // - returning a 200 code + for i := 0; i < 4; i++ { + doHTTPGet(t, fmt.Sprintf("%s%s/%d", url, path, i), 200) + } + + // Eventually, Prometheus would make this query visible + test.Eventually(t, testTimeout, func(t require.TestingT) { + var err error + results, err = pq.Query(`http_server_request_duration_seconds_count{` + + `http_request_method="GET",` + + `http_response_status_code="200",` + + `service_namespace="integration-test",` + + `service_name="` + comm + `",` + + `http_route="/test/:test_id"}`) + require.NoError(t, err) + enoughPromResults(t, results) + val := totalPromCount(t, results) + assert.LessOrEqual(t, 3, val) + if len(results) > 0 { + res := results[0] + addr := res.Metric["client_address"] + assert.NotNil(t, addr) + } + }) +} + +func testREDMetricsElixirHTTP(t *testing.T) { + for _, testCaseURL := range []string{ + "http://localhost:4000", + } { + t.Run(testCaseURL, func(t *testing.T) { + waitForElixirTestComponents(t, testCaseURL) + testREDMetricsForElixirHTTPLibrary(t, testCaseURL, "beam.smp") + }) + } +} diff --git a/test/integration/suites_test.go b/test/integration/suites_test.go index 3f4c7ee5c..6f4797ed7 100644 --- a/test/integration/suites_test.go +++ b/test/integration/suites_test.go @@ -512,6 +512,16 @@ func TestSuiteNoRoutes(t *testing.T) { t.Run("BPF pinning folder unmounted", testBPFPinningUnmounted) } +func TestSuite_Elixir(t *testing.T) { + compose, err := docker.ComposeSuite("docker-compose-elixir.yml", path.Join(pathOutput, "test-suite-elixir.log")) + require.NoError(t, err) + require.NoError(t, compose.Up()) + t.Run("Elixir RED metrics", testREDMetricsElixirHTTP) + t.Run("BPF pinning folder mounted", testBPFPinningMounted) + require.NoError(t, compose.Close()) + t.Run("BPF pinning folder unmounted", testBPFPinningUnmounted) +} + // Helpers var lockdownPath = "/sys/kernel/security/lockdown"