Skip to content

Commit

Permalink
Merge pull request #35 from newrelic/support_1.9
Browse files Browse the repository at this point in the history
updated to support Kotlin Coroutines 1.9
  • Loading branch information
dhilpipre authored Oct 2, 2024
2 parents bd8baa3 + 93cb817 commit 065a6f3
Show file tree
Hide file tree
Showing 26 changed files with 1,264 additions and 0 deletions.
38 changes: 38 additions & 0 deletions Kotlin-Coroutines_1.9/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

// Build.gradle generated for instrumentation module Kotlin-Coroutines_1.2

apply plugin: 'java'

targetCompatibility = JavaVersion.VERSION_1_9

dependencies {
implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: '1.9.0'

// New Relic Java Agent dependencies
implementation 'com.newrelic.agent.java:newrelic-agent:6.0.0'
implementation 'com.newrelic.agent.java:newrelic-api:6.0.0'
implementation fileTree(include: ['*.jar'], dir: '../libs')
}

jar {
manifest {
attributes 'Implementation-Title': 'com.newrelic.instrumentation.labs.Kotlin-Coroutines_1.9'
attributes 'Implementation-Vendor': 'New Relic Labs'
attributes 'Implementation-Vendor-Id': 'com.newrelic.labs'
attributes 'Implementation-Version': 2.0
}
}

verifyInstrumentation {
passes 'org.jetbrains.kotlinx:kotlinx-coroutines-core:[1.9.0,)]'
passes 'org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:[1.9.0,)'
excludeRegex '.*SNAPSHOT'
excludeRegex '.*alpha'
excludeRegex '.*-eap-.*'
excludeRegex '.*-native-.*'
excludeRegex '.*-M[0-9]'
excludeRegex '.*-rc'
excludeRegex '.*-RC'
excludeRegex '.*-RC[0-9]'
excludeRegex '.*-Beta'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.newrelic.instrumentation.kotlin.coroutines_17;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;

import com.newrelic.api.agent.Config;
import com.newrelic.api.agent.NewRelic;

public class DispatchedTaskIgnores {

private static List<String> ignoredTasks = new ArrayList<>();
private static final String DISPATCHEDIGNORECONFIG = "Coroutines.ignores.dispatched";

static {
Config config = NewRelic.getAgent().getConfig();
String ignores = config.getValue(DISPATCHEDIGNORECONFIG);
configure(ignores);

}


public static boolean ignoreDispatchedTask(String contString) {
return ignoredTasks.contains(contString);
}

public static void addIgnore(String ignore) {
if(!ignoredTasks.contains(ignore)) {
ignoredTasks.add(ignore);
NewRelic.getAgent().getLogger().log(Level.FINE, "Will ignore DispatchedTasks with continuation string {0}", ignore);
}
}

public static void reset() {
ignoredTasks.clear();
}

protected static void configure(String result) {
if(result == null || result.isEmpty()) return;
String[] ignores = result.split(",");
for(String ignore : ignores) {
addIgnore(ignore);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.newrelic.instrumentation.kotlin.coroutines_17;

import com.newrelic.agent.bridge.AgentBridge;
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Token;
import com.newrelic.api.agent.Trace;

import kotlin.coroutines.Continuation;
import kotlin.coroutines.CoroutineContext;

public class NRContinuationWrapper<T> implements Continuation<T> {

private Continuation<T> delegate = null;
private String name = null;
private static boolean isTransformed = false;

public NRContinuationWrapper(Continuation<T> d, String n) {
delegate = d;
name = n;
if(!isTransformed) {
AgentBridge.instrumentation.retransformUninstrumentedClass(getClass());
isTransformed = true;
}
}

@Override
public CoroutineContext getContext() {
return delegate.getContext();
}

@Override
@Trace(async=true)
public void resumeWith(Object p0) {
String contString = Utils.getContinuationString(delegate);
if(contString != null && !contString.isEmpty()) {
NewRelic.getAgent().getTracedMethod().setMetricName("Custom","ContinuationWrapper","resumeWith",contString);
} else if(name != null) {
NewRelic.getAgent().getTracedMethod().setMetricName("Custom","ContinuationWrapper","resumeWith",name);
} else {
NewRelic.getAgent().getTracedMethod().setMetricName("Custom","ContinuationWrapper","resumeWith",p0.getClass().getName());
}
Token t = Utils.getToken(getContext());
if(t != null) {
t.link();
}
if(delegate != null) {
delegate.resumeWith(p0);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.newrelic.instrumentation.kotlin.coroutines_17;

import com.newrelic.api.agent.Token;

import kotlin.coroutines.AbstractCoroutineContextElement;
import kotlin.coroutines.CoroutineContext;

public class NRCoroutineToken extends AbstractCoroutineContextElement
{
public static Key key = new Key();

public NRCoroutineToken(Token t) {
super(key);
token = t;
}

private Token token = null;

public static final class Key implements CoroutineContext.Key<NRCoroutineToken> {
private Key() {}
}

public Token getToken() {
return token;
}

@Override
public int hashCode() {
return token.hashCode();
}

@Override
public boolean equals(Object obj) {
if(this != obj ) {
if(obj instanceof NRCoroutineToken) {
NRCoroutineToken t = (NRCoroutineToken)obj;
return t.token == token;
}
} else {
return true;
}
return false;
}

@Override
public String toString() {
return "NRCoroutineToken";
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.newrelic.instrumentation.kotlin.coroutines_17;

import com.newrelic.agent.bridge.AgentBridge;

import kotlin.coroutines.Continuation;
import kotlin.coroutines.jvm.internal.SuspendFunction;
import kotlin.jvm.functions.Function1;

public class NRFunction1Wrapper<P1, R> implements Function1<P1, R> {

private Function1<P1, R> delegate = null;
private static boolean isTransformed = false;

public NRFunction1Wrapper(Function1<P1,R> d) {
delegate = d;
if(!isTransformed) {
AgentBridge.instrumentation.retransformUninstrumentedClass(getClass());
isTransformed = true;
}
}

@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public R invoke(P1 p1) {
if(p1 instanceof Continuation && !(p1 instanceof SuspendFunction)) {
// wrap if needed
if(!(p1 instanceof NRContinuationWrapper)) {
String cont_string = Utils.getContinuationString((Continuation)p1);
NRContinuationWrapper wrapper = new NRContinuationWrapper<>((Continuation)p1, cont_string);
p1 = (P1) wrapper;
}
}
return delegate != null ? delegate.invoke(p1) : null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.newrelic.instrumentation.kotlin.coroutines_17;

import com.newrelic.agent.bridge.AgentBridge;

import kotlin.coroutines.Continuation;
import kotlin.coroutines.jvm.internal.SuspendFunction;
import kotlin.jvm.functions.Function2;

public class NRFunction2Wrapper<P1, P2, R> implements Function2<P1, P2, R> {

private Function2<P1, P2, R> delegate = null;
private static boolean isTransformed = false;

public NRFunction2Wrapper(Function2<P1, P2, R> d) {
delegate = d;
if(!isTransformed) {
isTransformed = true;
AgentBridge.instrumentation.retransformUninstrumentedClass(getClass());
}
}

@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public R invoke(P1 p1, P2 p2) {

if(p2 instanceof Continuation && !(p2 instanceof SuspendFunction)) {
// wrap if needed
String cont_string = Utils.getContinuationString((Continuation)p2);
if(!(p2 instanceof NRContinuationWrapper) && !Utils.ignoreContinuation(cont_string)) {
NRContinuationWrapper wrapper = new NRContinuationWrapper<>((Continuation)p2, cont_string);
p2 = (P2) wrapper;
}
}
if(delegate != null) {
return delegate.invoke(p1, p2);
}
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.newrelic.instrumentation.kotlin.coroutines_17;

import com.newrelic.agent.bridge.AgentBridge;
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Token;
import com.newrelic.api.agent.Trace;

import kotlin.coroutines.Continuation;
import kotlinx.coroutines.DispatchedTask;

public class NRRunnable implements Runnable {

private Runnable delegate = null;
private Token token = null;
private static boolean isTransformed = false;

public NRRunnable(Runnable r,Token t) {
if(!isTransformed) {
AgentBridge.instrumentation.retransformUninstrumentedClass(getClass());
isTransformed = true;
}
delegate = r;
token = t;
}

@Override
@Trace(async = true)
public void run() {
boolean nameSet = false;
if(delegate != null && delegate instanceof DispatchedTask) {
DispatchedTask<?> task = (DispatchedTask<?>)delegate;
Continuation<?> cont_delegate = task.getDelegate$kotlinx_coroutines_core();
if(cont_delegate != null) {
String cont_string = Utils.getContinuationString(cont_delegate);
if(cont_string == null) cont_string = cont_delegate.getClass().getName();
NewRelic.getAgent().getTracedMethod().setMetricName("Custom","DispatchedTask",Utils.getContinuationString(cont_delegate));
nameSet = true;
}
}
if(!nameSet) {
String delegateType = delegate != null ? delegate.getClass().getName() : "null";
NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Kotlin","AsyncRunnableWrapper",delegateType);
}
if(token != null) {
token.linkAndExpire();
token = null;
}
if(delegate != null) {
delegate.run();
}
}

}
Loading

0 comments on commit 065a6f3

Please sign in to comment.