Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions Kotlin-Coroutines_1.4-nativemt/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@


apply plugin: 'java'
apply plugin: 'org.jetbrains.kotlin.jvm'

dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1-native-mt")

// New Relic Java Agent dependencies
implementation fileTree(include: ['*.jar'], dir: '../libs')
}

jar {
manifest {
attributes 'Implementation-Title': 'com.newrelic.instrumentation.labs.Kotlin-Coroutines_1.4-native-mt'
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.4.0,1.7.0)'
// passes 'org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:[1.4.0,1.7.0)'
passes 'org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:[1.5.1.native-mt,1.7.0)'
excludeRegex '.*SNAPSHOT'
excludeRegex '.*alpha'
excludeRegex '.*-eap-.*'
excludeRegex '.*-native-.*'
excludeRegex '.*-M[0-9]'
excludeRegex '.*-rc'
excludeRegex '.*-RC.*'
excludeRegex '.*-Beta'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.newrelic.instrumentation.kotlin.coroutines_14;

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

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

/*
* Used to ignore Dispatched Tasks that the user has configured to ignore
*/
public class DispatchedTaskIgnores {

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

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

}


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

public static void addIgnoredTasks(Collection<String> toIgnore) {
ignoredTasks.addAll(toIgnore);
}

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,56 @@
package com.newrelic.instrumentation.kotlin.coroutines_14;

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;
import org.jetbrains.annotations.NotNull;

/*
* Used to wrap a Continuation instance. Necessary to control which Continuations
* are tracked. Tracking all Continuations can effect performance.
* */
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;
}
}

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

@Override
@Trace(async=true)
public void resumeWith(@NotNull 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,146 @@
package com.newrelic.instrumentation.kotlin.coroutines_14;

import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Segment;
import kotlin.Unit;
import kotlin.coroutines.CoroutineContext;
import kotlin.jvm.functions.Function1;
import kotlinx.coroutines.CancellableContinuation;
import kotlinx.coroutines.CoroutineDispatcher;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/*
* Used to wrap the tracking of a call to the Coroutine delay function if tracking is enabled
* Will report the delay as a segment
*/
public class NRDelayCancellableContinuation<T> implements CancellableContinuation<T> {

private final CancellableContinuation<T> delegate;
private Segment segment;

public NRDelayCancellableContinuation(CancellableContinuation<T> delegate, String type) {
this.delegate = delegate;
String name = Utils.getContinuationString(delegate);
segment = NewRelic.getAgent().getTransaction().startSegment(type);
segment.addCustomAttribute("CancellableContinuation", name);
}

@Override
public void resumeWith(@NotNull Object o) {
if(segment != null) {
segment.end();
segment = null;
}
if(delegate != null) {
delegate.resumeWith(o);
}
}

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

@Override
public boolean isActive() {
return delegate.isActive();
}

@Override
public boolean isCompleted() {
return delegate.isCompleted();
}

@Override
public boolean isCancelled() {
return delegate.isCancelled();
}

@Override
public @Nullable Object tryResume(T t, @Nullable Object o) {
if(segment != null) {
segment.end();
segment = null;
}
return delegate.tryResume(t,o);
}

@Override
public @Nullable Object tryResume(T t, @Nullable Object o, @Nullable Function1<? super Throwable, Unit> function1) {
if(segment != null) {
segment.end();
segment = null;
}
return delegate.tryResume(t,o, function1);
}

@Override
public @Nullable Object tryResumeWithException(@NotNull Throwable throwable) {
NewRelic.noticeError(throwable);
if(segment != null) {
segment.end();
segment = null;
}
return delegate.tryResumeWithException(throwable);
}

@Override
public void completeResume(@NotNull Object o) {
if(segment != null) {
segment.end();
segment = null;
}
delegate.completeResume(o);
}

@Override
public void initCancellability() {
delegate.initCancellability();
}

@Override
public boolean cancel(@Nullable Throwable throwable) {
if(segment != null) {
segment.end();
segment = null;
}
return delegate.cancel(throwable);
}

@Override
public void invokeOnCancellation(@NotNull Function1<? super Throwable, Unit> function1) {
if(segment != null) {
segment.end();
segment = null;
}
delegate.invokeOnCancellation(function1);
}

@Override
public void resumeUndispatched(@NotNull CoroutineDispatcher coroutineDispatcher, T t) {
if(segment != null) {
segment.end();
segment = null;
}
delegate.resumeUndispatched(coroutineDispatcher, t);
}

@Override
public void resumeUndispatchedWithException(@NotNull CoroutineDispatcher coroutineDispatcher, @NotNull Throwable throwable) {
if(segment != null) {
segment.end();
segment = null;
}
delegate.resumeUndispatchedWithException(coroutineDispatcher, throwable);
}

@Override
public void resume(T t, @Nullable Function1<? super Throwable, Unit> function1) {
if(segment != null) {
segment.end();
segment = null;
}
delegate.resume(t,function1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.newrelic.instrumentation.kotlin.coroutines_14;

import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Segment;
import kotlin.coroutines.Continuation;
import kotlin.coroutines.CoroutineContext;
import kotlinx.coroutines.CancellableContinuation;
import org.jetbrains.annotations.NotNull;

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

private final Continuation<T> delegate;
private final Segment segment;

public NRDelayContinuation(Continuation<T> delegate, String type) {
this.delegate = delegate;
String name = Utils.getContinuationString(delegate);
segment = NewRelic.getAgent().getTransaction().startSegment(type);
segment.addCustomAttribute("Continuation", name);
boolean isCancellable = delegate instanceof CancellableContinuation;
segment.addCustomAttribute("isCancellable", isCancellable);
}

@Override
public void resumeWith(@NotNull Object o) {
if(segment != null) {
segment.end();
}
if(delegate != null) {
delegate.resumeWith(o);
}
}

@Override
public @NotNull CoroutineContext getContext() {
return delegate.getContext();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.newrelic.instrumentation.kotlin.coroutines_14;

import com.newrelic.agent.bridge.AgentBridge;
import com.newrelic.api.agent.NewRelic;
import kotlin.coroutines.Continuation;
import kotlin.jvm.functions.Function1;

/*
* Used to wrap a suspend function that was used as an input as a parameter to the
* Coroutine functions runBlocking, async, invoke, launch and withContext
*/
public class NRFunction1SuspendWrapper<P1, R> implements Function1<P1, R> {

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

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

@Override
public R invoke(P1 p1) {
if(p1 instanceof Continuation) {
Continuation<?> cont = (Continuation<?>)p1;

String cont_string = Utils.getContinuationString(cont);
if(cont_string != null) {
NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Block","SuspendFunction",cont_string);
} else {
NewRelic.getAgent().getTracedMethod().setMetricName("Custom","Block","SuspendFunction","UnknownSource");

}
}
return delegate != null ? delegate.invoke(p1) : null;
}

}
Loading
Loading