11package datadog .trace .agent .tooling ;
22
3+ import static datadog .trace .api .config .TraceInstrumentationConfig .EXPERIMENTAL_DEFER_INTEGRATIONS_UNTIL ;
4+ import static datadog .trace .util .AgentThreadFactory .AgentThread .RETRANSFORMER ;
5+
6+ import datadog .trace .agent .tooling .bytebuddy .matcher .CustomExcludes ;
7+ import datadog .trace .agent .tooling .bytebuddy .matcher .ProxyClassIgnores ;
8+ import datadog .trace .api .InstrumenterConfig ;
9+ import datadog .trace .api .time .TimeUtils ;
10+ import datadog .trace .util .AgentTaskScheduler ;
11+ import java .lang .instrument .Instrumentation ;
312import java .security .ProtectionDomain ;
13+ import java .util .ArrayList ;
414import java .util .BitSet ;
15+ import java .util .Iterator ;
516import java .util .List ;
17+ import java .util .Set ;
18+ import java .util .concurrent .TimeUnit ;
619import net .bytebuddy .agent .builder .AgentBuilder ;
720import net .bytebuddy .description .type .TypeDescription ;
821import net .bytebuddy .utility .JavaModule ;
1326final class CombiningMatcher implements AgentBuilder .RawMatcher {
1427 private static final Logger log = LoggerFactory .getLogger (CombiningMatcher .class );
1528
29+ private static final boolean DEFER_MATCHING =
30+ null != InstrumenterConfig .get ().deferIntegrationsUntil ();
31+
32+ private static final Set <String > DEFERRED_CLASSLOADER_NAMES =
33+ InstrumenterConfig .get ().getDeferredClassLoaders ();
34+
35+ private static final boolean DEFER_ALL = DEFERRED_CLASSLOADER_NAMES .isEmpty ();
36+
1637 // optimization to avoid repeated allocations inside BitSet as matched ids are set
1738 static final int MAX_COMBINED_ID_HINT = 512 ;
1839
@@ -25,9 +46,16 @@ final class CombiningMatcher implements AgentBuilder.RawMatcher {
2546 private final BitSet knownTypesMask ;
2647 private final MatchRecorder [] matchers ;
2748
28- CombiningMatcher (BitSet knownTypesMask , List <MatchRecorder > matchers ) {
49+ private volatile boolean deferring ;
50+
51+ CombiningMatcher (
52+ Instrumentation instrumentation , BitSet knownTypesMask , List <MatchRecorder > matchers ) {
2953 this .knownTypesMask = knownTypesMask ;
3054 this .matchers = matchers .toArray (new MatchRecorder [0 ]);
55+
56+ if (DEFER_MATCHING ) {
57+ scheduleResumeMatching (instrumentation , InstrumenterConfig .get ().deferIntegrationsUntil ());
58+ }
3159 }
3260
3361 @ Override
@@ -38,6 +66,11 @@ public boolean matches(
3866 Class <?> classBeingRedefined ,
3967 ProtectionDomain pd ) {
4068
69+ // check initial requests to see if we should defer matching until retransformation
70+ if (DEFER_MATCHING && null == classBeingRedefined && deferring && isDeferred (classLoader )) {
71+ return false ;
72+ }
73+
4174 BitSet ids = recordedMatches .get ();
4275 ids .clear ();
4376
@@ -63,4 +96,103 @@ public boolean matches(
6396
6497 return !ids .isEmpty ();
6598 }
99+
100+ /** Arranges for any deferred matching to resume at the requested trigger point. */
101+ private void scheduleResumeMatching (Instrumentation instrumentation , String untilTrigger ) {
102+ if (null != untilTrigger && !untilTrigger .isEmpty ()) {
103+ long delay = TimeUtils .parseSimpleDelay (untilTrigger );
104+ if (delay < 0 ) {
105+ log .info (
106+ "Unrecognized value for dd.{}: {}" ,
107+ EXPERIMENTAL_DEFER_INTEGRATIONS_UNTIL ,
108+ untilTrigger );
109+ } else if (delay >= 5 ) { // don't bother deferring small delays
110+
111+ new AgentTaskScheduler (RETRANSFORMER )
112+ .schedule (this ::resumeMatching , instrumentation , delay , TimeUnit .SECONDS );
113+
114+ deferring = true ;
115+ }
116+ }
117+ }
118+
119+ /**
120+ * Scans loaded classes to find which ones we should retransform to resume matching them.
121+ *
122+ * <p>We try to only trigger retransformations for classes we know would match. Caching and
123+ * memoization means running matching twice is cheaper than unnecessary retransformations.
124+ */
125+ void resumeMatching (Instrumentation instrumentation ) {
126+ if (!deferring ) {
127+ return ;
128+ }
129+
130+ deferring = false ;
131+
132+ Iterator <Iterable <Class <?>>> rediscovery =
133+ AgentStrategies .rediscoveryStrategy ().resolve (instrumentation ).iterator ();
134+
135+ List <Class <?>> resuming = new ArrayList <>();
136+ while (rediscovery .hasNext ()) {
137+ for (Class <?> clazz : rediscovery .next ()) {
138+ ClassLoader classLoader = clazz .getClassLoader ();
139+ if (isDeferred (classLoader )
140+ && !wouldIgnore (clazz .getName ())
141+ && instrumentation .isModifiableClass (clazz )
142+ && wouldMatch (classLoader , clazz )) {
143+ resuming .add (clazz );
144+ }
145+ }
146+ }
147+
148+ try {
149+ log .debug ("Resuming deferred matching for {}" , resuming );
150+ instrumentation .retransformClasses (resuming .toArray (new Class [0 ]));
151+ } catch (Throwable e ) {
152+ log .debug ("Problem resuming deferred matching" , e );
153+ }
154+ }
155+
156+ /**
157+ * Tests whether matches involving this class-loader should be deferred until later.
158+ *
159+ * <p>The bootstrap class-loader is never deferred.
160+ */
161+ private static boolean isDeferred (ClassLoader classLoader ) {
162+ return null != classLoader
163+ && (DEFER_ALL || DEFERRED_CLASSLOADER_NAMES .contains (classLoader .getClass ().getName ()));
164+ }
165+
166+ /** Tests whether this class would be ignored on retransformation. */
167+ private static boolean wouldIgnore (String name ) {
168+ return name .indexOf ('/' ) >= 0 // don't retransform lambdas
169+ || CustomExcludes .isExcluded (name )
170+ || ProxyClassIgnores .isIgnored (name );
171+ }
172+
173+ /** Tests whether this class would be matched at least once on retransformation. */
174+ private boolean wouldMatch (ClassLoader classLoader , Class <?> clazz ) {
175+ BitSet ids = recordedMatches .get ();
176+ ids .clear ();
177+
178+ knownTypesIndex .apply (clazz .getName (), knownTypesMask , ids );
179+ if (!ids .isEmpty ()) {
180+ return true ;
181+ }
182+
183+ TypeDescription target = new TypeDescription .ForLoadedType (clazz );
184+
185+ for (MatchRecorder matcher : matchers ) {
186+ try {
187+ matcher .record (target , classLoader , clazz , ids );
188+ if (!ids .isEmpty ()) {
189+ return true ;
190+ }
191+ } catch (Throwable ignore ) {
192+ // skip misbehaving matchers
193+ }
194+ }
195+
196+ return false ;
197+ }
66198}
0 commit comments