Skip to content

Commit

Permalink
Add Hierarchy tests to MP Telemetry
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitry Aleksandrov <dmitry.aleksandrov@oracle.com>
  • Loading branch information
dalexandrov committed Dec 1, 2023
1 parent a5764d1 commit a245a54
Show file tree
Hide file tree
Showing 6 changed files with 377 additions and 0 deletions.
15 changes: 15 additions & 0 deletions microprofile/telemetry/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
Support for MicroProfile Telemetry
</description>

<properties>
<version.lib.arquillian>1.7.0.Final</version.lib.arquillian>
</properties>


<dependencies>
<dependency>
<groupId>io.helidon.tracing.providers</groupId>
Expand Down Expand Up @@ -102,6 +107,16 @@
<artifactId>helidon-microprofile-testing-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.webclient</groupId>
<artifactId>helidon-webclient</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.helidon.microprofile.telemetry;

import static java.util.Comparator.comparingLong;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.hamcrest.MatcherAssert.assertThat;

import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

import org.awaitility.Awaitility;

import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import jakarta.enterprise.context.ApplicationScoped;
import org.hamcrest.Matchers;

@ApplicationScoped
public class InMemorySpanExporter implements SpanExporter {
private boolean isStopped = false;
private static final List<SpanData> finishedSpanItems = new CopyOnWriteArrayList<>();

/**
* In-memory span exporter inspired by TCKs.
*/
public List<SpanData> getFinishedSpanItems(int spanCount) {
assertSpanCount(spanCount);
return finishedSpanItems.stream().sorted(comparingLong(SpanData::getStartEpochNanos).reversed())
.collect(Collectors.toList());
}

public void assertSpanCount(int spanCount) {
Awaitility.await().pollDelay(3, SECONDS).atMost(10, SECONDS)
.untilAsserted(() -> assertThat(finishedSpanItems.size(), Matchers.is(spanCount)));
}

public void reset() {
finishedSpanItems.clear();
}

@Override
public CompletableResultCode export(Collection<SpanData> spans) {
if (isStopped) {
return CompletableResultCode.ofFailure();
}
finishedSpanItems.addAll(spans);
return CompletableResultCode.ofSuccess();
}

@Override
public CompletableResultCode flush() {
return CompletableResultCode.ofSuccess();
}

@Override
public CompletableResultCode shutdown() {
finishedSpanItems.clear();
isStopped = true;
return CompletableResultCode.ofSuccess();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.helidon.microprofile.telemetry;

import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import jakarta.enterprise.inject.spi.CDI;

public class InMemorySpanExporterProvider implements ConfigurableSpanExporterProvider {
@Override
public SpanExporter createExporter(final ConfigProperties config) {
return CDI.current().select(InMemorySpanExporter.class).get();
}

@Override
public String getName() {
return "in-memory";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


package io.helidon.microprofile.telemetry;

import java.util.List;

import io.helidon.http.Status;
import io.helidon.microprofile.testing.junit5.AddBean;
import io.helidon.microprofile.testing.junit5.AddConfig;
import io.helidon.microprofile.testing.junit5.AddExtension;
import io.helidon.microprofile.testing.junit5.HelidonTest;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.instrumentation.annotations.WithSpan;
import io.opentelemetry.sdk.trace.data.SpanData;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.Application;
import jakarta.ws.rs.core.Response;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static io.opentelemetry.api.trace.SpanKind.INTERNAL;
import static io.opentelemetry.api.trace.SpanKind.SERVER;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

/**
* Test Span Hierarchy with Tracer Mock
*/
@HelidonTest
@AddBean(RestSpanHierarchyTest.SpanResource.class)
@AddBean(InMemorySpanExporter.class)
@AddBean(InMemorySpanExporterProvider.class)
@AddConfig(key = "otel.service.name", value = "helidon-mp-telemetry")
@AddConfig(key = "otel.sdk.disabled", value = "false")
@AddConfig(key = "telemetry.span.full.url", value = "false")
@AddConfig(key = "otel.traces.exporter", value = "in-memory")
public class RestSpanHierarchyTest {

@Inject
WebTarget webTarget;

@Inject
InMemorySpanExporter spanExporter;

@BeforeEach
void setup() {
spanExporter.reset();
}

@Test
void spanHierarchy() {

assertThat(webTarget.path("mixed").request().get().getStatus(), is(Response.Status.OK.getStatusCode()));

List<SpanData> spanItems = spanExporter.getFinishedSpanItems(4);
assertThat(spanItems.get(0).getKind(), is(SERVER));
assertThat(spanItems.get(0).getName(), is("mixed_inner"));
assertThat(spanItems.get(0).getAttributes().get(AttributeKey.stringKey("attribute")), is("value"));
assertThat(spanItems.get(0).getParentSpanId(), is(spanItems.get(1).getSpanId()));


assertThat(spanItems.get(1).getKind(), is(INTERNAL));
assertThat(spanItems.get(1).getName(), is("mixed_parent"));
assertThat(spanItems.get(1).getParentSpanId(), is(spanItems.get(2).getSpanId()));


assertThat(spanItems.get(2).getKind(), is(SERVER));
assertThat(spanItems.get(2).getName(), is("mixed"));
}

@Test
void spanHierarchyInjected() {

assertThat(webTarget.path("mixed_injected").request().get().getStatus(), is(Response.Status.OK.getStatusCode()));

List<SpanData> spanItems = spanExporter.getFinishedSpanItems(4);
assertThat(spanItems.size(), is(4));
assertThat(spanItems.get(0).getKind(), is(SERVER));
assertThat(spanItems.get(0).getName(), is("mixed_inner_injected"));
assertThat(spanItems.get(0).getAttributes().get(AttributeKey.stringKey("attribute")), is("value"));
assertThat(spanItems.get(0).getParentSpanId(), is(spanItems.get(1).getSpanId()));


assertThat(spanItems.get(1).getKind(), is(INTERNAL));
assertThat(spanItems.get(1).getName(), is("mixed_parent_injected"));
assertThat(spanItems.get(1).getParentSpanId(), is(spanItems.get(2).getSpanId()));


assertThat(spanItems.get(2).getKind(), is(SERVER));
assertThat(spanItems.get(2).getName(), is("mixed_injected"));
}


@Path("/")
@ApplicationScoped
public static class SpanResource {

@Inject
private io.helidon.tracing.Tracer helidonTracerInjected;


@GET
@Path("mixed")
@WithSpan("mixed_parent")
public Response mixedSpan() {

io.helidon.tracing.Tracer helidonTracer = io.helidon.tracing.Tracer.global();
io.helidon.tracing.Span mixedSpan = helidonTracer.spanBuilder("mixed_inner")
.kind(io.helidon.tracing.Span.Kind.SERVER)
.tag("attribute", "value")
.start();
mixedSpan.end();

return Response.ok().build();
}

@GET
@Path("mixed_injected")
@WithSpan("mixed_parent_injected")
public Response mixedSpanInjected() {

io.helidon.tracing.Span mixedSpan = helidonTracerInjected.spanBuilder("mixed_inner_injected")
.kind(io.helidon.tracing.Span.Kind.SERVER)
.tag("attribute", "value")
.start();
mixedSpan.end();

return Response.ok().build();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


package io.helidon.microprofile.telemetry;

import java.util.List;

import io.helidon.http.Status;
import io.helidon.microprofile.testing.junit5.AddBean;
import io.helidon.microprofile.testing.junit5.AddConfig;
import io.helidon.microprofile.testing.junit5.AddExtension;
import io.helidon.microprofile.testing.junit5.HelidonTest;

import io.opentelemetry.sdk.trace.data.SpanData;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.Application;
import jakarta.ws.rs.core.Response;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

/**
* Test Span Hierarchy with Tracer Mock
*/
@HelidonTest
@AddBean(TestFullUrlName.SpanResource.class)
@AddBean(InMemorySpanExporter.class)
@AddBean(InMemorySpanExporterProvider.class)
@AddExtension(TelemetryCdiExtension.class)
@AddConfig(key = "otel.service.name", value = "helidon-mp-telemetry")
@AddConfig(key = "otel.sdk.disabled", value = "false")
@AddConfig(key = "telemetry.span.full.url", value = "true")
@AddConfig(key = "otel.traces.exporter", value = "in-memory")
public class TestFullUrlName {


@Inject
WebTarget webTarget;


@Inject
InMemorySpanExporter spanExporter;

@BeforeEach
void setup() {
if (spanExporter != null) {
spanExporter.reset();
}

}

@Test
void spanNaming() {

assertThat(webTarget.path("named").request().get().getStatus(), is(Response.Status.OK.getStatusCode()));

List<SpanData> spanItems = spanExporter.getFinishedSpanItems(2);
assertThat(spanItems.size(), is(2));
assertThat(spanItems.get(0).getName(), is("http://localhost:" + webTarget.getUri().getPort() + "/named"));
}


@Path("/")
@ApplicationScoped
public static class SpanResource {

@GET
@Path("named")
public Response mixedSpan() {
return Response.ok().build();
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.helidon.microprofile.telemetry.InMemorySpanExporterProvider

0 comments on commit a245a54

Please sign in to comment.