Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions .github/workflows/java-ec2-default-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand All @@ -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
Expand Down
23 changes: 23 additions & 0 deletions .github/workflows/test.yml
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove

Original file line number Diff line number Diff line change
@@ -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:
java-ec2-default:
uses: ./.github/workflows/java-ec2-default-test.yml
secrets: inherit
with:
caller-workflow-name: 'test'
aws-region: 'us-east-1'
4 changes: 4 additions & 0 deletions sample-apps/java/springboot-main-service/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -76,10 +92,70 @@ 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 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 (conditionally initialized)
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");

// Only create pipeline if environment variables exist (matching Python logic)
if (serviceName != null && deploymentEnvironmentName != null &&
!serviceName.isEmpty() && !deploymentEnvironmentName.isEmpty()) {

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 {
// No pipeline metrics if environment variables missing
this.customPipelineMeter = null;
this.customPipelineCounter = null;
this.customPipelineHistogram = null;
this.customPipelineGauge = null;
}
}

private int random(int min, int max) {
return (int) (Math.random() * (max - min + 1)) + min;
}

@GetMapping("/")
Expand All @@ -92,6 +168,18 @@ public String healthcheck() {
@GetMapping("/aws-sdk-call")
@ResponseBody
public String awssdkCall(@RequestParam(name = "testingId", required = false) String testingId) {

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"));

// 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) {
bucketName += "-" + testingId;
Expand Down Expand Up @@ -186,4 +274,4 @@ private String getXrayTraceId() {
String xrayTraceId = "1-" + traceId.substring(0, 8) + "-" + traceId.substring(8);
return String.format("{\"traceId\": \"%s\"}", xrayTraceId);
}
}
}
6 changes: 5 additions & 1 deletion terraform/java/ec2/default/amazon-cloudwatch-agent.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
}
16 changes: 10 additions & 6 deletions terraform/java/ec2/default/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -135,18 +135,22 @@ 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Undo


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='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" \
AWS_REGION='${var.aws_region}' \
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 &
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Undo


# The application needs time to come up and reach a steady state, this should not take longer than 30 seconds
sleep 30
Expand Down Expand Up @@ -233,7 +237,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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Undo


JAVA_TOOL_OPTIONS=' -javaagent:/home/ec2-user/adot.jar' \
OTEL_METRICS_EXPORTER=none \
Expand All @@ -244,7 +248,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 &
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Undo


# The application needs time to come up and reach a steady state, this should not take longer than 30 seconds
sleep 30
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
Loading
Loading