diff --git a/scouter.agent.java/pom.xml b/scouter.agent.java/pom.xml
index 494cfd4b3..889a351de 100644
--- a/scouter.agent.java/pom.xml
+++ b/scouter.agent.java/pom.xml
@@ -79,6 +79,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -101,6 +111,8 @@
+
+
@@ -121,6 +133,8 @@
+
+
@@ -206,6 +220,9 @@
1.6
1.6
+
+ **/scouter/xtra/reactive/*.java
+
@@ -340,6 +357,8 @@
scouter.tools.jar
scouter.kafka.jar
scouter.redis.jar
+ scouter.reactive.jar
+ scouter.java8.jar
${project.basedir}/lib/provided/tools.jar
${project.basedir}/lib/provided/java.net.http.jar
@@ -423,13 +442,37 @@
org.springframework
spring-web
- 4.3.18.RELEASE
+ 5.2.8.RELEASE
+ provided
+
+
+ org.springframework
+ spring-webflux
+ 5.2.8.RELEASE
+ provided
+
+
+ io.projectreactor
+ reactor-core
+ 3.3.8.RELEASE
+ compile
+
+
+ org.jetbrains.kotlinx
+ kotlinx-coroutines-reactor
+ 1.3.8
+ provided
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+ 1.3.72
provided
org.springframework
spring-core
- 4.3.19.RELEASE
+ 5.2.8.RELEASE
provided
@@ -438,6 +481,13 @@
0.10.1.0
provided
+
+ org.elasticsearch.client
+ elasticsearch-rest-client
+ 6.8.10
+ provided
+
+
io.lettuce
lettuce-core
diff --git a/scouter.agent.java/src/main/java/reactor/core/publisher/ScouterOptimizableOperatorProxy.java b/scouter.agent.java/src/main/java/reactor/core/publisher/ScouterOptimizableOperatorProxy.java
new file mode 100644
index 000000000..219b30060
--- /dev/null
+++ b/scouter.agent.java/src/main/java/reactor/core/publisher/ScouterOptimizableOperatorProxy.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2015 the original author or authors.
+ * @https://github.com/scouter-project/scouter
+ *
+ * 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 reactor.core.publisher;
+
+/**
+ * @author Gun Lee (gunlee01@gmail.com) on 2020/08/08
+ */
+public class ScouterOptimizableOperatorProxy {
+
+ public static final String EMPTY = "";
+
+ public static String nameOnCheckpoint(Object candidate) {
+ if (candidate instanceof OptimizableOperator) {
+ OptimizableOperator, ?> operator = ((OptimizableOperator, ?>) candidate).nextOptimizableSource();
+ if (operator == null) {
+ return EMPTY;
+ }
+ if (operator instanceof MonoOnAssembly) {
+ FluxOnAssembly.AssemblySnapshot snapshot = ((MonoOnAssembly) operator).stacktrace;
+ if (snapshot != null && snapshot.checkpointed) {
+ return snapshot.cached;
+ }
+ } else if (operator instanceof FluxOnAssembly) {
+ FluxOnAssembly.AssemblySnapshot snapshot = ((FluxOnAssembly) operator).snapshotStack;
+ if (snapshot != null && snapshot.checkpointed) {
+ return snapshot.cached;
+ }
+ }
+ }
+ return EMPTY;
+ }
+
+ public static void appendSources4Dump(Object candidate, StringBuilder builder) {
+ if (candidate instanceof OptimizableOperator) {
+ OptimizableOperator, ?> operator = ((OptimizableOperator, ?>) candidate).nextOptimizableSource();
+ if (operator == null) {
+ return;
+ }
+ String p1 = operator.toString();
+ builder.append(" (<-) ").append(p1);
+ if (p1.startsWith("checkpoint")) {
+ OptimizableOperator, ?> operator2 = operator.nextOptimizableSource();
+ if (operator2 != null) {
+ builder.append(" (<-) ").append(operator2.toString());
+ }
+ }
+ }
+ }
+}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/AgentCommonConstant.java b/scouter.agent.java/src/main/java/scouter/agent/AgentCommonConstant.java
index 92b7ba4dd..725712b52 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/AgentCommonConstant.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/AgentCommonConstant.java
@@ -12,6 +12,10 @@ public class AgentCommonConstant {
public static final String REQUEST_ATTRIBUTE_CALLER_TRANSFER_MAP = "__scouter__ctm__";
public static final String REQUEST_ATTRIBUTE_ALL_DISPATCHED_TRACE_CONTEXT = "__scouter__adtc__";
public static final String REQUEST_ATTRIBUTE_SELF_DISPATCHED = "__scouter__sd__";
+ public static final String TRACE_ID = "__scouter__txid__";
+ public static final String TRACE_CONTEXT = "__scouter__tctx__";
+ public static final String SUBS_DEPTH = "__scouter__subdepth__";
+ public static final String SCOUTER_ADDED_FIELD = "__scouter__added__";
public static final String ASYNC_SERVLET_DISPATCHED_PREFIX = "f>";
diff --git a/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java b/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java
index e7db4f8c4..3911ecefe 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java
@@ -28,9 +28,12 @@
import scouter.agent.asm.ApicallJavaHttpRequestASM;
import scouter.agent.asm.ApicallSpringHandleResponseASM;
import scouter.agent.asm.ApicallSpringHttpAccessorASM;
+import scouter.agent.asm.ApicallWebClientInfoASM;
+import scouter.agent.asm.ApicallWebClientResponseASM;
import scouter.agent.asm.CapArgsASM;
import scouter.agent.asm.CapReturnASM;
import scouter.agent.asm.CapThisASM;
+import scouter.agent.asm.HttpReactiveServiceASM;
import scouter.agent.asm.HttpServiceASM;
import scouter.agent.asm.IASM;
import scouter.agent.asm.InitialContextASM;
@@ -53,11 +56,16 @@
import scouter.agent.asm.UserTxASM;
import scouter.agent.asm.asyncsupport.AsyncContextDispatchASM;
import scouter.agent.asm.asyncsupport.CallRunnableASM;
+import scouter.agent.asm.asyncsupport.CoroutineThreadNameASM;
import scouter.agent.asm.asyncsupport.HystrixCommandASM;
+import scouter.agent.asm.asyncsupport.MonoKtASM;
import scouter.agent.asm.asyncsupport.RequestStartAsyncASM;
+import scouter.agent.asm.asyncsupport.ThreadASM;
import scouter.agent.asm.asyncsupport.executor.ExecutorServiceASM;
import scouter.agent.asm.asyncsupport.spring.SpringAsyncExecutionASM;
import scouter.agent.asm.asyncsupport.spring.SpringAsyncExecutionAspectSupportDoSubmitASM;
+import scouter.agent.asm.elasticsearch.HttpNioEntityASM;
+import scouter.agent.asm.elasticsearch.RestClientASM;
import scouter.agent.asm.kafka.KafkaProducerASM;
import scouter.agent.asm.rabbit.RabbitPublisherASM;
import scouter.agent.asm.redis.JedisCommandASM;
@@ -103,8 +111,16 @@ public void run() {
public static void reload() {
Configure conf = Configure.getInstance();
List temp = new ArrayList();
+ temp.add(new ThreadASM());
temp.add(new HttpServiceASM());
temp.add(new ServiceASM());
+ temp.add(new HttpReactiveServiceASM());
+ temp.add(new CoroutineThreadNameASM());
+ temp.add(new MonoKtASM());
+ temp.add(new ApicallWebClientInfoASM());
+ temp.add(new ApicallWebClientResponseASM());
+ temp.add(new HttpNioEntityASM());
+ temp.add(new RestClientASM());
temp.add(new RequestStartAsyncASM());
temp.add(new AsyncContextDispatchASM());
diff --git a/scouter.agent.java/src/main/java/scouter/agent/Configure.java b/scouter.agent.java/src/main/java/scouter/agent/Configure.java
index 35e825bf9..2de4ae86a 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/Configure.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/Configure.java
@@ -193,6 +193,9 @@ public static final Configure getInstance() {
@ConfigDesc("")
public boolean profile_fullstack_stmt_leak_enabled = false;
+ @ConfigDesc("Profile elastic search full query.\nIt need more payload and disk usage.")
+ public boolean elasticsearch_full_query_enabled = false;
+
//Trace
@ConfigDesc("User ID based(0 : Remote Address, 1 : Cookie, 2 : Scouter Cookie, 2 : Header) \n - able to set value for 1.Cookie and 3.Header \n - refer to 'trace_user_session_key'")
public int trace_user_mode = 2; // 0:Remote IP, 1:JSessionID, 2:Scouter Cookie, 3:Header
@@ -332,6 +335,8 @@ public static final Configure getInstance() {
public boolean xlog_error_on_apicall_exception_enabled = true;
@ConfigDesc("mark as error on xlog flag if redis error is occured.")
public boolean xlog_error_on_redis_exception_enabled = true;
+ @ConfigDesc("mark as error on xlog flag if redis error is occured.")
+ public boolean xlog_error_on_elasticsearch_exception_enabled = true;
//XLog hard sampling options
@ConfigDesc("XLog hard sampling mode enabled\n - for the best performance but it affects all statistics data")
@@ -343,6 +348,9 @@ public static final Configure getInstance() {
@ConfigDesc("XLog sampling - ignore global consequent sampling. the commencement service's sampling option affects it's children.")
public boolean ignore_global_consequent_sampling = false;
+ @ConfigDesc("XLog sampling exclude patterns.")
+ public String xlog_sampling_exclude_patterns = "";
+
@ConfigDesc("XLog sampling mode enabled")
public boolean xlog_sampling_enabled = false;
@ConfigDesc("XLog sampling but discard profile only not XLog.")
@@ -701,7 +709,17 @@ public static final Configure getInstance() {
@ConfigDesc("")
public boolean _hook_kafka_enabled = true;
@ConfigDesc("")
+ public boolean _hook_elasticsearch_enabled = true;
+ @ConfigDesc("")
public boolean _hook_rabbit_enabled = true;
+ @ConfigDesc("")
+ public boolean _hook_reactive_enabled = true;
+ @ConfigDesc("")
+ public boolean _hook_coroutine_enabled = true;
+ @ConfigDesc("")
+ public boolean _hook_coroutine_debugger_hook_enabled = false;
+ @ConfigDesc("")
+ public boolean _hook_thread_name_enabled = false;
@ConfigDesc("")
public String _hook_direct_patch_classes = "";
@@ -748,6 +766,7 @@ public static final Configure getInstance() {
public boolean _psts_enabled = false;
@ConfigDesc("PSTS(periodical stacktrace step) thread dump Interval(ms) - hard min limit 2000")
public int _psts_dump_interval_ms = 10000;
+ public boolean _psts_progressive_reactor_thread_trace_enabled = true;
//Summary
@ConfigDesc("Activating summary function")
@@ -1056,6 +1075,8 @@ private void apply() {
this.profile_fullstack_rs_leak_enabled = getBoolean("profile_fullstack_rs_leak_enabled", false);
this.profile_fullstack_stmt_leak_enabled = getBoolean("profile_fullstack_stmt_leak_enabled", false);
+ this.elasticsearch_full_query_enabled = getBoolean("elasticsearch_full_query_enabled", false);
+
this.net_udp_collection_interval_ms = getInt("net_udp_collection_interval_ms", 100);
this.trace_http_client_ip_header_key = getValue("trace_http_client_ip_header_key", "");
@@ -1088,7 +1109,12 @@ private void apply() {
this._hook_spring_rest_enabled = getBoolean("_hook_spring_rest_enabled", true);
this._hook_redis_enabled = getBoolean("_hook_redis_enabled", true);
this._hook_kafka_enabled = getBoolean("_hook_kafka_enabled", true);
+ this._hook_elasticsearch_enabled = getBoolean("_hook_elasticsearch_enabled", true);
this._hook_rabbit_enabled = getBoolean("_hook_rabbit_enabled", true);
+ this._hook_reactive_enabled = getBoolean("_hook_reactive_enabled", true);
+ this._hook_coroutine_enabled = getBoolean("_hook_coroutine_enabled", true);
+ this._hook_coroutine_debugger_hook_enabled = getBoolean("_hook_coroutine_debugger_hook_enabled", false);
+ this._hook_thread_name_enabled = getBoolean("_hook_thread_name_enabled", false);
this._hook_direct_patch_classes = getValue("_hook_direct_patch_classes", "");
@@ -1106,6 +1132,7 @@ private void apply() {
this._psts_enabled = getBoolean("_psts_enabled", false);
this._psts_dump_interval_ms = getInt("_psts_dump_interval_ms", 10000);
+ this._psts_progressive_reactor_thread_trace_enabled = getBoolean("_psts_progressive_reactor_dump_enabled", true);
// 웹시스템으로 부터 WAS 사이의 성능과 어떤 웹서버가 요청을 보내 왔는지를 추적하는 기능을 ON/OFF하고
// 관련 키정보를 지정한다.
@@ -1153,6 +1180,7 @@ private void apply() {
this.xlog_error_on_sqlexception_enabled = getBoolean("xlog_error_on_sqlexception_enabled", true);
this.xlog_error_on_apicall_exception_enabled = getBoolean("xlog_error_on_apicall_exception_enabled", true);
this.xlog_error_on_redis_exception_enabled = getBoolean("xlog_error_on_redis_exception_enabled", true);
+ this.xlog_error_on_elasticsearch_exception_enabled = getBoolean("xlog_error_on_elasticsearch_exception_enabled", true);
this._log_asm_enabled = getBoolean("_log_asm_enabled", false);
this.obj_type_inherit_to_child_enabled = getBoolean("obj_type_inherit_to_child_enabled", false);
@@ -1178,6 +1206,8 @@ private void apply() {
this.ignore_global_consequent_sampling = getBoolean("ignore_global_consequent_sampling", false);
+ this.xlog_sampling_exclude_patterns = getValue("xlog_sampling_exclude_patterns", "");
+
this.xlog_sampling_enabled = getBoolean("xlog_sampling_enabled", false);
this.xlog_sampling_only_profile = getBoolean("xlog_sampling_only_profile", false);
this.xlog_sampling_step1_ms = getInt("xlog_sampling_step1_ms", 100);
diff --git a/scouter.agent.java/src/main/java/scouter/agent/JavaAgent.java b/scouter.agent.java/src/main/java/scouter/agent/JavaAgent.java
index 4abbf23e7..33e962125 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/JavaAgent.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/JavaAgent.java
@@ -43,6 +43,10 @@ public class JavaAgent {
}
public static void premain(String options, Instrumentation instrum) {
+ Configure conf = Configure.getInstance();
+ if (conf._hook_coroutine_debugger_hook_enabled && System.getProperty("kotlinx.coroutines.debug") == null) {
+ System.setProperty("kotlinx.coroutines.debug", "");
+ }
preStart(options, instrum, new AgentTransformer());
}
@@ -95,6 +99,7 @@ private static void addAsyncRedefineClasses() {
redefineClasses.put("java.util.concurrent.AbstractExecutorService");
redefineClasses.put("java.util.concurrent.ThreadPoolExecutor");
+ redefineClasses.put("java.lang.Thread");
//java.lang.invoke.LambdaMetafactory.*,java.lang.invoke.CallSite.*,
//java.lang.invoke.ConstantCallSite.*,
diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/AddFieldASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/AddFieldASM.java
index 5b8a991f1..f4ef068ff 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/asm/AddFieldASM.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/asm/AddFieldASM.java
@@ -26,9 +26,14 @@
import scouter.agent.asm.util.HookingSet;
import java.util.Map;
+
+import static scouter.agent.AgentCommonConstant.SCOUTER_ADDED_FIELD;
+
public class AddFieldASM implements IASM, Opcodes {
public final Map target = HookingSet.getClassFieldSet(Configure.getInstance().hook_add_fields);
public AddFieldASM() {
+ target.put("org/springframework/web/reactive/function/client/DefaultClientRequestBuilder$BodyInserterRequest",
+ SCOUTER_ADDED_FIELD);
}
Configure conf = Configure.getInstance();
public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) {
diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/ApicallASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/ApicallASM.java
index a4f1d0456..8c975089f 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/asm/ApicallASM.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/asm/ApicallASM.java
@@ -16,10 +16,15 @@
*/
package scouter.agent.asm;
-import org.objectweb.asm.*;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
import org.objectweb.asm.commons.LocalVariablesSorter;
import scouter.agent.ClassDesc;
import scouter.agent.Configure;
+import scouter.agent.Logger;
import scouter.agent.asm.util.AsmUtil;
import scouter.agent.asm.util.HookingSet;
import scouter.agent.trace.TraceApiCall;
@@ -27,12 +32,15 @@
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
public class ApicallASM implements IASM, Opcodes {
private List target = HookingSet.getHookingMethodSet(Configure.getInstance().hook_apicall_patterns);
private Map reserved = new HashMap();
+ protected static Set onlyStartClass = new HashSet();
public static class ApiCallTargetRegister {
public static final List> klassMethod = new ArrayList>();
@@ -83,9 +91,13 @@ public ApicallASM() {
"Ljava/net/http/HttpResponse$BodyHandler;" +
")Ljava/net/http/HttpResponse;");
+ AsmUtil.add(reserved, "org/springframework/web/reactive/function/client/ExchangeFunctions$DefaultExchangeFunction", "exchange(" +
+ "Lorg/springframework/web/reactive/function/client/ClientRequest;" +
+ ")Lreactor/core/publisher/Mono;");
for(int i = ApiCallTargetRegister.klassMethod.size() - 1; i >= 0; i--) {
AsmUtil.add(reserved, ApiCallTargetRegister.klassMethod.get(i).getLeft(), ApiCallTargetRegister.klassMethod.get(i).getRight());
}
+ onlyStartClass.add("org/springframework/web/reactive/function/client/ExchangeFunctions$DefaultExchangeFunction");
}
public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) {
@@ -124,7 +136,7 @@ public MethodVisitor visitMethod(int access, String methodName, String desc, Str
if (AsmUtil.isSpecial(methodName)) {
return mv;
}
- //Logger.println("apicall: " + className + "." + methodName + desc);
+ Logger.println("apicall: " + className + "." + methodName + desc);
return new ApicallExtMV(access, desc, mv, Type.getArgumentTypes(desc), (access & ACC_STATIC) != 0, className,
methodName, desc);
}
@@ -234,6 +246,9 @@ public void visitInsn(int opcode) {
}
private void capReturn() {
+ if (ApicallASM.onlyStartClass.contains(className)) {
+ return;
+ }
Type tp = returnType;
if (tp == null || tp.equals(Type.VOID_TYPE)) {
mv.visitVarInsn(Opcodes.ALOAD, statIdx);
diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/ApicallWebClientInfoASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/ApicallWebClientInfoASM.java
new file mode 100644
index 000000000..45cc6dd18
--- /dev/null
+++ b/scouter.agent.java/src/main/java/scouter/agent/asm/ApicallWebClientInfoASM.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2015 the original author or authors.
+ * @https://github.com/scouter-project/scouter
+ *
+ * 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 scouter.agent.asm;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.LocalVariablesSorter;
+import scouter.agent.ClassDesc;
+import scouter.agent.Configure;
+import scouter.agent.asm.util.AsmUtil;
+import scouter.agent.asm.util.HookingSet;
+import scouter.agent.trace.TraceApiCall;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ApicallWebClientInfoASM implements IASM, Opcodes {
+
+ private Map reserved = new HashMap();
+
+ public ApicallWebClientInfoASM() {
+ AsmUtil.add(reserved, "org/springframework/web/reactive/function/client/DefaultClientRequestBuilder$BodyInserterRequest",
+ "writeTo(Lorg/springframework/http/client/reactive/ClientHttpRequest;" +
+ "Lorg/springframework/web/reactive/function/client/ExchangeStrategies;)" +
+ "Lreactor/core/publisher/Mono;");
+ }
+
+ @Override
+ public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) {
+ if (!Configure.getInstance()._hook_apicall_enabled) {
+ return cv;
+ }
+
+ HookingSet mset = reserved.get(className);
+ if (mset != null)
+ return new WebClientRequestBuilderBodyInserterCV(cv, mset, className);
+ return cv;
+ }
+}
+
+class WebClientRequestBuilderBodyInserterCV extends ClassVisitor implements Opcodes {
+ public String className;
+ private HookingSet mset;
+ public WebClientRequestBuilderBodyInserterCV(ClassVisitor cv, HookingSet mset, String className) {
+ super(ASM8, cv);
+ this.mset = mset;
+ this.className = className;
+ }
+ @Override
+ public MethodVisitor visitMethod(int access, String methodName, String desc, String signature, String[] exceptions) {
+ MethodVisitor mv = super.visitMethod(access, methodName, desc, signature, exceptions);
+ if (mv == null || mset.isA(methodName, desc) == false) {
+ return mv;
+
+ }
+ if (AsmUtil.isSpecial(methodName)) {
+ return mv;
+ }
+ return new RequestBuilderBodyInserterWriteToMV(access, methodName, desc, mv);
+ }
+}
+
+class RequestBuilderBodyInserterWriteToMV extends LocalVariablesSorter implements Opcodes {
+ private static final String TARGET = TraceApiCall.class.getName().replace('.', '/');
+ private static final String START_METHOD = "webClientInfo";
+ private static final String START_SIGNATURE = "(Ljava/lang/Object;Ljava/lang/Object;)V";
+
+ String name;
+ String desc;
+ int respIdx;
+
+ public RequestBuilderBodyInserterWriteToMV(int access, String name, String desc, MethodVisitor mv) {
+ super(ASM8, access, desc, mv);
+ this.name = name;
+ this.desc = desc;
+ }
+
+ @Override
+ public void visitCode() {
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, TARGET, START_METHOD, START_SIGNATURE, false);
+
+ mv.visitCode();
+ }
+}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/ApicallWebClientResponseASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/ApicallWebClientResponseASM.java
new file mode 100644
index 000000000..71fbb6987
--- /dev/null
+++ b/scouter.agent.java/src/main/java/scouter/agent/asm/ApicallWebClientResponseASM.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2015 the original author or authors.
+ * @https://github.com/scouter-project/scouter
+ *
+ * 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 scouter.agent.asm;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.LocalVariablesSorter;
+import scouter.agent.ClassDesc;
+import scouter.agent.Configure;
+import scouter.agent.asm.util.AsmUtil;
+import scouter.agent.asm.util.HookingSet;
+import scouter.agent.trace.TraceApiCall;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ApicallWebClientResponseASM implements IASM, Opcodes {
+
+ private Map reserved = new HashMap();
+
+ public ApicallWebClientResponseASM() {
+ AsmUtil.add(reserved, "org/springframework/web/reactive/function/client/ExchangeFunctions$DefaultExchangeFunction",
+ "logResponse(Lorg/springframework/http/client/reactive/ClientHttpResponse;Ljava/lang/String;)V");
+ }
+
+ @Override
+ public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) {
+ if (!Configure.getInstance()._hook_apicall_enabled) {
+ return cv;
+ }
+
+ HookingSet mset = reserved.get(className);
+ if (mset != null)
+ return new WebClientResponseLogCV(cv, mset, className);
+ return cv;
+ }
+}
+
+class WebClientResponseLogCV extends ClassVisitor implements Opcodes {
+ public String className;
+ private HookingSet mset;
+ public WebClientResponseLogCV(ClassVisitor cv, HookingSet mset, String className) {
+ super(ASM8, cv);
+ this.mset = mset;
+ this.className = className;
+ }
+ @Override
+ public MethodVisitor visitMethod(int access, String methodName, String desc, String signature, String[] exceptions) {
+ MethodVisitor mv = super.visitMethod(access, methodName, desc, signature, exceptions);
+ if (mv == null || mset.isA(methodName, desc) == false) {
+ return mv;
+
+ }
+ if (AsmUtil.isSpecial(methodName)) {
+ return mv;
+ }
+ return new WebClientResponseLogMV(access, methodName, desc, mv);
+ }
+}
+
+class WebClientResponseLogMV extends LocalVariablesSorter implements Opcodes {
+ private static final String TARGET = TraceApiCall.class.getName().replace('.', '/');
+ private static final String START_METHOD = "endWebClientApicall";
+ private static final String START_SIGNATURE = "(Ljava/lang/Object;Ljava/lang/Object;)V";
+
+ String name;
+ String desc;
+ int respIdx;
+
+ public WebClientResponseLogMV(int access, String name, String desc, MethodVisitor mv) {
+ super(ASM8, access, desc, mv);
+ this.name = name;
+ this.desc = desc;
+ }
+
+ @Override
+ public void visitCode() {
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, TARGET, START_METHOD, START_SIGNATURE, false);
+
+ mv.visitCode();
+ }
+}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/HttpReactiveServiceASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/HttpReactiveServiceASM.java
new file mode 100644
index 000000000..9584dad3e
--- /dev/null
+++ b/scouter.agent.java/src/main/java/scouter/agent/asm/HttpReactiveServiceASM.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2015 the original author or authors.
+ * @https://github.com/scouter-project/scouter
+ *
+ * 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 scouter.agent.asm;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.LocalVariablesSorter;
+import scouter.agent.ClassDesc;
+import scouter.agent.Configure;
+import scouter.agent.Logger;
+import scouter.agent.trace.TraceMain;
+
+import java.util.HashSet;
+
+public class HttpReactiveServiceASM implements IASM, Opcodes {
+ public HashSet handlers = new HashSet();
+// public HashSet handlersRes = new HashSet();
+ public HttpReactiveServiceASM() {
+ handlers.add("org/springframework/web/reactive/DispatcherHandler");
+ handlers.add("org/springframework/web/server/handler/FilteringWebHandler");
+// handlersRes.add("org/springframework/http/server/reactive/ReactorServerHttpResponse");
+ }
+
+ public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) {
+ if (Configure.getInstance()._hook_serivce_enabled == false) {
+ return cv;
+ }
+ if (Configure.getInstance()._hook_reactive_enabled == false) {
+ return cv;
+ }
+ if (handlers.contains(className)) {
+ return new HttpReactiveServiceCV(cv, className);
+ }
+// if (handlersRes.contains(className)) {
+// return new HttpReactiveServiceResCV(cv, className);
+// }
+ return cv;
+ }
+}
+
+class HttpReactiveServiceCV extends ClassVisitor implements Opcodes {
+ private static String handler = "invokeHandler";
+ private static String handler_sig = "(Lorg/springframework/web/server/ServerWebExchange;Ljava/lang/Object;)Lreactor/core/publisher/Mono;";
+
+ private static String handler2 = "handle";
+ private static String handler_sig2 = "(Lorg/springframework/web/server/ServerWebExchange;)Lreactor/core/publisher/Mono;";
+
+ private static String loading = "";
+ private static String loading_class = "org/springframework/web/reactive/DispatcherHandler";
+
+ private String className;
+
+ public HttpReactiveServiceCV(ClassVisitor cv, String className) {
+ super(ASM8, cv);
+ this.className = className;
+ }
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
+ if (mv == null) {
+ return mv;
+ }
+
+ if (desc.startsWith(handler_sig2) && handler2.equals(name) || desc.startsWith(handler_sig) && handler.equals(name)) {
+ Logger.println("A103", "HTTP-REACTIVE " + className);
+ return new HttpReactiveServiceMV(access, desc, mv);
+
+ } else if (loading.equals(name) && loading_class.equals(className)) {
+ Logger.println("A103", "HTTP-REACTIVE INIT" + className);
+ return new HttpReactiveInitMV(access, desc, mv);
+ }
+ return mv;
+ }
+}
+
+class HttpReactiveInitMV extends LocalVariablesSorter implements Opcodes {
+ private static final String TRACEMAIN = TraceMain.class.getName().replace('.', '/');
+ private final static String START = "startReactiveInit";
+ private static final String START_SIGNATURE = "(Ljava/lang/Object;)V";
+
+ public HttpReactiveInitMV(int access, String desc, MethodVisitor mv) {
+ super(ASM8, access, desc, mv);
+ }
+
+ @Override
+ public void visitCode() {
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACEMAIN, START, START_SIGNATURE, false);
+ mv.visitCode();
+ }
+}
+
+class HttpReactiveServiceMV extends LocalVariablesSorter implements Opcodes {
+ private static final String TRACEMAIN = TraceMain.class.getName().replace('.', '/');
+ private final static String START = "startReactiveHttpService";
+ private static final String START_SIGNATURE = "(Ljava/lang/Object;)V";
+ private final static String START_RETURN = "startReactiveHttpServiceReturn";
+ private final static String START_RETURN_SIGNATUER = "(Ljava/lang/Object;)Ljava/lang/Object;";
+
+ //TODO private final static String REJECT = "reject";
+
+ public HttpReactiveServiceMV(int access, String desc, MethodVisitor mv) {
+ super(ASM8, access, desc, mv);
+ }
+
+ @Override
+ public void visitCode() {
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACEMAIN, START, START_SIGNATURE, false);
+ mv.visitCode();
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ if ((opcode >= IRETURN && opcode <= RETURN)) {
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACEMAIN, START_RETURN, START_RETURN_SIGNATUER, false);
+ }
+ mv.visitInsn(opcode);
+ }
+}
+
+
+//class HttpReactiveServiceResCV extends ClassVisitor implements Opcodes {
+// private static String method1 = "writeWithInternal";
+// private static String desc1 = "(Lorg/reactivestreams/Publisher;)Lreactor/core/publisher/Mono;";
+// private static String method2 = "writeAndFlushWithInternal";
+// private static String desc2 = "(Lorg/reactivestreams/Publisher;)Lreactor/core/publisher/Mono;";
+//
+// private String className;
+//
+// public HttpReactiveServiceResCV(ClassVisitor cv, String className) {
+// super(ASM8, cv);
+// this.className = className;
+// }
+// @Override
+// public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+// MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
+// if (mv == null) {
+// return mv;
+// }
+//
+// if (method1.equals(name) && desc.startsWith(desc1) || method2.equals(name) && desc.startsWith(desc2) ) {
+// Logger.println("A103", "HTTP-REACTIVE-RES " + className);
+// return new HttpReactiveServiceResMV(access, desc, mv);
+// }
+// return mv;
+// }
+//}
+//
+//class HttpReactiveServiceResMV extends LocalVariablesSorter implements Opcodes {
+// private static final String TRACEMAIN = TraceMain.class.getName().replace('.', '/');
+// private final static String METHOD = "endReactiveHttpService";
+// private static final String METHOD_SIGNATURE = "()V";
+//
+//
+// public HttpReactiveServiceResMV(int access, String desc, MethodVisitor mv) {
+// super(ASM8, access, desc, mv);
+// }
+//
+// @Override
+// public void visitCode() {
+// mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACEMAIN, METHOD, METHOD_SIGNATURE, false);
+// mv.visitCode();
+// }
+//}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/asyncsupport/CoroutineThreadNameASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/asyncsupport/CoroutineThreadNameASM.java
new file mode 100644
index 000000000..6397019c0
--- /dev/null
+++ b/scouter.agent.java/src/main/java/scouter/agent/asm/asyncsupport/CoroutineThreadNameASM.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2015 the original author or authors.
+ * @https://github.com/scouter-project/scouter
+ *
+ * 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 scouter.agent.asm.asyncsupport;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.LocalVariablesSorter;
+import scouter.agent.ClassDesc;
+import scouter.agent.Configure;
+import scouter.agent.asm.IASM;
+import scouter.agent.trace.TraceReactive;
+
+public class CoroutineThreadNameASM implements IASM, Opcodes {
+
+ private Configure conf = Configure.getInstance();
+
+ public CoroutineThreadNameASM() {
+ }
+
+ public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) {
+ if (conf._hook_coroutine_debugger_hook_enabled == false) {
+ return cv;
+ }
+
+ if ("kotlinx/coroutines/CoroutineId".equals(className)) {
+ return new CoroutineIdCV(cv, className);
+ }
+ return cv;
+ }
+}
+
+class CoroutineIdCV extends ClassVisitor implements Opcodes {
+
+ public String className;
+
+ public CoroutineIdCV(ClassVisitor cv, String className) {
+ super(ASM8, cv);
+ this.className = className;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
+ if (mv == null) {
+ return mv;
+ }
+
+ if ("updateThreadContext".equals(name) && "(Lkotlin/coroutines/CoroutineContext;)Ljava/lang/String;".equals(desc)) {
+ return new CoroutineIdUpdateThreadContextMV(access, desc, mv, className);
+
+ } else if ("restoreThreadContext".equals(name) && "(Lkotlin/coroutines/CoroutineContext;Ljava/lang/String;)V".equals(desc)) {
+ return new CoroutineIdRestoreThreadContextMV(access, desc, mv, className);
+ }
+ return mv;
+ }
+}
+
+class CoroutineIdUpdateThreadContextMV extends LocalVariablesSorter implements Opcodes {
+ private static final String TRACE = TraceReactive.class.getName().replace('.', '/');
+
+ private Label startFinally = new Label();
+ private String className;
+
+ public CoroutineIdUpdateThreadContextMV(int access, String desc, MethodVisitor mv, String className) {
+ super(ASM8, access, desc, mv);
+ this.className = className;
+ }
+
+ @Override
+ public void visitCode() {
+ mv.visitVarInsn(ALOAD, 0);
+
+ mv.visitFieldInsn(GETFIELD, className, "id", "J");
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACE, "startCoroutineIdUpdateThreadContext", "(J)V", false);
+ mv.visitLabel(startFinally);
+ mv.visitCode();
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ if ((opcode >= IRETURN && opcode <= RETURN)) {
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACE, "endCoroutineIdUpdateThreadContext", "()V", false);
+ }
+ mv.visitInsn(opcode);
+ }
+
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ Label endFinally = new Label();
+ mv.visitTryCatchBlock(startFinally, endFinally, endFinally, null);
+ mv.visitLabel(endFinally);
+
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACE, "endCoroutineIdUpdateThreadContext", "()V", false);
+ mv.visitInsn(ATHROW);
+ mv.visitMaxs(maxStack + 8, maxLocals + 2);
+ }
+}
+
+
+class CoroutineIdRestoreThreadContextMV extends LocalVariablesSorter implements Opcodes {
+ private static final String TRACE = TraceReactive.class.getName().replace('.', '/');
+
+ private String className;
+
+ public CoroutineIdRestoreThreadContextMV(int access, String desc, MethodVisitor mv, String className) {
+ super(ASM8, access, desc, mv);
+ this.className = className;
+ }
+
+ @Override
+ public void visitCode() {
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACE, "startCoroutineIdRestoreThreadContext", "(Ljava/lang/Object;)V", false);
+ mv.visitCode();
+ }
+}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/asyncsupport/MonoKtASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/asyncsupport/MonoKtASM.java
new file mode 100644
index 000000000..df9568b94
--- /dev/null
+++ b/scouter.agent.java/src/main/java/scouter/agent/asm/asyncsupport/MonoKtASM.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2015 the original author or authors.
+ * @https://github.com/scouter-project/scouter
+ *
+ * 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 scouter.agent.asm.asyncsupport;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.LocalVariablesSorter;
+import scouter.agent.ClassDesc;
+import scouter.agent.Configure;
+import scouter.agent.asm.IASM;
+import scouter.agent.trace.TraceReactive;
+
+public class MonoKtASM implements IASM, Opcodes {
+
+ private Configure conf = Configure.getInstance();
+
+ public MonoKtASM() {
+ }
+
+ public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) {
+ if (conf._hook_coroutine_enabled == false) {
+ return cv;
+ }
+
+ if ("kotlinx/coroutines/reactor/MonoKt".equals(className)) {
+ return new MonoKtCV(cv, className);
+ }
+ return cv;
+ }
+}
+
+class MonoKtCV extends ClassVisitor implements Opcodes {
+
+ public String className;
+
+ public MonoKtCV(ClassVisitor cv, String className) {
+ super(ASM8, cv);
+ this.className = className;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
+ if (mv == null) {
+ return mv;
+ }
+
+ if ("mono".equals(name) && "(Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lreactor/core/publisher/Mono;".equals(desc)) {
+ return new MonoKtMV(access, desc, mv, className);
+
+ }
+ return mv;
+ }
+}
+
+class MonoKtMV extends LocalVariablesSorter implements Opcodes {
+ private static final String TRACE = TraceReactive.class.getName().replace('.', '/');
+
+ private Label startFinally = new Label();
+ private String className;
+
+ public MonoKtMV(int access, String desc, MethodVisitor mv, String className) {
+ super(ASM8, access, desc, mv);
+ this.className = className;
+ }
+
+ @Override
+ public void visitCode() {
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACE, "startMonoKtMono", "(Ljava/lang/Object;)Ljava/lang/Object;", false);
+ mv.visitVarInsn(ASTORE, 0);
+ mv.visitLabel(startFinally);
+ mv.visitCode();
+ }
+}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/asyncsupport/ThreadASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/asyncsupport/ThreadASM.java
new file mode 100644
index 000000000..350e176d7
--- /dev/null
+++ b/scouter.agent.java/src/main/java/scouter/agent/asm/asyncsupport/ThreadASM.java
@@ -0,0 +1,89 @@
+package scouter.agent.asm.asyncsupport;
+/*
+ * Copyright 2015 the original author or authors.
+ * @https://github.com/scouter-project/scouter
+ *
+ * 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.
+ */
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.LocalVariablesSorter;
+import scouter.agent.ClassDesc;
+import scouter.agent.Configure;
+import scouter.agent.Logger;
+import scouter.agent.asm.IASM;
+import scouter.agent.trace.TraceReactive;
+
+/**
+ * Created by Gun Lee(gunlee01@gmail.com) on 30/07/2020
+ */
+public class ThreadASM implements IASM, Opcodes {
+
+ private Configure conf = Configure.getInstance();
+
+ @Override
+ public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) {
+ if (conf._hook_thread_name_enabled == false) {
+ return cv;
+ }
+
+ if ("java/lang/Thread".equals(className)){
+ return new ThreadCV(cv, className);
+ }
+
+ return cv;
+ }
+}
+
+class ThreadCV extends ClassVisitor implements Opcodes {
+
+ private String className;
+ public ThreadCV(ClassVisitor cv, String className) {
+ super(ASM8, cv);
+ this.className = className;
+ Logger.println("G001", "Thread.class - " + className);
+ }
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
+ if (mv == null) {
+ return mv;
+ }
+ if ("setName".equals(name)) {
+ return new ThreadNameMV(access, desc, mv, className);
+ }
+ return mv;
+ }
+}
+
+class ThreadNameMV extends LocalVariablesSorter implements Opcodes {
+ private static final String TRACE = TraceReactive.class.getName().replace('.', '/');
+
+ private String className;
+
+ public ThreadNameMV(int access, String desc, MethodVisitor mv, String className) {
+ super(ASM8, access, desc, mv);
+ this.className = className;
+ }
+
+ @Override
+ public void visitCode() {
+ mv.visitVarInsn(Opcodes.ALOAD, 0);
+ mv.visitVarInsn(Opcodes.ALOAD, 1);
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACE, "threadSetName", "(Ljava/lang/Thread;Ljava/lang/String;)V", false);
+
+ mv.visitCode();
+ }
+}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/asyncsupport/executor/ExecutorServiceASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/asyncsupport/executor/ExecutorServiceASM.java
index 44b3fd15e..9e70ecea3 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/asm/asyncsupport/executor/ExecutorServiceASM.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/asm/asyncsupport/executor/ExecutorServiceASM.java
@@ -40,12 +40,9 @@ public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc class
if (conf.hook_async_thread_pool_executor_enabled == false) {
return cv;
}
- Logger.trace("[SCTRACE]className IN ExecutorServiceASM : " + className);
if (THREAD_POOL_EXECUTOR_CLASS_NAME.equals(className)) {
- Logger.trace("[SCTRACE]transform ThreadPoolExecutor");
return new ThreadPoolExecutorCV(cv, className);
} else if (ABSTRACT_EXECUTOR_SERVICE_CLASS_NAME.equals(className)) {
- Logger.trace("[SCTRACE]transform AbstractExecutorService");
return new AbstractExecutorServiceCV(cv, className);
}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/elasticsearch/HttpNioEntityASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/elasticsearch/HttpNioEntityASM.java
new file mode 100644
index 000000000..ddd335aee
--- /dev/null
+++ b/scouter.agent.java/src/main/java/scouter/agent/asm/elasticsearch/HttpNioEntityASM.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2015 the original author or authors.
+ * @https://github.com/scouter-project/scouter
+ *
+ * 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 scouter.agent.asm.elasticsearch;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.LocalVariablesSorter;
+import scouter.agent.ClassDesc;
+import scouter.agent.Configure;
+import scouter.agent.Logger;
+import scouter.agent.asm.IASM;
+
+import static scouter.agent.AgentCommonConstant.SCOUTER_ADDED_FIELD;
+
+public class HttpNioEntityASM implements IASM, Opcodes {
+
+ private Configure conf = Configure.getInstance();
+
+ public HttpNioEntityASM() {
+ }
+
+ public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) {
+ if (conf._hook_elasticsearch_enabled == false) {
+ return cv;
+ }
+
+ if ("org/apache/http/nio/entity/NByteArrayEntity".equals(className)
+ || "org/apache/http/nio/entity/NStringEntity".equals(className)) {
+ return new HttpNioEntityCV(cv, className);
+ }
+ return cv;
+ }
+}
+
+class HttpNioEntityCV extends ClassVisitor implements Opcodes {
+
+ public String className;
+
+ public HttpNioEntityCV(ClassVisitor cv, String className) {
+ super(ASM8, cv);
+ this.className = className;
+ }
+
+ boolean exist = false;
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ super.visit(version, access, name, signature, superName, interfaces);
+ if (!exist) {
+ super.visitField(ACC_PUBLIC, SCOUTER_ADDED_FIELD, Type.getDescriptor(Object.class), null, null).visitEnd();
+ }
+ }
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
+ if (name.equals(SCOUTER_ADDED_FIELD)) {
+ exist = true;
+ Logger.println("A901e", "fail to add the field " + name + " on " + className);
+ }
+ return super.visitField(access, name, desc, signature, value);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
+ if (mv == null) {
+ return mv;
+ }
+
+ if ("".equals(name) &&
+ (desc.startsWith("([B") || desc.startsWith("(Ljava/lang/String;"))) {
+ return new HttpNioEntityMV(access, desc, mv, className);
+ }
+ return mv;
+ }
+}
+
+class HttpNioEntityMV extends LocalVariablesSorter implements Opcodes {
+ private String className;
+
+ public HttpNioEntityMV(int access, String desc, MethodVisitor mv, String className) {
+ super(ASM8, access, desc, mv);
+ this.className = className;
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ if ((opcode >= IRETURN && opcode <= RETURN)) {
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitFieldInsn(PUTFIELD, className, SCOUTER_ADDED_FIELD, "Ljava/lang/Object;");
+ }
+ mv.visitInsn(opcode);
+ }
+}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/elasticsearch/RestClientASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/elasticsearch/RestClientASM.java
new file mode 100644
index 000000000..0fc2962cf
--- /dev/null
+++ b/scouter.agent.java/src/main/java/scouter/agent/asm/elasticsearch/RestClientASM.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2015 the original author or authors.
+ * @https://github.com/scouter-project/scouter
+ *
+ * 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 scouter.agent.asm.elasticsearch;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.LocalVariablesSorter;
+import scouter.agent.ClassDesc;
+import scouter.agent.Configure;
+import scouter.agent.asm.IASM;
+import scouter.agent.trace.TraceElasticSearch;
+
+public class RestClientASM implements IASM, Opcodes {
+
+ private Configure conf = Configure.getInstance();
+
+ public RestClientASM() {
+ }
+
+ public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) {
+ if (conf._hook_elasticsearch_enabled == false) {
+ return cv;
+ }
+
+ if ("org/elasticsearch/client/RestClient".equals(className)) {
+ return new RestClientCV(cv, className);
+ } else if ("org/elasticsearch/client/RequestLogger".equals(className)) {
+ return new RequestLoggerCV(cv, className);
+ }
+ return cv;
+ }
+
+ static class RestClientCV extends ClassVisitor implements Opcodes {
+ public String className;
+
+ public RestClientCV(ClassVisitor cv, String className) {
+ super(ASM8, cv);
+ this.className = className;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
+ if (mv == null) {
+ return mv;
+ }
+
+ if ("performRequestAsync".equals(name) && desc.startsWith("(JLorg/elasticsearch/client/RestClient$NodeTuple;Lorg/apache/http/client/methods/HttpRequestBase;")) {
+ return new RestClientStartMV(access, desc, mv, className);
+ }
+ return mv;
+ }
+ }
+
+ static class RestClientStartMV extends LocalVariablesSorter implements Opcodes {
+ private static final String TRACE = TraceElasticSearch.class.getName().replace('.', '/');
+ private final static String METHOD = "startRequest";
+ private static final String SIGNATURE = "(Ljava/lang/Object;)V";
+
+ private String className;
+
+ public RestClientStartMV(int access, String desc, MethodVisitor mv, String className) {
+ super(ASM8, access, desc, mv);
+ this.className = className;
+ }
+
+ @Override
+ public void visitCode() {
+ mv.visitVarInsn(ALOAD, 4);
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACE, METHOD, SIGNATURE, false);
+ mv.visitCode();
+ }
+ }
+
+
+ static class RequestLoggerCV extends ClassVisitor implements Opcodes {
+ public String className;
+
+ public RequestLoggerCV(ClassVisitor cv, String className) {
+ super(ASM8, cv);
+ this.className = className;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
+ if (mv == null) {
+ return mv;
+ }
+ if ("logResponse".equals(name)
+ && desc.startsWith("(Lorg/apache/commons/logging/Log;Lorg/apache/http/client/methods/HttpUriRequest;Lorg/apache/http/HttpHost;Lorg/apache/http/HttpResponse;")) {
+ return new RequestLoggerMV(access, desc, mv, className);
+
+ } else if ("logFailedRequest".equals(name)
+ && desc.startsWith("(Lorg/apache/commons/logging/Log;Lorg/apache/http/client/methods/HttpUriRequest;Lorg/elasticsearch/client/Node;Ljava/lang/Exception;")) {
+ return new RequestFailLoggerMV(access, desc, mv, className);
+ }
+ return mv;
+ }
+
+ static class RequestLoggerMV extends LocalVariablesSorter implements Opcodes {
+ private static final String TRACE = TraceElasticSearch.class.getName().replace('.', '/');
+ private final static String METHOD = "endRequest";
+ private static final String SIGNATURE = "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V";
+
+ private String className;
+
+ public RequestLoggerMV(int access, String desc, MethodVisitor mv, String className) {
+ super(ASM8, access, desc, mv);
+ this.className = className;
+ }
+
+ @Override
+ public void visitCode() {
+ mv.visitVarInsn(ALOAD, 1); //HttpUriRequest
+ mv.visitVarInsn(ALOAD, 2); //HttpHost
+ mv.visitVarInsn(ALOAD, 3); //HttpResponse
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACE, METHOD, SIGNATURE, false);
+ mv.visitCode();
+ }
+ }
+
+ static class RequestFailLoggerMV extends LocalVariablesSorter implements Opcodes {
+ private static final String TRACE = TraceElasticSearch.class.getName().replace('.', '/');
+ private final static String METHOD = "endFailRequest";
+ private static final String SIGNATURE = "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Exception;)V";
+
+ private String className;
+
+ public RequestFailLoggerMV(int access, String desc, MethodVisitor mv, String className) {
+ super(ASM8, access, desc, mv);
+ this.className = className;
+ }
+
+ @Override
+ public void visitCode() {
+ mv.visitVarInsn(ALOAD, 1); //HttpUriRequest
+ mv.visitVarInsn(ALOAD, 2); //Node
+ mv.visitVarInsn(ALOAD, 3); //Exception
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACE, METHOD, SIGNATURE, false);
+ mv.visitCode();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/scouter.agent.java/src/main/java/scouter/agent/counter/CounterExecutingManager.java b/scouter.agent.java/src/main/java/scouter/agent/counter/CounterExecutingManager.java
index f2c792c6c..1b2f86160 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/counter/CounterExecutingManager.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/counter/CounterExecutingManager.java
@@ -163,7 +163,8 @@ public void process(CounterBasket pw) throws Throwable {
try {
method.invoke(object, pw);
} catch (Exception e) {
- Logger.println("A111", object.getClass() + " " + method + " " + e);
+ Logger.println("A111", object.getClass() + " " + method + " " + e.getMessage(), e);
+ e.printStackTrace();
}
}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/counter/meter/MeterInteractionManager.java b/scouter.agent.java/src/main/java/scouter/agent/counter/meter/MeterInteractionManager.java
index e564bd046..2bc5c15ba 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/counter/meter/MeterInteractionManager.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/counter/meter/MeterInteractionManager.java
@@ -27,6 +27,7 @@
import static scouter.lang.counters.CounterConstants.INTR_API_INCOMING;
import static scouter.lang.counters.CounterConstants.INTR_API_OUTGOING;
import static scouter.lang.counters.CounterConstants.INTR_DB_CALL;
+import static scouter.lang.counters.CounterConstants.INTR_ELASTICSEARCH_CALL;
import static scouter.lang.counters.CounterConstants.INTR_KAFKA_CALL;
import static scouter.lang.counters.CounterConstants.INTR_RABBITMQ_CALL;
import static scouter.lang.counters.CounterConstants.INTR_NORMAL_INCOMING;
@@ -48,6 +49,7 @@ public class MeterInteractionManager extends Thread {
private static LinkedMap redisCallMeterMap = new LinkedMap().setMax(1000);
private static LinkedMap kafkaCallMeterMap = new LinkedMap().setMax(1000);
private static LinkedMap rabbitmqCallMeterMap = new LinkedMap().setMax(1000);
+ private static LinkedMap elasticSearchCallMeterMap = new LinkedMap().setMax(1000);
private MeterInteractionManager() {
}
@@ -92,6 +94,9 @@ public void run() {
} else if (INTR_RABBITMQ_CALL.equals(type)) {
rabbitmqCallMeterMap.put(key, meterInteraction);
+
+ } else if (INTR_ELASTICSEARCH_CALL.equals(type)) {
+ elasticSearchCallMeterMap.put(key, meterInteraction);
}
}
}
@@ -194,6 +199,18 @@ public MeterInteraction getRabbitmqCallMeter(int fromHash, int toHash) {
return meter;
}
+ /**
+ * @return nullable
+ */
+ public MeterInteraction getElasticSearchCallMeter(int fromHash, int toHash) {
+ Key key = new Key(fromHash, toHash);
+ MeterInteraction meter = elasticSearchCallMeterMap.get(key);
+ if (meter == null) {
+ queue.put(new Pair(INTR_ELASTICSEARCH_CALL, key));
+ }
+ return meter;
+ }
+
public LinkedMap getApiOutgoingMeterMap() {
return apiOutgoingMeterMap;
}
@@ -226,6 +243,10 @@ public LinkedMap getRabbitmqCallMeterMap() {
return rabbitmqCallMeterMap;
}
+ public LinkedMap getElasticSearchCallMeterMap() {
+ return elasticSearchCallMeterMap;
+ }
+
public static class Key {
public int fromHash;
diff --git a/scouter.agent.java/src/main/java/scouter/agent/counter/task/DebugService.java b/scouter.agent.java/src/main/java/scouter/agent/counter/task/DebugService.java
index fd1769030..cff4e1444 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/counter/task/DebugService.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/counter/task/DebugService.java
@@ -17,12 +17,6 @@
package scouter.agent.counter.task;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.Enumeration;
-
import scouter.agent.Configure;
import scouter.agent.Logger;
import scouter.agent.counter.CounterBasket;
@@ -37,6 +31,12 @@
import scouter.util.Hexa32;
import scouter.util.SysJMX;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+
public class DebugService {
Configure conf = Configure.getInstance();
@@ -53,6 +53,7 @@ public void checkService(CounterBasket pw) {
lastCheckStuckTime = now;
}
StringBuilder stuckMsg = new StringBuilder();
+ //TODO reactive support
Enumeration en = TraceContextManager.getContextEnumeration();
while (en.hasMoreElements()) {
TraceContext ctx = en.nextElement();
@@ -71,6 +72,7 @@ public void checkService(CounterBasket pw) {
}
private void checkStcukService(TraceContext ctx, PrintWriter out, StringBuilder msg) {
+ //TODO reactive support
if (conf.autodump_stuck_thread_ms <= 0) return;
long etime = System.currentTimeMillis() - ctx.startTime;
if (etime > conf.autodump_stuck_thread_ms) {
diff --git a/scouter.agent.java/src/main/java/scouter/agent/counter/task/InteractionPerf.java b/scouter.agent.java/src/main/java/scouter/agent/counter/task/InteractionPerf.java
index 71cfb0ab2..08922a54b 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/counter/task/InteractionPerf.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/counter/task/InteractionPerf.java
@@ -118,7 +118,7 @@ public void collectKafkaCallInteractionCounter(InteractionCounterBasket basket)
}
@InteractionCounter(interval = 5000)
- public void collecRabbitmqCallInteractionCounter(InteractionCounterBasket basket) {
+ public void collectRabbitmqCallInteractionCounter(InteractionCounterBasket basket) {
if (!conf.counter_interaction_enabled) {
return;
}
@@ -129,6 +129,18 @@ public void collecRabbitmqCallInteractionCounter(InteractionCounterBasket basket
addInteractionsToBasket(basket, interactionType, rabbitmqCallMeterMap, periodSec);
}
+ @InteractionCounter(interval = 5000)
+ public void collectElasticSearchCallInteractionCounter(InteractionCounterBasket basket) {
+ if (!conf.counter_interaction_enabled) {
+ return;
+ }
+
+ int periodSec = 30;
+ String interactionType = CounterConstants.INTR_ELASTICSEARCH_CALL;
+ LinkedMap esMeterMap = MeterInteractionManager.getInstance().getElasticSearchCallMeterMap();
+ addInteractionsToBasket(basket, interactionType, esMeterMap, periodSec);
+ }
+
private void addInteractionsToBasket(InteractionCounterBasket basket, String interactionType, LinkedMap apiIncomingMeterMap, int periodSec) {
Enumeration> entries = apiIncomingMeterMap.entries();
diff --git a/scouter.agent.java/src/main/java/scouter/agent/counter/task/MakeStack.java b/scouter.agent.java/src/main/java/scouter/agent/counter/task/MakeStack.java
index a0961f836..fdb829a31 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/counter/task/MakeStack.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/counter/task/MakeStack.java
@@ -8,6 +8,7 @@
import scouter.agent.proxy.ToolsMainFactory;
import scouter.agent.trace.TraceContext;
import scouter.agent.trace.TraceContextManager;
+import scouter.agent.trace.TraceMain;
import scouter.lang.pack.StackPack;
import scouter.lang.step.DumpStep;
@@ -16,99 +17,170 @@
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
+import java.util.ArrayList;
import java.util.Enumeration;
+import java.util.List;
public class MakeStack {
- static Configure conf = Configure.getInstance();
-
- public long lastStackTime;
- @Counter
- public void make(CounterBasket pw) {
- if (isPStackEnabled()== false){
- ToolsMainFactory.activeStack=false;
- return;
- }
- long now = System.currentTimeMillis();
- if (now < lastStackTime + getSFAInterval())
- return;
- lastStackTime = now;
- StringWriter sw = new StringWriter();
- PrintWriter out = new PrintWriter(sw);
- try {
- ToolsMainFactory.threadDump(out);
- } catch (Throwable e) {
- } finally {
- out.close();
- }
-
- String stack = sw.getBuffer().toString();
-
- StackPack p = new StackPack();
- p.time = System.currentTimeMillis();
- p.objHash = conf.getObjHash();
- p.setStack(stack);
-
- DataProxy.sendDirect(p);
-
- long elapsed = (System.currentTimeMillis() - now);
- Logger.trace("[SFA Counter Elasped]" + elapsed);
- }
-
- public static long pstack_requested;
- private boolean isPStackEnabled() {
- return conf.sfa_dump_enabled || System.currentTimeMillis() < pstack_requested;
- }
- private long getSFAInterval() {
- return conf.sfa_dump_interval_ms;
- }
-
-
- long lastStackTraceGenTime = 0;
- @Counter
- public void stackTraceStepGenerator(CounterBasket pw) {
- if (!conf._psts_enabled){
- return;
- }
-
- long now = System.currentTimeMillis();
- if (now < lastStackTraceGenTime + conf._psts_dump_interval_ms) {
- return;
- }
- lastStackTraceGenTime = now;
-
- ThreadMXBean tmxBean = ManagementFactory.getThreadMXBean();
- Enumeration en = TraceContextManager.getContextEnumeration();
- while (en.hasMoreElements()) {
- TraceContext ctx = en.nextElement();
- if(ctx == null || ctx.threadId <= 0) {
- continue;
- }
-
- ThreadInfo tInfo = tmxBean.getThreadInfo(ctx.threadId, 50);
- if (tInfo == null) continue;
-
- StackTraceElement[] elements = tInfo.getStackTrace();
- int length = elements.length;
- int[] stacks = new int[length];
-
- for(int i=0; i en = TraceContextManager.getContextEnumeration();
+ while (en.hasMoreElements()) {
+ TraceContext ctx = en.nextElement();
+ if (ctx != null) {
+ if (ctx.isReactiveStarted) {
+ reactiveStepDump(tmxBean, ctx);
+ } else {
+ stepDump(tmxBean, ctx);
+ }
+ }
+ }
+ long elapsed = (System.currentTimeMillis() - now);
+ }
+
+ private void stepDump(ThreadMXBean tmxBean, TraceContext ctx) {
+ if (ctx == null || ctx.threadId <= 0) {
+ return;
+ }
+
+ ThreadInfo tInfo = tmxBean.getThreadInfo(ctx.threadId, 50);
+ if (tInfo == null) return;
+
+ StackTraceElement[] elements = tInfo.getStackTrace();
+ int length = elements.length;
+ int[] stacks = new int[length];
+
+ for (int i = 0; i < length; i++) {
+ stacks[i] = DataProxy.sendStackElement(elements[i]);
+ }
+ DumpStep dumpStep = new DumpStep();
+ dumpStep.start_time = (int) (System.currentTimeMillis() - ctx.startTime);
+ dumpStep.stacks = stacks;
+ dumpStep.threadId = ctx.threadId;
+ dumpStep.threadName = tInfo.getThreadName();
+ dumpStep.threadState = tInfo.getThreadState().toString();
+ dumpStep.lockOwnerId = tInfo.getLockOwnerId();
+ dumpStep.lockName = tInfo.getLockName();
+ dumpStep.lockOwnerName = tInfo.getLockOwnerName();
+
+ ctx.temporaryDumpSteps.offer(dumpStep);
+ ctx.hasDumpStack = true;
+ }
+
+ private void reactiveStepDump(ThreadMXBean tmxBean, TraceContext ctx) {
+ if (ctx == null) {
+ return;
+ }
+
+ long now = System.currentTimeMillis();
+
+ List stacks = new ArrayList();
+
+ DumpStep dumpStep = new DumpStep();
+ dumpStep.start_time = (int) (System.currentTimeMillis() - ctx.startTime);
+
+ long threadId = TraceContextManager.getReactiveThreadId(ctx.txid);
+ if (threadId != 0) {
+ ThreadInfo tInfo = tmxBean.getThreadInfo(ctx.threadId, 50);
+ if (tInfo != null) {
+ StackTraceElement[] elements = tInfo.getStackTrace();
+ for (StackTraceElement element : elements) {
+ stacks.add(DataProxy.sendStackElement(element));
+ }
+
+ dumpStep.threadId = threadId;
+ dumpStep.threadName = tInfo.getThreadName();
+ dumpStep.threadState = tInfo.getThreadState().toString();
+ dumpStep.lockOwnerId = tInfo.getLockOwnerId();
+ dumpStep.lockName = tInfo.getLockName();
+ dumpStep.lockOwnerName = tInfo.getLockOwnerName();
+ }
+ }
+
+ if (ctx.scannables != null) {
+ Enumeration en = ctx.scannables.values();
+ stacks.add(DataProxy.sendStackElement("<<<<<<<<<< currently existing subscribes >>>>>>>>>>"));
+ while (en.hasMoreElements()) {
+ TraceContext.TimedScannable ts = en.nextElement();
+ if (ts == null) {
+ return;
+ }
+ String dumpScannable = TraceMain.reactiveSupport.dumpScannable(ctx, ts, now);
+ stacks.add(DataProxy.sendStackElement(dumpScannable));
+ }
+
+ dumpStep.stacks = convertIntegers(stacks);
+ }
+ ctx.temporaryDumpSteps.offer(dumpStep);
+ ctx.hasDumpStack = true;
+ }
+
+ private static int[] convertIntegers(List integers) {
+ int[] ret = new int[integers.size()];
+ for (int i = 0; i < ret.length; i++) {
+ ret[i] = integers.get(i);
+ }
+ return ret;
+ }
+
}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/netio/data/DataProxy.java b/scouter.agent.java/src/main/java/scouter/agent/netio/data/DataProxy.java
index a004378d0..3dcfb037b 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/netio/data/DataProxy.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/netio/data/DataProxy.java
@@ -362,4 +362,13 @@ public static int sendStackElement(StackTraceElement ste) {
udpCollect.add(new TextPack(TextTypes.STACK_ELEMENT, hash, ste.toString()));
return hash;
}
+ public static int sendStackElement(String ste) {
+ int hash = ste.hashCode();
+ if (stackElement.contains(hash)) {
+ return hash;
+ }
+ stackElement.put(hash);
+ udpCollect.add(new TextPack(TextTypes.STACK_ELEMENT, hash, ste));
+ return hash;
+ }
}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/netio/request/handle/AgentThread.java b/scouter.agent.java/src/main/java/scouter/agent/netio/request/handle/AgentThread.java
index a5497b7a6..5bc0ff7a1 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/netio/request/handle/AgentThread.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/netio/request/handle/AgentThread.java
@@ -24,10 +24,15 @@
import scouter.agent.proxy.ToolsMainFactory;
import scouter.agent.trace.TraceContext;
import scouter.agent.trace.TraceContextManager;
+import scouter.agent.trace.TraceMain;
import scouter.agent.util.DumpUtil;
import scouter.lang.pack.MapPack;
import scouter.lang.pack.Pack;
-import scouter.lang.value.*;
+import scouter.lang.value.BooleanValue;
+import scouter.lang.value.DecimalValue;
+import scouter.lang.value.ListValue;
+import scouter.lang.value.NullValue;
+import scouter.lang.value.TextValue;
import scouter.util.CastUtil;
import scouter.util.Hexa32;
import scouter.util.SysJMX;
@@ -36,65 +41,105 @@
import java.io.IOException;
import java.util.Enumeration;
-import static scouter.net.RequestCmd.*;
+import static scouter.net.RequestCmd.OBJECT_ACTIVE_SERVICE_LIST;
+import static scouter.net.RequestCmd.OBJECT_THREAD_CONTROL;
+import static scouter.net.RequestCmd.OBJECT_THREAD_DETAIL;
+import static scouter.net.RequestCmd.OBJECT_THREAD_DUMP;
+import static scouter.net.RequestCmd.OBJECT_THREAD_LIST;
+import static scouter.net.RequestCmd.PSTACK_ON;
+import static scouter.net.RequestCmd.TRIGGER_ACTIVE_SERVICE_LIST;
+import static scouter.net.RequestCmd.TRIGGER_DUMP_REASON;
+import static scouter.net.RequestCmd.TRIGGER_THREAD_DUMP;
+import static scouter.net.RequestCmd.TRIGGER_THREAD_DUMPS_FROM_CONDITIONS;
+import static scouter.net.RequestCmd.TRIGGER_THREAD_LIST;
public class AgentThread {
@RequestHandler(OBJECT_THREAD_DETAIL)
public Pack threadDetail(Pack param) {
MapPack paramPack = (MapPack) param;
- long thread = paramPack.getLong("id");
+ long threadId = paramPack.getLong("id");
long txid = paramPack.getLong("txid");
- MapPack p;
- TraceContext ctx;
+ MapPack p = new MapPack();
+ TraceContext ctx = TraceContextManager.getContextByTxid(txid);
+ if (ctx == null) {
+ p.put("Thread Name", new TextValue("[No Thread] End"));
+ p.put("State", new TextValue("end"));
+ return p;
+ }
- if(thread != 0L) {
- p = ThreadUtil.getThreadDetail(thread);
- ctx = TraceContextManager.getContext(thread);
+ if (ctx.isReactiveStarted) {
+ threadId = TraceContextManager.getReactiveThreadId(txid);
+ }
- if (ctx != null) {
- p.put("Service Txid", new TextValue(Hexa32.toString32(ctx.txid)));
- p.put("Service Name", new TextValue(ctx.serviceName));
- long etime = System.currentTimeMillis() - ctx.startTime;
- p.put("Service Elapsed", new DecimalValue(etime));
- String sql = ctx.sqltext;
- if (sql != null) {
- p.put("SQL", sql);
- }
- String subcall = ctx.apicall_name;
- if (subcall != null) {
- p.put("Subcall", subcall);
- }
- }
+ p.put("Service Txid", new TextValue(Hexa32.toString32(ctx.txid)));
+ p.put("Service Name", new TextValue(ctx.serviceName));
+ long etime = System.currentTimeMillis() - ctx.startTime;
+ p.put("Service Elapsed", new DecimalValue(etime));
+ String sql = ctx.sqltext;
+ if (sql != null) {
+ p.put("SQL", sql);
+ }
+ String subcall = ctx.apicall_name;
+ if (subcall != null) {
+ p.put("Subcall", subcall);
+ }
- } else {
- p = new MapPack();
- ctx = TraceContextManager.getDeferredContext(txid);
- p.put("Thread Id", new DecimalValue(0L));
+ if(threadId != 0L) {
+ p = ThreadUtil.appendThreadDetail(threadId, p);
- if (ctx != null) {
+ } else {
+ TraceContext deferredContext = TraceContextManager.getDeferredContext(txid);
+ if (deferredContext != null) {
p.put("Thread Name", new TextValue("[No Thread] wait on deferred queue"));
- p.put("State", new TextValue("n/a"));
-
- p.put("Service Txid", new TextValue(Hexa32.toString32(ctx.txid)));
- p.put("Service Name", new TextValue(ctx.serviceName));
- long etime = System.currentTimeMillis() - ctx.startTime;
- p.put("Service Elapsed", new DecimalValue(etime));
-
} else {
- p.put("Thread Name", new TextValue("[No Thread] End"));
- p.put("State", new TextValue("end"));
+ p.put("Thread Name", new TextValue("No dedicated thread"));
}
+ p.put("Thread Id", new DecimalValue(0L));
+ p.put("State", new TextValue("n/a"));
+ }
+
+ if (ctx.isReactiveStarted) {
+ String stack = p.getText("Stack Trace");
+ if (stack == null) {
+ stack = "";
+ }
+ stack = stack + "\n" + getUnfinishedReactiveStepsAsDumpString(ctx);
+ p.put("Stack Trace", new TextValue(stack));
}
return p;
}
+
+ private String getUnfinishedReactiveStepsAsDumpString(TraceContext ctx) {
+ if (ctx == null) {
+ return null;
+ }
+
+ long now = System.currentTimeMillis();
+ StringBuilder builder = new StringBuilder(200)
+ .append("<<<<<<<<<< currently existing subscribes >>>>>>>>>>").append("\n");
+
+ if (ctx.scannables != null) {
+ Enumeration en = ctx.scannables.values();
+ while (en.hasMoreElements()) {
+ TraceContext.TimedScannable ts = en.nextElement();
+ if (ts == null) {
+ break;
+ }
+ String dumpScannable = TraceMain.reactiveSupport.dumpScannable(ctx, ts, now);
+ builder.append(dumpScannable).append("\n");
+ }
+ }
+ return builder.toString();
+ }
+
@RequestHandler(OBJECT_THREAD_CONTROL)
public Pack threadKill(Pack param) {
long thread = ((MapPack) param).getLong("id");
String action = ((MapPack) param).getText("action");
// 쓰레드 상세 화면에서 쓰레드를 제어한다.
- TraceContext ctx = TraceContextManager.getContext(thread);
+ TraceContext ctx = TraceContextManager.getContextByThreadId(thread);
try {
if (ctx != null) {
if ("interrupt".equalsIgnoreCase(action)) {
@@ -135,7 +180,7 @@ public Pack threadList(Pack param) {
ListValue service = mpack.newList("service");
for (int i = 0; i < ids.size(); i++) {
long tid = CastUtil.clong(ids.get(i));
- TraceContext ctx = TraceContextManager.getContext(tid);
+ TraceContext ctx = TraceContextManager.getContextByThreadId(tid);
if (ctx != null) {
txid.add(new TextValue(Hexa32.toString32(ctx.txid)));
service.add(new TextValue(ctx.serviceName));
@@ -165,15 +210,33 @@ public Pack activeThreadList(Pack param) {
ListValue subcall = rPack.newList("subcall");
ListValue login = rPack.newList("login");
ListValue desc = rPack.newList("desc");
+
Enumeration en = TraceContextManager.getContextEnumeration();
while (en.hasMoreElements()) {
TraceContext ctx = en.nextElement();
if (ctx == null) {
continue;
}
- id.add(ctx.thread.getId());
- name.add(ctx.thread.getName());
- stat.add(ctx.thread.getState().name());
+ if (!ctx.isReactiveStarted) {
+ id.add(ctx.thread.getId());
+ name.add(ctx.thread.getName());
+ stat.add(ctx.thread.getState().name());
+ } else {
+ if (Configure.getInstance()._psts_progressive_reactor_thread_trace_enabled) {
+ id.add(TraceContextManager.getReactiveThreadId(ctx.txid));
+ } else {
+ id.add(0);
+ }
+ name.add("omit on reactive");
+ stat.add("n/a");
+ }
+ try {
+ cpu.add(SysJMX.getThreadCpuTime(ctx.thread));
+ } catch (Throwable th) {
+ Logger.println("A128", th);
+ cpu.add(0L);
+ }
+
txid.add(new TextValue(Hexa32.toString32(ctx.txid)));
service.add(new TextValue(ctx.serviceName));
ip.add(ctx.remoteIp);
@@ -181,12 +244,6 @@ public Pack activeThreadList(Pack param) {
elapsed.add(new DecimalValue(etime));
sql.add(ctx.sqltext);
subcall.add(ctx.apicall_name);
- try {
- cpu.add(SysJMX.getThreadCpuTime(ctx.thread));
- } catch (Throwable th) {
- Logger.println("A128", th);
- cpu.add(0L);
- }
login.add(ctx.login);
desc.add(ctx.desc);
}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/plugin/AbstractPlugin.java b/scouter.agent.java/src/main/java/scouter/agent/plugin/AbstractPlugin.java
index 5a5a9bbc2..49576db70 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/plugin/AbstractPlugin.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/plugin/AbstractPlugin.java
@@ -39,8 +39,6 @@ public static Object invokeMethod(Object o, String methodName) throws NoSuchMeth
public static Object invokeMethod(Object o, String methodName, Object[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
int argsSize = args.length;
- StringBuilder signature = new StringBuilder(o.getClass().getName()).append(":").append(methodName).append("():");
-
Class[] argClazzes = new Class[argsSize];
for(int i=0; i {
+ private static final ThreadLocal COROUTINE_DEBUGGING_ID = new ThreadLocal();
+
+ private static final LongKeyMap CID_TRACE_CONTEXT = new LongKeyMap();
+
+ public static void setCoroutineDebuggingId(Long id) {
+ COROUTINE_DEBUGGING_ID.set(id);
+ }
+
+ public static Long getCoroutineDebuggingId() {
+ return COROUTINE_DEBUGGING_ID.get();
+ }
+
+ public static void releaseCoroutineId() {
+ COROUTINE_DEBUGGING_ID.remove();
+ }
+
+
+ public T get() {
+ Long coroutineId = getCoroutineDebuggingId();
+ if (coroutineId == null) {
+ return null;
+ }
+ return (T) CID_TRACE_CONTEXT.get(coroutineId);
+ }
+
+ public T get(long id) {
+ return (T) CID_TRACE_CONTEXT.get(id);
+ }
+
+ public void put(T obj) {
+ Long coroutineId = getCoroutineDebuggingId();
+ CID_TRACE_CONTEXT.put(coroutineId, obj);
+ }
+
+ public void clear() {
+ Long coroutineId = getCoroutineDebuggingId();
+ if (coroutineId == null) {
+ return;
+ }
+ CID_TRACE_CONTEXT.put(coroutineId, null);
+ releaseCoroutineId();
+ }
+}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/trace/StepTransferMap.java b/scouter.agent.java/src/main/java/scouter/agent/trace/StepTransferMap.java
new file mode 100644
index 000000000..cb0222bd3
--- /dev/null
+++ b/scouter.agent.java/src/main/java/scouter/agent/trace/StepTransferMap.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2015 the original author or authors.
+ * @https://github.com/scouter-project/scouter
+ *
+ * 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 scouter.agent.trace;
+
+import scouter.lang.step.Step;
+import scouter.util.IntKeyLinkedMap;
+
+public class StepTransferMap {
+
+ public static class ID {
+ public TraceContext ctx;
+ public Step step;
+ public Object option;
+
+ public ID(TraceContext ctx, Step step, Object option) {
+ this.ctx = ctx;
+ this.step = step;
+ this.option = option;
+ }
+ }
+
+ private static IntKeyLinkedMap map = new IntKeyLinkedMap().setMax(2001);
+
+ public static void put(int hash, TraceContext ctx, Step step) {
+ map.put(hash, new ID(ctx,step, null));
+ }
+ public static void put(int hash, TraceContext ctx, Step step, Object option) {
+ map.put(hash, new ID(ctx,step, option));
+ }
+
+ public static void remove(int hash) {
+ map.remove(hash);
+ }
+
+ public static ID get(int hash) {
+ return map.get(hash);
+ }
+
+
+}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceApiCall.java b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceApiCall.java
index 8195f609e..8c655e5ac 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceApiCall.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceApiCall.java
@@ -287,6 +287,9 @@ public String getHeader(Object o, String key) {
public String getResponseHeader(Object o, String key) {
return null;
}
+ public int getResponseStatusCode(Object o) {
+ return 200;
+ }
public void addHeader(Object o, String key, String value) {
}
};
@@ -345,6 +348,30 @@ public static void endCreateSpringRestTemplateRequest(Object _this, Object oRtn)
}
}
+ public static void webClientInfo(Object bodyInserter, Object clientHttpRequest) {
+ if (!conf.trace_interservice_enabled) {
+ return;
+ }
+ ApiCallTraceHelper.webClientInfo(bodyInserter, clientHttpRequest);
+ }
+
+ public static void endWebClientApicall(Object exchangeFunction, Object clientResponse) {
+ if (!conf.trace_interservice_enabled) {
+ return;
+ }
+ LocalContext localContext = ApiCallTraceHelper.webClientProcessEnd(exchangeFunction, clientResponse);
+ Object option = localContext.option;
+ localContext.option = null;
+ Throwable throwable = null;
+ if (option instanceof Integer) {
+ int statusCode = (Integer) option;
+ if (statusCode >= 400) {
+ throwable = new RuntimeException("WebClient response code: " + statusCode);
+ }
+ }
+ endApicall(localContext, clientResponse, throwable);
+ }
+
public static void setCalleeToCtxInHttpClientResponse(Object _this, Object res) {
if (!conf.trace_interservice_enabled) {
return;
@@ -426,7 +453,7 @@ public static void initImmutableJavaHttpRequest(Object requestBuilder) {
if(ctx == null) return;
try {
- ApiCallTraceHelper.setCalleeToCtxJavaHttpRequest(ctx, requestBuilder);
+ ApiCallTraceHelper.setTransferToCtxJavaHttpRequest(ctx, requestBuilder);
} catch (Exception e) {
e.printStackTrace();
}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceContext.java b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceContext.java
index 7dcb51c77..72e775fd9 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceContext.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceContext.java
@@ -17,12 +17,14 @@
package scouter.agent.trace;
+import scouter.agent.proxy.IHttpTrace;
import scouter.lang.pack.XLogDiscardTypes;
import scouter.lang.step.ApiCallStep;
import scouter.lang.step.DumpStep;
import scouter.lang.step.SqlStep;
import scouter.lang.step.ThreadCallPossibleStep;
import scouter.util.IntKeyMap;
+import scouter.util.LongKeyLinkedMap;
import scouter.util.SysJMX;
import java.util.ArrayList;
@@ -30,10 +32,34 @@
import java.util.concurrent.LinkedBlockingQueue;
public class TraceContext {
+ public static class TimedScannable {
+ public long start;
+ public Object scannable;
+
+ public TimedScannable(long start, Object scannable) {
+ this.start = start;
+ this.scannable = scannable;
+ }
+ }
+ public enum GetBy {
+ ThreadLocal,
+ ThreadLocalTxid,
+ ThreadLocalTxidByCoroutine,
+ CoroutineLocal
+ }
+ public GetBy getBy;
+ public LongKeyLinkedMap scannables;
+
private boolean isSummary;
public boolean isStaticContents;
public boolean isFullyDiscardService;
+ public boolean isReactiveStarted;
+ public boolean isReactiveTxidMarked;
+ public long exchangeHashCode;
+ public boolean isCoroutineStarted;
+ public boolean isOnCoroutineIdUpdating;
+
protected TraceContext() {
}
@@ -46,6 +72,15 @@ public TraceContext(boolean profile_summary) {
}
}
+ public void initScannables() {
+ scannables = new LongKeyLinkedMap();
+ scannables.setMax(10000);
+ }
+
+ public Object req;
+ public Object res;
+ public IHttpTrace http;
+
public TraceContext parent;
public long txid;
public Thread thread;
@@ -164,6 +199,11 @@ public TraceContext(boolean profile_summary) {
public ArrayList plcGroupList = new ArrayList();
public TraceContext createChild() {
TraceContext child = new TraceContext(this.isSummary);
+ if (this.isReactiveStarted) {
+ child.initScannables();
+ child.isReactiveStarted = true;
+ child.exchangeHashCode = this.exchangeHashCode;
+ }
child.parent = this;
child.txid = this.txid;
child.thread = this.thread;
diff --git a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceContextManager.java b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceContextManager.java
index 23de5985f..ea5aa5b67 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceContextManager.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceContextManager.java
@@ -19,29 +19,43 @@
import scouter.agent.Configure;
import scouter.util.KeyGen;
+import scouter.util.LongKeyLinkedMap;
import scouter.util.LongKeyMap;
+import scouter.util.LongLongLinkedMap;
import java.util.Enumeration;
+import static scouter.agent.trace.TraceContext.GetBy.CoroutineLocal;
+import static scouter.agent.trace.TraceContext.GetBy.ThreadLocal;
+import static scouter.agent.trace.TraceContext.GetBy.ThreadLocalTxid;
+import static scouter.agent.trace.TraceContext.GetBy.ThreadLocalTxidByCoroutine;
+
public class TraceContextManager {
private static Configure conf = Configure.getInstance();
- private static LongKeyMap entry = new LongKeyMap();
- private static ThreadLocal local = new ThreadLocal();
+ private static LongKeyMap entryByThreadId = new LongKeyMap();
+ private static LongKeyLinkedMap entryByTxid = new LongKeyLinkedMap().setMax(10000);
+
+ private static final ThreadLocal local = new ThreadLocal();
+ private static final ThreadLocal txidLocal = new ThreadLocal();
+ public static final ThreadLocal txidByCoroutine = new ThreadLocal();
+
+ private static CoroutineDebuggingLocal coroutineDebuggingLocal = new CoroutineDebuggingLocal();
+
private static LongKeyMap deferredEntry = new LongKeyMap();
//pass = 1, discard = 2, end-processing-with-path = -1, end-processing-with-path = -2
private static ThreadLocal forceDiscard = new ThreadLocal();
public static int size() {
- return entry.size();
+ return entryByTxid.size();
}
public static int[] getActiveCount() {
int[] act = new int[3];
try {
long now = System.currentTimeMillis();
- Enumeration en = entry.values();
+ Enumeration en = entryByTxid.values();
while (en.hasMoreElements()) {
TraceContext ctx = en.nextElement();
long tm = now - ctx.startTime;
@@ -53,42 +67,77 @@ public static int[] getActiveCount() {
act[2]++;
}
}
-
- Enumeration enDeferred = deferredEntry.values();
- while (enDeferred.hasMoreElements()) {
- TraceContext ctx = enDeferred.nextElement();
- long tm = now - ctx.startTime;
- if (tm < conf.trace_activeserivce_yellow_time) {
- act[0]++;
- } else if (tm < conf.trace_activeservice_red_time) {
- act[1]++;
- } else {
- act[2]++;
- }
- }
} catch (Throwable t) {
}
return act;
}
public static Enumeration getContextEnumeration() {
- return entry.values();
+ return entryByTxid.values();
+ }
+
+ public static Enumeration getThreadingContextEnumeration() {
+ return entryByThreadId.values();
}
public static Enumeration getDeferredContextEnumeration() {
return deferredEntry.values();
}
- public static TraceContext getContext(long key) {
- return entry.get(key);
+ public static TraceContext getContext() {
+ Long txid = txidLocal.get();
+ TraceContext traceContext = txid == null ? null : entryByTxid.get(txid);
+
+ if (traceContext != null) {
+ traceContext.getBy = ThreadLocalTxid;
+ return traceContext;
+ }
+
+ txid = txidByCoroutine.get();
+ traceContext = txid == null ? null : entryByTxid.get(txid);
+
+ if (traceContext != null) {
+ traceContext.getBy = ThreadLocalTxidByCoroutine;
+ return traceContext;
+ }
+
+ traceContext = local.get();
+ if (traceContext != null) {
+ traceContext.getBy = ThreadLocal;
+ return traceContext;
+ }
+
+ traceContext = getCoroutineContext();
+ if (traceContext != null) {
+ traceContext.getBy = CoroutineLocal;
+ return traceContext;
+ }
+
+ return null;
+ }
+
+ public static TraceContext getContextByTxid(long txid) {
+ return entryByTxid.get(txid);
+ }
+
+ public static TraceContext getContextByThreadId(long key) {
+ return entryByThreadId.get(key);
}
public static TraceContext getDeferredContext(long key) {
return deferredEntry.get(key);
}
- public static TraceContext getContext() {
- return local.get();
+ public static TraceContext getCoroutineContext() {
+ return coroutineDebuggingLocal.get();
+ }
+
+ public static TraceContext getCoroutineContext(long id) {
+ return coroutineDebuggingLocal.get(id);
+ }
+
+ public static Long getLocalTxid() {
+ return txidLocal.get();
}
public static void clearForceDiscard() {
@@ -145,18 +194,57 @@ public static boolean startForceDiscard() {
return discard;
}
-
- public static long start(Thread thread, TraceContext o) {
- long key = thread.getId();
+ public static void start(TraceContext o) {
local.set(o);
- entry.put(key, o);
- return key;
+ txidLocal.set(o.txid);
+ entryByTxid.put(o.txid, o);
+
+ if (!o.isReactiveStarted) {
+ entryByThreadId.put(o.threadId, o);
+ }
+ }
+
+ public static void startByCoroutine(TraceContext o) {
+ txidByCoroutine.set(o.txid);
+ }
+
+ public static void end(TraceContext o) {
+ clearAllContext(o);
+ }
+
+ private static LongLongLinkedMap threadTxidMap = new LongLongLinkedMap().setMax(2000);
+ private static LongLongLinkedMap txidThreadMap = new LongLongLinkedMap().setMax(2000);
+
+ private static LongKeyMap map = new LongKeyMap();
+
+ public static void setTxidLocal(Long txid) {
+ txidLocal.set(txid);
+ if (txid != null && conf._psts_enabled && conf._psts_progressive_reactor_thread_trace_enabled) {
+ long threadId = Thread.currentThread().getId();
+ txidThreadMap.put(txid, threadId);
+ threadTxidMap.put(threadId, txid);
+ }
}
- public static void end(long key) {
+ public static long getReactiveThreadId(long txid) {
+ if (!conf._psts_progressive_reactor_thread_trace_enabled) {
+ return 0;
+ }
+ long threadId = txidThreadMap.get(txid);
+ if (threadId == 0) {
+ return 0;
+ }
+ long txid0 = threadTxidMap.get(threadId);
+ if (txid0 == txid) {
+ return threadId;
+ }
+ return 0;
+ }
+
+ public static void asCoroutineDebuggingMode(Long coroutineId, TraceContext o) {
+ CoroutineDebuggingLocal.setCoroutineDebuggingId(coroutineId);
+ coroutineDebuggingLocal.put(o);
local.set(null);
- entry.remove(key);
- clearForceDiscard();
}
public static void toDeferred(TraceContext o) {
@@ -166,4 +254,23 @@ public static void toDeferred(TraceContext o) {
public static void completeDeferred(TraceContext o) {
deferredEntry.remove(o.txid);
}
-}
\ No newline at end of file
+
+ public static void clearAllContext(TraceContext o) {
+ local.set(null);
+ coroutineDebuggingLocal.clear(); //it should be prev of txidLocal clear
+
+ entryByTxid.remove(o.txid);
+ if (conf._psts_progressive_reactor_thread_trace_enabled) {
+ txidThreadMap.remove(o.txid);
+ } else {
+ entryByThreadId.remove(o.threadId);
+ }
+
+ txidByCoroutine.set(null);
+ if (!o.isReactiveStarted) { //do not clear txidLocal in reactive
+ txidLocal.set(null);
+ }
+
+ clearForceDiscard();
+ }
+}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceElasticSearch.java b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceElasticSearch.java
new file mode 100644
index 000000000..0342b60ff
--- /dev/null
+++ b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceElasticSearch.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2015 the original author or authors.
+ * @https://github.com/scouter-project/scouter
+ *
+ * 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 scouter.agent.trace;
+
+import scouter.agent.Configure;
+import scouter.agent.counter.meter.MeterInteraction;
+import scouter.agent.counter.meter.MeterInteractionManager;
+import scouter.agent.netio.data.DataProxy;
+import scouter.agent.proxy.ElasticSearchTraceFactory;
+import scouter.agent.proxy.IElasticSearchTracer;
+import scouter.lang.enumeration.ParameterizedMessageLevel;
+import scouter.lang.step.ParameterizedMessageStep;
+import scouter.util.StringUtil;
+
+/**
+ * @author Gun Lee (gunlee01@gmail.com) on 2020/08/16
+ */
+public class TraceElasticSearch {
+
+ private static String ES_COMMAND_MSG = "[ElasticSearch] %s";
+ private static String ES_COMMAND_ERROR_MSG = "[ElasticSearch] %s\n[Exception:%s] %s";
+
+ static IElasticSearchTracer tracer;
+ static Configure conf = Configure.getInstance();
+
+ public static void startRequest(Object httpRequestBase) {
+ TraceContext ctx = TraceContextManager.getContext();
+ if (ctx == null) {
+ return;
+ }
+ if (tracer == null) {
+ tracer = ElasticSearchTraceFactory.create(httpRequestBase.getClass().getClassLoader());
+ }
+
+ String esRequestDesc = tracer.getRequestDescription(ctx, httpRequestBase);
+
+ ParameterizedMessageStep step = new ParameterizedMessageStep();
+ step.start_time = (int) (System.currentTimeMillis() - ctx.startTime);
+ step.putTempMessage("desc", esRequestDesc);
+ ctx.profile.push(step);
+ StepTransferMap.put(System.identityHashCode(httpRequestBase), ctx, step);
+ }
+
+ public static void endRequest(Object httpUriRequest, Object httpHost, Object httpResponse) {
+ endRequestFinal(httpUriRequest, httpResponse, httpHost, null);
+ }
+
+ public static void endFailRequest(Object httpUriRequest, Object node, Exception exception) {
+ endRequestFinal(httpUriRequest, null, node, exception);
+ }
+
+ private static void endRequestFinal(Object httpRequestBase, Object httpResponseBase, Object hostOrNode, Throwable throwable) {
+ if (httpRequestBase == null) {
+ return;
+ }
+
+ int requestBaseHash = System.identityHashCode(httpRequestBase);
+ StepTransferMap.ID id = StepTransferMap.get(requestBaseHash);
+ if (id == null) {
+ return;
+ }
+ StepTransferMap.remove(requestBaseHash);
+
+ TraceContext ctx = id.ctx;
+ ParameterizedMessageStep step = (ParameterizedMessageStep) id.step;
+ if (ctx == null || step == null) return;
+
+ if (tracer == null) {
+ tracer = ElasticSearchTraceFactory.create(httpRequestBase.getClass().getClassLoader());
+ }
+ if (throwable == null && httpResponseBase != null) {
+ throwable = tracer.getResponseError(httpRequestBase, httpResponseBase);
+ }
+
+ int elapsed = (int) (System.currentTimeMillis() - ctx.startTime) - step.start_time;
+ step.setElapsed(elapsed);
+
+ String desc = step.getTempMessage("desc");
+
+ if (StringUtil.isEmpty(desc)) desc = "-";
+
+ if (throwable == null) {
+ step.setMessage(DataProxy.sendHashedMessage(ES_COMMAND_MSG), desc);
+ step.setLevel(ParameterizedMessageLevel.INFO);
+
+ } else {
+ String msg = throwable.toString();
+ step.setMessage(DataProxy.sendHashedMessage(ES_COMMAND_ERROR_MSG), desc, throwable.getClass().getName(), msg);
+ step.setLevel(ParameterizedMessageLevel.ERROR);
+
+ if (ctx.error == 0 && conf.xlog_error_on_elasticsearch_exception_enabled) {
+ ctx.error = DataProxy.sendError(msg);
+ }
+ //TODO not yet error summary processing for es : ctx.offerErrorEntity(ErrorEntity.of(throwable, ctx.error, 0, 0));
+ }
+ ctx.profile.pop(step);
+
+ if (conf.counter_interaction_enabled) {
+ String node = tracer.getNode(ctx, hostOrNode);
+ int nodeHash = DataProxy.sendObjName(node);
+ MeterInteraction meterInteraction = MeterInteractionManager.getInstance().getElasticSearchCallMeter(conf.getObjHash(), nodeHash);
+ if (meterInteraction != null) {
+ meterInteraction.add(elapsed, throwable != null);
+ }
+ }
+ }
+}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java
index c666b8431..3f67be703 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceMain.java
@@ -29,6 +29,7 @@
import scouter.agent.error.STATEMENT_LEAK_SUSPECT;
import scouter.agent.error.USERTX_NOT_CLOSE;
import scouter.agent.netio.data.DataProxy;
+import scouter.agent.plugin.AbstractPlugin;
import scouter.agent.plugin.PluginAppServiceTrace;
import scouter.agent.plugin.PluginBackThreadTrace;
import scouter.agent.plugin.PluginCaptureTrace;
@@ -38,8 +39,10 @@
import scouter.agent.proxy.IHttpTrace;
import scouter.agent.proxy.IKafkaTracer;
import scouter.agent.proxy.ILettuceTrace;
+import scouter.agent.proxy.IReactiveSupport;
import scouter.agent.proxy.KafkaTraceFactory;
import scouter.agent.proxy.LettuceTraceFactory;
+import scouter.agent.proxy.ReactiveSupportFactory;
import scouter.agent.summary.ServiceSummary;
import scouter.agent.wrapper.async.WrTask;
import scouter.agent.wrapper.async.WrTaskCallable;
@@ -96,7 +99,10 @@ public Stat(TraceContext ctx) {
}
}
- private static IHttpTrace http = null;
+ public static IHttpTrace http = null;
+ public static IHttpTrace reactiveHttp = null;
+ public static IReactiveSupport reactiveSupport = null;
+
private static Configure conf = Configure.getInstance();
private static Error REJECT = new REQUEST_REJECT("service rejected");
private static Error userTxNotClose = new USERTX_NOT_CLOSE("UserTransaction missing commit/rollback Error");
@@ -137,6 +143,52 @@ public static Object startHttpFilter(Object req, Object res) {
return null;
}
+ public static void startReactiveInit(Object obj) {
+ try {
+ if (reactiveSupport == null) {
+ initReactiveSupport(obj);
+ }
+ } catch (Throwable t) {
+ Logger.println("A143", "fail to deploy ", t);
+ }
+ }
+
+ public static void startReactiveHttpService(Object exchange) {
+ try {
+ Object req = AbstractPlugin.invokeMethod(exchange, "getRequest");
+ Object res = AbstractPlugin.invokeMethod(exchange, "getResponse");
+
+ TraceContext ctx = TraceContextManager.getContext();
+ if (ctx != null && ctx.exchangeHashCode != exchange.hashCode()) {
+ //Logger.trace("exchange hash is different on context : " + exchange.hashCode() + " : " + ctx.exchangeHashCode);
+ ctx = null;
+ }
+ if (ctx != null) {
+ return;
+ }
+ if (TraceContextManager.startForceDiscard()) {
+ return;
+ }
+ startReactiveHttp(req, res, exchange);
+ } catch (Throwable t) {
+ Logger.println("A143", "fail to deploy ", t);
+ }
+ }
+
+ public static Object startReactiveHttpServiceReturn(Object mono) {
+ TraceContext ctx = TraceContextManager.getContext();
+ if (ctx == null) {
+ return mono;
+ }
+ if (!ctx.isReactiveStarted) {
+ return mono;
+ }
+ if (reactiveSupport == null) {
+ return mono;
+ }
+ return reactiveSupport.subscriptOnContext(mono, ctx);
+ }
+
public static Object reject(Object stat, Object req, Object res) {
Configure conf = Configure.getInstance();
if (plController != null) {
@@ -189,7 +241,7 @@ private static void addHttpServiceName(TraceContext ctx, Object req) {
StringBuilder sb = new StringBuilder();
if (conf.trace_service_name_post_key != null) {
- String v = http.getParameter(req, conf.trace_service_name_post_key);
+ String v = ctx.http.getParameter(req, conf.trace_service_name_post_key);
if (v != null) {
if (sb.length() == 0) {
sb.append(ctx.serviceName);
@@ -218,7 +270,7 @@ private static void addHttpServiceName(TraceContext ctx, Object req) {
}
}
if (conf.trace_service_name_header_key != null) {
- String v = http.getHeader(req, conf.trace_service_name_header_key);
+ String v = ctx.http.getHeader(req, conf.trace_service_name_header_key);
ctx.serviceName = new StringBuilder(ctx.serviceName.length() + v.length() + 5).append(ctx.serviceName)
.append('-').append(v).toString();
}
@@ -231,14 +283,30 @@ private static void addHttpServiceName(TraceContext ctx, Object req) {
private static Object lock = new Object();
+ private static Object startReactiveHttp(Object req, Object res, Object exchange) {
+ if (reactiveHttp == null) {
+ initReactiveHttp(req);
+ }
+ return startHttp(req, res, reactiveHttp, true, exchange);
+ }
+
private static Object startHttp(Object req, Object res) {
if (http == null) {
initHttp(req);
}
+ return startHttp(req, res, http, false, null);
+ }
+ private static Object startHttp(Object req, Object res, IHttpTrace http0, boolean isReactive, Object exchange) {
Configure conf = Configure.getInstance();
TraceContext ctx = new TraceContext(false);
+ if (isReactive) {
+ ctx.initScannables();
+ ctx.isReactiveStarted = true;
+ ctx.exchangeHashCode = exchange.hashCode();
+ }
ctx.thread = Thread.currentThread();
+ ctx.threadId = ctx.thread.getId();
ctx.txid = KeyGen.next();
ctx.startTime = System.currentTimeMillis();
ctx.startCpu = SysJMX.getCurrentThreadCPU();
@@ -251,7 +319,10 @@ private static Object startHttp(Object req, Object res) {
step.hash = DataProxy.sendHashedMessage("[driving thread] " + ctx.threadName);
ctx.profile.add(step);
- http.start(ctx, req, res);
+ http0.start(ctx, req, res);
+ ctx.req = req;
+ ctx.res = res;
+ ctx.http = http0;
if (ctx.isFullyDiscardService) {
return null;
@@ -261,14 +332,14 @@ private static Object startHttp(Object req, Object res) {
ctx.serviceName = "Non-URI";
}
- ctx.threadId = TraceContextManager.start(ctx.thread, ctx);
+ TraceContextManager.start(ctx);
Stat stat = new Stat(ctx, req, res);
stat.isStaticContents = ctx.isStaticContents;
if (stat.isStaticContents == false) {
if (ctx.xType != XLogTypes.ASYNCSERVLET_DISPATCHED_SERVICE) {
- PluginHttpServiceTrace.start(ctx, req, res);
+ PluginHttpServiceTrace.start(ctx, req, res, http0, isReactive);
}
if (plController != null) {
@@ -286,6 +357,46 @@ private static void initHttp(Object req) {
}
}
+ private static void initReactiveHttp(Object req) {
+ synchronized (lock) {
+ if (reactiveHttp == null) {
+ reactiveHttp = HttpTraceFactory.create(req.getClass().getClassLoader(), req);
+ }
+ }
+ }
+
+ private static void initReactiveSupport(Object obj) {
+ synchronized (lock) {
+ if (reactiveSupport == null) {
+ reactiveSupport = ReactiveSupportFactory.create(obj.getClass().getClassLoader());
+ reactiveSupport.contextOperatorHook();
+ }
+ }
+ }
+
+ public static void endReactiveHttpService() {
+ TraceContext context = TraceContextManager.getContext();
+ if (context == null) {
+ return;
+ }
+ Stat stat = new Stat(context, context.req, context.res);
+ endHttpService(stat, null);
+ }
+
+ public static void endCanceledHttpService(TraceContext traceContext) {
+ if (traceContext != null) {
+ traceContext.error += 1;
+
+ ParameterizedMessageStep step = new ParameterizedMessageStep();
+ step.setMessage(DataProxy.sendHashedMessage("reactive stream canceled!"), new String[0]);
+ step.setLevel(ParameterizedMessageLevel.ERROR);
+ step.start_time = (int) (System.currentTimeMillis() - traceContext.startTime);
+ traceContext.profile.add(step);
+
+ endHttpService(new Stat(traceContext), null);
+ }
+ }
+
public static void endHttpService(Object stat, Throwable thr) {
if (TraceContextManager.isForceDiscarded()) {
TraceContextManager.clearForceDiscard();
@@ -298,10 +409,13 @@ public static void endHttpService(Object stat, Throwable thr) {
return;
}
TraceContext ctx = stat0.ctx;
+ if (ctx == null) {
+ return;
+ }
//wait on async servlet completion
if (!ctx.asyncServletStarted) {
- endHttpServiceFinal(ctx, stat0.req, stat0.res, thr);
+ endHttpServiceFinal(ctx, ctx.req, ctx.res, thr);
} else {
HashedMessageStep step = new HashedMessageStep();
step.time = -1;
@@ -309,7 +423,7 @@ public static void endHttpService(Object stat, Throwable thr) {
step.start_time = (int) (System.currentTimeMillis() - ctx.startTime);
ctx.profile.add(step);
flushErrorSummary(ctx);
- TraceContextManager.end(ctx.threadId);
+ TraceContextManager.end(ctx);
ctx.latestCpu = SysJMX.getCurrentThreadCPU();
ctx.latestBytes = SysJMX.getCurrentThreadAllocBytes(conf.profile_thread_memory_usage_enabled);
TraceContextManager.toDeferred(ctx);
@@ -336,7 +450,7 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object
try {
if (conf.getEndUserPerfEndpointHash() == ctx.serviceHash) {
- TraceContextManager.end(ctx.threadId);
+ TraceContextManager.end(ctx);
return;
}
//additional service name
@@ -344,15 +458,15 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object
// add error summary
flushErrorSummary(ctx);
// HTTP END
- http.end(ctx, request, response);
+ ctx.http.end(ctx, request, response);
// static-contents -> stop processing
if (ctx.isStaticContents) {
- TraceContextManager.end(ctx.threadId);
+ TraceContextManager.end(ctx);
return;
}
// Plug-in end
if (ctx.xType != XLogTypes.ASYNCSERVLET_DISPATCHED_SERVICE) {
- PluginHttpServiceTrace.end(ctx, request, response);
+ PluginHttpServiceTrace.end(ctx, request, response, ctx.http, ctx.isReactiveStarted);
}
if (plController != null) {
plController.end(ctx, request, response);
@@ -396,7 +510,7 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object
}
// profile close
- TraceContextManager.end(ctx.threadId);
+ TraceContextManager.end(ctx);
Configure conf = Configure.getInstance();
XLogPack pack = new XLogPack();
@@ -422,27 +536,46 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object
} else {
pack.hasDump = 0;
}
- // ////////////////////////////////////////////////////////
if (ctx.error != 0) {
pack.error = ctx.error;
+
} else if (thr != null) {
if (thr == REJECT) {
Logger.println("A145", ctx.serviceName);
String emsg = conf.control_reject_text;
pack.error = DataProxy.sendError(emsg);
ServiceSummary.getInstance().process(thr, pack.error, ctx.serviceHash, ctx.txid, 0, 0);
+
} else {
String emsg = thr.toString();
if (conf.profile_fullstack_service_error_enabled) {
StringBuffer sb = new StringBuffer();
sb.append(emsg).append("\n");
ThreadUtil.getStackTrace(sb, thr, conf.profile_fullstack_max_lines);
+ Throwable[] suppressed = thr.getSuppressed();
+ if (suppressed != null) {
+ for (Throwable sup : suppressed) {
+ sb.append("\nSuppressed...\n");
+ sb.append(sup.toString()).append("\n");
+ ThreadUtil.getStackTrace(sb, sup, conf.profile_fullstack_max_lines);
+ }
+ }
+
Throwable thrCause = thr.getCause();
if (thrCause != null) {
thr = thrCause;
while (thr != null) {
sb.append("\nCause...\n");
ThreadUtil.getStackTrace(sb, thr, conf.profile_fullstack_max_lines);
+ Throwable[] suppressed2 = thr.getSuppressed();
+ if (suppressed2 != null) {
+ for (Throwable sup : suppressed2) {
+ sb.append("\nSuppressed...\n");
+ sb.append(sup.toString()).append("\n");
+ ThreadUtil.getStackTrace(sb, sup, conf.profile_fullstack_max_lines);
+ }
+ }
+
thr = thr.getCause();
}
}
@@ -512,15 +645,17 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object
//send all child xlogs, and check it again on the collector server. (follows parent's discard type)
if (discardMode != XLogDiscard.DISCARD_ALL || !pack.isDriving()) {
- if (ctx.latestCpu > 0) {
- pack.cpu = (int) (ctx.latestCpu - ctx.startCpu);
- } else {
- pack.cpu = (int) (SysJMX.getCurrentThreadCPU() - ctx.startCpu);
- }
- if (ctx.latestBytes > 0) {
- pack.kbytes = (int) ((ctx.latestBytes - ctx.bytes) / 1024.0d);
- } else {
- pack.kbytes = (int) ((SysJMX.getCurrentThreadAllocBytes(conf.profile_thread_memory_usage_enabled) - ctx.bytes) / 1024.0d);
+ if (!ctx.isReactiveStarted) {
+ if (ctx.latestCpu > 0) {
+ pack.cpu = (int) (ctx.latestCpu - ctx.startCpu);
+ } else {
+ pack.cpu = (int) (SysJMX.getCurrentThreadCPU() - ctx.startCpu);
+ }
+ if (ctx.latestBytes > 0) {
+ pack.kbytes = (int) ((ctx.latestBytes - ctx.bytes) / 1024.0d);
+ } else {
+ pack.kbytes = (int) ((SysJMX.getCurrentThreadAllocBytes(conf.profile_thread_memory_usage_enabled) - ctx.bytes) / 1024.0d);
+ }
}
DataProxy.sendXLog(pack);
@@ -621,7 +756,11 @@ public static Object startService(String name, String className, String methodNa
ctx.startTime = System.currentTimeMillis();
ctx.startCpu = SysJMX.getCurrentThreadCPU();
ctx.txid = KeyGen.next();
- ctx.threadId = TraceContextManager.start(ctx.thread, ctx);
+ ctx.thread = Thread.currentThread();
+ ctx.threadId = ctx.thread.getId();
+
+ TraceContextManager.start(ctx);
+
ctx.bytes = SysJMX.getCurrentThreadAllocBytes(conf.profile_thread_memory_usage_enabled);
ctx.profile_thread_cputime = conf.profile_thread_cputime_enabled;
ctx.xType = xType;
@@ -679,7 +818,7 @@ public static void endService(Object stat, Object returnValue, Throwable thr) {
}
step.error = errorCheck(ctx, thr);
ctx.profile.pop(step);
- TraceContextManager.end(ctx.threadId);
+ TraceContextManager.end(ctx);
ctx.profile.close(true);
return;
}
@@ -690,7 +829,7 @@ public static void endService(Object stat, Object returnValue, Throwable thr) {
PluginBackThreadTrace.end(ctx);
}
- TraceContextManager.end(ctx.threadId);
+ TraceContextManager.end(ctx);
XLogPack pack = new XLogPack();
pack.txid = ctx.txid;
@@ -1071,9 +1210,9 @@ public static void addMessage(String msg) {
}
public static void endRequestAsyncStart(Object asyncContext) {
- if (http == null) return;
TraceContext traceContext = TraceContextManager.getContext();
if (traceContext == null) return;
+ if (http == null) return;
http.addAsyncContextListener(asyncContext);
traceContext.asyncServletStarted = true;
}
@@ -1538,10 +1677,26 @@ public static void startExceptionHandler(String className, String methodName, St
if (conf.profile_fullstack_hooked_exception_enabled) {
sb.append("\n");
ThreadUtil.getStackTrace(sb, t, conf.profile_fullstack_max_lines);
+ Throwable[] suppressed = t.getSuppressed();
+ if (suppressed != null) {
+ for (Throwable sup : suppressed) {
+ sb.append("\nSuppressed...\n");
+ sb.append(sup.toString()).append("\n");
+ ThreadUtil.getStackTrace(sb, sup, conf.profile_fullstack_max_lines);
+ }
+ }
Throwable cause = t.getCause();
while (cause != null) {
sb.append("\nCause...\n");
ThreadUtil.getStackTrace(sb, cause, conf.profile_fullstack_max_lines);
+ Throwable[] suppressed2 = t.getSuppressed();
+ if (suppressed2 != null) {
+ for (Throwable sup : suppressed2) {
+ sb.append("\nSuppressed...\n");
+ sb.append(sup.toString()).append("\n");
+ ThreadUtil.getStackTrace(sb, sup, conf.profile_fullstack_max_lines);
+ }
+ }
cause = cause.getCause();
}
}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceReactive.java b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceReactive.java
new file mode 100644
index 000000000..3c64720a2
--- /dev/null
+++ b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceReactive.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2015 the original author or authors.
+ * @https://github.com/scouter-project/scouter
+ *
+ * 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 scouter.agent.trace;
+
+import java.lang.reflect.Method;
+
+public class TraceReactive {
+ //TODO
+ /*
+ Active Service 코루틴에 맞추기
+ child 코루틴과 연결 가능한지 테스트하기.
+ */
+
+ public static void threadSetName(Thread thread, String name) {
+ //System.out.println(">> Thread setname " + Thread.currentThread().getId() + " : " + Thread.currentThread().getName() + " -> " + name);
+ }
+
+ public static void startCoroutineIdUpdateThreadContext(long coroutineId) {
+ TraceContext context = TraceContextManager.getContext();
+ if (context == null) {
+ context = TraceContextManager.getCoroutineContext(coroutineId);
+ if (context == null) {
+ return;
+ }
+ }
+
+ CoroutineDebuggingLocal.setCoroutineDebuggingId(coroutineId);
+ if (context.isReactiveStarted && !context.isCoroutineStarted) {
+ context.isCoroutineStarted = true;
+ context.isOnCoroutineIdUpdating = true;
+ TraceContextManager.asCoroutineDebuggingMode(coroutineId, context);
+ }
+ }
+
+ public static void endCoroutineIdUpdateThreadContext() {
+ TraceContext context = TraceContextManager.getCoroutineContext();
+ if (context == null) {
+ return;
+ }
+ context.isOnCoroutineIdUpdating = false;
+ }
+
+ public static Object startMonoKtMono(Object coroutineContext) {
+ TraceContext context = TraceContextManager.getContext();
+ if (context == null) {
+ return coroutineContext;
+ }
+ return TraceMain.reactiveSupport.monoCoroutineContextHook(coroutineContext, context);
+ }
+
+
+
+
+
+
+
+
+
+ private static Object[] coroutineJobParams = null;
+ private static Method coroutineJobGetMethod = null;
+ private static Method jobIsActiveMethod = null;
+
+ public static void startCoroutineIdRestoreThreadContext(Object coroutineContext) {
+ CoroutineDebuggingLocal.releaseCoroutineId();
+ }
+// public static void startCoroutineIdRestoreThreadContext(Object coroutineContext) {
+// try {
+// if (coroutineJobParams == null) {
+// Class jobClass = Class.forName("kotlinx.coroutines.Job", false, Thread.currentThread().getContextClassLoader());
+// Field keyField = jobClass.getField("Key");
+// Object key = keyField.get(null);
+// Object[] params = new Object[1];
+// params[0] = key;
+// coroutineJobParams = params;
+// }
+//
+// if (coroutineJobGetMethod == null) {
+// Class[] typeParams = new Class[1];
+// Class arg = Class.forName("kotlin.coroutines.CoroutineContext$Key", false, Thread.currentThread().getContextClassLoader());
+// typeParams[0] = arg;
+// Method method = coroutineContext.getClass().getMethod("get", typeParams[0]);
+// coroutineJobGetMethod = method;
+// }
+//
+// Object job = coroutineJobGetMethod.invoke(coroutineContext, coroutineJobParams);
+//
+// if (jobIsActiveMethod == null) {
+// jobIsActiveMethod = job.getClass().getMethod("isActive");
+// }
+//
+// Object isActive = jobIsActiveMethod.invoke(job);
+// if (isActive instanceof Boolean && !((Boolean) isActive)) {
+// TraceContext context = TraceContextManager.getContext();
+// TraceMain.endCanceledHttpService(context);
+// }
+// //TODO
+// /*
+// Active Service 코루틴에 맞추기
+// child 코루틴과 연결 가능한지 테스트하기.
+// */
+//
+// } catch (Exception e) {
+// Logger.println("A342", "reflection errors.", e);
+// }
+//
+// CoroutineLocal.releaseCoroutineId();
+// }
+}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/trace/XLogSampler.java b/scouter.agent.java/src/main/java/scouter/agent/trace/XLogSampler.java
index ae58f444e..af8c27523 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/trace/XLogSampler.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/trace/XLogSampler.java
@@ -14,6 +14,7 @@ public class XLogSampler {
private static XLogSampler instance = new XLogSampler();
private Configure conf;
+ private String currentExcludeSamplingPattern;
private String currentDiscardServicePatterns;
private String currentSamplingServicePatterns;
private String currentSampling2ServicePatterns;
@@ -21,6 +22,7 @@ public class XLogSampler {
private String currentSampling4ServicePatterns;
private String currentSampling5ServicePatterns;
private String currentFullyDiscardServicePatterns;
+ private CommaSeparatedChainedStrMatcher excludeSamplingPatternMatcher;
private CommaSeparatedChainedStrMatcher discardPatternMatcher;
private CommaSeparatedChainedStrMatcher samplingPatternMatcher;
private CommaSeparatedChainedStrMatcher sampling2PatternMatcher;
@@ -50,6 +52,11 @@ private XLogSampler() {
@Override public void run() {
XLogSampler sampler = XLogSampler.getInstance();
Configure conf = Configure.getInstance();
+ if (sampler.currentExcludeSamplingPattern.equals(conf.xlog_sampling_exclude_patterns) == false) {
+ sampler.currentExcludeSamplingPattern = conf.xlog_sampling_exclude_patterns;
+ sampler.excludeSamplingPatternMatcher = new CommaSeparatedChainedStrMatcher(conf.xlog_sampling_exclude_patterns);
+ }
+
if (sampler.currentDiscardServicePatterns.equals(conf.xlog_discard_service_patterns) == false) {
sampler.currentDiscardServicePatterns = conf.xlog_discard_service_patterns;
sampler.discardPatternMatcher = new CommaSeparatedChainedStrMatcher(conf.xlog_discard_service_patterns);
@@ -93,6 +100,10 @@ public XLogDiscard evaluateXLogDiscard(int elapsed, String serviceName) {
return XLogDiscard.DISCARD_ALL;
}
+ if (conf.xlog_sampling_enabled && isExcludeSamplingServicePattern(serviceName)) {
+ return XLogDiscard.NONE;
+ }
+
boolean isSamplingServicePattern = false;
if (conf.xlog_patterned_sampling_enabled && (isSamplingServicePattern = isSamplingServicePattern(serviceName))) {
discardMode = samplingPatterned1(elapsed, discardMode);
@@ -251,6 +262,17 @@ private XLogDiscard samplingPatterned1(int elapsed, XLogDiscard discardMode) {
return discardMode;
}
+ private boolean isExcludeSamplingServicePattern(String serviceName) {
+ if (StringUtil.isEmpty(conf.xlog_sampling_exclude_patterns)) {
+ return false;
+ }
+ if (excludeSamplingPatternMatcher.isMatch(serviceName)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
public boolean isDiscardServicePattern(String serviceName) {
if (StringUtil.isEmpty(conf.xlog_discard_service_patterns)) {
return false;
diff --git a/scouter.agent.java/src/main/java/scouter/agent/trace/api/ApiCallTraceHelper.java b/scouter.agent.java/src/main/java/scouter/agent/trace/api/ApiCallTraceHelper.java
index 2cc3e5c64..6540f0e1a 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/trace/api/ApiCallTraceHelper.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/trace/api/ApiCallTraceHelper.java
@@ -18,6 +18,7 @@
package scouter.agent.trace.api;
import scouter.agent.trace.HookArgs;
+import scouter.agent.trace.LocalContext;
import scouter.agent.trace.TraceContext;
import scouter.lang.step.ApiCallStep;
@@ -34,6 +35,7 @@ static interface IHelper {
static ForHttpClient43 forHttpClient43 = new ForHttpClient43();
static ForSpringAsyncRestTemplate forSpringAsyncRestTemplate = new ForSpringAsyncRestTemplate();
static ForJavaNetHttpClient forJavaNetHttpClient = new ForJavaNetHttpClient();
+ static ForWebClient forWebClient = new ForWebClient();
static void put(String name, IHelper o) {
name = name.replace('.', '/');
@@ -48,14 +50,15 @@ public static IHelper get(String name) {
put("sun/net/www/protocol/http/HttpURLConnection", new ForHttpURLConnection());
put("sun/net/www/http/HttpClient", new ForSunHttpClient());
put("org/apache/commons/httpclient/HttpClient", new ForHttpClient());
- put("org/apache/http/impl/client/InternalHttpClient", new ForHttpClient43());
+ put("org/apache/http/impl/client/InternalHttpClient", forHttpClient43);
put("org/apache/http/impl/client/AbstractHttpClient", new ForHttpClient40());
put("com/sap/mw/jco/JCO$Client", new ForJCOClient());
put("com/netflix/ribbon/transport/netty/http/LoadBalancingHttpClient", new ForRibbonLB());
put("io/reactivex/netty/protocol/http/client/HttpClientImpl", new ForNettyHttpRequest());
put("org/springframework/web/client/RestTemplate", new ForSpringRestTemplate());
- put("org/springframework/web/client/AsyncRestTemplate", new ForSpringAsyncRestTemplate());
- put("jdk/internal/net/http/HttpClientImpl", new ForJavaNetHttpClient());
+ put("org/springframework/web/client/AsyncRestTemplate", forSpringAsyncRestTemplate);
+ put("jdk/internal/net/http/HttpClientImpl", forJavaNetHttpClient);
+ put("org/springframework/web/reactive/function/client/ExchangeFunctions$DefaultExchangeFunction", forWebClient);
}
private static IHelper defaultObj = new ForDefault();
@@ -82,7 +85,15 @@ public static void setCalleeToCtxInSpringClientHttpResponse(TraceContext ctx, Ob
forSpringAsyncRestTemplate.processSetCalleeToCtx(ctx, _this, response);
}
- public static void setCalleeToCtxJavaHttpRequest(TraceContext ctx, Object requestBuilder) {
+ public static void setTransferToCtxJavaHttpRequest(TraceContext ctx, Object requestBuilder) {
forJavaNetHttpClient.transfer(ctx, requestBuilder);
}
+
+ public static void webClientInfo(Object bodyInserterRequest, Object clientHttpRequest) {
+ forWebClient.processInfo(bodyInserterRequest, clientHttpRequest);
+ }
+
+ public static LocalContext webClientProcessEnd(Object exchangeFunction, Object clientResponse) {
+ return forWebClient.processEnd(exchangeFunction, clientResponse);
+ }
}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/trace/api/ForWebClient.java b/scouter.agent.java/src/main/java/scouter/agent/trace/api/ForWebClient.java
new file mode 100644
index 000000000..ae4a33ebf
--- /dev/null
+++ b/scouter.agent.java/src/main/java/scouter/agent/trace/api/ForWebClient.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2015 the original author or authors.
+ * @https://github.com/scouter-project/scouter
+ *
+ * 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 scouter.agent.trace.api;
+
+import scouter.agent.Configure;
+import scouter.agent.Logger;
+import scouter.agent.plugin.PluginHttpCallTrace;
+import scouter.agent.proxy.IHttpClient;
+import scouter.agent.proxy.WebClientFactory;
+import scouter.agent.trace.ApiCallTransferMap;
+import scouter.agent.trace.HookArgs;
+import scouter.agent.trace.LocalContext;
+import scouter.agent.trace.TraceContext;
+import scouter.lang.constants.B3Constant;
+import scouter.lang.step.ApiCallStep;
+import scouter.lang.step.ApiCallStep2;
+import scouter.util.Hexa32;
+import scouter.util.IntKeyLinkedMap;
+import scouter.util.KeyGen;
+
+public class ForWebClient implements ApiCallTraceHelper.IHelper {
+
+ private static IntKeyLinkedMap httpclients = new IntKeyLinkedMap().setMax(10);
+ private static Configure conf = Configure.getInstance();
+
+ public ApiCallStep process(TraceContext ctx, HookArgs hookPoint) {
+ ApiCallStep2 step = new ApiCallStep2();
+ ctx.apicall_name = hookPoint.class1;
+
+ if (ok) {
+ try {
+ if (hookPoint.args != null && hookPoint.args.length > 0) {
+ ApiCallTransferMap.put(System.identityHashCode(hookPoint.args[0]), ctx, step);
+ ApiCallTransferMap.put(System.identityHashCode(hookPoint.this1), ctx, step);
+
+ }
+ } catch (Exception e) {
+ this.ok = false;
+ }
+ }
+ return step;
+ }
+
+ public void processInfo(Object bodyInserter, Object clientHttpRequest) {
+ if (bodyInserter == null) {
+ return;
+ }
+ int bodyInserterHash = System.identityHashCode(bodyInserter);
+ ApiCallTransferMap.ID id = ApiCallTransferMap.get(bodyInserterHash);
+ if (id == null) {
+ return;
+ }
+ ApiCallTransferMap.remove(bodyInserterHash);
+
+ TraceContext ctx = id.ctx;
+ ApiCallStep2 step = id.step;
+ if (ok) {
+ try {
+ IHttpClient httpclient = getProxy(clientHttpRequest);
+ String host = httpclient.getHost(clientHttpRequest);
+
+ step.opt = 1;
+ step.address = host;
+ if (host != null)
+ ctx.apicall_target = host;
+ ctx.apicall_name = httpclient.getURI(clientHttpRequest);
+
+ step.txid = KeyGen.next();
+ transfer(httpclient, ctx, clientHttpRequest, step.txid);
+ } catch (Exception e) {
+ this.ok = false;
+ }
+ }
+ }
+
+ public void processEnd(TraceContext ctx, ApiCallStep step, Object rtn, HookArgs hookPoint) {
+ //processEnd(rtn);
+ }
+
+ public LocalContext processEnd(Object exchangeFunction, Object clientHttpResponse) {
+ int exchangeFunctionHash = System.identityHashCode(exchangeFunction);
+ ApiCallTransferMap.ID id = ApiCallTransferMap.get(exchangeFunctionHash);
+ if (id == null) {
+ return null;
+ }
+ ApiCallTransferMap.remove(exchangeFunctionHash);
+
+ TraceContext ctx = id.ctx;
+ ApiCallStep2 step = id.step;
+
+ IHttpClient httpclient = getProxy(exchangeFunction);
+ String calleeObjHashStr = httpclient.getResponseHeader(clientHttpResponse, conf._trace_interservice_callee_obj_header_key);
+ if (calleeObjHashStr != null) {
+ try {
+ ctx.lastCalleeObjHash = Integer.parseInt(calleeObjHashStr);
+ } catch (NumberFormatException e) {
+ }
+ } else {
+ ctx.lastCalleeObjHash = 0;
+ }
+
+ return new LocalContext(ctx, step, httpclient.getResponseStatusCode(clientHttpResponse));
+ }
+
+ private IHttpClient getProxy(Object _this) {
+ int key = System.identityHashCode(_this.getClass());
+ IHttpClient httpclient = httpclients.get(key);
+ if (httpclient == null) {
+ synchronized (this) {
+ httpclient = WebClientFactory.create(_this.getClass().getClassLoader());
+ httpclients.put(key, httpclient);
+ }
+ }
+ return httpclient;
+ }
+
+ private boolean ok = true;
+
+ private void transfer(IHttpClient httpclient, TraceContext ctx, Object req, long calleeTxid) {
+ if (conf.trace_interservice_enabled) {
+ try {
+ if (ctx.gxid == 0) {
+ ctx.gxid = ctx.txid;
+ }
+ httpclient.addHeader(req, conf._trace_interservice_gxid_header_key, Hexa32.toString32(ctx.gxid));
+ httpclient.addHeader(req, conf._trace_interservice_caller_header_key, Hexa32.toString32(ctx.txid));
+ httpclient.addHeader(req, conf._trace_interservice_callee_header_key, Hexa32.toString32(calleeTxid));
+ httpclient.addHeader(req, conf._trace_interservice_caller_obj_header_key, String.valueOf(conf.getObjHash()));
+
+ httpclient.addHeader(req, B3Constant.B3_HEADER_TRACEID, Hexa32.toUnsignedLongHex(ctx.gxid));
+ httpclient.addHeader(req, B3Constant.B3_HEADER_PARENTSPANID, Hexa32.toUnsignedLongHex(ctx.txid));
+ httpclient.addHeader(req, B3Constant.B3_HEADER_SPANID, Hexa32.toUnsignedLongHex(calleeTxid));
+ PluginHttpCallTrace.call(ctx, req);
+ } catch (Exception e) {
+ Logger.println("A178w", e);
+ ok = false;
+ }
+ }
+ }
+}
diff --git a/scouter.agent.java/src/main/java/scouter/agent/util/DumpUtil.java b/scouter.agent.java/src/main/java/scouter/agent/util/DumpUtil.java
index 84440de6e..3aec4505e 100644
--- a/scouter.agent.java/src/main/java/scouter/agent/util/DumpUtil.java
+++ b/scouter.agent.java/src/main/java/scouter/agent/util/DumpUtil.java
@@ -108,7 +108,7 @@ public static Pack triggerThreadList() {
out.print(stat.get(i) + ":");
out.print("cpu " + cpu.get(i));
- TraceContext ctx = TraceContextManager.getContext(tid);
+ TraceContext ctx = TraceContextManager.getContextByThreadId(tid);
if (ctx != null) {
out.print(":service " + Hexa32.toString32(ctx.txid) + ":");
out.print(ctx.serviceName + ":");
@@ -137,6 +137,7 @@ public static Pack triggerActiveService() {
try {
File file = DumpUtil.getDumpFile("scouter.activeservice");
out = new PrintWriter(new FileWriter(file));
+ //TODO reactive support
Enumeration en = TraceContextManager.getContextEnumeration();
for (int n = 0; en.hasMoreElements(); n++) {
TraceContext ctx = en.nextElement();
diff --git a/scouter.agent.java/src/main/java/scouter/test/Service24H.java b/scouter.agent.java/src/main/java/scouter/test/Service24H.java
index d5ca84d8b..508f6696f 100644
--- a/scouter.agent.java/src/main/java/scouter/test/Service24H.java
+++ b/scouter.agent.java/src/main/java/scouter/test/Service24H.java
@@ -17,9 +17,6 @@
package scouter.test;
-import java.util.Random;
-import java.util.Stack;
-
import scouter.AnyTrace;
import scouter.agent.AgentBoot;
import scouter.agent.Configure;
@@ -38,6 +35,9 @@
import scouter.util.SysJMX;
import scouter.util.ThreadUtil;
+import java.util.Random;
+import java.util.Stack;
+
public class Service24H {
public static void main(String[] args) {
@@ -135,17 +135,18 @@ private static void profile(long txid, int serviceHash) {
ctx.txid=txid;
ctx.serviceHash=serviceHash;
ctx.startTime=System.currentTimeMillis();
+ ctx.thread = Thread.currentThread();
- long key = TraceContextManager.start(Thread.currentThread(), ctx);
+ TraceContextManager.start(ctx);
AnyTrace.message("profile 1");
AnyTrace.message("profile 2");
ctx.profile.close(true);
- TraceContextManager.end(key);
+ TraceContextManager.end(ctx);
}
private static int next(Random r, int max) {
return Math.abs(r.nextInt() % max);
}
-}
\ No newline at end of file
+}
diff --git a/scouter.agent.java/src/main/java/scouter/test/TpsRush.java b/scouter.agent.java/src/main/java/scouter/test/TpsRush.java
index c0c6a0854..a99f7a3bd 100644
--- a/scouter.agent.java/src/main/java/scouter/test/TpsRush.java
+++ b/scouter.agent.java/src/main/java/scouter/test/TpsRush.java
@@ -17,8 +17,6 @@
package scouter.test;
-import java.util.Random;
-
import scouter.AnyTrace;
import scouter.agent.AgentBoot;
import scouter.agent.Configure;
@@ -37,8 +35,10 @@
import scouter.util.SysJMX;
import scouter.util.ThreadUtil;
+import java.util.Random;
+
public class TpsRush {
- public static void main(String[] args) {
+ public static void main2(String[] args) {
ShellArg sh = new ShellArg(args);
String server = sh.get("-h", "127.0.0.1");
@@ -115,17 +115,18 @@ private static void profile(long txid, int serviceHash) {
ctx.txid=txid;
ctx.serviceHash=serviceHash;
ctx.startTime=System.currentTimeMillis();
+ ctx.thread = Thread.currentThread();
- long key = TraceContextManager.start(Thread.currentThread(), ctx);
+ TraceContextManager.start(ctx);
AnyTrace.message("profile 1");
AnyTrace.message("profile 2");
ctx.profile.close(true);
- TraceContextManager.end(key);
+ TraceContextManager.end(ctx);
}
private static int next(Random r, int max) {
return Math.abs(r.nextInt() % max);
}
-}
\ No newline at end of file
+}
diff --git a/scouter.agent.java/src/main/java/scouter/xtra/http/HttpTrace.java b/scouter.agent.java/src/main/java/scouter/xtra/http/HttpTrace.java
index 53874d93d..45dc11f96 100644
--- a/scouter.agent.java/src/main/java/scouter/xtra/http/HttpTrace.java
+++ b/scouter.agent.java/src/main/java/scouter/xtra/http/HttpTrace.java
@@ -26,13 +26,22 @@
import scouter.agent.summary.EndUserErrorData;
import scouter.agent.summary.EndUserNavigationData;
import scouter.agent.summary.EndUserSummary;
-import scouter.agent.trace.*;
+import scouter.agent.trace.IProfileCollector;
+import scouter.agent.trace.TraceContext;
+import scouter.agent.trace.TraceContextManager;
+import scouter.agent.trace.TraceMain;
+import scouter.agent.trace.TransferMap;
+import scouter.agent.trace.XLogSampler;
import scouter.lang.conf.ConfObserver;
import scouter.lang.constants.B3Constant;
import scouter.lang.pack.XLogTypes;
import scouter.lang.step.HashedMessageStep;
import scouter.lang.step.MessageStep;
-import scouter.util.*;
+import scouter.util.CastUtil;
+import scouter.util.CompareUtil;
+import scouter.util.HashUtil;
+import scouter.util.Hexa32;
+import scouter.util.StringUtil;
import scouter.util.zipkin.HexCodec;
import javax.servlet.http.Cookie;
@@ -42,7 +51,9 @@
import java.io.PrintWriter;
import java.util.Enumeration;
-import static scouter.agent.AgentCommonConstant.*;
+import static scouter.agent.AgentCommonConstant.ASYNC_SERVLET_DISPATCHED_PREFIX;
+import static scouter.agent.AgentCommonConstant.REQUEST_ATTRIBUTE_CALLER_TRANSFER_MAP;
+import static scouter.agent.AgentCommonConstant.REQUEST_ATTRIBUTE_SELF_DISPATCHED;
public class HttpTrace implements IHttpTrace {
boolean remote_by_header;
@@ -73,6 +84,48 @@ public void run() {
});
}
+ private String getRequestURI(HttpServletRequest request) {
+ String uri = request.getRequestURI();
+ if (uri == null)
+ return "no-url";
+ int x = uri.indexOf(';');
+ if (x > 0)
+ return uri.substring(0, x);
+ else
+ return uri;
+ }
+
+ private String getRemoteAddr(HttpServletRequest request) {
+ try {
+ //For Testing
+ if (__ip_dummy_test) {
+ return getRandomIp();
+ }
+
+ if (remote_by_header) {
+ String remoteIp = request.getHeader(http_remote_ip_header_key);
+ if (remoteIp == null) {
+ return request.getRemoteAddr();
+ }
+
+ int commaPos = remoteIp.indexOf(',');
+ if (commaPos > -1) {
+ remoteIp = remoteIp.substring(0, commaPos);
+ }
+
+ Logger.trace("remoteIp: " + remoteIp);
+ return remoteIp;
+
+ } else {
+ return request.getRemoteAddr();
+ }
+
+ } catch (Throwable t) {
+ remote_by_header = false;
+ return "0.0.0.0";
+ }
+ }
+
public String getParameter(Object req, String key) {
HttpServletRequest request = (HttpServletRequest) req;
@@ -88,6 +141,60 @@ public String getHeader(Object req, String key) {
return request.getHeader(key);
}
+ @Override
+ public String getCookie(Object req, String key) {
+ HttpServletRequest request = (HttpServletRequest) req;
+ Cookie[] cookies = request.getCookies();
+ for (Cookie cookie : cookies) {
+ if (cookie.getName().equals(key)) {
+ return cookie.getValue();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String getRequestURI(Object req) {
+ HttpServletRequest request = (HttpServletRequest) req;
+ return getRequestURI(request);
+ }
+
+ @Override
+ public String getRemoteAddr(Object req) {
+ HttpServletRequest request = (HttpServletRequest) req;
+ return getRemoteAddr(request);
+ }
+
+ @Override
+ public String getMethod(Object req) {
+ HttpServletRequest request = (HttpServletRequest) req;
+ return request.getMethod();
+ }
+
+ @Override
+ public String getQueryString(Object req) {
+ HttpServletRequest request = (HttpServletRequest) req;
+ return request.getQueryString();
+ }
+
+ @Override
+ public Object getAttribute(Object req, String key) {
+ HttpServletRequest request = (HttpServletRequest) req;
+ return request.getAttribute(key);
+ }
+
+ @Override
+ public Enumeration getParameterNames(Object req) {
+ HttpServletRequest request = (HttpServletRequest) req;
+ return request.getParameterNames();
+ }
+
+ @Override
+ public Enumeration getHeaderNames(Object req) {
+ HttpServletRequest request = (HttpServletRequest) req;
+ return request.getHeaderNames();
+ }
+
public void start(TraceContext ctx, Object req, Object res) {
Configure conf = Configure.getInstance();
HttpServletRequest request = (HttpServletRequest) req;
@@ -338,48 +445,6 @@ private void processEndUserData(HttpServletRequest request) {
}
- private String getRequestURI(HttpServletRequest request) {
- String uri = request.getRequestURI();
- if (uri == null)
- return "no-url";
- int x = uri.indexOf(';');
- if (x > 0)
- return uri.substring(0, x);
- else
- return uri;
- }
-
- private String getRemoteAddr(HttpServletRequest request) {
- try {
- //For Testing
- if (__ip_dummy_test) {
- return getRandomIp();
- }
-
- if (remote_by_header) {
- String remoteIp = request.getHeader(http_remote_ip_header_key);
- if (remoteIp == null) {
- return request.getRemoteAddr();
- }
-
- int commaPos = remoteIp.indexOf(',');
- if (commaPos > -1) {
- remoteIp = remoteIp.substring(0, commaPos);
- }
-
- Logger.trace("remoteIp: " + remoteIp);
- return remoteIp;
-
- } else {
- return request.getRemoteAddr();
- }
-
- } catch (Throwable t) {
- remote_by_header = false;
- return "0.0.0.0";
- }
- }
-
private String getRandomIp() {
int len = ipRandom.length;
int randomNum = (int) (Math.random() * (len-1));
@@ -495,4 +560,5 @@ public void setSelfDispatch(Object oAsyncContext, boolean self) {
public boolean isSelfDispatch(Object oAsyncContext) {
return false;
}
+
}
diff --git a/scouter.agent.java/src/main/java/scouter/xtra/http/UseridUtil.java b/scouter.agent.java/src/main/java/scouter/xtra/http/UseridUtil.java
index 03fdd803c..43671a971 100644
--- a/scouter.agent.java/src/main/java/scouter/xtra/http/UseridUtil.java
+++ b/scouter.agent.java/src/main/java/scouter/xtra/http/UseridUtil.java
@@ -15,6 +15,10 @@
* limitations under the License.
*/
package scouter.xtra.http;
+
+import org.springframework.http.ResponseCookie;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
import scouter.agent.Logger;
import scouter.util.HashUtil;
import scouter.util.Hexa32;
@@ -25,6 +29,7 @@
import javax.servlet.http.HttpServletResponse;
public class UseridUtil {
private static final String SCOUTE_R = "SCOUTER";
+
public static long getUserid(HttpServletRequest req, HttpServletResponse res, String cookiePath) {
try {
String cookie = req.getHeader("Cookie");
@@ -55,6 +60,41 @@ public static long getUserid(HttpServletRequest req, HttpServletResponse res, St
}
return 0;
}
+
+ public static long getUserid(ServerHttpRequest req, ServerHttpResponse res, String cookiePath) {
+ try {
+ String cookie = req.getHeaders().getFirst("Cookie");
+ if (cookie != null) {
+ int x1 = cookie.indexOf(SCOUTE_R);
+ if (x1 >= 0) {
+ String value = null;
+ int x2 = cookie.indexOf(';', x1);
+ if (x2 > 0) {
+ value = cookie.substring(x1 + SCOUTE_R.length() + 1, x2);
+ } else {
+ value = cookie.substring(x1 + SCOUTE_R.length() + 1);
+ }
+ try {
+ return Hexa32.toLong32(value);
+ } catch (Throwable th) {
+ }
+ }
+ }
+
+ ResponseCookie.ResponseCookieBuilder c = ResponseCookie
+ .from(SCOUTE_R, Hexa32.toString32(KeyGen.next()));
+ if ( cookiePath != null && cookiePath.trim().length() > 0 ) {
+ c.path(cookiePath);
+ }
+ c.maxAge(Integer.MAX_VALUE);
+ res.addCookie(c.build());
+
+ } catch (Throwable t) {
+ Logger.println("A153", t.toString());
+ }
+ return 0;
+ }
+
public static long getUseridCustom(HttpServletRequest req, HttpServletResponse res, String key) {
if (key == null || key.length() == 0)
return 0;
@@ -81,6 +121,32 @@ public static long getUseridCustom(HttpServletRequest req, HttpServletResponse r
return 0;
}
+ public static long getUseridCustom(ServerHttpRequest req, ServerHttpResponse res, String key) {
+ if (key == null || key.length() == 0)
+ return 0;
+ try {
+ String cookie = req.getHeaders().getFirst("Cookie");
+ if (cookie != null) {
+ int x1 = cookie.indexOf(key);
+ if (x1 >= 0) {
+ String value = null;
+ int x2 = cookie.indexOf(';', x1);
+ if (x2 > 0) {
+ value = cookie.substring(x1 + key.length() + 1, x2);
+ } else {
+ value = cookie.substring(x1 + key.length() + 1);
+ }
+ if (value != null) {
+ return HashUtil.hash(value);
+ }
+ }
+ }
+ } catch (Throwable t) {
+ Logger.println("A154", t.toString());
+ }
+ return 0;
+ }
+
public static long getUseridFromHeader(HttpServletRequest req, HttpServletResponse res, String key) {
if (key == null || key.length() == 0)
return 0;
@@ -94,4 +160,18 @@ public static long getUseridFromHeader(HttpServletRequest req, HttpServletRespon
}
return 0;
}
+
+ public static long getUseridFromHeader(ServerHttpRequest req, ServerHttpResponse res, String key) {
+ if (key == null || key.length() == 0)
+ return 0;
+ try {
+ String headerValue = req.getHeaders().getFirst(key);
+ if (headerValue != null) {
+ return HashUtil.hash(headerValue);
+ }
+ } catch (Throwable t) {
+ Logger.println("A155", t.toString());
+ }
+ return 0;
+ }
}
diff --git a/scouter.agent.java/src/main/java/scouter/xtra/http/WebfluxHttpTrace.java b/scouter.agent.java/src/main/java/scouter/xtra/http/WebfluxHttpTrace.java
new file mode 100644
index 000000000..bed41e85b
--- /dev/null
+++ b/scouter.agent.java/src/main/java/scouter/xtra/http/WebfluxHttpTrace.java
@@ -0,0 +1,479 @@
+/*
+ * Copyright 2015 the original author or authors.
+ * @https://github.com/scouter-project/scouter
+ *
+ * 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 scouter.xtra.http;
+
+import org.springframework.http.HttpCookie;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseCookie;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import scouter.agent.Configure;
+import scouter.agent.Logger;
+import scouter.agent.counter.meter.MeterUsers;
+import scouter.agent.netio.data.DataProxy;
+import scouter.agent.proxy.IHttpTrace;
+import scouter.agent.trace.IProfileCollector;
+import scouter.agent.trace.TraceContext;
+import scouter.agent.trace.TraceMain;
+import scouter.agent.trace.XLogSampler;
+import scouter.lang.conf.ConfObserver;
+import scouter.lang.constants.B3Constant;
+import scouter.lang.step.HashedMessageStep;
+import scouter.lang.step.MessageStep;
+import scouter.util.CompareUtil;
+import scouter.util.HashUtil;
+import scouter.util.Hexa32;
+import scouter.util.StringUtil;
+import scouter.util.zipkin.HexCodec;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class WebfluxHttpTrace implements IHttpTrace {
+ boolean remote_by_header;
+ String http_remote_ip_header_key;
+
+ public WebfluxHttpTrace() {
+ Configure conf = Configure.getInstance();
+ this.http_remote_ip_header_key = conf.trace_http_client_ip_header_key;
+ this.remote_by_header = !StringUtil.isEmpty(this.http_remote_ip_header_key);
+
+ ConfObserver.add(WebfluxHttpTrace.class.getName(), new Runnable() {
+ public void run() {
+ String x = Configure.getInstance().trace_http_client_ip_header_key;
+ if (CompareUtil.equals(x, http_remote_ip_header_key) == false) {
+ remote_by_header = StringUtil.isEmpty(x) == false;
+ http_remote_ip_header_key = x;
+ }
+ }
+ });
+ }
+
+ public String getParameter(Object req, String key) {
+ ServerHttpRequest request = (ServerHttpRequest) req;
+ if (MediaType.APPLICATION_FORM_URLENCODED.equals(request.getHeaders().getContentType()))
+ return null;
+
+ return request.getQueryParams().getFirst(key);
+ }
+
+ private static String getMethod(ServerHttpRequest request) {
+ HttpMethod method = request.getMethod();
+ return method != null ? method.toString() : null;
+ }
+
+ private static String getQuery(ServerHttpRequest request) {
+ URI uri = request.getURI();
+ return uri != null ? uri.getQuery() : null;
+ }
+
+ private static String getContentType(ServerHttpRequest request) {
+ MediaType contentType = request.getHeaders().getContentType();
+ return contentType != null ? contentType.toString() : null;
+ }
+
+ private static String getRequestURI(ServerHttpRequest request) {
+ URI uri = request.getURI();
+ if (uri == null)
+ return "no-url";
+
+ String path = uri.getPath();
+ if (path == null)
+ return "no-url";
+
+ return path;
+ }
+
+ private static String getHeader(ServerHttpRequest request, String key) {
+ HttpHeaders headers = request.getHeaders();
+ if (headers == null) {
+ return null;
+ }
+ return headers.getFirst(key);
+ }
+
+ private String getRemoteAddr(ServerHttpRequest request) {
+ try {
+ if (remote_by_header) {
+ String remoteIp = request.getHeaders().getFirst(http_remote_ip_header_key);
+ if (remoteIp == null) {
+ return request.getRemoteAddress().getAddress().getHostAddress();
+ }
+
+ int commaPos = remoteIp.indexOf(',');
+ if (commaPos > -1) {
+ remoteIp = remoteIp.substring(0, commaPos);
+ }
+ Logger.trace("remoteIp: " + remoteIp);
+ return remoteIp;
+
+ } else {
+ return request.getRemoteAddress().getAddress().getHostAddress();
+ }
+
+ } catch (Throwable t) {
+ remote_by_header = false;
+ return "0.0.0.0";
+ }
+ }
+
+ public String getHeader(Object req, String key) {
+ ServerHttpRequest request = (ServerHttpRequest) req;
+ return getHeader(request, key);
+ }
+
+ @Override
+ public String getCookie(Object req, String key) {
+ ServerHttpRequest request = (ServerHttpRequest) req;
+ HttpCookie first = request.getCookies().getFirst(key);
+ return first != null ? first.getValue() : null;
+ }
+
+ @Override
+ public String getRequestURI(Object req) {
+ return getRequestURI((ServerHttpRequest) req);
+ }
+
+ @Override
+ public String getRemoteAddr(Object req) {
+ return getRemoteAddr((ServerHttpRequest) req);
+ }
+
+ @Override
+ public String getMethod(Object req) {
+ return getMethod((ServerHttpRequest) req);
+ }
+
+ @Override
+ public String getQueryString(Object req) {
+ ServerHttpRequest request = (ServerHttpRequest) req;
+ URI uri = request.getURI();
+ return uri != null ? uri.getQuery() : null;
+ }
+
+ @Override
+ public Object getAttribute(Object req, String key) {
+ return null;
+ }
+
+ @Override
+ public Enumeration getParameterNames(Object req) {
+ ServerHttpRequest request = (ServerHttpRequest) req;
+ Set strings = request.getQueryParams().keySet();
+ return Collections.enumeration(strings);
+ }
+
+ @Override
+ public Enumeration getHeaderNames(Object req) {
+ ServerHttpRequest request = (ServerHttpRequest) req;
+ Set strings = request.getHeaders().keySet();
+ return Collections.enumeration(strings);
+ }
+
+ public void start(TraceContext ctx, Object req, Object res) {
+ Configure conf = Configure.getInstance();
+ ServerHttpRequest request = (ServerHttpRequest) req;
+ ServerHttpResponse response = (ServerHttpResponse) res;
+
+ ctx.serviceName = getRequestURI(request);
+ ctx.serviceHash = HashUtil.hash(ctx.serviceName);
+
+ if (ctx.serviceHash == conf.getEndUserPerfEndpointHash()) {
+ ctx.isStaticContents = true;
+ //TODO processEndUserData(request);
+ return;
+ }
+
+ if (XLogSampler.getInstance().isFullyDiscardServicePattern(ctx.serviceName)) {
+ ctx.isFullyDiscardService = true;
+ return;
+ }
+
+ ctx.isStaticContents = TraceMain.isStaticContents(ctx.serviceName);
+
+ ctx.http_method = getMethod(request);
+ ctx.http_query = getQuery(request);
+ ctx.http_content_type = getContentType(request);
+ ctx.remoteIp = getRemoteAddr(request);
+
+ try {
+ switch (conf.trace_user_mode) {
+ case 3:
+ ctx.userid = UseridUtil.getUseridFromHeader(request, response, conf.trace_user_session_key);
+ if (ctx.userid == 0 && ctx.remoteIp != null) {
+ ctx.userid = HashUtil.hash(ctx.remoteIp);
+ }
+ break;
+ case 2:
+ ctx.userid = UseridUtil.getUserid(request, response, conf.trace_user_cookie_path);
+ break;
+ case 1:
+ ctx.userid = UseridUtil.getUseridCustom(request, response, conf.trace_user_session_key);
+ if (ctx.userid == 0 && ctx.remoteIp != null) {
+ ctx.userid = HashUtil.hash(ctx.remoteIp);
+ }
+ break;
+ default:
+ if (ctx.remoteIp != null) {
+ ctx.userid = HashUtil.hash(ctx.remoteIp);
+ }
+ break;
+ }
+ MeterUsers.add(ctx.userid);
+ } catch (Throwable e) {
+ // ignore
+ }
+ String referer = getHeader(request, "Referer");
+ if (referer != null) {
+ ctx.referer = DataProxy.sendReferer(referer);
+ }
+ String userAgent = getHeader(request, "User-Agent");
+ if (userAgent != null) {
+ ctx.userAgent = DataProxy.sendUserAgent(userAgent);
+ ctx.userAgentString = userAgent;
+ }
+ dump(ctx.profile, request, ctx);
+ if (conf.trace_interservice_enabled) {
+ try {
+ boolean b3ModeValid = false;
+ String b3TraceId = getHeader(request, B3Constant.B3_HEADER_TRACEID);
+ String gxid = getHeader(request, conf._trace_interservice_gxid_header_key);
+
+ if (gxid != null) {
+ ctx.gxid = Hexa32.toLong32(gxid);
+ } else {
+ if (b3TraceId != null && !b3TraceId.equals("0")) {
+ b3ModeValid = true;
+ }
+ }
+
+ if (b3ModeValid) {
+ ctx.gxid = HexCodec.lowerHexToUnsignedLong(b3TraceId);
+ ctx.txid = HexCodec.lowerHexToUnsignedLong(getHeader(request, B3Constant.B3_HEADER_SPANID));
+ ctx.caller = HexCodec.lowerHexToUnsignedLong(getHeader(request, B3Constant.B3_HEADER_PARENTSPANID));
+ ctx.b3Mode = true;
+ ctx.b3Traceid = b3TraceId;
+
+ } else {
+ String txid = getHeader(request, conf._trace_interservice_callee_header_key);
+ if (txid != null) {
+ ctx.txid = Hexa32.toLong32(txid);
+ ctx.is_child_tx = true;
+ }
+ String caller = getHeader(request, conf._trace_interservice_caller_header_key);
+ if (caller != null) {
+ ctx.caller = Hexa32.toLong32(caller);
+ ctx.is_child_tx = true;
+ }
+ String callerObjHashStr = getHeader(request, conf._trace_interservice_caller_obj_header_key);
+ if (callerObjHashStr != null) {
+ try {
+ ctx.callerObjHash = Integer.parseInt(callerObjHashStr);
+ } catch (NumberFormatException e) {
+ }
+ ctx.is_child_tx = true;
+ }
+ }
+ } catch (Throwable t) {
+ Logger.println("Z101", "check propergation: " + t.getMessage());
+ }
+
+ if (ctx.is_child_tx) {
+ response.getHeaders().add(conf._trace_interservice_callee_obj_header_key, String.valueOf(conf.getObjHash()));
+ }
+ }
+
+ if (conf.trace_response_gxid_enabled && !ctx.isStaticContents) {
+ try {
+ if (ctx.gxid == 0)
+ ctx.gxid = ctx.txid;
+
+ String resGxId = Hexa32.toString32(ctx.gxid) + ":" + ctx.startTime;
+ response.getHeaders().add(conf._trace_interservice_gxid_header_key, resGxId);
+
+ ResponseCookie c = ResponseCookie.from(conf._trace_interservice_gxid_header_key, resGxId).build();
+ response.addCookie(c);
+
+ } catch (Throwable t) {
+ }
+ }
+
+ //check queuing from front proxy
+ if (conf.trace_request_queuing_enabled) {
+ try {
+ ctx.queuingHost = getHeader(request, conf.trace_request_queuing_start_host_header);
+ String startTime = getHeader(request, conf.trace_request_queuing_start_time_header);
+ if (startTime != null) {
+ int t = startTime.indexOf("t=");
+ int ts = startTime.indexOf("ts=");
+ long startMillis = 0l;
+ if (t >= 0) {
+ startMillis = Long.parseLong(startTime.substring(t + 2).trim())/1000;
+ } else if (ts >= 0) {
+ startMillis = Long.parseLong(startTime.substring(ts + 3).replace(".", ""));
+ }
+
+ if (startMillis > 0) {
+ ctx.queuingTime = (int) (System.currentTimeMillis() - startMillis);
+ }
+ }
+
+ ctx.queuing2ndHost = getHeader(request, conf.trace_request_queuing_start_2nd_host_header);
+ startTime = getHeader(request, conf.trace_request_queuing_start_2nd_time_header);
+ if (startTime != null) {
+ int t = startTime.indexOf("t=");
+ int ts = startTime.indexOf("ts=");
+ long startMillis = 0l;
+ if (t >= 0) {
+ startMillis = Long.parseLong(startTime.substring(t + 2).trim())/1000;
+ } else if (ts >= 0) {
+ startMillis = Long.parseLong(startTime.substring(ts + 3).replace(".", ""));
+ }
+
+ if (startMillis > 0) {
+ ctx.queuing2ndTime = (int) (System.currentTimeMillis() - startMillis);
+ }
+ }
+ } catch (Throwable t) {
+ }
+ }
+ }
+
+ public void end(TraceContext ctx, Object req, Object res) {
+ Configure conf = Configure.getInstance();
+
+ ServerHttpRequest request = (ServerHttpRequest) req;
+
+ if (conf.profile_http_parameter_enabled) {
+ if (conf.profile_http_parameter_url_prefix == null || ctx.serviceName.indexOf(conf.profile_http_parameter_url_prefix) >= 0) {
+ String ctype = getContentType(request);
+ if (ctype != null && ctype.indexOf("multipart") >= 0)
+ return;
+
+ Enumeration en = getParameterNames(request);
+ if (en != null) {
+ int start_time = (int) (System.currentTimeMillis() - ctx.startTime);
+ while (en.hasMoreElements()) {
+ String key = (String) en.nextElement();
+ String value = new StringBuilder().append("parameter: ").append(key).append("=")
+ .append(StringUtil.limiting(getParameter(request, key), 1024)).toString();
+
+ MessageStep step = new MessageStep(value);
+ step.start_time = start_time;
+ ctx.profile.add(step);
+ }
+ }
+ }
+ }
+ }
+
+ private static void dump(IProfileCollector p, ServerHttpRequest request, TraceContext ctx) {
+ Configure conf = Configure.getInstance();
+ if (conf.profile_http_querystring_enabled) {
+ String msg = request.getMethod() + " ?" + StringUtil.trimToEmpty(getQuery(request));
+ MessageStep step = new MessageStep(msg);
+ step.start_time = (int) (System.currentTimeMillis() - ctx.startTime);
+ p.add(step);
+ }
+ if (conf.profile_http_header_enabled) {
+ if (conf.profile_http_header_url_prefix == null || ctx.serviceName.indexOf(conf.profile_http_header_url_prefix) >= 0) {
+ Set>> entries = request.getHeaders().entrySet();
+ if (entries != null) {
+ int start_time = (int) (System.currentTimeMillis() - ctx.startTime);
+ Iterator>> iterator = entries.iterator();
+ while (iterator.hasNext()) {
+ for (int i = 0; i < entries.size(); i++) {
+ Map.Entry> entry = iterator.next();
+ if (conf._profile_http_header_keys != null
+ && conf._profile_http_header_keys.size() > 0
+ && !conf._profile_http_header_keys.contains(entry.getKey().toUpperCase())) {
+ continue;
+ }
+ if (entry.getValue() != null) {
+ for (int j = 0; j < entry.getValue().size(); j++) {
+ String value = new StringBuilder().append("header: ").append(entry.getKey()).append("=")
+ .append(StringUtil.limiting(entry.getValue().get(j), 1024)).toString();
+
+ MessageStep step = new MessageStep(value);
+ step.start_time = start_time;
+
+ p.add(step);
+ }
+ }
+ }
+
+ }
+ }
+ }
+ }
+
+ if (conf.profile_http_parameter_enabled) {
+ HashedMessageStep step = new HashedMessageStep();
+ step.hash = DataProxy.sendHashedMessage("[HTTP parameters] will be shown in the last of this profile if available.(profile_http_parameter_enabled : true)");
+ step.start_time = (int) (System.currentTimeMillis() - ctx.startTime);
+ step.time = -1;
+ ctx.profile.add(step);
+ }
+ }
+
+ public void rejectText(Object res, String text) {
+ ServerHttpResponse response = (ServerHttpResponse) res;
+ try {
+ //TODO
+ response.setRawStatusCode(400);
+ } catch (Exception e) {
+ }
+ }
+
+ public void rejectUrl(Object res, String url) {
+ ServerHttpResponse response = (ServerHttpResponse) res;
+ try {
+ //TODO
+ response.setRawStatusCode(400);
+ } catch (Exception e) {
+ }
+ }
+
+ public void addAsyncContextListener(Object ac) {
+ return;
+ }
+
+ public TraceContext getTraceContextFromAsyncContext(Object oAsyncContext) {
+ return null;
+ }
+
+ public void setDispatchTransferMap(Object oAsyncContext, long gxid, long caller, long callee, byte xType) {
+ return;
+ }
+
+ public void setSelfDispatch(Object oAsyncContext, boolean self) {
+ return;
+ }
+
+ public boolean isSelfDispatch(Object oAsyncContext) {
+ return false;
+ }
+
+}
diff --git a/scouter.agent.java/src/main/java/scouter/xtra/httpclient/HttpClient43.java b/scouter.agent.java/src/main/java/scouter/xtra/httpclient/HttpClient43.java
index cec8c9389..68dbb8bac 100644
--- a/scouter.agent.java/src/main/java/scouter/xtra/httpclient/HttpClient43.java
+++ b/scouter.agent.java/src/main/java/scouter/xtra/httpclient/HttpClient43.java
@@ -64,6 +64,14 @@ public String getResponseHeader(Object o, String key) {
return null;
}
+ public int getResponseStatusCode(Object o) {
+ if (o instanceof HttpResponse) {
+ HttpResponse res = (HttpResponse) o;
+ return res.getStatusLine().getStatusCode();
+ }
+ return 0;
+ }
+
public String getURI(Object o) {
if (o instanceof HttpUriRequest) {
HttpUriRequest req = (HttpUriRequest) o;
diff --git a/scouter.agent.java/src/main/java/scouter/xtra/httpclient/JavaNetHttpClient.java b/scouter.agent.java/src/main/java/scouter/xtra/httpclient/JavaNetHttpClient.java
index a9f48e34d..a76390e7b 100644
--- a/scouter.agent.java/src/main/java/scouter/xtra/httpclient/JavaNetHttpClient.java
+++ b/scouter.agent.java/src/main/java/scouter/xtra/httpclient/JavaNetHttpClient.java
@@ -66,4 +66,9 @@ public String getResponseHeader(Object o, String key) {
}
return null;
}
+
+ @Override
+ public int getResponseStatusCode(Object o) {
+ return 0;
+ }
}
diff --git a/scouter.agent.java/src/main/java/scouter/xtra/httpclient/NettyHttpClient.java b/scouter.agent.java/src/main/java/scouter/xtra/httpclient/NettyHttpClient.java
index e3aca31f8..b137d3dd6 100644
--- a/scouter.agent.java/src/main/java/scouter/xtra/httpclient/NettyHttpClient.java
+++ b/scouter.agent.java/src/main/java/scouter/xtra/httpclient/NettyHttpClient.java
@@ -41,6 +41,14 @@ public String getResponseHeader(Object o, String key) {
return null;
}
+ public int getResponseStatusCode(Object o) {
+ if (o instanceof HttpClientResponse) {
+ HttpClientResponse res = (HttpClientResponse) o;
+ return res.getStatus().code();
+ }
+ return 0;
+ }
+
public String getURI(Object o) {
if (o instanceof HttpClientRequest) {
diff --git a/scouter.agent.java/src/main/java/scouter/xtra/httpclient/SpringRestTemplateHttpRequest.java b/scouter.agent.java/src/main/java/scouter/xtra/httpclient/SpringRestTemplateHttpRequest.java
index 6258f57b9..ca63c6bd4 100644
--- a/scouter.agent.java/src/main/java/scouter/xtra/httpclient/SpringRestTemplateHttpRequest.java
+++ b/scouter.agent.java/src/main/java/scouter/xtra/httpclient/SpringRestTemplateHttpRequest.java
@@ -44,6 +44,10 @@ public String getResponseHeader(Object o, String key) {
return null;
}
+ public int getResponseStatusCode(Object o) {
+ return 0;
+ }
+
public String getURI(Object o) {
if (o instanceof HttpRequest) {
HttpRequest chr = (HttpRequest) o;
diff --git a/scouter.agent.java/src/main/java/scouter/xtra/httpclient/WebClient.java b/scouter.agent.java/src/main/java/scouter/xtra/httpclient/WebClient.java
new file mode 100644
index 000000000..53080e563
--- /dev/null
+++ b/scouter.agent.java/src/main/java/scouter/xtra/httpclient/WebClient.java
@@ -0,0 +1,62 @@
+package scouter.xtra.httpclient;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.client.reactive.ClientHttpRequest;
+import org.springframework.http.client.reactive.ClientHttpResponse;
+import scouter.agent.proxy.IHttpClient;
+
+import java.util.List;
+
+public class WebClient implements IHttpClient {
+ public String getHost(Object o) {
+ if (o instanceof ClientHttpRequest) {
+ ClientHttpRequest chr = (ClientHttpRequest) o;
+ return chr.getURI().getHost() + ":" + chr.getURI().getPort();
+ }
+
+ return o.toString();
+ }
+
+ public void addHeader(Object o, String key, String value) {
+ if (o instanceof ClientHttpRequest) {
+ ClientHttpRequest chr = (ClientHttpRequest) o;
+ HttpHeaders headers = chr.getHeaders();
+ headers.set(key, value);
+ }
+ }
+
+ public String getHeader(Object o, String key) {
+ if (o instanceof ClientHttpRequest) {
+ ClientHttpRequest chr = (ClientHttpRequest) o;
+ List headerValues = chr.getHeaders().get(key);
+ if(headerValues != null && headerValues.size() > 0) {
+ return headerValues.get(0);
+ }
+ }
+ return null;
+ }
+
+ public String getResponseHeader(Object o, String key) {
+ if (o instanceof ClientHttpResponse) {
+ ClientHttpResponse res = (ClientHttpResponse) o;
+ return res.getHeaders().getFirst(key);
+ }
+ return null;
+ }
+
+ public int getResponseStatusCode(Object o) {
+ if (o instanceof ClientHttpResponse) {
+ ClientHttpResponse res = (ClientHttpResponse) o;
+ return res.getRawStatusCode();
+ }
+ return 0;
+ }
+
+ public String getURI(Object o) {
+ if (o instanceof ClientHttpRequest) {
+ ClientHttpRequest chr = (ClientHttpRequest) o;
+ return chr.getURI().getPath();
+ }
+ return o.toString();
+ }
+}
diff --git a/scouter.agent.java/src/main/java/scouter/xtra/java8/ElasticSearchTracer.java b/scouter.agent.java/src/main/java/scouter/xtra/java8/ElasticSearchTracer.java
new file mode 100644
index 000000000..ac80aeeb6
--- /dev/null
+++ b/scouter.agent.java/src/main/java/scouter/xtra/java8/ElasticSearchTracer.java
@@ -0,0 +1,102 @@
+package scouter.xtra.java8;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
+import org.elasticsearch.client.Node;
+import scouter.agent.AgentCommonConstant;
+import scouter.agent.Configure;
+import scouter.agent.Logger;
+import scouter.agent.proxy.IElasticSearchTracer;
+import scouter.agent.trace.TraceContext;
+import scouter.util.LinkedMap;
+import scouter.util.StringUtil;
+
+import java.lang.reflect.Field;
+
+public class ElasticSearchTracer implements IElasticSearchTracer {
+ private static final LinkedMap, Field> fieldMap = new LinkedMap, Field>().setMax(30);
+ boolean err = false;
+
+ private static final Configure conf = Configure.getInstance();
+
+ @Override
+ public String getRequestDescription(TraceContext ctx, Object httpRequestBase0) {
+ return getRequestDescription0(httpRequestBase0, !conf.elasticsearch_full_query_enabled);
+ }
+
+ @Override
+ public String getNode(TraceContext ctx, Object hostOrNode) {
+ if (hostOrNode == null) {
+ return "Unknown-ElasticSearch";
+ }
+ if (hostOrNode instanceof HttpHost) {
+ return ((HttpHost) hostOrNode).toHostString();
+
+ } else if (hostOrNode instanceof Node) {
+ return ((Node) hostOrNode).getHost().toHostString();
+ } else {
+ return "Unknown-ElasticSearch";
+ }
+ }
+
+ @Override
+ public Throwable getResponseError(Object httpRequestBase0, Object httpResponse0) {
+ if (httpResponse0 instanceof HttpResponse) {
+ HttpResponse resp = (HttpResponse) httpResponse0;
+ if (resp.getStatusLine() == null) {
+ return null;
+ }
+ if (resp.getStatusLine().getStatusCode() < 400) {
+ return null;
+ }
+ return new RuntimeException(resp.getStatusLine().getStatusCode()
+ + ": " + resp.toString() + ", [REQUEST]" + getRequestDescription0(httpRequestBase0, false));
+
+ } else {
+ return null;
+ }
+ }
+
+ private String getRequestDescription0(Object httpRequestBase0, boolean cut) {
+ if (httpRequestBase0 == null) {
+ return "No info";
+ }
+ if (httpRequestBase0 instanceof HttpEntityEnclosingRequestBase) {
+ HttpEntityEnclosingRequestBase requestBase = (HttpEntityEnclosingRequestBase) httpRequestBase0;
+ String url = requestBase.toString();
+ if (cut) {
+ return StringUtil.limiting(url, 45);
+ }
+ HttpEntity entity = requestBase.getEntity();
+ try {
+ Class extends HttpEntity> clazz = entity.getClass();
+ Field field = fieldMap.get(clazz);
+ if (field == null) {
+ field = clazz.getField(AgentCommonConstant.SCOUTER_ADDED_FIELD);
+ fieldMap.put(clazz, field);
+ }
+ Object entityDesc = field.get(entity);
+ if (entityDesc == null) {
+ return url;
+ } else {
+ String append = entityDesc instanceof byte[] ? new String((byte[]) entityDesc)
+ : entityDesc.toString();
+ return url + ", entity desc: " + append;
+ }
+ } catch (Exception e) {
+ err = true;
+ Logger.println("G177p", "error, so skip it later.", e);
+ return "No info";
+ }
+
+ } else {
+ String url = httpRequestBase0.toString();
+ if (!conf.elasticsearch_full_query_enabled) {
+ return StringUtil.limiting(url, 45);
+ }
+ return url;
+ }
+ }
+}
diff --git a/scouter.agent.java/src/main/java/scouter/xtra/reactive/ReactiveSupport.java b/scouter.agent.java/src/main/java/scouter/xtra/reactive/ReactiveSupport.java
new file mode 100644
index 000000000..514f30022
--- /dev/null
+++ b/scouter.agent.java/src/main/java/scouter/xtra/reactive/ReactiveSupport.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright 2015 the original author or authors.
+ * @https://github.com/scouter-project/scouter
+ *
+ * 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 scouter.xtra.reactive;
+
+import kotlin.coroutines.CoroutineContext;
+import kotlinx.coroutines.ThreadContextElement;
+import kotlinx.coroutines.ThreadContextElementKt;
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscription;
+import reactor.core.CoreSubscriber;
+import reactor.core.Fuseable;
+import reactor.core.Scannable;
+import reactor.core.publisher.Hooks;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.Operators;
+import reactor.core.publisher.ScouterOptimizableOperatorProxy;
+import reactor.core.publisher.SignalType;
+import reactor.util.context.Context;
+import scouter.agent.AgentCommonConstant;
+import scouter.agent.Configure;
+import scouter.agent.Logger;
+import scouter.agent.netio.data.DataProxy;
+import scouter.agent.proxy.IReactiveSupport;
+import scouter.agent.trace.TraceContext;
+import scouter.agent.trace.TraceContextManager;
+import scouter.agent.trace.TraceMain;
+import scouter.lang.enumeration.ParameterizedMessageLevel;
+import scouter.lang.step.ParameterizedMessageStep;
+import scouter.util.StringUtil;
+
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class ReactiveSupport implements IReactiveSupport {
+
+ static Configure configure = Configure.getInstance();
+
+ @Override
+ public Object subscriptOnContext(Object mono0, final TraceContext traceContext) {
+ try {
+ if (traceContext.isReactiveTxidMarked) {
+ return mono0;
+ }
+ Mono> mono = (Mono>) mono0;
+ return mono.subscriberContext(new Function() {
+ @Override
+ public Context apply(Context context) {
+ traceContext.isReactiveTxidMarked = true;
+ return context.put(TraceContext.class, traceContext);
+ //return context.put(AgentCommonConstant.TRACE_ID, traceContext.txid);
+ }
+ }).doOnSuccess(new Consumer