diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 4e23aa3c4..171fe00e6 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -55,6 +55,10 @@ "matchPackagePrefixes": ["org.apache.maven:"], "matchCurrentVersion": "3.5.0", "enabled": false + }, + { + "matchPackagePrefixes": ["com.diffplug.spotless"], + "groupName": "spotless packages" } ] } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1abb23f2a..d49521876 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK for running Gradle - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: temurin java-version: 17 @@ -48,14 +48,14 @@ jobs: - id: setup-test-java name: Set up JDK ${{ matrix.test-java-version }} for running tests - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: # using zulu because new releases get published quickly distribution: zulu java-version: ${{ matrix.test-java-version }} - name: Set up JDK for running Gradle - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: temurin java-version: 17 @@ -78,7 +78,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK for running Gradle - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: temurin java-version: 17 @@ -130,7 +130,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK for running Gradle - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: temurin java-version: 17 diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index 08d986743..cef572536 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Java 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: temurin java-version: 17 diff --git a/.github/workflows/issue-management-stale-action.yml b/.github/workflows/issue-management-stale-action.yml index 18f80c196..af57d2e33 100644 --- a/.github/workflows/issue-management-stale-action.yml +++ b/.github/workflows/issue-management-stale-action.yml @@ -9,7 +9,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v8 + - uses: actions/stale@v9 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 815174172..8cbc5644a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK for running Gradle - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: temurin java-version: 17 @@ -32,7 +32,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK for running Gradle - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: temurin java-version: 17 @@ -113,7 +113,7 @@ jobs: fetch-depth: 0 - name: Set up JDK for running Gradle - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: temurin java-version: 17 @@ -176,7 +176,6 @@ jobs: gh release create --target $GITHUB_REF_NAME \ --title "Version $VERSION" \ --notes-file /tmp/release-notes.txt \ - --discussion-category announcements \ v$VERSION \ opentelemetry-jmx-metrics.jar diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f6438e06..c0b59878e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,33 @@ ## Unreleased +## Version 1.32.0 (2023-11-27) + +### Disk buffering + +- Using Android 21 as minimum supported for disk-buffering + ([#1096](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1096)) + +## Version 1.31.0 (2023-10-18) + +### Consistent sampling + +- Explicitly pass invalid p-value to root sampler + ([#1053](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1053)) +- Consistent sampler prototypes using 56 bits of randomness + ([#1063](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1063)) + +### Runtime attach + +- Rename runtime attach method from `attachJavaagentToCurrentJVM` + to `attachJavaagentToCurrentJvm` + ([#1077](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1077)) + +### Samplers + +- Support `thread.name` attributes in RuleBasedRoutingSampler + ([#1030](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1030)) + ## Version 1.30.0 (2023-09-18) ### Disk buffering diff --git a/README.md b/README.md index d38018487..7afe816c2 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Triagers ([@open-telemetry/java-contrib-triagers](https://github.com/orgs/open-t Approvers ([@open-telemetry/java-contrib-approvers](https://github.com/orgs/open-telemetry/teams/java-contrib-approvers)): +- [Jason Plumb](https://github.com/breedx-splk), Splunk - [John Watson](https://github.com/jkwatson), Verta.ai - [Lauri Tulmin](https://github.com/laurit), Splunk diff --git a/aws-xray/build.gradle.kts b/aws-xray/build.gradle.kts index bfbb14504..ccec9d52f 100644 --- a/aws-xray/build.gradle.kts +++ b/aws-xray/build.gradle.kts @@ -13,7 +13,7 @@ dependencies { compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") implementation("com.squareup.okhttp3:okhttp") - implementation("io.opentelemetry.semconv:opentelemetry-semconv") + implementation("io.opentelemetry:opentelemetry-semconv") annotationProcessor("com.google.auto.service:auto-service") testImplementation("com.google.auto.service:auto-service") @@ -45,3 +45,10 @@ testing { } } } + +configurations.all { + resolutionStrategy { + // TODO this module still needs to be updated to the latest semconv + force("io.opentelemetry:opentelemetry-semconv:1.28.0-alpha") + } +} diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGenerator.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGenerator.java index 69f2e63a3..bf55d061e 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGenerator.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGenerator.java @@ -15,24 +15,24 @@ import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_SPAN_KIND; import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_STREAM_NAME; import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_TABLE_NAME; -import static io.opentelemetry.semconv.ResourceAttributes.SERVICE_NAME; -import static io.opentelemetry.semconv.SemanticAttributes.DB_OPERATION; -import static io.opentelemetry.semconv.SemanticAttributes.DB_SYSTEM; -import static io.opentelemetry.semconv.SemanticAttributes.FAAS_INVOKED_NAME; -import static io.opentelemetry.semconv.SemanticAttributes.FAAS_TRIGGER; -import static io.opentelemetry.semconv.SemanticAttributes.GRAPHQL_OPERATION_TYPE; -import static io.opentelemetry.semconv.SemanticAttributes.HTTP_REQUEST_METHOD; -import static io.opentelemetry.semconv.SemanticAttributes.MESSAGING_OPERATION; -import static io.opentelemetry.semconv.SemanticAttributes.MESSAGING_SYSTEM; -import static io.opentelemetry.semconv.SemanticAttributes.PEER_SERVICE; -import static io.opentelemetry.semconv.SemanticAttributes.RPC_METHOD; -import static io.opentelemetry.semconv.SemanticAttributes.RPC_SERVICE; -import static io.opentelemetry.semconv.SemanticAttributes.SERVER_ADDRESS; -import static io.opentelemetry.semconv.SemanticAttributes.SERVER_PORT; -import static io.opentelemetry.semconv.SemanticAttributes.SERVER_SOCKET_ADDRESS; -import static io.opentelemetry.semconv.SemanticAttributes.SERVER_SOCKET_PORT; -import static io.opentelemetry.semconv.SemanticAttributes.URL_FULL; -import static io.opentelemetry.semconv.SemanticAttributes.URL_PATH; +import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DB_OPERATION; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DB_SYSTEM; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.FAAS_INVOKED_NAME; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.FAAS_TRIGGER; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.GRAPHQL_OPERATION_TYPE; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_METHOD; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_TARGET; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_URL; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_SYSTEM; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_PEER_NAME; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_PEER_PORT; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_SOCK_PEER_ADDR; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_SOCK_PEER_PORT; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.PEER_SERVICE; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.RPC_METHOD; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.RPC_SERVICE; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; @@ -40,8 +40,8 @@ import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.semconv.ResourceAttributes; -import io.opentelemetry.semconv.SemanticAttributes; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.net.MalformedURLException; import java.net.URL; import java.util.Optional; @@ -156,8 +156,8 @@ private static boolean isValidOperation(SpanData span) { if (operation == null || operation.equals(UNKNOWN_OPERATION)) { return false; } - if (isKeyPresent(span, HTTP_REQUEST_METHOD)) { - String httpMethod = span.getAttributes().get(HTTP_REQUEST_METHOD); + if (isKeyPresent(span, HTTP_METHOD)) { + String httpMethod = span.getAttributes().get(HTTP_METHOD); return !operation.equals(httpMethod); } return true; @@ -260,15 +260,15 @@ private static void setRemoteServiceAndOperation(SpanData span, AttributesBuilde */ private static String generateIngressOperation(SpanData span) { String operation = UNKNOWN_OPERATION; - if (isKeyPresent(span, URL_PATH)) { - String httpTarget = span.getAttributes().get(URL_PATH); + if (isKeyPresent(span, HTTP_TARGET)) { + String httpTarget = span.getAttributes().get(HTTP_TARGET); // get the first part from API path string as operation value // the more levels/parts we get from API path the higher chance for getting high cardinality // data if (httpTarget != null) { operation = extractApiPathValue(httpTarget); - if (isKeyPresent(span, HTTP_REQUEST_METHOD)) { - String httpMethod = span.getAttributes().get(HTTP_REQUEST_METHOD); + if (isKeyPresent(span, HTTP_METHOD)) { + String httpMethod = span.getAttributes().get(HTTP_METHOD); if (httpMethod != null) { operation = httpMethod + " " + operation; } @@ -284,8 +284,8 @@ private static String generateIngressOperation(SpanData span) { */ private static String generateRemoteOperation(SpanData span) { String remoteOperation = UNKNOWN_REMOTE_OPERATION; - if (isKeyPresent(span, URL_FULL)) { - String httpUrl = span.getAttributes().get(URL_FULL); + if (isKeyPresent(span, HTTP_URL)) { + String httpUrl = span.getAttributes().get(HTTP_URL); try { URL url; if (httpUrl != null) { @@ -296,8 +296,8 @@ private static String generateRemoteOperation(SpanData span) { logger.log(Level.FINEST, "invalid http.url attribute: ", httpUrl); } } - if (isKeyPresent(span, HTTP_REQUEST_METHOD)) { - String httpMethod = span.getAttributes().get(HTTP_REQUEST_METHOD); + if (isKeyPresent(span, HTTP_METHOD)) { + String httpMethod = span.getAttributes().get(HTTP_METHOD); remoteOperation = httpMethod + " " + remoteOperation; } if (remoteOperation.equals(UNKNOWN_REMOTE_OPERATION)) { @@ -325,16 +325,16 @@ private static String extractApiPathValue(String httpTarget) { private static String generateRemoteService(SpanData span) { String remoteService = UNKNOWN_REMOTE_SERVICE; - if (isKeyPresent(span, SERVER_ADDRESS)) { - remoteService = getRemoteService(span, SERVER_ADDRESS); - if (isKeyPresent(span, SERVER_PORT)) { - Long port = span.getAttributes().get(SERVER_PORT); + if (isKeyPresent(span, NET_PEER_NAME)) { + remoteService = getRemoteService(span, NET_PEER_NAME); + if (isKeyPresent(span, NET_PEER_PORT)) { + Long port = span.getAttributes().get(NET_PEER_PORT); remoteService += ":" + port; } - } else if (isKeyPresent(span, SERVER_SOCKET_ADDRESS)) { - remoteService = getRemoteService(span, SERVER_SOCKET_ADDRESS); - if (isKeyPresent(span, SERVER_SOCKET_PORT)) { - Long port = span.getAttributes().get(SERVER_SOCKET_PORT); + } else if (isKeyPresent(span, NET_SOCK_PEER_ADDR)) { + remoteService = getRemoteService(span, NET_SOCK_PEER_ADDR); + if (isKeyPresent(span, NET_SOCK_PEER_PORT)) { + Long port = span.getAttributes().get(NET_SOCK_PEER_PORT); remoteService += ":" + port; } } else { diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessor.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessor.java index 7dec5d0ac..fb2c2a103 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessor.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessor.java @@ -5,7 +5,7 @@ package io.opentelemetry.contrib.awsxray; -import static io.opentelemetry.semconv.SemanticAttributes.HTTP_RESPONSE_STATUS_CODE; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_STATUS_CODE; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.DoubleHistogram; @@ -107,7 +107,7 @@ public boolean isEndRequired() { } private void recordErrorOrFault(SpanData spanData, Attributes attributes) { - Long httpStatusCode = spanData.getAttributes().get(HTTP_RESPONSE_STATUS_CODE); + Long httpStatusCode = spanData.getAttributes().get(HTTP_STATUS_CODE); if (httpStatusCode == null) { httpStatusCode = getAwsStatusCode(spanData); diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java index 5f3d9a8bc..1ef8abf50 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java @@ -17,8 +17,8 @@ import io.opentelemetry.sdk.trace.samplers.Sampler; import io.opentelemetry.sdk.trace.samplers.SamplingDecision; import io.opentelemetry.sdk.trace.samplers.SamplingResult; -import io.opentelemetry.semconv.ResourceAttributes; -import io.opentelemetry.semconv.SemanticAttributes; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.time.Duration; import java.util.Collections; import java.util.Date; @@ -162,25 +162,13 @@ boolean matches(Attributes attributes, Resource resource) { String host = null; for (Map.Entry, Object> entry : attributes.asMap().entrySet()) { - if (entry.getKey().equals(SemanticAttributes.URL_PATH)) { + if (entry.getKey().equals(SemanticAttributes.HTTP_TARGET)) { httpTarget = (String) entry.getValue(); - } else if (entry.getKey().equals(SemanticAttributes.HTTP_TARGET)) { - // TODO remove support for deprecated http.target attribute - httpTarget = (String) entry.getValue(); - } else if (entry.getKey().equals(SemanticAttributes.URL_FULL)) { - httpUrl = (String) entry.getValue(); } else if (entry.getKey().equals(SemanticAttributes.HTTP_URL)) { - // TODO remove support for deprecated http.url attribute httpUrl = (String) entry.getValue(); - } else if (entry.getKey().equals(SemanticAttributes.HTTP_REQUEST_METHOD)) { - httpMethod = (String) entry.getValue(); } else if (entry.getKey().equals(SemanticAttributes.HTTP_METHOD)) { - // TODO remove support for deprecated http.method attribute httpMethod = (String) entry.getValue(); - } else if (entry.getKey().equals(SemanticAttributes.SERVER_ADDRESS)) { - host = (String) entry.getValue(); } else if (entry.getKey().equals(SemanticAttributes.NET_HOST_NAME)) { - // TODO remove support for deprecated net.host.name attribute host = (String) entry.getValue(); } else if (entry.getKey().equals(SemanticAttributes.HTTP_HOST)) { // TODO (trask) remove support for deprecated http.host attribute diff --git a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGeneratorTest.java b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGeneratorTest.java index 256be20ac..b1df48daa 100644 --- a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGeneratorTest.java +++ b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGeneratorTest.java @@ -15,25 +15,25 @@ import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_SPAN_KIND; import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_STREAM_NAME; import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_TABLE_NAME; -import static io.opentelemetry.semconv.ResourceAttributes.SERVICE_NAME; -import static io.opentelemetry.semconv.SemanticAttributes.DB_OPERATION; -import static io.opentelemetry.semconv.SemanticAttributes.DB_SYSTEM; -import static io.opentelemetry.semconv.SemanticAttributes.FAAS_INVOKED_NAME; -import static io.opentelemetry.semconv.SemanticAttributes.FAAS_INVOKED_PROVIDER; -import static io.opentelemetry.semconv.SemanticAttributes.FAAS_TRIGGER; -import static io.opentelemetry.semconv.SemanticAttributes.GRAPHQL_OPERATION_TYPE; -import static io.opentelemetry.semconv.SemanticAttributes.HTTP_REQUEST_METHOD; -import static io.opentelemetry.semconv.SemanticAttributes.MESSAGING_OPERATION; -import static io.opentelemetry.semconv.SemanticAttributes.MESSAGING_SYSTEM; -import static io.opentelemetry.semconv.SemanticAttributes.PEER_SERVICE; -import static io.opentelemetry.semconv.SemanticAttributes.RPC_METHOD; -import static io.opentelemetry.semconv.SemanticAttributes.RPC_SERVICE; -import static io.opentelemetry.semconv.SemanticAttributes.SERVER_ADDRESS; -import static io.opentelemetry.semconv.SemanticAttributes.SERVER_PORT; -import static io.opentelemetry.semconv.SemanticAttributes.SERVER_SOCKET_ADDRESS; -import static io.opentelemetry.semconv.SemanticAttributes.SERVER_SOCKET_PORT; -import static io.opentelemetry.semconv.SemanticAttributes.URL_FULL; -import static io.opentelemetry.semconv.SemanticAttributes.URL_PATH; +import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DB_OPERATION; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DB_SYSTEM; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.FAAS_INVOKED_NAME; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.FAAS_INVOKED_PROVIDER; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.FAAS_TRIGGER; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.GRAPHQL_OPERATION_TYPE; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_METHOD; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_TARGET; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_URL; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_OPERATION; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.MESSAGING_SYSTEM; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_PEER_NAME; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_PEER_PORT; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_SOCK_PEER_ADDR; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NET_SOCK_PEER_PORT; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.PEER_SERVICE; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.RPC_METHOD; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.RPC_SERVICE; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -170,7 +170,7 @@ public void testServerSpanWithNullSpanName() { public void testServerSpanWithSpanNameAsHttpMethod() { updateResourceWithServiceName(); when(spanDataMock.getName()).thenReturn("GET"); - mockAttribute(HTTP_REQUEST_METHOD, "GET"); + mockAttribute(HTTP_METHOD, "GET"); Attributes expectedAttributes = Attributes.of( @@ -178,15 +178,15 @@ public void testServerSpanWithSpanNameAsHttpMethod() { AWS_LOCAL_SERVICE, SERVICE_NAME_VALUE, AWS_LOCAL_OPERATION, UNKNOWN_OPERATION); validateAttributesProducedForSpanOfKind(expectedAttributes, SpanKind.SERVER); - mockAttribute(HTTP_REQUEST_METHOD, null); + mockAttribute(HTTP_METHOD, null); } @Test public void testServerSpanWithSpanNameWithHttpTarget() { updateResourceWithServiceName(); when(spanDataMock.getName()).thenReturn("POST"); - mockAttribute(HTTP_REQUEST_METHOD, "POST"); - mockAttribute(URL_PATH, "/payment/123"); + mockAttribute(HTTP_METHOD, "POST"); + mockAttribute(HTTP_TARGET, "/payment/123"); Attributes expectedAttributes = Attributes.of( @@ -197,8 +197,8 @@ public void testServerSpanWithSpanNameWithHttpTarget() { AWS_LOCAL_OPERATION, "POST /payment"); validateAttributesProducedForSpanOfKind(expectedAttributes, SpanKind.SERVER); - mockAttribute(HTTP_REQUEST_METHOD, null); - mockAttribute(URL_PATH, null); + mockAttribute(HTTP_METHOD, null); + mockAttribute(HTTP_TARGET, null); } @Test @@ -283,44 +283,44 @@ public void testRemoteAttributesCombinations() { mockAttribute(GRAPHQL_OPERATION_TYPE, null); // Validate behaviour of extracting Remote Service from net.peer.name - mockAttribute(SERVER_ADDRESS, "www.example.com"); + mockAttribute(NET_PEER_NAME, "www.example.com"); validateExpectedRemoteAttributes("www.example.com", UNKNOWN_REMOTE_OPERATION); - mockAttribute(SERVER_ADDRESS, null); + mockAttribute(NET_PEER_NAME, null); // Validate behaviour of extracting Remote Service from net.peer.name and net.peer.port - mockAttribute(SERVER_ADDRESS, "192.168.0.0"); - mockAttribute(SERVER_PORT, 8081L); + mockAttribute(NET_PEER_NAME, "192.168.0.0"); + mockAttribute(NET_PEER_PORT, 8081L); validateExpectedRemoteAttributes("192.168.0.0:8081", UNKNOWN_REMOTE_OPERATION); - mockAttribute(SERVER_ADDRESS, null); - mockAttribute(SERVER_PORT, null); + mockAttribute(NET_PEER_NAME, null); + mockAttribute(NET_PEER_PORT, null); // Validate behaviour of extracting Remote Service from net.peer.socket.addr - mockAttribute(SERVER_SOCKET_ADDRESS, "www.example.com"); + mockAttribute(NET_SOCK_PEER_ADDR, "www.example.com"); validateExpectedRemoteAttributes("www.example.com", UNKNOWN_REMOTE_OPERATION); - mockAttribute(SERVER_SOCKET_ADDRESS, null); + mockAttribute(NET_SOCK_PEER_ADDR, null); // Validate behaviour of extracting Remote Service from net.peer.socket.addr and // net.sock.peer.port - mockAttribute(SERVER_SOCKET_ADDRESS, "192.168.0.0"); - mockAttribute(SERVER_SOCKET_PORT, 8081L); + mockAttribute(NET_SOCK_PEER_ADDR, "192.168.0.0"); + mockAttribute(NET_SOCK_PEER_PORT, 8081L); validateExpectedRemoteAttributes("192.168.0.0:8081", UNKNOWN_REMOTE_OPERATION); - mockAttribute(SERVER_SOCKET_ADDRESS, null); - mockAttribute(SERVER_SOCKET_PORT, null); + mockAttribute(NET_SOCK_PEER_ADDR, null); + mockAttribute(NET_SOCK_PEER_PORT, null); // Validate behavior of Remote Operation from HttpTarget - with 1st api part, then remove it - mockAttribute(URL_FULL, "http://www.example.com/payment/123"); + mockAttribute(HTTP_URL, "http://www.example.com/payment/123"); validateExpectedRemoteAttributes(UNKNOWN_REMOTE_SERVICE, "/payment"); - mockAttribute(URL_FULL, null); + mockAttribute(HTTP_URL, null); // Validate behavior of Remote Operation from HttpTarget - without 1st api part, then remove it - mockAttribute(URL_FULL, "http://www.example.com"); + mockAttribute(HTTP_URL, "http://www.example.com"); validateExpectedRemoteAttributes(UNKNOWN_REMOTE_SERVICE, "/"); - mockAttribute(URL_FULL, null); + mockAttribute(HTTP_URL, null); // Validate behavior of Remote Operation from HttpTarget - invalid url, then remove it - mockAttribute(URL_FULL, "abc"); + mockAttribute(HTTP_URL, "abc"); validateExpectedRemoteAttributes(UNKNOWN_REMOTE_SERVICE, UNKNOWN_REMOTE_OPERATION); - mockAttribute(URL_FULL, null); + mockAttribute(HTTP_URL, null); // Validate behaviour of Peer service attribute, then remove it. mockAttribute(PEER_SERVICE, "Peer service"); @@ -338,8 +338,8 @@ public void testPeerServiceDoesOverrideOtherRemoteServices() { validatePeerServiceDoesOverride(FAAS_INVOKED_PROVIDER); validatePeerServiceDoesOverride(MESSAGING_SYSTEM); validatePeerServiceDoesOverride(GRAPHQL_OPERATION_TYPE); - validatePeerServiceDoesOverride(SERVER_ADDRESS); - validatePeerServiceDoesOverride(SERVER_SOCKET_ADDRESS); + validatePeerServiceDoesOverride(NET_PEER_NAME); + validatePeerServiceDoesOverride(NET_SOCK_PEER_ADDR); // Actually testing that peer service overrides "UnknownRemoteService". validatePeerServiceDoesOverride(AttributeKey.stringKey("unknown.service.key")); } diff --git a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessorTest.java b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessorTest.java index 77188a606..c2109b267 100644 --- a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessorTest.java +++ b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessorTest.java @@ -5,7 +5,7 @@ package io.opentelemetry.contrib.awsxray; -import static io.opentelemetry.semconv.SemanticAttributes.HTTP_RESPONSE_STATUS_CODE; +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_STATUS_CODE; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -141,7 +141,7 @@ public void testOnEndMetricsGenerationWithoutSpanAttributes() { @Test public void testOnEndMetricsGenerationWithoutMetricAttributes() { - Attributes spanAttributes = Attributes.of(HTTP_RESPONSE_STATUS_CODE, 500L); + Attributes spanAttributes = Attributes.of(HTTP_STATUS_CODE, 500L); ReadableSpan readableSpanMock = buildReadableSpanMock(spanAttributes); Attributes metricAttributes = buildMetricAttributes(CONTAINS_NO_ATTRIBUTES); configureMocksForOnEnd(readableSpanMock, metricAttributes); @@ -154,7 +154,7 @@ public void testOnEndMetricsGenerationWithoutMetricAttributes() { @Test public void testOnEndMetricsGenerationWithoutEndRequired() { - Attributes spanAttributes = Attributes.of(HTTP_RESPONSE_STATUS_CODE, 500L); + Attributes spanAttributes = Attributes.of(HTTP_STATUS_CODE, 500L); ReadableSpan readableSpanMock = buildReadableSpanMock(spanAttributes); Attributes metricAttributes = buildMetricAttributes(CONTAINS_ATTRIBUTES); configureMocksForOnEnd(readableSpanMock, metricAttributes); @@ -167,7 +167,7 @@ public void testOnEndMetricsGenerationWithoutEndRequired() { @Test public void testOnEndMetricsGenerationWithLatency() { - Attributes spanAttributes = Attributes.of(HTTP_RESPONSE_STATUS_CODE, 200L); + Attributes spanAttributes = Attributes.of(HTTP_STATUS_CODE, 200L); ReadableSpan readableSpanMock = buildReadableSpanMock(spanAttributes); Attributes metricAttributes = buildMetricAttributes(CONTAINS_ATTRIBUTES); configureMocksForOnEnd(readableSpanMock, metricAttributes); @@ -245,7 +245,7 @@ private static ReadableSpan buildReadableSpanMock(Attributes spanAttributes) { private static ReadableSpan buildReadableSpanWithThrowableMock(Throwable throwable) { // config http status code as null - Attributes spanAttributes = Attributes.of(HTTP_RESPONSE_STATUS_CODE, null); + Attributes spanAttributes = Attributes.of(HTTP_STATUS_CODE, null); ReadableSpan readableSpanMock = mock(ReadableSpan.class); SpanData mockSpanData = mock(SpanData.class); InstrumentationScopeInfo awsSdkScopeInfo = @@ -280,7 +280,7 @@ private void configureMocksForOnEnd(ReadableSpan readableSpanMock, Attributes me private void validateMetricsGeneratedForHttpStatusCode( Long httpStatusCode, ExpectedStatusMetric expectedStatusMetric) { - Attributes spanAttributes = Attributes.of(HTTP_RESPONSE_STATUS_CODE, httpStatusCode); + Attributes spanAttributes = Attributes.of(HTTP_STATUS_CODE, httpStatusCode); ReadableSpan readableSpanMock = buildReadableSpanMock(spanAttributes); Attributes metricAttributes = buildMetricAttributes(CONTAINS_ATTRIBUTES); configureMocksForOnEnd(readableSpanMock, metricAttributes); diff --git a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSamplerProviderTest.java b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSamplerProviderTest.java index 046b7cd11..5a51318f2 100644 --- a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSamplerProviderTest.java +++ b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSamplerProviderTest.java @@ -9,7 +9,7 @@ import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.trace.SdkTracerProvider; -import io.opentelemetry.semconv.ResourceAttributes; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; diff --git a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplierTest.java b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplierTest.java index e783c5a10..6bb6e82a4 100644 --- a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplierTest.java +++ b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplierTest.java @@ -5,7 +5,7 @@ package io.opentelemetry.contrib.awsxray; -import static io.opentelemetry.semconv.ResourceAttributes.SERVICE_NAME; +import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; @@ -22,8 +22,8 @@ import io.opentelemetry.sdk.testing.time.TestClock; import io.opentelemetry.sdk.trace.samplers.SamplingDecision; import io.opentelemetry.sdk.trace.samplers.SamplingResult; -import io.opentelemetry.semconv.ResourceAttributes; -import io.opentelemetry.semconv.SemanticAttributes; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.io.IOException; import java.io.UncheckedIOException; import java.time.Duration; @@ -61,9 +61,9 @@ class ExactMatch { private final Attributes attributes = Attributes.builder() - .put(SemanticAttributes.HTTP_REQUEST_METHOD, "GET") - .put(SemanticAttributes.SERVER_ADDRESS, "opentelemetry.io") - .put(SemanticAttributes.URL_PATH, "/instrument-me") + .put(SemanticAttributes.HTTP_METHOD, "GET") + .put(SemanticAttributes.NET_HOST_NAME, "opentelemetry.io") + .put(SemanticAttributes.HTTP_TARGET, "/instrument-me") .put(AttributeKey.stringKey("animal"), "cat") .put(AttributeKey.longKey("speed"), 10) .build(); @@ -109,8 +109,8 @@ void matches() { assertThat( applier.matches( attributes.toBuilder() - .remove(SemanticAttributes.URL_PATH) - .put(SemanticAttributes.URL_FULL, "scheme://host:port/instrument-me") + .remove(SemanticAttributes.HTTP_TARGET) + .put(SemanticAttributes.HTTP_URL, "scheme://host:port/instrument-me") .build(), resource)) .isTrue(); @@ -133,7 +133,7 @@ void serviceNameNullNotMatch() { @Test void methodNotMatch() { Attributes attributes = - this.attributes.toBuilder().put(SemanticAttributes.HTTP_REQUEST_METHOD, "POST").build(); + this.attributes.toBuilder().put(SemanticAttributes.HTTP_METHOD, "POST").build(); assertThat(applier.matches(attributes, resource)).isFalse(); } @@ -143,7 +143,7 @@ void hostNotMatch() { // wildcard. Attributes attributes = this.attributes.toBuilder() - .put(SemanticAttributes.SERVER_ADDRESS, "opentelemetryfio") + .put(SemanticAttributes.NET_HOST_NAME, "opentelemetryfio") .build(); assertThat(applier.matches(attributes, resource)).isFalse(); } @@ -151,18 +151,20 @@ void hostNotMatch() { @Test void pathNotMatch() { Attributes attributes = - this.attributes.toBuilder().put(SemanticAttributes.URL_PATH, "/instrument-you").build(); + this.attributes.toBuilder() + .put(SemanticAttributes.HTTP_TARGET, "/instrument-you") + .build(); assertThat(applier.matches(attributes, resource)).isFalse(); attributes = this.attributes.toBuilder() - .remove(SemanticAttributes.URL_PATH) - .put(SemanticAttributes.URL_FULL, "scheme://host:port/instrument-you") + .remove(SemanticAttributes.HTTP_TARGET) + .put(SemanticAttributes.HTTP_URL, "scheme://host:port/instrument-you") .build(); assertThat(applier.matches(attributes, resource)).isFalse(); attributes = this.attributes.toBuilder() - .remove(SemanticAttributes.URL_PATH) - .put(SemanticAttributes.URL_FULL, "scheme://host:port") + .remove(SemanticAttributes.HTTP_TARGET) + .put(SemanticAttributes.HTTP_URL, "scheme://host:port") .build(); assertThat(applier.matches(attributes, resource)).isFalse(); @@ -170,8 +172,8 @@ void pathNotMatch() { // present. attributes = this.attributes.toBuilder() - .remove(SemanticAttributes.URL_PATH) - .put(SemanticAttributes.URL_FULL, "host:port/instrument-me") + .remove(SemanticAttributes.HTTP_TARGET) + .put(SemanticAttributes.HTTP_URL, "host:port/instrument-me") .build(); assertThat(applier.matches(attributes, resource)).isFalse(); } @@ -234,9 +236,9 @@ class WildcardMatch { private final Attributes attributes = Attributes.builder() - .put(SemanticAttributes.HTTP_REQUEST_METHOD, "GET") - .put(SemanticAttributes.SERVER_ADDRESS, "opentelemetry.io") - .put(SemanticAttributes.URL_PATH, "/instrument-me?foo=bar&cat=meow") + .put(SemanticAttributes.HTTP_METHOD, "GET") + .put(SemanticAttributes.NET_HOST_NAME, "opentelemetry.io") + .put(SemanticAttributes.HTTP_TARGET, "/instrument-me?foo=bar&cat=meow") .put(AttributeKey.stringKey("animal"), "cat") .put(AttributeKey.longKey("speed"), 10) .build(); @@ -308,24 +310,22 @@ void serviceNameNotMatch() { @Test void methodMatches() { Attributes attributes = - this.attributes.toBuilder() - .put(SemanticAttributes.HTTP_REQUEST_METHOD, "BADGETGOOD") - .build(); + this.attributes.toBuilder().put(SemanticAttributes.HTTP_METHOD, "BADGETGOOD").build(); assertThat(applier.matches(attributes, resource)).isTrue(); attributes = - this.attributes.toBuilder().put(SemanticAttributes.HTTP_REQUEST_METHOD, "BADGET").build(); + this.attributes.toBuilder().put(SemanticAttributes.HTTP_METHOD, "BADGET").build(); assertThat(applier.matches(attributes, resource)).isTrue(); attributes = - this.attributes.toBuilder().put(SemanticAttributes.HTTP_REQUEST_METHOD, "GETGET").build(); + this.attributes.toBuilder().put(SemanticAttributes.HTTP_METHOD, "GETGET").build(); assertThat(applier.matches(attributes, resource)).isTrue(); } @Test void methodNotMatch() { Attributes attributes = - this.attributes.toBuilder().put(SemanticAttributes.HTTP_REQUEST_METHOD, "POST").build(); + this.attributes.toBuilder().put(SemanticAttributes.HTTP_METHOD, "POST").build(); assertThat(applier.matches(attributes, resource)).isFalse(); - attributes = removeAttribute(this.attributes, SemanticAttributes.HTTP_REQUEST_METHOD); + attributes = removeAttribute(this.attributes, SemanticAttributes.HTTP_METHOD); assertThat(applier.matches(attributes, resource)).isFalse(); } @@ -333,27 +333,27 @@ void methodNotMatch() { void hostMatches() { Attributes attributes = this.attributes.toBuilder() - .put(SemanticAttributes.SERVER_ADDRESS, "alpha.opentelemetry.io") + .put(SemanticAttributes.NET_HOST_NAME, "alpha.opentelemetry.io") .build(); assertThat(applier.matches(attributes, resource)).isTrue(); attributes = this.attributes.toBuilder() - .put(SemanticAttributes.SERVER_ADDRESS, "opfdnqtelemetry.io") + .put(SemanticAttributes.NET_HOST_NAME, "opfdnqtelemetry.io") .build(); assertThat(applier.matches(attributes, resource)).isTrue(); attributes = this.attributes.toBuilder() - .put(SemanticAttributes.SERVER_ADDRESS, "opentglemetry.io") + .put(SemanticAttributes.NET_HOST_NAME, "opentglemetry.io") .build(); assertThat(applier.matches(attributes, resource)).isTrue(); attributes = this.attributes.toBuilder() - .put(SemanticAttributes.SERVER_ADDRESS, "opentglemry.io") + .put(SemanticAttributes.NET_HOST_NAME, "opentglemry.io") .build(); assertThat(applier.matches(attributes, resource)).isTrue(); attributes = this.attributes.toBuilder() - .put(SemanticAttributes.SERVER_ADDRESS, "opentglemrz.io") + .put(SemanticAttributes.NET_HOST_NAME, "opentglemrz.io") .build(); assertThat(applier.matches(attributes, resource)).isTrue(); } @@ -362,20 +362,20 @@ void hostMatches() { void hostNotMatch() { Attributes attributes = this.attributes.toBuilder() - .put(SemanticAttributes.SERVER_ADDRESS, "opentelemetryfio") + .put(SemanticAttributes.NET_HOST_NAME, "opentelemetryfio") .build(); assertThat(applier.matches(attributes, resource)).isFalse(); attributes = this.attributes.toBuilder() - .put(SemanticAttributes.SERVER_ADDRESS, "opentgalemetry.io") + .put(SemanticAttributes.NET_HOST_NAME, "opentgalemetry.io") .build(); assertThat(applier.matches(attributes, resource)).isFalse(); attributes = this.attributes.toBuilder() - .put(SemanticAttributes.SERVER_ADDRESS, "alpha.oentelemetry.io") + .put(SemanticAttributes.NET_HOST_NAME, "alpha.oentelemetry.io") .build(); assertThat(applier.matches(attributes, resource)).isFalse(); - attributes = removeAttribute(this.attributes, SemanticAttributes.SERVER_ADDRESS); + attributes = removeAttribute(this.attributes, SemanticAttributes.NET_HOST_NAME); assertThat(applier.matches(attributes, resource)).isFalse(); } @@ -383,13 +383,13 @@ void hostNotMatch() { void pathMatches() { Attributes attributes = this.attributes.toBuilder() - .put(SemanticAttributes.URL_PATH, "/instrument-me?foo=bar&cat=") + .put(SemanticAttributes.HTTP_TARGET, "/instrument-me?foo=bar&cat=") .build(); assertThat(applier.matches(attributes, resource)).isTrue(); // Deceptive question mark, it's actually a wildcard :-) attributes = this.attributes.toBuilder() - .put(SemanticAttributes.URL_PATH, "/instrument-meafoo=bar&cat=") + .put(SemanticAttributes.HTTP_TARGET, "/instrument-meafoo=bar&cat=") .build(); assertThat(applier.matches(attributes, resource)).isTrue(); } @@ -398,15 +398,15 @@ void pathMatches() { void pathNotMatch() { Attributes attributes = this.attributes.toBuilder() - .put(SemanticAttributes.URL_PATH, "/instrument-mea?foo=bar&cat=") + .put(SemanticAttributes.HTTP_TARGET, "/instrument-mea?foo=bar&cat=") .build(); assertThat(applier.matches(attributes, resource)).isFalse(); attributes = this.attributes.toBuilder() - .put(SemanticAttributes.URL_PATH, "foo/instrument-meafoo=bar&cat=") + .put(SemanticAttributes.HTTP_TARGET, "foo/instrument-meafoo=bar&cat=") .build(); assertThat(applier.matches(attributes, resource)).isFalse(); - attributes = removeAttribute(this.attributes, SemanticAttributes.URL_PATH); + attributes = removeAttribute(this.attributes, SemanticAttributes.HTTP_TARGET); assertThat(applier.matches(attributes, resource)).isFalse(); } @@ -507,9 +507,9 @@ class AwsLambdaTest { private final Attributes attributes = Attributes.builder() - .put(SemanticAttributes.HTTP_REQUEST_METHOD, "GET") - .put(SemanticAttributes.SERVER_ADDRESS, "opentelemetry.io") - .put(SemanticAttributes.URL_PATH, "/instrument-me") + .put(SemanticAttributes.HTTP_METHOD, "GET") + .put(SemanticAttributes.NET_HOST_NAME, "opentelemetry.io") + .put(SemanticAttributes.HTTP_TARGET, "/instrument-me") .put(AttributeKey.stringKey("animal"), "cat") .put(AttributeKey.longKey("speed"), 10) .build(); diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 306e62c30..3afc845ce 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -1,7 +1,7 @@ plugins { `kotlin-dsl` // When updating, update below in dependencies too - id("com.diffplug.spotless") version "6.21.0" + id("com.diffplug.spotless") version "6.23.3" } repositories { @@ -12,10 +12,10 @@ repositories { dependencies { // When updating, update above in plugins too - implementation("com.diffplug.spotless:spotless-plugin-gradle:6.21.0") + implementation("com.diffplug.spotless:spotless-plugin-gradle:6.23.3") implementation("net.ltgt.gradle:gradle-errorprone-plugin:3.1.0") implementation("net.ltgt.gradle:gradle-nullaway-plugin:1.6.0") - implementation("com.gradle.enterprise:com.gradle.enterprise.gradle.plugin:3.15") + implementation("com.gradle.enterprise:com.gradle.enterprise.gradle.plugin:3.16") } spotless { diff --git a/consistent-sampling/build.gradle.kts b/consistent-sampling/build.gradle.kts index 65f9fc25e..06b330c2f 100644 --- a/consistent-sampling/build.gradle.kts +++ b/consistent-sampling/build.gradle.kts @@ -4,11 +4,11 @@ plugins { } description = "Sampler and exporter implementations for consistent sampling" -otelJava.moduleName.set("io.opentelemetry.contrib.sampler.consistent") +otelJava.moduleName.set("io.opentelemetry.contrib.sampler") dependencies { api("io.opentelemetry:opentelemetry-sdk-trace") api("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") - testImplementation("org.hipparchus:hipparchus-core:2.3") - testImplementation("org.hipparchus:hipparchus-stat:2.3") + testImplementation("org.hipparchus:hipparchus-core:3.0") + testImplementation("org.hipparchus:hipparchus-stat:3.0") } diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOffSampler.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOffSampler.java new file mode 100644 index 000000000..405c1c019 --- /dev/null +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOffSampler.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import javax.annotation.concurrent.Immutable; + +@Immutable +final class ConsistentAlwaysOffSampler extends ConsistentSampler { + + ConsistentAlwaysOffSampler(RandomValueGenerator randomValueGenerator) { + super(randomValueGenerator); + } + + @Override + protected long getThreshold(long parentThreshold, boolean isRoot) { + return ConsistentSamplingUtil.getMaxThreshold(); + } + + @Override + public String getDescription() { + return "ConsistentAlwaysOffSampler"; + } +} diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOnSampler.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOnSampler.java new file mode 100644 index 000000000..9f9a79e44 --- /dev/null +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOnSampler.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getMinThreshold; + +import javax.annotation.concurrent.Immutable; + +@Immutable +final class ConsistentAlwaysOnSampler extends ConsistentSampler { + + ConsistentAlwaysOnSampler(RandomValueGenerator randomValueGenerator) { + super(randomValueGenerator); + } + + @Override + protected long getThreshold(long parentThreshold, boolean isRoot) { + return getMinThreshold(); + } + + @Override + public String getDescription() { + return "ConsistentAlwaysOnSampler"; + } +} diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentComposedAndSampler.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentComposedAndSampler.java new file mode 100644 index 000000000..f9210692f --- /dev/null +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentComposedAndSampler.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static java.util.Objects.requireNonNull; + +import javax.annotation.concurrent.Immutable; + +/** + * A consistent sampler composed of two consistent samplers. + * + *

This sampler samples if both samplers would sample. + */ +@Immutable +final class ConsistentComposedAndSampler extends ConsistentSampler { + + private final ConsistentSampler sampler1; + private final ConsistentSampler sampler2; + private final String description; + + ConsistentComposedAndSampler( + ConsistentSampler sampler1, + ConsistentSampler sampler2, + RandomValueGenerator randomValueGenerator) { + super(randomValueGenerator); + this.sampler1 = requireNonNull(sampler1); + this.sampler2 = requireNonNull(sampler2); + this.description = + "ConsistentComposedAndSampler{" + + "sampler1=" + + sampler1.getDescription() + + ",sampler2=" + + sampler2.getDescription() + + '}'; + } + + @Override + protected long getThreshold(long parentThreshold, boolean isRoot) { + long threshold1 = sampler1.getThreshold(parentThreshold, isRoot); + long threshold2 = sampler2.getThreshold(parentThreshold, isRoot); + if (ConsistentSamplingUtil.isValidThreshold(threshold1) + && ConsistentSamplingUtil.isValidThreshold(threshold2)) { + return Math.max(threshold1, threshold2); + } else { + return ConsistentSamplingUtil.getInvalidThreshold(); + } + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentComposedOrSampler.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentComposedOrSampler.java new file mode 100644 index 000000000..40c473793 --- /dev/null +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentComposedOrSampler.java @@ -0,0 +1,61 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static java.util.Objects.requireNonNull; + +import javax.annotation.concurrent.Immutable; + +/** + * A consistent sampler composed of two consistent samplers. + * + *

This sampler samples if any of the two samplers would sample. + */ +@Immutable +final class ConsistentComposedOrSampler extends ConsistentSampler { + + private final ConsistentSampler sampler1; + private final ConsistentSampler sampler2; + private final String description; + + ConsistentComposedOrSampler( + ConsistentSampler sampler1, + ConsistentSampler sampler2, + RandomValueGenerator randomValueGenerator) { + super(randomValueGenerator); + this.sampler1 = requireNonNull(sampler1); + this.sampler2 = requireNonNull(sampler2); + this.description = + "ConsistentComposedOrSampler{" + + "sampler1=" + + sampler1.getDescription() + + ",sampler2=" + + sampler2.getDescription() + + '}'; + } + + @Override + protected long getThreshold(long parentThreshold, boolean isRoot) { + long threshold1 = sampler1.getThreshold(parentThreshold, isRoot); + long threshold2 = sampler2.getThreshold(parentThreshold, isRoot); + if (ConsistentSamplingUtil.isValidThreshold(threshold1)) { + if (ConsistentSamplingUtil.isValidThreshold(threshold2)) { + return Math.min(threshold1, threshold2); + } + return threshold1; + } else { + if (ConsistentSamplingUtil.isValidThreshold(threshold2)) { + return threshold2; + } + return ConsistentSamplingUtil.getInvalidThreshold(); + } + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentFixedThresholdSampler.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentFixedThresholdSampler.java new file mode 100644 index 000000000..dd160f387 --- /dev/null +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentFixedThresholdSampler.java @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.calculateSamplingProbability; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.checkThreshold; + +public class ConsistentFixedThresholdSampler extends ConsistentSampler { + + private final long threshold; + private final String description; + + protected ConsistentFixedThresholdSampler( + long threshold, RandomValueGenerator randomValueGenerator) { + super(randomValueGenerator); + checkThreshold(threshold); + this.threshold = threshold; + + String thresholdString; + if (threshold == ConsistentSamplingUtil.getMaxThreshold()) { + thresholdString = "max"; + } else { + thresholdString = + ConsistentSamplingUtil.appendLast56BitHexEncodedWithoutTrailingZeros( + new StringBuilder(), threshold) + .toString(); + } + + this.description = + "ConsistentFixedThresholdSampler{threshold=" + + thresholdString + + ", sampling probability=" + + calculateSamplingProbability(threshold) + + "}"; + } + + @Override + public String getDescription() { + return description; + } + + @Override + protected long getThreshold(long parentThreshold, boolean isRoot) { + return threshold; + } +} diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentParentBasedSampler.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentParentBasedSampler.java new file mode 100644 index 000000000..43c8a0027 --- /dev/null +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentParentBasedSampler.java @@ -0,0 +1,52 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getInvalidThreshold; +import static java.util.Objects.requireNonNull; + +import javax.annotation.concurrent.Immutable; + +/** + * A consistent sampler that makes the same sampling decision as the parent. For root spans the + * sampling decision is delegated to the root sampler. + */ +@Immutable +final class ConsistentParentBasedSampler extends ConsistentSampler { + + private final ConsistentSampler rootSampler; + + private final String description; + + /** + * Constructs a new consistent parent based sampler using the given root sampler and the given + * thread-safe random generator. + * + * @param rootSampler the root sampler + * @param randomValueGenerator the function to use for generating the r-value + */ + ConsistentParentBasedSampler( + ConsistentSampler rootSampler, RandomValueGenerator randomValueGenerator) { + super(randomValueGenerator); + this.rootSampler = requireNonNull(rootSampler); + this.description = + "ConsistentParentBasedSampler{rootSampler=" + rootSampler.getDescription() + '}'; + } + + @Override + protected long getThreshold(long parentThreshold, boolean isRoot) { + if (isRoot) { + return rootSampler.getThreshold(getInvalidThreshold(), isRoot); + } else { + return parentThreshold; + } + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentRateLimitingSampler.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentRateLimitingSampler.java new file mode 100644 index 000000000..b04221df1 --- /dev/null +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentRateLimitingSampler.java @@ -0,0 +1,162 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.calculateThreshold; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getMinThreshold; +import static java.util.Objects.requireNonNull; + +import io.opentelemetry.sdk.trace.samplers.Sampler; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.LongSupplier; +import javax.annotation.concurrent.Immutable; + +/** + * This consistent {@link Sampler} adjusts the sampling probability dynamically to limit the rate of + * sampled spans. + * + *

This sampler uses exponential smoothing to estimate on irregular data (compare Wright, David + * J. "Forecasting data published at irregular time intervals using an extension of Holt's method." + * Management science 32.4 (1986): 499-510.) to estimate the average waiting time between spans + * which further allows to estimate the current rate of spans. In the paper, Eq. 2 defines the + * weighted average of a sequence of data + * + *

{@code ..., X(n-2), X(n-1), X(n)} + * + *

at irregular times + * + *

{@code ..., t(n-2), t(n-1), t(n)} + * + *

as + * + *

{@code E(X(n)) := A(n) * V(n)}. + * + *

{@code A(n)} and {@code V(n)} are computed recursively using Eq. 5 and Eq. 6 given by + * + *

{@code A(n) = b(n) * A(n-1) + X(n)} and {@code V(n) = V(n-1) / (b(n) + V(n-1))} + * + *

where + * + *

{@code b(n) := (1 - a)^(t(n) - t(n-1)) = exp((t(n) - t(n-1)) * ln(1 - a))}. + * + *

Introducing + * + *

{@code C(n) := 1 / V(n)} + * + *

the recursion can be rewritten as + * + *

{@code A(n) = b(n) * A(n-1) + X(n)} and {@code C(n) = b(n) * C(n-1) + 1}. + * + *

+ * + *

Since we want to estimate the average waiting time, our data is given by + * + *

{@code X(n) := t(n) - t(n-1)}. + * + *

+ * + *

The following correspondence is used for the implementation: + * + *

+ */ +final class ConsistentRateLimitingSampler extends ConsistentSampler { + + private static final double NANOS_IN_SECONDS = 1e-9; + + @Immutable + private static final class State { + private final double effectiveWindowCount; + private final double effectiveWindowNanos; + private final long lastNanoTime; + + public State(double effectiveWindowCount, double effectiveWindowNanos, long lastNanoTime) { + this.effectiveWindowCount = effectiveWindowCount; + this.effectiveWindowNanos = effectiveWindowNanos; + this.lastNanoTime = lastNanoTime; + } + } + + private final String description; + private final LongSupplier nanoTimeSupplier; + private final double inverseAdaptationTimeNanos; + private final double targetSpansPerNanosecondLimit; + private final AtomicReference state; + + /** + * Constructor. + * + * @param targetSpansPerSecondLimit the desired spans per second limit + * @param adaptationTimeSeconds the typical time to adapt to a new load (time constant used for + * exponential smoothing) + * @param randomValueGenerator the function to use for generating the r-value + * @param nanoTimeSupplier a supplier for the current nano time + */ + ConsistentRateLimitingSampler( + double targetSpansPerSecondLimit, + double adaptationTimeSeconds, + RandomValueGenerator randomValueGenerator, + LongSupplier nanoTimeSupplier) { + super(randomValueGenerator); + + if (targetSpansPerSecondLimit < 0.0) { + throw new IllegalArgumentException("Limit for sampled spans per second must be nonnegative!"); + } + if (adaptationTimeSeconds < 0.0) { + throw new IllegalArgumentException("Adaptation rate must be nonnegative!"); + } + this.description = + "ConsistentRateLimitingSampler{targetSpansPerSecondLimit=" + + targetSpansPerSecondLimit + + ", adaptationTimeSeconds=" + + adaptationTimeSeconds + + "}"; + this.nanoTimeSupplier = requireNonNull(nanoTimeSupplier); + + this.inverseAdaptationTimeNanos = NANOS_IN_SECONDS / adaptationTimeSeconds; + this.targetSpansPerNanosecondLimit = NANOS_IN_SECONDS * targetSpansPerSecondLimit; + + this.state = new AtomicReference<>(new State(0, 0, nanoTimeSupplier.getAsLong())); + } + + private State updateState(State oldState, long currentNanoTime) { + if (currentNanoTime <= oldState.lastNanoTime) { + return new State( + oldState.effectiveWindowCount + 1, oldState.effectiveWindowNanos, oldState.lastNanoTime); + } + long nanoTimeDelta = currentNanoTime - oldState.lastNanoTime; + double decayFactor = Math.exp(-nanoTimeDelta * inverseAdaptationTimeNanos); + double currentEffectiveWindowCount = oldState.effectiveWindowCount * decayFactor + 1; + double currentEffectiveWindowNanos = + oldState.effectiveWindowNanos * decayFactor + nanoTimeDelta; + return new State(currentEffectiveWindowCount, currentEffectiveWindowNanos, currentNanoTime); + } + + @Override + protected long getThreshold(long parentThreshold, boolean isRoot) { + long currentNanoTime = nanoTimeSupplier.getAsLong(); + State currentState = state.updateAndGet(s -> updateState(s, currentNanoTime)); + + double samplingProbability = + (currentState.effectiveWindowNanos * targetSpansPerNanosecondLimit) + / currentState.effectiveWindowCount; + + if (samplingProbability >= 1.) { + return getMinThreshold(); + } else { + return calculateThreshold(samplingProbability); + } + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSampler.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSampler.java new file mode 100644 index 000000000..e8beec0c7 --- /dev/null +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSampler.java @@ -0,0 +1,312 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getInvalidRandomValue; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getInvalidThreshold; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.isValidThreshold; +import static java.util.Objects.requireNonNull; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.trace.data.LinkData; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.opentelemetry.sdk.trace.samplers.SamplingDecision; +import io.opentelemetry.sdk.trace.samplers.SamplingResult; +import java.util.List; +import java.util.function.LongSupplier; + +/** Abstract base class for consistent samplers. */ +public abstract class ConsistentSampler implements Sampler { + + /** + * Returns a {@link ConsistentSampler} that samples all spans. + * + * @return a sampler + */ + public static ConsistentSampler alwaysOn() { + return alwaysOn(RandomValueGenerators.getDefault()); + } + + /** + * Returns a {@link ConsistentSampler} that samples all spans. + * + * @param randomValueGenerator the function to use for generating the random value + * @return a sampler + */ + public static ConsistentSampler alwaysOn(RandomValueGenerator randomValueGenerator) { + return new ConsistentAlwaysOnSampler(randomValueGenerator); + } + + /** + * Returns a {@link ConsistentSampler} that does not sample any span. + * + * @return a sampler + */ + public static ConsistentSampler alwaysOff() { + return alwaysOff(RandomValueGenerators.getDefault()); + } + + /** + * Returns a {@link ConsistentSampler} that does not sample any span. + * + * @param randomValueGenerator the function to use for generating the random value + * @return a sampler + */ + public static ConsistentSampler alwaysOff(RandomValueGenerator randomValueGenerator) { + return new ConsistentAlwaysOffSampler(randomValueGenerator); + } + + /** + * Returns a {@link ConsistentSampler} that samples each span with a fixed probability. + * + * @param samplingProbability the sampling probability + * @return a sampler + */ + public static ConsistentSampler probabilityBased(double samplingProbability) { + return probabilityBased(samplingProbability, RandomValueGenerators.getDefault()); + } + + /** + * Returns a {@link ConsistentSampler} that samples each span with a fixed probability. + * + * @param samplingProbability the sampling probability + * @param randomValueGenerator the function to use for generating the r-value + * @return a sampler + */ + public static ConsistentSampler probabilityBased( + double samplingProbability, RandomValueGenerator randomValueGenerator) { + long threshold = ConsistentSamplingUtil.calculateThreshold(samplingProbability); + return new ConsistentFixedThresholdSampler(threshold, randomValueGenerator); + } + + /** + * Returns a new {@link ConsistentSampler} that respects the sampling decision of the parent span + * or falls-back to the given sampler if it is a root span. + * + * @param rootSampler the root sampler + */ + public static ConsistentSampler parentBased(ConsistentSampler rootSampler) { + return parentBased(rootSampler, RandomValueGenerators.getDefault()); + } + + /** + * Returns a new {@link ConsistentSampler} that respects the sampling decision of the parent span + * or falls-back to the given sampler if it is a root span. + * + * @param rootSampler the root sampler + * @param randomValueGenerator the function to use for generating the random value + */ + public static ConsistentSampler parentBased( + ConsistentSampler rootSampler, RandomValueGenerator randomValueGenerator) { + return new ConsistentParentBasedSampler(rootSampler, randomValueGenerator); + } + + /** + * Returns a new {@link ConsistentSampler} that attempts to adjust the sampling probability + * dynamically to meet the target span rate. + * + * @param targetSpansPerSecondLimit the desired spans per second limit + * @param adaptationTimeSeconds the typical time to adapt to a new load (time constant used for + * exponential smoothing) + */ + public static ConsistentSampler rateLimited( + double targetSpansPerSecondLimit, double adaptationTimeSeconds) { + return rateLimited( + targetSpansPerSecondLimit, adaptationTimeSeconds, RandomValueGenerators.getDefault()); + } + + /** + * Returns a new {@link ConsistentSampler} that attempts to adjust the sampling probability + * dynamically to meet the target span rate. + * + * @param targetSpansPerSecondLimit the desired spans per second limit + * @param adaptationTimeSeconds the typical time to adapt to a new load (time constant used for + * exponential smoothing) + * @param randomValueGenerator the function to use for generating the random value + */ + public static ConsistentSampler rateLimited( + double targetSpansPerSecondLimit, + double adaptationTimeSeconds, + RandomValueGenerator randomValueGenerator) { + return rateLimited( + targetSpansPerSecondLimit, adaptationTimeSeconds, randomValueGenerator, System::nanoTime); + } + + /** + * Returns a new {@link ConsistentSampler} that attempts to adjust the sampling probability + * dynamically to meet the target span rate. + * + * @param targetSpansPerSecondLimit the desired spans per second limit + * @param adaptationTimeSeconds the typical time to adapt to a new load (time constant used for + * exponential smoothing) + * @param randomValueGenerator the function to use for generating the random value + * @param nanoTimeSupplier a supplier for the current nano time + */ + static ConsistentSampler rateLimited( + double targetSpansPerSecondLimit, + double adaptationTimeSeconds, + RandomValueGenerator randomValueGenerator, + LongSupplier nanoTimeSupplier) { + return new ConsistentRateLimitingSampler( + targetSpansPerSecondLimit, adaptationTimeSeconds, randomValueGenerator, nanoTimeSupplier); + } + + /** + * Returns a {@link ConsistentSampler} that samples a span if both this and the other given + * consistent sampler would sample the span. + * + *

If the other consistent sampler is the same as this, this consistent sampler will be + * returned. + * + *

The returned sampler takes care of setting the trace state correctly, which would not happen + * if the {@link #shouldSample(Context, String, String, SpanKind, Attributes, List)} method was + * called for each sampler individually. Also, the combined sampler is more efficient than + * evaluating the two samplers individually and combining both results afterwards. + * + * @param otherConsistentSampler the other consistent sampler + * @return the composed consistent sampler + */ + public ConsistentSampler and(ConsistentSampler otherConsistentSampler) { + if (otherConsistentSampler == this) { + return this; + } + return new ConsistentComposedAndSampler( + this, otherConsistentSampler, RandomValueGenerators.getDefault()); + } + + /** + * Returns a {@link ConsistentSampler} that samples a span if this or the other given consistent + * sampler would sample the span. + * + *

If the other consistent sampler is the same as this, this consistent sampler will be + * returned. + * + *

The returned sampler takes care of setting the trace state correctly, which would not happen + * if the {@link #shouldSample(Context, String, String, SpanKind, Attributes, List)} method was + * called for each sampler individually. Also, the combined sampler is more efficient than + * evaluating the two samplers individually and combining both results afterwards. + * + * @param otherConsistentSampler the other consistent sampler + * @return the composed consistent sampler + */ + public ConsistentSampler or(ConsistentSampler otherConsistentSampler) { + if (otherConsistentSampler == this) { + return this; + } + return new ConsistentComposedOrSampler( + this, otherConsistentSampler, RandomValueGenerators.getDefault()); + } + + private final RandomValueGenerator randomValueGenerator; + + protected ConsistentSampler(RandomValueGenerator randomValueGenerator) { + this.randomValueGenerator = requireNonNull(randomValueGenerator); + } + + @Override + public final SamplingResult shouldSample( + Context parentContext, + String traceId, + String name, + SpanKind spanKind, + Attributes attributes, + List parentLinks) { + + Span parentSpan = Span.fromContext(parentContext); + SpanContext parentSpanContext = parentSpan.getSpanContext(); + boolean isRoot = !parentSpanContext.isValid(); + boolean isParentSampled = parentSpanContext.isSampled(); + + boolean isRandomTraceIdFlagSet = false; // TODO in future get the random trace ID flag, compare + // https://www.w3.org/TR/trace-context-2/#random-trace-id-flag + + TraceState parentTraceState = parentSpanContext.getTraceState(); + String otelTraceStateString = parentTraceState.get(OtelTraceState.TRACE_STATE_KEY); + OtelTraceState otelTraceState = OtelTraceState.parse(otelTraceStateString); + + long randomValue; + if (otelTraceState.hasValidRandomValue()) { + randomValue = otelTraceState.getRandomValue(); + } else if (isRandomTraceIdFlagSet) { + randomValue = OtelTraceState.parseHex(traceId, 18, 14, getInvalidRandomValue()); + } else { + randomValue = randomValueGenerator.generate(traceId); + otelTraceState.invalidateThreshold(); + otelTraceState.setRandomValue(randomValue); + } + + long parentThreshold; + if (otelTraceState.hasValidThreshold()) { + long threshold = otelTraceState.getThreshold(); + if ((randomValue >= threshold) == isParentSampled) { // test invariant + parentThreshold = threshold; + } else { + parentThreshold = getInvalidThreshold(); + } + } else { + parentThreshold = getInvalidThreshold(); + } + + // determine new threshold that is used for the sampling decision + long threshold = getThreshold(parentThreshold, isRoot); + + // determine sampling decision + boolean isSampled; + if (isValidThreshold(threshold)) { + isSampled = (randomValue >= threshold); + otelTraceState.setThreshold(threshold); + } else { + isSampled = isParentSampled; + otelTraceState.invalidateThreshold(); + } + + SamplingDecision samplingDecision = + isSampled ? SamplingDecision.RECORD_AND_SAMPLE : SamplingDecision.DROP; + + String newOtTraceState = otelTraceState.serialize(); + + return new SamplingResult() { + + @Override + public SamplingDecision getDecision() { + return samplingDecision; + } + + @Override + public Attributes getAttributes() { + return Attributes.empty(); + } + + @Override + public TraceState getUpdatedTraceState(TraceState parentTraceState) { + return parentTraceState.toBuilder() + .put(OtelTraceState.TRACE_STATE_KEY, newOtTraceState) + .build(); + } + }; + } + + /** + * Returns the threshold that is used for the sampling decision. + * + *

NOTE: In future, further information like span attributes could be also added as arguments + * such that the sampling probability could be made dependent on those extra arguments. However, + * in any case the returned threshold value must not depend directly or indirectly on the random + * value. In particular this means that the parent sampled flag must not be used for the + * calculation of the threshold as the sampled flag depends itself on the random value. + * + * @param parentThreshold is the threshold (if known) that was used for a consistent sampling + * decision by the parent + * @param isRoot is true for the root span + * @return the threshold to be used for the sampling decision + */ + protected abstract long getThreshold(long parentThreshold, boolean isRoot); +} diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplingUtil.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplingUtil.java new file mode 100644 index 000000000..8afa38bf0 --- /dev/null +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplingUtil.java @@ -0,0 +1,141 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; + +public final class ConsistentSamplingUtil { + + private static final int RANDOM_VALUE_BITS = 56; + private static final long MAX_THRESHOLD = + 1L << RANDOM_VALUE_BITS; // corresponds to 0% sampling probability + private static final long MIN_THRESHOLD = 0; // corresponds to 100% sampling probability + private static final long MAX_RANDOM_VALUE = MAX_THRESHOLD - 1; + private static final long INVALID_THRESHOLD = -1; + private static final long INVALID_RANDOM_VALUE = -1; + + private ConsistentSamplingUtil() {} + + /** + * Returns for a given threshold the corresponding sampling probability. + * + *

The returned value does not always exactly match the applied sampling probability, since + * some least significant binary digits may not be represented by double-precision floating point + * numbers. + * + * @param threshold the threshold + * @return the sampling probability + */ + public static double calculateSamplingProbability(long threshold) { + checkThreshold(threshold); + return (MAX_THRESHOLD - threshold) * 0x1p-56; + } + + /** + * Returns the closest sampling threshold that can be used to realize sampling with the given + * probability. + * + * @param samplingProbability the sampling probability + * @return the threshold + */ + public static long calculateThreshold(double samplingProbability) { + checkProbability(samplingProbability); + return MAX_THRESHOLD - Math.round(samplingProbability * 0x1p56); + } + + /** + * Calculates the adjusted count from a given threshold. + * + * @param threshold the threshold + * @return the adjusted count + */ + public static double calculateAdjustedCount(long threshold) { + checkThreshold(threshold); + return 0x1p56 / (MAX_THRESHOLD - threshold); + } + + /** + * Returns an invalid random value. + * + *

{@code isValidRandomValue(getInvalidRandomValue())} will always return true. + * + * @return an invalid random value + */ + public static long getInvalidRandomValue() { + return INVALID_RANDOM_VALUE; + } + + /** + * Returns an invalid threshold. + * + *

{@code isValidThreshold(getInvalidThreshold())} will always return true. + * + * @return an invalid threshold value + */ + public static long getInvalidThreshold() { + return INVALID_THRESHOLD; + } + + public static long getMaxRandomValue() { + return MAX_RANDOM_VALUE; + } + + public static long getMinThreshold() { + return MIN_THRESHOLD; + } + + public static long getMaxThreshold() { + return MAX_THRESHOLD; + } + + public static boolean isValidRandomValue(long randomValue) { + return 0 <= randomValue && randomValue <= getMaxRandomValue(); + } + + public static boolean isValidThreshold(long threshold) { + return getMinThreshold() <= threshold && threshold <= getMaxThreshold(); + } + + public static boolean isValidProbability(double probability) { + return 0 <= probability && probability <= 1; + } + + static void checkThreshold(long threshold) { + if (!isValidThreshold(threshold)) { + throw new IllegalArgumentException("The threshold must be in the range [0,2^56]!"); + } + } + + static void checkProbability(double probability) { + if (!isValidProbability(probability)) { + throw new IllegalArgumentException("The probability must be in the range [0,1]!"); + } + } + + private static final char[] HEX_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + @CanIgnoreReturnValue + static StringBuilder appendLast56BitHexEncoded(StringBuilder sb, long l) { + return appendLast56BitHexEncodedHelper(sb, l, 0); + } + + @CanIgnoreReturnValue + static StringBuilder appendLast56BitHexEncodedWithoutTrailingZeros(StringBuilder sb, long l) { + int numTrailingBits = Long.numberOfTrailingZeros(l | 0x80000000000000L); + return appendLast56BitHexEncodedHelper(sb, l, numTrailingBits); + } + + @CanIgnoreReturnValue + private static StringBuilder appendLast56BitHexEncodedHelper( + StringBuilder sb, long l, int numTrailingZeroBits) { + for (int i = 52; i >= numTrailingZeroBits - 3; i -= 4) { + sb.append(HEX_DIGITS[(int) ((l >>> i) & 0xf)]); + } + return sb; + } +} diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/OtelTraceState.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/OtelTraceState.java new file mode 100644 index 000000000..e546b184b --- /dev/null +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/OtelTraceState.java @@ -0,0 +1,256 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; + +final class OtelTraceState { + + public static final String TRACE_STATE_KEY = "ot"; + + private static final String SUBKEY_RANDOM_VALUE = "rv"; + private static final String SUBKEY_THRESHOLD = "th"; + private static final int TRACE_STATE_SIZE_LIMIT = 256; + + private long randomValue; // valid in the interval [0, MAX_RANDOM_VALUE] + private long threshold; // valid in the interval [0, MAX_THRESHOLD] + + private final List otherKeyValuePairs; + + private OtelTraceState(long randomValue, long threshold, List otherKeyValuePairs) { + this.randomValue = randomValue; + this.threshold = threshold; + this.otherKeyValuePairs = otherKeyValuePairs; + } + + private OtelTraceState() { + this( + ConsistentSamplingUtil.getInvalidRandomValue(), + ConsistentSamplingUtil.getInvalidThreshold(), + Collections.emptyList()); + } + + public long getRandomValue() { + return randomValue; + } + + public long getThreshold() { + return threshold; + } + + public boolean hasValidRandomValue() { + return ConsistentSamplingUtil.isValidRandomValue(randomValue); + } + + public boolean hasValidThreshold() { + return ConsistentSamplingUtil.isValidThreshold(threshold); + } + + public void invalidateRandomValue() { + randomValue = ConsistentSamplingUtil.getInvalidRandomValue(); + } + + public void invalidateThreshold() { + threshold = ConsistentSamplingUtil.getInvalidThreshold(); + } + + /** + * Sets a new th-value. + * + *

If the given th-value is invalid, the current th-value is invalidated. + * + * @param threshold the new th-value + */ + public void setThreshold(long threshold) { + if (ConsistentSamplingUtil.isValidThreshold(threshold)) { + this.threshold = threshold; + } else { + invalidateThreshold(); + } + } + + /** + * Sets a new rv-value. + * + *

If the given rv-value is invalid, the current rv-value is invalidated. + * + * @param randomValue the new rv-value + */ + public void setRandomValue(long randomValue) { + if (ConsistentSamplingUtil.isValidRandomValue(randomValue)) { + this.randomValue = randomValue; + } else { + invalidateRandomValue(); + } + } + + /** + * Returns a string representing this state. + * + * @return a string + */ + public String serialize() { + StringBuilder sb = new StringBuilder(); + if (hasValidThreshold() && threshold < ConsistentSamplingUtil.getMaxThreshold()) { + sb.append(SUBKEY_THRESHOLD).append(':'); + ConsistentSamplingUtil.appendLast56BitHexEncodedWithoutTrailingZeros(sb, threshold); + } + if (hasValidRandomValue()) { + if (sb.length() > 0) { + sb.append(';'); + } + sb.append(SUBKEY_RANDOM_VALUE).append(':'); + ConsistentSamplingUtil.appendLast56BitHexEncoded(sb, randomValue); + } + for (String pair : otherKeyValuePairs) { + int ex = sb.length(); + if (ex != 0) { + ex += 1; + } + if (ex + pair.length() > TRACE_STATE_SIZE_LIMIT) { + break; + } + if (sb.length() > 0) { + sb.append(';'); + } + sb.append(pair); + } + return sb.toString(); + } + + private static boolean isValueByte(char c) { + return isLowerCaseAlphaNum(c) || isUpperCaseAlpha(c) || c == '.' || c == '_' || c == '-'; + } + + private static boolean isLowerCaseAlphaNum(char c) { + return isLowerCaseAlpha(c) || isDigit(c); + } + + private static boolean isDigit(char c) { + return c >= '0' && c <= '9'; + } + + private static boolean isLowerCaseAlpha(char c) { + return c >= 'a' && c <= 'z'; + } + + private static boolean isUpperCaseAlpha(char c) { + return c >= 'A' && c <= 'Z'; + } + + private static long parseRandomValue(String s, int startIncl, int endIncl) { + int len = endIncl - startIncl; + if (len != 14) { + return ConsistentSamplingUtil.getInvalidRandomValue(); + } + return parseHex(s, startIncl, len, ConsistentSamplingUtil.getInvalidRandomValue()); + } + + private static long parseThreshold(String s, int startIncl, int endIncl) { + int len = endIncl - startIncl; + if (len > 14) { + return ConsistentSamplingUtil.getInvalidThreshold(); + } + return parseHex(s, startIncl, len, ConsistentSamplingUtil.getInvalidThreshold()); + } + + static long parseHex(String s, int startIncl, int len, long invalidReturnValue) { + long r = 0; + for (int i = 0; i < len; ++i) { + long c = s.charAt(startIncl + i); + long x; + if (c >= '0' && c <= '9') { + x = c - '0'; + } else if (c >= 'a' && c <= 'f') { + x = c - 'a' + 10; + } else { + return invalidReturnValue; + } + r |= x << (52 - (i << 2)); + } + return r; + } + + /** + * Parses the trace state from a given string. + * + *

If the string cannot be successfully parsed, a new empty {@code OtelTraceState2} is + * returned. + * + * @param ts the string + * @return the parsed trace state or an empty trace state in case of parsing errors + */ + public static OtelTraceState parse(@Nullable String ts) { + List otherKeyValuePairs = null; + long threshold = ConsistentSamplingUtil.getInvalidThreshold(); + long randomValue = ConsistentSamplingUtil.getInvalidRandomValue(); + + if (ts == null || ts.isEmpty()) { + return new OtelTraceState(); + } + + if (ts.length() > TRACE_STATE_SIZE_LIMIT) { + return new OtelTraceState(); + } + + int startPos = 0; + int len = ts.length(); + + while (true) { + int colonPos = startPos; + for (; colonPos < len; colonPos++) { + char c = ts.charAt(colonPos); + if (!isLowerCaseAlpha(c) && (!isDigit(c) || colonPos == startPos)) { + break; + } + } + if (colonPos == startPos || colonPos == len || ts.charAt(colonPos) != ':') { + return new OtelTraceState(); + } + + int separatorPos = colonPos + 1; + while (separatorPos < len && isValueByte(ts.charAt(separatorPos))) { + separatorPos++; + } + + if (colonPos - startPos == SUBKEY_THRESHOLD.length() + && ts.startsWith(SUBKEY_THRESHOLD, startPos)) { + threshold = parseThreshold(ts, colonPos + 1, separatorPos); + } else if (colonPos - startPos == SUBKEY_RANDOM_VALUE.length() + && ts.startsWith(SUBKEY_RANDOM_VALUE, startPos)) { + randomValue = parseRandomValue(ts, colonPos + 1, separatorPos); + } else { + if (otherKeyValuePairs == null) { + otherKeyValuePairs = new ArrayList<>(); + } + otherKeyValuePairs.add(ts.substring(startPos, separatorPos)); + } + + if (separatorPos < len && ts.charAt(separatorPos) != ';') { + return new OtelTraceState(); + } + + if (separatorPos == len) { + break; + } + + startPos = separatorPos + 1; + + // test for a trailing ; + if (startPos == len) { + return new OtelTraceState(); + } + } + + return new OtelTraceState( + randomValue, + threshold, + (otherKeyValuePairs != null) ? otherKeyValuePairs : Collections.emptyList()); + } +} diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/RandomValueGenerator.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/RandomValueGenerator.java new file mode 100644 index 000000000..39ba47b03 --- /dev/null +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/RandomValueGenerator.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +/** + * A function for generating random values. + * + *

The distribution of random values generated by this function must be uniform over the range + * [0,2^56-1] + */ +@FunctionalInterface +public interface RandomValueGenerator { + + /** + * Returns a 56-bit uniformly distributed random value. + * + * @param traceId the trace ID + * @return the random value + */ + long generate(String traceId); +} diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/RandomValueGenerators.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/RandomValueGenerators.java new file mode 100644 index 000000000..fcd0f6f5f --- /dev/null +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/RandomValueGenerators.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getMaxRandomValue; + +import java.util.concurrent.ThreadLocalRandom; + +final class RandomValueGenerators { + + private static final RandomValueGenerator DEFAULT = createDefault(); + + static RandomValueGenerator getDefault() { + return DEFAULT; + } + + private static RandomValueGenerator createDefault() { + return s -> ThreadLocalRandom.current().nextLong() & getMaxRandomValue(); + } + + private RandomValueGenerators() {} +} diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOffSamplerTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOffSamplerTest.java new file mode 100644 index 000000000..04d266ac2 --- /dev/null +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOffSamplerTest.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getInvalidThreshold; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getMaxThreshold; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +public class ConsistentAlwaysOffSamplerTest { + + @Test + void testDescription() { + assertThat(ConsistentSampler.alwaysOff().getDescription()) + .isEqualTo("ConsistentAlwaysOffSampler"); + } + + @Test + void testThreshold() { + assertThat(ConsistentSampler.alwaysOff().getThreshold(getInvalidThreshold(), false)) + .isEqualTo(getMaxThreshold()); + assertThat(ConsistentSampler.alwaysOff().getThreshold(getInvalidThreshold(), true)) + .isEqualTo(getMaxThreshold()); + assertThat(ConsistentSampler.alwaysOff().getThreshold(getMaxThreshold(), false)) + .isEqualTo(getMaxThreshold()); + assertThat(ConsistentSampler.alwaysOff().getThreshold(getMaxThreshold(), true)) + .isEqualTo(getMaxThreshold()); + assertThat(ConsistentSampler.alwaysOff().getThreshold(0, false)).isEqualTo(getMaxThreshold()); + assertThat(ConsistentSampler.alwaysOff().getThreshold(0, true)).isEqualTo(getMaxThreshold()); + } +} diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOnSamplerTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOnSamplerTest.java new file mode 100644 index 000000000..6df53066b --- /dev/null +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOnSamplerTest.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getInvalidThreshold; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getMinThreshold; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +public class ConsistentAlwaysOnSamplerTest { + + @Test + void testDescription() { + assertThat(ConsistentSampler.alwaysOn().getDescription()) + .isEqualTo("ConsistentAlwaysOnSampler"); + } + + @Test + void testThreshold() { + assertThat(ConsistentSampler.alwaysOn().getThreshold(getInvalidThreshold(), false)) + .isEqualTo(getMinThreshold()); + assertThat(ConsistentSampler.alwaysOn().getThreshold(getInvalidThreshold(), true)) + .isEqualTo(getMinThreshold()); + assertThat(ConsistentSampler.alwaysOn().getThreshold(getMinThreshold(), false)) + .isEqualTo(getMinThreshold()); + assertThat(ConsistentSampler.alwaysOn().getThreshold(getMinThreshold(), true)) + .isEqualTo(getMinThreshold()); + assertThat(ConsistentSampler.alwaysOn().getThreshold(0, false)).isEqualTo(getMinThreshold()); + assertThat(ConsistentSampler.alwaysOn().getThreshold(0, true)).isEqualTo(getMinThreshold()); + } +} diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentFixedThresholdSamplerTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentFixedThresholdSamplerTest.java new file mode 100644 index 000000000..2cec02dff --- /dev/null +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentFixedThresholdSamplerTest.java @@ -0,0 +1,110 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.calculateThreshold; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getMaxRandomValue; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.trace.data.LinkData; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.opentelemetry.sdk.trace.samplers.SamplingDecision; +import io.opentelemetry.sdk.trace.samplers.SamplingResult; +import java.util.Collections; +import java.util.List; +import java.util.SplittableRandom; +import org.hipparchus.stat.inference.AlternativeHypothesis; +import org.hipparchus.stat.inference.BinomialTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class ConsistentFixedThresholdSamplerTest { + + private Context parentContext; + private String traceId; + private String name; + private SpanKind spanKind; + private Attributes attributes; + private List parentLinks; + + @BeforeEach + public void init() { + + parentContext = Context.root(); + traceId = "0123456789abcdef0123456789abcdef"; + name = "name"; + spanKind = SpanKind.SERVER; + attributes = Attributes.empty(); + parentLinks = Collections.emptyList(); + } + + private void testSampling(SplittableRandom rng, double samplingProbability) { + int numSpans = 10000; + + Sampler sampler = + ConsistentSampler.probabilityBased( + samplingProbability, s -> rng.nextLong() & getMaxRandomValue()); + + int numSampled = 0; + for (long i = 0; i < numSpans; ++i) { + SamplingResult samplingResult = + sampler.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks); + if (samplingResult.getDecision() == SamplingDecision.RECORD_AND_SAMPLE) { + String traceStateString = + samplingResult + .getUpdatedTraceState(TraceState.getDefault()) + .get(OtelTraceState.TRACE_STATE_KEY); + OtelTraceState traceState = OtelTraceState.parse(traceStateString); + assertThat(traceState.hasValidRandomValue()).isTrue(); + assertThat(traceState.hasValidThreshold()).isTrue(); + assertThat(traceState.getThreshold()).isEqualTo(calculateThreshold(samplingProbability)); + + numSampled += 1; + } + } + + assertThat( + new BinomialTest() + .binomialTest( + numSpans, numSampled, samplingProbability, AlternativeHypothesis.TWO_SIDED)) + .isGreaterThan(0.005); + } + + @Test + public void testSampling() { + + // fix seed to get reproducible results + SplittableRandom random = new SplittableRandom(0); + + testSampling(random, 1.); + testSampling(random, 0.5); + testSampling(random, 0.25); + testSampling(random, 0.125); + testSampling(random, 0.0); + testSampling(random, 0.45); + testSampling(random, 0.2); + testSampling(random, 0.13); + testSampling(random, 0.05); + } + + @Test + public void testDescription() { + assertThat(ConsistentSampler.probabilityBased(1.0).getDescription()) + .isEqualTo("ConsistentFixedThresholdSampler{threshold=0, sampling probability=1.0}"); + assertThat(ConsistentSampler.probabilityBased(0.5).getDescription()) + .isEqualTo("ConsistentFixedThresholdSampler{threshold=8, sampling probability=0.5}"); + assertThat(ConsistentSampler.probabilityBased(0.25).getDescription()) + .isEqualTo("ConsistentFixedThresholdSampler{threshold=c, sampling probability=0.25}"); + assertThat(ConsistentSampler.probabilityBased(1e-300).getDescription()) + .isEqualTo("ConsistentFixedThresholdSampler{threshold=max, sampling probability=0.0}"); + assertThat(ConsistentSampler.probabilityBased(0).getDescription()) + .isEqualTo("ConsistentFixedThresholdSampler{threshold=max, sampling probability=0.0}"); + } +} diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentRateLimitingSamplerTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentRateLimitingSamplerTest.java new file mode 100644 index 000000000..24c70e4e5 --- /dev/null +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentRateLimitingSamplerTest.java @@ -0,0 +1,231 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getMaxRandomValue; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.trace.data.LinkData; +import io.opentelemetry.sdk.trace.samplers.SamplingDecision; +import io.opentelemetry.sdk.trace.samplers.SamplingResult; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.SplittableRandom; +import java.util.concurrent.TimeUnit; +import java.util.function.LongSupplier; +import org.assertj.core.data.Percentage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ConsistentRateLimitingSamplerTest { + + private long[] nanoTime; + private LongSupplier nanoTimeSupplier; + private Context parentContext; + private String traceId; + private String name; + private SpanKind spanKind; + private Attributes attributes; + private List parentLinks; + + private static RandomValueGenerator randomValueGenerator() { + SplittableRandom random = new SplittableRandom(0L); + return s -> random.nextLong() & getMaxRandomValue(); + } + + @BeforeEach + void init() { + nanoTime = new long[] {0L}; + nanoTimeSupplier = () -> nanoTime[0]; + parentContext = Context.root(); + traceId = "0123456789abcdef0123456789abcdef"; + name = "name"; + spanKind = SpanKind.SERVER; + attributes = Attributes.empty(); + parentLinks = Collections.emptyList(); + } + + private void advanceTime(long nanosIncrement) { + nanoTime[0] += nanosIncrement; + } + + private long getCurrentTimeNanos() { + return nanoTime[0]; + } + + @Test + void testConstantRate() { + + double targetSpansPerSecondLimit = 1000; + double adaptationTimeSeconds = 5; + + ConsistentSampler sampler = + ConsistentSampler.rateLimited( + targetSpansPerSecondLimit, + adaptationTimeSeconds, + randomValueGenerator(), + nanoTimeSupplier); + + long nanosBetweenSpans = TimeUnit.MICROSECONDS.toNanos(100); + int numSpans = 1000000; + + List spanSampledNanos = new ArrayList<>(); + + for (int i = 0; i < numSpans; ++i) { + advanceTime(nanosBetweenSpans); + SamplingResult samplingResult = + sampler.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks); + if (SamplingDecision.RECORD_AND_SAMPLE.equals(samplingResult.getDecision())) { + spanSampledNanos.add(getCurrentTimeNanos()); + } + } + + long numSampledSpansInLast5Seconds = + spanSampledNanos.stream() + .filter(x -> x > TimeUnit.SECONDS.toNanos(95) && x <= TimeUnit.SECONDS.toNanos(100)) + .count(); + + assertThat(numSampledSpansInLast5Seconds / 5.) + .isCloseTo(targetSpansPerSecondLimit, Percentage.withPercentage(5)); + } + + @Test + void testRateIncrease() { + + double targetSpansPerSecondLimit = 1000; + double adaptationTimeSeconds = 5; + + ConsistentSampler sampler = + ConsistentSampler.rateLimited( + targetSpansPerSecondLimit, + adaptationTimeSeconds, + randomValueGenerator(), + nanoTimeSupplier); + + long nanosBetweenSpans1 = TimeUnit.MICROSECONDS.toNanos(100); + long nanosBetweenSpans2 = TimeUnit.MICROSECONDS.toNanos(10); + int numSpans1 = 500000; + int numSpans2 = 5000000; + + List spanSampledNanos = new ArrayList<>(); + + for (int i = 0; i < numSpans1; ++i) { + advanceTime(nanosBetweenSpans1); + SamplingResult samplingResult = + sampler.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks); + if (SamplingDecision.RECORD_AND_SAMPLE.equals(samplingResult.getDecision())) { + spanSampledNanos.add(getCurrentTimeNanos()); + } + } + for (int i = 0; i < numSpans2; ++i) { + advanceTime(nanosBetweenSpans2); + SamplingResult samplingResult = + sampler.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks); + if (SamplingDecision.RECORD_AND_SAMPLE.equals(samplingResult.getDecision())) { + spanSampledNanos.add(getCurrentTimeNanos()); + } + } + + long numSampledSpansWithin5SecondsBeforeChange = + spanSampledNanos.stream() + .filter(x -> x > TimeUnit.SECONDS.toNanos(45) && x <= TimeUnit.SECONDS.toNanos(50)) + .count(); + long numSampledSpansWithin5SecondsAfterChange = + spanSampledNanos.stream() + .filter(x -> x > TimeUnit.SECONDS.toNanos(50) && x <= TimeUnit.SECONDS.toNanos(55)) + .count(); + long numSampledSpansInLast5Seconds = + spanSampledNanos.stream() + .filter(x -> x > TimeUnit.SECONDS.toNanos(95) && x <= TimeUnit.SECONDS.toNanos(100)) + .count(); + + assertThat(numSampledSpansWithin5SecondsBeforeChange / 5.) + .isCloseTo(targetSpansPerSecondLimit, Percentage.withPercentage(5)); + assertThat(numSampledSpansWithin5SecondsAfterChange / 5.) + .isGreaterThan(2. * targetSpansPerSecondLimit); + assertThat(numSampledSpansInLast5Seconds / 5.) + .isCloseTo(targetSpansPerSecondLimit, Percentage.withPercentage(5)); + } + + @Test + void testRateDecrease() { + + double targetSpansPerSecondLimit = 1000; + double adaptationTimeSeconds = 5; + + ConsistentSampler sampler = + ConsistentSampler.rateLimited( + targetSpansPerSecondLimit, + adaptationTimeSeconds, + randomValueGenerator(), + nanoTimeSupplier); + + long nanosBetweenSpans1 = TimeUnit.MICROSECONDS.toNanos(10); + long nanosBetweenSpans2 = TimeUnit.MICROSECONDS.toNanos(100); + int numSpans1 = 5000000; + int numSpans2 = 500000; + + List spanSampledNanos = new ArrayList<>(); + + for (int i = 0; i < numSpans1; ++i) { + advanceTime(nanosBetweenSpans1); + SamplingResult samplingResult = + sampler.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks); + if (SamplingDecision.RECORD_AND_SAMPLE.equals(samplingResult.getDecision())) { + spanSampledNanos.add(getCurrentTimeNanos()); + } + } + for (int i = 0; i < numSpans2; ++i) { + advanceTime(nanosBetweenSpans2); + SamplingResult samplingResult = + sampler.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks); + if (SamplingDecision.RECORD_AND_SAMPLE.equals(samplingResult.getDecision())) { + spanSampledNanos.add(getCurrentTimeNanos()); + } + } + + long numSampledSpansWithin5SecondsBeforeChange = + spanSampledNanos.stream() + .filter(x -> x > TimeUnit.SECONDS.toNanos(45) && x <= TimeUnit.SECONDS.toNanos(50)) + .count(); + long numSampledSpansWithin5SecondsAfterChange = + spanSampledNanos.stream() + .filter(x -> x > TimeUnit.SECONDS.toNanos(50) && x <= TimeUnit.SECONDS.toNanos(55)) + .count(); + long numSampledSpansInLast5Seconds = + spanSampledNanos.stream() + .filter(x -> x > TimeUnit.SECONDS.toNanos(95) && x <= TimeUnit.SECONDS.toNanos(100)) + .count(); + + assertThat(numSampledSpansWithin5SecondsBeforeChange / 5.) + .isCloseTo(targetSpansPerSecondLimit, Percentage.withPercentage(5)); + assertThat(numSampledSpansWithin5SecondsAfterChange / 5.) + .isLessThan(0.5 * targetSpansPerSecondLimit); + assertThat(numSampledSpansInLast5Seconds / 5.) + .isCloseTo(targetSpansPerSecondLimit, Percentage.withPercentage(5)); + } + + @Test + void testDescription() { + + double targetSpansPerSecondLimit = 123.456; + double adaptationTimeSeconds = 7.89; + ConsistentSampler sampler = + ConsistentSampler.rateLimited(targetSpansPerSecondLimit, adaptationTimeSeconds); + + assertThat(sampler.getDescription()) + .isEqualTo( + "ConsistentRateLimitingSampler{targetSpansPerSecondLimit=" + + targetSpansPerSecondLimit + + ", adaptationTimeSeconds=" + + adaptationTimeSeconds + + "}"); + } +} diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplerTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplerTest.java new file mode 100644 index 000000000..da6236874 --- /dev/null +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplerTest.java @@ -0,0 +1,263 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getMaxRandomValue; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getMaxThreshold; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.trace.data.LinkData; +import io.opentelemetry.sdk.trace.samplers.SamplingDecision; +import io.opentelemetry.sdk.trace.samplers.SamplingResult; +import java.util.Collections; +import java.util.List; +import java.util.OptionalLong; +import java.util.SplittableRandom; +import org.junit.jupiter.api.Test; + +class ConsistentSamplerTest { + + private static class Input { + private static final String traceId = "00112233445566778800000000000000"; + private static final String spanId = "0123456789abcdef"; + private static final String name = "name"; + private static final SpanKind spanKind = SpanKind.SERVER; + private static final Attributes attributes = Attributes.empty(); + private static final List parentLinks = Collections.emptyList(); + private boolean parentSampled = true; + + private OptionalLong parentThreshold = OptionalLong.empty(); + private OptionalLong parentRandomValue = OptionalLong.empty(); + + private final SplittableRandom random = new SplittableRandom(0L); + + public void setParentSampled(boolean parentSampled) { + this.parentSampled = parentSampled; + } + + public void setParentThreshold(long parentThreshold) { + assertThat(parentThreshold).isBetween(0L, 0xffffffffffffffL); + this.parentThreshold = OptionalLong.of(parentThreshold); + } + + public void setParentRandomValue(long parentRandomValue) { + assertThat(parentRandomValue).isBetween(0L, 0xffffffffffffffL); + this.parentRandomValue = OptionalLong.of(parentRandomValue); + } + + public Context getParentContext() { + return createParentContext( + traceId, spanId, parentThreshold, parentRandomValue, parentSampled); + } + + public static String getTraceId() { + return traceId; + } + + public static String getName() { + return name; + } + + public static SpanKind getSpanKind() { + return spanKind; + } + + public static Attributes getAttributes() { + return attributes; + } + + public static List getParentLinks() { + return parentLinks; + } + + public RandomValueGenerator getRandomValueGenerator() { + return s -> random.nextLong() & getMaxRandomValue(); + } + } + + private static class Output { + + private final SamplingResult samplingResult; + private final Context parentContext; + + Output(SamplingResult samplingResult, Context parentContext) { + this.samplingResult = samplingResult; + this.parentContext = parentContext; + } + + boolean getSampledFlag() { + return SamplingDecision.RECORD_AND_SAMPLE.equals(samplingResult.getDecision()); + } + + OptionalLong getThreshold() { + Span parentSpan = Span.fromContext(parentContext); + OtelTraceState otelTraceState = + OtelTraceState.parse( + samplingResult + .getUpdatedTraceState(parentSpan.getSpanContext().getTraceState()) + .get(OtelTraceState.TRACE_STATE_KEY)); + return otelTraceState.hasValidThreshold() + ? OptionalLong.of(otelTraceState.getThreshold()) + : OptionalLong.empty(); + } + + OptionalLong getRandomValue() { + Span parentSpan = Span.fromContext(parentContext); + OtelTraceState otelTraceState = + OtelTraceState.parse( + samplingResult + .getUpdatedTraceState(parentSpan.getSpanContext().getTraceState()) + .get(OtelTraceState.TRACE_STATE_KEY)); + return otelTraceState.hasValidRandomValue() + ? OptionalLong.of(otelTraceState.getRandomValue()) + : OptionalLong.empty(); + } + } + + private static TraceState createTraceState(OptionalLong threshold, OptionalLong randomValue) { + OtelTraceState state = OtelTraceState.parse(""); + threshold.ifPresent(x -> state.setThreshold(x)); + randomValue.ifPresent(x -> state.setRandomValue(x)); + return TraceState.builder().put(OtelTraceState.TRACE_STATE_KEY, state.serialize()).build(); + } + + private static Context createParentContext( + String traceId, + String spanId, + OptionalLong threshold, + OptionalLong randomValue, + boolean sampled) { + TraceState parentTraceState = createTraceState(threshold, randomValue); + TraceFlags traceFlags = sampled ? TraceFlags.getSampled() : TraceFlags.getDefault(); + SpanContext parentSpanContext = + SpanContext.create(traceId, spanId, traceFlags, parentTraceState); + Span parentSpan = Span.wrap(parentSpanContext); + return parentSpan.storeInContext(Context.root()); + } + + private static Output sample(Input input, ConsistentSampler sampler) { + + Context parentContext = input.getParentContext(); + SamplingResult samplingResult = + sampler.shouldSample( + parentContext, + Input.getTraceId(), + Input.getName(), + Input.getSpanKind(), + Input.getAttributes(), + Input.getParentLinks()); + return new Output(samplingResult, parentContext); + } + + @Test + void testMinThresholdWithoutParentRandomValue() { + + Input input = new Input(); + + ConsistentSampler sampler = ConsistentSampler.alwaysOn(input.getRandomValueGenerator()); + + Output output = sample(input, sampler); + + assertThat(output.samplingResult.getDecision()).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE); + assertThat(output.getThreshold()).hasValue(0); + assertThat(output.getRandomValue()).hasValue(0x20a8397b1dcdafL); + assertThat(output.getSampledFlag()).isTrue(); + } + + @Test + void testMinThresholdWithParentRandomValue() { + + long parentRandomValue = 0x7f99aa40c02744L; + + Input input = new Input(); + input.setParentRandomValue(parentRandomValue); + + ConsistentSampler sampler = ConsistentSampler.alwaysOn(input.getRandomValueGenerator()); + + Output output = sample(input, sampler); + + assertThat(output.samplingResult.getDecision()).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE); + assertThat(output.getThreshold()).hasValue(0); + assertThat(output.getRandomValue()).hasValue(parentRandomValue); + assertThat(output.getSampledFlag()).isTrue(); + } + + @Test + void testMaxThreshold() { + + Input input = new Input(); + + ConsistentSampler sampler = + new ConsistentFixedThresholdSampler(getMaxThreshold(), input.getRandomValueGenerator()); + + Output output = sample(input, sampler); + + assertThat(output.samplingResult.getDecision()).isEqualTo(SamplingDecision.DROP); + assertThat(output.getThreshold()).isEmpty(); + assertThat(output.getRandomValue()).hasValue(0x20a8397b1dcdafL); + assertThat(output.getSampledFlag()).isFalse(); + } + + @Test + void testHalfThresholdNotSampled() { + + Input input = new Input(); + input.setParentRandomValue(0x7FFFFFFFFFFFFFL); + + ConsistentSampler sampler = + new ConsistentFixedThresholdSampler(0x80000000000000L, input.getRandomValueGenerator()); + + Output output = sample(input, sampler); + + assertThat(output.samplingResult.getDecision()).isEqualTo(SamplingDecision.DROP); + assertThat(output.getThreshold()).hasValue(0x80000000000000L); + assertThat(output.getRandomValue()).hasValue(0x7FFFFFFFFFFFFFL); + assertThat(output.getSampledFlag()).isFalse(); + } + + @Test + void testHalfThresholdSampled() { + + Input input = new Input(); + input.setParentRandomValue(0x80000000000000L); + + ConsistentSampler sampler = + new ConsistentFixedThresholdSampler(0x80000000000000L, input.getRandomValueGenerator()); + + Output output = sample(input, sampler); + + assertThat(output.samplingResult.getDecision()).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE); + assertThat(output.getThreshold()).hasValue(0x80000000000000L); + assertThat(output.getRandomValue()).hasValue(0x80000000000000L); + assertThat(output.getSampledFlag()).isTrue(); + } + + @Test + void testParentViolatingInvariant() { + + Input input = new Input(); + input.setParentThreshold(0x80000000000000L); + input.setParentRandomValue(0x80000000000000L); + input.setParentSampled(false); + + ConsistentSampler sampler = + new ConsistentFixedThresholdSampler(0x0L, input.getRandomValueGenerator()); + Output output = sample(input, sampler); + + assertThat(output.samplingResult.getDecision()).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE); + + assertThat(output.getThreshold()).hasValue(0x0L); + assertThat(output.getRandomValue()).hasValue(0x80000000000000L); + assertThat(output.getSampledFlag()).isTrue(); + } +} diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplingUtilTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplingUtilTest.java new file mode 100644 index 000000000..fcf2dcd8d --- /dev/null +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplingUtilTest.java @@ -0,0 +1,126 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.calculateAdjustedCount; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.calculateSamplingProbability; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.calculateThreshold; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getInvalidRandomValue; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getInvalidThreshold; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getMaxThreshold; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getMinThreshold; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.isValidRandomValue; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.isValidThreshold; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +import org.junit.jupiter.api.Test; + +public class ConsistentSamplingUtilTest { + + @Test + void testCalculateSamplingProbability() { + assertThat(calculateSamplingProbability(getMinThreshold())).isOne(); + assertThat(calculateSamplingProbability(0xc0000000000000L)).isEqualTo(0.25); + assertThat(calculateSamplingProbability(0x80000000000000L)).isEqualTo(0.5); + assertThat(calculateSamplingProbability(getMaxThreshold())).isZero(); + assertThatIllegalArgumentException().isThrownBy(() -> calculateSamplingProbability(-1)); + assertThatIllegalArgumentException() + .isThrownBy(() -> calculateSamplingProbability(getMaxThreshold() + 1)); + assertThatIllegalArgumentException() + .isThrownBy(() -> calculateSamplingProbability(getMinThreshold() - 1)); + } + + @Test + void testCalculateThreshold() { + assertThat(calculateThreshold(0.)).isEqualTo(getMaxThreshold()); + assertThat(calculateThreshold(0.25)).isEqualTo(0xc0000000000000L); + assertThat(calculateThreshold(0.5)).isEqualTo(0x80000000000000L); + assertThat(calculateThreshold(1.)).isEqualTo(getMinThreshold()); + assertThatIllegalArgumentException().isThrownBy(() -> calculateThreshold(Math.nextDown(0.))); + assertThatIllegalArgumentException().isThrownBy(() -> calculateThreshold(Math.nextUp(1.))); + assertThatIllegalArgumentException() + .isThrownBy(() -> calculateThreshold(Double.POSITIVE_INFINITY)); + assertThatIllegalArgumentException() + .isThrownBy(() -> calculateThreshold(Double.NEGATIVE_INFINITY)); + assertThatIllegalArgumentException().isThrownBy(() -> calculateThreshold(Double.NaN)); + } + + @Test + void testGetInvalidRandomValue() { + assertThat(isValidRandomValue(getInvalidRandomValue())).isFalse(); + } + + @Test + void testGetInvalidThreshold() { + assertThat(isValidThreshold(getInvalidThreshold())).isFalse(); + } + + @Test + void testGetMinThreshold() { + assertThat(getMinThreshold()).isZero(); + } + + @Test + void testGetMaxThreshold() { + assertThat(getMaxThreshold()).isEqualTo(0x100000000000000L); + } + + @Test + void testGetMaxRandomValue() { + assertThat(ConsistentSamplingUtil.getMaxRandomValue()).isEqualTo(0xFFFFFFFFFFFFFFL); + } + + @Test + void testCalculateAdjustedCount() { + assertThat(calculateAdjustedCount(getMinThreshold())).isOne(); + assertThat(calculateAdjustedCount(0xc0000000000000L)).isEqualTo(4.); + assertThat(calculateAdjustedCount(0x80000000000000L)).isEqualTo(2.); + assertThat(calculateAdjustedCount(getMaxThreshold() - 1)).isEqualTo(0x1p56); + assertThat(calculateAdjustedCount(getMaxThreshold())).isInfinite(); + assertThatIllegalArgumentException() + .isThrownBy(() -> calculateAdjustedCount(getMinThreshold() - 1)); + assertThatIllegalArgumentException() + .isThrownBy(() -> calculateAdjustedCount(getMaxThreshold() + 1)); + } + + @Test + void testAppendLast56BitHexEncoded() { + assertThat( + ConsistentSamplingUtil.appendLast56BitHexEncoded(new StringBuilder(), 0x3a436f7842456L)) + .hasToString("03a436f7842456"); + assertThat( + ConsistentSamplingUtil.appendLast56BitHexEncoded( + new StringBuilder(), 0x3a436f7842456abL)) + .hasToString("a436f7842456ab"); + assertThat(ConsistentSamplingUtil.appendLast56BitHexEncoded(new StringBuilder(), 0L)) + .hasToString("00000000000000"); + } + + @Test + void testAppendLast56BitHexEncodedWithoutTrailingZeros() { + assertThat( + ConsistentSamplingUtil.appendLast56BitHexEncodedWithoutTrailingZeros( + new StringBuilder(), 0x3a436f7842456L)) + .hasToString("03a436f7842456"); + assertThat( + ConsistentSamplingUtil.appendLast56BitHexEncodedWithoutTrailingZeros( + new StringBuilder(), 0x3a436f7842456abL)) + .hasToString("a436f7842456ab"); + assertThat( + ConsistentSamplingUtil.appendLast56BitHexEncodedWithoutTrailingZeros( + new StringBuilder(), 0x80000000000000L)) + .hasToString("8"); + assertThat( + ConsistentSamplingUtil.appendLast56BitHexEncodedWithoutTrailingZeros( + new StringBuilder(), 0x11000000000000L)) + .hasToString("11"); + assertThat( + ConsistentSamplingUtil.appendLast56BitHexEncodedWithoutTrailingZeros( + new StringBuilder(), 0L)) + .hasToString("0"); + } +} diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/OtelTraceStateTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/OtelTraceStateTest.java new file mode 100644 index 000000000..a131e9b78 --- /dev/null +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/OtelTraceStateTest.java @@ -0,0 +1,75 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; + +public class OtelTraceStateTest { + + private static String getXString(int len) { + return Stream.generate(() -> "X").limit(len).collect(Collectors.joining()); + } + + @Test + public void test() { + + assertEquals("", OtelTraceState.parse("").serialize()); + assertEquals("", OtelTraceState.parse("").serialize()); + + assertEquals("", OtelTraceState.parse("a").serialize()); + assertEquals("", OtelTraceState.parse("#").serialize()); + assertEquals("", OtelTraceState.parse(" ").serialize()); + + assertEquals("rv:1234567890abcd", OtelTraceState.parse("rv:1234567890abcd").serialize()); + assertEquals("rv:01020304050607", OtelTraceState.parse("rv:01020304050607").serialize()); + assertEquals("", OtelTraceState.parse("rv:1234567890abcde").serialize()); + + assertEquals("th:1234567890abcd", OtelTraceState.parse("th:1234567890abcd").serialize()); + assertEquals("th:01020304050607", OtelTraceState.parse("th:01020304050607").serialize()); + assertEquals("th:1", OtelTraceState.parse("th:10000000000000").serialize()); + assertEquals("th:12345", OtelTraceState.parse("th:1234500000000").serialize()); + assertEquals("th:0", OtelTraceState.parse("th:0").serialize()); // TODO + assertEquals("", OtelTraceState.parse("th:100000000000000").serialize()); + assertEquals("", OtelTraceState.parse("th:1234567890abcde").serialize()); + + assertEquals( + "th:1234567890abcd;rv:1234567890abcd;a:" + getXString(214) + ";x:3", + OtelTraceState.parse("a:" + getXString(214) + ";rv:1234567890abcd;th:1234567890abcd;x:3") + .serialize()); + assertEquals( + "", + OtelTraceState.parse("a:" + getXString(215) + ";rv:1234567890abcd;th:1234567890abcd;x:3") + .serialize()); + + assertEquals("", OtelTraceState.parse("th:x").serialize()); + assertEquals("", OtelTraceState.parse("th:100000000000000").serialize()); + assertEquals("th:1", OtelTraceState.parse("th:10000000000000").serialize()); + assertEquals("th:1", OtelTraceState.parse("th:1000000000000").serialize()); + assertEquals("th:1", OtelTraceState.parse("th:100000000000").serialize()); + assertEquals("th:1", OtelTraceState.parse("th:10000000000").serialize()); + assertEquals("th:1", OtelTraceState.parse("th:1000000000").serialize()); + assertEquals("th:1", OtelTraceState.parse("th:100000000").serialize()); + assertEquals("th:1", OtelTraceState.parse("th:10000000").serialize()); + assertEquals("th:1", OtelTraceState.parse("th:1000000").serialize()); + assertEquals("th:1", OtelTraceState.parse("th:100000").serialize()); + assertEquals("th:1", OtelTraceState.parse("th:10000").serialize()); + assertEquals("th:1", OtelTraceState.parse("th:1000").serialize()); + assertEquals("th:1", OtelTraceState.parse("th:100").serialize()); + assertEquals("th:1", OtelTraceState.parse("th:10").serialize()); + assertEquals("th:1", OtelTraceState.parse("th:1").serialize()); + + assertEquals("th:10000000000001", OtelTraceState.parse("th:10000000000001").serialize()); + assertEquals("th:1000000000001", OtelTraceState.parse("th:10000000000010").serialize()); + assertEquals("", OtelTraceState.parse("rv:x").serialize()); + assertEquals("", OtelTraceState.parse("rv:100000000000000").serialize()); + assertEquals("rv:10000000000000", OtelTraceState.parse("rv:10000000000000").serialize()); + assertEquals("", OtelTraceState.parse("rv:1000000000000").serialize()); + } +} diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/RandomValueGeneratorsTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/RandomValueGeneratorsTest.java new file mode 100644 index 000000000..ab7d378b6 --- /dev/null +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/RandomValueGeneratorsTest.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getMaxRandomValue; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +public class RandomValueGeneratorsTest { + @Test + void testRandomRange() { + int attempts = 10000; + for (int i = 0; i < attempts; ++i) { + assertThat(RandomValueGenerators.getDefault().generate("")) + .isBetween(0L, getMaxRandomValue()); + } + } +} diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 03d4b414f..23d83c926 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -8,13 +8,13 @@ val dependencyVersions = hashMapOf() rootProject.extra["versions"] = dependencyVersions val DEPENDENCY_BOMS = listOf( - "com.fasterxml.jackson:jackson-bom:2.15.2", - "com.google.guava:guava-bom:32.1.2-jre", - "com.linecorp.armeria:armeria-bom:1.25.2", - "org.junit:junit-bom:5.10.0", - "io.grpc:grpc-bom:1.58.0", - "io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:1.30.0-alpha", - "org.testcontainers:testcontainers-bom:1.19.0" + "com.fasterxml.jackson:jackson-bom:2.16.0", + "com.google.guava:guava-bom:32.1.3-jre", + "com.linecorp.armeria:armeria-bom:1.26.4", + "org.junit:junit-bom:5.10.1", + "io.grpc:grpc-bom:1.60.0", + "io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:1.32.0-alpha", + "org.testcontainers:testcontainers-bom:1.19.3" ) val autoServiceVersion = "1.1.1" @@ -48,8 +48,8 @@ val CORE_DEPENDENCIES = listOf( val DEPENDENCIES = listOf( "com.google.code.findbugs:annotations:3.0.1u2", "com.google.code.findbugs:jsr305:3.0.2", - "com.squareup.okhttp3:okhttp:4.11.0", - "com.uber.nullaway:nullaway:0.10.14", + "com.squareup.okhttp3:okhttp:4.12.0", + "com.uber.nullaway:nullaway:0.10.18", "org.assertj:assertj-core:3.24.2", "org.awaitility:awaitility:4.2.0", "org.bouncycastle:bcpkix-jdk15on:1.70", diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index 86f13f37a..74f51a72c 100644 --- a/disk-buffering/build.gradle.kts +++ b/disk-buffering/build.gradle.kts @@ -3,9 +3,9 @@ import ru.vyarus.gradle.plugin.animalsniffer.AnimalSniffer plugins { id("otel.java-conventions") id("otel.publish-conventions") - id("me.champeau.jmh") version "0.7.1" + id("me.champeau.jmh") version "0.7.2" id("ru.vyarus.animalsniffer") version "1.7.1" - id("com.squareup.wire") version "4.9.1" + id("com.squareup.wire") version "4.9.3" } description = "Exporter implementations that store signals on disk" @@ -20,7 +20,7 @@ dependencies { api("io.opentelemetry:opentelemetry-sdk") compileOnly("com.google.auto.value:auto-value-annotations") annotationProcessor("com.google.auto.value:auto-value") - signature("com.toasttab.android:gummy-bears-api-24:0.5.1@signature") + signature("com.toasttab.android:gummy-bears-api-21:0.6.1:coreLib@signature") testImplementation("org.mockito:mockito-inline") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c4..d64cd4917 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 864d6c475..db8c3baaf 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=591855b517fc635b9e04de1d05d5e76ada3f89f5fc76f87978d1b245b4f69225 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionSha256Sum=9d926787066a081739e8200858338b4a69e837c3a821a33aca9db09dd4a41026 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 0adc8e1a5..1aa94a426 100755 --- a/gradlew +++ b/gradlew @@ -145,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -153,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -202,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/JettyIntegrationTest.java b/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/JettyIntegrationTest.java index 0fb04a608..761b6c842 100644 --- a/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/JettyIntegrationTest.java +++ b/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/JettyIntegrationTest.java @@ -29,7 +29,7 @@ class JettyIntegrationTest extends AbstractIntegrationTest { .withDockerfileFromBuilder( builder -> builder - .from("jetty") + .from("jetty:11") .run( "java", "-jar", diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy index 369a4a6b1..0f69f5aea 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/InstrumentHelper.groovy @@ -165,10 +165,18 @@ class InstrumentHelper { private static Map getLabels(GroovyMBean mbean, Map labelFuncs, Map additionalLabels) { def labels = [:] labelFuncs.each { label, labelFunc -> - labels[label] = labelFunc(mbean) as String + try { + labels[label] = labelFunc(mbean) as String + } catch(AttributeNotFoundException e) { + logger.warning("Attribute missing for label:${label}, label was not applied") + } } additionalLabels.each { label, labelFunc -> - labels[label] = labelFunc(mbean) as String + try { + labels[label] = labelFunc(mbean) as String + } catch(AttributeNotFoundException e) { + logger.warning("Attribute missing for label:${label}, label was not applied") + } } return labels } diff --git a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/MBeanHelper.groovy b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/MBeanHelper.groovy index 9cabdecf2..2c05fb484 100644 --- a/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/MBeanHelper.groovy +++ b/jmx-metrics/src/main/groovy/io/opentelemetry/contrib/jmxmetrics/MBeanHelper.groovy @@ -122,8 +122,16 @@ class MBeanHelper { } Object getBeanAttributeWithTransform(GroovyMBean bean, String attribute){ - def transformationClosure = attributeTransformation.get(attribute); - return transformationClosure != null ? transformationClosure(bean) : getBeanAttribute(bean, attribute) + def transformationClosure = attributeTransformation.get(attribute); + if (transformationClosure == null) { + return getBeanAttribute(bean, attribute) + } + try { + return transformationClosure(bean) + } catch(AttributeNotFoundException e) { + logger.warning("Transformed attribute not found in ${bean.name()}") + return null + } } static Object getBeanAttribute(GroovyMBean bean, String attribute) { diff --git a/maven-extension/README.md b/maven-extension/README.md index 292c38b3b..3ae23c7f9 100644 --- a/maven-extension/README.md +++ b/maven-extension/README.md @@ -192,9 +192,8 @@ Otherwise, the instrumentation of the Maven plugin is noop. It is recommended to enrich spans using the [OpenTelemetry Semantic Conventions](https://opentelemetry.io/docs/concepts/semantic-conventions/) to improve the visualization and analysis in Observability products. -The [HTTP](https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/) -and [database client calls](https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/database/) -conventions are particularly useful when invoking external systems. +The HTTP and database client [semantic conventions](https://github.com/open-telemetry/semantic-conventions) +are particularly useful when invoking external systems. Steps to instrument a Maven Mojo: diff --git a/micrometer-meter-provider/build.gradle.kts b/micrometer-meter-provider/build.gradle.kts index e73ce74c4..9f194a994 100644 --- a/micrometer-meter-provider/build.gradle.kts +++ b/micrometer-meter-provider/build.gradle.kts @@ -19,14 +19,14 @@ dependencies { annotationProcessor("com.google.auto.value:auto-value") compileOnly("com.google.auto.value:auto-value-annotations") - testImplementation("io.micrometer:micrometer-core:1.11.4") + testImplementation("io.micrometer:micrometer-core:1.12.1") } testing { suites { val integrationTest by registering(JvmTestSuite::class) { dependencies { - implementation("io.micrometer:micrometer-registry-prometheus:1.11.4") + implementation("io.micrometer:micrometer-registry-prometheus:1.12.1") } } } diff --git a/prometheus-client-bridge/src/main/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/PrometheusCollector.java b/prometheus-client-bridge/src/main/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/PrometheusCollector.java index c5aff3bb8..3acc1096a 100644 --- a/prometheus-client-bridge/src/main/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/PrometheusCollector.java +++ b/prometheus-client-bridge/src/main/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/PrometheusCollector.java @@ -11,7 +11,6 @@ import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.export.CollectionRegistration; import io.opentelemetry.sdk.metrics.export.MetricReader; -import io.opentelemetry.sdk.metrics.internal.export.MetricProducer; import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; import java.util.ArrayList; @@ -28,10 +27,10 @@ public final class PrometheusCollector implements MetricReader { private final Collector collector; - private volatile MetricProducer metricProducer = MetricProducer.noop(); + private volatile CollectionRegistration collectionRegistration = CollectionRegistration.noop(); PrometheusCollector() { - this.collector = new CollectorImpl(() -> getMetricProducer().collectAllMetrics()); + this.collector = new CollectorImpl(() -> collectionRegistration.collectAllMetrics()); this.collector.register(); } @@ -43,13 +42,9 @@ public static PrometheusCollector create() { return new PrometheusCollector(); } - private MetricProducer getMetricProducer() { - return metricProducer; - } - @Override public void register(CollectionRegistration registration) { - this.metricProducer = MetricProducer.asMetricProducer(registration); + this.collectionRegistration = registration; } @Override diff --git a/prometheus-client-bridge/src/test/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/PrometheusCollectorTest.java b/prometheus-client-bridge/src/test/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/PrometheusCollectorTest.java index b1e77a321..ebc011752 100644 --- a/prometheus-client-bridge/src/test/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/PrometheusCollectorTest.java +++ b/prometheus-client-bridge/src/test/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/PrometheusCollectorTest.java @@ -16,11 +16,11 @@ import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.export.CollectionRegistration; import io.opentelemetry.sdk.metrics.internal.data.ImmutableDoublePointData; import io.opentelemetry.sdk.metrics.internal.data.ImmutableLongPointData; import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData; import io.opentelemetry.sdk.metrics.internal.data.ImmutableSumData; -import io.opentelemetry.sdk.metrics.internal.export.MetricProducer; import io.opentelemetry.sdk.resources.Resource; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.exporter.common.TextFormat; @@ -36,14 +36,14 @@ @ExtendWith(MockitoExtension.class) class PrometheusCollectorTest { - @Mock MetricProducer metricProducer; + @Mock CollectionRegistration collectionRegistration; PrometheusCollector prometheusCollector; @BeforeEach void setUp() { // Apply the SDK metric producer registers with prometheus. prometheusCollector = new PrometheusCollector(); - prometheusCollector.register(metricProducer); + prometheusCollector.register(collectionRegistration); } @Test @@ -60,7 +60,7 @@ void registerWithSdkMeterProvider() { @Test void registerToDefault() throws IOException { - when(metricProducer.collectAllMetrics()).thenReturn(generateTestData()); + when(collectionRegistration.collectAllMetrics()).thenReturn(generateTestData()); StringWriter stringWriter = new StringWriter(); TextFormat.write004(stringWriter, CollectorRegistry.defaultRegistry.metricFamilySamples()); assertThat(stringWriter.toString()) diff --git a/runtime-attach/README.md b/runtime-attach/README.md index 695124f8e..294345a75 100644 --- a/runtime-attach/README.md +++ b/runtime-attach/README.md @@ -39,7 +39,7 @@ We give below an example for Spring Boot applications: public class SpringBootApp { public static void main(String[] args) { - RuntimeAttach.attachJavaagentToCurrentJVM(); + RuntimeAttach.attachJavaagentToCurrentJvm(); SpringApplication.run(SpringBootApp.class, args); } diff --git a/runtime-attach/runtime-attach-core/build.gradle.kts b/runtime-attach/runtime-attach-core/build.gradle.kts index 71314c7b4..fd899ee42 100644 --- a/runtime-attach/runtime-attach-core/build.gradle.kts +++ b/runtime-attach/runtime-attach-core/build.gradle.kts @@ -7,7 +7,7 @@ description = "To help in create an OpenTelemetry distro able to runtime attach otelJava.moduleName.set("io.opentelemetry.contrib.attach.core") dependencies { - implementation("net.bytebuddy:byte-buddy-agent:1.14.8") + implementation("net.bytebuddy:byte-buddy-agent:1.14.10") // Used by byte-buddy but not brought in as a transitive dependency. compileOnly("com.google.code.findbugs:annotations") diff --git a/runtime-attach/runtime-attach-core/src/main/java/io/opentelemetry/contrib/attach/core/CoreRuntimeAttach.java b/runtime-attach/runtime-attach-core/src/main/java/io/opentelemetry/contrib/attach/core/CoreRuntimeAttach.java index dab3217fb..ee5545f7b 100644 --- a/runtime-attach/runtime-attach-core/src/main/java/io/opentelemetry/contrib/attach/core/CoreRuntimeAttach.java +++ b/runtime-attach/runtime-attach-core/src/main/java/io/opentelemetry/contrib/attach/core/CoreRuntimeAttach.java @@ -35,7 +35,7 @@ public CoreRuntimeAttach(String agentJarResourceName) { * beginning of the main method. */ @SuppressWarnings("MemberName") - public void attachJavaagentToCurrentJVM() { + public void attachJavaagentToCurrentJvm() { if (!shouldAttach()) { return; } diff --git a/runtime-attach/runtime-attach/src/main/java/io/opentelemetry/contrib/attach/RuntimeAttach.java b/runtime-attach/runtime-attach/src/main/java/io/opentelemetry/contrib/attach/RuntimeAttach.java index 9c97720a4..2d1e1ff11 100644 --- a/runtime-attach/runtime-attach/src/main/java/io/opentelemetry/contrib/attach/RuntimeAttach.java +++ b/runtime-attach/runtime-attach/src/main/java/io/opentelemetry/contrib/attach/RuntimeAttach.java @@ -15,11 +15,11 @@ public final class RuntimeAttach { * beginning of the main method. */ @SuppressWarnings("MemberName") - public static void attachJavaagentToCurrentJVM() { + public static void attachJavaagentToCurrentJvm() { CoreRuntimeAttach distroRuntimeAttach = new CoreRuntimeAttach("/otel-agent.jar"); - distroRuntimeAttach.attachJavaagentToCurrentJVM(); + distroRuntimeAttach.attachJavaagentToCurrentJvm(); } private RuntimeAttach() {} diff --git a/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AgentDisabledByEnvironmentVariableTest.java b/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AgentDisabledByEnvironmentVariableTest.java index 5ad6f71a7..41571a2e4 100644 --- a/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AgentDisabledByEnvironmentVariableTest.java +++ b/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AgentDisabledByEnvironmentVariableTest.java @@ -14,7 +14,7 @@ public class AgentDisabledByEnvironmentVariableTest extends AbstractAttachmentTe @Test void shouldNotAttachWhenAgentDisabledWithEnvVariable() { - RuntimeAttach.attachJavaagentToCurrentJVM(); + RuntimeAttach.attachJavaagentToCurrentJvm(); verifyNoAttachment(); } diff --git a/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AgentDisabledBySystemPropertyTest.java b/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AgentDisabledBySystemPropertyTest.java index 39a071ee2..a24c81296 100644 --- a/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AgentDisabledBySystemPropertyTest.java +++ b/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AgentDisabledBySystemPropertyTest.java @@ -14,7 +14,7 @@ public class AgentDisabledBySystemPropertyTest extends AbstractAttachmentTest { @Test void shouldNotAttachWhenAgentDisabledWithProperty() { - RuntimeAttach.attachJavaagentToCurrentJVM(); + RuntimeAttach.attachJavaagentToCurrentJvm(); verifyNoAttachment(); } diff --git a/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/RunTimeAttachBasicTest.java b/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/RunTimeAttachBasicTest.java index 3f0436472..5d8f201ae 100644 --- a/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/RunTimeAttachBasicTest.java +++ b/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/RunTimeAttachBasicTest.java @@ -14,7 +14,7 @@ public class RunTimeAttachBasicTest extends AbstractAttachmentTest { @Test void shouldAttach() { - RuntimeAttach.attachJavaagentToCurrentJVM(); + RuntimeAttach.attachJavaagentToCurrentJvm(); verifyAttachment(); } diff --git a/settings.gradle.kts b/settings.gradle.kts index 97ba5e61e..e68374f54 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,7 +1,7 @@ pluginManagement { plugins { id("com.github.johnrengelman.shadow") version "8.1.1" - id("com.gradle.enterprise") version "3.15" + id("com.gradle.enterprise") version "3.16" id("io.github.gradle-nexus.publish-plugin") version "1.3.0" } } diff --git a/static-instrumenter/agent-instrumenter/build.gradle.kts b/static-instrumenter/agent-instrumenter/build.gradle.kts index e0671c521..eee36f9d1 100644 --- a/static-instrumenter/agent-instrumenter/build.gradle.kts +++ b/static-instrumenter/agent-instrumenter/build.gradle.kts @@ -142,6 +142,5 @@ class AgentJarsProvider( @PathSensitive(PathSensitivity.RELATIVE) val noInstAgentJar: Provider, ) : CommandLineArgumentProvider { - override fun asArguments(): Iterable = - listOf("-Dagent=${file(agentJar).path}", "-Dno.inst.agent=${file(noInstAgentJar).path}") + override fun asArguments(): Iterable = listOf("-Dagent=${file(agentJar).path}", "-Dno.inst.agent=${file(noInstAgentJar).path}") } diff --git a/static-instrumenter/maven-plugin/build.gradle.kts b/static-instrumenter/maven-plugin/build.gradle.kts index 8777917ec..48c7004bd 100644 --- a/static-instrumenter/maven-plugin/build.gradle.kts +++ b/static-instrumenter/maven-plugin/build.gradle.kts @@ -17,11 +17,11 @@ otelJava { dependencies { implementation("org.apache.maven:maven-plugin-api:3.5.0") // do not auto-update this version implementation("org.apache.maven:maven-project:2.2.1") - compileOnly("org.apache.maven.plugin-tools:maven-plugin-annotations:3.9.0") + compileOnly("org.apache.maven.plugin-tools:maven-plugin-annotations:3.10.2") compileOnly("org.apache.maven:maven-core:3.5.0") // do not auto-update this version compileOnly("org.slf4j:slf4j-api") - testImplementation("org.apache.maven.plugin-tools:maven-plugin-annotations:3.9.0") + testImplementation("org.apache.maven.plugin-tools:maven-plugin-annotations:3.10.2") testImplementation("org.apache.maven:maven-core:3.5.0") testImplementation("org.slf4j:slf4j-simple") } diff --git a/static-instrumenter/maven-plugin/src/main/java/io/opentelemetry/contrib/staticinstrumenter/plugin/maven/ZipEntryCreator.java b/static-instrumenter/maven-plugin/src/main/java/io/opentelemetry/contrib/staticinstrumenter/plugin/maven/ZipEntryCreator.java index aee90eb75..52e81104a 100644 --- a/static-instrumenter/maven-plugin/src/main/java/io/opentelemetry/contrib/staticinstrumenter/plugin/maven/ZipEntryCreator.java +++ b/static-instrumenter/maven-plugin/src/main/java/io/opentelemetry/contrib/staticinstrumenter/plugin/maven/ZipEntryCreator.java @@ -24,6 +24,14 @@ static void moveEntryUpdating( FileSystem targetFs, String targetPath, JarEntry sourceEntry, JarFile sourceJar) throws IOException { + if (targetPath.equals( + "META-INF/services/io.opentelemetry.javaagent.tooling.BeforeAgentListener")) { + // TEMPORARY hack to make things pass after a BeforeAgentListener was added in the agent + // in https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/9301 + // which then causes conflict with the BeforeAgentListener in this module + return; + } + Path entry = targetFs.getPath("/", targetPath); Files.createDirectories(entry.getParent()); try (InputStream sourceInput = sourceJar.getInputStream(sourceEntry)) { diff --git a/version.gradle.kts b/version.gradle.kts index a7e1145a2..66e2a5677 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -1,5 +1,5 @@ -val stableVersion = "1.31.0-SNAPSHOT" -val alphaVersion = "1.31.0-alpha-SNAPSHOT" +val stableVersion = "1.33.0-SNAPSHOT" +val alphaVersion = "1.33.0-alpha-SNAPSHOT" allprojects { if (findProperty("otel.stable") != "true") {