From 0392d075a12194a1d4dbd7d6809840349aef2ab3 Mon Sep 17 00:00:00 2001 From: Vherremi Date: Thu, 30 Oct 2025 10:05:57 -0700 Subject: [PATCH 1/4] Testing Java custom metrics --- .github/workflows/test.yml | 23 ++ .../springboot-main-service/build.gradle.kts | 4 + .../sampleapp/FrontendServiceController.java | 68 +++++- .../ec2/default/amazon-cloudwatch-agent.json | 6 +- terraform/java/ec2/default/main.tf | 15 +- .../PredefinedExpectedTemplate.java | 3 + .../default/aws-otel-custom-metrics.mustache | 228 ++++++++++++++++++ .../ec2/default/custom-metric-validation.yml | 6 + 8 files changed, 345 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 validator/src/main/resources/expected-data-template/java/ec2/default/aws-otel-custom-metrics.mustache create mode 100644 validator/src/main/resources/validations/java/ec2/default/custom-metric-validation.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..6da228c75 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,23 @@ +## Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +## SPDX-License-Identifier: Apache-2.0 + +# This is a reusable workflow for running the Enablement test for App Signals. +# It is meant to be called from another workflow. +# Read more about reusable workflows: https://docs.github.com/en/actions/using-workflows/reusing-workflows#overview +name: Test +on: + push: + branches: + - Java_Custom_metrics + +permissions: + id-token: write + contents: read + +jobs: + python-ec2-default: + uses: ./.github/workflows/java-ec2-default-test.yml + secrets: inherit + with: + caller-workflow-name: 'test' + aws-region: 'us-east-1' \ No newline at end of file diff --git a/sample-apps/java/springboot-main-service/build.gradle.kts b/sample-apps/java/springboot-main-service/build.gradle.kts index 16efdcfe4..ca79ae082 100644 --- a/sample-apps/java/springboot-main-service/build.gradle.kts +++ b/sample-apps/java/springboot-main-service/build.gradle.kts @@ -43,6 +43,10 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-logging") implementation("io.opentelemetry:opentelemetry-api:1.34.1") + implementation("io.opentelemetry:opentelemetry-sdk:1.34.1") + implementation("io.opentelemetry:opentelemetry-sdk-metrics:1.34.1") + implementation("io.opentelemetry:opentelemetry-exporter-otlp:1.34.1") + implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.34.1") implementation("software.amazon.awssdk:s3") implementation("software.amazon.awssdk:sts") implementation("com.mysql:mysql-connector-j:8.4.0") diff --git a/sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java b/sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java index a84855c9f..d20fc9538 100644 --- a/sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java +++ b/sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java @@ -45,6 +45,22 @@ import org.springframework.web.bind.annotation.ResponseBody; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.GetBucketLocationRequest; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; +import io.opentelemetry.sdk.metrics.export.MetricExporter; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.metrics.export.MetricReader; +import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.api.metrics.DoubleHistogram; +import io.opentelemetry.api.metrics.LongUpDownCounter; +import java.time.Duration; @Controller public class FrontendServiceController { @@ -76,10 +92,50 @@ private void runLocalRootClientCallRecurringService() { // run the service executorService.scheduleAtFixedRate(runnableTask, 100, 1000, TimeUnit.MILLISECONDS); } + // Agent-based metrics using GlobalOpenTelemetry + private static final Meter meter = GlobalOpenTelemetry.getMeter("myMeter"); + private static final LongCounter counter = meter.counterBuilder("agent_based_counter").build(); + private static final DoubleHistogram histogram = meter.histogramBuilder("agent_based_histogram").build(); + private static final LongUpDownCounter gauge = meter.upDownCounterBuilder("agent_based_gauge").build(); + + // Pipeline-based metrics (initialized in constructor) + private final Meter pipelineMeter; + private final LongCounter pipelineCounter; + private final DoubleHistogram pipelineHistogram; + private final LongUpDownCounter pipelineGauge; + @Autowired public FrontendServiceController(CloseableHttpClient httpClient, S3Client s3) { this.httpClient = httpClient; this.s3 = s3; + + MetricExporter pipelineMetricExporter = OtlpHttpMetricExporter.builder() + .setEndpoint("http://localhost:4318/v1/metrics") + .setTimeout(Duration.ofSeconds(10)) + .build(); + + MetricReader pipelineMetricReader = PeriodicMetricReader.builder(pipelineMetricExporter) + .setInterval(Duration.ofSeconds(60)) + .build(); + + SdkMeterProvider pipelineMeterProvider = SdkMeterProvider.builder() + .setResource(Resource.getDefault()) + .registerMetricReader(pipelineMetricReader) + .build(); + + OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() + .setMeterProvider(pipelineMeterProvider) + .build(); + + // Initialize pipeline metrics + this.pipelineMeter = openTelemetry.getMeter("myMeter"); + this.pipelineCounter = pipelineMeter.counterBuilder("custom_pipeline_counter").build(); + this.pipelineHistogram = pipelineMeter.histogramBuilder("custom_pipeline_histogram").build(); + this.pipelineGauge = pipelineMeter.upDownCounterBuilder("custom_pipeline_gauge").build(); + } + + private int random(int min, int max) { + return (int) (Math.random() * (max - min + 1)) + min; } @GetMapping("/") @@ -92,6 +148,16 @@ public String healthcheck() { @GetMapping("/aws-sdk-call") @ResponseBody public String awssdkCall(@RequestParam(name = "testingId", required = false) String testingId) { + + logger.info("Incrementing custom counter - OpenTelemetry available: {}", GlobalOpenTelemetry.get() != null); + counter.add(1, Attributes.of(AttributeKey.stringKey("Operation"), "counter")); + histogram.record((double)random(100,1000), Attributes.of(AttributeKey.stringKey("Operation"), "histogram")); + gauge.add(random(-10,10), Attributes.of(AttributeKey.stringKey("Operation"), "gauge")); + + pipelineCounter.add(1, Attributes.of(AttributeKey.stringKey("Operation"), "pipeline_counter")); + pipelineHistogram.record(random(100,1000), Attributes.of(AttributeKey.stringKey("Operation"), "pipeline_histogram")); + pipelineGauge.add(random(-10,10), Attributes.of(AttributeKey.stringKey("Operation"), "pipeline_gauge")); + String bucketName = "e2e-test-bucket-name"; if (testingId != null) { bucketName += "-" + testingId; @@ -186,4 +252,4 @@ private String getXrayTraceId() { String xrayTraceId = "1-" + traceId.substring(0, 8) + "-" + traceId.substring(8); return String.format("{\"traceId\": \"%s\"}", xrayTraceId); } -} +} \ No newline at end of file diff --git a/terraform/java/ec2/default/amazon-cloudwatch-agent.json b/terraform/java/ec2/default/amazon-cloudwatch-agent.json index a98a40d36..f65bfe32e 100644 --- a/terraform/java/ec2/default/amazon-cloudwatch-agent.json +++ b/terraform/java/ec2/default/amazon-cloudwatch-agent.json @@ -10,7 +10,11 @@ }, "logs": { "metrics_collected": { - "application_signals": {} + "application_signals": {}, + "otlp": { + "grpc_endpoint": "0.0.0.0:4317", + "http_endpoint": "0.0.0.0:4318" + } } } } \ No newline at end of file diff --git a/terraform/java/ec2/default/main.tf b/terraform/java/ec2/default/main.tf index 3ef773a08..eebfdd30d 100644 --- a/terraform/java/ec2/default/main.tf +++ b/terraform/java/ec2/default/main.tf @@ -135,18 +135,21 @@ resource "null_resource" "main_service_setup" { ${var.get_adot_jar_command} # Get and run the sample application with configuration - aws s3 cp ${var.sample_app_jar} ./main-service.jar + aws s3 cp ${var.sample_app_jar} ./main-service-delete-me.jar JAVA_TOOL_OPTIONS=' -javaagent:/home/ec2-user/adot.jar' \ - OTEL_METRICS_EXPORTER=none \ + OTEL_METRICS_EXPORTER=otlp \ OTEL_LOGS_EXPORT=none \ OTEL_AWS_APPLICATION_SIGNALS_ENABLED=true \ OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT=http://localhost:4316/v1/metrics \ OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4316/v1/traces \ - OTEL_RESOURCE_ATTRIBUTES="service.name=sample-application-${var.test_id},Internal_Org=Financial,Business Unit=Payments,Region=us-east-1,aws.application_signals.metric_resource_keys=Business Unit&Region&Organization" \ + SERVICE_NAME='python-sample-application-${var.test_id}' + DEPLOYMENT_ENVIRONMENT_NAME='ec2:default' + OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://localhost:4318/v1/metrics \ + OTEL_RESOURCE_ATTRIBUTES="service.name=$${SERVICE_NAME},deployment.environment.name=$${DEPLOYMENT_ENVIRONMENT_NAME},Internal_Org=Financial,Business Unit=Payments,Region=us-east-1,aws.application_signals.metric_resource_keys=Business Unit&Region&Organization" \ OTEL_INSTRUMENTATION_COMMON_EXPERIMENTAL_CONTROLLER_TELEMETRY_ENABLED=true \ - nohup java -XX:+UseG1GC -jar main-service.jar &> nohup.out & + nohup java -XX:+UseG1GC -jar main-service-delete-me.jar &> nohup.out & # The application needs time to come up and reach a steady state, this should not take longer than 30 seconds sleep 30 @@ -233,7 +236,7 @@ resource "null_resource" "remote_service_setup" { ${var.get_adot_jar_command} # Get and run the sample application with configuration - aws s3 cp ${var.sample_remote_app_jar} ./remote-service.jar + aws s3 cp ${var.sample_remote_app_jar} ./remote-service-delete-me.jar JAVA_TOOL_OPTIONS=' -javaagent:/home/ec2-user/adot.jar' \ OTEL_METRICS_EXPORTER=none \ @@ -244,7 +247,7 @@ resource "null_resource" "remote_service_setup" { OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4316/v1/traces \ OTEL_RESOURCE_ATTRIBUTES=service.name=sample-remote-application-${var.test_id} \ OTEL_INSTRUMENTATION_COMMON_EXPERIMENTAL_CONTROLLER_TELEMETRY_ENABLED=true \ - nohup java -XX:+UseG1GC -jar remote-service.jar &> nohup.out & + nohup java -XX:+UseG1GC -jar remote-service-delete-me.jar &> nohup.out & # The application needs time to come up and reach a steady state, this should not take longer than 30 seconds sleep 30 diff --git a/validator/src/main/java/com/amazon/aoc/fileconfigs/PredefinedExpectedTemplate.java b/validator/src/main/java/com/amazon/aoc/fileconfigs/PredefinedExpectedTemplate.java index 9346119ee..a130d9cac 100644 --- a/validator/src/main/java/com/amazon/aoc/fileconfigs/PredefinedExpectedTemplate.java +++ b/validator/src/main/java/com/amazon/aoc/fileconfigs/PredefinedExpectedTemplate.java @@ -71,6 +71,9 @@ public enum PredefinedExpectedTemplate implements FileConfig { JAVA_EC2_DEFAULT_AWS_SDK_CALL_METRIC("/expected-data-template/java/ec2/default/aws-sdk-call-metric.mustache"), JAVA_EC2_DEFAULT_AWS_SDK_CALL_TRACE("/expected-data-template/java/ec2/default/aws-sdk-call-trace.mustache"), + /** Java EC2 Default Custom Metrics Test Case Validations */ + JAVA_EC2_DEFAULT_AWS_OTEL_CUSTOM_METRIC("/expected-data-template/java/ec2/default/aws-otel-custom-metrics.mustache"), + JAVA_EC2_DEFAULT_REMOTE_SERVICE_LOG("/expected-data-template/java/ec2/default/remote-service-log.mustache"), JAVA_EC2_DEFAULT_REMOTE_SERVICE_METRIC("/expected-data-template/java/ec2/default/remote-service-metric.mustache"), JAVA_EC2_DEFAULT_REMOTE_SERVICE_TRACE("/expected-data-template/java/ec2/default/remote-service-trace.mustache"), diff --git a/validator/src/main/resources/expected-data-template/java/ec2/default/aws-otel-custom-metrics.mustache b/validator/src/main/resources/expected-data-template/java/ec2/default/aws-otel-custom-metrics.mustache new file mode 100644 index 000000000..ba88624c7 --- /dev/null +++ b/validator/src/main/resources/expected-data-template/java/ec2/default/aws-otel-custom-metrics.mustache @@ -0,0 +1,228 @@ +# OpenTelemetry Custom Metrics Validation Templates - AWS SDK Call Only +# ANY_VALUE defines a string to = 'ANY_VALUE' to pass validation testing +# Custom export templates +- + metricName: agent_based_counter + namespace: {{metricNamespace}} + dimensions: + - + name: deployment.environment.name + value: ec2:default + - + name: aws.local.service + value: {{serviceName}} + - + name: cloud.region + value: {{region}} + - + name: service.name + value: {{serviceName}} + - + name: Operation + value: counter + - + name: host.type + value: ANY_VALUE + - + name: cloud.availability_zone + value: ANY_VALUE + - + name: telemetry.sdk.name + value: opentelemetry + - + name: telemetry.sdk.language + value: java + - + name: cloud.provider + value: aws + - + name: cloud.account.id + value: {{accountId}} + - + name: host.name + value: ANY_VALUE + - + name: telemetry.sdk.version + value: ANY_VALUE + - + name: host.id + value: ANY_VALUE + - + name: telemetry.auto.version + value: ANY_VALUE + - + name: cloud.platform + value: aws_ec2 +- + metricName: agent_based_histogram + namespace: {{metricNamespace}} + dimensions: + - + name: deployment.environment.name + value: ec2:default + - + name: aws.local.service + value: {{serviceName}} + - + name: cloud.region + value: {{region}} + - + name: service.name + value: {{serviceName}} + - + name: Operation + value: histogram + - + name: host.type + value: ANY_VALUE + - + name: cloud.availability_zone + value: ANY_VALUE + - + name: telemetry.sdk.name + value: opentelemetry + - + name: telemetry.sdk.language + value: java + - + name: cloud.provider + value: aws + - + name: cloud.account.id + value: {{accountId}} + - + name: host.name + value: ANY_VALUE + - + name: telemetry.sdk.version + value: ANY_VALUE + - + name: host.id + value: ANY_VALUE + - + name: telemetry.auto.version + value: ANY_VALUE + - + name: cloud.platform + value: aws_ec2 +- + metricName: agent_based_gauge + namespace: {{metricNamespace}} + dimensions: + - + name: deployment.environment.name + value: ec2:default + - + name: aws.local.service + value: {{serviceName}} + - + name: cloud.region + value: {{region}} + - + name: service.name + value: {{serviceName}} + - + name: Operation + value: gauge + - + name: host.type + value: ANY_VALUE + - + name: cloud.availability_zone + value: ANY_VALUE + - + name: telemetry.sdk.name + value: opentelemetry + - + name: telemetry.sdk.language + value: java + - + name: cloud.provider + value: aws + - + name: cloud.account.id + value: {{accountId}} + - + name: host.name + value: ANY_VALUE + - + name: telemetry.sdk.version + value: ANY_VALUE + - + name: host.id + value: ANY_VALUE + - + name: telemetry.auto.version + value: ANY_VALUE + - + name: cloud.platform + value: aws_ec2 + value: aws_ec2 + +# Export pipeline metrics +- + metricName: custom_pipeline_counter + namespace: {{metricNamespace}} + dimensions: + - + name: deployment.environment.name + value: ec2:default + - + name: service.name + value: {{serviceName}} + - + name: Operation + value: pipeline_counter + - + name: telemetry.sdk.name + value: opentelemetry + - + name: telemetry.sdk.language + value: java + - + name: telemetry.sdk.version + value: ANY_VALUE +- + metricName: custom_pipeline_histogram + namespace: {{metricNamespace}} + dimensions: + - + name: deployment.environment.name + value: ec2:default + - + name: service.name + value: {{serviceName}} + - + name: Operation + value: pipeline_histogram + - + name: telemetry.sdk.name + value: opentelemetry + - + name: telemetry.sdk.language + value: java + - + name: telemetry.sdk.version + value: ANY_VALUE +- + metricName: custom_pipeline_gauge + namespace: {{metricNamespace}} + dimensions: + - + name: deployment.environment.name + value: ec2:default + - + name: service.name + value: {{serviceName}} + - + name: Operation + value: pipeline_gauge + - + name: telemetry.sdk.name + value: opentelemetry + - + name: telemetry.sdk.language + value: java + - + name: telemetry.sdk.version + value: ANY_VALUE \ No newline at end of file diff --git a/validator/src/main/resources/validations/java/ec2/default/custom-metric-validation.yml b/validator/src/main/resources/validations/java/ec2/default/custom-metric-validation.yml new file mode 100644 index 000000000..2e1b27ac3 --- /dev/null +++ b/validator/src/main/resources/validations/java/ec2/default/custom-metric-validation.yml @@ -0,0 +1,6 @@ +- + validationType: "cw-metric" + httpPath: "aws-sdk-call" + httpMethod: "get" + callingType: "http-with-query" + expectedMetricTemplate: "JAVA_EC2_DEFAULT_AWS_OTEL_CUSTOM_METRIC" \ No newline at end of file From 2928537730534beab8c9d9140cc80afda8e0e174 Mon Sep 17 00:00:00 2001 From: Vherremi Date: Thu, 30 Oct 2025 12:56:19 -0700 Subject: [PATCH 2/4] fixing pr changes --- .../sampleapp/FrontendServiceController.java | 15 +++++++-------- terraform/java/ec2/default/main.tf | 2 +- .../ec2/default/aws-otel-custom-metrics.mustache | 1 - 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java b/sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java index d20fc9538..76398d812 100644 --- a/sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java +++ b/sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java @@ -94,15 +94,15 @@ private void runLocalRootClientCallRecurringService() { // run the service // Agent-based metrics using GlobalOpenTelemetry private static final Meter meter = GlobalOpenTelemetry.getMeter("myMeter"); - private static final LongCounter counter = meter.counterBuilder("agent_based_counter").build(); - private static final DoubleHistogram histogram = meter.histogramBuilder("agent_based_histogram").build(); - private static final LongUpDownCounter gauge = meter.upDownCounterBuilder("agent_based_gauge").build(); + private static final LongCounter agentBasedCounter = meter.counterBuilder("agent_based_counter").build(); + private static final DoubleHistogram agentBasedHistogram = meter.histogramBuilder("agent_based_histogram").build(); + private static final LongUpDownCounter agentBasedGauge = meter.upDownCounterBuilder("agent_based_gauge").build(); // Pipeline-based metrics (initialized in constructor) private final Meter pipelineMeter; - private final LongCounter pipelineCounter; - private final DoubleHistogram pipelineHistogram; - private final LongUpDownCounter pipelineGauge; + private final LongCounter CustomPipelineCounter; + private final DoubleHistogram CustomPipelineHistogram; + private final LongUpDownCounter CustomPipelineGauge; @Autowired public FrontendServiceController(CloseableHttpClient httpClient, S3Client s3) { @@ -115,7 +115,7 @@ public FrontendServiceController(CloseableHttpClient httpClient, S3Client s3) { .build(); MetricReader pipelineMetricReader = PeriodicMetricReader.builder(pipelineMetricExporter) - .setInterval(Duration.ofSeconds(60)) + .setInterval(Duration.ofSeconds(1)) .build(); SdkMeterProvider pipelineMeterProvider = SdkMeterProvider.builder() @@ -149,7 +149,6 @@ public String healthcheck() { @ResponseBody public String awssdkCall(@RequestParam(name = "testingId", required = false) String testingId) { - logger.info("Incrementing custom counter - OpenTelemetry available: {}", GlobalOpenTelemetry.get() != null); counter.add(1, Attributes.of(AttributeKey.stringKey("Operation"), "counter")); histogram.record((double)random(100,1000), Attributes.of(AttributeKey.stringKey("Operation"), "histogram")); gauge.add(random(-10,10), Attributes.of(AttributeKey.stringKey("Operation"), "gauge")); diff --git a/terraform/java/ec2/default/main.tf b/terraform/java/ec2/default/main.tf index eebfdd30d..8537c30d7 100644 --- a/terraform/java/ec2/default/main.tf +++ b/terraform/java/ec2/default/main.tf @@ -144,7 +144,7 @@ resource "null_resource" "main_service_setup" { OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT=http://localhost:4316/v1/metrics \ OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4316/v1/traces \ - SERVICE_NAME='python-sample-application-${var.test_id}' + SERVICE_NAME='sample-application-${var.test_id}' DEPLOYMENT_ENVIRONMENT_NAME='ec2:default' OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://localhost:4318/v1/metrics \ OTEL_RESOURCE_ATTRIBUTES="service.name=$${SERVICE_NAME},deployment.environment.name=$${DEPLOYMENT_ENVIRONMENT_NAME},Internal_Org=Financial,Business Unit=Payments,Region=us-east-1,aws.application_signals.metric_resource_keys=Business Unit&Region&Organization" \ diff --git a/validator/src/main/resources/expected-data-template/java/ec2/default/aws-otel-custom-metrics.mustache b/validator/src/main/resources/expected-data-template/java/ec2/default/aws-otel-custom-metrics.mustache index ba88624c7..9ec466b63 100644 --- a/validator/src/main/resources/expected-data-template/java/ec2/default/aws-otel-custom-metrics.mustache +++ b/validator/src/main/resources/expected-data-template/java/ec2/default/aws-otel-custom-metrics.mustache @@ -157,7 +157,6 @@ - name: cloud.platform value: aws_ec2 - value: aws_ec2 # Export pipeline metrics - From feaa11a015826e66ffc71d5803645d2aba0edf07 Mon Sep 17 00:00:00 2001 From: Vherremi Date: Thu, 30 Oct 2025 14:18:49 -0700 Subject: [PATCH 3/4] addressing pr comments --- .github/workflows/java-ec2-default-test.yml | 22 +++++++- .github/workflows/test.yml | 2 +- .../sampleapp/FrontendServiceController.java | 50 ++++++++++++------- terraform/java/ec2/default/main.tf | 1 + 4 files changed, 55 insertions(+), 20 deletions(-) diff --git a/.github/workflows/java-ec2-default-test.yml b/.github/workflows/java-ec2-default-test.yml index b94942178..7dafe7af9 100644 --- a/.github/workflows/java-ec2-default-test.yml +++ b/.github/workflows/java-ec2-default-test.yml @@ -251,9 +251,27 @@ jobs: --instance-id ${{ env.MAIN_SERVICE_INSTANCE_ID }} --rollup' + - name: Validate custom metrics + id: cwagent-metric-validation + if: (success() || steps.log-validation.outcome == 'failure' || steps.metric-validation.outcome == 'failure') && !cancelled() + run: ./gradlew validator:run --args='-c java/ec2/default/metric-validation.yml + --testing-id ${{ env.TESTING_ID }} + --endpoint http://${{ env.MAIN_SERVICE_ENDPOINT }} + --remote-service-deployment-name ${{ env.REMOTE_SERVICE_IP }}:8080 + --region ${{ inputs.aws-region }} + --account-id ${{ env.ACCOUNT_ID }} + --metric-namespace CWAgent + --log-group ${{ env.LOG_GROUP_NAME }} + --service-name sample-application-${{ env.TESTING_ID }} + --remote-service-name sample-remote-application-${{ env.TESTING_ID }} + --query-string ip=${{ env.REMOTE_SERVICE_IP }}&testingId=${{ env.TESTING_ID }} + --instance-ami ${{ env.EC2_INSTANCE_AMI }} + --instance-id ${{ env.MAIN_SERVICE_INSTANCE_ID }} + --rollup' + - name: Validate generated traces id: trace-validation - if: (success() || steps.log-validation.outcome == 'failure' || steps.metric-validation.outcome == 'failure') && !cancelled() + if: (success() || steps.log-validation.outcome == 'failure' || steps.metric-validation.outcome == 'failure' || steps.cwagent-metric-validation.outcome == 'failure') && !cancelled() run: ./gradlew validator:run --args='-c java/ec2/default/trace-validation.yml --testing-id ${{ env.TESTING_ID }} --endpoint http://${{ env.MAIN_SERVICE_ENDPOINT }} @@ -280,7 +298,7 @@ jobs: if: always() id: validation-result run: | - if [ "${{ steps.log-validation.outcome }}" = "success" ] && [ "${{ steps.metric-validation.outcome }}" = "success" ] && [ "${{ steps.trace-validation.outcome }}" = "success" ]; then + if [ "${{ steps.log-validation.outcome }}" = "success" ] && [ "${{ steps.metric-validation.outcome }}" = "success" ] && [ "${{ steps.cwagent-metric-validation.outcome }}" = "success" ] && [ "${{ steps.trace-validation.outcome }}" = "success" ]; then echo "validation-result=success" >> $GITHUB_OUTPUT else echo "validation-result=failure" >> $GITHUB_OUTPUT diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6da228c75..656aba12a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ permissions: contents: read jobs: - python-ec2-default: + java-ec2-default: uses: ./.github/workflows/java-ec2-default-test.yml secrets: inherit with: diff --git a/sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java b/sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java index 76398d812..c22d578c2 100644 --- a/sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java +++ b/sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java @@ -99,27 +99,43 @@ private void runLocalRootClientCallRecurringService() { // run the service private static final LongUpDownCounter agentBasedGauge = meter.upDownCounterBuilder("agent_based_gauge").build(); // Pipeline-based metrics (initialized in constructor) - private final Meter pipelineMeter; - private final LongCounter CustomPipelineCounter; - private final DoubleHistogram CustomPipelineHistogram; - private final LongUpDownCounter CustomPipelineGauge; + private final Meter customPipelineMeter; + private final LongCounter customPipelineCounter; + private final DoubleHistogram customPipelineHistogram; + private final LongUpDownCounter customPipelineGauge; @Autowired public FrontendServiceController(CloseableHttpClient httpClient, S3Client s3) { this.httpClient = httpClient; this.s3 = s3; + + // Get environment variables + String serviceName = System.getenv("SERVICE_NAME"); + String deploymentEnvironmentName = System.getenv("DEPLOYMENT_ENVIRONMENT_NAME"); + + // Create pipeline resource without interfering attributes + Resource pipelineResource; + if (serviceName != null && deploymentEnvironmentName != null && + !serviceName.isEmpty() && !deploymentEnvironmentName.isEmpty()) { + pipelineResource = Resource.getDefault().toBuilder() + .put("service.name", serviceName) + .put("deployment.environment.name", deploymentEnvironmentName) + .build(); + } else { + pipelineResource = Resource.getDefault(); + } MetricExporter pipelineMetricExporter = OtlpHttpMetricExporter.builder() .setEndpoint("http://localhost:4318/v1/metrics") .setTimeout(Duration.ofSeconds(10)) .build(); - MetricReader pipelineMetricReader = PeriodicMetricReader.builder(pipelineMetricExporter) + MetricReader pipelineMetricReader = PeriodicMetricReader.builder(pipelineMetricExporter) .setInterval(Duration.ofSeconds(1)) .build(); SdkMeterProvider pipelineMeterProvider = SdkMeterProvider.builder() - .setResource(Resource.getDefault()) + .setResource(pipelineResource) .registerMetricReader(pipelineMetricReader) .build(); @@ -128,12 +144,12 @@ public FrontendServiceController(CloseableHttpClient httpClient, S3Client s3) { .build(); // Initialize pipeline metrics - this.pipelineMeter = openTelemetry.getMeter("myMeter"); - this.pipelineCounter = pipelineMeter.counterBuilder("custom_pipeline_counter").build(); - this.pipelineHistogram = pipelineMeter.histogramBuilder("custom_pipeline_histogram").build(); - this.pipelineGauge = pipelineMeter.upDownCounterBuilder("custom_pipeline_gauge").build(); + this.customPipelineMeter = openTelemetry.getMeter("myMeter"); + this.customPipelineCounter = customPipelineMeter.counterBuilder("custom_pipeline_counter").build(); + this.customPipelineHistogram = customPipelineMeter.histogramBuilder("custom_pipeline_histogram").build(); + this.customPipelineGauge = customPipelineMeter.upDownCounterBuilder("custom_pipeline_gauge").build(); } - + private int random(int min, int max) { return (int) (Math.random() * (max - min + 1)) + min; } @@ -149,13 +165,13 @@ public String healthcheck() { @ResponseBody public String awssdkCall(@RequestParam(name = "testingId", required = false) String testingId) { - counter.add(1, Attributes.of(AttributeKey.stringKey("Operation"), "counter")); - histogram.record((double)random(100,1000), Attributes.of(AttributeKey.stringKey("Operation"), "histogram")); - gauge.add(random(-10,10), Attributes.of(AttributeKey.stringKey("Operation"), "gauge")); + agentBasedCounter.add(1, Attributes.of(AttributeKey.stringKey("Operation"), "counter")); + agentBasedHistogram.record((double)random(100,1000), Attributes.of(AttributeKey.stringKey("Operation"), "histogram")); + agentBasedGauge.add(random(-10,10), Attributes.of(AttributeKey.stringKey("Operation"), "gauge")); - pipelineCounter.add(1, Attributes.of(AttributeKey.stringKey("Operation"), "pipeline_counter")); - pipelineHistogram.record(random(100,1000), Attributes.of(AttributeKey.stringKey("Operation"), "pipeline_histogram")); - pipelineGauge.add(random(-10,10), Attributes.of(AttributeKey.stringKey("Operation"), "pipeline_gauge")); + customPipelineCounter.add(1, Attributes.of(AttributeKey.stringKey("Operation"), "pipeline_counter")); + customPipelineHistogram.record(random(100,1000), Attributes.of(AttributeKey.stringKey("Operation"), "pipeline_histogram")); + customPipelineGauge.add(random(-10,10), Attributes.of(AttributeKey.stringKey("Operation"), "pipeline_gauge")); String bucketName = "e2e-test-bucket-name"; if (testingId != null) { diff --git a/terraform/java/ec2/default/main.tf b/terraform/java/ec2/default/main.tf index 8537c30d7..c94b206e9 100644 --- a/terraform/java/ec2/default/main.tf +++ b/terraform/java/ec2/default/main.tf @@ -148,6 +148,7 @@ resource "null_resource" "main_service_setup" { DEPLOYMENT_ENVIRONMENT_NAME='ec2:default' OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://localhost:4318/v1/metrics \ OTEL_RESOURCE_ATTRIBUTES="service.name=$${SERVICE_NAME},deployment.environment.name=$${DEPLOYMENT_ENVIRONMENT_NAME},Internal_Org=Financial,Business Unit=Payments,Region=us-east-1,aws.application_signals.metric_resource_keys=Business Unit&Region&Organization" \ + AWS_REGION='${var.aws_region}' \ OTEL_INSTRUMENTATION_COMMON_EXPERIMENTAL_CONTROLLER_TELEMETRY_ENABLED=true \ nohup java -XX:+UseG1GC -jar main-service-delete-me.jar &> nohup.out & From 276b77ae8787757b624d09112f1043d6ba624d60 Mon Sep 17 00:00:00 2001 From: Vherremi Date: Tue, 11 Nov 2025 07:39:54 -0800 Subject: [PATCH 4/4] Making pipeline conditional, JAR has NOT been created or uploaded --- .../sampleapp/FrontendServiceController.java | 71 ++++++++++--------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java b/sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java index c22d578c2..59346fc2b 100644 --- a/sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java +++ b/sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java @@ -98,7 +98,7 @@ private void runLocalRootClientCallRecurringService() { // run the service private static final DoubleHistogram agentBasedHistogram = meter.histogramBuilder("agent_based_histogram").build(); private static final LongUpDownCounter agentBasedGauge = meter.upDownCounterBuilder("agent_based_gauge").build(); - // Pipeline-based metrics (initialized in constructor) + // Pipeline-based metrics (conditionally initialized) private final Meter customPipelineMeter; private final LongCounter customPipelineCounter; private final DoubleHistogram customPipelineHistogram; @@ -113,41 +113,45 @@ public FrontendServiceController(CloseableHttpClient httpClient, S3Client s3) { String serviceName = System.getenv("SERVICE_NAME"); String deploymentEnvironmentName = System.getenv("DEPLOYMENT_ENVIRONMENT_NAME"); - // Create pipeline resource without interfering attributes - Resource pipelineResource; + // Only create pipeline if environment variables exist (matching Python logic) if (serviceName != null && deploymentEnvironmentName != null && !serviceName.isEmpty() && !deploymentEnvironmentName.isEmpty()) { - pipelineResource = Resource.getDefault().toBuilder() + + Resource pipelineResource = Resource.getDefault().toBuilder() .put("service.name", serviceName) .put("deployment.environment.name", deploymentEnvironmentName) .build(); + + MetricExporter pipelineMetricExporter = OtlpHttpMetricExporter.builder() + .setEndpoint("http://localhost:4317") + .setTimeout(Duration.ofSeconds(10)) + .build(); + + MetricReader pipelineMetricReader = PeriodicMetricReader.builder(pipelineMetricExporter) + .setInterval(Duration.ofSeconds(1)) + .build(); + + SdkMeterProvider pipelineMeterProvider = SdkMeterProvider.builder() + .setResource(pipelineResource) + .registerMetricReader(pipelineMetricReader) + .build(); + + OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() + .setMeterProvider(pipelineMeterProvider) + .build(); + + // Initialize pipeline metrics + this.customPipelineMeter = openTelemetry.getMeter("myMeter"); + this.customPipelineCounter = customPipelineMeter.counterBuilder("custom_pipeline_counter").build(); + this.customPipelineHistogram = customPipelineMeter.histogramBuilder("custom_pipeline_histogram").build(); + this.customPipelineGauge = customPipelineMeter.upDownCounterBuilder("custom_pipeline_gauge").build(); } else { - pipelineResource = Resource.getDefault(); + // No pipeline metrics if environment variables missing + this.customPipelineMeter = null; + this.customPipelineCounter = null; + this.customPipelineHistogram = null; + this.customPipelineGauge = null; } - - MetricExporter pipelineMetricExporter = OtlpHttpMetricExporter.builder() - .setEndpoint("http://localhost:4318/v1/metrics") - .setTimeout(Duration.ofSeconds(10)) - .build(); - - MetricReader pipelineMetricReader = PeriodicMetricReader.builder(pipelineMetricExporter) - .setInterval(Duration.ofSeconds(1)) - .build(); - - SdkMeterProvider pipelineMeterProvider = SdkMeterProvider.builder() - .setResource(pipelineResource) - .registerMetricReader(pipelineMetricReader) - .build(); - - OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() - .setMeterProvider(pipelineMeterProvider) - .build(); - - // Initialize pipeline metrics - this.customPipelineMeter = openTelemetry.getMeter("myMeter"); - this.customPipelineCounter = customPipelineMeter.counterBuilder("custom_pipeline_counter").build(); - this.customPipelineHistogram = customPipelineMeter.histogramBuilder("custom_pipeline_histogram").build(); - this.customPipelineGauge = customPipelineMeter.upDownCounterBuilder("custom_pipeline_gauge").build(); } private int random(int min, int max) { @@ -169,9 +173,12 @@ public String awssdkCall(@RequestParam(name = "testingId", required = false) Str agentBasedHistogram.record((double)random(100,1000), Attributes.of(AttributeKey.stringKey("Operation"), "histogram")); agentBasedGauge.add(random(-10,10), Attributes.of(AttributeKey.stringKey("Operation"), "gauge")); - customPipelineCounter.add(1, Attributes.of(AttributeKey.stringKey("Operation"), "pipeline_counter")); - customPipelineHistogram.record(random(100,1000), Attributes.of(AttributeKey.stringKey("Operation"), "pipeline_histogram")); - customPipelineGauge.add(random(-10,10), Attributes.of(AttributeKey.stringKey("Operation"), "pipeline_gauge")); + // Only record pipeline metrics if pipeline exists (matching Python logic) + if (customPipelineCounter != null) { + customPipelineCounter.add(1, Attributes.of(AttributeKey.stringKey("Operation"), "pipeline_counter")); + customPipelineHistogram.record(random(100,1000), Attributes.of(AttributeKey.stringKey("Operation"), "pipeline_histogram")); + customPipelineGauge.add(random(-10,10), Attributes.of(AttributeKey.stringKey("Operation"), "pipeline_gauge")); + } String bucketName = "e2e-test-bucket-name"; if (testingId != null) {