Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
8119e41
adding Ktor client instrumentation
May 18, 2026
38b769c
adding Ktor client instrumentation
May 18, 2026
0fdaf7b
restore gradle.properties
May 18, 2026
2521bcb
initial checkout of ktor core
May 18, 2026
304e93e
fix inconsistencies in applying ignores
May 18, 2026
feb80cc
Merge branch 'kotlin-ignore-changes' into add-ktor-core
May 18, 2026
35f62df
modifications to Ktor Service
May 18, 2026
94eb5f9
make it easier to ignore a Kotlin Coroutine built framework (e.g. Ktor)
May 18, 2026
7270c38
Merge branch 'change_kotlin_ignores' into add-ktor-core
May 18, 2026
1257784
added Ktor to ignored frameworks
May 18, 2026
919d9f9
Merge branch 'add-ktor-core' into add-ktor-clients
May 18, 2026
95473b5
add support for Ktor Pipelines
May 18, 2026
697a51d
Merge branch 'add-ktor-core' into add-ktor-servers
May 18, 2026
04a3b8b
initial checkin
May 19, 2026
15dfc8b
added header
May 19, 2026
839ee17
code cleanup, fixed verify on core, added copywrite headers
May 19, 2026
dc9744f
code cleanup
May 19, 2026
7acfc01
"merging"Merge branch 'main' into change_kotlin_ignores
sharvath-newrelic Jun 15, 2026
2006469
Fix broken coroutines tests
sharvath-newrelic Jun 15, 2026
40e3ee1
Revert gradle property inclusions
sharvath-newrelic Jun 16, 2026
81bdb68
Add newline to properties file
sharvath-newrelic Jun 16, 2026
ea02ddd
"hydrating from base"Merge branch 'change_kotlin_ignores' into add-kt…
sharvath-newrelic Jun 16, 2026
0ea1ace
"hydrating"Merge branch 'add-ktor-core' into add-ktor-utils
sharvath-newrelic Jun 16, 2026
b6bff85
resolving merge conflict
sharvath-newrelic Jun 16, 2026
edcd6f1
resolve merge conflicts
sharvath-newrelic Jun 16, 2026
32e98ce
Merge pull request #2893 from newrelic/add-ktor-core
sharvath-newrelic Jun 16, 2026
55672df
Add license blocks and unit tests; fix bugs
sharvath-newrelic Jun 17, 2026
b908679
Merge pull request #2892 from newrelic/add-ktor-utils
sharvath-newrelic Jun 17, 2026
e744ed9
"hydrating"Merge branch 'change_kotlin_ignores' into add-ktor-clients
sharvath-newrelic Jun 17, 2026
80435e1
Add license blocks and unit tests; fix bugs
sharvath-newrelic Jun 22, 2026
96a28d4
Add license blocks
sharvath-newrelic Jun 22, 2026
c92d601
Merge pull request #2894 from newrelic/add-ktor-clients
sharvath-newrelic Jun 22, 2026
cfddedb
"rehydrating"Merge branch 'change_kotlin_ignores' into add-ktor-servers
sharvath-newrelic Jun 22, 2026
bfc5f15
Bug fixes, add unit tests
sharvath-newrelic Jun 23, 2026
7a2c551
Add missing license blurbs
sharvath-newrelic Jun 23, 2026
b44213d
Merge pull request #2895 from newrelic/add-ktor-servers
sharvath-newrelic Jun 23, 2026
01211e7
Fix unit tests
sharvath-newrelic Jun 23, 2026
9ae9877
Fix unit tests
sharvath-newrelic Jun 23, 2026
a2230ca
Verify instrumentation fixes
sharvath-newrelic Jun 24, 2026
ca15a71
Reduce surface area
sharvath-newrelic Jun 30, 2026
5eead4d
Fix bugs
sharvath-newrelic Jul 1, 2026
b079c71
Fix bugs, reduce surface area
sharvath-newrelic Jul 1, 2026
f1e77e4
Fix bugs
sharvath-newrelic Jul 3, 2026
e9b1259
Fix bugs
sharvath-newrelic Jul 3, 2026
2fc181f
Fix bugs
sharvath-newrelic Jul 6, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.newrelic.agent.jfr.JfrService;
import com.newrelic.agent.jmx.JmxService;
import com.newrelic.agent.kotlincoroutines.KotlinCoroutinesService;
import com.newrelic.agent.ktor.KtorService;
import com.newrelic.agent.language.SourceLanguageService;
import com.newrelic.agent.model.SpanEvent;
import com.newrelic.agent.normalization.NormalizationService;
Expand Down Expand Up @@ -91,6 +92,7 @@ class IntrospectorServiceManager extends AbstractService implements ServiceManag
private volatile ServerlessService serverlessService;
private ExpirationService expirationService;
private volatile KotlinCoroutinesService kotlinCoroutinesService;
private volatile KtorService ktorService;

