Move opentelemetry-sdk-extension-jfr-events (#421)

This commit is contained in:
jack-berg 2022-08-24 12:35:28 -05:00 committed by GitHub
parent a57808884a
commit 1279280fa2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 390 additions and 0 deletions

23
jfr-events/README.md Normal file
View File

@ -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

View File

@ -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
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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")