Move opentelemetry-sdk-extension-jfr-events (#421)
This commit is contained in:
parent
a57808884a
commit
1279280fa2
|
|
@ -0,0 +1,23 @@
|
||||||
|
# OpenTelemetry Java Flight Recorder (JFR) Events
|
||||||
|
|
||||||
|
[![Javadocs][javadoc-image]][javadoc-url]
|
||||||
|
|
||||||
|
Create JFR events that can be recorded and viewed in Java Mission Control (JMC).
|
||||||
|
* Creates Open Telemetry Tracing/Span events for spans
|
||||||
|
* The thread and stracktrace will be of the thead ending the span which might be different than the thread creating the span.
|
||||||
|
* Has the fields
|
||||||
|
* Operation Name
|
||||||
|
* Trace ID
|
||||||
|
* Parent Span ID
|
||||||
|
* Span ID
|
||||||
|
* Creates Open Telemetry Tracing/Scope events for scopes
|
||||||
|
* Thread will match the thread the scope was active in and the stacktrace will be when scope was closed
|
||||||
|
* Multiple scopes might be collected for a single span
|
||||||
|
* Has the fields
|
||||||
|
* Trace ID
|
||||||
|
* Span ID
|
||||||
|
* Supports the Open Source version of JFR in Java 11.
|
||||||
|
* Might support back port to OpenJDK 8, but not tested and classes are built with JDK 11 bytecode.
|
||||||
|
|
||||||
|
[javadoc-image]: https://www.javadoc.io/badge/io.opentelemetry/opentelemetry-sdk-extension-jfr-events.svg
|
||||||
|
[javadoc-url]: https://www.javadoc.io/doc/io.opentelemetry/opentelemetry-sdk-extension-jfr-events
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
plugins {
|
||||||
|
id("otel.java-conventions")
|
||||||
|
id("otel.publish-conventions")
|
||||||
|
}
|
||||||
|
|
||||||
|
description = "OpenTelemetry JFR Events"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("io.opentelemetry:opentelemetry-sdk")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks {
|
||||||
|
withType(JavaCompile::class) {
|
||||||
|
options.release.set(11)
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
val testJavaVersion: String? by project
|
||||||
|
if (testJavaVersion == "8") {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disabled due to https://bugs.openjdk.java.net/browse/JDK-8245283
|
||||||
|
configure<JacocoTaskExtension> {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.sdk.extension.jfr;
|
||||||
|
|
||||||
|
import io.opentelemetry.api.trace.Span;
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
|
import io.opentelemetry.context.ContextStorage;
|
||||||
|
import io.opentelemetry.context.Scope;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public final class JfrContextStorageWrapper implements ContextStorage {
|
||||||
|
|
||||||
|
private final ContextStorage wrapped;
|
||||||
|
|
||||||
|
public JfrContextStorageWrapper(ContextStorage wrapped) {
|
||||||
|
this.wrapped = wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Scope attach(Context toAttach) {
|
||||||
|
Scope scope = wrapped.attach(toAttach);
|
||||||
|
ScopeEvent event = new ScopeEvent(Span.fromContext(toAttach).getSpanContext());
|
||||||
|
event.begin();
|
||||||
|
return () -> {
|
||||||
|
if (event.shouldCommit()) {
|
||||||
|
event.commit();
|
||||||
|
}
|
||||||
|
scope.close();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Context current() {
|
||||||
|
return wrapped.current();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.sdk.extension.jfr;
|
||||||
|
|
||||||
|
import io.opentelemetry.api.trace.SpanContext;
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
|
import io.opentelemetry.context.internal.shaded.WeakConcurrentMap;
|
||||||
|
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||||
|
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
||||||
|
import io.opentelemetry.sdk.trace.ReadableSpan;
|
||||||
|
import io.opentelemetry.sdk.trace.SpanProcessor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Span processor to create new JFR events for the Span as they are started, and commit on end.
|
||||||
|
*
|
||||||
|
* <p>NOTE: The JfrSpanProcessor measures the timing of spans, avoid if possible to wrap it with any
|
||||||
|
* other SpanProcessor which may affect timings. When possible, register it first before any other
|
||||||
|
* processors to allow the most accurate measurements.
|
||||||
|
*/
|
||||||
|
public final class JfrSpanProcessor implements SpanProcessor {
|
||||||
|
|
||||||
|
private final WeakConcurrentMap<SpanContext, SpanEvent> spanEvents =
|
||||||
|
new WeakConcurrentMap.WithInlinedExpunction<>();
|
||||||
|
|
||||||
|
private volatile boolean closed;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart(Context parentContext, ReadWriteSpan span) {
|
||||||
|
if (closed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (span.getSpanContext().isValid()) {
|
||||||
|
SpanEvent event = new SpanEvent(span.toSpanData());
|
||||||
|
event.begin();
|
||||||
|
spanEvents.put(span.getSpanContext(), event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStartRequired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnd(ReadableSpan rs) {
|
||||||
|
SpanEvent event = spanEvents.remove(rs.getSpanContext());
|
||||||
|
if (!closed && event != null && event.shouldCommit()) {
|
||||||
|
event.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEndRequired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableResultCode shutdown() {
|
||||||
|
closed = true;
|
||||||
|
return CompletableResultCode.ofSuccess();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.sdk.extension.jfr;
|
||||||
|
|
||||||
|
import io.opentelemetry.api.trace.SpanContext;
|
||||||
|
import jdk.jfr.Category;
|
||||||
|
import jdk.jfr.Description;
|
||||||
|
import jdk.jfr.Event;
|
||||||
|
import jdk.jfr.Label;
|
||||||
|
import jdk.jfr.Name;
|
||||||
|
|
||||||
|
@Name("io.opentelemetry.context.Scope")
|
||||||
|
@Label("Scope")
|
||||||
|
@Category("Open Telemetry Tracing")
|
||||||
|
@Description(
|
||||||
|
"Open Telemetry trace event corresponding to the span currently "
|
||||||
|
+ "in scope/active on this thread.")
|
||||||
|
class ScopeEvent extends Event {
|
||||||
|
|
||||||
|
private final String traceId;
|
||||||
|
private final String spanId;
|
||||||
|
|
||||||
|
ScopeEvent(SpanContext spanContext) {
|
||||||
|
this.traceId = spanContext.getTraceId();
|
||||||
|
this.spanId = spanContext.getSpanId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Label("Trace Id")
|
||||||
|
public String getTraceId() {
|
||||||
|
return traceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Label("Span Id")
|
||||||
|
public String getSpanId() {
|
||||||
|
return spanId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.sdk.extension.jfr;
|
||||||
|
|
||||||
|
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||||
|
import jdk.jfr.Category;
|
||||||
|
import jdk.jfr.Description;
|
||||||
|
import jdk.jfr.Event;
|
||||||
|
import jdk.jfr.Label;
|
||||||
|
import jdk.jfr.Name;
|
||||||
|
|
||||||
|
@Label("Span")
|
||||||
|
@Name("io.opentelemetry.trace.Span")
|
||||||
|
@Category("Open Telemetry Tracing")
|
||||||
|
@Description("Open Telemetry trace event corresponding to a span.")
|
||||||
|
class SpanEvent extends Event {
|
||||||
|
|
||||||
|
private final String operationName;
|
||||||
|
private final String traceId;
|
||||||
|
private final String spanId;
|
||||||
|
private final String parentId;
|
||||||
|
|
||||||
|
SpanEvent(SpanData spanData) {
|
||||||
|
this.operationName = spanData.getName();
|
||||||
|
this.traceId = spanData.getTraceId();
|
||||||
|
this.spanId = spanData.getSpanId();
|
||||||
|
this.parentId = spanData.getParentSpanId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Label("Operation Name")
|
||||||
|
public String getOperationName() {
|
||||||
|
return operationName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Label("Trace Id")
|
||||||
|
public String getTraceId() {
|
||||||
|
return traceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Label("Span Id")
|
||||||
|
public String getSpanId() {
|
||||||
|
return spanId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Label("Parent Id")
|
||||||
|
public String getParentId() {
|
||||||
|
return parentId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capture Spans and Scopes as events in JFR recordings.
|
||||||
|
*
|
||||||
|
* @see io.opentelemetry.sdk.extension.jfr.JfrSpanProcessor
|
||||||
|
*/
|
||||||
|
@ParametersAreNonnullByDefault
|
||||||
|
package io.opentelemetry.sdk.extension.jfr;
|
||||||
|
|
||||||
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.sdk.extension.jfr;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
||||||
|
|
||||||
|
import io.opentelemetry.api.trace.Span;
|
||||||
|
import io.opentelemetry.api.trace.Tracer;
|
||||||
|
import io.opentelemetry.context.ContextStorage;
|
||||||
|
import io.opentelemetry.context.Scope;
|
||||||
|
import io.opentelemetry.sdk.trace.SdkTracerProvider;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import jdk.jfr.Recording;
|
||||||
|
import jdk.jfr.consumer.RecordedEvent;
|
||||||
|
import jdk.jfr.consumer.RecordingFile;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class JfrSpanProcessorTest {
|
||||||
|
|
||||||
|
private static final String OPERATION_NAME = "Test Span";
|
||||||
|
|
||||||
|
private SdkTracerProvider sdkTracerProvider;
|
||||||
|
private Tracer tracer;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
sdkTracerProvider =
|
||||||
|
SdkTracerProvider.builder().addSpanProcessor(new JfrSpanProcessor()).build();
|
||||||
|
tracer = sdkTracerProvider.get("JfrSpanProcessorTest");
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() {
|
||||||
|
sdkTracerProvider.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
ContextStorage.addWrapper(JfrContextStorageWrapper::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test basic single span.
|
||||||
|
*
|
||||||
|
* @throws java.io.IOException on io error
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void basicSpan() throws IOException {
|
||||||
|
Path output = Files.createTempFile("test-basic-span", ".jfr");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Recording recording = new Recording();
|
||||||
|
recording.start();
|
||||||
|
Span span;
|
||||||
|
|
||||||
|
try (recording) {
|
||||||
|
|
||||||
|
span = tracer.spanBuilder(OPERATION_NAME).setNoParent().startSpan();
|
||||||
|
span.end();
|
||||||
|
|
||||||
|
recording.dump(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RecordedEvent> events = RecordingFile.readAllEvents(output);
|
||||||
|
assertThat(events).hasSize(1);
|
||||||
|
assertThat(events)
|
||||||
|
.extracting(e -> e.getValue("traceId"))
|
||||||
|
.isEqualTo(span.getSpanContext().getTraceId());
|
||||||
|
assertThat(events)
|
||||||
|
.extracting(e -> e.getValue("spanId"))
|
||||||
|
.isEqualTo(span.getSpanContext().getSpanId());
|
||||||
|
assertThat(events).extracting(e -> e.getValue("operationName")).isEqualTo(OPERATION_NAME);
|
||||||
|
} finally {
|
||||||
|
Files.delete(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test basic single span with a scope.
|
||||||
|
*
|
||||||
|
* @throws java.io.IOException on io error
|
||||||
|
* @throws java.lang.InterruptedException interrupted sleep
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void basicSpanWithScope() throws IOException, InterruptedException {
|
||||||
|
Path output = Files.createTempFile("test-basic-span-with-scope", ".jfr");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Recording recording = new Recording();
|
||||||
|
recording.start();
|
||||||
|
Span span;
|
||||||
|
|
||||||
|
try (recording) {
|
||||||
|
span = tracer.spanBuilder(OPERATION_NAME).setNoParent().startSpan();
|
||||||
|
try (Scope s = span.makeCurrent()) {
|
||||||
|
Thread.sleep(10);
|
||||||
|
}
|
||||||
|
span.end();
|
||||||
|
|
||||||
|
recording.dump(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RecordedEvent> events = RecordingFile.readAllEvents(output);
|
||||||
|
assertThat(events).hasSize(2);
|
||||||
|
assertThat(events)
|
||||||
|
.extracting(e -> e.getValue("traceId"))
|
||||||
|
.isEqualTo(span.getSpanContext().getTraceId());
|
||||||
|
assertThat(events)
|
||||||
|
.extracting(e -> e.getValue("spanId"))
|
||||||
|
.isEqualTo(span.getSpanContext().getSpanId());
|
||||||
|
assertThat(events)
|
||||||
|
.filteredOn(e -> "Span".equals(e.getEventType().getLabel()))
|
||||||
|
.extracting(e -> e.getValue("operationName"))
|
||||||
|
.isEqualTo(OPERATION_NAME);
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
Files.delete(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -42,6 +42,7 @@ include(":aws-xray")
|
||||||
include(":consistent-sampling")
|
include(":consistent-sampling")
|
||||||
include(":dependencyManagement")
|
include(":dependencyManagement")
|
||||||
include(":example")
|
include(":example")
|
||||||
|
include(":jfr-events")
|
||||||
include(":jfr-streaming")
|
include(":jfr-streaming")
|
||||||
include(":micrometer-meter-provider")
|
include(":micrometer-meter-provider")
|
||||||
include(":jmx-metrics")
|
include(":jmx-metrics")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue