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(":dependencyManagement")
|
||||
include(":example")
|
||||
include(":jfr-events")
|
||||
include(":jfr-streaming")
|
||||
include(":micrometer-meter-provider")
|
||||
include(":jmx-metrics")
|
||||
|
|
|
|||
Loading…
Reference in New Issue