diff --git a/smoke-tests/build.gradle.kts b/smoke-tests/build.gradle.kts index 4a14fe7ca22b..dbbb0e4882bf 100644 --- a/smoke-tests/build.gradle.kts +++ b/smoke-tests/build.gradle.kts @@ -37,8 +37,9 @@ dependencies { testImplementation("com.github.docker-java:docker-java-core:$dockerJavaVersion") testImplementation("com.github.docker-java:docker-java-transport-httpclient5:$dockerJavaVersion") - // make IntelliJ see shaded Armeria + // make IntelliJ see shaded Armeria and protobuf testCompileOnly(project(":testing:armeria-shaded-for-testing", configuration = "shadow")) + testCompileOnly(project(":testing:proto-shaded-for-testing", configuration = "shadow")) } tasks { diff --git a/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/AgentDebugLoggingTest.groovy b/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/AgentDebugLoggingTest.groovy deleted file mode 100644 index ab3572de81c3..000000000000 --- a/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/AgentDebugLoggingTest.groovy +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.smoketest - -import spock.lang.IgnoreIf - -import java.time.Duration - -import static io.opentelemetry.smoketest.TestContainerManager.useWindowsContainers - -@IgnoreIf({ useWindowsContainers() }) -class AgentDebugLoggingTest extends SmokeTest { - - protected String getTargetImage(String jdk) { - "ghcr.io/open-telemetry/opentelemetry-java-instrumentation/smoke-test-spring-boot:jdk$jdk-20211213.1570880324" - } - - @Override - protected TargetWaitStrategy getWaitStrategy() { - return new TargetWaitStrategy.Log(Duration.ofMinutes(1), ".*DEBUG io.opentelemetry.javaagent.tooling.VersionLogger.*") - } - - def "verify that debug logging is working"() { - setup: - startTarget(8) - - cleanup: - stopTarget() - } -} diff --git a/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/LogsSmokeTest.groovy b/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/LogsSmokeTest.groovy deleted file mode 100644 index 4818a056936b..000000000000 --- a/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/LogsSmokeTest.groovy +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.smoketest - -import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest -import spock.lang.IgnoreIf - -import java.time.Duration - -import static io.opentelemetry.smoketest.TestContainerManager.useWindowsContainers -import static java.util.stream.Collectors.toList - -@IgnoreIf({ useWindowsContainers() }) -class LogsSmokeTest extends SmokeTest { - - protected String getTargetImage(String jdk) { - "ghcr.io/open-telemetry/opentelemetry-java-instrumentation/smoke-test-spring-boot:jdk$jdk-20211213.1570880324" - } - - @Override - protected TargetWaitStrategy getWaitStrategy() { - return new TargetWaitStrategy.Log(Duration.ofMinutes(1), ".*Started SpringbootApplication in.*") - } - - def "Should export logs"(int jdk) { - setup: - startTarget(jdk) - - when: - client().get("/greeting").aggregate().join() - Collection logs = waitForLogs() - - then: - logs.size() > 0 - - def logRecords = logs.stream() - .flatMap(log -> log.getResourceLogsList().stream()) - .flatMap(log -> log.getScopeLogsList().stream()) - .flatMap(log -> log.getLogRecordsList().stream()) - .collect(toList()) - logRecords.size() >= logs.size() - - cleanup: - stopTarget() - - where: - jdk << [8, 11, 17] - - } - -} diff --git a/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/QuarkusSmokeTest.groovy b/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/QuarkusSmokeTest.groovy deleted file mode 100644 index 6009973dbdcd..000000000000 --- a/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/QuarkusSmokeTest.groovy +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.smoketest - -import io.opentelemetry.semconv.ServiceAttributes -import spock.lang.IgnoreIf -import spock.lang.Unroll - -import java.time.Duration -import java.util.jar.Attributes -import java.util.jar.JarFile - -import static io.opentelemetry.smoketest.TestContainerManager.useWindowsContainers - -@IgnoreIf({ useWindowsContainers() }) -class QuarkusSmokeTest extends SmokeTest { - - protected String getTargetImage(String jdk) { - "ghcr.io/open-telemetry/opentelemetry-java-instrumentation/smoke-test-quarkus:jdk$jdk-20241105.11678591860" - } - - @Override - protected TargetWaitStrategy getWaitStrategy() { - return new TargetWaitStrategy.Log(Duration.ofMinutes(1), ".*Listening on.*") - } - - @Override - protected boolean getSetServiceName() { - return false - } - - @Unroll - def "quarkus smoke test on JDK #jdk"(int jdk) { - setup: - startTarget(jdk) - - def currentAgentVersion = new JarFile(agentPath).getManifest().getMainAttributes().get(Attributes.Name.IMPLEMENTATION_VERSION) - - when: - client().get("/hello").aggregate().join() - TraceInspector traces = new TraceInspector(waitForTraces()) - - then: "Expected span names" - traces.countSpansByName('GET /hello') == 1 - - and: "telemetry.distro.version is set" - traces.countFilteredResourceAttributes("telemetry.distro.version", currentAgentVersion) == 1 - - and: "service.name is detected from manifest" - traces.countFilteredResourceAttributes(ServiceAttributes.SERVICE_NAME.key, "quarkus") == 1 - - cleanup: - stopTarget() - - where: - jdk << [17, 21, 23] // Quarkus 3.7+ requires Java 17+ - } -} diff --git a/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/SecurityManagerSmokeTest.groovy b/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/SecurityManagerSmokeTest.groovy deleted file mode 100644 index 268b68f1b081..000000000000 --- a/smoke-tests/src/test/groovy/io/opentelemetry/smoketest/SecurityManagerSmokeTest.groovy +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.smoketest - -import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest -import spock.lang.IgnoreIf -import spock.lang.Unroll - -import static io.opentelemetry.smoketest.TestContainerManager.useWindowsContainers - -@IgnoreIf({ useWindowsContainers() }) -class SecurityManagerSmokeTest extends SmokeTest { - - @Override - protected String getTargetImage(String jdk) { - "ghcr.io/open-telemetry/opentelemetry-java-instrumentation/smoke-test-security-manager:jdk$jdk-20241021.11448062560" - } - - @Override - protected Map getExtraEnv() { - return Collections.singletonMap("OTEL_JAVAAGENT_EXPERIMENTAL_SECURITY_MANAGER_SUPPORT_ENABLED", "true") - } - - @Unroll - def "security manager smoke test on JDK #jdk"(int jdk) { - setup: - startTarget(jdk) - - expect: - Collection traces = waitForTraces() - countSpansByName(traces, 'test') == 1 - - cleanup: - stopTarget() - - where: - jdk << [8, 11, 17, 21, 23] - } -} diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/AgentDebugLoggingTest.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/AgentDebugLoggingTest.java new file mode 100644 index 000000000000..3e881f6fb41c --- /dev/null +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/AgentDebugLoggingTest.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.smoketest; + +import java.time.Duration; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; + +@DisabledIf("io.opentelemetry.smoketest.TestContainerManager#useWindowsContainers") +class AgentDebugLoggingTest extends JavaSmokeTest { + @Override + protected String getTargetImage(String jdk) { + return "ghcr.io/open-telemetry/opentelemetry-java-instrumentation/smoke-test-spring-boot:jdk" + + jdk + + "-20250915.17728045097"; + } + + @Override + protected TargetWaitStrategy getWaitStrategy() { + return new TargetWaitStrategy.Log( + Duration.ofMinutes(1), ".*DEBUG io.opentelemetry.javaagent.tooling.VersionLogger.*"); + } + + @DisplayName("verifies that debug logging is working by checking for a debug log on startup") + @Test + void verifyLogging() throws Exception { + withTarget(8, () -> {}); + } +} diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/JavaSmokeTest.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/JavaSmokeTest.java new file mode 100644 index 000000000000..9c4f23053447 --- /dev/null +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/JavaSmokeTest.java @@ -0,0 +1,135 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.smoketest; + +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; + +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.smoketest.windows.WindowsTestContainerManager; +import io.opentelemetry.testing.internal.armeria.client.WebClient; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import org.junit.jupiter.api.BeforeEach; +import org.testcontainers.containers.output.OutputFrame; + +public abstract class JavaSmokeTest { + protected static final TestContainerManager containerManager = createContainerManager(); + private JavaTelemetryRetriever telemetryRetriever; + + protected String agentPath = + System.getProperty("io.opentelemetry.smoketest.agent.shadowJar.path"); + + protected WebClient client() { + return WebClient.of("h1c://localhost:" + containerManager.getTargetMappedPort(8080)); + } + + /** Subclasses can override this method to pass jvm arguments in another environment variable */ + protected String getJvmArgsEnvVarName() { + return "JAVA_TOOL_OPTIONS"; + } + + /** Subclasses can override this method to customise target application's environment */ + protected Map getExtraEnv() { + return emptyMap(); + } + + /** Subclasses can override this method to disable setting default service name */ + protected boolean getSetServiceName() { + return true; + } + + /** Subclasses can override this method to provide additional files to copy to target container */ + protected List getExtraResources() { + return emptyList(); + } + + /** + * Subclasses can override this method to provide additional ports that should be exposed from the + * target container + */ + protected List getExtraPorts() { + return emptyList(); + } + + @BeforeEach + void setUp() { + containerManager.startEnvironmentOnce(); + telemetryRetriever = new JavaTelemetryRetriever(containerManager.getBackendMappedPort()); + } + + public void withTarget(int jdk, TargetRunner runner) throws Exception { + startTarget(jdk); + try { + runner.runInTarget(); + } finally { + stopTarget(); + } + } + + public Consumer startTarget(int jdk) { + return startTarget(String.valueOf(jdk), null, false); + } + + public Consumer startTarget(String jdk, String serverVersion, boolean windows) { + String targetImage = getTargetImage(jdk, serverVersion, windows); + return containerManager.startTarget( + targetImage, + agentPath, + getJvmArgsEnvVarName(), + getExtraEnv(), + getSetServiceName(), + getExtraResources(), + getExtraPorts(), + getWaitStrategy(), + getCommand()); + } + + protected abstract String getTargetImage(String jdk); + + protected String getTargetImage(String jdk, String serverVersion, boolean windows) { + return getTargetImage(jdk); + } + + protected TargetWaitStrategy getWaitStrategy() { + return null; + } + + protected String[] getCommand() { + return null; + } + + public void cleanup() { + telemetryRetriever.clearTelemetry(); + } + + public void stopTarget() { + containerManager.stopTarget(); + cleanup(); + } + + protected List waitForTraces() { + return telemetryRetriever.waitForTraces(); + } + + protected Collection waitForMetrics() { + return telemetryRetriever.waitForMetrics(); + } + + protected Collection waitForLogs() { + return telemetryRetriever.waitForLogs(); + } + + private static TestContainerManager createContainerManager() { + return TestContainerManager.useWindowsContainers() + ? new WindowsTestContainerManager() + : new LinuxTestContainerManager(); + } +} diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/JavaTelemetryRetriever.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/JavaTelemetryRetriever.java new file mode 100644 index 000000000000..3670c089c70f --- /dev/null +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/JavaTelemetryRetriever.java @@ -0,0 +1,113 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.smoketest; + +import io.opentelemetry.instrumentation.testing.internal.TelemetryConverter; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.testing.internal.armeria.client.WebClient; +import io.opentelemetry.testing.internal.jackson.core.JsonProcessingException; +import io.opentelemetry.testing.internal.jackson.databind.ObjectMapper; +import io.opentelemetry.testing.internal.proto.collector.logs.v1.ExportLogsServiceRequest; +import io.opentelemetry.testing.internal.proto.collector.metrics.v1.ExportMetricsServiceRequest; +import io.opentelemetry.testing.internal.proto.collector.trace.v1.ExportTraceServiceRequest; +import io.opentelemetry.testing.internal.protobuf.GeneratedMessage; +import io.opentelemetry.testing.internal.protobuf.InvalidProtocolBufferException; +import io.opentelemetry.testing.internal.protobuf.util.JsonFormat; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class JavaTelemetryRetriever { + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private final WebClient client; + + public JavaTelemetryRetriever(int backendPort) { + client = WebClient.of("http://localhost:" + backendPort); + } + + public void clearTelemetry() { + client.get("/clear").aggregate().join(); + } + + public List waitForTraces() { + Collection requests = + waitForTelemetry("get-traces", ExportTraceServiceRequest::newBuilder); + return TelemetryConverter.getSpanData( + convert(requests, ExportTraceServiceRequest::getResourceSpansList)); + } + + public Collection waitForMetrics() { + Collection requests = + waitForTelemetry("get-metrics", ExportMetricsServiceRequest::newBuilder); + return TelemetryConverter.getMetricsData( + convert(requests, ExportMetricsServiceRequest::getResourceMetricsList)); + } + + public Collection waitForLogs() { + Collection requests = + waitForTelemetry("get-logs", ExportLogsServiceRequest::newBuilder); + return TelemetryConverter.getLogRecordData( + convert(requests, ExportLogsServiceRequest::getResourceLogsList)); + } + + private static List convert(Collection items, Function> converter) { + return items.stream() + .flatMap(item -> converter.apply(item).stream()) + .collect(Collectors.toList()); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private + Collection waitForTelemetry(String path, Supplier builderConstructor) { + try { + return OBJECT_MAPPER + .readTree(waitForContent(path)) + .valueStream() + .map( + jsonNode -> { + B builder = builderConstructor.get(); + // TODO: Register parser into object mapper to avoid de -> re -> deserialize. + try { + JsonFormat.parser().merge(OBJECT_MAPPER.writeValueAsString(jsonNode), builder); + return (T) builder.build(); + } catch (InvalidProtocolBufferException | JsonProcessingException e) { + throw new IllegalStateException(e); + } + }) + .collect(Collectors.toList()); + } catch (InterruptedException | JsonProcessingException e) { + throw new IllegalStateException(e); + } + } + + @SuppressWarnings("SystemOut") + private String waitForContent(String path) throws InterruptedException { + long previousSize = 0; + long deadline = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(30); + String content = "[]"; + while (System.currentTimeMillis() < deadline) { + content = client.get(path).aggregate().join().contentUtf8(); + if (content.length() > 2 && content.length() == previousSize) { + break; + } + + previousSize = content.length(); + System.out.println("Current content size " + previousSize); + TimeUnit.MILLISECONDS.sleep(500); + } + + return content; + } + + public final WebClient getClient() { + return client; + } +} diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/LogsSmokeTest.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/LogsSmokeTest.java new file mode 100644 index 000000000000..c49fb4311bf8 --- /dev/null +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/LogsSmokeTest.java @@ -0,0 +1,44 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.smoketest; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.sdk.logs.data.LogRecordData; +import java.time.Duration; +import java.util.Collection; +import org.junit.jupiter.api.condition.DisabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +@DisabledIf("io.opentelemetry.smoketest.TestContainerManager#useWindowsContainers") +class LogsSmokeTest extends JavaSmokeTest { + @Override + protected String getTargetImage(String jdk) { + return "ghcr.io/open-telemetry/opentelemetry-java-instrumentation/smoke-test-spring-boot:jdk" + + jdk + + "-20211213.1570880324"; + } + + @Override + protected TargetWaitStrategy getWaitStrategy() { + return new TargetWaitStrategy.Log( + Duration.ofMinutes(1), ".*Started SpringbootApplication in.*"); + } + + @ParameterizedTest + @ValueSource(ints = {8, 11, 17}) + void shouldExportLogs(int jdk) throws Exception { + withTarget( + jdk, + () -> { + client().get("/greeting").aggregate().join(); + Collection logs = waitForLogs(); + + assertThat(logs).isNotEmpty(); + }); + } +} diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/QuarkusSmokeTest.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/QuarkusSmokeTest.java new file mode 100644 index 000000000000..c2ab2a37f693 --- /dev/null +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/QuarkusSmokeTest.java @@ -0,0 +1,73 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.smoketest; + +import static io.opentelemetry.sdk.testing.assertj.TracesAssert.assertThat; + +import io.opentelemetry.semconv.ServiceAttributes; +import io.opentelemetry.semconv.incubating.TelemetryIncubatingAttributes; +import java.time.Duration; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import org.junit.jupiter.api.condition.DisabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +@DisabledIf("io.opentelemetry.smoketest.TestContainerManager#useWindowsContainers") +class QuarkusSmokeTest extends JavaSmokeTest { + @Override + protected String getTargetImage(String jdk) { + return "ghcr.io/open-telemetry/opentelemetry-java-instrumentation/smoke-test-quarkus:jdk" + + jdk + + "-20250915.17728045126"; + } + + @Override + protected TargetWaitStrategy getWaitStrategy() { + return new TargetWaitStrategy.Log(Duration.ofMinutes(1), ".*Listening on.*"); + } + + @Override + protected boolean getSetServiceName() { + return false; + } + + @ParameterizedTest + @ValueSource(ints = {17, 21, 23}) // Quarkus 3.7+ requires Java 17+ + void quarkusSmokeTest(int jdk) throws Exception { + withTarget( + jdk, + () -> { + String currentAgentVersion; + try (JarFile agentJar = new JarFile(agentPath)) { + currentAgentVersion = + agentJar + .getManifest() + .getMainAttributes() + .getValue(Attributes.Name.IMPLEMENTATION_VERSION); + } + + client().get("/hello").aggregate().join(); + + assertThat(waitForTraces()) + .hasTracesSatisfyingExactly( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET /hello") + .hasResourceSatisfying( + resource -> { + resource + .hasAttribute( + TelemetryIncubatingAttributes + .TELEMETRY_DISTRO_VERSION, + currentAgentVersion) + .hasAttribute( + ServiceAttributes.SERVICE_NAME, "quarkus"); + }))); + }); + } +} diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/SecurityManagerSmokeTest.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/SecurityManagerSmokeTest.java new file mode 100644 index 000000000000..216255628f65 --- /dev/null +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/SecurityManagerSmokeTest.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.smoketest; + +import static io.opentelemetry.sdk.testing.assertj.TracesAssert.assertThat; + +import java.util.Collections; +import java.util.Map; +import org.junit.jupiter.api.condition.DisabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +@DisabledIf("io.opentelemetry.smoketest.TestContainerManager#useWindowsContainers") +class SecurityManagerSmokeTest extends JavaSmokeTest { + @Override + protected String getTargetImage(String jdk) { + return "ghcr.io/open-telemetry/opentelemetry-java-instrumentation/smoke-test-security-manager:jdk" + + jdk + + "-20250915.17728045123"; + } + + @Override + protected Map getExtraEnv() { + return Collections.singletonMap( + "OTEL_JAVAAGENT_EXPERIMENTAL_SECURITY_MANAGER_SUPPORT_ENABLED", "true"); + } + + @ParameterizedTest + @ValueSource(ints = {8, 11, 17, 21, 23}) + void securityManagerSmokeTest(int jdk) throws Exception { + withTarget( + jdk, + () -> + assertThat(waitForTraces()) + .hasTracesSatisfyingExactly( + trace -> trace.hasSpansSatisfyingExactly(span -> span.hasName("test")))); + } +} diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/TargetRunner.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/TargetRunner.java new file mode 100644 index 000000000000..57ff7c14bf97 --- /dev/null +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/TargetRunner.java @@ -0,0 +1,11 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.smoketest; + +@FunctionalInterface +public interface TargetRunner { + void runInTarget() throws Exception; +} diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/TelemetryConverter.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/TelemetryConverter.java new file mode 100644 index 000000000000..0390ce6f78f2 --- /dev/null +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/TelemetryConverter.java @@ -0,0 +1,717 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.testing.internal; + +import static io.opentelemetry.api.common.AttributeKey.booleanArrayKey; +import static io.opentelemetry.api.common.AttributeKey.doubleArrayKey; +import static io.opentelemetry.api.common.AttributeKey.longArrayKey; +import static io.opentelemetry.api.common.AttributeKey.stringArrayKey; +import static java.util.stream.Collectors.toList; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.common.Value; +import io.opentelemetry.api.incubator.common.ExtendedAttributes; +import io.opentelemetry.api.incubator.common.ExtendedAttributesBuilder; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.api.trace.TraceStateBuilder; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.DoublePointData; +import io.opentelemetry.sdk.metrics.data.HistogramPointData; +import io.opentelemetry.sdk.metrics.data.LongPointData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.data.SummaryPointData; +import io.opentelemetry.sdk.metrics.data.ValueAtQuantile; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableDoublePointData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableGaugeData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableHistogramData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableHistogramPointData; +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.data.ImmutableSummaryData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableSummaryPointData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableValueAtQuantile; +import io.opentelemetry.sdk.testing.logs.TestLogRecordData; +import io.opentelemetry.sdk.testing.logs.internal.TestExtendedLogRecordData; +import io.opentelemetry.sdk.testing.trace.TestSpanData; +import io.opentelemetry.sdk.trace.data.EventData; +import io.opentelemetry.sdk.trace.data.LinkData; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.data.StatusData; +import io.opentelemetry.testing.internal.proto.common.v1.AnyValue; +import io.opentelemetry.testing.internal.proto.common.v1.ArrayValue; +import io.opentelemetry.testing.internal.proto.common.v1.InstrumentationScope; +import io.opentelemetry.testing.internal.proto.common.v1.KeyValue; +import io.opentelemetry.testing.internal.proto.common.v1.KeyValueList; +import io.opentelemetry.testing.internal.proto.logs.v1.LogRecord; +import io.opentelemetry.testing.internal.proto.logs.v1.ResourceLogs; +import io.opentelemetry.testing.internal.proto.logs.v1.ScopeLogs; +import io.opentelemetry.testing.internal.proto.logs.v1.SeverityNumber; +import io.opentelemetry.testing.internal.proto.metrics.v1.HistogramDataPoint; +import io.opentelemetry.testing.internal.proto.metrics.v1.Metric; +import io.opentelemetry.testing.internal.proto.metrics.v1.NumberDataPoint; +import io.opentelemetry.testing.internal.proto.metrics.v1.ResourceMetrics; +import io.opentelemetry.testing.internal.proto.metrics.v1.ScopeMetrics; +import io.opentelemetry.testing.internal.proto.metrics.v1.Sum; +import io.opentelemetry.testing.internal.proto.metrics.v1.SummaryDataPoint; +import io.opentelemetry.testing.internal.proto.resource.v1.Resource; +import io.opentelemetry.testing.internal.proto.trace.v1.ResourceSpans; +import io.opentelemetry.testing.internal.proto.trace.v1.ScopeSpans; +import io.opentelemetry.testing.internal.proto.trace.v1.Span; +import io.opentelemetry.testing.internal.proto.trace.v1.Status; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class TelemetryConverter { + private static final char TRACESTATE_KEY_VALUE_DELIMITER = '='; + private static final char TRACESTATE_ENTRY_DELIMITER = ','; + private static final Pattern TRACESTATE_ENTRY_DELIMITER_SPLIT_PATTERN = + Pattern.compile("[ \t]*" + TRACESTATE_ENTRY_DELIMITER + "[ \t]*"); + + // opentelemetry-api-1.27:javaagent tests use an older version of opentelemetry-api where Value + // class is missing + private static final boolean canUseValue = classAvailable("io.opentelemetry.api.common.Value"); + private static final boolean hasExtendedLogRecordData = + classAvailable("io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData"); + private static final boolean hasExtendedAttributes = + classAvailable("io.opentelemetry.api.incubator.common.ExtendedAttributes"); + + public static List getSpanData(Collection allResourceSpans) { + List spans = new ArrayList<>(); + for (ResourceSpans resourceSpans : allResourceSpans) { + Resource resource = resourceSpans.getResource(); + for (ScopeSpans ilSpans : resourceSpans.getScopeSpansList()) { + InstrumentationScope instrumentationScope = ilSpans.getScope(); + for (Span span : ilSpans.getSpansList()) { + String traceId = bytesToHex(span.getTraceId().toByteArray()); + spans.add( + TestSpanData.builder() + .setSpanContext( + SpanContext.create( + traceId, + bytesToHex(span.getSpanId().toByteArray()), + TraceFlags.getDefault(), + extractTraceState(span.getTraceState()))) + // TODO is it ok to use default trace flags and default trace state here? + .setParentSpanContext( + SpanContext.create( + traceId, + bytesToHex(span.getParentSpanId().toByteArray()), + TraceFlags.getDefault(), + TraceState.getDefault())) + .setResource( + io.opentelemetry.sdk.resources.Resource.create( + fromProto(resource.getAttributesList()))) + .setInstrumentationScopeInfo( + InstrumentationScopeInfo.builder(instrumentationScope.getName()) + // emptyToNull since they are the same at protobuf layer, + // and allows for simpler verification of InstrumentationScope + .setVersion(emptyToNull(instrumentationScope.getVersion())) + .build()) + .setName(span.getName()) + .setStartEpochNanos(span.getStartTimeUnixNano()) + .setEndEpochNanos(span.getEndTimeUnixNano()) + .setAttributes(fromProto(span.getAttributesList())) + .setEvents( + span.getEventsList().stream() + .map( + event -> + EventData.create( + event.getTimeUnixNano(), + event.getName(), + fromProto(event.getAttributesList()), + event.getDroppedAttributesCount() + + event.getAttributesCount())) + .collect(toList())) + .setStatus(fromProto(span.getStatus())) + .setKind(fromProto(span.getKind())) + .setLinks( + span.getLinksList().stream() + .map( + link -> + LinkData.create( + SpanContext.create( + bytesToHex(link.getTraceId().toByteArray()), + bytesToHex(link.getSpanId().toByteArray()), + TraceFlags.getDefault(), + extractTraceState(link.getTraceState())), + fromProto(link.getAttributesList()), + link.getDroppedAttributesCount() + link.getAttributesCount())) + .collect(toList())) + // OTLP doesn't have hasRemoteParent + .setHasEnded(true) + .setTotalRecordedEvents(span.getEventsCount() + span.getDroppedEventsCount()) + .setTotalRecordedLinks(span.getLinksCount() + span.getDroppedLinksCount()) + .setTotalAttributeCount( + span.getAttributesCount() + span.getDroppedAttributesCount()) + .build()); + } + } + } + return spans; + } + + public static List getMetricsData(Collection allResourceMetrics) { + List metrics = new ArrayList<>(); + for (ResourceMetrics resourceMetrics : allResourceMetrics) { + Resource resource = resourceMetrics.getResource(); + for (ScopeMetrics ilMetrics : resourceMetrics.getScopeMetricsList()) { + InstrumentationScope instrumentationScope = ilMetrics.getScope(); + for (Metric metric : ilMetrics.getMetricsList()) { + metrics.add( + createMetricData( + metric, + io.opentelemetry.sdk.resources.Resource.create( + fromProto(resource.getAttributesList())), + InstrumentationScopeInfo.builder(instrumentationScope.getName()) + // emptyToNull since they are the same at protobuf layer, + // and allows for simpler verification of InstrumentationScope + .setVersion(emptyToNull(instrumentationScope.getVersion())) + .build())); + } + } + } + return metrics; + } + + public static List getLogRecordData(List allResourceLogs) { + List logs = new ArrayList<>(); + for (ResourceLogs resourceLogs : allResourceLogs) { + Resource resource = resourceLogs.getResource(); + for (ScopeLogs ilLogs : resourceLogs.getScopeLogsList()) { + InstrumentationScope instrumentationScope = ilLogs.getScope(); + for (LogRecord logRecord : ilLogs.getLogRecordsList()) { + logs.add( + createLogData( + logRecord, + io.opentelemetry.sdk.resources.Resource.create( + fromProto(resource.getAttributesList())), + InstrumentationScopeInfo.builder(instrumentationScope.getName()) + // emptyToNull since they are the same at protobuf layer, + // and allows for simpler verification of InstrumentationScope + .setVersion(emptyToNull(instrumentationScope.getVersion())) + .build())); + } + } + } + return logs; + } + + private static MetricData createMetricData( + Metric metric, + io.opentelemetry.sdk.resources.Resource resource, + InstrumentationScopeInfo instrumentationScopeInfo) { + switch (metric.getDataCase()) { + case GAUGE: + if (isDouble(metric.getGauge().getDataPointsList())) { + return ImmutableMetricData.createDoubleGauge( + resource, + instrumentationScopeInfo, + metric.getName(), + metric.getDescription(), + metric.getUnit(), + // TODO: Remove usages of internal types. + ImmutableGaugeData.create( + getDoublePointDatas(metric.getGauge().getDataPointsList()))); + } else { + return ImmutableMetricData.createLongGauge( + resource, + instrumentationScopeInfo, + metric.getName(), + metric.getDescription(), + metric.getUnit(), + ImmutableGaugeData.create(getLongPointDatas(metric.getGauge().getDataPointsList()))); + } + case SUM: + if (isDouble(metric.getSum().getDataPointsList())) { + Sum doubleSum = metric.getSum(); + return ImmutableMetricData.createDoubleSum( + resource, + instrumentationScopeInfo, + metric.getName(), + metric.getDescription(), + metric.getUnit(), + ImmutableSumData.create( + doubleSum.getIsMonotonic(), + getTemporality(doubleSum.getAggregationTemporality()), + getDoublePointDatas(metric.getSum().getDataPointsList()))); + } else { + Sum longSum = metric.getSum(); + return ImmutableMetricData.createLongSum( + resource, + instrumentationScopeInfo, + metric.getName(), + metric.getDescription(), + metric.getUnit(), + ImmutableSumData.create( + longSum.getIsMonotonic(), + getTemporality(longSum.getAggregationTemporality()), + getLongPointDatas(metric.getSum().getDataPointsList()))); + } + case HISTOGRAM: + return ImmutableMetricData.createDoubleHistogram( + resource, + instrumentationScopeInfo, + metric.getName(), + metric.getDescription(), + metric.getUnit(), + ImmutableHistogramData.create( + getTemporality(metric.getHistogram().getAggregationTemporality()), + getDoubleHistogramDataPoints(metric.getHistogram().getDataPointsList()))); + case SUMMARY: + return ImmutableMetricData.createDoubleSummary( + resource, + instrumentationScopeInfo, + metric.getName(), + metric.getDescription(), + metric.getUnit(), + ImmutableSummaryData.create( + getDoubleSummaryDataPoints(metric.getSummary().getDataPointsList()))); + default: + throw new AssertionError("Unexpected metric data: " + metric.getDataCase()); + } + } + + private static LogRecordData createLogData( + LogRecord logRecord, + io.opentelemetry.sdk.resources.Resource resource, + InstrumentationScopeInfo instrumentationScopeInfo) { + if (hasExtendedLogRecordData) { + return createExtendedLogData(logRecord, resource, instrumentationScopeInfo); + } + TestLogRecordData.Builder builder = + TestLogRecordData.builder() + .setResource(resource) + .setInstrumentationScopeInfo(instrumentationScopeInfo) + .setTimestamp(logRecord.getTimeUnixNano(), TimeUnit.NANOSECONDS) + .setSpanContext( + SpanContext.create( + bytesToHex(logRecord.getTraceId().toByteArray()), + bytesToHex(logRecord.getSpanId().toByteArray()), + TraceFlags.getDefault(), + TraceState.getDefault())) + .setSeverity(fromProto(logRecord.getSeverityNumber())) + .setSeverityText(logRecord.getSeverityText()) + .setAttributes(fromProto(logRecord.getAttributesList())); + if (canUseValue) { + builder.setBodyValue(getBodyValue(logRecord.getBody())); + } else { + builder.setBody(logRecord.getBody().getStringValue()); + } + return builder.build(); + } + + private static LogRecordData createExtendedLogData( + LogRecord logRecord, + io.opentelemetry.sdk.resources.Resource resource, + InstrumentationScopeInfo instrumentationScopeInfo) { + TestExtendedLogRecordData.Builder builder = + TestExtendedLogRecordData.builder() + .setResource(resource) + .setInstrumentationScopeInfo(instrumentationScopeInfo) + .setTimestamp(logRecord.getTimeUnixNano(), TimeUnit.NANOSECONDS) + .setSpanContext( + SpanContext.create( + bytesToHex(logRecord.getTraceId().toByteArray()), + bytesToHex(logRecord.getSpanId().toByteArray()), + TraceFlags.getDefault(), + TraceState.getDefault())) + .setSeverity(fromProto(logRecord.getSeverityNumber())) + .setSeverityText(logRecord.getSeverityText()) + .setEventName(logRecord.getEventName()) + .setBodyValue(getBodyValue(logRecord.getBody())); + if (hasExtendedAttributes) { + builder.setExtendedAttributes(fromProtoExtended(logRecord.getAttributesList())); + } else { + builder.setAttributes(fromProto(logRecord.getAttributesList())); + } + + return builder.build(); + } + + private static Value getBodyValue(AnyValue value) { + switch (value.getValueCase()) { + case STRING_VALUE: + return Value.of(value.getStringValue()); + case BOOL_VALUE: + return Value.of(value.getBoolValue()); + case INT_VALUE: + return Value.of(value.getIntValue()); + case DOUBLE_VALUE: + return Value.of(value.getDoubleValue()); + case ARRAY_VALUE: + ArrayValue array = value.getArrayValue(); + List> convertedValues = new ArrayList<>(); + for (int i = 0; i < array.getValuesCount(); i++) { + convertedValues.add(getBodyValue(array.getValues(i))); + } + return Value.of(convertedValues); + case KVLIST_VALUE: + KeyValueList keyValueList = value.getKvlistValue(); + io.opentelemetry.api.common.KeyValue[] convertedKeyValueList = + new io.opentelemetry.api.common.KeyValue[keyValueList.getValuesCount()]; + for (int i = 0; i < keyValueList.getValuesCount(); i++) { + KeyValue keyValue = keyValueList.getValues(i); + convertedKeyValueList[i] = + io.opentelemetry.api.common.KeyValue.of( + keyValue.getKey(), getBodyValue(keyValue.getValue())); + } + return Value.of(convertedKeyValueList); + case BYTES_VALUE: + return Value.of(value.getBytesValue().toByteArray()); + case VALUE_NOT_SET: + return null; + } + throw new IllegalStateException("Unexpected attribute: " + value.getValueCase()); + } + + private static boolean isDouble(List points) { + if (points.isEmpty()) { + return true; + } + return points.get(0).getValueCase() == NumberDataPoint.ValueCase.AS_DOUBLE; + } + + private static List getDoublePointDatas(List points) { + return points.stream() + .map( + point -> { + double value; + switch (point.getValueCase()) { + case AS_INT: + value = point.getAsInt(); + break; + case AS_DOUBLE: + default: + value = point.getAsDouble(); + break; + } + return ImmutableDoublePointData.create( + point.getStartTimeUnixNano(), + point.getTimeUnixNano(), + fromProto(point.getAttributesList()), + value); + }) + .collect(toList()); + } + + private static List getLongPointDatas(List points) { + return points.stream() + .map( + point -> { + long value; + switch (point.getValueCase()) { + case AS_INT: + value = point.getAsInt(); + break; + case AS_DOUBLE: + default: + value = (long) point.getAsDouble(); + break; + } + return ImmutableLongPointData.create( + point.getStartTimeUnixNano(), + point.getTimeUnixNano(), + fromProto(point.getAttributesList()), + value); + }) + .collect(toList()); + } + + private static Collection getDoubleHistogramDataPoints( + List dataPointsList) { + return dataPointsList.stream() + .map( + point -> + ImmutableHistogramPointData.create( + point.getStartTimeUnixNano(), + point.getTimeUnixNano(), + fromProto(point.getAttributesList()), + point.getSum(), + point.hasMin(), + point.getMin(), + point.hasMax(), + point.getMax(), + point.getExplicitBoundsList(), + point.getBucketCountsList())) + .collect(toList()); + } + + private static Collection getDoubleSummaryDataPoints( + List dataPointsList) { + return dataPointsList.stream() + .map( + point -> + ImmutableSummaryPointData.create( + point.getStartTimeUnixNano(), + point.getTimeUnixNano(), + fromProto(point.getAttributesList()), + point.getCount(), + point.getSum(), + getValues(point))) + .collect(toList()); + } + + private static List getValues(SummaryDataPoint point) { + return point.getQuantileValuesList().stream() + .map(v -> ImmutableValueAtQuantile.create(v.getQuantile(), v.getValue())) + .collect(Collectors.toList()); + } + + private static AggregationTemporality getTemporality( + io.opentelemetry.testing.internal.proto.metrics.v1.AggregationTemporality + aggregationTemporality) { + switch (aggregationTemporality) { + case AGGREGATION_TEMPORALITY_CUMULATIVE: + return AggregationTemporality.CUMULATIVE; + case AGGREGATION_TEMPORALITY_DELTA: + return AggregationTemporality.DELTA; + default: + throw new IllegalStateException( + "Unexpected aggregation temporality: " + aggregationTemporality); + } + } + + private static ExtendedAttributes fromProtoExtended(List attributes) { + ExtendedAttributesBuilder converted = ExtendedAttributes.builder(); + for (KeyValue attribute : attributes) { + String key = attribute.getKey(); + AnyValue value = attribute.getValue(); + switch (value.getValueCase()) { + case STRING_VALUE: + converted.put(key, value.getStringValue()); + break; + case BOOL_VALUE: + converted.put(key, value.getBoolValue()); + break; + case INT_VALUE: + converted.put(key, value.getIntValue()); + break; + case DOUBLE_VALUE: + converted.put(key, value.getDoubleValue()); + break; + case ARRAY_VALUE: + ArrayValue array = value.getArrayValue(); + if (array.getValuesCount() != 0) { + switch (array.getValues(0).getValueCase()) { + case STRING_VALUE: + converted.put( + stringArrayKey(key), + array.getValuesList().stream().map(AnyValue::getStringValue).collect(toList())); + break; + case BOOL_VALUE: + converted.put( + booleanArrayKey(key), + array.getValuesList().stream().map(AnyValue::getBoolValue).collect(toList())); + break; + case INT_VALUE: + converted.put( + longArrayKey(key), + array.getValuesList().stream().map(AnyValue::getIntValue).collect(toList())); + break; + case DOUBLE_VALUE: + converted.put( + doubleArrayKey(key), + array.getValuesList().stream().map(AnyValue::getDoubleValue).collect(toList())); + break; + case VALUE_NOT_SET: + break; + default: + throw new IllegalStateException( + "Unexpected attribute: " + array.getValues(0).getValueCase()); + } + } + break; + case KVLIST_VALUE: + converted.put(key, fromProtoExtended(value.getKvlistValue().getValuesList())); + break; + case VALUE_NOT_SET: + break; + default: + throw new IllegalStateException("Unexpected attribute: " + value.getValueCase()); + } + } + return converted.build(); + } + + private static Attributes fromProto(List attributes) { + AttributesBuilder converted = Attributes.builder(); + for (KeyValue attribute : attributes) { + String key = attribute.getKey(); + AnyValue value = attribute.getValue(); + switch (value.getValueCase()) { + case STRING_VALUE: + converted.put(key, value.getStringValue()); + break; + case BOOL_VALUE: + converted.put(key, value.getBoolValue()); + break; + case INT_VALUE: + converted.put(key, value.getIntValue()); + break; + case DOUBLE_VALUE: + converted.put(key, value.getDoubleValue()); + break; + case ARRAY_VALUE: + ArrayValue array = value.getArrayValue(); + if (array.getValuesCount() != 0) { + switch (array.getValues(0).getValueCase()) { + case STRING_VALUE: + converted.put( + stringArrayKey(key), + array.getValuesList().stream().map(AnyValue::getStringValue).collect(toList())); + break; + case BOOL_VALUE: + converted.put( + booleanArrayKey(key), + array.getValuesList().stream().map(AnyValue::getBoolValue).collect(toList())); + break; + case INT_VALUE: + converted.put( + longArrayKey(key), + array.getValuesList().stream().map(AnyValue::getIntValue).collect(toList())); + break; + case DOUBLE_VALUE: + converted.put( + doubleArrayKey(key), + array.getValuesList().stream().map(AnyValue::getDoubleValue).collect(toList())); + break; + case VALUE_NOT_SET: + break; + default: + throw new IllegalStateException( + "Unexpected attribute: " + array.getValues(0).getValueCase()); + } + } + break; + case VALUE_NOT_SET: + break; + default: + throw new IllegalStateException("Unexpected attribute: " + value.getValueCase()); + } + } + return converted.build(); + } + + private static StatusData fromProto(Status status) { + StatusCode code; + switch (status.getCode()) { + case STATUS_CODE_OK: + code = StatusCode.OK; + break; + case STATUS_CODE_ERROR: + code = StatusCode.ERROR; + break; + default: + code = StatusCode.UNSET; + break; + } + return StatusData.create(code, status.getMessage()); + } + + private static SpanKind fromProto(Span.SpanKind kind) { + switch (kind) { + case SPAN_KIND_INTERNAL: + return SpanKind.INTERNAL; + case SPAN_KIND_SERVER: + return SpanKind.SERVER; + case SPAN_KIND_CLIENT: + return SpanKind.CLIENT; + case SPAN_KIND_PRODUCER: + return SpanKind.PRODUCER; + case SPAN_KIND_CONSUMER: + return SpanKind.CONSUMER; + default: + throw new IllegalArgumentException("Unexpected span kind: " + kind); + } + } + + private static Severity fromProto(SeverityNumber proto) { + for (Severity severity : Severity.values()) { + if (severity.getSeverityNumber() == proto.getNumber()) { + return severity; + } + } + throw new IllegalArgumentException("Unexpected SeverityNumber: " + proto); + } + + private static TraceState extractTraceState(String traceStateHeader) { + if (traceStateHeader.isEmpty()) { + return TraceState.getDefault(); + } + TraceStateBuilder traceStateBuilder = TraceState.builder(); + String[] listMembers = TRACESTATE_ENTRY_DELIMITER_SPLIT_PATTERN.split(traceStateHeader); + // Iterate in reverse order because when call builder set the elements is added in the + // front of the list. + for (int i = listMembers.length - 1; i >= 0; i--) { + String listMember = listMembers[i]; + int index = listMember.indexOf(TRACESTATE_KEY_VALUE_DELIMITER); + traceStateBuilder.put(listMember.substring(0, index), listMember.substring(index + 1)); + } + return traceStateBuilder.build(); + } + + private static String bytesToHex(byte[] bytes) { + char[] dest = new char[bytes.length * 2]; + bytesToBase16(bytes, dest); + return new String(dest); + } + + private static void bytesToBase16(byte[] bytes, char[] dest) { + for (int i = 0; i < bytes.length; i++) { + byteToBase16(bytes[i], dest, i * 2); + } + } + + private static void byteToBase16(byte value, char[] dest, int destOffset) { + int b = value & 0xFF; + dest[destOffset] = ENCODING[b]; + dest[destOffset + 1] = ENCODING[b | 0x100]; + } + + private static final String ALPHABET = "0123456789abcdef"; + private static final char[] ENCODING = buildEncodingArray(); + + private static char[] buildEncodingArray() { + char[] encoding = new char[512]; + for (int i = 0; i < 256; ++i) { + encoding[i] = ALPHABET.charAt(i >>> 4); + encoding[i | 0x100] = ALPHABET.charAt(i & 0xF); + } + return encoding; + } + + private static String emptyToNull(String string) { + return string == null || string.isEmpty() ? null : string; + } + + private static boolean classAvailable(String className) { + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + + private TelemetryConverter() {} +} diff --git a/testing-common/src/main/java/io/opentelemetry/javaagent/testing/common/AgentTestingExporterAccess.java b/testing-common/src/main/java/io/opentelemetry/javaagent/testing/common/AgentTestingExporterAccess.java index aaeb5d890b0e..54ea74035cb5 100644 --- a/testing-common/src/main/java/io/opentelemetry/javaagent/testing/common/AgentTestingExporterAccess.java +++ b/testing-common/src/main/java/io/opentelemetry/javaagent/testing/common/AgentTestingExporterAccess.java @@ -5,103 +5,29 @@ package io.opentelemetry.javaagent.testing.common; -import static io.opentelemetry.api.common.AttributeKey.booleanArrayKey; -import static io.opentelemetry.api.common.AttributeKey.doubleArrayKey; -import static io.opentelemetry.api.common.AttributeKey.longArrayKey; -import static io.opentelemetry.api.common.AttributeKey.stringArrayKey; import static java.util.stream.Collectors.toList; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.api.common.Value; -import io.opentelemetry.api.incubator.common.ExtendedAttributes; -import io.opentelemetry.api.incubator.common.ExtendedAttributesBuilder; -import io.opentelemetry.api.logs.Severity; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.TraceFlags; -import io.opentelemetry.api.trace.TraceState; -import io.opentelemetry.api.trace.TraceStateBuilder; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.instrumentation.testing.internal.TelemetryConverter; import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.metrics.data.AggregationTemporality; -import io.opentelemetry.sdk.metrics.data.DoublePointData; -import io.opentelemetry.sdk.metrics.data.HistogramPointData; -import io.opentelemetry.sdk.metrics.data.LongPointData; import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.metrics.data.SummaryPointData; -import io.opentelemetry.sdk.metrics.data.ValueAtQuantile; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableDoublePointData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableGaugeData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableHistogramData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableHistogramPointData; -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.data.ImmutableSummaryData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableSummaryPointData; -import io.opentelemetry.sdk.metrics.internal.data.ImmutableValueAtQuantile; -import io.opentelemetry.sdk.testing.logs.TestLogRecordData; -import io.opentelemetry.sdk.testing.logs.internal.TestExtendedLogRecordData; -import io.opentelemetry.sdk.testing.trace.TestSpanData; -import io.opentelemetry.sdk.trace.data.EventData; -import io.opentelemetry.sdk.trace.data.LinkData; import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.data.StatusData; import io.opentelemetry.testing.internal.proto.collector.logs.v1.ExportLogsServiceRequest; import io.opentelemetry.testing.internal.proto.collector.metrics.v1.ExportMetricsServiceRequest; import io.opentelemetry.testing.internal.proto.collector.trace.v1.ExportTraceServiceRequest; -import io.opentelemetry.testing.internal.proto.common.v1.AnyValue; -import io.opentelemetry.testing.internal.proto.common.v1.ArrayValue; -import io.opentelemetry.testing.internal.proto.common.v1.InstrumentationScope; -import io.opentelemetry.testing.internal.proto.common.v1.KeyValue; -import io.opentelemetry.testing.internal.proto.common.v1.KeyValueList; -import io.opentelemetry.testing.internal.proto.logs.v1.LogRecord; -import io.opentelemetry.testing.internal.proto.logs.v1.ResourceLogs; -import io.opentelemetry.testing.internal.proto.logs.v1.ScopeLogs; -import io.opentelemetry.testing.internal.proto.logs.v1.SeverityNumber; -import io.opentelemetry.testing.internal.proto.metrics.v1.HistogramDataPoint; -import io.opentelemetry.testing.internal.proto.metrics.v1.Metric; -import io.opentelemetry.testing.internal.proto.metrics.v1.NumberDataPoint; -import io.opentelemetry.testing.internal.proto.metrics.v1.ResourceMetrics; -import io.opentelemetry.testing.internal.proto.metrics.v1.ScopeMetrics; -import io.opentelemetry.testing.internal.proto.metrics.v1.Sum; -import io.opentelemetry.testing.internal.proto.metrics.v1.SummaryDataPoint; -import io.opentelemetry.testing.internal.proto.resource.v1.Resource; import io.opentelemetry.testing.internal.proto.trace.v1.ResourceSpans; -import io.opentelemetry.testing.internal.proto.trace.v1.ScopeSpans; -import io.opentelemetry.testing.internal.proto.trace.v1.Span; -import io.opentelemetry.testing.internal.proto.trace.v1.Status; import io.opentelemetry.testing.internal.protobuf.InvalidProtocolBufferException; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; -import java.util.stream.Collectors; public final class AgentTestingExporterAccess { - private static final char TRACESTATE_KEY_VALUE_DELIMITER = '='; - private static final char TRACESTATE_ENTRY_DELIMITER = ','; - private static final Pattern TRACESTATE_ENTRY_DELIMITER_SPLIT_PATTERN = - Pattern.compile("[ \t]*" + TRACESTATE_ENTRY_DELIMITER + "[ \t]*"); private static final MethodHandle getSpanExportRequests; private static final MethodHandle getMetricExportRequests; private static final MethodHandle getLogExportRequests; private static final MethodHandle reset; private static final MethodHandle forceFlushCalled; - // opentelemetry-api-1.27:javaagent tests use an older version of opentelemetry-api where Value - // class is missing - private static final boolean canUseValue = classAvailable("io.opentelemetry.api.common.Value"); - private static final boolean hasExtendedLogRecordData = - classAvailable("io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData"); - private static final boolean hasExtendedAttributes = - classAvailable("io.opentelemetry.api.incubator.common.ExtendedAttributes"); static { try { @@ -137,15 +63,6 @@ public final class AgentTestingExporterAccess { } } - private static boolean classAvailable(String className) { - try { - Class.forName(className); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } - public static void reset() { try { reset.invokeExact(); @@ -164,671 +81,67 @@ public static boolean forceFlushCalled() { @SuppressWarnings("unchecked") public static List getExportedSpans() { - List exportRequests; try { - exportRequests = (List) getSpanExportRequests.invokeExact(); + List bytes = (List) getSpanExportRequests.invokeExact(); + List allResourceSpans = + bytes.stream() + .map( + serialized -> { + try { + return ExportTraceServiceRequest.parseFrom(serialized); + } catch (InvalidProtocolBufferException e) { + throw new AssertionError(e); + } + }) + .flatMap(request -> request.getResourceSpansList().stream()) + .collect(toList()); + + return TelemetryConverter.getSpanData(allResourceSpans); } catch (Throwable t) { throw new AssertionError("Could not invoke getSpanExportRequests", t); } - - List allResourceSpans = - exportRequests.stream() - .map( - serialized -> { - try { - return ExportTraceServiceRequest.parseFrom(serialized); - } catch (InvalidProtocolBufferException e) { - throw new AssertionError(e); - } - }) - .flatMap(request -> request.getResourceSpansList().stream()) - .collect(toList()); - List spans = new ArrayList<>(); - for (ResourceSpans resourceSpans : allResourceSpans) { - Resource resource = resourceSpans.getResource(); - for (ScopeSpans ilSpans : resourceSpans.getScopeSpansList()) { - InstrumentationScope instrumentationScope = ilSpans.getScope(); - for (Span span : ilSpans.getSpansList()) { - String traceId = bytesToHex(span.getTraceId().toByteArray()); - spans.add( - TestSpanData.builder() - .setSpanContext( - SpanContext.create( - traceId, - bytesToHex(span.getSpanId().toByteArray()), - TraceFlags.getDefault(), - extractTraceState(span.getTraceState()))) - // TODO is it ok to use default trace flags and default trace state here? - .setParentSpanContext( - SpanContext.create( - traceId, - bytesToHex(span.getParentSpanId().toByteArray()), - TraceFlags.getDefault(), - TraceState.getDefault())) - .setResource( - io.opentelemetry.sdk.resources.Resource.create( - fromProto(resource.getAttributesList()))) - .setInstrumentationScopeInfo( - InstrumentationScopeInfo.builder(instrumentationScope.getName()) - // emptyToNull since they are the same at protobuf layer, - // and allows for simpler verification of InstrumentationScope - .setVersion(emptyToNull(instrumentationScope.getVersion())) - .build()) - .setName(span.getName()) - .setStartEpochNanos(span.getStartTimeUnixNano()) - .setEndEpochNanos(span.getEndTimeUnixNano()) - .setAttributes(fromProto(span.getAttributesList())) - .setEvents( - span.getEventsList().stream() - .map( - event -> - EventData.create( - event.getTimeUnixNano(), - event.getName(), - fromProto(event.getAttributesList()), - event.getDroppedAttributesCount() - + event.getAttributesCount())) - .collect(toList())) - .setStatus(fromProto(span.getStatus())) - .setKind(fromProto(span.getKind())) - .setLinks( - span.getLinksList().stream() - .map( - link -> - LinkData.create( - SpanContext.create( - bytesToHex(link.getTraceId().toByteArray()), - bytesToHex(link.getSpanId().toByteArray()), - TraceFlags.getDefault(), - extractTraceState(link.getTraceState())), - fromProto(link.getAttributesList()), - link.getDroppedAttributesCount() + link.getAttributesCount())) - .collect(toList())) - // OTLP doesn't have hasRemoteParent - .setHasEnded(true) - .setTotalRecordedEvents(span.getEventsCount() + span.getDroppedEventsCount()) - .setTotalRecordedLinks(span.getLinksCount() + span.getDroppedLinksCount()) - .setTotalAttributeCount( - span.getAttributesCount() + span.getDroppedAttributesCount()) - .build()); - } - } - } - return spans; } @SuppressWarnings("unchecked") public static List getExportedMetrics() { - List exportRequests; try { - exportRequests = (List) getMetricExportRequests.invokeExact(); + return TelemetryConverter.getMetricsData( + ((List) getMetricExportRequests.invokeExact()) + .stream() + .map( + serialized -> { + try { + return ExportMetricsServiceRequest.parseFrom(serialized); + } catch (InvalidProtocolBufferException e) { + throw new AssertionError(e); + } + }) + .flatMap(request -> request.getResourceMetricsList().stream()) + .collect(toList())); } catch (Throwable t) { - throw new AssertionError("Could not invoke getMetricExportRequests", t); - } - - List allResourceMetrics = - exportRequests.stream() - .map( - serialized -> { - try { - return ExportMetricsServiceRequest.parseFrom(serialized); - } catch (InvalidProtocolBufferException e) { - throw new AssertionError(e); - } - }) - .flatMap(request -> request.getResourceMetricsList().stream()) - .collect(toList()); - List metrics = new ArrayList<>(); - for (ResourceMetrics resourceMetrics : allResourceMetrics) { - Resource resource = resourceMetrics.getResource(); - for (ScopeMetrics ilMetrics : resourceMetrics.getScopeMetricsList()) { - InstrumentationScope instrumentationScope = ilMetrics.getScope(); - for (Metric metric : ilMetrics.getMetricsList()) { - metrics.add( - createMetricData( - metric, - io.opentelemetry.sdk.resources.Resource.create( - fromProto(resource.getAttributesList())), - InstrumentationScopeInfo.builder(instrumentationScope.getName()) - // emptyToNull since they are the same at protobuf layer, - // and allows for simpler verification of InstrumentationScope - .setVersion(emptyToNull(instrumentationScope.getVersion())) - .build())); - } - } + throw new AssertionError("Could not invoke getSpanExportRequests", t); } - return metrics; } @SuppressWarnings("unchecked") public static List getExportedLogRecords() { - List exportRequests; try { - exportRequests = (List) getLogExportRequests.invokeExact(); + return TelemetryConverter.getLogRecordData( + ((List) getLogExportRequests.invokeExact()) + .stream() + .map( + serialized -> { + try { + return ExportLogsServiceRequest.parseFrom(serialized); + } catch (InvalidProtocolBufferException e) { + throw new AssertionError(e); + } + }) + .flatMap(request -> request.getResourceLogsList().stream()) + .collect(toList())); } catch (Throwable t) { - throw new AssertionError("Could not invoke getMetricExportRequests", t); + throw new AssertionError("Could not invoke getLogExportRequests", t); } - - List allResourceLogs = - exportRequests.stream() - .map( - serialized -> { - try { - return ExportLogsServiceRequest.parseFrom(serialized); - } catch (InvalidProtocolBufferException e) { - throw new AssertionError(e); - } - }) - .flatMap(request -> request.getResourceLogsList().stream()) - .collect(toList()); - List logs = new ArrayList<>(); - for (ResourceLogs resourceLogs : allResourceLogs) { - Resource resource = resourceLogs.getResource(); - for (ScopeLogs ilLogs : resourceLogs.getScopeLogsList()) { - InstrumentationScope instrumentationScope = ilLogs.getScope(); - for (LogRecord logRecord : ilLogs.getLogRecordsList()) { - logs.add( - createLogData( - logRecord, - io.opentelemetry.sdk.resources.Resource.create( - fromProto(resource.getAttributesList())), - InstrumentationScopeInfo.builder(instrumentationScope.getName()) - // emptyToNull since they are the same at protobuf layer, - // and allows for simpler verification of InstrumentationScope - .setVersion(emptyToNull(instrumentationScope.getVersion())) - .build())); - } - } - } - return logs; - } - - private static MetricData createMetricData( - Metric metric, - io.opentelemetry.sdk.resources.Resource resource, - InstrumentationScopeInfo instrumentationScopeInfo) { - switch (metric.getDataCase()) { - case GAUGE: - if (isDouble(metric.getGauge().getDataPointsList())) { - return ImmutableMetricData.createDoubleGauge( - resource, - instrumentationScopeInfo, - metric.getName(), - metric.getDescription(), - metric.getUnit(), - // TODO: Remove usages of internal types. - ImmutableGaugeData.create( - getDoublePointDatas(metric.getGauge().getDataPointsList()))); - } else { - return ImmutableMetricData.createLongGauge( - resource, - instrumentationScopeInfo, - metric.getName(), - metric.getDescription(), - metric.getUnit(), - ImmutableGaugeData.create(getLongPointDatas(metric.getGauge().getDataPointsList()))); - } - case SUM: - if (isDouble(metric.getSum().getDataPointsList())) { - Sum doubleSum = metric.getSum(); - return ImmutableMetricData.createDoubleSum( - resource, - instrumentationScopeInfo, - metric.getName(), - metric.getDescription(), - metric.getUnit(), - ImmutableSumData.create( - doubleSum.getIsMonotonic(), - getTemporality(doubleSum.getAggregationTemporality()), - getDoublePointDatas(metric.getSum().getDataPointsList()))); - } else { - Sum longSum = metric.getSum(); - return ImmutableMetricData.createLongSum( - resource, - instrumentationScopeInfo, - metric.getName(), - metric.getDescription(), - metric.getUnit(), - ImmutableSumData.create( - longSum.getIsMonotonic(), - getTemporality(longSum.getAggregationTemporality()), - getLongPointDatas(metric.getSum().getDataPointsList()))); - } - case HISTOGRAM: - return ImmutableMetricData.createDoubleHistogram( - resource, - instrumentationScopeInfo, - metric.getName(), - metric.getDescription(), - metric.getUnit(), - ImmutableHistogramData.create( - getTemporality(metric.getHistogram().getAggregationTemporality()), - getDoubleHistogramDataPoints(metric.getHistogram().getDataPointsList()))); - case SUMMARY: - return ImmutableMetricData.createDoubleSummary( - resource, - instrumentationScopeInfo, - metric.getName(), - metric.getDescription(), - metric.getUnit(), - ImmutableSummaryData.create( - getDoubleSummaryDataPoints(metric.getSummary().getDataPointsList()))); - default: - throw new AssertionError("Unexpected metric data: " + metric.getDataCase()); - } - } - - private static LogRecordData createLogData( - LogRecord logRecord, - io.opentelemetry.sdk.resources.Resource resource, - InstrumentationScopeInfo instrumentationScopeInfo) { - if (hasExtendedLogRecordData) { - return createExtendedLogData(logRecord, resource, instrumentationScopeInfo); - } - TestLogRecordData.Builder builder = - TestLogRecordData.builder() - .setResource(resource) - .setInstrumentationScopeInfo(instrumentationScopeInfo) - .setTimestamp(logRecord.getTimeUnixNano(), TimeUnit.NANOSECONDS) - .setSpanContext( - SpanContext.create( - bytesToHex(logRecord.getTraceId().toByteArray()), - bytesToHex(logRecord.getSpanId().toByteArray()), - TraceFlags.getDefault(), - TraceState.getDefault())) - .setSeverity(fromProto(logRecord.getSeverityNumber())) - .setSeverityText(logRecord.getSeverityText()) - .setAttributes(fromProto(logRecord.getAttributesList())); - if (canUseValue) { - builder.setBodyValue(getBodyValue(logRecord.getBody())); - } else { - builder.setBody(logRecord.getBody().getStringValue()); - } - return builder.build(); - } - - private static LogRecordData createExtendedLogData( - LogRecord logRecord, - io.opentelemetry.sdk.resources.Resource resource, - InstrumentationScopeInfo instrumentationScopeInfo) { - TestExtendedLogRecordData.Builder builder = - TestExtendedLogRecordData.builder() - .setResource(resource) - .setInstrumentationScopeInfo(instrumentationScopeInfo) - .setTimestamp(logRecord.getTimeUnixNano(), TimeUnit.NANOSECONDS) - .setSpanContext( - SpanContext.create( - bytesToHex(logRecord.getTraceId().toByteArray()), - bytesToHex(logRecord.getSpanId().toByteArray()), - TraceFlags.getDefault(), - TraceState.getDefault())) - .setSeverity(fromProto(logRecord.getSeverityNumber())) - .setSeverityText(logRecord.getSeverityText()) - .setEventName(logRecord.getEventName()) - .setBodyValue(getBodyValue(logRecord.getBody())); - if (hasExtendedAttributes) { - builder.setExtendedAttributes(fromProtoExtended(logRecord.getAttributesList())); - } else { - builder.setAttributes(fromProto(logRecord.getAttributesList())); - } - - return builder.build(); - } - - private static Value getBodyValue(AnyValue value) { - switch (value.getValueCase()) { - case STRING_VALUE: - return Value.of(value.getStringValue()); - case BOOL_VALUE: - return Value.of(value.getBoolValue()); - case INT_VALUE: - return Value.of(value.getIntValue()); - case DOUBLE_VALUE: - return Value.of(value.getDoubleValue()); - case ARRAY_VALUE: - ArrayValue array = value.getArrayValue(); - List> convertedValues = new ArrayList<>(); - for (int i = 0; i < array.getValuesCount(); i++) { - convertedValues.add(getBodyValue(array.getValues(i))); - } - return Value.of(convertedValues); - case KVLIST_VALUE: - KeyValueList keyValueList = value.getKvlistValue(); - io.opentelemetry.api.common.KeyValue[] convertedKeyValueList = - new io.opentelemetry.api.common.KeyValue[keyValueList.getValuesCount()]; - for (int i = 0; i < keyValueList.getValuesCount(); i++) { - KeyValue keyValue = keyValueList.getValues(i); - convertedKeyValueList[i] = - io.opentelemetry.api.common.KeyValue.of( - keyValue.getKey(), getBodyValue(keyValue.getValue())); - } - return Value.of(convertedKeyValueList); - case BYTES_VALUE: - return Value.of(value.getBytesValue().toByteArray()); - case VALUE_NOT_SET: - return null; - } - throw new IllegalStateException("Unexpected attribute: " + value.getValueCase()); - } - - private static boolean isDouble(List points) { - if (points.isEmpty()) { - return true; - } - return points.get(0).getValueCase() == NumberDataPoint.ValueCase.AS_DOUBLE; - } - - private static List getDoublePointDatas(List points) { - return points.stream() - .map( - point -> { - double value; - switch (point.getValueCase()) { - case AS_INT: - value = point.getAsInt(); - break; - case AS_DOUBLE: - default: - value = point.getAsDouble(); - break; - } - return ImmutableDoublePointData.create( - point.getStartTimeUnixNano(), - point.getTimeUnixNano(), - fromProto(point.getAttributesList()), - value); - }) - .collect(toList()); - } - - private static List getLongPointDatas(List points) { - return points.stream() - .map( - point -> { - long value; - switch (point.getValueCase()) { - case AS_INT: - value = point.getAsInt(); - break; - case AS_DOUBLE: - default: - value = (long) point.getAsDouble(); - break; - } - return ImmutableLongPointData.create( - point.getStartTimeUnixNano(), - point.getTimeUnixNano(), - fromProto(point.getAttributesList()), - value); - }) - .collect(toList()); - } - - private static Collection getDoubleHistogramDataPoints( - List dataPointsList) { - return dataPointsList.stream() - .map( - point -> - ImmutableHistogramPointData.create( - point.getStartTimeUnixNano(), - point.getTimeUnixNano(), - fromProto(point.getAttributesList()), - point.getSum(), - point.hasMin(), - point.getMin(), - point.hasMax(), - point.getMax(), - point.getExplicitBoundsList(), - point.getBucketCountsList())) - .collect(toList()); - } - - private static Collection getDoubleSummaryDataPoints( - List dataPointsList) { - return dataPointsList.stream() - .map( - point -> - ImmutableSummaryPointData.create( - point.getStartTimeUnixNano(), - point.getTimeUnixNano(), - fromProto(point.getAttributesList()), - point.getCount(), - point.getSum(), - getValues(point))) - .collect(toList()); - } - - private static List getValues(SummaryDataPoint point) { - return point.getQuantileValuesList().stream() - .map(v -> ImmutableValueAtQuantile.create(v.getQuantile(), v.getValue())) - .collect(Collectors.toList()); - } - - private static AggregationTemporality getTemporality( - io.opentelemetry.testing.internal.proto.metrics.v1.AggregationTemporality - aggregationTemporality) { - switch (aggregationTemporality) { - case AGGREGATION_TEMPORALITY_CUMULATIVE: - return AggregationTemporality.CUMULATIVE; - case AGGREGATION_TEMPORALITY_DELTA: - return AggregationTemporality.DELTA; - default: - throw new IllegalStateException( - "Unexpected aggregation temporality: " + aggregationTemporality); - } - } - - private static ExtendedAttributes fromProtoExtended(List attributes) { - ExtendedAttributesBuilder converted = ExtendedAttributes.builder(); - for (KeyValue attribute : attributes) { - String key = attribute.getKey(); - AnyValue value = attribute.getValue(); - switch (value.getValueCase()) { - case STRING_VALUE: - converted.put(key, value.getStringValue()); - break; - case BOOL_VALUE: - converted.put(key, value.getBoolValue()); - break; - case INT_VALUE: - converted.put(key, value.getIntValue()); - break; - case DOUBLE_VALUE: - converted.put(key, value.getDoubleValue()); - break; - case ARRAY_VALUE: - ArrayValue array = value.getArrayValue(); - if (array.getValuesCount() != 0) { - switch (array.getValues(0).getValueCase()) { - case STRING_VALUE: - converted.put( - stringArrayKey(key), - array.getValuesList().stream().map(AnyValue::getStringValue).collect(toList())); - break; - case BOOL_VALUE: - converted.put( - booleanArrayKey(key), - array.getValuesList().stream().map(AnyValue::getBoolValue).collect(toList())); - break; - case INT_VALUE: - converted.put( - longArrayKey(key), - array.getValuesList().stream().map(AnyValue::getIntValue).collect(toList())); - break; - case DOUBLE_VALUE: - converted.put( - doubleArrayKey(key), - array.getValuesList().stream().map(AnyValue::getDoubleValue).collect(toList())); - break; - case VALUE_NOT_SET: - break; - default: - throw new IllegalStateException( - "Unexpected attribute: " + array.getValues(0).getValueCase()); - } - } - break; - case KVLIST_VALUE: - converted.put(key, fromProtoExtended(value.getKvlistValue().getValuesList())); - break; - case VALUE_NOT_SET: - break; - default: - throw new IllegalStateException("Unexpected attribute: " + value.getValueCase()); - } - } - return converted.build(); - } - - private static Attributes fromProto(List attributes) { - AttributesBuilder converted = Attributes.builder(); - for (KeyValue attribute : attributes) { - String key = attribute.getKey(); - AnyValue value = attribute.getValue(); - switch (value.getValueCase()) { - case STRING_VALUE: - converted.put(key, value.getStringValue()); - break; - case BOOL_VALUE: - converted.put(key, value.getBoolValue()); - break; - case INT_VALUE: - converted.put(key, value.getIntValue()); - break; - case DOUBLE_VALUE: - converted.put(key, value.getDoubleValue()); - break; - case ARRAY_VALUE: - ArrayValue array = value.getArrayValue(); - if (array.getValuesCount() != 0) { - switch (array.getValues(0).getValueCase()) { - case STRING_VALUE: - converted.put( - stringArrayKey(key), - array.getValuesList().stream().map(AnyValue::getStringValue).collect(toList())); - break; - case BOOL_VALUE: - converted.put( - booleanArrayKey(key), - array.getValuesList().stream().map(AnyValue::getBoolValue).collect(toList())); - break; - case INT_VALUE: - converted.put( - longArrayKey(key), - array.getValuesList().stream().map(AnyValue::getIntValue).collect(toList())); - break; - case DOUBLE_VALUE: - converted.put( - doubleArrayKey(key), - array.getValuesList().stream().map(AnyValue::getDoubleValue).collect(toList())); - break; - case VALUE_NOT_SET: - break; - default: - throw new IllegalStateException( - "Unexpected attribute: " + array.getValues(0).getValueCase()); - } - } - break; - case VALUE_NOT_SET: - break; - default: - throw new IllegalStateException("Unexpected attribute: " + value.getValueCase()); - } - } - return converted.build(); - } - - private static StatusData fromProto(Status status) { - StatusCode code; - switch (status.getCode()) { - case STATUS_CODE_OK: - code = StatusCode.OK; - break; - case STATUS_CODE_ERROR: - code = StatusCode.ERROR; - break; - default: - code = StatusCode.UNSET; - break; - } - return StatusData.create(code, status.getMessage()); - } - - private static SpanKind fromProto(Span.SpanKind kind) { - switch (kind) { - case SPAN_KIND_INTERNAL: - return SpanKind.INTERNAL; - case SPAN_KIND_SERVER: - return SpanKind.SERVER; - case SPAN_KIND_CLIENT: - return SpanKind.CLIENT; - case SPAN_KIND_PRODUCER: - return SpanKind.PRODUCER; - case SPAN_KIND_CONSUMER: - return SpanKind.CONSUMER; - default: - throw new IllegalArgumentException("Unexpected span kind: " + kind); - } - } - - private static Severity fromProto(SeverityNumber proto) { - for (Severity severity : Severity.values()) { - if (severity.getSeverityNumber() == proto.getNumber()) { - return severity; - } - } - throw new IllegalArgumentException("Unexpected SeverityNumber: " + proto); - } - - private static TraceState extractTraceState(String traceStateHeader) { - if (traceStateHeader.isEmpty()) { - return TraceState.getDefault(); - } - TraceStateBuilder traceStateBuilder = TraceState.builder(); - String[] listMembers = TRACESTATE_ENTRY_DELIMITER_SPLIT_PATTERN.split(traceStateHeader); - // Iterate in reverse order because when call builder set the elements is added in the - // front of the list. - for (int i = listMembers.length - 1; i >= 0; i--) { - String listMember = listMembers[i]; - int index = listMember.indexOf(TRACESTATE_KEY_VALUE_DELIMITER); - traceStateBuilder.put(listMember.substring(0, index), listMember.substring(index + 1)); - } - return traceStateBuilder.build(); - } - - private static String bytesToHex(byte[] bytes) { - char[] dest = new char[bytes.length * 2]; - bytesToBase16(bytes, dest); - return new String(dest); - } - - private static void bytesToBase16(byte[] bytes, char[] dest) { - for (int i = 0; i < bytes.length; i++) { - byteToBase16(bytes[i], dest, i * 2); - } - } - - private static void byteToBase16(byte value, char[] dest, int destOffset) { - int b = value & 0xFF; - dest[destOffset] = ENCODING[b]; - dest[destOffset + 1] = ENCODING[b | 0x100]; - } - - private static final String ALPHABET = "0123456789abcdef"; - private static final char[] ENCODING = buildEncodingArray(); - - private static char[] buildEncodingArray() { - char[] encoding = new char[512]; - for (int i = 0; i < 256; ++i) { - encoding[i] = ALPHABET.charAt(i >>> 4); - encoding[i | 0x100] = ALPHABET.charAt(i & 0xF); - } - return encoding; - } - - private static String emptyToNull(String string) { - return string == null || string.isEmpty() ? null : string; } private AgentTestingExporterAccess() {} diff --git a/testing/proto-shaded-for-testing/build.gradle.kts b/testing/proto-shaded-for-testing/build.gradle.kts index d35dcb3fffe0..5b011488ead6 100644 --- a/testing/proto-shaded-for-testing/build.gradle.kts +++ b/testing/proto-shaded-for-testing/build.gradle.kts @@ -5,12 +5,18 @@ plugins { dependencies { implementation("io.opentelemetry.proto:opentelemetry-proto") + implementation("com.google.protobuf:protobuf-java-util:4.32.1") } tasks { shadowJar { relocate("io.opentelemetry.proto", "io.opentelemetry.testing.internal.proto") relocate("com.google.protobuf", "io.opentelemetry.testing.internal.protobuf") + relocate("com.google.gson", "io.opentelemetry.testing.internal.gson") + relocate("com.google.common", "io.opentelemetry.testing.internal.guava") + + enableAutoRelocation = true + relocationPrefix = "io.opentelemetry.testing.internal" } val extractShadowJar by registering(Copy::class) {