-
Notifications
You must be signed in to change notification settings - Fork 325
Improve virtual thread context tracking support #10208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,132 @@ | ||||
| package datadog.trace.agent.test; | ||||
|
|
||||
| import static org.junit.jupiter.api.Assertions.assertNull; | ||||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||||
|
|
||||
| import datadog.instrument.classinject.ClassInjector; | ||||
| import datadog.trace.agent.tooling.AgentInstaller; | ||||
| import datadog.trace.agent.tooling.InstrumenterModule; | ||||
| import datadog.trace.agent.tooling.TracerInstaller; | ||||
| import datadog.trace.agent.tooling.bytebuddy.matcher.ClassLoaderMatchers; | ||||
| import datadog.trace.api.Config; | ||||
| import datadog.trace.api.IdGenerationStrategy; | ||||
| import datadog.trace.bootstrap.instrumentation.api.AgentSpan; | ||||
| import datadog.trace.bootstrap.instrumentation.api.AgentTracer; | ||||
| import datadog.trace.common.writer.ListWriter; | ||||
| import datadog.trace.core.CoreTracer; | ||||
| import datadog.trace.core.DDSpan; | ||||
| import datadog.trace.core.PendingTrace; | ||||
| import datadog.trace.core.TraceCollector; | ||||
| import java.lang.instrument.ClassFileTransformer; | ||||
| import java.lang.instrument.Instrumentation; | ||||
| import java.util.ServiceLoader; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| import java.util.concurrent.TimeoutException; | ||||
| import net.bytebuddy.agent.ByteBuddyAgent; | ||||
| import org.junit.jupiter.api.AfterEach; | ||||
| import org.junit.jupiter.api.BeforeEach; | ||||
| import org.junit.jupiter.api.extension.ExtendWith; | ||||
|
|
||||
| @ExtendWith(TestClassShadowingExtension.class) | ||||
| public abstract class AbstractInstrumentationTest { | ||||
| static final Instrumentation INSTRUMENTATION = ByteBuddyAgent.getInstrumentation(); | ||||
|
|
||||
| static final long TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(20); | ||||
|
|
||||
| protected AgentTracer.TracerAPI tracer; | ||||
|
|
||||
| protected ListWriter writer; | ||||
|
|
||||
| protected ClassFileTransformer activeTransformer; | ||||
| protected ClassFileTransformerListener transformerLister; | ||||
|
|
||||
| @BeforeEach | ||||
| public void init() { | ||||
| // If this fails, it's likely the result of another test loading Config before it can be | ||||
| // injected into the bootstrap classpath. | ||||
| // If one test extends AgentTestRunner in a module, all tests must extend | ||||
| assertNull(Config.class.getClassLoader(), "Config must load on the bootstrap classpath."); | ||||
|
|
||||
| // Initialize test tracer | ||||
| this.writer = new ListWriter(); | ||||
| // Initialize test tracer | ||||
| CoreTracer tracer = | ||||
PerfectSlayer marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
| CoreTracer.builder() | ||||
| .writer(this.writer) | ||||
| .idGenerationStrategy(IdGenerationStrategy.fromName(idGenerationStrategyName())) | ||||
| .strictTraceWrites(useStrictTraceWrites()) | ||||
| .build(); | ||||
| TracerInstaller.forceInstallGlobalTracer(tracer); | ||||
bric3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
| this.tracer = tracer; | ||||
|
|
||||
| ClassInjector.enableClassInjection(INSTRUMENTATION); | ||||
|
|
||||
| // if a test enables the instrumentation it verifies, | ||||
| // the cache needs to be recomputed taking into account that instrumentation's matchers | ||||
| ClassLoaderMatchers.resetState(); | ||||
|
|
||||
| assertTrue( | ||||
| ServiceLoader.load( | ||||
| InstrumenterModule.class, AbstractInstrumentationTest.class.getClassLoader()) | ||||
| .iterator() | ||||
| .hasNext(), | ||||
| "No instrumentation found"); | ||||
| this.transformerLister = new ClassFileTransformerListener(); | ||||
| this.activeTransformer = | ||||
| AgentInstaller.installBytebuddyAgent( | ||||
| INSTRUMENTATION, true, AgentInstaller.getEnabledSystems(), this.transformerLister); | ||||
| } | ||||
|
|
||||
| protected String idGenerationStrategyName() { | ||||
| return "SEQUENTIAL"; | ||||
| } | ||||
|
|
||||
| private boolean useStrictTraceWrites() { | ||||
| return true; | ||||
| } | ||||
|
|
||||
| @AfterEach | ||||
| public void tearDown() { | ||||
| this.tracer.close(); | ||||
| this.writer.close(); | ||||
| if (this.activeTransformer != null) { | ||||
| INSTRUMENTATION.removeTransformer(this.activeTransformer); | ||||
| this.activeTransformer = null; | ||||
| } | ||||
|
|
||||
| // All cleanups should happen before these assertions. | ||||
| // If not, a failing assertion may prevent cleanup | ||||
| this.transformerLister.verify(); | ||||
| this.transformerLister = null; | ||||
| } | ||||
|
|
||||
| protected void blockUntilChildSpansFinished(final int numberOfSpans) { | ||||
| blockUntilChildSpansFinished(this.tracer.activeSpan(), numberOfSpans); | ||||
| } | ||||
|
|
||||
| static void blockUntilChildSpansFinished(AgentSpan span, int numberOfSpans) { | ||||
| if (span instanceof DDSpan) { | ||||
| TraceCollector traceCollector = ((DDSpan) span).context().getTraceCollector(); | ||||
| if (!(traceCollector instanceof PendingTrace)) { | ||||
| throw new IllegalStateException( | ||||
| "Expected $PendingTrace.name trace collector, got $traceCollector.class.name"); | ||||
| } | ||||
|
|
||||
| PendingTrace pendingTrace = (PendingTrace) traceCollector; | ||||
| long deadline = System.currentTimeMillis() + TIMEOUT_MILLIS; | ||||
|
|
||||
| while (pendingTrace.size() < numberOfSpans) { | ||||
| if (System.currentTimeMillis() > deadline) { | ||||
|
Comment on lines
+116
to
+119
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: You're probably better off with nanos, as
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a port from the Groovy implementation: Line 611 in c426f8c
I would recommend using a dedicated PR to solve it separately because having different implementation for the same logic will bite us later 😅 But yes, this is not the right way, even if nanos is more expensive, we don't care for testing. |
||||
| throw new RuntimeException( | ||||
| new TimeoutException( | ||||
| "Timed out waiting for child spans. Received: " + pendingTrace.size())); | ||||
| } | ||||
| try { | ||||
| Thread.sleep(10); | ||||
| } catch (InterruptedException e) { | ||||
| Thread.currentThread().interrupt(); | ||||
| } | ||||
| } | ||||
| } | ||||
| } | ||||
| } | ||||
PerfectSlayer marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| package datadog.trace.agent.test; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
|
||
| import com.google.common.collect.Sets; | ||
| import datadog.trace.agent.tooling.bytebuddy.matcher.GlobalIgnores; | ||
| import de.thetaphi.forbiddenapis.SuppressForbidden; | ||
| import java.util.Set; | ||
| import java.util.concurrent.atomic.AtomicInteger; | ||
| import net.bytebuddy.agent.builder.AgentBuilder; | ||
| import net.bytebuddy.description.type.TypeDescription; | ||
| import net.bytebuddy.dynamic.DynamicType; | ||
| import net.bytebuddy.utility.JavaModule; | ||
| import net.bytebuddy.utility.nullability.MaybeNull; | ||
|
|
||
| public class ClassFileTransformerListener implements AgentBuilder.Listener { | ||
|
|
||
| final Set<String> transformedClassesNames = Sets.newConcurrentHashSet(); | ||
| final Set<TypeDescription> transformedClassesTypes = Sets.newConcurrentHashSet(); | ||
bric3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| final AtomicInteger instrumentationErrorCount = new AtomicInteger(0); | ||
|
|
||
| @Override | ||
| public void onTransformation( | ||
| TypeDescription typeDescription, | ||
| @MaybeNull ClassLoader classLoader, | ||
| @MaybeNull JavaModule module, | ||
| boolean loaded, | ||
| DynamicType dynamicType) { | ||
| this.transformedClassesNames.add(typeDescription.getActualName()); | ||
| this.transformedClassesTypes.add(typeDescription); | ||
| } | ||
|
|
||
| @SuppressForbidden // Allows System.out.println | ||
| @Override | ||
| public void onError( | ||
| String typeName, | ||
| ClassLoader classLoader, | ||
| JavaModule module, | ||
| boolean loaded, | ||
| Throwable throwable) { | ||
| // Incorrect* classes assert on incorrect api usage. Error expected. | ||
| if (typeName.startsWith("context.FieldInjectionTestInstrumentation$Incorrect") | ||
| && throwable.getMessage().startsWith("Incorrect Context Api Usage detected.")) { | ||
| return; | ||
| } | ||
|
|
||
| System.out.println( | ||
bric3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "Unexpected instrumentation error when instrumenting " + typeName + " on " + classLoader); | ||
| throwable.printStackTrace(); | ||
| instrumentationErrorCount.incrementAndGet(); | ||
| } | ||
|
|
||
| @Override | ||
| public void onDiscovery( | ||
| String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { | ||
| // Nothing special to do | ||
| } | ||
|
|
||
| @Override | ||
| public void onIgnored( | ||
| TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded) { | ||
| // Nothing special to do | ||
| } | ||
|
|
||
| @Override | ||
| public void onComplete( | ||
| String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) { | ||
| // Nothing special to do | ||
| } | ||
|
|
||
| public void verify() { | ||
| // Check instrumentation errors | ||
| int errorCount = this.instrumentationErrorCount.get(); | ||
| assertEquals(0, errorCount, errorCount + " instrumentation errors during test"); | ||
| // Check effectively transformed classes that should have been ignored | ||
| assertTrue( | ||
| this.transformedClassesTypes.stream() | ||
| .map(TypeDescription::getActualName) | ||
| .noneMatch(GlobalIgnores::isAdditionallyIgnored), | ||
| "Transformed classes match global libraries ignore matcher"); | ||
| } | ||
| } | ||
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.