private IntrospectorServiceManager(String name) {
super(name);
Expand Down Expand Up @@ -153,6 +155,7 @@ private void setup(Map<String, Object> config) {
jarCollectorService = new IgnoringJarCollectorService();
distributedTraceService = new DistributedTraceServiceImpl();
kotlinCoroutinesService = new KotlinCoroutinesService(configService.getDefaultAgentConfig().getKotlinCoroutinesConfig());
ktorService = new KtorService();

TransactionDataToDistributedTraceIntrinsics transactionDataToDistributedTraceIntrinsics = new TransactionDataToDistributedTraceIntrinsics(distributedTraceService);
transactionEventsService = new TransactionEventsService(transactionDataToDistributedTraceIntrinsics);
Expand Down Expand Up @@ -450,6 +453,10 @@ public KotlinCoroutinesService getKotlinCoroutinesService() {
return kotlinCoroutinesService;
}

public KtorService getKtorService() {
return ktorService;
}

@Override
public void refreshDataForCRaCRestore() {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.DispatchedTask;
import kotlinx.coroutines.AbstractCoroutine_Instrumentation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import java.util.*;
import java.util.logging.Level;
import java.util.regex.Pattern;

Expand All @@ -23,24 +22,20 @@ public class Utils implements CoroutineConfigListener {
private static final List<Pattern> ignoredContinuationPatterns = new ArrayList<>();
private static final List<String> ignoredScopes = new ArrayList<>();
private static final List<Pattern> ignoredScopePatterns = new ArrayList<>();
private static final Set<String> ignoredFrameworks = new HashSet<>();

public static final String CREATE_METHOD_1 = "Continuation at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$4";
public static final String CREATE_METHOD_2 = "Continuation at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$3";
private static final String CONT_LOC = "Continuation at";
public static boolean DELAYED_ENABLED = true;
private static final Utils INSTANCE = new Utils();

public static Utils getInstance() {
return INSTANCE;
}

static {
/*
* Register this class with the KotlinCoroutinesService to initialize and update
* the ignored items
*/
* Register this class with the KotlinCoroutinesService to initialize and update
* the ignored items
*/
KotlinCoroutinesService service = ServiceFactory.getKotlinCoroutinesService();
service.addCoroutineConfigListener(INSTANCE);
service.addCoroutineConfigListener(new Utils());
ignoredContinuations.add(CREATE_METHOD_1);
ignoredContinuations.add(CREATE_METHOD_2);

Expand All @@ -57,12 +52,12 @@ public static NRRunnable getRunnableWrapper(Runnable r) {
if(r instanceof DispatchedTask) {
DispatchedTask<?> task = (DispatchedTask<?>)r;
Continuation<?> cont = task.getDelegate$kotlinx_coroutines_core();
String cont_string = getContinuationString(cont);
if(cont_string == null || DispatchedTaskIgnores.ignoreDispatchedTask(cont_string)) {
return null;
}
}

String cont_string = getContinuationString(cont);
if(cont_string == null || DispatchedTaskIgnores.ignoreDispatchedTask(cont_string)) {
return null;
}
}
Token t = NewRelic.getAgent().getTransaction().getToken();
if(t != null && t.isActive()) {
return new NRRunnable(r, t);
Expand All @@ -85,9 +80,9 @@ public static boolean continueWithScope(CoroutineScope scope) {
}

/*
* Allows certain Coroutine scopes to be ignored
* coroutineScope can be a Coroutine name or CoroutineScope class name
*/
* Allows certain Coroutine scopes to be ignored
* coroutineScope can be a Coroutine name or CoroutineScope class name
*/
public static boolean continueWithScope(String coroutineScope) {
if(coroutineScope == null) {
return true;
Expand All @@ -105,11 +100,13 @@ public static boolean continueWithContinuation(Continuation<?> continuation) {
* Don't trace internal Coroutines Continuations
*/
String className = continuation.getClass().getName();
if(className.startsWith("kotlin")) return false;
for(String framework : ignoredFrameworks) {
if(className.startsWith(framework)) return false;
}

/*
* Get the continuation string and check if it should be ignored
*/
* Get the continuation string and check if it should be ignored
*/
String cont_string = getContinuationString(continuation);
if(cont_string == null) { return false; }

Expand All @@ -129,9 +126,9 @@ public static boolean continueWithContinuation(Continuation<?> continuation) {
public static String sub = "createCoroutineFromSuspendFunction";

/*
* Set the async token in the CoroutineContext
* Used to track the transaction across multiple threads
*/
* Set the async token in the CoroutineContext
* Used to track the transaction across multiple threads
*/
public static void setToken(CoroutineContext context) {
TokenContext tokenContext = NRTokenContextKt.getTokenContextOrNull(context);
if (tokenContext == null) {
Expand Down Expand Up @@ -165,11 +162,11 @@ public static void expireToken(CoroutineContext context) {
TokenContext tokenContext = NRTokenContextKt.getTokenContextOrNull(context);
if(tokenContext != null) {
Token token = tokenContext.getToken();
token.expire();
token.expire();
NRTokenContextKt.removeTokenContext(context);
}
}
}

@SuppressWarnings("unchecked")
public static <T> String getCoroutineName(CoroutineContext context, Continuation<T> continuation) {

Expand All @@ -192,24 +189,24 @@ public static String getCoroutineName(CoroutineContext context) {

public static <T> String getContinuationString(Continuation<T> continuation) {
String contString = continuation.toString();

if(contString.equals(CREATE_METHOD_1) || contString.equals(CREATE_METHOD_2)) {
return sub;
}

if(contString.startsWith(CONT_LOC)) {
return contString;
}

if(continuation instanceof AbstractCoroutine_Instrumentation) {
return ((AbstractCoroutine_Instrumentation<?>)continuation).nameString$kotlinx_coroutines_core();
}

int index = contString.indexOf('@');
if(index > -1) {
return contString.substring(0, index);
}

return null;
}

Expand Down Expand Up @@ -260,4 +257,12 @@ public void configureDispatchedTasksIgnores(String[] ignores, String[] ignoresRe
public void configureDelay(boolean enabled) {
DELAYED_ENABLED = enabled;
}
}

@Override
public void configureIgnoredFrameworks(String[] ignores) {
if (ignores != null && ignores.length > 0) {
ignoredFrameworks.clear();
ignoredFrameworks.addAll(Arrays.asList(ignores));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,7 @@ class CoroutinesTest {
// test the flag for whether to capture a call to Delay as a segment or not. This tests that it is captured
@Test
fun testDelayCaptured() {
val utils = Utils.getInstance()
utils.configureDelay(true)
Utils.DELAYED_ENABLED = true
delayTransaction()

assertEquals("Expected 1 finished transaction", 1, introspector.finishedTransactionCount)
Expand All @@ -276,8 +275,7 @@ class CoroutinesTest {
// test the flag for whether to capture a call to Delay as a segment or not. This tests that it is captured
@Test
fun testDelayIgnored() {
val utils = Utils.getInstance()
utils.configureDelay(false)
Utils.DELAYED_ENABLED = false
delayTransaction()

assertEquals("Expected 1 finished transaction", 1, introspector.finishedTransactionCount)
Expand All @@ -290,7 +288,7 @@ class CoroutinesTest {
// ensure that the Delay segment metric is not included
assertFalse(metrics.keys.contains("Custom/Delay"))
// set back to default true value
utils.configureDelay(true)
Utils.DELAYED_ENABLED = true
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.DispatchedTask;
import kotlinx.coroutines.AbstractCoroutine_Instrumentation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import java.util.*;
import java.util.logging.Level;
import java.util.regex.Pattern;

Expand All @@ -23,24 +22,20 @@ public class Utils implements CoroutineConfigListener {
private static final List<Pattern> ignoredContinuationPatterns = new ArrayList<>();
private static final List<String> ignoredScopes = new ArrayList<>();
private static final List<Pattern> ignoredScopePatterns = new ArrayList<>();
private static final Set<String> ignoredFrameworks = new HashSet<>();

public static final String CREATE_METHOD_1 = "Continuation at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$4";
public static final String CREATE_METHOD_2 = "Continuation at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$3";
private static final String CONT_LOC = "Continuation at";
public static boolean DELAYED_ENABLED = true;
private static final Utils INSTANCE = new Utils();

public static Utils getInstance() {
return INSTANCE;
}

static {
/*
* Register this class with the KotlinCoroutinesService to initialize and update
* the ignored items
*/
KotlinCoroutinesService service = ServiceFactory.getKotlinCoroutinesService();
service.addCoroutineConfigListener(INSTANCE);
service.addCoroutineConfigListener(new Utils());
ignoredContinuations.add(CREATE_METHOD_1);
ignoredContinuations.add(CREATE_METHOD_2);

Expand Down Expand Up @@ -105,7 +100,9 @@ public static boolean continueWithContinuation(Continuation<?> continuation) {
* Don't trace internal Coroutines Continuations
*/
String className = continuation.getClass().getName();
if(className.startsWith("kotlin")) return false;
for(String framework : ignoredFrameworks) {
if(className.startsWith(framework)) return false;
}

/*
* Get the continuation string and check if it should be ignored
Expand Down Expand Up @@ -223,4 +220,12 @@ public void configureDispatchedTasksIgnores(String[] ignores, String[] ignoresRe
public void configureDelay(boolean enabled) {
DELAYED_ENABLED = enabled;
}
}

@Override
public void configureIgnoredFrameworks(String[] ignores) {
if(ignores != null && ignores.length > 0) {
ignoredFrameworks.clear();
ignoredFrameworks.addAll(Arrays.asList(ignores));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
package coroutines

import com.newrelic.agent.bridge.AgentBridge
import com.newrelic.agent.deps.org.checkerframework.checker.units.qual.m
import com.newrelic.agent.introspec.InstrumentationTestConfig
import com.newrelic.agent.introspec.InstrumentationTestRunner
import com.newrelic.agent.introspec.Introspector
Expand Down Expand Up @@ -259,8 +258,7 @@ class CoroutinesTest {
// test the flag for whether to capture a call to Delay as a segment or not. This tests that it is captured
@Test
fun testDelayCaptured() {
val utils = Utils.getInstance()
utils.configureDelay(true)
Utils.DELAYED_ENABLED = true
delayTransaction()

assertEquals("Expected 1 finished transaction", 1, introspector.finishedTransactionCount)
Expand All @@ -277,8 +275,7 @@ class CoroutinesTest {
// test the flag for whether to capture a call to Delay as a segment or not. This tests that it is captured
@Test
fun testDelayIgnored() {
val utils = Utils.getInstance()
utils.configureDelay(false)
Utils.DELAYED_ENABLED = false
delayTransaction()

assertEquals("Expected 1 finished transaction", 1, introspector.finishedTransactionCount)
Expand All @@ -291,7 +288,7 @@ class CoroutinesTest {
// ensure that the Delay segment metric is not included
assertFalse(metrics.keys.contains("Custom/Delay"))
// set back to default true value
utils.configureDelay(true)
Utils.DELAYED_ENABLED = true
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.DispatchedTask;
import kotlinx.coroutines.AbstractCoroutine_Instrumentation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import java.util.*;
import java.util.logging.Level;
import java.util.regex.Pattern;

Expand All @@ -23,24 +22,20 @@ public class Utils implements CoroutineConfigListener {
private static final List<Pattern> ignoredContinuationPatterns = new ArrayList<>();
private static final List<String> ignoredScopes = new ArrayList<>();
private static final List<Pattern> ignoredScopePatterns = new ArrayList<>();
private static final Set<String> ignoredFrameworks = new HashSet<>();

public static final String CREATE_METHOD_1 = "Continuation at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$4";
public static final String CREATE_METHOD_2 = "Continuation at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$3";
private static final String CONT_LOC = "Continuation at";
public static boolean DELAYED_ENABLED = true;
private static final Utils INSTANCE = new Utils();

public static Utils getInstance() {
return INSTANCE;
}

static {
/*
* Register this class with the KotlinCoroutinesService to initialize and update
* the ignored items
*/
KotlinCoroutinesService service = ServiceFactory.getKotlinCoroutinesService();
service.addCoroutineConfigListener(INSTANCE);
service.addCoroutineConfigListener(new Utils());
ignoredContinuations.add(CREATE_METHOD_1);
ignoredContinuations.add(CREATE_METHOD_2);

Expand Down Expand Up @@ -105,7 +100,9 @@ public static boolean continueWithContinuation(Continuation<?> continuation) {
* Don't trace internal Coroutines Continuations
*/
String className = continuation.getClass().getName();
if(className.startsWith("kotlin")) return false;
for(String framework : ignoredFrameworks) {
if(className.startsWith(framework)) return false;
}

/*
* Get the continuation string and check if it should be ignored
Expand Down Expand Up @@ -218,4 +215,12 @@ public void configureDispatchedTasksIgnores(String[] ignores, String[] ignoresRe
public void configureDelay(boolean enabled) {
DELAYED_ENABLED = enabled;
}

@Override
public void configureIgnoredFrameworks(String[] ignores) {
if(ignores != null && ignores.length > 0) {
ignoredFrameworks.clear();
ignoredFrameworks.addAll(Arrays.asList(ignores));
}
}
}
Loading
Loading