diff --git a/agent-module/agent-testweb/ktor-plugin-testweb/pom.xml b/agent-module/agent-testweb/ktor-plugin-testweb/pom.xml
new file mode 100644
index 000000000000..a70b3e1794f9
--- /dev/null
+++ b/agent-module/agent-testweb/ktor-plugin-testweb/pom.xml
@@ -0,0 +1,111 @@
+
+
+
+ 4.0.0
+
+ com.navercorp.pinpoint
+ pinpoint-agent-testweb
+ 3.0.1-SNAPSHOT
+
+
+ pinpoint-ktor-plugin-testweb
+
+ jar
+
+
+
+ ${pinpoint.agent.default.jvmargument}
+
+ 2.0.0
+
+
+
+
+ io.ktor
+ ktor-server-status-pages
+ 2.3.12
+ runtime
+
+
+ io.ktor
+ ktor-server-core-jvm
+ 2.3.12
+
+
+ io.ktor
+ ktor-server-netty-jvm
+ 2.3.12
+
+
+ io.ktor
+ ktor-server-config-yaml-jvm
+ 2.3.12
+ runtime
+
+
+ io.ktor
+ ktor-server-tests-jvm
+ 2.3.12
+
+
+
+ io.ktor
+ ktor-client-core-jvm
+ 2.3.12
+
+
+
+ io.ktor
+ ktor-client-cio-jvm
+ 2.3.12
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+ ${kotlin.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-test
+ ${kotlin.version}
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+ 1.8
+
+
+
+
+
\ No newline at end of file
diff --git a/agent-module/agent-testweb/ktor-plugin-testweb/src/main/java/com/pinpoint/test/plugin/Application.kt b/agent-module/agent-testweb/ktor-plugin-testweb/src/main/java/com/pinpoint/test/plugin/Application.kt
new file mode 100644
index 000000000000..d479132dac46
--- /dev/null
+++ b/agent-module/agent-testweb/ktor-plugin-testweb/src/main/java/com/pinpoint/test/plugin/Application.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pinpoint.test.plugin
+
+import io.ktor.server.application.*
+import io.ktor.server.engine.*
+import io.ktor.server.netty.*
+
+fun main() {
+ embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = Application::module)
+ .start(wait = true)
+}
+
+fun Application.module() {
+ configureRouting()
+}
\ No newline at end of file
diff --git a/agent-module/agent-testweb/ktor-plugin-testweb/src/main/java/com/pinpoint/test/plugin/Routing.kt b/agent-module/agent-testweb/ktor-plugin-testweb/src/main/java/com/pinpoint/test/plugin/Routing.kt
new file mode 100644
index 000000000000..71a0350ec2e0
--- /dev/null
+++ b/agent-module/agent-testweb/ktor-plugin-testweb/src/main/java/com/pinpoint/test/plugin/Routing.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pinpoint.test.plugin
+
+import io.ktor.client.*
+import io.ktor.client.engine.cio.*
+import io.ktor.client.request.*
+import io.ktor.client.statement.*
+import io.ktor.http.*
+import io.ktor.server.application.*
+import io.ktor.server.http.content.*
+import io.ktor.server.plugins.statuspages.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+
+fun Application.configureRouting() {
+ install(StatusPages) {
+ exception { call, cause ->
+ call.respondText("App in illegal state as ${cause.message}")
+ }
+ }
+ routing {
+ staticResources("/content", "mycontent")
+
+ get("/") {
+ call.respondText("Hello World!")
+ }
+
+ get("/test1") {
+ val text = "Hello From Ktor
"
+ val type = ContentType.parse("text/html")
+ call.respondText(text, type)
+ }
+ get("/client") {
+ val client = HttpClient(CIO)
+ val response: HttpResponse = client.get("https://ktor.io/")
+ println(response.status)
+ client.close()
+ call.respondText(response.toString())
+ }
+
+ get("/error-test") {
+ throw IllegalStateException("Too Busy")
+ }
+ }
+}
\ No newline at end of file
diff --git a/agent-module/agent-testweb/pom.xml b/agent-module/agent-testweb/pom.xml
index 5c172c7b0ddb..6e21ff0ad1d6 100644
--- a/agent-module/agent-testweb/pom.xml
+++ b/agent-module/agent-testweb/pom.xml
@@ -88,6 +88,7 @@
okhttp-plugin-testweb
grpc-plugin-testweb
resilience4j-plugin-testweb
+ ktor-plugin-testweb
closed-module-testweb
closed-module-testlib
diff --git a/agent-module/agent/src/main/resources/profiles/local/pinpoint.config b/agent-module/agent/src/main/resources/profiles/local/pinpoint.config
index b5de7faa5caf..8d3ce67548fb 100644
--- a/agent-module/agent/src/main/resources/profiles/local/pinpoint.config
+++ b/agent-module/agent/src/main/resources/profiles/local/pinpoint.config
@@ -623,6 +623,34 @@ profiler.reactor-netty.client.mark.error.transport.error=false
profiler.reactor-netty.client.trace.http.error=true
profiler.reactor-netty.client.mark.error.http.error=false
+###########################################################
+# Ktor
+###########################################################
+profiler.ktor.enable=true
+
+# Server
+# Classes for detecting application server type. Comma separated list of fully qualified class names. Wildcard not supported.
+profiler.ktor.server.bootstrap.main=
+# trace param in request ,default value is true
+profiler.ktor.server.tracerequestparam=true
+# URLs to exclude from tracing.
+# Support ant style pattern. e.g. /aa/*.html, /??/exclude.html
+profiler.ktor.server.excludeurl=
+# profiler.ktor.server.trace.excludemethod=
+# HTTP Request methods to exclude from tracing
+#profiler.ktor.server.excludemethod=
+
+# original IP address header
+# https://en.wikipedia.org/wiki/X-Forwarded-For
+#profiler.ktor.server.realipheader=X-Forwarded-For
+# nginx real ip header
+#profiler.ktor.server.realipheader=X-Real-IP
+# optional parameter, If the header value is ${profiler.ktor.realipemptyvalue}, Ignore header value.
+#profiler.ktor.server.realipemptyvalue=unknown
+
+# Retransform
+profiler.ktor.http.server.retransform.configure-routing=true
+
###########################################################
# JSP #
###########################################################
diff --git a/agent-module/agent/src/main/resources/profiles/release/pinpoint.config b/agent-module/agent/src/main/resources/profiles/release/pinpoint.config
index 9783fc91c6f9..19ea5ec07048 100644
--- a/agent-module/agent/src/main/resources/profiles/release/pinpoint.config
+++ b/agent-module/agent/src/main/resources/profiles/release/pinpoint.config
@@ -620,6 +620,34 @@ profiler.reactor-netty.client.mark.error.transport.error=false
profiler.reactor-netty.client.trace.http.error=false
profiler.reactor-netty.client.mark.error.http.error=false
+###########################################################
+# Ktor
+###########################################################
+profiler.ktor.enable=false
+
+# Server
+# Classes for detecting application server type. Comma separated list of fully qualified class names. Wildcard not supported.
+profiler.ktor.server.bootstrap.main=
+# trace param in request ,default value is true
+profiler.ktor.server.tracerequestparam=true
+# URLs to exclude from tracing.
+# Support ant style pattern. e.g. /aa/*.html, /??/exclude.html
+profiler.ktor.server.excludeurl=
+# profiler.ktor.server.trace.excludemethod=
+# HTTP Request methods to exclude from tracing
+#profiler.ktor.server.excludemethod=
+
+# original IP address header
+# https://en.wikipedia.org/wiki/X-Forwarded-For
+#profiler.ktor.server.realipheader=X-Forwarded-For
+# nginx real ip header
+#profiler.ktor.server.realipheader=X-Real-IP
+# optional parameter, If the header value is ${profiler.ktor.realipemptyvalue}, Ignore header value.
+#profiler.ktor.server.realipemptyvalue=unknown
+
+# Retransform
+profiler.ktor.http.server.retransform.configure-routing=true
+
###########################################################
# JSP #
###########################################################
diff --git a/agent-module/plugins/assembly/pom.xml b/agent-module/plugins/assembly/pom.xml
index 28c5d9076b7f..8056159ff674 100644
--- a/agent-module/plugins/assembly/pom.xml
+++ b/agent-module/plugins/assembly/pom.xml
@@ -432,6 +432,11 @@
pinpoint-spring-cloud-sleuth-plugin
${project.version}
+
+ com.navercorp.pinpoint
+ pinpoint-ktor-plugin
+ ${project.version}
+
diff --git a/agent-module/plugins/ktor/README.md b/agent-module/plugins/ktor/README.md
new file mode 100644
index 000000000000..cca74f2472a0
--- /dev/null
+++ b/agent-module/plugins/ktor/README.md
@@ -0,0 +1,38 @@
+## Ktor
+* Since: Pinpoint 3.0.1
+* See: https://ktor.io/
+
+### Pinpoint Configuration
+pinpoint.config
+
+#### Set enable options.
+~~~
+###########################################################
+# Ktor
+###########################################################
+profiler.ktor.enable=false
+
+# Server
+# Classes for detecting application server type. Comma separated list of fully qualified class names. Wildcard not supported.
+profiler.ktor.server.bootstrap.main=
+# trace param in request ,default value is true
+profiler.ktor.server.tracerequestparam=true
+# URLs to exclude from tracing.
+# Support ant style pattern. e.g. /aa/*.html, /??/exclude.html
+profiler.ktor.server.excludeurl=
+# profiler.ktor.server.trace.excludemethod=
+# HTTP Request methods to exclude from tracing
+#profiler.ktor.server.excludemethod=
+
+# original IP address header
+# https://en.wikipedia.org/wiki/X-Forwarded-For
+#profiler.ktor.server.realipheader=X-Forwarded-For
+# nginx real ip header
+#profiler.ktor.server.realipheader=X-Real-IP
+# optional parameter, If the header value is ${profiler.ktor.realipemptyvalue}, Ignore header value.
+#profiler.ktor.server.realipemptyvalue=unknown
+
+# Retransform
+profiler.ktor.http.server.retransform.configure-routing=true
+
+~~~
diff --git a/agent-module/plugins/ktor/pom.xml b/agent-module/plugins/ktor/pom.xml
new file mode 100644
index 000000000000..7cb5411a184b
--- /dev/null
+++ b/agent-module/plugins/ktor/pom.xml
@@ -0,0 +1,59 @@
+
+
+ 4.0.0
+
+ com.navercorp.pinpoint
+ pinpoint-plugins
+ 3.0.1-SNAPSHOT
+
+
+ pinpoint-ktor-plugin
+ pinpoint-ktor-plugin
+ jar
+
+
+
+
+
+
+ com.navercorp.pinpoint
+ pinpoint-bootstrap-core
+ provided
+
+
+ com.navercorp.pinpoint
+ pinpoint-common-servlet
+ ${project.version}
+ provided
+
+
+
+ io.ktor
+ ktor-server-core-jvm
+ 2.3.12
+ provided
+
+
+ io.ktor
+ ktor-server-netty-jvm
+ 2.3.12
+ provided
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+ com/navercorp/**/*
+ META-INF/**/*
+
+
+
+
+
+
diff --git a/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/CoroutineContextGetter.java b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/CoroutineContextGetter.java
new file mode 100644
index 000000000000..13e1af430812
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/CoroutineContextGetter.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.plugin.ktor;
+
+import kotlin.coroutines.CoroutineContext;
+
+public interface CoroutineContextGetter {
+ CoroutineContext _$PINPOINT$_getCoroutineContext();
+}
diff --git a/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/KtorConstants.java b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/KtorConstants.java
new file mode 100644
index 000000000000..a89f47f33cf9
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/KtorConstants.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.plugin.ktor;
+
+import com.navercorp.pinpoint.common.trace.ServiceType;
+import com.navercorp.pinpoint.common.trace.ServiceTypeProvider;
+
+public class KtorConstants {
+
+ public static final ServiceType KTOR = ServiceTypeProvider.getByName("KTOR");
+ public static final ServiceType KTOR_INTERNAL = ServiceTypeProvider.getByName("KTOR_INTERNAL");
+
+}
diff --git a/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/KtorDetector.java b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/KtorDetector.java
new file mode 100644
index 000000000000..afa3dad92e28
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/KtorDetector.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2019 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.plugin.ktor;
+
+import com.navercorp.pinpoint.bootstrap.resolver.condition.MainClassCondition;
+import com.navercorp.pinpoint.common.util.CollectionUtils;
+
+import java.util.Collections;
+import java.util.List;
+
+public class KtorDetector {
+ private static final String DEFAULT_EXPECTED_MAIN_CLASS = "io.ktor.server.engine.ApplicationEngine";
+
+ private final List expectedMainClasses;
+
+ public KtorDetector(List expectedMainClasses) {
+ if (CollectionUtils.isEmpty(expectedMainClasses)) {
+ this.expectedMainClasses = Collections.singletonList(DEFAULT_EXPECTED_MAIN_CLASS);
+ } else {
+ this.expectedMainClasses = expectedMainClasses;
+ }
+ }
+
+ public boolean detect() {
+ String bootstrapMainClass = MainClassCondition.INSTANCE.getValue();
+ boolean isExpectedMainClass = expectedMainClasses.contains(bootstrapMainClass);
+ if (!isExpectedMainClass) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/KtorPlugin.java b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/KtorPlugin.java
new file mode 100644
index 000000000000..68ce4447b56a
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/KtorPlugin.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.plugin.ktor;
+
+import com.navercorp.pinpoint.bootstrap.async.AsyncContextAccessor;
+import com.navercorp.pinpoint.bootstrap.instrument.InstrumentClass;
+import com.navercorp.pinpoint.bootstrap.instrument.InstrumentException;
+import com.navercorp.pinpoint.bootstrap.instrument.InstrumentMethod;
+import com.navercorp.pinpoint.bootstrap.instrument.Instrumentor;
+import com.navercorp.pinpoint.bootstrap.instrument.MethodFilters;
+import com.navercorp.pinpoint.bootstrap.instrument.transformer.MatchableTransformTemplate;
+import com.navercorp.pinpoint.bootstrap.instrument.transformer.MatchableTransformTemplateAware;
+import com.navercorp.pinpoint.bootstrap.instrument.transformer.TransformCallback;
+import com.navercorp.pinpoint.bootstrap.logging.PluginLogManager;
+import com.navercorp.pinpoint.bootstrap.logging.PluginLogger;
+import com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin;
+import com.navercorp.pinpoint.bootstrap.plugin.ProfilerPluginSetupContext;
+import com.navercorp.pinpoint.common.trace.ServiceType;
+import com.navercorp.pinpoint.plugin.ktor.interceptor.ConfigureRoutingFactoryInterceptor;
+import com.navercorp.pinpoint.plugin.ktor.interceptor.NettyApplicationCallHandlerInterceptor;
+import com.navercorp.pinpoint.plugin.ktor.interceptor.NettyHttp1HandlerHandleRequestInterceptor;
+import com.navercorp.pinpoint.plugin.ktor.interceptor.NettyHttp1HandlerPrepareCallFromRequestInterceptor;
+import com.navercorp.pinpoint.plugin.ktor.interceptor.SuspendFunctionGunInterceptor;
+
+import java.security.ProtectionDomain;
+
+import static com.navercorp.pinpoint.common.util.VarArgs.va;
+
+public class KtorPlugin implements ProfilerPlugin, MatchableTransformTemplateAware {
+ private final PluginLogger logger = PluginLogManager.getLogger(getClass());
+ private MatchableTransformTemplate transformTemplate;
+
+ @Override
+ public void setup(ProfilerPluginSetupContext context) {
+ KtorPluginConfig config = new KtorPluginConfig(context.getConfig());
+ if (!config.isEnable()) {
+ logger.info("{} disabled", this.getClass().getSimpleName());
+ return;
+ }
+ logger.info("{} config:{}", this.getClass().getSimpleName(), config);
+
+ if (ServiceType.UNDEFINED.equals(context.getConfiguredApplicationType())) {
+ final KtorDetector detector = new KtorDetector(config.getBootstrapMains());
+ if (detector.detect()) {
+ logger.info("Detected application type : {}", KtorConstants.KTOR);
+ if (!context.registerApplicationType(KtorConstants.KTOR)) {
+ logger.info("Application type [{}] already set, skipping [{}] registration.", context.getApplicationType(), KtorConstants.KTOR);
+ }
+ }
+ }
+
+ // Server
+ transformTemplate.transform("io.ktor.server.netty.http1.NettyHttp1Handler", NettyHttp1HandlerTransform.class);
+ transformTemplate.transform("io.ktor.server.netty.http1.NettyHttp1ApplicationCall", NettyHttp1ApplicationCallTransform.class);
+ transformTemplate.transform("io.ktor.server.netty.NettyApplicationCallHandler", NettyApplicationCallHandlerTransform.class);
+ transformTemplate.transform("io.ktor.util.pipeline.SuspendFunctionGun", SuspendFunctionGunTransform.class);
+ if (config.isRetransformConfigureRouting()) {
+ transformTemplate.transform("io.ktor.server.routing.Route", RouteTransform.class);
+ }
+ }
+
+ @Override
+ public void setTransformTemplate(MatchableTransformTemplate transformTemplate) {
+ this.transformTemplate = transformTemplate;
+ }
+
+ public static class NettyHttp1HandlerTransform implements TransformCallback {
+ @Override
+ public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
+ final InstrumentClass target = instrumentor.getInstrumentClass(loader, className, classfileBuffer);
+ // Async Object
+ target.addField(AsyncContextAccessor.class);
+
+ InstrumentMethod handleRequestMethod = target.getDeclaredMethod("handleRequest", "io.netty.channel.ChannelHandlerContext", "io.netty.handler.codec.http.HttpRequest");
+ if (handleRequestMethod != null) {
+ handleRequestMethod.addInterceptor(NettyHttp1HandlerHandleRequestInterceptor.class);
+ }
+
+ InstrumentMethod prepareCallFromRequestMethod = target.getDeclaredMethod("prepareCallFromRequest", "io.netty.channel.ChannelHandlerContext", "io.netty.handler.codec.http.HttpRequest");
+ if (prepareCallFromRequestMethod != null) {
+ prepareCallFromRequestMethod.addInterceptor(NettyHttp1HandlerPrepareCallFromRequestInterceptor.class);
+ }
+
+ return target.toBytecode();
+ }
+ }
+
+ public static class NettyHttp1ApplicationCallTransform implements TransformCallback {
+ @Override
+ public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
+ final InstrumentClass target = instrumentor.getInstrumentClass(loader, className, classfileBuffer);
+ // Async Object
+ target.addField(AsyncContextAccessor.class);
+
+ return target.toBytecode();
+ }
+ }
+
+ public static class NettyApplicationCallHandlerTransform implements TransformCallback {
+ @Override
+ public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
+ final InstrumentClass target = instrumentor.getInstrumentClass(loader, className, classfileBuffer);
+ target.addGetter(CoroutineContextGetter.class, "coroutineContext");
+
+ for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("handleRequest"))) {
+ if (method != null) {
+ method.addInterceptor(NettyApplicationCallHandlerInterceptor.class);
+ }
+ }
+
+ return target.toBytecode();
+ }
+ }
+
+ public static class SuspendFunctionGunTransform implements TransformCallback {
+ @Override
+ public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
+ final InstrumentClass target = instrumentor.getInstrumentClass(loader, className, classfileBuffer);
+
+ for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("loop"))) {
+ method.addInterceptor(SuspendFunctionGunInterceptor.class);
+ }
+
+ return target.toBytecode();
+ }
+ }
+
+ public static class RouteTransform implements TransformCallback {
+ @Override
+ public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
+ final InstrumentClass target = instrumentor.getInstrumentClass(loader, className, classfileBuffer);
+
+ final RouteMethodTransformer routeMethodTransformer = new RouteMethodTransformer(Boolean.FALSE);
+ for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("handle"))) {
+ method.addInterceptor(ConfigureRoutingFactoryInterceptor.class, va(routeMethodTransformer));
+ }
+
+ return target.toBytecode();
+ }
+ }
+}
diff --git a/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/KtorPluginConfig.java b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/KtorPluginConfig.java
new file mode 100644
index 000000000000..83648b6075d6
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/KtorPluginConfig.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.plugin.ktor;
+
+import com.navercorp.pinpoint.bootstrap.config.Filter;
+import com.navercorp.pinpoint.bootstrap.config.ProfilerConfig;
+import com.navercorp.pinpoint.bootstrap.config.ServerConfig;
+
+import java.util.List;
+import java.util.Objects;
+
+public class KtorPluginConfig {
+
+ private final boolean enable;
+ private final List bootstrapMains;
+ private final boolean enableAsyncEndPoint;
+ private final boolean traceRequestParam;
+ private final Filter excludeUrlFilter;
+ private final Filter traceExcludeMethodFilter;
+ private final String realIpHeader;
+ private final String realIpEmptyValue;
+ private final Filter excludeProfileMethodFilter;
+
+ private final boolean retransformConfigureRouting;
+
+ public KtorPluginConfig(ProfilerConfig config) {
+ Objects.requireNonNull(config, "config");
+
+ // plugin
+ this.enable = config.readBoolean("profiler.ktor.enable", Boolean.TRUE);
+ // Server
+ final ServerConfig serverConfig = new ServerConfig(config);
+ this.bootstrapMains = config.readList("profiler.ktor.http.server.bootstrap.main");
+ this.enableAsyncEndPoint = config.readBoolean("profiler.ktor.http.server.end-point.async.enable", true);
+ this.traceRequestParam = serverConfig.isTraceRequestParam("profiler.ktor.http.server.tracerequestparam");
+ this.excludeUrlFilter = serverConfig.getExcludeUrlFilter("profiler.ktor.http.server.excludeurl");
+ this.traceExcludeMethodFilter = serverConfig.getTraceExcludeMethodFilter("profiler.ktor.http.server.trace.excludemethod");
+ this.realIpHeader = serverConfig.getRealIpHeader("profiler.ktor.http.server.realipheader");
+ this.realIpEmptyValue = serverConfig.getRealIpEmptyValue("profiler.ktor.http.server.realipemptyvalue");
+ this.excludeProfileMethodFilter = serverConfig.getExcludeMethodFilter("profiler.ktor.http.server.excludemethod");
+
+ this.retransformConfigureRouting = config.readBoolean("profiler.ktor.http.server.retransform.configure-routing", Boolean.TRUE);
+ }
+
+ public boolean isEnable() {
+ return enable;
+ }
+
+ public List getBootstrapMains() {
+ return bootstrapMains;
+ }
+
+ public boolean isEnableAsyncEndPoint() {
+ return enableAsyncEndPoint;
+ }
+
+ public boolean isTraceRequestParam() {
+ return traceRequestParam;
+ }
+
+ public Filter getExcludeUrlFilter() {
+ return excludeUrlFilter;
+ }
+
+ public Filter getTraceExcludeMethodFilter() {
+ return traceExcludeMethodFilter;
+ }
+
+ public String getRealIpHeader() {
+ return realIpHeader;
+ }
+
+ public String getRealIpEmptyValue() {
+ return realIpEmptyValue;
+ }
+
+ public Filter getExcludeProfileMethodFilter() {
+ return excludeProfileMethodFilter;
+ }
+
+ public boolean isRetransformConfigureRouting() {
+ return retransformConfigureRouting;
+ }
+
+ @Override
+ public String toString() {
+ return "KtorPluginConfig{" +
+ "enable=" + enable +
+ ", bootstrapMains=" + bootstrapMains +
+ ", enableAsyncEndPoint=" + enableAsyncEndPoint +
+ ", traceRequestParam=" + traceRequestParam +
+ ", excludeUrlFilter=" + excludeUrlFilter +
+ ", traceExcludeMethodFilter=" + traceExcludeMethodFilter +
+ ", realIpHeader='" + realIpHeader + '\'' +
+ ", realIpEmptyValue='" + realIpEmptyValue + '\'' +
+ ", excludeProfileMethodFilter=" + excludeProfileMethodFilter +
+ ", retransformConfigureRouting=" + retransformConfigureRouting +
+ '}';
+ }
+}
diff --git a/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/RouteMethodTransformer.java b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/RouteMethodTransformer.java
new file mode 100644
index 000000000000..41975dd78f1e
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/RouteMethodTransformer.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2014 NAVER Corp.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.plugin.ktor;
+
+import com.navercorp.pinpoint.bootstrap.instrument.InstrumentClass;
+import com.navercorp.pinpoint.bootstrap.instrument.InstrumentException;
+import com.navercorp.pinpoint.bootstrap.instrument.InstrumentMethod;
+import com.navercorp.pinpoint.bootstrap.instrument.Instrumentor;
+import com.navercorp.pinpoint.bootstrap.instrument.MethodFilter;
+import com.navercorp.pinpoint.bootstrap.instrument.MethodFilters;
+import com.navercorp.pinpoint.bootstrap.instrument.transformer.TransformCallback;
+import com.navercorp.pinpoint.bootstrap.logging.PluginLogManager;
+import com.navercorp.pinpoint.bootstrap.logging.PluginLogger;
+import com.navercorp.pinpoint.plugin.ktor.interceptor.RouteMethodInterceptor;
+
+import java.lang.reflect.Modifier;
+import java.security.ProtectionDomain;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static com.navercorp.pinpoint.common.util.VarArgs.va;
+
+public class RouteMethodTransformer implements TransformCallback {
+ private static final int REQUIRED_ACCESS_FLAG = Modifier.PUBLIC;
+ private static final int REJECTED_ACCESS_FLAG = Modifier.ABSTRACT | Modifier.NATIVE | Modifier.STATIC;
+ private static final MethodFilter METHOD_FILTER = MethodFilters.modifier(REQUIRED_ACCESS_FLAG, REJECTED_ACCESS_FLAG);
+ private final PluginLogger logger = PluginLogManager.getLogger(getClass());
+
+ private final Object lock = new Object();
+ private final AtomicInteger interceptorId = new AtomicInteger(-1);
+
+ private final boolean markError;
+
+ public RouteMethodTransformer(boolean markError) {
+ this.markError = markError;
+ }
+
+ @Override
+ public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
+ try {
+ final InstrumentClass target = instrumentor.getInstrumentClass(loader, className, classfileBuffer);
+ if (!target.isInterceptable()) {
+ return null;
+ }
+
+ final List methodList = target.getDeclaredMethods(METHOD_FILTER);
+ for (InstrumentMethod method : methodList) {
+ addInterceptor(method);
+ }
+
+ return target.toBytecode();
+ } catch (Exception e) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Failed to transform", e);
+ }
+
+ return null;
+ }
+ }
+
+ private void addInterceptor(InstrumentMethod targetMethod) throws InstrumentException {
+ int id = interceptorId.get();
+
+ if (id != -1) {
+ targetMethod.addInterceptor(id);
+ return;
+ }
+
+ synchronized (lock) {
+ id = interceptorId.get();
+ if (id != -1) {
+ targetMethod.addInterceptor(id);
+ return;
+ }
+
+ id = targetMethod.addInterceptor(RouteMethodInterceptor.class, va(markError));
+ interceptorId.set(id);
+ }
+ }
+}
\ No newline at end of file
diff --git a/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/ConfigureRoutingFactoryInterceptor.java b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/ConfigureRoutingFactoryInterceptor.java
new file mode 100644
index 000000000000..fee103d3b354
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/ConfigureRoutingFactoryInterceptor.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.plugin.ktor.interceptor;
+
+import com.navercorp.pinpoint.bootstrap.instrument.Instrumentor;
+import com.navercorp.pinpoint.bootstrap.instrument.transformer.TransformCallback;
+import com.navercorp.pinpoint.bootstrap.interceptor.AroundInterceptor;
+import com.navercorp.pinpoint.bootstrap.logging.PluginLogManager;
+import com.navercorp.pinpoint.bootstrap.logging.PluginLogger;
+import com.navercorp.pinpoint.common.util.ArrayArgumentUtils;
+
+public class ConfigureRoutingFactoryInterceptor implements AroundInterceptor {
+ private final PluginLogger logger = PluginLogManager.getLogger(getClass());
+ private final boolean isDebug = logger.isDebugEnabled();
+
+ private final Instrumentor instrumentor;
+ private final TransformCallback transformer;
+
+ public ConfigureRoutingFactoryInterceptor(Instrumentor instrumentor, TransformCallback transformer) {
+ this.instrumentor = instrumentor;
+ this.transformer = transformer;
+ }
+
+ @Override
+ public void before(Object target, Object[] args) {
+ }
+
+ @Override
+ public void after(Object target, Object[] args, Object result, Throwable throwable) {
+ if (throwable != null) {
+ return;
+ }
+
+ Object object = findHandleObject(args);
+ if (object == null) {
+ return;
+ }
+
+ processBean(object);
+ }
+
+ Object findHandleObject(Object[] args) {
+ return ArrayArgumentUtils.getArgument(args, 0, Object.class);
+ }
+
+ public final void processBean(Object bean) {
+ Class> clazz = bean.getClass();
+
+ // If you want to trace inherited methods, you have to retranform super classes, too.
+ instrumentor.retransform(clazz, transformer);
+ if (isDebug) {
+ logger.debug("Retransform class=" + clazz.getName());
+ }
+ }
+}
\ No newline at end of file
diff --git a/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/HttpRequestAdaptor.java b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/HttpRequestAdaptor.java
new file mode 100644
index 000000000000..c029c672f4e8
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/HttpRequestAdaptor.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.plugin.ktor.interceptor;
+
+import com.navercorp.pinpoint.bootstrap.plugin.request.RequestAdaptor;
+import com.navercorp.pinpoint.common.plugin.util.HostAndPort;
+import com.navercorp.pinpoint.common.util.CollectionUtils;
+import io.netty.channel.Channel;
+import io.netty.handler.codec.http.HttpHeaders;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public class HttpRequestAdaptor implements RequestAdaptor {
+
+ @Override
+ public String getHeader(HttpRequestAndContext httpRequestAndContext, String name) {
+ try {
+ final HttpHeaders httpHeaders = httpRequestAndContext.getHttpRequest().headers();
+ if (httpHeaders == null) {
+ return null;
+ }
+ final String values = httpHeaders.get(name);
+ if (values != null) {
+ return values;
+ }
+ } catch (Exception ignored) {
+ }
+
+ return null;
+ }
+
+ @Override
+ public Collection getHeaderNames(HttpRequestAndContext httpRequestAndContext) {
+ try {
+ final HttpHeaders httpHeaders = httpRequestAndContext.getHttpRequest().headers();
+ if (httpHeaders == null) {
+ return Collections.emptyList();
+ }
+ final Set headerNames = httpHeaders.names();
+ if (CollectionUtils.isEmpty(headerNames)) {
+ return Collections.emptyList();
+ }
+ Set values = new HashSet<>(headerNames.size());
+ for (String headerName : headerNames) {
+ values.add(headerName);
+ }
+ return values;
+ } catch (Exception ignored) {
+ }
+ return Collections.emptyList();
+ }
+
+ @Override
+ public String getRpcName(HttpRequestAndContext httpRequestAndContext) {
+ try {
+ return UriUtils.path(httpRequestAndContext.getHttpRequest().uri());
+ } catch (Exception ignored) {
+ }
+ return null;
+ }
+
+ @Override
+ public String getMethodName(HttpRequestAndContext httpRequestAndContext) {
+ try {
+ return httpRequestAndContext.getHttpRequest().method().name();
+ } catch (Exception ignored) {
+ }
+ return null;
+ }
+
+ @Override
+ public String getEndPoint(HttpRequestAndContext httpRequestAndContext) {
+ try {
+ Channel ch = httpRequestAndContext.getContext().channel();
+ if (ch != null) {
+ return getHost((InetSocketAddress) ch.localAddress());
+ }
+ } catch (Exception ignored) {
+ }
+ return null;
+ }
+
+ @Override
+ public String getRemoteAddress(HttpRequestAndContext httpRequestAndContext) {
+ try {
+ Channel ch = httpRequestAndContext.getContext().channel();
+ if (ch != null) {
+ return getHost((InetSocketAddress) ch.remoteAddress());
+ }
+ } catch (Exception ignored) {
+ }
+ return null;
+ }
+
+ @Override
+ public String getAcceptorHost(HttpRequestAndContext httpRequestAndContext) {
+ try {
+ Channel ch = httpRequestAndContext.getContext().channel();
+ if (ch != null) {
+ return getHost((InetSocketAddress) ch.localAddress());
+ }
+ } catch (Exception ignored) {
+ }
+ return null;
+ }
+
+ private String getHost(InetSocketAddress inetSocketAddress) {
+ if (inetSocketAddress != null) {
+ final InetAddress address = inetSocketAddress.getAddress();
+ if (address != null) {
+ return HostAndPort.toHostAndPortString(address.getHostAddress(), inetSocketAddress.getPort());
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/HttpRequestAndContext.java b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/HttpRequestAndContext.java
new file mode 100644
index 000000000000..1662b368cbcf
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/HttpRequestAndContext.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.plugin.ktor.interceptor;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.HttpRequest;
+
+public class HttpRequestAndContext {
+ private HttpRequest httpRequest;
+ private ChannelHandlerContext context;
+
+ public HttpRequestAndContext(HttpRequest httpRequest, ChannelHandlerContext context) {
+ this.httpRequest = httpRequest;
+ this.context = context;
+ }
+
+ public HttpRequest getHttpRequest() {
+ return httpRequest;
+ }
+
+ public ChannelHandlerContext getContext() {
+ return context;
+ }
+}
diff --git a/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/HttpRequestParameterExtractor.java b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/HttpRequestParameterExtractor.java
new file mode 100644
index 000000000000..ca4db2b7d578
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/HttpRequestParameterExtractor.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.plugin.ktor.interceptor;
+
+import com.navercorp.pinpoint.bootstrap.plugin.request.util.ParameterExtractor;
+
+public class HttpRequestParameterExtractor implements ParameterExtractor {
+ private int eachLimit;
+ private int totalLimit;
+
+ public HttpRequestParameterExtractor(int eachLimit, int totalLimit) {
+ this.eachLimit = eachLimit;
+ this.totalLimit = totalLimit;
+ }
+
+ @Override
+ public String extractParameter(HttpRequestAndContext request) {
+ return "";
+ }
+}
\ No newline at end of file
diff --git a/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/MethodFilterExtractor.java b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/MethodFilterExtractor.java
new file mode 100644
index 000000000000..990d9d3a672a
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/MethodFilterExtractor.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.plugin.ktor.interceptor;
+
+import com.navercorp.pinpoint.bootstrap.config.Filter;
+import com.navercorp.pinpoint.bootstrap.plugin.request.util.ParameterExtractor;
+
+public class MethodFilterExtractor implements ParameterExtractor {
+
+ private final Filter excludeProfileMethodFilter;
+
+ private final ParameterExtractor delegate;
+
+ public MethodFilterExtractor(Filter excludeProfileMethodFilter, ParameterExtractor delegate) {
+ this.excludeProfileMethodFilter = excludeProfileMethodFilter;
+ this.delegate = delegate;
+ }
+
+ @Override
+ public String extractParameter(HttpRequestAndContext httpRequestAndContext) {
+ if (excludeProfileMethodFilter.filter(httpRequestAndContext.getHttpRequest().method().name())) {
+ return null;
+ }
+ return delegate.extractParameter(httpRequestAndContext);
+ }
+}
diff --git a/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/NettyApplicationCallHandlerInterceptor.java b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/NettyApplicationCallHandlerInterceptor.java
new file mode 100644
index 000000000000..a7de79a05b2f
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/NettyApplicationCallHandlerInterceptor.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.plugin.ktor.interceptor;
+
+import com.navercorp.pinpoint.bootstrap.async.AsyncContextAccessorUtils;
+import com.navercorp.pinpoint.bootstrap.context.AsyncContext;
+import com.navercorp.pinpoint.bootstrap.context.TraceContext;
+import com.navercorp.pinpoint.bootstrap.interceptor.ApiIdAwareAroundInterceptor;
+import com.navercorp.pinpoint.bootstrap.logging.PluginLogManager;
+import com.navercorp.pinpoint.bootstrap.logging.PluginLogger;
+import com.navercorp.pinpoint.plugin.ktor.CoroutineContextGetter;
+import kotlin.coroutines.CoroutineContext;
+
+public class NettyApplicationCallHandlerInterceptor implements ApiIdAwareAroundInterceptor {
+ private final PluginLogger logger = PluginLogManager.getLogger(this.getClass());
+ private final boolean isDebug = logger.isDebugEnabled();
+ private final TraceContext traceContext;
+
+ public NettyApplicationCallHandlerInterceptor(TraceContext traceContext) {
+ this.traceContext = traceContext;
+ }
+
+ @Override
+ public void before(Object target, int apiId, Object[] args) {
+ if (isDebug) {
+ logger.beforeInterceptor(target, args);
+ }
+ try {
+ final AsyncContext asyncContext = AsyncContextAccessorUtils.getAsyncContext(args, 1);
+ if (asyncContext != null) {
+ if (target instanceof CoroutineContextGetter) {
+ CoroutineContext coroutineContext = ((CoroutineContextGetter) target)._$PINPOINT$_getCoroutineContext();
+ AsyncContextAccessorUtils.setAsyncContext(asyncContext, coroutineContext);
+ }
+ }
+ } catch (Throwable t) {
+ logger.info("Failed to request event handle.", t);
+ }
+ }
+
+ @Override
+ public void after(Object target, int apiId, Object[] args, Object result, Throwable throwable) {
+ }
+}
diff --git a/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/NettyHttp1HandlerHandleRequestInterceptor.java b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/NettyHttp1HandlerHandleRequestInterceptor.java
new file mode 100644
index 000000000000..5536b6e1f634
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/NettyHttp1HandlerHandleRequestInterceptor.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.plugin.ktor.interceptor;
+
+import com.navercorp.pinpoint.bootstrap.async.AsyncContextAccessorUtils;
+import com.navercorp.pinpoint.bootstrap.config.ProfilerConfig;
+import com.navercorp.pinpoint.bootstrap.context.AsyncContext;
+import com.navercorp.pinpoint.bootstrap.context.MethodDescriptor;
+import com.navercorp.pinpoint.bootstrap.context.MethodDescriptorHelper;
+import com.navercorp.pinpoint.bootstrap.context.SpanEventRecorder;
+import com.navercorp.pinpoint.bootstrap.context.Trace;
+import com.navercorp.pinpoint.bootstrap.context.TraceContext;
+import com.navercorp.pinpoint.bootstrap.interceptor.ApiIdAwareAroundInterceptor;
+import com.navercorp.pinpoint.bootstrap.logging.PluginLogManager;
+import com.navercorp.pinpoint.bootstrap.logging.PluginLogger;
+import com.navercorp.pinpoint.bootstrap.plugin.RequestRecorderFactory;
+import com.navercorp.pinpoint.bootstrap.plugin.request.RequestAdaptor;
+import com.navercorp.pinpoint.bootstrap.plugin.request.ServerCookieRecorder;
+import com.navercorp.pinpoint.bootstrap.plugin.request.ServerHeaderRecorder;
+import com.navercorp.pinpoint.bootstrap.plugin.request.ServletRequestListener;
+import com.navercorp.pinpoint.bootstrap.plugin.request.ServletRequestListenerBuilder;
+import com.navercorp.pinpoint.bootstrap.plugin.request.util.ParameterRecorder;
+import com.navercorp.pinpoint.common.util.ArrayArgumentUtils;
+import com.navercorp.pinpoint.plugin.ktor.KtorConstants;
+import com.navercorp.pinpoint.plugin.ktor.KtorPluginConfig;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.HttpRequest;
+
+
+public class NettyHttp1HandlerHandleRequestInterceptor implements ApiIdAwareAroundInterceptor {
+ private final PluginLogger logger = PluginLogManager.getLogger(this.getClass());
+ private final boolean isDebug = logger.isDebugEnabled();
+ private final TraceContext traceContext;
+ private final ServletRequestListener servletRequestListener;
+
+
+ public NettyHttp1HandlerHandleRequestInterceptor(TraceContext traceContext, RequestRecorderFactory requestRecorderFactory) {
+ this.traceContext = traceContext;
+ final KtorPluginConfig config = new KtorPluginConfig(traceContext.getProfilerConfig());
+ RequestAdaptor requestAdaptor = new HttpRequestAdaptor();
+ ParameterRecorder parameterRecorder = ParameterRecorderFactory.newParameterRecorderFactory(config.getExcludeProfileMethodFilter(), config.isTraceRequestParam());
+
+ ServletRequestListenerBuilder reqBuilder = new ServletRequestListenerBuilder<>(KtorConstants.KTOR, traceContext, requestAdaptor);
+ reqBuilder.setExcludeURLFilter(config.getExcludeUrlFilter());
+ reqBuilder.setTraceExcludeMethodFilter(config.getTraceExcludeMethodFilter());
+ reqBuilder.setParameterRecorder(parameterRecorder);
+ reqBuilder.setRequestRecorderFactory(requestRecorderFactory);
+
+ final ProfilerConfig profilerConfig = traceContext.getProfilerConfig();
+ reqBuilder.setRealIpSupport(config.getRealIpHeader(), config.getRealIpEmptyValue());
+ reqBuilder.setHttpStatusCodeRecorder(profilerConfig.getHttpStatusCodeErrors());
+ reqBuilder.setServerHeaderRecorder(profilerConfig.readList(ServerHeaderRecorder.CONFIG_KEY_RECORD_REQ_HEADERS));
+ reqBuilder.setServerCookieRecorder(profilerConfig.readList(ServerCookieRecorder.CONFIG_KEY_RECORD_REQ_COOKIES));
+
+ this.servletRequestListener = reqBuilder.build();
+ }
+
+ @Override
+ public void before(Object target, int apiId, Object[] args) {
+ if (isDebug) {
+ logger.beforeInterceptor(target, args);
+ }
+ try {
+ final ChannelHandlerContext ctx = ArrayArgumentUtils.getArgument(args, 0, ChannelHandlerContext.class);
+ final HttpRequest request = ArrayArgumentUtils.getArgument(args, 1, HttpRequest.class);
+ if (ctx == null || request == null) {
+ return;
+ }
+
+ MethodDescriptor methodDescriptor = MethodDescriptorHelper.apiId(apiId);
+ final HttpRequestAndContext httpRequestAndContext = new HttpRequestAndContext(request, ctx);
+ this.servletRequestListener.initialized(httpRequestAndContext, KtorConstants.KTOR_INTERNAL, methodDescriptor);
+
+ // Set end-point
+ final Trace trace = this.traceContext.currentRawTraceObject();
+ if (trace == null) {
+ return;
+ }
+
+ final SpanEventRecorder recorder = trace.currentSpanEventRecorder();
+ if (recorder != null) {
+ // make asynchronous trace-id
+ final AsyncContext asyncContext = recorder.recordNextAsyncContext();
+ // HttpRequest
+ AsyncContextAccessorUtils.setAsyncContext(asyncContext, args, 1);
+ if (isDebug) {
+ logger.debug("Set asyncContext to args[2]. asyncContext={}", asyncContext);
+ }
+ }
+ } catch (Throwable t) {
+ logger.info("Failed to request event handle.", t);
+ }
+ }
+
+ @Override
+ public void after(Object target, int apiId, Object[] args, Object result, Throwable throwable) {
+ if (isDebug) {
+ logger.afterInterceptor(target, args, result, throwable);
+ }
+
+ try {
+ final ChannelHandlerContext ctx = ArrayArgumentUtils.getArgument(args, 0, ChannelHandlerContext.class);
+ final HttpRequest request = ArrayArgumentUtils.getArgument(args, 1, HttpRequest.class);
+ if (ctx == null || request == null) {
+ return;
+ }
+
+ final int statusCode = getStatusCode(request);
+ final HttpRequestAndContext httpRequestAndContext = new HttpRequestAndContext(request, ctx);
+ this.servletRequestListener.destroyed(httpRequestAndContext, throwable, statusCode);
+ } catch (Throwable t) {
+ logger.info("Failed to request event handle.", t);
+ }
+ }
+
+ private int getStatusCode(final HttpRequest response) {
+ return 0;
+ }
+}
diff --git a/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/NettyHttp1HandlerPrepareCallFromRequestInterceptor.java b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/NettyHttp1HandlerPrepareCallFromRequestInterceptor.java
new file mode 100644
index 000000000000..bf5f135cffdd
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/NettyHttp1HandlerPrepareCallFromRequestInterceptor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.plugin.ktor.interceptor;
+
+import com.navercorp.pinpoint.bootstrap.async.AsyncContextAccessorUtils;
+import com.navercorp.pinpoint.bootstrap.context.AsyncContext;
+import com.navercorp.pinpoint.bootstrap.context.SpanEventRecorder;
+import com.navercorp.pinpoint.bootstrap.context.TraceContext;
+import com.navercorp.pinpoint.bootstrap.interceptor.SpanEventApiIdAwareAroundInterceptorForPlugin;
+import com.navercorp.pinpoint.plugin.ktor.KtorConstants;
+
+public class NettyHttp1HandlerPrepareCallFromRequestInterceptor extends SpanEventApiIdAwareAroundInterceptorForPlugin {
+ public NettyHttp1HandlerPrepareCallFromRequestInterceptor(TraceContext traceContext) {
+ super(traceContext);
+ }
+
+ @Override
+ public void doInBeforeTrace(SpanEventRecorder recorder, Object target, int apiId, Object[] args) throws Exception {
+ }
+
+ @Override
+ public void doInAfterTrace(SpanEventRecorder recorder, Object target, int apiId, Object[] args, Object result, Throwable throwable) throws Exception {
+ recorder.recordServiceType(KtorConstants.KTOR_INTERNAL);
+ recorder.recordException(throwable);
+ recorder.recordApiId(apiId);
+
+ if (throwable == null) {
+ AsyncContext asyncContext = recorder.recordNextAsyncContext();
+ AsyncContextAccessorUtils.setAsyncContext(asyncContext, result);
+ }
+ }
+}
diff --git a/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/ParameterRecorderFactory.java b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/ParameterRecorderFactory.java
new file mode 100644
index 000000000000..a2482aa7fd24
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/ParameterRecorderFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.plugin.ktor.interceptor;
+
+import com.navercorp.pinpoint.bootstrap.config.Filter;
+import com.navercorp.pinpoint.bootstrap.plugin.request.util.DisableParameterRecorder;
+import com.navercorp.pinpoint.bootstrap.plugin.request.util.HttpParameterRecorder;
+import com.navercorp.pinpoint.bootstrap.plugin.request.util.ParameterExtractor;
+import com.navercorp.pinpoint.bootstrap.plugin.request.util.ParameterRecorder;
+
+public class ParameterRecorderFactory {
+ public static ParameterRecorder newParameterRecorderFactory(Filter excludeProfileMethodFilter, boolean traceRequestParam) {
+ if (!traceRequestParam) {
+ return new DisableParameterRecorder<>();
+ }
+ ParameterExtractor parameterExtractor = new HttpRequestParameterExtractor(64, 512);
+ ParameterExtractor methodFilterExtractor = new MethodFilterExtractor(excludeProfileMethodFilter, parameterExtractor);
+ return new HttpParameterRecorder<>(methodFilterExtractor);
+ }
+}
diff --git a/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/RouteMethodInterceptor.java b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/RouteMethodInterceptor.java
new file mode 100644
index 000000000000..bd235925a128
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/RouteMethodInterceptor.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.plugin.ktor.interceptor;
+
+import com.navercorp.pinpoint.bootstrap.context.SpanEventRecorder;
+import com.navercorp.pinpoint.bootstrap.context.Trace;
+import com.navercorp.pinpoint.bootstrap.context.TraceContext;
+import com.navercorp.pinpoint.bootstrap.interceptor.ApiIdAwareAroundInterceptor;
+import com.navercorp.pinpoint.bootstrap.logging.PluginLogManager;
+import com.navercorp.pinpoint.bootstrap.logging.PluginLogger;
+import com.navercorp.pinpoint.plugin.ktor.KtorConstants;
+
+public class RouteMethodInterceptor implements ApiIdAwareAroundInterceptor {
+ private final PluginLogger logger = PluginLogManager.getLogger(RouteMethodInterceptor.class);
+ private final boolean isDebug = logger.isDebugEnabled();
+
+ private final TraceContext traceContext;
+ private final boolean markError;
+
+ public RouteMethodInterceptor(TraceContext traceContext, boolean markError) {
+ this.traceContext = traceContext;
+ this.markError = markError;
+ }
+
+ @Override
+ public void before(Object target, int apiId, Object[] args) {
+ if (isDebug) {
+ logger.beforeInterceptor(target, args);
+ }
+
+ final Trace trace = traceContext.currentTraceObject();
+ if (trace == null) {
+ return;
+ }
+
+ final SpanEventRecorder recorder = trace.traceBlockBegin();
+ recorder.recordServiceType(KtorConstants.KTOR_INTERNAL);
+ }
+
+ @Override
+ public void after(Object target, int apiId, Object[] args, Object result, Throwable throwable) {
+ if (isDebug) {
+ logger.afterInterceptor(target, args);
+ }
+
+ final Trace trace = traceContext.currentTraceObject();
+ if (trace == null) {
+ return;
+ }
+
+ try {
+ final SpanEventRecorder recorder = trace.currentSpanEventRecorder();
+ recorder.recordApiId(apiId);
+ recorder.recordException(markError, throwable);
+ } finally {
+ trace.traceBlockEnd();
+ }
+ }
+}
\ No newline at end of file
diff --git a/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/SuspendFunctionGunInterceptor.java b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/SuspendFunctionGunInterceptor.java
new file mode 100644
index 000000000000..4db5975d8167
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/SuspendFunctionGunInterceptor.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.plugin.ktor.interceptor;
+
+import com.navercorp.pinpoint.bootstrap.async.AsyncContextAccessor;
+import com.navercorp.pinpoint.bootstrap.async.AsyncContextAccessorUtils;
+import com.navercorp.pinpoint.bootstrap.context.AsyncContext;
+import com.navercorp.pinpoint.bootstrap.context.MethodDescriptor;
+import com.navercorp.pinpoint.bootstrap.context.SpanEventRecorder;
+import com.navercorp.pinpoint.bootstrap.context.TraceContext;
+import com.navercorp.pinpoint.bootstrap.interceptor.AsyncContextSpanEventSimpleAroundInterceptor;
+import com.navercorp.pinpoint.plugin.ktor.KtorConstants;
+import io.ktor.util.pipeline.PipelineContext;
+import kotlin.coroutines.CoroutineContext;
+import kotlinx.coroutines.CoroutineScope;
+
+public class SuspendFunctionGunInterceptor extends AsyncContextSpanEventSimpleAroundInterceptor {
+
+ public SuspendFunctionGunInterceptor(TraceContext traceContext, MethodDescriptor descriptor) {
+ super(traceContext, descriptor);
+ }
+
+ @Override
+ public AsyncContext getAsyncContext(Object target, Object[] args, Object result, Throwable throwable) {
+ return getAsyncContext(target);
+ }
+
+ @Override
+ public AsyncContext getAsyncContext(Object target, Object[] args) {
+ return getAsyncContext(target);
+ }
+
+ private AsyncContext getAsyncContext(Object object) {
+ if (object instanceof PipelineContext) {
+ final PipelineContext pipelineContext = (PipelineContext) object;
+ final Object context = pipelineContext.getContext();
+ if (context instanceof AsyncContextAccessor) {
+ return AsyncContextAccessorUtils.getAsyncContext(context);
+ }
+ if (context instanceof CoroutineScope) {
+ final CoroutineScope continuation = (CoroutineScope) context;
+ final CoroutineContext coroutineContext = continuation.getCoroutineContext();
+ if (coroutineContext instanceof AsyncContextAccessor) {
+ return AsyncContextAccessorUtils.getAsyncContext(coroutineContext);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public void doInBeforeTrace(SpanEventRecorder recorder, AsyncContext asyncContext, Object target, Object[] args) {
+ }
+
+ @Override
+ public void doInAfterTrace(SpanEventRecorder recorder, Object target, Object[] args, Object result, Throwable throwable) {
+ recorder.recordApi(methodDescriptor);
+ recorder.recordServiceType(KtorConstants.KTOR_INTERNAL);
+ recorder.recordException(throwable);
+ }
+}
\ No newline at end of file
diff --git a/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/UriUtils.java b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/UriUtils.java
new file mode 100644
index 000000000000..9d5b850f1182
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/java/com/navercorp/pinpoint/plugin/ktor/interceptor/UriUtils.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2024 NAVER Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.plugin.ktor.interceptor;
+
+import com.navercorp.pinpoint.common.plugin.util.HostAndPort;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+public class UriUtils {
+
+ public static String path(final String uri) {
+ if (uri == null) {
+ return null;
+ }
+
+ String path = uri;
+ int index = path.indexOf('?');
+ if (index > -1) {
+ path = path.substring(0, index);
+ }
+ index = path.indexOf('#');
+ if (index > -1) {
+ path = path.substring(0, index);
+ }
+
+ return path;
+ }
+
+ public static String host(String uri) {
+ if (uri == null) {
+ return null;
+ }
+ try {
+ URI u = new URI(uri);
+ String host = u.getHost();
+ int port = u.getPort();
+ return HostAndPort.toHostAndPortString(host, port);
+ } catch (URISyntaxException e) {
+ return null;
+ }
+ }
+
+ public static String params(final String uri) {
+ if (uri == null) {
+ return null;
+ }
+
+ String params = uri;
+ int index = params.indexOf('?');
+ if (index == -1) {
+ return null;
+ }
+ params = params.substring(index + 1);
+ index = params.indexOf('#');
+ if (index > -1) {
+ params = params.substring(0, index);
+ }
+
+ return params;
+ }
+}
diff --git a/agent-module/plugins/ktor/src/main/resources/META-INF/pinpoint/type-provider.yml b/agent-module/plugins/ktor/src/main/resources/META-INF/pinpoint/type-provider.yml
new file mode 100644
index 000000000000..37248afff1ab
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/resources/META-INF/pinpoint/type-provider.yml
@@ -0,0 +1,8 @@
+serviceTypes:
+ - code: 1160
+ name: 'KTOR'
+ property:
+ recordStatistics: true
+ - code: 1161
+ name: 'KTOR_INTERNAL'
+ desc: 'KTOR'
\ No newline at end of file
diff --git a/agent-module/plugins/ktor/src/main/resources/META-INF/services/com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin b/agent-module/plugins/ktor/src/main/resources/META-INF/services/com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin
new file mode 100644
index 000000000000..e866b1d96820
--- /dev/null
+++ b/agent-module/plugins/ktor/src/main/resources/META-INF/services/com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin
@@ -0,0 +1 @@
+com.navercorp.pinpoint.plugin.ktor.KtorPlugin
\ No newline at end of file
diff --git a/agent-module/plugins/pom.xml b/agent-module/plugins/pom.xml
index fc3fb20c2329..f852412dfb79 100644
--- a/agent-module/plugins/pom.xml
+++ b/agent-module/plugins/pom.xml
@@ -117,6 +117,7 @@
clickhouse-jdbc
spring-cloud-sleuth
spring-stub
+ ktor
diff --git a/commons/src/main/java/com/navercorp/pinpoint/common/trace/ServiceType.java b/commons/src/main/java/com/navercorp/pinpoint/common/trace/ServiceType.java
index fb9676649d45..0b6646a79cf1 100644
--- a/commons/src/main/java/com/navercorp/pinpoint/common/trace/ServiceType.java
+++ b/commons/src/main/java/com/navercorp/pinpoint/common/trace/ServiceType.java
@@ -73,6 +73,8 @@
* 1141 | REACTOR_NETTY_INTERNAL |
* 1150 | ARMERIA |
* 1151 | ARMERIA_INTERNAL |
+ * 1160 | KSTOR |
+ * 1161 | KSTOR_INTERNAL |
*
* 1300 | C_CPP |
* 1301 | C_CPP_METHOD |