From 2ce0aaedb5a4fccb5ad393f91c075c3f2d1b795e Mon Sep 17 00:00:00 2001 From: Vherremi Date: Thu, 30 Oct 2025 11:21:00 -0700 Subject: [PATCH 01/10] Testing .NET custom metrics --- .github/workflows/dotnet-ec2-default-test.yml | 17 ++ .github/workflows/test.yml | 23 ++ .../Controllers/AppController.cs | 41 +++- .../dotnet/asp_frontend_service/Startup.cs | 16 ++ .../asp_frontend_service.csproj | 4 + .../PredefinedExpectedTemplate.java | 3 + .../default/aws-otel-custom-metrics.mustache | 228 ++++++++++++++++++ .../ec2/default/custom-metric-validation.yml | 6 + 8 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test.yml create mode 100644 validator/src/main/resources/expected-data-template/node/ec2/default/aws-otel-custom-metrics.mustache create mode 100644 validator/src/main/resources/validations/node/ec2/default/custom-metric-validation.yml diff --git a/.github/workflows/dotnet-ec2-default-test.yml b/.github/workflows/dotnet-ec2-default-test.yml index a1e76e5f3..9a4242e60 100644 --- a/.github/workflows/dotnet-ec2-default-test.yml +++ b/.github/workflows/dotnet-ec2-default-test.yml @@ -229,6 +229,23 @@ jobs: --instance-id ${{ env.MAIN_SERVICE_INSTANCE_ID }} --rollup' + - name: Validate custom metrics + id: CWagent-metric-validation + if: (success() || steps.log-validation.outcome == 'failure') && !cancelled() + run: ./gradlew validator:run --args='-c dotnet/ec2/default/metric-validation.yml + --testing-id ${{ env.TESTING_ID }} + --endpoint http://${{ env.MAIN_SERVICE_ENDPOINT }} + --remote-service-deployment-name ${{ env.REMOTE_SERVICE_IP }}:8081 + --region ${{ env.E2E_TEST_AWS_REGION }} + --metric-namespace CWAgent + --log-group ${{ env.LOG_GROUP_NAME }} + --service-name dotnet-sample-application-${{ env.TESTING_ID }} + --remote-service-name dotnet-sample-remote-application-${{ env.TESTING_ID }} + --query-string ip=${{ env.REMOTE_SERVICE_IP }} + --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() diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..25973f837 --- /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: + - dotnet_Custom_metrics + +permissions: + id-token: write + contents: read + +jobs: + python-ec2-default: + uses: ./.github/workflows/dotnet-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/dotnet/asp_frontend_service/Controllers/AppController.cs b/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs index c4427f9ed..c6d05ae9f 100644 --- a/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs +++ b/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs @@ -9,6 +9,9 @@ using Amazon.S3; using Microsoft.AspNetCore.Mvc; using Amazon.S3.Model; +using System.Diagnostics.Metrics; +using System.Collections.Generic; + namespace asp_frontend_service.Controllers; @@ -22,6 +25,16 @@ public class AppController : ControllerBase private static bool threadStarted = false; private readonly AmazonS3Client s3Client = new AmazonS3Client(); private readonly HttpClient httpClient = new HttpClient(); + private static readonly Meter meter = new Meter("myMeter"); + private static readonly Counter agentBasedCounter = meter.CreateCounter("agent_based_counter"); + private static readonly Histogram agentBasedHistogram = meter.CreateHistogram("agent_based_histogram"); + private static readonly UpDownCounter agentBasedGauge = meter.CreateUpDownCounter("agent_based_gauge"); + + // Custom pipeline metrics + private static readonly Meter customPipelineMeter = new Meter("customPipelineMeter"); + private static readonly Counter customPipelineCounter = customPipelineMeter.CreateCounter("custom_pipeline_counter"); + private static readonly Histogram customPipelineHistogram = customPipelineMeter.CreateHistogram("custom_pipeline_histogram"); + private static readonly UpDownCounter customPipelineGauge = customPipelineMeter.CreateUpDownCounter("custom_pipeline_gauge"); private static readonly Thread thread = new Thread(() => { @@ -69,7 +82,33 @@ public string OutgoingHttp() [Route("/aws-sdk-call")] public string AWSSDKCall([FromQuery] string testingId) { - var request = new GetBucketLocationRequest() + var random = new Random(); + + // Agent-based metrics + var histogramValue = random.NextDouble() * 100; + var gaugeValue = random.Next(-10, 11); + agentBasedCounter.Add(1, new KeyValuePair("Operation", "counter")); + agentBasedHistogram.Record(histogramValue, new KeyValuePair("Operation", "histogram")); + agentBasedGauge.Add(gaugeValue, new KeyValuePair("Operation", "gauge")); + + // Custom pipeline metrics with required Telemetry.Source attribute + var pipelineHistogramValue = random.NextDouble() * 50; + var pipelineGaugeValue = random.Next(-5, 6); + var pipelineAttributes = new KeyValuePair[] { + new("Operation", "pipeline_counter"), + new("Telemetry.Source", "UserMetric"), + }; + var pipelineHistogramAttributes = new KeyValuePair[] { + new("Operation", "pipeline_histogram"), + new("Telemetry.Source", "UserMetric"), + }; + var pipelineGaugeAttributes = new KeyValuePair[] { + new("Operation", "pipeline_gauge"), + new("Telemetry.Source", "UserMetric"), + }; + + + var request = new GetBucketLocationRequest() { BucketName = testingId }; diff --git a/sample-apps/dotnet/asp_frontend_service/Startup.cs b/sample-apps/dotnet/asp_frontend_service/Startup.cs index 657099447..4e9d105fd 100644 --- a/sample-apps/dotnet/asp_frontend_service/Startup.cs +++ b/sample-apps/dotnet/asp_frontend_service/Startup.cs @@ -7,6 +7,9 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using OpenTelemetry.Metrics; +using OpenTelemetry.Resources; +using System.Collections.Generic; namespace asp_frontend_service; @@ -25,6 +28,19 @@ public void ConfigureServices(IServiceCollection services) services.AddControllers(); AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); + + // Configure OpenTelemetry for custom pipeline metrics + services.AddOpenTelemetry() + .ConfigureResource(resource => resource + .AddService(Environment.GetEnvironmentVariable("SERVICE_NAME") ?? "dotnet-sample-application") + .AddAttributes(new Dictionary { { "Telemetry.Source", "UserMetric" } })) + .WithMetrics(metrics => metrics + .AddMeter("customPipelineMeter") + .AddOtlpExporter(options => { + options.Endpoint = new Uri(Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT") ?? "http://localhost:4318/v1/metrics"); + Console.WriteLine($"Custom pipeline OTLP endpoint: {options.Endpoint}"); + }) + .AddConsoleExporter()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/sample-apps/dotnet/asp_frontend_service/asp_frontend_service.csproj b/sample-apps/dotnet/asp_frontend_service/asp_frontend_service.csproj index e1a7b7816..25c905d07 100644 --- a/sample-apps/dotnet/asp_frontend_service/asp_frontend_service.csproj +++ b/sample-apps/dotnet/asp_frontend_service/asp_frontend_service.csproj @@ -8,6 +8,10 @@ + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all 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..99c896a8d 100644 --- a/validator/src/main/java/com/amazon/aoc/fileconfigs/PredefinedExpectedTemplate.java +++ b/validator/src/main/java/com/amazon/aoc/fileconfigs/PredefinedExpectedTemplate.java @@ -315,6 +315,9 @@ public enum PredefinedExpectedTemplate implements FileConfig { DOTNET_EC2_WINDOWS_DEFAULT_AWS_SDK_CALL_METRIC("/expected-data-template/dotnet/ec2/windows/aws-sdk-call-metric.mustache"), DOTNET_EC2_WINDOWS_DEFAULT_AWS_SDK_CALL_TRACE("/expected-data-template/dotnet/ec2/windows/aws-sdk-call-trace.mustache"), + /** Python EC2 Default Custom Metrics Test Case Validations */ + DOTNET_EC2_DEFAULT_AWS_OTEL_CUSTOM_METRIC("/expected-data-template/dotnet/ec2/default/aws-otel-custom-metrics.mustache"), + DOTNET_EC2_WINDOWS_DEFAULT_REMOTE_SERVICE_LOG("/expected-data-template/dotnet/ec2/windows/remote-service-log.mustache"), DOTNET_EC2_WINDOWS_DEFAULT_REMOTE_SERVICE_METRIC("/expected-data-template/dotnet/ec2/windows/remote-service-metric.mustache"), // Because of a time sync issue, block the remote service trace check for now diff --git a/validator/src/main/resources/expected-data-template/node/ec2/default/aws-otel-custom-metrics.mustache b/validator/src/main/resources/expected-data-template/node/ec2/default/aws-otel-custom-metrics.mustache new file mode 100644 index 000000000..9fc5d6480 --- /dev/null +++ b/validator/src/main/resources/expected-data-template/node/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: dotnet + - + 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: dotnet + - + 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: dotnet + - + 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: dotnet + - + 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: dotnet + - + 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: dotnet + - + name: telemetry.sdk.version + value: ANY_VALUE \ No newline at end of file diff --git a/validator/src/main/resources/validations/node/ec2/default/custom-metric-validation.yml b/validator/src/main/resources/validations/node/ec2/default/custom-metric-validation.yml new file mode 100644 index 000000000..04227aecc --- /dev/null +++ b/validator/src/main/resources/validations/node/ec2/default/custom-metric-validation.yml @@ -0,0 +1,6 @@ +- + validationType: "cw-metric" + httpPath: "aws-sdk-call" + httpMethod: "get" + callingType: "http-with-query" + expectedMetricTemplate: "DOTNET_EC2_DEFAULT_AWS_OTEL_CUSTOM_METRIC" \ No newline at end of file From 7e9982f52ed84ff028508df2f14b599cff4e5cee Mon Sep 17 00:00:00 2001 From: Vherremi Date: Mon, 10 Nov 2025 09:49:26 -0800 Subject: [PATCH 02/10] dotnet-ec2-default test 1 --- .github/workflows/dotnet-ec2-default-test.yml | 10 +-- .github/workflows/test.yml | 4 +- .../Controllers/AppController.cs | 82 ++++++++++++++----- terraform/dotnet/ec2/default/main.tf | 15 ++-- .../PredefinedExpectedTemplate.java | 2 +- .../ec2/default/custom-metric-validation.yml | 0 6 files changed, 78 insertions(+), 35 deletions(-) rename validator/src/main/resources/validations/{node => dotnet}/ec2/default/custom-metric-validation.yml (100%) diff --git a/.github/workflows/dotnet-ec2-default-test.yml b/.github/workflows/dotnet-ec2-default-test.yml index 9a4242e60..493c6df7c 100644 --- a/.github/workflows/dotnet-ec2-default-test.yml +++ b/.github/workflows/dotnet-ec2-default-test.yml @@ -230,9 +230,9 @@ jobs: --rollup' - name: Validate custom metrics - id: CWagent-metric-validation - if: (success() || steps.log-validation.outcome == 'failure') && !cancelled() - run: ./gradlew validator:run --args='-c dotnet/ec2/default/metric-validation.yml + id: cwagent-metric-validation + if: (success() || steps.log-validation.outcome == 'failure' || steps.log-validation.outcome == 'failure') && !cancelled() + run: ./gradlew validator:run --args='-c dotnet/ec2/default/custom-metric-validation.yml --testing-id ${{ env.TESTING_ID }} --endpoint http://${{ env.MAIN_SERVICE_ENDPOINT }} --remote-service-deployment-name ${{ env.REMOTE_SERVICE_IP }}:8081 @@ -248,7 +248,7 @@ jobs: - 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.custom-metric-validation.outcome == 'failure') && !cancelled() run: ./gradlew validator:run --args='-c dotnet/ec2/default/trace-validation.yml --testing-id ${{ env.TESTING_ID }} --endpoint http://${{ env.MAIN_SERVICE_ENDPOINT }} @@ -275,7 +275,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.custom-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 25973f837..9f5f25f78 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,14 +8,14 @@ name: Test on: push: branches: - - dotnet_Custom_metrics + - dotnet_custom_metrics permissions: id-token: write contents: read jobs: - python-ec2-default: + dotnet-ec2-default: uses: ./.github/workflows/dotnet-ec2-default-test.yml secrets: inherit with: diff --git a/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs b/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs index c6d05ae9f..2ed3b5c91 100644 --- a/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs +++ b/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs @@ -11,6 +11,10 @@ using Amazon.S3.Model; using System.Diagnostics.Metrics; using System.Collections.Generic; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Resources; +using OpenTelemetry.Exporter; namespace asp_frontend_service.Controllers; @@ -30,11 +34,48 @@ public class AppController : ControllerBase private static readonly Histogram agentBasedHistogram = meter.CreateHistogram("agent_based_histogram"); private static readonly UpDownCounter agentBasedGauge = meter.CreateUpDownCounter("agent_based_gauge"); - // Custom pipeline metrics - private static readonly Meter customPipelineMeter = new Meter("customPipelineMeter"); - private static readonly Counter customPipelineCounter = customPipelineMeter.CreateCounter("custom_pipeline_counter"); - private static readonly Histogram customPipelineHistogram = customPipelineMeter.CreateHistogram("custom_pipeline_histogram"); - private static readonly UpDownCounter customPipelineGauge = customPipelineMeter.CreateUpDownCounter("custom_pipeline_gauge"); + // Custom pipeline metrics - only create if specific env vars exist + private static readonly Meter? pipelineMeter; + private static readonly Counter? customPipelineCounter; + private static readonly Histogram? customPipelineHistogram; + private static readonly UpDownCounter? customPipelineGauge; + private static readonly MeterProvider? pipelineMeterProvider; + + static AppController() + { + var serviceName = Environment.GetEnvironmentVariable("SERVICE_NAME"); + var deploymentEnv = Environment.GetEnvironmentVariable("DEPLOYMENT_ENVIRONMENT_NAME"); + + if (!string.IsNullOrEmpty(serviceName) && !string.IsNullOrEmpty(deploymentEnv)) + { + var pipelineResource = ResourceBuilder.CreateDefault() + .AddAttributes(new Dictionary + { + ["service.name"] = serviceName, + ["deployment.environment.name"] = deploymentEnv + }) + .Build(); + + pipelineMeterProvider = Sdk.CreateMeterProviderBuilder() + .SetResourceBuilder(ResourceBuilder.CreateDefault().AddAttributes(new Dictionary + { + ["service.name"] = serviceName, + ["deployment.environment.name"] = deploymentEnv + })) + .AddOtlpExporter(options => + { + options.Endpoint = new Uri("http://localhost:4317"); + options.Protocol = OtlpExportProtocol.Grpc; + }) + .AddMeter("myMeter") + .Build(); + + pipelineMeter = new Meter("myMeter"); + customPipelineCounter = pipelineMeter.CreateCounter("custom_pipeline_counter", "1", "pipeline export counter"); + customPipelineHistogram = pipelineMeter.CreateHistogram("custom_pipeline_histogram", "ms", "pipeline export histogram"); + customPipelineGauge = pipelineMeter.CreateUpDownCounter("custom_pipeline_gauge", "1", "pipeline export gauge"); + } + } private static readonly Thread thread = new Thread(() => { @@ -63,7 +104,6 @@ public AppController() { if (!threadStarted) { - Console.WriteLine("Starting thread"); threadStarted = true; thread.Start(); } @@ -87,27 +127,27 @@ public string AWSSDKCall([FromQuery] string testingId) // Agent-based metrics var histogramValue = random.NextDouble() * 100; var gaugeValue = random.Next(-10, 11); + agentBasedCounter.Add(1, new KeyValuePair("Operation", "counter")); agentBasedHistogram.Record(histogramValue, new KeyValuePair("Operation", "histogram")); agentBasedGauge.Add(gaugeValue, new KeyValuePair("Operation", "gauge")); - // Custom pipeline metrics with required Telemetry.Source attribute - var pipelineHistogramValue = random.NextDouble() * 50; - var pipelineGaugeValue = random.Next(-5, 6); - var pipelineAttributes = new KeyValuePair[] { - new("Operation", "pipeline_counter"), - new("Telemetry.Source", "UserMetric"), - }; - var pipelineHistogramAttributes = new KeyValuePair[] { - new("Operation", "pipeline_histogram"), - new("Telemetry.Source", "UserMetric"), - }; - var pipelineGaugeAttributes = new KeyValuePair[] { - new("Operation", "pipeline_gauge"), - new("Telemetry.Source", "UserMetric"), - }; + // Custom pipeline metrics - only record if pipeline exists + if (customPipelineCounter != null) + { + customPipelineCounter.Add(1, new KeyValuePair("Operation", "pipeline_counter")); + customPipelineHistogram?.Record(random.Next(100, 1001), new KeyValuePair("Operation", "pipeline_histogram")); + customPipelineGauge?.Add(random.Next(-10, 11), new KeyValuePair("Operation", "pipeline_gauge")); + }nsole.WriteLine("[PIPELINE] Metrics recorded - will be exported via OTLP to localhost:4317"); + } + var bucketName = "e2e-test-bucket-name"; + if (!string.IsNullOrEmpty(testingId)) + { + bucketName += "-" + testingId; + } + var request = new GetBucketLocationRequest() { BucketName = testingId diff --git a/terraform/dotnet/ec2/default/main.tf b/terraform/dotnet/ec2/default/main.tf index 17bb0e478..875084802 100644 --- a/terraform/dotnet/ec2/default/main.tf +++ b/terraform/dotnet/ec2/default/main.tf @@ -132,8 +132,8 @@ resource "null_resource" "main_service_setup" { ${var.get_adot_distro_command} # Get and run the sample application with configuration - aws s3 cp ${var.sample_app_zip} ./dotnet-sample-app.zip - unzip -o dotnet-sample-app.zip + aws s3 cp ${var.sample_app_zip} ./dotnet-sample-app-delete-me.zip + unzip -o dotnet-sample-app-delete-me.zip # Get Absolute Path current_dir=$(pwd) @@ -153,8 +153,11 @@ resource "null_resource" "main_service_setup" { export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4316 export OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT=http://127.0.0.1:4316/v1/metrics - export OTEL_METRICS_EXPORTER=none - export OTEL_RESOURCE_ATTRIBUTES=service.name=dotnet-sample-application-${var.test_id} + export OTEL_METRICS_EXPORTER=otlp + export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://localhost:4317 + export SERVICE_NAME='dotnet-sample-application-${var.test_id}' + export DEPLOYMENT_ENVIRONMENT_NAME='ec2:default' + export OTEL_RESOURCE_ATTRIBUTES="service.name=$${SERVICE_NAME},deployment.environment.name=$${DEPLOYMENT_ENVIRONMENT_NAME}" export OTEL_AWS_APPLICATION_SIGNALS_ENABLED=true export OTEL_AWS_APPLICATION_SIGNALS_RUNTIME_ENABLED=false export OTEL_TRACES_SAMPLER=always_on @@ -240,8 +243,8 @@ resource "null_resource" "remote_service_setup" { ${var.get_adot_distro_command} # Get and run the sample application with configuration - aws s3 cp ${var.sample_app_zip} ./dotnet-sample-app.zip - unzip -o dotnet-sample-app.zip + aws s3 cp ${var.sample_app_zip} ./dotnet-sample-app-delete-me.zip + unzip -o dotnet-sample-app-delete-me.zip # Get Absolute Path current_dir=$(pwd) 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 99c896a8d..659d7e220 100644 --- a/validator/src/main/java/com/amazon/aoc/fileconfigs/PredefinedExpectedTemplate.java +++ b/validator/src/main/java/com/amazon/aoc/fileconfigs/PredefinedExpectedTemplate.java @@ -315,7 +315,7 @@ public enum PredefinedExpectedTemplate implements FileConfig { DOTNET_EC2_WINDOWS_DEFAULT_AWS_SDK_CALL_METRIC("/expected-data-template/dotnet/ec2/windows/aws-sdk-call-metric.mustache"), DOTNET_EC2_WINDOWS_DEFAULT_AWS_SDK_CALL_TRACE("/expected-data-template/dotnet/ec2/windows/aws-sdk-call-trace.mustache"), - /** Python EC2 Default Custom Metrics Test Case Validations */ + /** DOTNET EC2 Default Custom Metrics Test Case Validations */ DOTNET_EC2_DEFAULT_AWS_OTEL_CUSTOM_METRIC("/expected-data-template/dotnet/ec2/default/aws-otel-custom-metrics.mustache"), DOTNET_EC2_WINDOWS_DEFAULT_REMOTE_SERVICE_LOG("/expected-data-template/dotnet/ec2/windows/remote-service-log.mustache"), diff --git a/validator/src/main/resources/validations/node/ec2/default/custom-metric-validation.yml b/validator/src/main/resources/validations/dotnet/ec2/default/custom-metric-validation.yml similarity index 100% rename from validator/src/main/resources/validations/node/ec2/default/custom-metric-validation.yml rename to validator/src/main/resources/validations/dotnet/ec2/default/custom-metric-validation.yml From a8a5d5f17fa4eda6a72427e159b586084dc81dcc Mon Sep 17 00:00:00 2001 From: Vherremi Date: Mon, 10 Nov 2025 09:52:01 -0800 Subject: [PATCH 03/10] Updating branch name in test file --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9f5f25f78..acd84982d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ name: Test on: push: branches: - - dotnet_custom_metrics + - DOTNET_Custom_metrics permissions: id-token: write From 4c7abceb39b7a08e2f939359c7707025cdd6bd81 Mon Sep 17 00:00:00 2001 From: Vherremi Date: Mon, 10 Nov 2025 10:04:31 -0800 Subject: [PATCH 04/10] dotnet-ec2-default test 2 --- .../{node => dotnet}/ec2/default/aws-otel-custom-metrics.mustache | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename validator/src/main/resources/expected-data-template/{node => dotnet}/ec2/default/aws-otel-custom-metrics.mustache (100%) diff --git a/validator/src/main/resources/expected-data-template/node/ec2/default/aws-otel-custom-metrics.mustache b/validator/src/main/resources/expected-data-template/dotnet/ec2/default/aws-otel-custom-metrics.mustache similarity index 100% rename from validator/src/main/resources/expected-data-template/node/ec2/default/aws-otel-custom-metrics.mustache rename to validator/src/main/resources/expected-data-template/dotnet/ec2/default/aws-otel-custom-metrics.mustache From 557bc18781356fb6a71bae8f3b4897d0145ed82d Mon Sep 17 00:00:00 2001 From: Vherremi Date: Mon, 10 Nov 2025 10:28:27 -0800 Subject: [PATCH 05/10] dotnet-ec2-default test 3, adding otlp config --- .../asp_frontend_service/Controllers/AppController.cs | 1 - terraform/node/ec2/default/amazon-cloudwatch-agent.json | 6 +++++- .../dotnet/ec2/default/aws-otel-custom-metrics.mustache | 1 - 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs b/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs index 2ed3b5c91..83f06edbe 100644 --- a/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs +++ b/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs @@ -138,7 +138,6 @@ public string AWSSDKCall([FromQuery] string testingId) customPipelineCounter.Add(1, new KeyValuePair("Operation", "pipeline_counter")); customPipelineHistogram?.Record(random.Next(100, 1001), new KeyValuePair("Operation", "pipeline_histogram")); customPipelineGauge?.Add(random.Next(-10, 11), new KeyValuePair("Operation", "pipeline_gauge")); - }nsole.WriteLine("[PIPELINE] Metrics recorded - will be exported via OTLP to localhost:4317"); } diff --git a/terraform/node/ec2/default/amazon-cloudwatch-agent.json b/terraform/node/ec2/default/amazon-cloudwatch-agent.json index a98a40d36..f65bfe32e 100644 --- a/terraform/node/ec2/default/amazon-cloudwatch-agent.json +++ b/terraform/node/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/validator/src/main/resources/expected-data-template/dotnet/ec2/default/aws-otel-custom-metrics.mustache b/validator/src/main/resources/expected-data-template/dotnet/ec2/default/aws-otel-custom-metrics.mustache index 9fc5d6480..f0335f8e1 100644 --- a/validator/src/main/resources/expected-data-template/dotnet/ec2/default/aws-otel-custom-metrics.mustache +++ b/validator/src/main/resources/expected-data-template/dotnet/ec2/default/aws-otel-custom-metrics.mustache @@ -157,7 +157,6 @@ - name: cloud.platform value: aws_ec2 - value: aws_ec2 # Export pipeline metrics - From ee77b94bee55e6c9bb49a1ddccaa3490b3779805 Mon Sep 17 00:00:00 2001 From: Vherremi Date: Mon, 10 Nov 2025 10:47:02 -0800 Subject: [PATCH 06/10] dotnet-ec2-default test 4, testing main.tf change --- terraform/dotnet/ec2/default/main.tf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/terraform/dotnet/ec2/default/main.tf b/terraform/dotnet/ec2/default/main.tf index 875084802..cd31a747c 100644 --- a/terraform/dotnet/ec2/default/main.tf +++ b/terraform/dotnet/ec2/default/main.tf @@ -154,7 +154,8 @@ resource "null_resource" "main_service_setup" { export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4316 export OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT=http://127.0.0.1:4316/v1/metrics export OTEL_METRICS_EXPORTER=otlp - export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://localhost:4317 + export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=localhost:4317 + export OTEL_EXPORTER_OTLP_METRICS_INSECURE=true export SERVICE_NAME='dotnet-sample-application-${var.test_id}' export DEPLOYMENT_ENVIRONMENT_NAME='ec2:default' export OTEL_RESOURCE_ATTRIBUTES="service.name=$${SERVICE_NAME},deployment.environment.name=$${DEPLOYMENT_ENVIRONMENT_NAME}" From 07336593334bbd627385fcfc0c8e7b9b7d5caa79 Mon Sep 17 00:00:00 2001 From: Vherremi Date: Mon, 10 Nov 2025 10:57:01 -0800 Subject: [PATCH 07/10] dotnet-ec2-default test 5, testing main.tf change --- terraform/dotnet/ec2/default/main.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/terraform/dotnet/ec2/default/main.tf b/terraform/dotnet/ec2/default/main.tf index cd31a747c..00ff94809 100644 --- a/terraform/dotnet/ec2/default/main.tf +++ b/terraform/dotnet/ec2/default/main.tf @@ -155,6 +155,7 @@ resource "null_resource" "main_service_setup" { export OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT=http://127.0.0.1:4316/v1/metrics export OTEL_METRICS_EXPORTER=otlp export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=localhost:4317 + export OTEL_EXPORTER_OTLP_METRICS_PROTOCOL=grpc export OTEL_EXPORTER_OTLP_METRICS_INSECURE=true export SERVICE_NAME='dotnet-sample-application-${var.test_id}' export DEPLOYMENT_ENVIRONMENT_NAME='ec2:default' From b48ec4535ced5510686db023b58d0353b594791f Mon Sep 17 00:00:00 2001 From: Vherremi Date: Mon, 10 Nov 2025 11:33:22 -0800 Subject: [PATCH 08/10] dotnet-ec2-default test 6 --- .../asp_frontend_service/Controllers/AppController.cs | 8 ++++---- sample-apps/dotnet/asp_frontend_service/Startup.cs | 2 +- terraform/dotnet/ec2/default/main.tf | 5 ++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs b/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs index 83f06edbe..ccb091977 100644 --- a/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs +++ b/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs @@ -64,13 +64,13 @@ static AppController() })) .AddOtlpExporter(options => { - options.Endpoint = new Uri("http://localhost:4317"); - options.Protocol = OtlpExportProtocol.Grpc; + options.Endpoint = new Uri("http://localhost:4318/v1/metrics"); + options.Protocol = OtlpExportProtocol.HttpProtobuf; }) - .AddMeter("myMeter") + .AddMeter("customPipelineMeter") .Build(); - pipelineMeter = new Meter("myMeter"); + pipelineMeter = new Meter("customPipelineMeter"); customPipelineCounter = pipelineMeter.CreateCounter("custom_pipeline_counter", "1", "pipeline export counter"); customPipelineHistogram = pipelineMeter.CreateHistogram("custom_pipeline_histogram", "ms", "pipeline export histogram"); customPipelineGauge = pipelineMeter.CreateUpDownCounter("custom_pipeline_gauge", "1", "pipeline export gauge"); diff --git a/sample-apps/dotnet/asp_frontend_service/Startup.cs b/sample-apps/dotnet/asp_frontend_service/Startup.cs index 4e9d105fd..5647af21c 100644 --- a/sample-apps/dotnet/asp_frontend_service/Startup.cs +++ b/sample-apps/dotnet/asp_frontend_service/Startup.cs @@ -35,7 +35,7 @@ public void ConfigureServices(IServiceCollection services) .AddService(Environment.GetEnvironmentVariable("SERVICE_NAME") ?? "dotnet-sample-application") .AddAttributes(new Dictionary { { "Telemetry.Source", "UserMetric" } })) .WithMetrics(metrics => metrics - .AddMeter("customPipelineMeter") + .AddMeter("myMeter") .AddOtlpExporter(options => { options.Endpoint = new Uri(Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT") ?? "http://localhost:4318/v1/metrics"); Console.WriteLine($"Custom pipeline OTLP endpoint: {options.Endpoint}"); diff --git a/terraform/dotnet/ec2/default/main.tf b/terraform/dotnet/ec2/default/main.tf index 00ff94809..b5f5ad6cc 100644 --- a/terraform/dotnet/ec2/default/main.tf +++ b/terraform/dotnet/ec2/default/main.tf @@ -154,9 +154,8 @@ resource "null_resource" "main_service_setup" { export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4316 export OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT=http://127.0.0.1:4316/v1/metrics export OTEL_METRICS_EXPORTER=otlp - export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=localhost:4317 - export OTEL_EXPORTER_OTLP_METRICS_PROTOCOL=grpc - export OTEL_EXPORTER_OTLP_METRICS_INSECURE=true + export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://localhost:4318/v1/metrics + export OTEL_EXPORTER_OTLP_METRICS_PROTOCOL=http/protobuf export SERVICE_NAME='dotnet-sample-application-${var.test_id}' export DEPLOYMENT_ENVIRONMENT_NAME='ec2:default' export OTEL_RESOURCE_ATTRIBUTES="service.name=$${SERVICE_NAME},deployment.environment.name=$${DEPLOYMENT_ENVIRONMENT_NAME}" From a31fda0397d3a9f927cb921ec9f6f9f62dd43874 Mon Sep 17 00:00:00 2001 From: Vherremi Date: Mon, 10 Nov 2025 12:13:37 -0800 Subject: [PATCH 09/10] dotnet-ec2-default test 7 --- terraform/dotnet/ec2/default/amazon-cloudwatch-agent.json | 6 +++++- terraform/node/ec2/default/amazon-cloudwatch-agent.json | 6 +----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/terraform/dotnet/ec2/default/amazon-cloudwatch-agent.json b/terraform/dotnet/ec2/default/amazon-cloudwatch-agent.json index a98a40d36..f65bfe32e 100644 --- a/terraform/dotnet/ec2/default/amazon-cloudwatch-agent.json +++ b/terraform/dotnet/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/node/ec2/default/amazon-cloudwatch-agent.json b/terraform/node/ec2/default/amazon-cloudwatch-agent.json index f65bfe32e..a98a40d36 100644 --- a/terraform/node/ec2/default/amazon-cloudwatch-agent.json +++ b/terraform/node/ec2/default/amazon-cloudwatch-agent.json @@ -10,11 +10,7 @@ }, "logs": { "metrics_collected": { - "application_signals": {}, - "otlp": { - "grpc_endpoint": "0.0.0.0:4317", - "http_endpoint": "0.0.0.0:4318" - } + "application_signals": {} } } } \ No newline at end of file From 04e1c9014ae60dfd911492c21989290b220e3e87 Mon Sep 17 00:00:00 2001 From: Vherremi Date: Mon, 10 Nov 2025 15:03:48 -0800 Subject: [PATCH 10/10] dotnet-ec2-default test 8 --- .../asp_frontend_service/Controllers/AppController.cs | 4 ++-- sample-apps/dotnet/asp_frontend_service/Startup.cs | 7 +++++-- .../asp_frontend_service/asp_frontend_service.csproj | 1 + terraform/dotnet/ec2/default/main.tf | 1 + 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs b/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs index ccb091977..a77cc768a 100644 --- a/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs +++ b/sample-apps/dotnet/asp_frontend_service/Controllers/AppController.cs @@ -67,10 +67,10 @@ static AppController() options.Endpoint = new Uri("http://localhost:4318/v1/metrics"); options.Protocol = OtlpExportProtocol.HttpProtobuf; }) - .AddMeter("customPipelineMeter") + .AddMeter("myMeter") .Build(); - pipelineMeter = new Meter("customPipelineMeter"); + pipelineMeter = new Meter("myMeter"); customPipelineCounter = pipelineMeter.CreateCounter("custom_pipeline_counter", "1", "pipeline export counter"); customPipelineHistogram = pipelineMeter.CreateHistogram("custom_pipeline_histogram", "ms", "pipeline export histogram"); customPipelineGauge = pipelineMeter.CreateUpDownCounter("custom_pipeline_gauge", "1", "pipeline export gauge"); diff --git a/sample-apps/dotnet/asp_frontend_service/Startup.cs b/sample-apps/dotnet/asp_frontend_service/Startup.cs index 5647af21c..8bea30b91 100644 --- a/sample-apps/dotnet/asp_frontend_service/Startup.cs +++ b/sample-apps/dotnet/asp_frontend_service/Startup.cs @@ -35,12 +35,15 @@ public void ConfigureServices(IServiceCollection services) .AddService(Environment.GetEnvironmentVariable("SERVICE_NAME") ?? "dotnet-sample-application") .AddAttributes(new Dictionary { { "Telemetry.Source", "UserMetric" } })) .WithMetrics(metrics => metrics + .AddAspNetCoreInstrumentation() .AddMeter("myMeter") .AddOtlpExporter(options => { options.Endpoint = new Uri(Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT") ?? "http://localhost:4318/v1/metrics"); - Console.WriteLine($"Custom pipeline OTLP endpoint: {options.Endpoint}"); }) - .AddConsoleExporter()); + .AddConsoleExporter((exporterOptions, metricReaderOptions) => + { + metricReaderOptions.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds = 1000; + })); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/sample-apps/dotnet/asp_frontend_service/asp_frontend_service.csproj b/sample-apps/dotnet/asp_frontend_service/asp_frontend_service.csproj index 25c905d07..7f2bb0ebc 100644 --- a/sample-apps/dotnet/asp_frontend_service/asp_frontend_service.csproj +++ b/sample-apps/dotnet/asp_frontend_service/asp_frontend_service.csproj @@ -12,6 +12,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/terraform/dotnet/ec2/default/main.tf b/terraform/dotnet/ec2/default/main.tf index b5f5ad6cc..bc27ee9e5 100644 --- a/terraform/dotnet/ec2/default/main.tf +++ b/terraform/dotnet/ec2/default/main.tf @@ -154,6 +154,7 @@ resource "null_resource" "main_service_setup" { export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4316 export OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT=http://127.0.0.1:4316/v1/metrics export OTEL_METRICS_EXPORTER=otlp + export OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES=myMeter export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://localhost:4318/v1/metrics export OTEL_EXPORTER_OTLP_METRICS_PROTOCOL=http/protobuf export SERVICE_NAME='dotnet-sample-application-${var.test_id}'