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 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 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() { + @Override + public void accept(Object o) { + TraceMain.endHttpService(new TraceMain.Stat(traceContext), null); + } + }).doOnError(new Consumer() { + @Override + public void accept(Throwable throwable) { + TraceMain.endHttpService(new TraceMain.Stat(traceContext), throwable); + } + }).doOnCancel(new Runnable() { + @Override + public void run() { + TraceMain.endCanceledHttpService(traceContext); + } + }).doFinally(new Consumer() { + @Override + public void accept(SignalType signalType) { + TraceContextManager.clearAllContext(traceContext); + } + }).doAfterTerminate(new Runnable() { + @Override + public void run() { + } + }).doOnNext(new Consumer() { + @Override + public void accept(Object o) { + } + }).doFirst(new Runnable() { + @Override + public void run() { + } + }); + } catch (Exception e) { + e.printStackTrace(); + return mono0; + } + } + + @Override + public void contextOperatorHook() { + try { + Hooks.onEachOperator(AgentCommonConstant.TRACE_ID, Operators.lift( + new BiFunction, CoreSubscriber>() { + @Override + public CoreSubscriber apply(Scannable scannable, CoreSubscriber subscriber) { + if (scannable instanceof Fuseable.ScalarCallable) { + return subscriber; + } + //TODO reactor context : parent child 관계 (depth) 생성 (ScopePassingSpanSubscriber 참고) + Context context = subscriber.currentContext(); + TraceContext traceContext = getTraceContext(scannable, context); + + if (traceContext != null) { + //걍 이걸 txidlifter로 넘기자 + return new TxidLifter(subscriber, scannable, null, traceContext); + } else { + return subscriber; + } + } + })); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private TraceContext getTraceContext(Scannable scannable, Context currentContext) { + if (scannable == null || currentContext == null) { + return null; + } + return currentContext.getOrDefault(TraceContext.class, null); + } + + @Override + public Object monoCoroutineContextHook(Object _coroutineContext, TraceContext traceContext) { + CoroutineContext coroutineContext = (CoroutineContext) _coroutineContext; + + TraceContextManager.startByCoroutine(traceContext); + + ThreadContextElement threadContextElement = ThreadContextElementKt + .asContextElement(TraceContextManager.txidByCoroutine, traceContext.txid); + return coroutineContext.plus(threadContextElement); + } + + public static class SubscribeDepth {} + public static class TxidLifter implements SpanSubscription, Scannable { + + private final CoreSubscriber coreSubscriber; + private final Context ctx; + private final Scannable scannable; + private final Publisher publisher; + private final TraceContext traceContext; + private final String checkpointDesc; + private final Integer depth; + private Subscription orgSubs; + + private enum ReactorCheckPointType { + ON_SUBSCRIBE, + ON_COMPLETE, + ON_ERROR, + ON_CANCEL + } + + public TxidLifter(CoreSubscriber coreSubscriber, Scannable scannable, Publisher publisher, + TraceContext traceContext) { + this.coreSubscriber = coreSubscriber; + Context context = coreSubscriber.currentContext(); + this.scannable = scannable; + this.publisher = publisher; + this.traceContext = traceContext; + + checkpointDesc = ScouterOptimizableOperatorProxy.nameOnCheckpoint(scannable); + Integer parentDepth = context.getOrDefault(SubscribeDepth.class, 0); + depth = (!"".equals(checkpointDesc)) ? parentDepth + 1 : parentDepth; + this.ctx = context.put(SubscribeDepth.class, depth); + + //todo parent something +// this.ctx = parent != null +// && !parent.equals(ctx.getOrDefault(TraceContext.class, null)) +// ? ctx.put(TraceContext.class, parent) : ctx; + } + + @Override + public void onSubscribe(Subscription subs) { + copyToThread(currentContext(), traceContext); + try { + //TODO 생성자로.. +// Integer depth = context.getOrDefault(AgentCommonConstant.SUBS_DEPTH, -1); +// depth = depth + 1; +// context.put(AgentCommonConstant.SUBS_DEPTH, depth); +// System.out.println(">>>>>>>>> depth: " + depth + ", scannable:" + scannable); + traceContext.scannables.put(scannable.hashCode(), + new TraceContext.TimedScannable(System.currentTimeMillis(), scannable)); + profileCheckPoint(scannable, traceContext, ReactorCheckPointType.ON_SUBSCRIBE, null); + } catch (Exception e) { + Logger.println("[R109]", "reactive support onSubscribe error.", e); + } + this.orgSubs = subs; + coreSubscriber.onSubscribe(this); + } + + @Override + public void onNext(T t) { + copyToThread(currentContext(), traceContext); + coreSubscriber.onNext(t); + } + + @Override + public void onError(Throwable throwable) { + copyToThread(currentContext(), traceContext); + try { + TraceContext.TimedScannable timedScannable = traceContext.scannables.remove(scannable.hashCode()); + profileCheckPoint(scannable, traceContext, ReactorCheckPointType.ON_ERROR, timedScannable); + } catch (Exception e) { + Logger.println("[R110]", "reactive support onError error.", e); + } + coreSubscriber.onError(throwable); + } + + @Override + public void onComplete() { + copyToThread(currentContext(), traceContext); + try { + TraceContext.TimedScannable timedScannable = traceContext.scannables.remove(scannable.hashCode()); + profileCheckPoint(scannable, traceContext, ReactorCheckPointType.ON_COMPLETE, timedScannable); + } catch (Exception e) { + Logger.println("[R111]", "reactive support onComplete error.", e); + } + coreSubscriber.onComplete(); + } + + @Override + public void request(long n) { + this.orgSubs.request(n); + } + + @Override + public void cancel() { + copyToThread(currentContext(), traceContext); + try { + TraceContext.TimedScannable timedScannable = traceContext.scannables.remove(scannable.hashCode()); + profileCheckPoint(scannable, traceContext, ReactorCheckPointType.ON_CANCEL, timedScannable); + } catch (Exception e) { + Logger.println("[R112]", "reactive support onCancel error.", e); + } + this.orgSubs.cancel(); + } + + @Override + public Context currentContext() { + return ctx; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return this.orgSubs; + } + else { + return key == Attr.ACTUAL ? this.coreSubscriber : null; + } + } + + private void copyToThread(Context context, TraceContext traceContext) { + Long threadLocalTxid = TraceContextManager.getLocalTxid(); + if (threadLocalTxid == null) { + TraceContextManager.setTxidLocal(traceContext.txid); + } else if (threadLocalTxid != traceContext.txid) { + TraceContextManager.setTxidLocal(traceContext.txid); + } + } + + private TraceContext getTraceContext(Scannable scannable, Context currentContext) { + if (scannable == null || currentContext == null) { + return null; + } + return currentContext.getOrDefault(TraceContext.class, null); + } + + private void profileCheckPoint(Scannable scannable, TraceContext traceContext, ReactorCheckPointType type, + TraceContext.TimedScannable timedScannable) { + + if (scannable.isScanAvailable()) { + if (!"".equals(checkpointDesc)) { + String duration; + StringBuilder messageBuilder = new StringBuilder(300) + .append(StringUtil.padding((depth - 1) * 2, ' ')) + .append("[") + .append(type.name()); + + if (timedScannable != null) { + messageBuilder.append("(%sms): "); + duration = String.valueOf(System.currentTimeMillis() - timedScannable.start); + } else { + messageBuilder.append(": "); + duration = ""; + } + + String message = messageBuilder.append(scannable.name()) + .append("] ") + .append(checkpointDesc).toString(); + + ParameterizedMessageStep step = new ParameterizedMessageStep(); + step.setMessage(DataProxy.sendHashedMessage(message), duration); + step.start_time = (int) (System.currentTimeMillis() - traceContext.startTime); + + if (checkpointDesc.startsWith("checkpoint")) { + step.setLevel(ParameterizedMessageLevel.INFO); + } else { + step.setLevel(ParameterizedMessageLevel.DEBUG); + } + traceContext.profile.add(step); + } + } + } + } + + public String dumpScannable(TraceContext traceContext, TraceContext.TimedScannable timedScannable, long now) { + + if (traceContext == null || timedScannable == null) { + return null; + } + Scannable scannable = (Scannable) timedScannable.scannable; + long duration = now - timedScannable.start; + StringBuilder builder = new StringBuilder(1000) + .append(scannable.name()).append(" ").append(duration).append("ms"); + + ScouterOptimizableOperatorProxy.appendSources4Dump(scannable, builder); + return builder.toString(); + } +} diff --git a/scouter.agent.java/src/main/java/scouter/xtra/reactive/SpanSubscription.java b/scouter.agent.java/src/main/java/scouter/xtra/reactive/SpanSubscription.java new file mode 100644 index 000000000..65ed44fdd --- /dev/null +++ b/scouter.agent.java/src/main/java/scouter/xtra/reactive/SpanSubscription.java @@ -0,0 +1,77 @@ +/* + * 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. + * + */ + +/* + * Copyright 2013-2019 the original author or authors. + * + * 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 + * + * https://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 org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; + +/** + * A {@link SpanSubscription} is a {@link Subscription} that fakes being {@link Fuseable} + * (implementing {@link Fuseable.QueueSubscription} with default no-op + * methods and always negotiating fusion to be {@link Fuseable#NONE}). + * + * @param - type of the subscription + * @author Marcin Grzejszczak + */ +interface SpanSubscription + extends Subscription, CoreSubscriber, Fuseable.QueueSubscription { + + @Override + default T poll() { + return null; + } + + @Override + default int requestFusion(int i) { + return Fuseable.NONE; // always negotiate to no fusion + } + + @Override + default int size() { + return 0; + } + + @Override + default boolean isEmpty() { + return true; + } + + @Override + default void clear() { + // NO-OP + } + +} diff --git a/scouter.common/pom.xml b/scouter.common/pom.xml index acb4b143f..06ac01ee7 100644 --- a/scouter.common/pom.xml +++ b/scouter.common/pom.xml @@ -30,6 +30,7 @@ maven-compiler-plugin 3.1 + 1.6 1.6 1.6 diff --git a/scouter.common/src/main/java/scouter/lang/counters/CounterConstants.java b/scouter.common/src/main/java/scouter/lang/counters/CounterConstants.java index 71b55aced..99ed2c14f 100644 --- a/scouter.common/src/main/java/scouter/lang/counters/CounterConstants.java +++ b/scouter.common/src/main/java/scouter/lang/counters/CounterConstants.java @@ -170,4 +170,5 @@ public class CounterConstants { public static final String INTR_REDIS_CALL = "INTR_REDIS_CALL"; public static final String INTR_KAFKA_CALL = "INTR_KAFKA_CALL"; public static final String INTR_RABBITMQ_CALL = "INTR_RABBITMQ_CALL"; + public static final String INTR_ELASTICSEARCH_CALL = "INTR_ELASTICSEARCH_CALL"; } diff --git a/scouter.common/src/main/java/scouter/util/ThreadUtil.java b/scouter.common/src/main/java/scouter/util/ThreadUtil.java index cb546daa5..ae15bd9f7 100644 --- a/scouter.common/src/main/java/scouter/util/ThreadUtil.java +++ b/scouter.common/src/main/java/scouter/util/ThreadUtil.java @@ -72,6 +72,11 @@ public static String getName(Class clazz) { public static MapPack getThreadDetail(long thread_id) { MapPack m = new MapPack(); + return appendThreadDetail(thread_id, m); + } + + public static MapPack appendThreadDetail(long thread_id, MapPack m) { + if (thread_id == 0) return m; ThreadMXBean tmb = ManagementFactory.getThreadMXBean();