From f73f30bb2f2089c848b2978cb8e9eed2061b2a80 Mon Sep 17 00:00:00 2001 From: Doug Hilpipre Date: Fri, 24 May 2024 14:51:12 -0500 Subject: [PATCH] baseline 5-24 --- .../HttpListenerClassMethodMatcher.java | 30 ++ .../connector/HttpListenerMethodMatcher.java | 31 ++ .../http/connector/HttpListenerTracer.java | 454 ++++++++++++++++++ .../HttpRequesterClassMethodMatcher.java | 25 + ...HttpResponseReadyCallbackClassMatcher.java | 34 ++ ...sponseReadyCallbackClassMethodMatcher.java | 31 ++ ...ttpResponseReadyCallbackMethodMatcher.java | 36 ++ .../connector/MuleHttpConnectorFactory.java | 82 ++++ .../connector/MuleHttpConnectorFactory2.java | 79 +++ .../connector/MuleHttpConnectorService.java | 92 ++++ .../MuleHttpConnectorTransformer.java | 75 +++ .../http/connector/RequestHandlerMatcher.java | 41 ++ .../http/connector/ResponseSenderMatcher.java | 24 + .../mule/http/connector/TracerUtils.java | 30 ++ 14 files changed, 1064 insertions(+) create mode 100644 Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpListenerClassMethodMatcher.java create mode 100644 Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpListenerMethodMatcher.java create mode 100644 Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpListenerTracer.java create mode 100644 Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpRequesterClassMethodMatcher.java create mode 100644 Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpResponseReadyCallbackClassMatcher.java create mode 100644 Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpResponseReadyCallbackClassMethodMatcher.java create mode 100644 Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpResponseReadyCallbackMethodMatcher.java create mode 100644 Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/MuleHttpConnectorFactory.java create mode 100644 Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/MuleHttpConnectorFactory2.java create mode 100644 Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/MuleHttpConnectorService.java create mode 100644 Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/MuleHttpConnectorTransformer.java create mode 100644 Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/RequestHandlerMatcher.java create mode 100644 Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/ResponseSenderMatcher.java create mode 100644 Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/TracerUtils.java diff --git a/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpListenerClassMethodMatcher.java b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpListenerClassMethodMatcher.java new file mode 100644 index 0000000..d557603 --- /dev/null +++ b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpListenerClassMethodMatcher.java @@ -0,0 +1,30 @@ +package com.newrelic.instrumentation.mule.http.connector; + +import com.newrelic.agent.instrumentation.classmatchers.ClassAndMethodMatcher; +import com.newrelic.agent.instrumentation.classmatchers.ClassMatcher; +import com.newrelic.agent.instrumentation.classmatchers.ExactClassMatcher; +import com.newrelic.agent.instrumentation.methodmatchers.MethodMatcher; + +public class HttpListenerClassMethodMatcher implements ClassAndMethodMatcher { + + protected static final String HTTPLISTENER = "org.mule.extension.http.internal.listener.HttpListener"; + + private HttpListenerMethodMatcher methodMatcher; + private ExactClassMatcher classMatcher; + + public HttpListenerClassMethodMatcher() { + classMatcher = new ExactClassMatcher(HTTPLISTENER); + methodMatcher = new HttpListenerMethodMatcher(); + } + + @Override + public ClassMatcher getClassMatcher() { + return classMatcher; + } + + @Override + public MethodMatcher getMethodMatcher() { + return methodMatcher; + } + +} diff --git a/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpListenerMethodMatcher.java b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpListenerMethodMatcher.java new file mode 100644 index 0000000..429f100 --- /dev/null +++ b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpListenerMethodMatcher.java @@ -0,0 +1,31 @@ +package com.newrelic.instrumentation.mule.http.connector; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import com.newrelic.agent.deps.org.objectweb.asm.commons.Method; +import com.newrelic.agent.instrumentation.methodmatchers.MethodMatcher; + +public class HttpListenerMethodMatcher implements MethodMatcher { + + List methods = new ArrayList(); + + public HttpListenerMethodMatcher() { + methods.add("onError"); + methods.add("onSuccess"); + methods.add("onStart"); + } + + @Override + public boolean matches(int access, String name, String desc, Set annotations) { + return methods.contains(name); + } + + @Override + public Method[] getExactMethods() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpListenerTracer.java b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpListenerTracer.java new file mode 100644 index 0000000..3356fa6 --- /dev/null +++ b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpListenerTracer.java @@ -0,0 +1,454 @@ +package com.newrelic.instrumentation.mule.http.connector; + +import java.text.MessageFormat; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +import com.newrelic.agent.Agent; +import com.newrelic.agent.MetricNames; +import com.newrelic.agent.Transaction; +import com.newrelic.agent.TransactionActivity; +import com.newrelic.agent.bridge.TracedMethod; +import com.newrelic.agent.config.TransactionTracerConfig; +import com.newrelic.agent.database.SqlObfuscator; +import com.newrelic.agent.deps.org.objectweb.asm.Opcodes; +import com.newrelic.agent.dispatchers.Dispatcher; +import com.newrelic.agent.dispatchers.OtherDispatcher; +import com.newrelic.agent.stats.ResponseTimeStats; +import com.newrelic.agent.stats.TransactionStats; +import com.newrelic.agent.trace.TransactionGuidFactory; +import com.newrelic.agent.trace.TransactionSegment; +import com.newrelic.agent.tracers.AbstractTracer; +import com.newrelic.agent.tracers.ClassMethodSignature; +import com.newrelic.agent.tracers.DefaultTracer; +import com.newrelic.agent.tracers.SkipTracer; +import com.newrelic.agent.tracers.Tracer; +import com.newrelic.agent.tracers.TracerFlags; +import com.newrelic.agent.tracers.TransactionActivityInitiator; +import com.newrelic.agent.tracers.metricname.MetricNameFormat; +import com.newrelic.agent.tracers.metricname.SimpleMetricNameFormat; +import com.newrelic.agent.util.Strings; +import com.newrelic.api.agent.Token; + +public class HttpListenerTracer extends AbstractTracer implements TransactionActivityInitiator { + + public static final MetricNameFormat NULL_METRIC_NAME_FORMATTER = new SimpleMetricNameFormat(null); + + private final long startTime; + private long duration; + private long exclusiveDuration; + private MetricNameFormat metricNameFormat; + private int childCount = 0; + private boolean isParent; + + private Tracer parentTracer; + private byte tracerFlags; + private String guid; + private final ClassMethodSignature classMethodSignature; + private long endTime; + private Object invocationTarget; + private boolean isAsync = false; + private Token token = null; + + private boolean childHasStackTrace; + + public HttpListenerTracer(Transaction transaction, ClassMethodSignature sig, Object object, MetricNameFormat format) { + this(transaction, sig, object, format, DefaultTracer.DEFAULT_TRACER_FLAGS); + } + + public HttpListenerTracer(Transaction transaction, ClassMethodSignature sig, Object object, MetricNameFormat format, int tracerflags) { + super(transaction); + startTime = System.nanoTime(); + metricNameFormat = format; + guid = TransactionGuidFactory.generate16CharGuid(); + tracerFlags = (byte) tracerflags; + classMethodSignature = sig; + invocationTarget = object; + parentTracer = transaction.getTransactionActivity().getLastTracer(); + } + + public HttpListenerTracer(Transaction transaction, ClassMethodSignature sig, Object object) { + this(transaction,sig,object,new SimpleMetricNameFormat("Custom/"+object.getClass().getSimpleName()+"/"+sig.getMethodName()),DefaultTracer.DEFAULT_TRACER_FLAGS); + } + + public void setToken(Token token) { + this.token = token; + } + + @Override + public long getStartTime() { + return startTime; + } + + @Override + public TracedMethod getParentTracedMethod() { + return parentTracer; + } + + @Override + public long getStartTimeInMillis() { + return getStartTimeInMilliseconds(); + } + + @Override + public long getStartTimeInMilliseconds() { + return TimeUnit.MILLISECONDS.convert(startTime, TimeUnit.NANOSECONDS); + } + + @Override + public long getEndTime() { + if(endTime == 0) endTime = System.nanoTime(); + return endTime; + } + + @Override + public long getEndTimeInMilliseconds() { + return TimeUnit.MILLISECONDS.convert(getEndTime(), TimeUnit.NANOSECONDS); + } + + @Override + public long getExclusiveDuration() { + return exclusiveDuration; + } + + @Override + public long getRunningDurationInNanos() { + return duration > 0 ? duration : Math.max(0, System.nanoTime() - getStartTime()); + } + + @Override + public String getMetricName() { + return metricNameFormat == null ? null : metricNameFormat.getMetricName(); + } + + @Override + public String getTransactionSegmentName() { + return metricNameFormat == null ? null : metricNameFormat.getTransactionSegmentName(); + } + + @Override + public String getTransactionSegmentUri() { + return metricNameFormat == null ? null : metricNameFormat.getTransactionSegmentUri(); + } + + @Override + public void childTracerFinished(Tracer child) { + if (child.isMetricProducer() && !(child instanceof SkipTracer)) { + childCount++; + exclusiveDuration -= child.getDuration(); + if (isTransactionSegment() && child.isTransactionSegment()) { + isParent = true; + if (child.isChildHasStackTrace()) { + childHasStackTrace = true; + } + } + } + } + + public void setAsync(boolean isAsync) { + this.isAsync = isAsync; + } + + @Override + public boolean isAsync() { + return isAsync; + } + + + @Override + protected final Object getInvocationTarget() { + return invocationTarget; + } + + @Override + public int getChildCount() { + return childCount; + } + + @Override + public Tracer getParentTracer() { + return parentTracer; + } + + @Override + public void setParentTracer(Tracer tracer) { + parentTracer = tracer; + } + + @Override + public boolean isParent() { + return isParent; + } + + @Override + public boolean isTransactionSegment() { +// return (tracerFlags & TracerFlags.TRANSACTION_TRACER_SEGMENT) == TracerFlags.TRANSACTION_TRACER_SEGMENT; + return true; + } + + @Override + public TransactionSegment getTransactionSegment(TransactionTracerConfig ttConfig, SqlObfuscator sqlObfuscator, + long startTime, TransactionSegment lastSibling) { + return new TransactionSegment(ttConfig, sqlObfuscator, startTime, this); + } + + @Override + public void removeTransactionSegment() { + this.tracerFlags = (byte) TracerFlags.clearSegment(this.tracerFlags); + } + + @Override + public String getGuid() { + return guid; + } + + @Override + public long getDurationInMilliseconds() { + return TimeUnit.MILLISECONDS.convert(getDuration(), TimeUnit.NANOSECONDS); + } + + @Override + public long getDuration() { + if(duration <= 0) { + if(endTime >= startTime) { + duration = endTime - startTime; + } else { + duration = System.nanoTime() - startTime; + } + } + return duration; + } + + @Override + public void finish(Throwable throwable) { + + Transaction tx = getTransaction(); + if(tx == null) { + // This is either a serious internal error, or the application + // used "@Trace(async = true)" and never called startAsyncActivity() + // to associate with a Transaction. Either way, don't leak the + // Activity as a stale ThreadLocal. + TransactionActivity.clear(); + return; + } + setThrownException(throwable); + tx.noticeTracerException(throwable, getGuid()); + + if (!tx.getTransactionState().finish(tx, this)) { + return; + } + try { + getTransactionActivity().lockTracerStart(); + } catch (Throwable t) { + String msg = MessageFormat.format("An error occurred finishing tracer for class {0} : {1}", + classMethodSignature.getClassName(), t); + if (Agent.LOG.isLoggable(Level.FINER)) { + Agent.LOG.log(Level.WARNING, msg, t); + } else { + Agent.LOG.warning(msg); + } + } finally { + getTransactionActivity().unlockTracerStart(); + } + + finish(Opcodes.ATHROW, null); + + if (Agent.isDebugEnabled()) { + Agent.LOG.log(Level.FINE, "(Debug) Tracer.finish(Throwable)"); + } + + } + + @Override + public void finish(int opcode, Object returnValue) { + Agent.LOG.log(Level.FINE, "Call to HttpListenerTracer.finish({0},{1})", opcode,returnValue); + + endTime = System.nanoTime(); + TransactionActivity txa = getTransactionActivity(); + if (txa == null) { + // Internal error - null txa is permitted for + // a weird legacy Play instrumentation case + Agent.LOG.log(Level.FINER, "Transaction activity for {0} was null", this); + return; + } + + // Get transaction from this tracer's txa. + Transaction tx = getTransaction(); + if (tx != null && !tx.getTransactionState().finish(tx, this)) { + return; + } + + performFinishWork(endTime, opcode, returnValue); + } + + public void performFinishWork(long finishTime, int opcode, Object returnValue) { + if(token != null) { + token.linkAndExpire(); + token = null; + } + // Believe it or not, it's possible to get a negative value! + // (At least on some old, broken Linux kernels is is.) + duration = Math.max(0, finishTime - getStartTime()); + exclusiveDuration += duration; + if (exclusiveDuration < 0 || exclusiveDuration > duration) { + Agent.LOG.log(Level.FINE, "Invalid exclusive time {0} for tracer {1}", exclusiveDuration, + this.getClass().getName()); + exclusiveDuration = duration; + } + + getTransactionActivity().lockTracerStart(); + try { + try { + attemptToStoreStackTrace(); + } catch (Throwable t) { + if (Agent.LOG.isFinestEnabled()) { + String msg = MessageFormat.format("An error occurred getting stack trace for class {0} : {1}", + classMethodSignature.getClassName(), t.toString()); + Agent.LOG.log(Level.FINEST, msg, t); + } + } + + if (impactsParent(parentTracer)) { + parentTracer.childTracerFinished(this); + } + + try { + recordMetrics(getTransactionActivity().getTransactionStats()); + } catch (Throwable t) { + String msg = MessageFormat.format("An error occurred recording tracer metrics for class {0} : {1}", + classMethodSignature.getClassName(), t.toString()); + Agent.LOG.severe(msg); + Agent.LOG.log(Level.FINER, msg, t); + } + + try { + if (!(this instanceof SkipTracer)) { + getTransactionActivity().tracerFinished(this, opcode); + } + } catch (Throwable t) { + String msg = MessageFormat.format( + "An error occurred calling Transaction.tracerFinished() for class {0} : {1}", + classMethodSignature.getClassName(), t.toString()); + Agent.LOG.severe(msg); + Agent.LOG.log(Level.FINER, msg, t); + } + reset(); + } finally { + getTransactionActivity().unlockTracerStart(); + } + } + + protected void reset() { + invocationTarget = null; + } + + + protected void recordMetrics(TransactionStats transactionStats) { + Agent.LOG.log(Level.FINE, "HttpListenerTracer - Call to recordMetrics"); + if (getTransaction() == null || getTransaction().isIgnore()) { + return; + } + if (isMetricProducer()) { + String metricName = getMetricName(); + Agent.LOG.log(Level.FINE, "HttpListenerTracer - Recording metrics for {0}", metricName); + if (metricName != null) { + // record the scoped metrics + ResponseTimeStats stats = transactionStats.getScopedStats().getOrCreateResponseTimeStats(metricName); + stats.recordResponseTimeInNanos(getDuration(), getExclusiveDuration()); + + // there is now an unscoped metric for every scoped metric + // the unscoped metric is created in the StatsEngineImpl + + } + if (getRollupMetricNames() != null) { + for (String name : getRollupMetricNames()) { + ResponseTimeStats stats = transactionStats.getUnscopedStats().getOrCreateResponseTimeStats(name); + stats.recordResponseTimeInNanos(getDuration(), getExclusiveDuration()); + } + } + if (getExclusiveRollupMetricNames() != null) { + for (String name : getExclusiveRollupMetricNames()) { + ResponseTimeStats stats = transactionStats.getUnscopedStats().getOrCreateResponseTimeStats(name); + stats.recordResponseTimeInNanos(getExclusiveDuration(), getExclusiveDuration()); + } + } + } + } + + + private boolean impactsParent(Tracer parent) { + return (parent != null && parent.getTransactionActivity() == this.getTransactionActivity()); + } + + protected boolean shouldStoreStackTrace() { + return isTransactionSegment(); + } + + public void storeStackTrace() { + setAgentAttribute(DefaultTracer.BACKTRACE_PARAMETER_NAME, Thread.currentThread().getStackTrace()); + } + + + private void attemptToStoreStackTrace() { + if (getTransaction() != null && shouldStoreStackTrace()) { + TransactionTracerConfig transactionTracerConfig = getTransaction().getTransactionTracerConfig(); + double stackTraceThresholdInNanos = transactionTracerConfig.getStackTraceThresholdInNanos(); + int stackTraceMax = transactionTracerConfig.getMaxStackTraces(); + // you must be over the duration and either child has taken a stack trace or we are under the stack trace + // count + if ((getDuration() > stackTraceThresholdInNanos) + && (childHasStackTrace || (getTransaction().getTransactionCounts().getStackTraceCount() < stackTraceMax))) { + storeStackTrace(); + // only increment the stack trace count if there are no children which have taken a stack trace + if (!childHasStackTrace) { + getTransaction().getTransactionCounts().incrementStackTraceCount(); + // this property is used to tell parents not to increment the stack trace count + childHasStackTrace = true; + } + } + } + } + + + @Override + public void markFinishTime() { + endTime = System.nanoTime(); + } + + @Override + public boolean isMetricProducer() { + //return (tracerFlags & TracerFlags.GENERATE_SCOPED_METRIC) == TracerFlags.GENERATE_SCOPED_METRIC; + return true; + } + + public void setMetricNameFormat(MetricNameFormat nameFormat) { + metricNameFormat = nameFormat; + } + + @Override + public void setMetricNameFormatInfo(String metricName, String transactionSegmentName, String transactionSegmentUri) { + MetricNameFormat format = new SimpleMetricNameFormat(metricName, transactionSegmentName, transactionSegmentUri); + setMetricNameFormat(format); + } + + @Override + public void setMetricName(String... metricNameParts) { + String metricName = Strings.join(MetricNames.SEGMENT_DELIMITER, metricNameParts); + if (metricName != null) { + setMetricNameFormat(new SimpleMetricNameFormat(metricName)); + } + MetricNames.recordApiSupportabilityMetric(MetricNames.SUPPORTABILITY_API_SEGMENT_SET_METRIC_NAME); + } + + @Override + public ClassMethodSignature getClassMethodSignature() { + return classMethodSignature; + } + + @Override + public Dispatcher createDispatcher() { + // TODO Auto-generated method stub + return new OtherDispatcher(getTransaction(), metricNameFormat); + } + +} diff --git a/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpRequesterClassMethodMatcher.java b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpRequesterClassMethodMatcher.java new file mode 100644 index 0000000..fefa1d2 --- /dev/null +++ b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpRequesterClassMethodMatcher.java @@ -0,0 +1,25 @@ +package com.newrelic.instrumentation.mule.http.connector; + +import java.util.Collections; + +import com.newrelic.agent.instrumentation.classmatchers.ClassAndMethodMatcher; +import com.newrelic.agent.instrumentation.classmatchers.ClassMatcher; +import com.newrelic.agent.instrumentation.classmatchers.ExactClassMatcher; +import com.newrelic.agent.instrumentation.methodmatchers.ExactMethodMatcher; +import com.newrelic.agent.instrumentation.methodmatchers.MethodMatcher; + +public class HttpRequesterClassMethodMatcher implements ClassAndMethodMatcher { + + protected static final String HTTPREQUESTER = "org.mule.extension.http.internal.request.HttpRequester"; + + @Override + public ClassMatcher getClassMatcher() { + return new ExactClassMatcher(HTTPREQUESTER); + } + + @Override + public MethodMatcher getMethodMatcher() { + return new ExactMethodMatcher("doRequest", Collections.emptyList()); + } + +} diff --git a/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpResponseReadyCallbackClassMatcher.java b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpResponseReadyCallbackClassMatcher.java new file mode 100644 index 0000000..51502fd --- /dev/null +++ b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpResponseReadyCallbackClassMatcher.java @@ -0,0 +1,34 @@ +package com.newrelic.instrumentation.mule.http.connector; + +import java.util.logging.Level; + +import com.newrelic.agent.deps.org.objectweb.asm.ClassReader; +import com.newrelic.agent.instrumentation.classmatchers.ExactClassMatcher; +import com.newrelic.agent.instrumentation.classmatchers.InterfaceMatcher; +import com.newrelic.agent.instrumentation.classmatchers.OrClassMatcher; +import com.newrelic.api.agent.NewRelic; + +public class HttpResponseReadyCallbackClassMatcher extends OrClassMatcher { + + public HttpResponseReadyCallbackClassMatcher() { + super(new InterfaceMatcher("org.mule.runtime.http.api.server.async.HttpResponseReadyCallback"), new ExactClassMatcher("org.mule.service.http.impl.service.server.grizzly.GrizzlyRequestDispatcherFilter$1")); + } + + @Override + public boolean isMatch(ClassLoader loader, ClassReader cr) { + + boolean b = super.isMatch(loader, cr); + NewRelic.getAgent().getLogger().log(Level.FINE, "HttpResponseReadyCallbackClassMatcher.isMatch for {0} is {1}", cr.getClassName(), b); + return b; + } + + @Override + public boolean isMatch(Class clazz) { + + boolean b = super.isMatch(clazz); + NewRelic.getAgent().getLogger().log(Level.FINE, "HttpResponseReadyCallbackClassMatcher.isMatch for {0} is {1}", clazz.getName(), b); + return b; + } + + +} diff --git a/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpResponseReadyCallbackClassMethodMatcher.java b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpResponseReadyCallbackClassMethodMatcher.java new file mode 100644 index 0000000..e07d873 --- /dev/null +++ b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpResponseReadyCallbackClassMethodMatcher.java @@ -0,0 +1,31 @@ +package com.newrelic.instrumentation.mule.http.connector; + +import com.newrelic.agent.instrumentation.classmatchers.ClassAndMethodMatcher; +import com.newrelic.agent.instrumentation.classmatchers.ClassMatcher; +import com.newrelic.agent.instrumentation.classmatchers.InterfaceMatcher; +import com.newrelic.agent.instrumentation.methodmatchers.MethodMatcher; + +public class HttpResponseReadyCallbackClassMethodMatcher implements ClassAndMethodMatcher { + + protected static final String RESPONSE_READY = "responseReady"; + protected static final String START_RESPONSE = "startResponse"; + + ClassMatcher classMatcher = null; + MethodMatcher methodMatcher = null; + + public HttpResponseReadyCallbackClassMethodMatcher() { + classMatcher = new HttpResponseReadyCallbackClassMatcher(); + methodMatcher = new HttpResponseReadyCallbackMethodMatcher(); + } + + @Override + public ClassMatcher getClassMatcher() { + return classMatcher; + } + + @Override + public MethodMatcher getMethodMatcher() { + return methodMatcher; + } + +} diff --git a/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpResponseReadyCallbackMethodMatcher.java b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpResponseReadyCallbackMethodMatcher.java new file mode 100644 index 0000000..ab559f9 --- /dev/null +++ b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/HttpResponseReadyCallbackMethodMatcher.java @@ -0,0 +1,36 @@ +package com.newrelic.instrumentation.mule.http.connector; + +import java.util.Set; +import java.util.logging.Level; + +import com.newrelic.agent.deps.org.objectweb.asm.commons.Method; +import com.newrelic.agent.instrumentation.methodmatchers.MethodMatcher; +import com.newrelic.agent.instrumentation.methodmatchers.NameMethodMatcher; +import com.newrelic.agent.instrumentation.methodmatchers.OrMethodMatcher; +import com.newrelic.api.agent.NewRelic; + +public class HttpResponseReadyCallbackMethodMatcher implements MethodMatcher { + + private MethodMatcher actual = null; + + public HttpResponseReadyCallbackMethodMatcher() { + actual = OrMethodMatcher.getMethodMatcher(new NameMethodMatcher(HttpResponseReadyCallbackClassMethodMatcher.RESPONSE_READY), + new NameMethodMatcher(HttpResponseReadyCallbackClassMethodMatcher.START_RESPONSE)); + } + + @Override + public boolean matches(int access, String name, String desc, Set annotations) { + boolean b = actual.matches(access, name, desc, annotations); + if(b) { + NewRelic.getAgent().getLogger().log(Level.FINE, "Found HttpResponseReadyCallback method match, {0}, {1}" , name,desc); + } + return b; + } + + @Override + public Method[] getExactMethods() { + + return actual.getExactMethods(); + } + +} diff --git a/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/MuleHttpConnectorFactory.java b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/MuleHttpConnectorFactory.java new file mode 100644 index 0000000..7d5b3ee --- /dev/null +++ b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/MuleHttpConnectorFactory.java @@ -0,0 +1,82 @@ +package com.newrelic.instrumentation.mule.http.connector; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; + +import com.newrelic.agent.Transaction; +import com.newrelic.agent.tracers.AbstractTracerFactory; +import com.newrelic.agent.tracers.ClassMethodSignature; +import com.newrelic.agent.tracers.DefaultTracer; +import com.newrelic.agent.tracers.Tracer; +import com.newrelic.agent.tracers.TracerFlags; +import com.newrelic.agent.tracers.metricname.MetricNameFormat; +import com.newrelic.agent.tracers.metricname.SimpleMetricNameFormat; +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; + +public class MuleHttpConnectorFactory extends AbstractTracerFactory { + + @Override + public Tracer doGetTracer(Transaction transaction, ClassMethodSignature sig, Object object, Object[] args) { + + String classname = object.getClass().getName(); + MetricNameFormat format = null; + String methodName = sig.getMethodName(); + Token token = null; + NewRelic.getAgent().getLogger().log(Level.FINE, "MuleHttpConnectorFactory.getTracer({0},{1},{2},{3})", transaction,sig,object,args); + + if(classname.equals(HttpListenerClassMethodMatcher.HTTPLISTENER)) { + format = new SimpleMetricNameFormat("Custom/HttpListener/"+methodName); + } else if(classname.equals(HttpRequesterClassMethodMatcher.HTTPREQUESTER)) { + format = new SimpleMetricNameFormat("Custom/HttpRequester/onRequest"); + } else if(classname.equals(ResponseSenderMatcher.RESPONSESENDER)) { + format = new SimpleMetricNameFormat("Custom/HttpListenerResponseSender/sendResponse"); + } else { + if(methodName.equals(RequestHandlerMatcher.CREATERESULT)) { + format = new SimpleMetricNameFormat("Custom/ModuleRequestHandler/createResult"); + if(args.length > 1) { + + } + } else if(methodName.equals(RequestHandlerMatcher.HANDLEREQUEST)) { + format = new SimpleMetricNameFormat("Custom/RequestHandler/handleRequest"); + if(args.length > 1) { + Integer hash = args[1].hashCode(); + boolean b = TracerUtils.addToken(hash); + + if(b) { + NewRelic.getAgent().getLogger().log(Level.FINE, "MuleHttpConnectorFactory created token for object with hash {0} and object {1}",hash,args[1]); + } else { + NewRelic.getAgent().getLogger().log(Level.FINE, "MuleHttpConnectorFactory did not create token for object with hash {0} and object {1}",hash, args[1]); + } + + } + } else if(methodName.endsWith(HttpResponseReadyCallbackClassMethodMatcher.RESPONSE_READY)) { + NewRelic.getAgent().getLogger().log(Level.FINE, "MuleHttpConnectorFactory creating tracer for HttpResponseReadyCallback.responseReady, object: {0}", object); + + format = new SimpleMetricNameFormat("Custom/HttpResponseReadyCallback/responseReady"); + Integer hash = object.hashCode(); + token = TracerUtils.getToken(hash); + NewRelic.getAgent().getLogger().log(Level.FINE, "MuleHttpConnectorFactory retrieved token {0} and object with hash {1} and method responseReady", token, hash); + + } else if(methodName.equals(HttpResponseReadyCallbackClassMethodMatcher.START_RESPONSE)) { + NewRelic.getAgent().getLogger().log(Level.FINE, "MuleHttpConnectorFactory creating tracer for HttpResponseReadyCallback.startResponse, object: {0}", object); + format = new SimpleMetricNameFormat("Custom/HttpResponseReadyCallback/startResponse"); + Integer hash = object.hashCode(); + token = TracerUtils.getToken(hash); + NewRelic.getAgent().getLogger().log(Level.FINE, "MuleHttpConnectorFactory retrieved token {0} and object with hash {1} and method startResponse", token, hash); + + } + } + if(format != null) { + if(token != null) { + HttpListenerTracer t = new HttpListenerTracer(transaction, sig, object,format,TracerFlags.DISPATCHER|TracerFlags.ASYNC|DefaultTracer.DEFAULT_TRACER_FLAGS); + t.setAsync(true); + t.setToken(token); + return t; + } + return new HttpListenerTracer(transaction, sig, object,format); + } + return new HttpListenerTracer(transaction, sig, object); + } + +} diff --git a/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/MuleHttpConnectorFactory2.java b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/MuleHttpConnectorFactory2.java new file mode 100644 index 0000000..1e4f775 --- /dev/null +++ b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/MuleHttpConnectorFactory2.java @@ -0,0 +1,79 @@ +package com.newrelic.instrumentation.mule.http.connector; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; + +import com.newrelic.agent.Transaction; +import com.newrelic.agent.tracers.AbstractTracerFactory; +import com.newrelic.agent.tracers.ClassMethodSignature; +import com.newrelic.agent.tracers.DefaultTracer; +import com.newrelic.agent.tracers.Tracer; +import com.newrelic.agent.tracers.TracerFlags; +import com.newrelic.agent.tracers.metricname.MetricNameFormat; +import com.newrelic.agent.tracers.metricname.SimpleMetricNameFormat; +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; + +public class MuleHttpConnectorFactory2 extends AbstractTracerFactory { + + + @Override + public Tracer doGetTracer(Transaction transaction, ClassMethodSignature sig, Object object, Object[] args) { + + String classname = object.getClass().getName(); + MetricNameFormat format = null; + String methodName = sig.getMethodName(); + Token token = null; + NewRelic.getAgent().getLogger().log(Level.FINE, "MuleHttpConnectorFactory2.getTracer({0},{1},{2},{3})", transaction,sig,object,args); + + if(classname.equals(HttpListenerClassMethodMatcher.HTTPLISTENER)) { + format = new SimpleMetricNameFormat("Custom/HttpListener/"+methodName); + } else if(classname.equals(HttpRequesterClassMethodMatcher.HTTPREQUESTER)) { + format = new SimpleMetricNameFormat("Custom/HttpRequester/onRequest"); + } else if(classname.equals(ResponseSenderMatcher.RESPONSESENDER)) { + format = new SimpleMetricNameFormat("Custom/HttpListenerResponseSender/sendResponse"); + } else { + if(methodName.equals(RequestHandlerMatcher.CREATERESULT)) { + format = new SimpleMetricNameFormat("Custom/ModuleRequestHandler/createResult"); + if(args.length > 1) { + + } + } else if(methodName.equals(RequestHandlerMatcher.HANDLEREQUEST)) { + format = new SimpleMetricNameFormat("Custom/RequestHandler/handleRequest"); + if(args.length > 1) { + Integer hash = args[1].hashCode(); + boolean b = TracerUtils.addToken(hash); + if(b) { + NewRelic.getAgent().getLogger().log(Level.FINE, "MuleHttpConnectorFactory2 created token for object with hash {0}", hash); + } + } + } else if(methodName.endsWith(HttpResponseReadyCallbackClassMethodMatcher.RESPONSE_READY)) { + NewRelic.getAgent().getLogger().log(Level.FINE, "MuleHttpConnectorFactory2 creating tracer for HttpResponseReadyCallback.responseReady, object: {0}", object); + + format = new SimpleMetricNameFormat("Custom/HttpResponseReadyCallback/responseReady"); + Integer hash = object.hashCode(); + token = TracerUtils.getToken(hash); + NewRelic.getAgent().getLogger().log(Level.FINE, "MuleHttpConnectorFactory2 retrieved token {0} and object with hash {1} and method responseReady", token, hash); + + } else if(methodName.equals(HttpResponseReadyCallbackClassMethodMatcher.START_RESPONSE)) { + NewRelic.getAgent().getLogger().log(Level.FINE, "MuleHttpConnectorFactory2 creating tracer for HttpResponseReadyCallback.startResponse, object: {0}", object); + format = new SimpleMetricNameFormat("Custom/HttpResponseReadyCallback/startResponse"); + Integer hash = object.hashCode(); + token = TracerUtils.getToken(hash); + NewRelic.getAgent().getLogger().log(Level.FINE, "MuleHttpConnectorFactory2 retrieved token {0} and object with hash {1} and method startResponse", token, hash); + + } + } + if(format != null) { + if(token != null) { + HttpListenerTracer t = new HttpListenerTracer(transaction, sig, object,format,TracerFlags.DISPATCHER|TracerFlags.ASYNC|DefaultTracer.DEFAULT_TRACER_FLAGS); + t.setAsync(true); + t.setToken(token); + return t; + } + return new HttpListenerTracer(transaction, sig, object,format); + } + return new HttpListenerTracer(transaction, sig, object); + } + +} diff --git a/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/MuleHttpConnectorService.java b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/MuleHttpConnectorService.java new file mode 100644 index 0000000..79704c8 --- /dev/null +++ b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/MuleHttpConnectorService.java @@ -0,0 +1,92 @@ +package com.newrelic.instrumentation.mule.http.connector; + +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +import com.newrelic.agent.InstrumentationProxy; +import com.newrelic.agent.TracerService; +import com.newrelic.agent.core.CoreService; +import com.newrelic.agent.instrumentation.ClassTransformerService; +import com.newrelic.agent.instrumentation.classmatchers.ClassAndMethodMatcher; +import com.newrelic.agent.instrumentation.context.InstrumentationContextManager; +import com.newrelic.agent.service.AbstractService; +import com.newrelic.agent.service.ServiceFactory; +import com.newrelic.api.agent.NewRelic; + +public class MuleHttpConnectorService extends AbstractService { + + + public static final String TRACER_FACTORY_NAME = "MuleHttpConnector"; + public static final String TRACER_FACTORY_NAME2 = "MuleHttpConnector2"; + + public MuleHttpConnectorService() { + super("MuleHttpConnectorService"); + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + protected void doStart() throws Exception { + boolean started = setup(); + if(!started) { + Executors.newSingleThreadScheduledExecutor().schedule(new SetupThread(), 10L, TimeUnit.SECONDS); + } + } + + @Override + protected void doStop() throws Exception { + + } + + public boolean setup() { + TracerService tracerService = ServiceFactory.getTracerService(); + ClassTransformerService classTransformerService = ServiceFactory.getClassTransformerService(); + CoreService coreService = ServiceFactory.getCoreService(); + if(classTransformerService != null && coreService != null && tracerService != null) { + + tracerService.registerTracerFactory(TRACER_FACTORY_NAME, new MuleHttpConnectorFactory()); + tracerService.registerTracerFactory(TRACER_FACTORY_NAME2, new MuleHttpConnectorFactory2()); + + InstrumentationContextManager contextMgr = classTransformerService.getContextManager(); + InstrumentationProxy proxy = coreService.getInstrumentation(); + if(contextMgr != null && proxy != null) { + MuleHttpConnectorTransformer transformer = new MuleHttpConnectorTransformer(contextMgr, proxy); + ClassAndMethodMatcher matcher = new HttpListenerClassMethodMatcher(); + transformer.addMatcher(matcher,TRACER_FACTORY_NAME); + transformer.addMatcher(new HttpRequesterClassMethodMatcher(),TRACER_FACTORY_NAME); + transformer.addMatcher(new RequestHandlerMatcher(),TRACER_FACTORY_NAME); + transformer.addMatcher(new ResponseSenderMatcher(),TRACER_FACTORY_NAME); + transformer.addMatcher(new HttpResponseReadyCallbackClassMethodMatcher(),TRACER_FACTORY_NAME); + NewRelic.getAgent().getLogger().log(Level.INFO, "Mule HttpListener transformer started"); + return true; + } + } + + return false; + } + + private class SetupThread extends Thread { + + @Override + public void run() { + boolean started = false; + + while(!started) { + started = setup(); + if(!started) { + try { + Thread.sleep(3000L); + } catch (InterruptedException e) { + } + } + } + + } + + + } +} diff --git a/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/MuleHttpConnectorTransformer.java b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/MuleHttpConnectorTransformer.java new file mode 100644 index 0000000..1518557 --- /dev/null +++ b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/MuleHttpConnectorTransformer.java @@ -0,0 +1,75 @@ +package com.newrelic.instrumentation.mule.http.connector; + +import java.lang.instrument.IllegalClassFormatException; +import java.security.ProtectionDomain; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; + +import com.newrelic.agent.InstrumentationProxy; +import com.newrelic.agent.deps.org.objectweb.asm.commons.Method; +import com.newrelic.agent.instrumentation.classmatchers.ClassAndMethodMatcher; +import com.newrelic.agent.instrumentation.classmatchers.OptimizedClassMatcher.Match; +import com.newrelic.agent.instrumentation.classmatchers.OptimizedClassMatcherBuilder; +import com.newrelic.agent.instrumentation.context.ClassMatchVisitorFactory; +import com.newrelic.agent.instrumentation.context.ContextClassTransformer; +import com.newrelic.agent.instrumentation.context.InstrumentationContext; +import com.newrelic.agent.instrumentation.context.InstrumentationContextManager; +import com.newrelic.agent.instrumentation.methodmatchers.MethodMatcher; +import com.newrelic.agent.instrumentation.tracing.TraceDetailsBuilder; +import com.newrelic.api.agent.NewRelic; + +public class MuleHttpConnectorTransformer implements ContextClassTransformer { + + private final InstrumentationContextManager contextManager; + private final Map matchers = new HashMap(); + private final Map factoryMappings = new HashMap<>(); + + public MuleHttpConnectorTransformer(InstrumentationContextManager mgr,InstrumentationProxy pInstrumentation) { + contextManager = mgr; + } + + protected void addMatcher(ClassAndMethodMatcher matcher,String tracerFactory) { + NewRelic.getAgent().getLogger().log(Level.FINE, "Call to MuleHttpConnectorTransformer.addMatcher({0})", matcher); + OptimizedClassMatcherBuilder builder = OptimizedClassMatcherBuilder.newBuilder(); + builder.addClassMethodMatcher(matcher); + ClassMatchVisitorFactory matchVisitor = builder.build(); + String simpleName = matcher.getClass().getSimpleName(); + matchers.put(simpleName, matchVisitor); + + factoryMappings.put(simpleName, tracerFactory); + contextManager.addContextClassTransformer(matchVisitor, this); + } + + protected void removeMatcher(ClassAndMethodMatcher matcher) { + ClassMatchVisitorFactory matchVisitor = matchers.get(matcher.getClass().getSimpleName()); + if(matchVisitor != null) { + contextManager.removeMatchVisitor(matchVisitor); + } + } + + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer, InstrumentationContext context, Match match) + throws IllegalClassFormatException { + + NewRelic.getAgent().getLogger().log(Level.FINE, "Call to MuleHttpConnectorTransformer.transform({0},{1},{2},{3},{4},{5},{6})", loader,className,classBeingRedefined,protectionDomain,classfileBuffer,context,match); + + for (Method method : match.getMethods()) { + for (ClassAndMethodMatcher matcher : match.getClassMatches().keySet()) { + if (matcher.getMethodMatcher().matches(MethodMatcher.UNSPECIFIED_ACCESS, method.getName(), + method.getDescriptor(), match.getMethodAnnotations(method))) { + String factoryName = factoryMappings.getOrDefault(matcher.getClass().getSimpleName(), MuleHttpConnectorService.TRACER_FACTORY_NAME); + + context.putTraceAnnotation(method, TraceDetailsBuilder.newBuilder().setTracerFactoryName(factoryName).build()); + NewRelic.getAgent().getLogger().log(Level.FINE, "Call to MuleHttpConnectorTransformer.transform matched method {0} to matcher {1} and uses tracerfactory {2}", method.getName(), matcher,factoryName); + + } + + } + } + + return null; + } + + +} diff --git a/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/RequestHandlerMatcher.java b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/RequestHandlerMatcher.java new file mode 100644 index 0000000..4e4a6f4 --- /dev/null +++ b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/RequestHandlerMatcher.java @@ -0,0 +1,41 @@ +package com.newrelic.instrumentation.mule.http.connector; + +import java.util.ArrayList; +import java.util.List; + +import com.newrelic.agent.instrumentation.classmatchers.ClassAndMethodMatcher; +import com.newrelic.agent.instrumentation.classmatchers.ClassMatcher; +import com.newrelic.agent.instrumentation.classmatchers.InterfaceMatcher; +import com.newrelic.agent.instrumentation.classmatchers.OrClassMatcher; +import com.newrelic.agent.instrumentation.methodmatchers.MethodMatcher; +import com.newrelic.agent.instrumentation.methodmatchers.NameMethodMatcher; +import com.newrelic.agent.instrumentation.methodmatchers.OrMethodMatcher; + +public class RequestHandlerMatcher implements ClassAndMethodMatcher { + + private static final String REQUESTHANDLER_INTERFACE = "org.mule.runtime.http.api.server.RequestHandler"; + private static final String MODULEREQUESTHANDLER_INTERFACE = "org.mule.extension.http.internal.listener.server.ModuleRequestHandler"; + protected static final String HANDLEREQUEST = "handleRequest"; + protected static final String CREATERESULT = "createResult"; + + @Override + public ClassMatcher getClassMatcher() { + InterfaceMatcher reqHandlerMatcher = new InterfaceMatcher(REQUESTHANDLER_INTERFACE); + InterfaceMatcher modReqHandlerMatcher = new InterfaceMatcher(MODULEREQUESTHANDLER_INTERFACE); + List matchers = new ArrayList(); + matchers.add(modReqHandlerMatcher); + matchers.add(reqHandlerMatcher); + + + return new OrClassMatcher(matchers); + } + + @Override + public MethodMatcher getMethodMatcher() { + NameMethodMatcher matcher1 = new NameMethodMatcher(HANDLEREQUEST); + NameMethodMatcher matcher2 = new NameMethodMatcher(CREATERESULT); + + return OrMethodMatcher.getMethodMatcher(matcher1,matcher2); + } + +} diff --git a/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/ResponseSenderMatcher.java b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/ResponseSenderMatcher.java new file mode 100644 index 0000000..58b3109 --- /dev/null +++ b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/ResponseSenderMatcher.java @@ -0,0 +1,24 @@ +package com.newrelic.instrumentation.mule.http.connector; + +import com.newrelic.agent.instrumentation.classmatchers.ClassAndMethodMatcher; +import com.newrelic.agent.instrumentation.classmatchers.ClassMatcher; +import com.newrelic.agent.instrumentation.classmatchers.ExactClassMatcher; +import com.newrelic.agent.instrumentation.methodmatchers.MethodMatcher; +import com.newrelic.agent.instrumentation.methodmatchers.NameMethodMatcher; + +public class ResponseSenderMatcher implements ClassAndMethodMatcher { + + protected static final String RESPONSESENDER = "org.mule.extension.http.internal.listener.HttpListenerResponseSender"; + + @Override + public ClassMatcher getClassMatcher() { + + return new ExactClassMatcher(RESPONSESENDER); + } + + @Override + public MethodMatcher getMethodMatcher() { + return new NameMethodMatcher("sendResponse"); + } + +} diff --git a/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/TracerUtils.java b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/TracerUtils.java new file mode 100644 index 0000000..71d9053 --- /dev/null +++ b/Mule-HttpConnector/src/main/java/com/newrelic/instrumentation/mule/http/connector/TracerUtils.java @@ -0,0 +1,30 @@ +package com.newrelic.instrumentation.mule.http.connector; + +import java.util.concurrent.ConcurrentHashMap; + +import com.newrelic.api.agent.NewRelic; +import com.newrelic.api.agent.Token; + +public class TracerUtils { + + + private static ConcurrentHashMap tokenCache = new ConcurrentHashMap<>(); + + public static boolean addToken(Integer hash) { + if(!tokenCache.containsKey(hash)) { + Token token = NewRelic.getAgent().getTransaction().getToken(); + if(token != null && token.isActive()) { + tokenCache.put(hash, token); + return true; + } else { + token.expire(); + token = null; + } + } + return false; + } + + public static Token getToken(Integer hash) { + return tokenCache.remove(hash); + } +}