diff --git a/pom.xml b/pom.xml index 6838436b5..18f7aa8d2 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.3 + 1.8.4 pom SCOUTER APM @@ -43,7 +43,7 @@ local-maven-repo - file:///${project.basedir}/local-maven-repo + file:///${project.basedir}/../local-maven-repo redhat.com diff --git a/scouter.agent.batch/pom.xml b/scouter.agent.batch/pom.xml index 22e8a5a1e..0be8c9f96 100644 --- a/scouter.agent.batch/pom.xml +++ b/scouter.agent.batch/pom.xml @@ -5,7 +5,7 @@ io.github.scouter-project scouter-parent - 1.8.3 + 1.8.4 scouter-agent-batch diff --git a/scouter.agent.host/pom.xml b/scouter.agent.host/pom.xml index e5fff83e5..b22276cad 100644 --- a/scouter.agent.host/pom.xml +++ b/scouter.agent.host/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.3 + 1.8.4 scouter-agent-host diff --git a/scouter.agent.java/pom.xml b/scouter.agent.java/pom.xml index 531be5c50..d13e8487d 100644 --- a/scouter.agent.java/pom.xml +++ b/scouter.agent.java/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.3 + 1.8.4 scouter-agent-java 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 e4696d746..815d2c0e2 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java +++ b/scouter.agent.java/src/main/java/scouter/agent/AgentTransformer.java @@ -27,6 +27,7 @@ import scouter.agent.asm.IASM; import scouter.agent.asm.InitialContextASM; import scouter.agent.asm.JDBCConnectionOpenASM; +import scouter.agent.asm.JDBCGetConnectionASM; import scouter.agent.asm.JDBCDriverASM; import scouter.agent.asm.JDBCPreparedStatementASM; import scouter.agent.asm.JDBCResultSetASM; @@ -48,6 +49,9 @@ import scouter.agent.asm.asyncsupport.RequestStartAsyncASM; import scouter.agent.asm.asyncsupport.executor.ExecutorServiceASM; import scouter.agent.asm.asyncsupport.spring.SpringAsyncExecutionASM; +import scouter.agent.asm.redis.JedisConnectionASM; +import scouter.agent.asm.redis.RedisCacheKeyASM; +import scouter.agent.asm.redis.RedisKeyASM; import scouter.agent.asm.util.AsmUtil; import scouter.agent.util.AsyncRunner; import scouter.lang.conf.ConfObserver; @@ -102,7 +106,7 @@ public static void reload() { temp.add(new JDBCStatementASM()); temp.add(new SqlMapASM()); temp.add(new UserTxASM()); - + temp.add(new JDBCGetConnectionASM()); temp.add(new JDBCConnectionOpenASM()); temp.add(new JDBCDriverASM()); temp.add(new InitialContextASM()); @@ -118,6 +122,9 @@ public static void reload() { temp.add(new SpringAsyncExecutionASM()); temp.add(new CallRunnableASM()); temp.add(new ExecutorServiceASM()); + temp.add(new RedisKeyASM()); + temp.add(new RedisCacheKeyASM()); + temp.add(new JedisConnectionASM()); temp.add(new SpringReqMapASM()); temp.add(new HystrixCommandASM()); 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 cec9bbfc3..cad3db15b 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/Configure.java +++ b/scouter.agent.java/src/main/java/scouter/agent/Configure.java @@ -167,6 +167,12 @@ public final static synchronized Configure getInstance() { public boolean profile_fullstack_sql_commit_enabled = false; @ConfigDesc("Stack profile in occurrence of sql error") public boolean profile_fullstack_hooked_exception_enabled = false; + + @ConfigDesc("Stack profile in occurrence of redis error") + public boolean profile_fullstack_redis_error_enabled = false; + @ConfigDesc("make unknown redis key stringify by force. (using new String(byte[])") + public boolean profile_redis_key_forcibly_stringify_enabled = false; + @ConfigDesc("Number of stack profile lines in occurrence of error") public int profile_fullstack_max_lines = 0; @@ -310,6 +316,8 @@ public final static synchronized Configure getInstance() { public boolean xlog_error_on_sqlexception_enabled = true; @ConfigDesc("mark as error on xlog flag if Api call errors are occured.") 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; //XLog hard sampling options @ConfigDesc("XLog hard sampling mode enabled\n - for the best performance but it affects all statistics data") @@ -418,6 +426,11 @@ public final static synchronized Configure getInstance() { @ConfigValueType(ValueType.COMMA_SEPARATED_VALUE) public String hook_connection_open_patterns = ""; + @ConfigDesc("Method set for getconnection hooking") + @ConfigValueType(ValueType.COMMA_SEPARATED_VALUE) + public String hook_get_connection_patterns = ""; + + @ConfigDesc("IntialContext Class Set") @ConfigValueType(ValueType.COMMA_SEPARATED_VALUE) public String hook_context_classes = "javax/naming/InitialContext"; @@ -526,6 +539,10 @@ public final static synchronized Configure getInstance() { @ConfigValueType(ValueType.COMMA_SEPARATED_VALUE) public String hook_async_callrunnable_scan_package_prefixes = ""; + @ConfigDesc("redis key setting patterns.\n refer to org.springframework.data.redis.core.AbstractOperations#rawKey") + @ConfigValueType(ValueType.COMMA_SEPARATED_VALUE) + public String _hook_redis_set_key_patterns = ""; + @ConfigDesc("PRE-released option before stable release!\nhook threadpool executor for tracing async processing.") public boolean hook_async_thread_pool_executor_enabled = false; @@ -553,9 +570,13 @@ public final static synchronized Configure getInstance() { @ConfigDesc("") public boolean _hook_usertx_enabled = true; @ConfigDesc("") - public String _hook_direct_patch_classes = ""; - @ConfigDesc("") public boolean _hook_spring_rest_enabled = true; + @ConfigDesc("") + public boolean _hook_redis_enabled = true; + + @ConfigDesc("") + public String _hook_direct_patch_classes = ""; + @ConfigDesc("") public String _hook_boot_prefix = null; @ConfigDesc("for warning a big Map type object that have a lot of entities.\n It may increase system load. be careful to enable this option.") @@ -794,7 +815,8 @@ private void apply() { this.hook_return_patterns = getValue("hook_return_patterns", ""); this.hook_constructor_patterns = getValue("hook_constructor_patterns", ""); this.hook_connection_open_patterns = getValue("hook_connection_open_patterns", ""); - + this.hook_get_connection_patterns = getValue("hook_get_connection_patterns",""); + this._log_datasource_lookup_enabled = getBoolean("_log_datasource_lookup_enabled", true); this.profile_connection_open_enabled = getBoolean("profile_connection_open_enabled", true); this._summary_connection_leak_fullstack_enabled = getBoolean("_summary_connection_leak_fullstack_enabled", false); @@ -853,6 +875,8 @@ private void apply() { this.hook_async_callrunnable_scan_package_prefixes = getValue("hook_async_callrunnable_scan_package_prefixes", ""); + this._hook_redis_set_key_patterns = getValue("_hook_redis_set_key_patterns", ""); + this.hook_async_thread_pool_executor_enabled = getBoolean("hook_async_thread_pool_executor_enabled", false); this.hook_lambda_instrumentation_strategy_enabled = getBoolean("hook_lambda_instrumentation_strategy_enabled", false); @@ -885,6 +909,8 @@ private void apply() { this.profile_fullstack_sql_error_enabled = getBoolean("profile_fullstack_sql_error_enabled", false); this.profile_fullstack_sql_commit_enabled = getBoolean("profile_fullstack_sql_commit_enabled", false); this.profile_fullstack_hooked_exception_enabled = getBoolean("profile_fullstack_hooked_exception_enabled", false); + this.profile_fullstack_redis_error_enabled = getBoolean("profile_fullstack_redis_error_enabled", false); + this.profile_redis_key_forcibly_stringify_enabled = getBoolean("profile_redis_key_forcibly_stringify_enabled", false); this.profile_fullstack_max_lines = getInt("profile_fullstack_max_lines", 0); this.profile_fullstack_rs_leak_enabled = getBoolean("profile_fullstack_rs_leak_enabled", false); @@ -916,7 +942,11 @@ private void apply() { this._hook_async_enabled = getBoolean("_hook_async_enabled", true); this.trace_db2_enabled = getBoolean("trace_db2_enabled", true); this._hook_usertx_enabled = getBoolean("_hook_usertx_enabled", true); + this._hook_spring_rest_enabled = getBoolean("_hook_spring_rest_enabled", true); + this._hook_redis_enabled = getBoolean("_hook_redis_enabled", true); + this._hook_direct_patch_classes = getValue("_hook_direct_patch_classes", ""); + this._hook_boot_prefix = getValue("_hook_boot_prefix"); this._hook_map_impl_enabled = getBoolean("_hook_map_impl_enabled", false); this._hook_map_impl_warning_size = getInt("_hook_map_impl_warning_size", 50000); @@ -969,7 +999,6 @@ private void apply() { this.__ip_dummy_test = getBoolean("__ip_dummy_test", false); this.alert_perm_warning_pct = getInt("alert_perm_warning_pct", 90); - this._hook_spring_rest_enabled = getBoolean("_hook_spring_rest_enabled", true); this.alert_message_length = getInt("alert_message_length", 3000); this.alert_send_interval_ms = getInt("alert_send_interval_ms", 10000); @@ -977,6 +1006,7 @@ private void apply() { this.xlog_error_sql_time_max_ms = getInt("xlog_error_sql_time_max_ms", 30000); 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._log_asm_enabled = getBoolean("_log_asm_enabled", false); this.obj_type_inherit_to_child_enabled = getBoolean("obj_type_inherit_to_child_enabled", false); diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/JDBCGetConnectionASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/JDBCGetConnectionASM.java new file mode 100644 index 000000000..d6674f6dd --- /dev/null +++ b/scouter.agent.java/src/main/java/scouter/agent/asm/JDBCGetConnectionASM.java @@ -0,0 +1,128 @@ +/* + * 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 java.util.HashMap; +import java.util.List; +import java.util.Map; + +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.TraceSQL; +import scouter.org.objectweb.asm.ClassVisitor; +import scouter.org.objectweb.asm.MethodVisitor; +import scouter.org.objectweb.asm.Opcodes; +import scouter.org.objectweb.asm.Type; +import scouter.org.objectweb.asm.commons.LocalVariablesSorter; + +public class JDBCGetConnectionASM implements IASM, Opcodes { + private List target = HookingSet.getHookingMethodSet(Configure.getInstance().hook_get_connection_patterns); + private Map reserved = new HashMap(); + + public JDBCGetConnectionASM() { + //AsmUtil.add(reserved, "weblogic/jdbc/common/internal/RmiDataSource", "getConnection()Ljava/sql/Connection;"); + + } + + public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) { + if (Configure.getInstance()._hook_dbsql_enabled == false) { + return cv; + } + + HookingSet mset = reserved.get(className); + if (mset != null) + return new DataSourceCV(cv, mset, className); + + for (int i = 0; i < target.size(); i++) { + mset = target.get(i); + if (mset.classMatch.include(className)) { + return new DataSourceCV(cv, mset, className); + } + } + return cv; + } +} + +class DataSourceCV extends ClassVisitor implements Opcodes { + + public String className; + private HookingSet mset; + + public DataSourceCV(ClassVisitor cv, HookingSet mset, String className) { + super(ASM5, cv); + this.mset = mset; + 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 || mset.isA(name, desc) == false) { + return mv; + } + if (AsmUtil.isSpecial(name)) { + return mv; + } + return new DataSourceMV(access, desc, mv, className,name); + } +} + +// /////////////////////////////////////////////////////////////////////////// +class DataSourceMV extends LocalVariablesSorter implements Opcodes { + private static final String TRACE_SQL = TraceSQL.class.getName().replace('.', '/'); + private final static String METHOD = "getConnection"; + private static final String SIGNATURE = "(Ljava/sql/Connection;)Ljava/sql/Connection;"; + + private Type returnType; + private String className; + private String methodName; + private String methodDesc; + + public DataSourceMV(int access, String desc, MethodVisitor mv, String className, String methodName) { + super(ASM5,access, desc, mv); + this.returnType = Type.getReturnType(desc); + this.className = className; + this.methodName = methodName; + this.methodDesc = desc; + } + + + @Override + public void visitInsn(int opcode) { + if ((opcode >= IRETURN && opcode <= RETURN)) { + int i = newLocal(this.returnType); + mv.visitVarInsn(ASTORE, i); + mv.visitVarInsn(Opcodes.ALOAD, i); + AsmUtil.PUSH(mv, className); + AsmUtil.PUSH(mv, methodName); + AsmUtil.PUSH(mv, methodDesc); + mv.visitVarInsn(Opcodes.ALOAD, 0); + + mv.visitVarInsn(Opcodes.ALOAD, i); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACE_SQL, METHOD, SIGNATURE,false); + + } + mv.visitInsn(opcode); + } + + +} \ No newline at end of file diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/redis/JedisConnectionASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/redis/JedisConnectionASM.java new file mode 100644 index 000000000..56ab69704 --- /dev/null +++ b/scouter.agent.java/src/main/java/scouter/agent/asm/redis/JedisConnectionASM.java @@ -0,0 +1,151 @@ +/* + * 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.redis; + +import scouter.agent.ClassDesc; +import scouter.agent.Configure; +import scouter.agent.asm.IASM; +import scouter.agent.asm.util.AsmUtil; +import scouter.agent.asm.util.HookingSet; +import scouter.agent.trace.TraceMain; +import scouter.org.objectweb.asm.ClassVisitor; +import scouter.org.objectweb.asm.Label; +import scouter.org.objectweb.asm.MethodVisitor; +import scouter.org.objectweb.asm.Opcodes; +import scouter.org.objectweb.asm.Type; +import scouter.org.objectweb.asm.commons.LocalVariablesSorter; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Gun Lee (gunlee01@gmail.com) on 2018. 3. 20. + */ +public class JedisConnectionASM implements IASM, Opcodes { + private Configure conf = Configure.getInstance(); + + private static List hookingPattern = new ArrayList(); + static { + hookingPattern.add("redis.clients.jedis.Protocol.sendCommand(Lredis/clients/util/RedisOutputStream;[B[[B)V"); + } + private List targetList; + + public JedisConnectionASM() { + targetList = HookingSet.getHookingMethodSet(HookingSet.buildPatterns("", hookingPattern)); + } + + public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) { + if (conf._hook_redis_enabled == false) + return cv; + + for (int i = 0; i < targetList.size(); i++) { + HookingSet mset = targetList.get(i); + if (mset.classMatch.include(className)) { + return new JedisConnectionCV(cv, mset, className); + } + } + + return cv; + } +} + +class JedisConnectionCV extends ClassVisitor implements Opcodes { + String className; + HookingSet mset; + + public JedisConnectionCV(ClassVisitor cv, HookingSet mset, String className) { + super(ASM5, cv); + this.mset = mset; + 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 || mset.isA(name, desc) == false) { + return mv; + } + if (AsmUtil.isSpecial(name)) { + return mv; + } + + return new SendCommandMV(access, name, desc, mv); + } +} + +class SendCommandMV extends LocalVariablesSorter implements Opcodes { + private static final String TRACEMAIN = TraceMain.class.getName().replace('.', '/'); + private static final String START_METHOD = "startSendRedisCommand"; + private static final String START_SIGNATURE = "()Ljava/lang/Object;"; + private static final String END_METHOD = "endSendRedisCommand"; + private static final String END_SIGNATURE = "([B[[BLjava/lang/Object;Ljava/lang/Throwable;)V"; + //private static final String END_SIGNATURE = "(Ljava/lang/Object;Ljava/lang/Throwable;)V"; + + private String name; + private String desc; + private Type returnType; + private int statIdx; + private Label startFinally = new Label(); + + public SendCommandMV(int access, String name, String desc, MethodVisitor mv) { + super(ASM5, access, desc, mv); + this.name = name; + this.desc = desc; + this.returnType = Type.getReturnType(desc); + } + + @Override + public void visitCode() { + mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACEMAIN, START_METHOD, START_SIGNATURE, false); + statIdx = newLocal(Type.getType(Object.class)); + mv.visitVarInsn(Opcodes.ASTORE, statIdx); + mv.visitLabel(startFinally); + mv.visitCode(); + } + + @Override + public void visitInsn(int opcode) { + if ((opcode >= IRETURN && opcode <= RETURN)) { + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitVarInsn(Opcodes.ALOAD, statIdx); + mv.visitInsn(Opcodes.ACONST_NULL); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACEMAIN, END_METHOD, END_SIGNATURE, 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.visitInsn(DUP); + int errIdx = newLocal(Type.getType(Throwable.class)); + mv.visitVarInsn(Opcodes.ASTORE, errIdx); + + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitVarInsn(Opcodes.ALOAD, statIdx); + mv.visitVarInsn(Opcodes.ALOAD, errIdx); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACEMAIN, END_METHOD, END_SIGNATURE, false); + mv.visitInsn(ATHROW); + mv.visitMaxs(maxStack + 8, maxLocals + 2); + } +} diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisCacheKeyASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisCacheKeyASM.java new file mode 100644 index 000000000..ab39c2b17 --- /dev/null +++ b/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisCacheKeyASM.java @@ -0,0 +1,145 @@ +/* + * 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.redis; + +import scouter.agent.ClassDesc; +import scouter.agent.Configure; +import scouter.agent.Logger; +import scouter.agent.asm.IASM; +import scouter.agent.asm.util.AsmUtil; +import scouter.agent.asm.util.HookingSet; +import scouter.agent.trace.TraceMain; +import scouter.org.objectweb.asm.ClassVisitor; +import scouter.org.objectweb.asm.FieldVisitor; +import scouter.org.objectweb.asm.MethodVisitor; +import scouter.org.objectweb.asm.Opcodes; +import scouter.org.objectweb.asm.Type; +import scouter.org.objectweb.asm.commons.LocalVariablesSorter; + +import java.util.ArrayList; +import java.util.List; + +import static scouter.agent.asm.redis.RedisCacheKeyASM.KEY_ELEMENT_FIELD; +import static scouter.agent.asm.redis.RedisCacheKeyASM.KEY_ELEMENT_FIELD_DESC; + +/** + * @author Gun Lee (gunlee01@gmail.com) on 2018. 3. 20. + */ +public class RedisCacheKeyASM implements IASM, Opcodes { + public static final String KEY_ELEMENT_FIELD = "keyElement"; + public static final String KEY_ELEMENT_FIELD_DESC = "Ljava/lang/Object;"; + + private Configure conf = Configure.getInstance(); + + private static List pattern = new ArrayList(); + static { + pattern.add("org.springframework.data.redis.cache.RedisCacheKey.getKeyBytes()[B"); + } + private List targetList; + + public RedisCacheKeyASM() { + targetList = HookingSet.getHookingMethodSet(HookingSet.buildPatterns("", pattern)); + } + + public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) { + if (conf._hook_redis_enabled == false) + return cv; + + for (int i = 0; i < targetList.size(); i++) { + HookingSet mset = targetList.get(i); + if (mset.classMatch.include(className)) { + return new RedisCacheKeyCV(cv, mset, className); + } + } + + return cv; + } +} + +class RedisCacheKeyCV extends ClassVisitor implements Opcodes { + String className; + HookingSet mset; + boolean existKeyElementField; + + public RedisCacheKeyCV(ClassVisitor cv, HookingSet mset, String className) { + super(ASM5, cv); + this.mset = mset; + this.className = className; + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + if (name.equals(KEY_ELEMENT_FIELD) && desc.equals(KEY_ELEMENT_FIELD_DESC)) { + existKeyElementField = true; + } + 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 (!existKeyElementField) { + Logger.println("A902", "Ignore hooking - No Field " + KEY_ELEMENT_FIELD + " on " + className); + return mv; + } + + if (mv == null || mset.isA(name, desc) == false) { + return mv; + } + + if (AsmUtil.isSpecial(name)) { + return mv; + } + + return new GetKeyBytesMV(access, this.className, name, desc, mv); + } +} + +class GetKeyBytesMV extends LocalVariablesSorter implements Opcodes { + + private static final String TRACEMAIN = TraceMain.class.getName().replace('.', '/'); + private static final String START_METHOD = "setRedisKey"; + private static final String START_SIGNATURE = "([BLjava/lang/Object;)V"; + + private String className; + private String name; + private String desc; + private Type returnType; + + public GetKeyBytesMV(int access, String className, String name, String desc, MethodVisitor mv) { + super(ASM5, access, desc, mv); + this.className = className; + this.name = name; + this.desc = desc; + this.returnType = Type.getReturnType(desc); + } + + public void visitInsn(int opcode) { + if ((opcode >= IRETURN && opcode <= RETURN)) { + Type tp = returnType; + if (tp.getSort() == Type.ARRAY && tp.getElementType().getSort() == Type.BYTE) { + mv.visitInsn(Opcodes.DUP); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, this.className, KEY_ELEMENT_FIELD, "Ljava/lang/Object;"); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACEMAIN, START_METHOD, START_SIGNATURE, false); + } + } + mv.visitInsn(opcode); + } +} diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisKeyASM.java b/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisKeyASM.java new file mode 100644 index 000000000..e412559a2 --- /dev/null +++ b/scouter.agent.java/src/main/java/scouter/agent/asm/redis/RedisKeyASM.java @@ -0,0 +1,118 @@ +/* + * 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.redis; + +import scouter.agent.ClassDesc; +import scouter.agent.Configure; +import scouter.agent.asm.IASM; +import scouter.agent.asm.util.AsmUtil; +import scouter.agent.asm.util.HookingSet; +import scouter.agent.trace.TraceMain; +import scouter.org.objectweb.asm.ClassVisitor; +import scouter.org.objectweb.asm.MethodVisitor; +import scouter.org.objectweb.asm.Opcodes; +import scouter.org.objectweb.asm.Type; +import scouter.org.objectweb.asm.commons.LocalVariablesSorter; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Gun Lee (gunlee01@gmail.com) on 2018. 3. 20. + */ +public class RedisKeyASM implements IASM, Opcodes { + private Configure conf = Configure.getInstance(); + + private static List setKeyPattern = new ArrayList(); + static { + setKeyPattern.add("org.springframework.data.redis.core.AbstractOperations.rawKey(Ljava/lang/Object;)[B"); + } + private List targetList; + + public RedisKeyASM() { + targetList = HookingSet.getHookingMethodSet(HookingSet.buildPatterns(conf._hook_redis_set_key_patterns, setKeyPattern)); + } + + public ClassVisitor transform(ClassVisitor cv, String className, ClassDesc classDesc) { + if (conf._hook_redis_enabled == false) + return cv; + + for (int i = 0; i < targetList.size(); i++) { + HookingSet mset = targetList.get(i); + if (mset.classMatch.include(className)) { + return new RedisKeySetCV(cv, mset, className); + } + } + + return cv; + } +} + +class RedisKeySetCV extends ClassVisitor implements Opcodes { + String className; + HookingSet mset; + + public RedisKeySetCV(ClassVisitor cv, HookingSet mset, String className) { + super(ASM5, cv); + this.mset = mset; + 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 || mset.isA(name, desc) == false) { + return mv; + } + if (AsmUtil.isSpecial(name)) { + return mv; + } + + return new KeySetMV(access, name, desc, mv); + } +} + +class KeySetMV extends LocalVariablesSorter implements Opcodes { + private static final String TRACEMAIN = TraceMain.class.getName().replace('.', '/'); + private static final String START_METHOD = "setRedisKey"; + private static final String START_SIGNATURE = "([BLjava/lang/Object;)V"; + + private String name; + private String desc; + private Type returnType; + + public KeySetMV(int access, String name, String desc, MethodVisitor mv) { + super(ASM5, access, desc, mv); + this.name = name; + this.desc = desc; + this.returnType = Type.getReturnType(desc); + } + + public void visitInsn(int opcode) { + if ((opcode >= IRETURN && opcode <= RETURN)) { + Type tp = returnType; + if (tp.getSort() == Type.ARRAY && tp.getElementType().getSort() == Type.BYTE) { + mv.visitInsn(Opcodes.DUP); + mv.visitVarInsn(Opcodes.ALOAD, 1);// stat + mv.visitMethodInsn(Opcodes.INVOKESTATIC, TRACEMAIN, START_METHOD, START_SIGNATURE, false); + } + } + mv.visitInsn(opcode); + } +} diff --git a/scouter.agent.java/src/main/java/scouter/agent/asm/util/HookingSet.java b/scouter.agent.java/src/main/java/scouter/agent/asm/util/HookingSet.java index 654b8f6a3..b06e963ad 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/asm/util/HookingSet.java +++ b/scouter.agent.java/src/main/java/scouter/agent/asm/util/HookingSet.java @@ -218,7 +218,6 @@ public static String classPattrensToMethodPatterns(String classPatterns, String String s = classes[i]; classMethodPatterns.add(s + "." + method); } - return buildPatterns("", classMethodPatterns); } } diff --git a/scouter.agent.java/src/main/java/scouter/agent/counter/task/HeapUsage.java b/scouter.agent.java/src/main/java/scouter/agent/counter/task/HeapUsage.java index 970ebf5a5..2e08a0daf 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/counter/task/HeapUsage.java +++ b/scouter.agent.java/src/main/java/scouter/agent/counter/task/HeapUsage.java @@ -34,21 +34,25 @@ public class HeapUsage { public void getHeapUsage(CounterBasket pw) { long total = Runtime.getRuntime().totalMemory(); long free = Runtime.getRuntime().freeMemory(); - float used = (float) ((total - free) / 1024. / 1024.); + + float tatalMb = (float) (total / 1024. / 1024.); + float usedMb = (float) ((total - free) / 1024. / 1024.); heapmin.add(total - free); - float usedmin = (float) (heapmin.getAvg(300) / 1024. / 1024.); + float used5MinAvgMb = (float) (heapmin.getAvg(300) / 1024. / 1024.); ListValue heapValues = new ListValue(); - heapValues.add((float) (total / 1024. / 1024.)); - heapValues.add(used); + heapValues.add(tatalMb); + heapValues.add(usedMb); PerfCounterPack p = pw.getPack(TimeTypeEnum.REALTIME); p.put(CounterConstants.JAVA_HEAP_TOT_USAGE, heapValues); - p.put(CounterConstants.JAVA_HEAP_USED, new FloatValue(used)); + p.put(CounterConstants.JAVA_HEAP_USED, new FloatValue(usedMb)); + p.put(CounterConstants.JAVA_HEAP_TOTAL, new FloatValue(tatalMb)); p = pw.getPack(TimeTypeEnum.FIVE_MIN); - p.put(CounterConstants.JAVA_HEAP_USED, new FloatValue(usedmin)); + p.put(CounterConstants.JAVA_HEAP_USED, new FloatValue(used5MinAvgMb)); + p.put(CounterConstants.JAVA_HEAP_TOTAL, new FloatValue(tatalMb)); } -} \ No newline at end of file +} 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 d0a05dd35..c445ea7f7 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 @@ -39,6 +39,7 @@ import scouter.agent.wrapper.async.WrTask; import scouter.lang.AlertLevel; import scouter.lang.TextTypes; +import scouter.lang.enumeration.ParameterizedMessageLevel; import scouter.lang.pack.AlertPack; import scouter.lang.pack.XLogPack; import scouter.lang.pack.XLogTypes; @@ -47,9 +48,11 @@ import scouter.lang.step.MessageStep; import scouter.lang.step.MethodStep; import scouter.lang.step.MethodStep2; +import scouter.lang.step.ParameterizedMessageStep; import scouter.lang.step.ThreadCallPossibleStep; import scouter.lang.value.MapValue; import scouter.util.ArrayUtil; +import scouter.util.ByteArrayKeyLinkedMap; import scouter.util.HashUtil; import scouter.util.Hexa32; import scouter.util.IPUtil; @@ -99,7 +102,7 @@ public static Object startHttpService(Object req, Object res) { if (ctx != null) { return null; } - if(TraceContextManager.startForceDiscard()) { + if (TraceContextManager.startForceDiscard()) { return null; } return startHttp(req, res); @@ -115,7 +118,7 @@ public static Object startHttpFilter(Object req, Object res) { if (ctx != null) { return null; } - if(TraceContextManager.startForceDiscard()) { + if (TraceContextManager.startForceDiscard()) { return null; } return startHttp(req, res); @@ -127,20 +130,20 @@ public static Object startHttpFilter(Object req, Object res) { public static Object reject(Object stat, Object req, Object res) { Configure conf = Configure.getInstance(); - if(plController != null) { - if (stat == null || req == null || res == null) + if (plController != null) { + if (stat == null || req == null || res == null) return null; - if (http == null) { + if (http == null) { initHttp(req); } - Stat stat0 = (Stat) stat; + Stat stat0 = (Stat) stat; if (stat0.isStaticContents) { return null; } - if(plController.reject(stat0.ctx, req, res,http)) { - endHttpService(stat0, REJECT); - return REJECT; - } + if (plController.reject(stat0.ctx, req, res, http)) { + endHttpService(stat0, REJECT); + return REJECT; + } } if (conf.control_reject_service_enabled) { if (stat == null || req == null || res == null) @@ -151,8 +154,8 @@ public static Object reject(Object stat, Object req, Object res) { Stat stat0 = (Stat) stat; if (stat0.isStaticContents) return null; - - + + // reject by customized plugin if (PluginHttpServiceTrace.reject(stat0.ctx, req, res) // reject by control_reject_service_max_count @@ -252,8 +255,8 @@ private static Object startHttp(Object req, Object res) { PluginHttpServiceTrace.start(ctx, req, res); } - if(plController != null) { - plController.start(ctx, req, res); + if (plController != null) { + plController.start(ctx, req, res); } } return stat; @@ -268,7 +271,7 @@ private static void initHttp(Object req) { } public static void endHttpService(Object stat, Throwable thr) { - if(TraceContextManager.isForceDiscarded()) { + if (TraceContextManager.isForceDiscarded()) { TraceContextManager.clearForceDiscard(); return; } @@ -281,7 +284,7 @@ public static void endHttpService(Object stat, Throwable thr) { TraceContext ctx = stat0.ctx; //wait on async servlet completion - if(!ctx.asyncServletStarted) { + if (!ctx.asyncServletStarted) { endHttpServiceFinal(ctx, stat0.req, stat0.res, thr); } else { HashedMessageStep step = new HashedMessageStep(); @@ -301,7 +304,7 @@ public static void endHttpService(Object stat, Throwable thr) { } public static void endHttpServiceFinal(TraceContext ctx, Object request, Object response, Throwable thr) { - if(TraceContextManager.isForceDiscarded()) { + if (TraceContextManager.isForceDiscarded()) { TraceContextManager.clearForceDiscard(); return; } @@ -335,17 +338,17 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object if (ctx.xType != XLogTypes.ASYNCSERVLET_DISPATCHED_SERVICE) { PluginHttpServiceTrace.end(ctx, request, response); } - if(plController != null) { - plController.end(ctx, request, response); + if (plController != null) { + plController.end(ctx, request, response); } //profile rs - if(conf.trace_rs_leak_enabled && ctx.unclosedRsMap.size() > 0) { + if (conf.trace_rs_leak_enabled && ctx.unclosedRsMap.size() > 0) { MapValue mv = new MapValue(); mv.put(AlertPack.HASH_FLAG + TextTypes.SERVICE + "_service-name", ctx.serviceHash); - if(conf.profile_fullstack_rs_leak_enabled) { + if (conf.profile_fullstack_rs_leak_enabled) { String message = ctx.unclosedRsMap.values().nextElement(); - if(message != null) { + if (message != null) { message = "ResultSet Leak suspected!\n" + message; HashedMessageStep step = new HashedMessageStep(); step.hash = DataProxy.sendHashedMessage(message); @@ -358,13 +361,13 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object } //profile stmt - if(conf.trace_stmt_leak_enabled && ctx.unclosedStmtMap.size() > 0) { + if (conf.trace_stmt_leak_enabled && ctx.unclosedStmtMap.size() > 0) { MapValue mv = new MapValue(); mv.put(AlertPack.HASH_FLAG + TextTypes.SERVICE + "_service-name", ctx.serviceHash); - if(conf.profile_fullstack_stmt_leak_enabled) { + if (conf.profile_fullstack_stmt_leak_enabled) { String message = ctx.unclosedStmtMap.values().nextElement(); - if(message != null) { + if (message != null) { message = "Statement Leak suspected!\n" + message; HashedMessageStep step = new HashedMessageStep(); step.hash = DataProxy.sendHashedMessage(message); @@ -390,12 +393,12 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object pack.xType = ctx.xType; //default 0 : XLogType.WEB_SERVICE pack.txid = ctx.txid; pack.gxid = ctx.gxid; - if(ctx.latestCpu > 0) { + if (ctx.latestCpu > 0) { pack.cpu = (int) (ctx.latestCpu - ctx.startCpu); } else { pack.cpu = (int) (SysJMX.getCurrentThreadCPU() - ctx.startCpu); } - if(ctx.latestBytes > 0) { + if (ctx.latestBytes > 0) { pack.kbytes = (int) ((ctx.latestBytes - ctx.bytes) / 1024.0d); } else { pack.kbytes = (int) ((SysJMX.getCurrentThreadAllocBytes() - ctx.bytes) / 1024.0d); @@ -406,7 +409,7 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object pack.ipaddr = IPUtil.toBytes(ctx.remoteIp); pack.userid = ctx.userid; - if(ctx.hasDumpStack) { + if (ctx.hasDumpStack) { pack.hasDump = 1; } else { pack.hasDump = 0; @@ -427,7 +430,7 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object sb.append(emsg).append("\n"); ThreadUtil.getStackTrace(sb, thr, conf.profile_fullstack_max_lines); Throwable thrCause = thr.getCause(); - if(thrCause != null) { + if (thrCause != null) { thr = thrCause; while (thr != null) { sb.append("\nCause...\n"); @@ -440,13 +443,13 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object pack.error = DataProxy.sendError(emsg); ServiceSummary.getInstance().process(thr, pack.error, ctx.serviceHash, ctx.txid, 0, 0); } - } else if (ctx.userTransaction > 0 && conf.xlog_error_check_user_transaction_enabled) { + } else if (ctx.userTransaction > 0 && conf.xlog_error_check_user_transaction_enabled) { pack.error = DataProxy.sendError("UserTransaction missing commit/rollback Error"); ServiceSummary.getInstance().process(userTxNotClose, pack.error, ctx.serviceHash, ctx.txid, 0, 0); - } else if(conf.trace_rs_leak_enabled && ctx.unclosedRsMap.size() > 0) { + } else if (conf.trace_rs_leak_enabled && ctx.unclosedRsMap.size() > 0) { pack.error = DataProxy.sendError("ResultSet Leak suspected!"); ServiceSummary.getInstance().process(resultSetLeakSuspect, pack.error, ctx.serviceHash, ctx.txid, 0, 0); - } else if(conf.trace_stmt_leak_enabled && ctx.unclosedStmtMap.size() > 0) { + } else if (conf.trace_stmt_leak_enabled && ctx.unclosedStmtMap.size() > 0) { pack.error = DataProxy.sendError("Statement Leak suspected!"); ServiceSummary.getInstance().process(statementLeakSuspect, pack.error, ctx.serviceHash, ctx.txid, 0, 0); } @@ -454,14 +457,14 @@ public static void endHttpServiceFinal(TraceContext ctx, Object request, Object //check xlog sampling XLogDiscard discardMode = pack.error != 0 ? XLogDiscard.NONE : XLogSampler.getInstance().evaluateXLogDiscard(pack.elapsed, ctx.serviceName); //check xlog discard pattern - if(XLogSampler.getInstance().isDiscardServicePattern(ctx.serviceName)) { + if (XLogSampler.getInstance().isDiscardServicePattern(ctx.serviceName)) { discardMode = XLogDiscard.DISCARD_ALL; if (pack.error != 0 && conf.xlog_discard_service_show_error) { discardMode = XLogDiscard.NONE; } } - ctx.profile.close(discardMode==XLogDiscard.NONE ? true : false); + ctx.profile.close(discardMode == XLogDiscard.NONE ? true : false); if (ctx.group != null) { pack.group = DataProxy.sendGroup(ctx.group); } @@ -581,7 +584,7 @@ public static Object startService(String name, String className, String methodNa if (ctx != null) { return null; } - if(TraceContextManager.startForceDiscard()) { + if (TraceContextManager.startForceDiscard()) { return null; } @@ -674,7 +677,7 @@ public static void endService(Object stat, Object returnValue, Throwable thr) { //check xlog sampling XLogDiscard discardMode = pack.error != 0 ? XLogDiscard.NONE : XLogSampler.getInstance().evaluateXLogDiscard(pack.elapsed, ctx.serviceName); - ctx.profile.close(discardMode==XLogDiscard.NONE ? true : false); + ctx.profile.close(discardMode == XLogDiscard.NONE ? true : false); DataProxy.sendServiceName(ctx.serviceHash, ctx.serviceName); pack.service = ctx.serviceHash; pack.threadNameHash = DataProxy.sendHashedMessage(ctx.threadName); @@ -731,7 +734,7 @@ private static int errorCheck(TraceContext ctx, Throwable thr) { sb.append(emsg).append("\n"); ThreadUtil.getStackTrace(sb, thr, conf.profile_fullstack_max_lines); Throwable thrCause = thr.getCause(); - if(thrCause != null) { + if (thrCause != null) { thr = thrCause; while (thr != null) { sb.append("\nCause...\n"); @@ -743,7 +746,7 @@ private static int errorCheck(TraceContext ctx, Throwable thr) { } error = DataProxy.sendError(emsg); ServiceSummary.getInstance().process(thr, error, ctx.serviceHash, ctx.txid, 0, 0); - } else if (ctx.userTransaction > 0 && conf.xlog_error_check_user_transaction_enabled) { + } else if (ctx.userTransaction > 0 && conf.xlog_error_check_user_transaction_enabled) { error = DataProxy.sendError("Missing Commit/Rollback Error"); ServiceSummary.getInstance().process(userTxNotClose, error, ctx.serviceHash, ctx.txid, 0, 0); } @@ -849,7 +852,7 @@ public static Object startMethod(int hash, String classMethod) { return null; } - if(TraceContextManager.isForceDiscarded()) { + if (TraceContextManager.isForceDiscarded()) { return null; } @@ -858,16 +861,16 @@ public static Object startMethod(int hash, String classMethod) { //System.out.println("[Scouter][HookMethodCtxNull]" + classMethod); if (conf._trace_auto_service_enabled) { Object localContext = startService(classMethod, null, null, null, null, null, XLogTypes.APP_SERVICE); - if (localContext != null) { - //service start - ((LocalContext) localContext).service = true; - if (conf._trace_auto_service_backstack_enabled) { - String stack = ThreadUtil.getStackTrace(Thread.currentThread().getStackTrace(), 2); - AutoServiceStartAnalyzer.put(classMethod, stack); - MessageStep m = new MessageStep(); - m.message = "SERVICE BACKSTACK:\n" + stack; - ((LocalContext) localContext).context.profile.add(m); - } + if (localContext != null) { + //service start + ((LocalContext) localContext).service = true; + if (conf._trace_auto_service_backstack_enabled) { + String stack = ThreadUtil.getStackTrace(Thread.currentThread().getStackTrace(), 2); + AutoServiceStartAnalyzer.put(classMethod, stack); + MessageStep m = new MessageStep(); + m.message = "SERVICE BACKSTACK:\n" + stack; + ((LocalContext) localContext).context.profile.add(m); + } } return localContext; } @@ -908,7 +911,7 @@ public static void setSpringControllerName(String name) { TraceContext ctx = TraceContextManager.getContext(); if (ctx == null || name == null) return; - if(!ctx.alreadySetControllerName) { + if (!ctx.alreadySetControllerName) { ctx.alreadySetControllerName = true; ctx.serviceName = name; ctx.serviceHash = HashUtil.hash(name); @@ -919,13 +922,13 @@ public static void startSpringControllerMethod(String className, String methodNa TraceContext ctx = TraceContextManager.getContext(); if (ctx == null) return; - if(conf.profile_spring_controller_method_parameter_enabled) { + if (conf.profile_spring_controller_method_parameter_enabled) { if (arg == null) { return; } int start_time = (int) (System.currentTimeMillis() - ctx.startTime); - for(int i=0; i caller txid : " + id.caller + "=" + Hexa32.toString32(id.caller) - + " ctx.txid : " + ctx.txid + "=" + Hexa32.toString32(ctx.txid) - + " id.callee : " + id.callee + "=" + Hexa32.toString32(id.callee) + + " ctx.txid : " + ctx.txid + "=" + Hexa32.toString32(ctx.txid) + + " id.callee : " + id.callee + "=" + Hexa32.toString32(id.callee) + " id.thread : " + id.callerThreadId + " current.thread : " + Thread.currentThread().getName() + "=" + Thread.currentThread().getId()); return null; } } - LocalContext localContext = (LocalContext)startService(fullName, className, methodName, methodDesc, _this, arg, XLogTypes.BACK_THREAD2); + LocalContext localContext = (LocalContext) startService(fullName, className, methodName, methodDesc, _this, arg, XLogTypes.BACK_THREAD2); if (localContext == null) { return null; } localContext.service = true; - if(id.gxid != 0) localContext.context.gxid = id.gxid; - if(id.callee != 0) localContext.context.txid = id.callee; - if(id.caller != 0) localContext.context.caller = id.caller; + if (id.gxid != 0) localContext.context.gxid = id.gxid; + if (id.callee != 0) localContext.context.txid = id.callee; + if (id.caller != 0) localContext.context.caller = id.caller; String serviceName = StringUtil.removeLastString(className, '/') + "#" + methodName + "() -- " + fullName; serviceName = serviceName.replace("$ByteBuddy", ""); @@ -1110,7 +1113,7 @@ public static Object startAsyncPossibleService(Object keyObject, String fullName localContext.context.serviceHash = HashUtil.hash(serviceName); localContext.context.serviceName = serviceName; - if(id.tcStep != null) { + if (id.tcStep != null) { id.tcStep.threaded = 1; id.tcStep.hash = DataProxy.sendApicall(serviceName); } @@ -1139,9 +1142,9 @@ public static void endAsyncPossibleService(Object oRtn, Object oLocalContext, Th public static void springAsyncExecutionSubmit(Object _this, Callable callable) { try { TraceContext ctx = TraceContextManager.getContext(); - if(ctx == null) return; + if (ctx == null) return; - if(TransferMap.get(System.identityHashCode(callable)) != null) { + if (TransferMap.get(System.identityHashCode(callable)) != null) { return; } @@ -1170,16 +1173,16 @@ public static void springAsyncExecutionSubmit(Object _this, Callable callable) { public static void springAsyncDetermineExecutor(Method m) { TraceContext ctx = TraceContextManager.getContext(); - if(ctx == null) return; - if(m == null) return; + if (ctx == null) return; + if (m == null) return; ctx.lastThreadCallName = m.getDeclaringClass().getName() + "#" + m.getName() + "()"; } public static void executorServiceSubmitted(Object callRunnable) { TraceContext ctx = TraceContextManager.getContext(); - if(ctx == null) return; - if(callRunnable == null) return; + if (ctx == null) return; + if (callRunnable == null) return; ctx.lastThreadCallName = callRunnable.getClass().getName(); } @@ -1187,10 +1190,10 @@ public static void executorServiceSubmitted(Object callRunnable) { public static void executorServiceExecuted(Object callRunnable) { try { TraceContext ctx = TraceContextManager.getContext(); - if(ctx == null) return; - if(callRunnable == null) return; + if (ctx == null) return; + if (callRunnable == null) return; - if(TransferMap.get(System.identityHashCode(callRunnable)) != null) { + if (TransferMap.get(System.identityHashCode(callRunnable)) != null) { return; } @@ -1238,32 +1241,32 @@ public static Object callRunnableCallInvoked(Object callRunnableObj) { return null; } - if(ctx != null) { - if(ctx.txid == id.caller) { + if (ctx != null) { + if (ctx.txid == id.caller) { return null; } else { Logger.trace("B110 - recevieAsyncPossibleStep -> caller txid : " + id.caller + "=" + Hexa32.toString32(id.caller) - + " ctx.txid : " + ctx.txid + "=" + Hexa32.toString32(ctx.txid) - + " id.callee : " + id.callee + "=" + Hexa32.toString32(id.callee) + + " ctx.txid : " + ctx.txid + "=" + Hexa32.toString32(ctx.txid) + + " id.callee : " + id.callee + "=" + Hexa32.toString32(id.callee) + " id.thread : " + id.callerThreadId + " current.thread : " + Thread.currentThread().getName() + "=" + Thread.currentThread().getId()); return null; } } - if(id.tcStep != null) { + if (id.tcStep != null) { id.tcStep.threaded = 1; } - LocalContext localContext = (LocalContext)startService(id.tcStep.nameTemp, null, null, null, null, null, XLogTypes.BACK_THREAD2); + LocalContext localContext = (LocalContext) startService(id.tcStep.nameTemp, null, null, null, null, null, XLogTypes.BACK_THREAD2); if (localContext == null) { return null; } localContext.service = true; - if(id.gxid != 0) localContext.context.gxid = id.gxid; - if(id.callee != 0) localContext.context.txid = id.callee; - if(id.caller != 0) localContext.context.caller = id.caller; + if (id.gxid != 0) localContext.context.gxid = id.gxid; + if (id.callee != 0) localContext.context.txid = id.callee; + if (id.caller != 0) localContext.context.caller = id.caller; return localContext; } catch (Throwable t) { @@ -1280,9 +1283,9 @@ public static void callRunnableCallEnd(Object oRtn, Object oLocalContext, Throwa public static void hystrixPrepareInvoked(Object hystrixCommand) { TraceContext ctx = TraceContextManager.getContext(); - if(ctx == null) return; + if (ctx == null) return; - if(TransferMap.get(System.identityHashCode(hystrixCommand)) != null) { + if (TransferMap.get(System.identityHashCode(hystrixCommand)) != null) { return; } Class clazz = hystrixCommand.getClass(); @@ -1318,9 +1321,9 @@ public static void hystrixPrepareInvoked(Object hystrixCommand) { public static void callRunnableInitInvoked(Object callRunnableObj) { try { TraceContext ctx = TraceContextManager.getContext(); - if(ctx == null) return; + if (ctx == null) return; - if(TransferMap.get(System.identityHashCode(callRunnableObj)) != null) { + if (TransferMap.get(System.identityHashCode(callRunnableObj)) != null) { return; } @@ -1351,13 +1354,13 @@ public static void endExceptionConstructor(String className, String methodDesc, TraceContext ctx = TraceContextManager.getContext(); if (ctx == null) return; - if(!(this0 instanceof Throwable)) { + if (!(this0 instanceof Throwable)) { return; } if (ctx.error != 0) { return; } - Throwable t = (Throwable)this0; + Throwable t = (Throwable) this0; String msg = t.getMessage(); if (msg == null) { @@ -1384,7 +1387,7 @@ public static void endExceptionConstructor(String className, String methodDesc, public static StringBuilder appendParentClassName(Class clazz, StringBuilder sb) { Class superClazz = clazz.getSuperclass(); - if(superClazz != null) { + if (superClazz != null) { sb.append(",").append(superClazz.getName()); return appendParentClassName(superClazz, sb); } else { @@ -1393,7 +1396,7 @@ public static StringBuilder appendParentClassName(Class clazz, StringBuilder sb) } public static String buildClassHierarchyConcatString(Class clazz) { - if(clazz == null) return null; + if (clazz == null) return null; StringBuilder sb = new StringBuilder(clazz.getName()); appendParentClassName(clazz, sb); return sb.toString(); @@ -1406,9 +1409,9 @@ public static void startExceptionHandler(String className, String methodName, St if (args == null || args.length == 0) return; Throwable t = null; - for(int i=0; i redisKeyMap =new ByteArrayKeyLinkedMap().setMax(100); + private static String JEDIS_COMMAND_MSG = "[REDIS]%s: %s"; + private static String JEDIS_COMMAND_ERROR_MSG = "[REDIS][ERROR]%s: %s [Exception:%s] %s"; + + public static void setRedisKey(byte[] barr, Object key) { + redisKeyMap.put(barr, key.toString()); + } + + public static Object startSendRedisCommand() { + if (TraceContextManager.isForceDiscarded()) { + return null; + } + + TraceContext ctx = TraceContextManager.getContext(); + if (ctx == null) { + return null; + } + + ParameterizedMessageStep step = new ParameterizedMessageStep(); + step.start_time = (int) (System.currentTimeMillis() - ctx.startTime); + ctx.profile.push(step); + + return new LocalContext(ctx, step); + } + + public static void endSendRedisCommand(byte[] cmd, byte[][] args, Object localContext, Throwable thr) { + if (localContext == null) + return; + + LocalContext lctx = (LocalContext) localContext; + + ParameterizedMessageStep step = (ParameterizedMessageStep) lctx.stepSingle; + if (step == null) return; + + TraceContext tctx = lctx.context; + if (tctx == null) return; + + String key = null; + if (args.length > 0) { + key = redisKeyMap.get(args[0]); + } + if (key == null) { + if (conf.profile_redis_key_forcibly_stringify_enabled) { + if (args.length > 0) { + key = new String(args[0]); + } else { + key = "EMPTY"; + } + } else { + key = "-"; + } + } + String command = new String(cmd); + + + step.setElapsed((int) (System.currentTimeMillis() - tctx.startTime) - step.start_time); + if (thr == null) { + step.setMessage(DataProxy.sendHashedMessage(JEDIS_COMMAND_MSG), command, key); + step.setLevel(ParameterizedMessageLevel.INFO); + tctx.profile.pop(step); + } else { + String msg = thr.toString(); + step.setMessage(DataProxy.sendHashedMessage(JEDIS_COMMAND_ERROR_MSG), command, key, thr.getClass().getName(), msg); + step.setLevel(ParameterizedMessageLevel.ERROR); + tctx.profile.pop(step); + + if (tctx.error == 0 && conf.xlog_error_on_redis_exception_enabled) { + if (conf.profile_fullstack_redis_error_enabled) { + StringBuffer sb = new StringBuffer(); + sb.append(msg).append("\n"); + ThreadUtil.getStackTrace(sb, thr, conf.profile_fullstack_max_lines); + thr = thr.getCause(); + while (thr != null) { + sb.append("\nCause...\n"); + ThreadUtil.getStackTrace(sb, thr, conf.profile_fullstack_max_lines); + thr = thr.getCause(); + } + msg = sb.toString(); + } + + int hash = DataProxy.sendError(msg); + tctx.error = hash; + } + } + } + + public static void endSendRedisCommand(Object localContext, Throwable thr) { + System.out.println(localContext); + } } diff --git a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceSQL.java b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceSQL.java index 6ac0e28af..9baa55d36 100644 --- a/scouter.agent.java/src/main/java/scouter/agent/trace/TraceSQL.java +++ b/scouter.agent.java/src/main/java/scouter/agent/trace/TraceSQL.java @@ -530,6 +530,14 @@ public static void driverConnect(String url, Throwable thr) { } } + public static Connection getConnection(Connection conn) { + if (conn == null) + return conn; + if (conn instanceof WrConnection) + return conn; + return new WrConnection(conn); + } + public static void userTxOpen() { TraceContext ctx = TraceContextManager.getContext(); if (ctx == null) diff --git a/scouter.client/icons/filter-old.png b/scouter.client/icons/filter-old.png new file mode 100644 index 000000000..03ddd799f Binary files /dev/null and b/scouter.client/icons/filter-old.png differ diff --git a/scouter.client/icons/filter.png b/scouter.client/icons/filter.png old mode 100644 new mode 100755 index 03ddd799f..cfc2702ac Binary files a/scouter.client/icons/filter.png and b/scouter.client/icons/filter.png differ diff --git a/scouter.client/icons/search.png b/scouter.client/icons/search.png new file mode 100755 index 000000000..2f193889f Binary files /dev/null and b/scouter.client/icons/search.png differ diff --git a/scouter.client/src/scouter/client/Images.java b/scouter.client/src/scouter/client/Images.java index b5ddfd7d3..380bcf85b 100644 --- a/scouter.client/src/scouter/client/Images.java +++ b/scouter.client/src/scouter/client/Images.java @@ -63,6 +63,7 @@ public class Images { public static final Image add = Activator.getImage("icons/add.gif"); public static final Image filter = Activator.getImage("icons/filter.png"); + public static final Image search = Activator.getImage("icons/search.png"); public static final Image monitor = Activator.getImage("icons/monitor.png"); diff --git a/scouter.client/src/scouter/client/util/ZipUtil.java b/scouter.client/src/scouter/client/util/ZipUtil.java index d01422360..f9e037e0e 100644 --- a/scouter.client/src/scouter/client/util/ZipUtil.java +++ b/scouter.client/src/scouter/client/util/ZipUtil.java @@ -85,7 +85,7 @@ private static void compressZip(File file, String root, ZipOutputStream zos) thr FileInputStream fis = null; try { String zipName = file.getPath().replace(root + "\\", ""); - zipName = file.getPath().replace(root + "/", ""); + zipName = zipName.replace(root + "/", ""); System.out.println("zipname:" + zipName); // 파일을 읽어드림 diff --git a/scouter.client/src/scouter/client/xlog/ProfileText.java b/scouter.client/src/scouter/client/xlog/ProfileText.java index d94e2155b..20b333684 100644 --- a/scouter.client/src/scouter/client/xlog/ProfileText.java +++ b/scouter.client/src/scouter/client/xlog/ProfileText.java @@ -838,10 +838,10 @@ public static void toString(StringBuffer sb, ParameterizedMessageStep pmStep) { message = pmStep.buildMessasge(messageFormat); } + sb.append(message); if(pmStep.getElapsed() != -1) { - sb.append("[").append(FormatUtil.print(pmStep.getElapsed(), "#,##0")).append(" ms] "); + sb.append(" [").append(FormatUtil.print(pmStep.getElapsed(), "#,##0")).append(" ms]"); } - sb.append(message); } public static void toString(StringBuffer sb, DumpStep p, int lineHead) { diff --git a/scouter.client/src/scouter/client/xlog/XLogFilterStatus.java b/scouter.client/src/scouter/client/xlog/XLogFilterStatus.java index 214cf464c..41fffa728 100644 --- a/scouter.client/src/scouter/client/xlog/XLogFilterStatus.java +++ b/scouter.client/src/scouter/client/xlog/XLogFilterStatus.java @@ -7,6 +7,8 @@ public class XLogFilterStatus { public String objName = ""; public String service = ""; public String ip = ""; + public String startHmsFrom = ""; + public String startHmsTo = ""; public String login = ""; public String desc = ""; public String text1 = ""; @@ -24,6 +26,8 @@ public int hashCode() { int filter_hash = HashUtil.hash(objName); filter_hash ^= HashUtil.hash(service); filter_hash ^= HashUtil.hash(ip); + filter_hash ^= HashUtil.hash(startHmsFrom); + filter_hash ^= HashUtil.hash(startHmsTo); filter_hash ^= HashUtil.hash(login); filter_hash ^= HashUtil.hash(desc); filter_hash ^= HashUtil.hash(text1); @@ -43,6 +47,8 @@ public XLogFilterStatus clone() { status.objName = objName; status.service = service; status.ip = ip; + status.startHmsFrom = startHmsFrom; + status.startHmsTo = startHmsTo; status.login = login; status.desc = desc; status.text1 = text1; diff --git a/scouter.client/src/scouter/client/xlog/dialog/XLogFilterDialog.java b/scouter.client/src/scouter/client/xlog/dialog/XLogFilterDialog.java index a47f64c12..9f841f4b2 100644 --- a/scouter.client/src/scouter/client/xlog/dialog/XLogFilterDialog.java +++ b/scouter.client/src/scouter/client/xlog/dialog/XLogFilterDialog.java @@ -23,7 +23,9 @@ import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.VerifyListener; import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.RowData; @@ -38,11 +40,16 @@ import org.eclipse.swt.widgets.Text; import scouter.client.xlog.XLogFilterStatus; import scouter.client.xlog.views.XLogViewCommon; +import scouter.util.StringUtil; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; public class XLogFilterDialog extends Dialog { Combo objCombo; - Text serviceTxt, ipTxt, userAgentTxt, loginText, descText, text1Text, text2Text, text3Text, text4Text, text5Text; + Text serviceTxt, ipTxt, startHmsFromTxt, startHmsToTxt, userAgentTxt, loginText, descText, text1Text, text2Text, text3Text, text4Text, text5Text; Button onlySqlBtn, onlyApiBtn, onlyErrorBtn; Button clearBtn, applyBtn; @@ -111,6 +118,41 @@ public void modifyText(ModifyEvent arg0) { } }); + label = new Label(filterGrp, SWT.NONE); + label.setText("StartHMS"); + label.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, false)); + + Composite startTimeComposite = new Composite(filterGrp, SWT.NONE); + startTimeComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + FillLayout filllayout = new FillLayout(); + filllayout.marginWidth = 0; + filllayout.marginHeight = 0; + startTimeComposite.setLayout(filllayout); + + + startHmsFromTxt = new Text(startTimeComposite, SWT.BORDER | SWT.SINGLE); + startHmsFromTxt.setTextLimit(6); + startHmsFromTxt.setText(status.startHmsFrom); + startHmsFromTxt.addVerifyListener(hhmmssListener); + startHmsFromTxt.addModifyListener(arg0 -> { + newStatus.startHmsFrom = startHmsFromTxt.getText(); + compareHash(); + }); + + + label = new Label(startTimeComposite, SWT.CENTER); + label.setText(" ~ "); + + startHmsToTxt = new Text(startTimeComposite, SWT.BORDER | SWT.SINGLE); + startHmsToTxt.setTextLimit(6); + startHmsToTxt.setText(status.startHmsTo); + startHmsToTxt.addVerifyListener(hhmmssListener); + startHmsToTxt.addModifyListener(arg0 -> { + newStatus.startHmsTo = startHmsToTxt.getText(); + compareHash(); + }); + label = new Label(filterGrp, SWT.NONE); label.setText("LOGIN"); label.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, false)); @@ -266,6 +308,8 @@ public void widgetSelected(SelectionEvent e) { objCombo.setText(""); serviceTxt.setText(""); ipTxt.setText(""); + startHmsFromTxt.setText(""); + startHmsToTxt.setText(""); loginText.setText(""); descText.setText(""); text1Text.setText(""); @@ -305,11 +349,12 @@ private void compareHash() { } } - - @Override protected void okPressed() { if (newStatus.hashCode() != filterHash) { + view.setFilter(newStatus); + filterHash = newStatus.hashCode(); + compareHash(); } super.okPressed(); } @@ -325,8 +370,26 @@ protected void configureShell(Shell newShell) { newShell.setText("XLog Filter"); } - @Override - protected boolean isResizable() { - return true; - } + VerifyListener hhmmssListener = e -> { + if (!StringUtil.isInteger(e.text) && !StringUtil.isEmpty(e.text)) { + e.doit = false; + return; + } + + Text text = (Text) e.getSource(); + final String prev = text.getText(); + String after = prev.substring(0, e.start) + e.text + prev.substring(e.end); + + for(int i = after.length(); i < 6; i++) { + after += '0'; + } + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HHmmss"); + try { + LocalTime.parse(after, formatter); + } catch (DateTimeParseException ignore) { + e.doit = false; + } + + }; } diff --git a/scouter.client/src/scouter/client/xlog/views/XLogRealTimeView.java b/scouter.client/src/scouter/client/xlog/views/XLogRealTimeView.java index 0cb5bcbcd..d16affac0 100644 --- a/scouter.client/src/scouter/client/xlog/views/XLogRealTimeView.java +++ b/scouter.client/src/scouter/client/xlog/views/XLogRealTimeView.java @@ -17,11 +17,10 @@ */ package scouter.client.xlog.views; -import java.io.IOException; - import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IStatusLineManager; import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.action.Separator; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; @@ -34,7 +33,6 @@ import org.eclipse.ui.IViewSite; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; - import scouter.client.Images; import scouter.client.model.AgentDailyListProxy; import scouter.client.model.AgentModelThread; @@ -66,6 +64,8 @@ import scouter.util.DateUtil; import scouter.util.StringUtil; +import java.io.IOException; + public class XLogRealTimeView extends XLogViewCommon implements Refreshable { @@ -93,7 +93,15 @@ public void createPartControl(final Composite parent) { IToolBarManager man = getViewSite().getActionBars().getToolBarManager(); create(parent, man); - + + Action searchOpenAction = new Action("search", ImageUtil.getImageDescriptor(Images.search)) { + public void run() { + new OpenSearchXLogDialogAction(PlatformUI.getWorkbench().getActiveWorkbenchWindow(), serverId, objType).run(); + } + }; + man.add(searchOpenAction); + man.add(new Separator()); + ; man.add(new Action("zoom in", ImageUtil.getImageDescriptor(Images.zoomin)) { public void run() { viewPainter.keyPressed(16777259); diff --git a/scouter.client/src/scouter/client/xlog/views/XLogViewCommon.java b/scouter.client/src/scouter/client/xlog/views/XLogViewCommon.java index 45c4ce5fa..b14a0121d 100644 --- a/scouter.client/src/scouter/client/xlog/views/XLogViewCommon.java +++ b/scouter.client/src/scouter/client/xlog/views/XLogViewCommon.java @@ -22,9 +22,22 @@ import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.Separator; import org.eclipse.swt.SWT; -import org.eclipse.swt.events.*; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.*; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.ToolTip; import org.eclipse.ui.part.ViewPart; import scouter.client.Images; import scouter.client.constants.HelpConstants; @@ -38,7 +51,12 @@ import scouter.client.util.ImageUtil; import scouter.client.xlog.XLogFilterStatus; import scouter.client.xlog.XLogYAxisEnum; -import scouter.client.xlog.dialog.*; +import scouter.client.xlog.dialog.XLogFilterDialog; +import scouter.client.xlog.dialog.XLogSummaryIPDialog; +import scouter.client.xlog.dialog.XLogSummaryRefererDialog; +import scouter.client.xlog.dialog.XLogSummaryServiceDialog; +import scouter.client.xlog.dialog.XLogSummaryUserAgentDialog; +import scouter.client.xlog.dialog.XLogSummaryUserDialog; import scouter.client.xlog.views.XLogViewPainter.ITimeChange; import scouter.util.LongKeyLinkedMap; @@ -79,6 +97,25 @@ abstract public class XLogViewCommon extends ViewPart implements ITimeChange, IO public void create(Composite parent, IToolBarManager man) { + + helpAction = new Action("help", ImageUtil.getImageDescriptor(Images.help)) { + public void run() { + org.eclipse.swt.program.Program.launch(HelpConstants.HELP_URL_XLOG_VIEW); + } + }; + man.add(helpAction); + + man.add(new Separator()); + + Action filterOpenAction = new Action("filter", ImageUtil.getImageDescriptor(Images.filter)) { + public void run() { + filterDialog.setStatus(filterStatus); + filterDialog.open(); + } + }; + man.add(filterOpenAction); + man.add(new Separator()); + onlySqlAction = new Action("Only SQL", IAction.AS_CHECK_BOX) { public void run() { filterStatus.onlySql = isChecked(); @@ -109,15 +146,8 @@ public void run() { onlyErrorAction.setImageDescriptor(ImageUtil.getImageDescriptor(Images.error)); man.add(onlyErrorAction); - helpAction = new Action("help", ImageUtil.getImageDescriptor(Images.help)) { - public void run() { - org.eclipse.swt.program.Program.launch(HelpConstants.HELP_URL_XLOG_VIEW); - } - }; - man.add(helpAction); - man.add(new Separator()); - + canvas = new Canvas(parent, SWT.DOUBLE_BUFFERED); canvas.setLayout(new GridLayout()); diff --git a/scouter.client/src/scouter/client/xlog/views/XLogViewPainter.java b/scouter.client/src/scouter/client/xlog/views/XLogViewPainter.java index 5ee944abc..997958f28 100644 --- a/scouter.client/src/scouter/client/xlog/views/XLogViewPainter.java +++ b/scouter.client/src/scouter/client/xlog/views/XLogViewPainter.java @@ -38,8 +38,17 @@ import scouter.client.xlog.XLogFilterStatus; import scouter.client.xlog.XLogYAxisEnum; import scouter.lang.pack.XLogPack; -import scouter.util.*; - +import scouter.util.DateUtil; +import scouter.util.FormatUtil; +import scouter.util.HashUtil; +import scouter.util.IPUtil; +import scouter.util.LongKeyLinkedMap; +import scouter.util.Pair; +import scouter.util.StrMatch; +import scouter.util.StringUtil; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.Enumeration; @@ -74,6 +83,7 @@ public class XLogViewPainter { public StrMatch objNameMat; public StrMatch serviceMat; public StrMatch ipMat; + public Pair startFromToMat; public StrMatch loginMat; public StrMatch descMat; public StrMatch text1Mat; @@ -163,10 +173,10 @@ public void build() { onGoing = false; } } - + int chart_x; long paintedEndTime; - + public long getLastTime() { return paintedEndTime; } @@ -614,6 +624,7 @@ public boolean isFilterOk(XLogData d) { return isObjNameFilterOk(d) && isServiceFilterOk(d) && isIpFilterOk(d.p) + && isStartTimeFilterOk(d.p) && isLoginFilterOk(d) && isDescFilterOk(d) && isText1FilterOk(d) @@ -660,6 +671,14 @@ public boolean isIpFilterOk(XLogPack p) { return ipMat.include(value); } + public boolean isStartTimeFilterOk(XLogPack p) { + if (StringUtil.isEmpty(filterStatus.startHmsFrom) || StringUtil.isEmpty(filterStatus.startHmsTo)) { + return true; + } + long start = p.endTime - p.elapsed; + return startFromToMat.getLeft() <= start && start <= startFromToMat.getRight(); + } + public boolean isLoginFilterOk(XLogData d) { if (StringUtil.isEmpty(filterStatus.login)) { return true; @@ -761,13 +780,15 @@ public void setYAxisMode(XLogYAxisEnum yAxis) { this.yValueMax = yAxis.getDefaultMax(); this.yValueMin = 0; } - + + private DateTimeFormatter hmsFormatter = DateTimeFormatter.ofPattern("HHmmss"); public void setFilterStatus(XLogFilterStatus status) { this.filterStatus = status; filter_hash = filterStatus.hashCode(); objNameMat = new StrMatch(status.objName); serviceMat = new StrMatch(status.service); ipMat = new StrMatch(status.ip); + loginMat = new StrMatch(status.login); text1Mat = new StrMatch(status.text1); text2Mat = new StrMatch(status.text2); @@ -776,5 +797,11 @@ public void setFilterStatus(XLogFilterStatus status) { text5Mat = new StrMatch(status.text5); descMat = new StrMatch(status.desc); userAgentMat = new StrMatch(status.userAgent); + + long dateMillis = DateUtil.dateUnitToTimeMillis(DateUtil.getDateUnit(paintedEndTime)); + long startFrom = dateMillis + LocalTime.parse(status.startHmsFrom, hmsFormatter).toSecondOfDay() * 1000; + long startTo = dateMillis + LocalTime.parse(status.startHmsTo, hmsFormatter).toSecondOfDay() * 1000; + + startFromToMat = new Pair<>(startFrom, startTo); } } diff --git a/scouter.common/pom.xml b/scouter.common/pom.xml index 6e668def1..af6adf623 100644 --- a/scouter.common/pom.xml +++ b/scouter.common/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.3 + 1.8.4 scouter-common 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 fc47750e1..fd2da7e59 100644 --- a/scouter.common/src/main/java/scouter/lang/counters/CounterConstants.java +++ b/scouter.common/src/main/java/scouter/lang/counters/CounterConstants.java @@ -71,6 +71,7 @@ public class CounterConstants { public final static String JAVA_GC_TIME = "GcTime"; public final static String JAVA_HEAP_TOT_USAGE = "HeapTotUsage"; public final static String JAVA_HEAP_USED = "HeapUsed"; + public final static String JAVA_HEAP_TOTAL = "HeapTotal"; public final static String JAVA_CPU_TIME = "CpuTime"; public final static String JAVA_PERM_USED = "PermUsed"; public final static String JAVA_PERM_PERCENT = "PermPercent"; diff --git a/scouter.common/src/main/java/scouter/net/RequestCmd.java b/scouter.common/src/main/java/scouter/net/RequestCmd.java index 135b77a4e..907ee6738 100644 --- a/scouter.common/src/main/java/scouter/net/RequestCmd.java +++ b/scouter.common/src/main/java/scouter/net/RequestCmd.java @@ -104,6 +104,7 @@ public class RequestCmd { public static final String TRANX_LOAD_TIME = "TRANX_LOAD_TIME"; public static final String XLOG_READ_BY_TXID = "XLOG_READ_BY_TXID"; public static final String XLOG_READ_BY_GXID = "XLOG_READ_BY_GXID"; + public static final String XLOG_LOAD_BY_TXIDS = "XLOG_LOAD_BY_TXIDS"; public static final String XLOG_LOAD_BY_GXID = "XLOG_LOAD_BY_GXID"; public static final String TRANX_PROFILE = "TRANX_PROFILE"; public static final String TRANX_PROFILE_FULL = "TRANX_PROFILE_FULL"; diff --git a/scouter.common/src/main/java/scouter/util/ByteArrayEnumer.java b/scouter.common/src/main/java/scouter/util/ByteArrayEnumer.java new file mode 100644 index 000000000..8cd20b859 --- /dev/null +++ b/scouter.common/src/main/java/scouter/util/ByteArrayEnumer.java @@ -0,0 +1,22 @@ +/* + * 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.util; +public interface ByteArrayEnumer { + public boolean hasMoreElements(); + public byte[] nextKey(); +} diff --git a/scouter.common/src/main/java/scouter/util/ByteArrayKeyLinkedMap.java b/scouter.common/src/main/java/scouter/util/ByteArrayKeyLinkedMap.java new file mode 100644 index 000000000..9ab29879e --- /dev/null +++ b/scouter.common/src/main/java/scouter/util/ByteArrayKeyLinkedMap.java @@ -0,0 +1,521 @@ +/* + * 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.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.NoSuchElementException; + +/** + * @author Gun Lee (gunlee01@gmail.com) + * + */ +public class ByteArrayKeyLinkedMap { + private static final int DEFAULT_CAPACITY = 101; + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + private ByteArrayKeyLinkedEntry table[]; + private ByteArrayKeyLinkedEntry header; + private int count; + private int threshold; + private float loadFactor; + + public ByteArrayKeyLinkedMap(int initCapacity, float loadFactor) { + if (initCapacity < 0) + throw new RuntimeException("Capacity Error: " + initCapacity); + if (loadFactor <= 0) + throw new RuntimeException("Load Count Error: " + loadFactor); + if (initCapacity == 0) + initCapacity = 1; + this.loadFactor = loadFactor; + this.table = new ByteArrayKeyLinkedEntry[initCapacity]; + this.header = new ByteArrayKeyLinkedEntry(new byte[0], null, null); + this.header.link_next = header.link_prev = header; + threshold = (int) (initCapacity * loadFactor); + } + + public ByteArrayKeyLinkedMap() { + this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); + } + + public int size() { + return count; + } + + public byte[][] keyArray() { + byte[][] _keys = new byte[this.size()][]; + ByteArrayEnumer en = this.keys(); + for (int i = 0; i < _keys.length; i++) + _keys[i] = en.nextKey(); + return _keys; + } + + public synchronized ByteArrayEnumer keys() { + return new Enumer(TYPE.KEYS); + } + + public synchronized Enumeration values() { + return new Enumer(TYPE.VALUES); + } + + public synchronized Enumeration> entries() { + return new Enumer(TYPE.ENTRIES); + } + + public synchronized boolean containsValue(Object value) { + if (value == null) { + throw new NullPointerException(); + } + ByteArrayKeyLinkedEntry tab[] = table; + int i = tab.length; + while (i-- > 0) { + for (ByteArrayKeyLinkedEntry e = tab[i]; e != null; e = e.next) { + if (CompareUtil.equals(e.value, value)) { + return true; + } + } + } + return false; + } + + public synchronized boolean containsKey(byte[] key) { + ByteArrayKeyLinkedEntry tab[] = table; + int index = hash(key) % tab.length; + for (ByteArrayKeyLinkedEntry e = tab[index]; e != null; e = e.next) { + if (CompareUtil.equals(e.key, key)) { + return true; + } + } + return false; + } + + public synchronized V get(byte[] key) { + ByteArrayKeyLinkedEntry tab[] = table; + int index = hash(key) % tab.length; + for (ByteArrayKeyLinkedEntry e = tab[index]; e != null; e = e.next) { + if (CompareUtil.equals(e.key, key)) { + return e.value; + } + } + return null; + } + + public synchronized byte[] getFirstKey() { + return this.header.link_next.key; + } + + public synchronized byte[] getLastKey() { + return this.header.link_prev.key; + } + + public synchronized V getFirstValue() { + return this.header.link_next.value; + } + + public synchronized V getLastValue() { + return this.header.link_prev.value; + } + + protected void overflowed(byte[] key, V value) { + } + + protected V create(byte[] key) { + throw new RuntimeException("not implemented create()"); + } + + public V intern(byte[] key) { + return _intern(key, MODE.LAST); + } + + private synchronized V _intern(byte[] key, MODE m) { + ByteArrayKeyLinkedEntry tab[] = table; + int index = hash(key) % tab.length; + for (ByteArrayKeyLinkedEntry e = tab[index]; e != null; e = e.next) { + if (CompareUtil.equals(e.key, key)) { + return e.value; + } + } + V value = create(key); + if (value == null) + return null; + + if (max > 0) { + switch (m) { + case FORCE_FIRST: + case FIRST: + while (count >= max) { + byte[] k = header.link_prev.key; + V v = remove(k); + overflowed(k, v); + } + break; + case FORCE_LAST: + case LAST: + while (count >= max) { + byte[] k = header.link_next.key; + V v = remove(k); + overflowed(k, v); + } + break; + } + } + if (count >= threshold) { + rehash(); + tab = table; + index = hash(key) % tab.length; + } + ByteArrayKeyLinkedEntry e = new ByteArrayKeyLinkedEntry(key, value, tab[index]); + tab[index] = e; + switch (m) { + case FORCE_FIRST: + case FIRST: + chain(header, header.link_next, e); + break; + case FORCE_LAST: + case LAST: + chain(header.link_prev, header, e); + break; + } + count++; + return value; + } + + private int hash(byte[] key) { + return Arrays.hashCode(key) & Integer.MAX_VALUE; + } + + protected void rehash() { + int oldCapacity = table.length; + ByteArrayKeyLinkedEntry oldMap[] = table; + int newCapacity = oldCapacity * 2 + 1; + ByteArrayKeyLinkedEntry newMap[] = new ByteArrayKeyLinkedEntry[newCapacity]; + threshold = (int) (newCapacity * loadFactor); + table = newMap; + for (int i = oldCapacity; i-- > 0;) { + ByteArrayKeyLinkedEntry old = oldMap[i]; + while (old != null) { + ByteArrayKeyLinkedEntry e = old; + old = old.next; + byte[] key = e.key; + int index = hash(key) % newCapacity; + e.next = newMap[index]; + newMap[index] = e; + } + } + } + + private int max; + + public ByteArrayKeyLinkedMap setMax(int max) { + this.max = max; + return this; + } + + private static enum MODE { + FORCE_FIRST, FORCE_LAST, FIRST, LAST + }; + + public V put(byte[] key, V value) { + return _put(key, value, MODE.LAST); + } + + public V putLast(byte[] key, V value) { + return _put(key, value, MODE.FORCE_LAST); + } + + public V putFirst(byte[] key, V value) { + return _put(key, value, MODE.FORCE_FIRST); + } + + private synchronized V _put(byte[] key, V value, MODE m) { + ByteArrayKeyLinkedEntry tab[] = table; + int index = hash(key) % tab.length; + for (ByteArrayKeyLinkedEntry e = tab[index]; e != null; e = e.next) { + if (CompareUtil.equals(e.key, key)) { + V old = e.value; + e.value = value; + switch (m) { + case FORCE_FIRST: + if (header.link_next != e) { + unchain(e); + chain(header, header.link_next, e); + } + break; + case FORCE_LAST: + if (header.link_prev != e) { + unchain(e); + chain(header.link_prev, header, e); + } + break; + } + return old; + } + } + if (max > 0) { + switch (m) { + case FORCE_FIRST: + case FIRST: + while (count >= max) { + // removeLast(); + byte[] k = header.link_prev.key; + V v = remove(k); + overflowed(k, v); + } + break; + case FORCE_LAST: + case LAST: + while (count >= max) { + // removeFirst(); + byte[] k = header.link_next.key; + V v = remove(k); + overflowed(k, v); + + } + break; + } + } + if (count >= threshold) { + rehash(); + tab = table; + index = hash(key) % tab.length; + } + ByteArrayKeyLinkedEntry e = new ByteArrayKeyLinkedEntry(key, value, tab[index]); + tab[index] = e; + switch (m) { + case FORCE_FIRST: + case FIRST: + chain(header, header.link_next, e); + break; + case FORCE_LAST: + case LAST: + chain(header.link_prev, header, e); + break; + } + count++; + return null; + } + + public synchronized V remove(byte[] key) { + ByteArrayKeyLinkedEntry tab[] = table; + int index = hash(key) % tab.length; + for (ByteArrayKeyLinkedEntry e = tab[index], prev = null; e != null; prev = e, e = e.next) { + if (CompareUtil.equals(e.key, key)) { + if (prev != null) { + prev.next = e.next; + } else { + tab[index] = e.next; + } + count--; + V oldValue = e.value; + e.value = null; + // + unchain(e); + return oldValue; + } + } + return null; + } + + public synchronized V removeFirst() { + if (isEmpty()) + return null; + return remove(header.link_next.key); + } + + public synchronized V removeLast() { + if (isEmpty()) + return null; + return remove(header.link_prev.key); + } + + public boolean isEmpty() { + return size() == 0; + } + + public synchronized void clear() { + ByteArrayKeyLinkedEntry tab[] = table; + for (int index = tab.length; --index >= 0;) + tab[index] = null; + this.header.link_next = header.link_prev = header; + count = 0; + } + + public String toString() { + StringBuffer buf = new StringBuffer(); + Enumeration it = entries(); + buf.append("{"); + for (int i = 0; it.hasMoreElements(); i++) { + ByteArrayKeyLinkedEntry e = (ByteArrayKeyLinkedEntry) (it.nextElement()); + if (i > 0) + buf.append(", "); + buf.append(Arrays.toString(e.getKey()) + "=" + e.getValue()); + } + buf.append("}"); + return buf.toString(); + } + + public String toFormatString() { + StringBuffer buf = new StringBuffer(); + Enumeration it = entries(); + buf.append("{\n"); + while (it.hasMoreElements()) { + ByteArrayKeyLinkedEntry e = (ByteArrayKeyLinkedEntry) it.nextElement(); + buf.append("\t").append(Arrays.toString(e.getKey()) + "=" + e.getValue()).append("\n"); + } + buf.append("}"); + return buf.toString(); + } + + private enum TYPE { + KEYS, VALUES, ENTRIES + } + + private class Enumer implements Enumeration, ByteArrayEnumer { + TYPE type; + ByteArrayKeyLinkedEntry entry = ByteArrayKeyLinkedMap.this.header.link_next; + + Enumer(TYPE type) { + this.type = type; + } + + public boolean hasMoreElements() { + return header != entry && entry != null; + } + + public V nextElement() { + if (hasMoreElements()) { + ByteArrayKeyLinkedEntry e = entry; + entry = e.link_next; + switch (type) { + case KEYS: + return (V) e.key; + case VALUES: + return (V) e.value; + default: + return (V) e; + } + } + throw new NoSuchElementException("no more next"); + } + + public byte[] nextKey() { + if (hasMoreElements()) { + ByteArrayKeyLinkedEntry e = entry; + entry = e.link_next; + return e.key; + } + throw new NoSuchElementException("no more next"); + } + } + + private void chain(ByteArrayKeyLinkedEntry link_prev, ByteArrayKeyLinkedEntry link_next, ByteArrayKeyLinkedEntry e) { + e.link_prev = link_prev; + e.link_next = link_next; + link_prev.link_next = e; + link_next.link_prev = e; + } + + private void unchain(ByteArrayKeyLinkedEntry e) { + e.link_prev.link_next = e.link_next; + e.link_next.link_prev = e.link_prev; + e.link_prev = null; + e.link_next = null; + } + + public static class ByteArrayKeyLinkedEntry { + byte[] key; + V value; + ByteArrayKeyLinkedEntry next; + ByteArrayKeyLinkedEntry link_next, link_prev; + + protected ByteArrayKeyLinkedEntry(byte[] key, V value, ByteArrayKeyLinkedEntry next) { + this.key = key; + this.value = value; + this.next = next; + } + + protected Object clone() { + return new ByteArrayKeyLinkedEntry(key, value, (next == null ? null : (ByteArrayKeyLinkedEntry) next.clone())); + } + + public byte[] getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(V value) { + if (value == null) + throw new NullPointerException(); + V oldValue = this.value; + this.value = value; + return oldValue; + } + + public boolean equals(Object o) { + if (!(o instanceof ByteArrayKeyLinkedMap.ByteArrayKeyLinkedEntry)) + return false; + ByteArrayKeyLinkedEntry e = (ByteArrayKeyLinkedEntry) o; + return CompareUtil.equals(e.key, key) && CompareUtil.equals(e.value, value); + } + + public int hashCode() { + return Arrays.hashCode(key) ^ (value == null ? 0 : value.hashCode()); + } + + public String toString() { + return key + "=" + value; + } + } + + public synchronized void sort(Comparator> c) { + ArrayList> list = new ArrayList>(this.size()); + Enumeration> en = this.entries(); + while (en.hasMoreElements()) { + list.add(en.nextElement()); + } + Collections.sort(list, c); + this.clear(); + for (int i = 0; i < list.size(); i++) { + ByteArrayKeyLinkedEntry e = list.get(i); + this.put(e.getKey(), e.getValue()); + } + } + + public static void main(String[] args) { + ByteArrayKeyLinkedMap m = new ByteArrayKeyLinkedMap(); + for (int i = 0; i < 10; i++) { + byte[] b = new byte[1]; + b[0] = new Byte(String.valueOf(i)); + m.put(b, i); + } + System.out.println(m); + m.sort(new Comparator>() { + @Override + public int compare(ByteArrayKeyLinkedEntry o1, ByteArrayKeyLinkedEntry o2) { + return CompareUtil.compareTo(o2.getKey(), o1.getKey()); + } + }); + System.out.println(m); + } + +} diff --git a/scouter.common/src/main/java/scouter/util/ByteArrayKeyMap.java b/scouter.common/src/main/java/scouter/util/ByteArrayKeyMap.java new file mode 100644 index 000000000..03f9438e4 --- /dev/null +++ b/scouter.common/src/main/java/scouter/util/ByteArrayKeyMap.java @@ -0,0 +1,315 @@ +/* + * 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.util; + +import java.util.Arrays; +import java.util.Enumeration; +import java.util.NoSuchElementException; + +/** + * @author Gun Lee (gunlee01@gmail.com) + * + */ +public class ByteArrayKeyMap { + private static final int DEFAULT_CAPACITY = 101; + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + private ByteArrayKeyEntry table[]; + private int count; + private int threshold; + private float loadFactor; + + public ByteArrayKeyMap(int initCapacity, float loadFactor) { + if (initCapacity < 0) + throw new RuntimeException("Capacity Error: " + initCapacity); + if (loadFactor <= 0) + throw new RuntimeException("Load Count Error: " + loadFactor); + if (initCapacity == 0) + initCapacity = 1; + this.loadFactor = loadFactor; + table = new ByteArrayKeyEntry[initCapacity]; + threshold = (int) (initCapacity * loadFactor); + } + + public ByteArrayKeyMap() { + this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); + } + + public int size() { + return count; + } + + public synchronized ByteArrayEnumer keys() { + return new Enumer(TYPE.KEYS); + } + + public synchronized Enumeration values() { + return new Enumer(TYPE.VALUES); + } + + public synchronized Enumeration> entries() { + return new Enumer(TYPE.ENTRIES); + } + + public synchronized boolean containsValue(V value) { + if (value == null) { + return false; + } + ByteArrayKeyEntry tab[] = table; + int i = tab.length; + while (i-- > 0) { + for (ByteArrayKeyEntry e = tab[i]; e != null; e = e.next) { + if (CompareUtil.equals(e.value, value)) { + return true; + } + } + } + return false; + } + + public synchronized boolean containsKey(byte[] key) { + ByteArrayKeyEntry tab[] = table; + int index = hash(key) % tab.length; + for (ByteArrayKeyEntry e = tab[index]; e != null; e = e.next) { + if (e.key == key) { + return true; + } + } + return false; + } + + private int hash(byte[] h) { + return Arrays.hashCode(h) & Integer.MAX_VALUE; + } + + public synchronized V get(byte[] key) { + ByteArrayKeyEntry tab[] = table; + int index = hash(key) % tab.length; + for (ByteArrayKeyEntry e = tab[index]; e != null; e = e.next) { + if (e.key == key) { + return e.value; + } + } + return null; + } + + protected void rehash() { + int oldCapacity = table.length; + ByteArrayKeyEntry oldMap[] = table; + int newCapacity = oldCapacity * 2 + 1; + ByteArrayKeyEntry newMap[] = new ByteArrayKeyEntry[newCapacity]; + threshold = (int) (newCapacity * loadFactor); + table = newMap; + for (int i = oldCapacity; i-- > 0;) { + for (ByteArrayKeyEntry old = oldMap[i]; old != null;) { + ByteArrayKeyEntry e = old; + old = old.next; + int index = hash(e.key) % newCapacity; + e.next = newMap[index]; + newMap[index] = e; + } + } + } + + public synchronized byte[][] keyArray() { + byte[][] _keys = new byte[this.size()][]; + ByteArrayEnumer en = this.keys(); + for (int i = 0; i < _keys.length; i++) + _keys[i] = en.nextKey(); + return _keys; + } + + public synchronized V put(byte[] key, V value) { + ByteArrayKeyEntry tab[] = table; + int _hash = hash(key); + int index = _hash % tab.length; + for (ByteArrayKeyEntry e = tab[index]; e != null; e = e.next) { + if (e.key == key) { + V old = e.value; + e.value = value; + return old; + } + } + if (count >= threshold) { + rehash(); + tab = table; + index = _hash % tab.length; + } + ByteArrayKeyEntry e = new ByteArrayKeyEntry(key, value, tab[index]); + tab[index] = e; + count++; + return null; + } + + public synchronized V remove(byte[] key) { + ByteArrayKeyEntry tab[] = table; + int index = hash(key) % tab.length; + for (ByteArrayKeyEntry e = tab[index], prev = null; e != null; prev = e, e = e.next) { + if (e.key == key) { + if (prev != null) { + prev.next = e.next; + } else { + tab[index] = e.next; + } + count--; + V oldValue = e.value; + e.value = null; + return oldValue; + } + } + return null; + } + + public synchronized void clear() { + ByteArrayKeyEntry tab[] = table; + for (int index = tab.length; --index >= 0;) + tab[index] = null; + count = 0; + } + + public String toString() { + StringBuffer buf = new StringBuffer(); + Enumeration it = entries(); + buf.append("{"); + for (int i = 0; it.hasMoreElements(); i++) { + ByteArrayKeyEntry e = (ByteArrayKeyEntry) (it.nextElement()); + if (i > 0) + buf.append(", "); + buf.append(Arrays.toString(e.getKey()) + "=" + e.getValue()); + } + buf.append("}"); + return buf.toString(); + } + + public String toFormatString() { + StringBuffer buf = new StringBuffer(); + Enumeration it = entries(); + buf.append("{\n"); + while (it.hasMoreElements()) { + ByteArrayKeyEntry e = (ByteArrayKeyEntry) it.nextElement(); + buf.append("\t").append(Arrays.toString(e.getKey()) + "=" + e.getValue()).append("\n"); + } + buf.append("}"); + return buf.toString(); + } + + private enum TYPE { + KEYS, VALUES, ENTRIES + } + + private class Enumer implements Enumeration, ByteArrayEnumer { + ByteArrayKeyEntry[] table = ByteArrayKeyMap.this.table; + int index = table.length; + ByteArrayKeyEntry entry = null; + ByteArrayKeyEntry lastReturned = null; + TYPE type; + + Enumer(TYPE type) { + this.type = type; + } + + public boolean hasMoreElements() { + while (entry == null && index > 0) + entry = table[--index]; + return entry != null; + } + + public Object nextElement() { + while (entry == null && index > 0) + entry = table[--index]; + if (entry != null) { + ByteArrayKeyEntry e = lastReturned = entry; + entry = e.next; + switch (type) { + case KEYS: + return e.key; + case VALUES: + return e.value; + default: + return e; + } + } + throw new NoSuchElementException("no more next"); + } + + public byte[] nextKey() { + while (entry == null && index > 0) + entry = table[--index]; + if (entry != null) { + ByteArrayKeyEntry e = lastReturned = entry; + entry = e.next; + return e.key; + } + throw new NoSuchElementException("no more next"); + } + } + + public void putAll(ByteArrayKeyMap other) { + Enumeration it = other.entries(); + for (int i = 0, max = other.size(); i <= max; i++) { + ByteArrayKeyEntry e = (ByteArrayKeyEntry) (it.nextElement()); + this.put(e.getKey(), e.getValue()); + } + } + + public static class ByteArrayKeyEntry { + byte[] key; + V value; + ByteArrayKeyEntry next; + + protected ByteArrayKeyEntry(byte[] key, V value, ByteArrayKeyEntry next) { + this.key = key; + this.value = value; + this.next = next; + } + + protected Object clone() { + return new ByteArrayKeyEntry(key, value, (next == null ? null : (ByteArrayKeyEntry) next.clone())); + } + + public byte[] getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(V value) { + if (value == null) + throw new NullPointerException(); + V oldValue = this.value; + this.value = value; + return oldValue; + } + + public boolean equals(Object o) { + if (!(o instanceof ByteArrayKeyMap.ByteArrayKeyEntry)) + return false; + ByteArrayKeyEntry e = (ByteArrayKeyEntry) o; + return (key == e.getKey()) && (value == null ? e.getValue() == null : value.equals(e.getValue())); + } + + public int hashCode() { + return Arrays.hashCode(key) ^ (value == null ? 0 : value.hashCode()); + } + + public String toString() { + return Arrays.toString(key) + "=" + value.toString(); + } + } +} diff --git a/scouter.common/src/main/java/scouter/util/DateUtil.java b/scouter.common/src/main/java/scouter/util/DateUtil.java index 2aa5a3039..432d934df 100644 --- a/scouter.common/src/main/java/scouter/util/DateUtil.java +++ b/scouter.common/src/main/java/scouter/util/DateUtil.java @@ -50,6 +50,10 @@ public static long getDateUnit() { return helper.getDateUnit(); } + public static long dateUnitToTimeMillis(long dateUnit) { + return helper.dateUnitToTimeMillis(dateUnit); + } + public static long getDateUnit(long time) { return helper.getDateUnit(time); } diff --git a/scouter.common/src/main/java/scouter/util/StringUtil.java b/scouter.common/src/main/java/scouter/util/StringUtil.java index ef3f1d672..9bcf1b9a3 100644 --- a/scouter.common/src/main/java/scouter/util/StringUtil.java +++ b/scouter.common/src/main/java/scouter/util/StringUtil.java @@ -370,4 +370,16 @@ public static String emptyToDefault(String text, String defaultText) { return text; } } + + public static boolean isInteger(String s) { + try { + Integer.parseInt(s); + } catch(NumberFormatException e) { + return false; + } catch(NullPointerException e) { + return false; + } + // only got here if we didn't return false + return true; + } } diff --git a/scouter.common/src/main/resources/scouter/lang/counters/counters.xml b/scouter.common/src/main/resources/scouter/lang/counters/counters.xml index c713f619c..cb9673eb9 100644 --- a/scouter.common/src/main/resources/scouter/lang/counters/counters.xml +++ b/scouter.common/src/main/resources/scouter/lang/counters/counters.xml @@ -36,7 +36,11 @@ + + + + diff --git a/scouter.deploy/pom.xml b/scouter.deploy/pom.xml index d24848120..0ba89cc6c 100644 --- a/scouter.deploy/pom.xml +++ b/scouter.deploy/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.3 + 1.8.4 scouter-deploy diff --git a/scouter.server.boot/pom.xml b/scouter.server.boot/pom.xml index 227e55ae1..4d907fcc0 100644 --- a/scouter.server.boot/pom.xml +++ b/scouter.server.boot/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.3 + 1.8.4 scouter-server-boot diff --git a/scouter.server/pom.xml b/scouter.server/pom.xml index df787c0cf..a206c5bce 100644 --- a/scouter.server/pom.xml +++ b/scouter.server/pom.xml @@ -4,7 +4,7 @@ io.github.scouter-project scouter-parent - 1.8.3 + 1.8.4 scouter-server diff --git a/scouter.server/src/main/java/scouter/server/Configure.java b/scouter.server/src/main/java/scouter/server/Configure.java index 5f6f16901..68ebf9aa1 100644 --- a/scouter.server/src/main/java/scouter/server/Configure.java +++ b/scouter.server/src/main/java/scouter/server/Configure.java @@ -144,9 +144,9 @@ public final static synchronized Configure getInstance() { @ConfigDesc("Swagger option of host's ip or domain to call APIs.") public String net_http_api_swagger_host_ip = ""; @ConfigDesc("API CORS support for Access-Control-Allow-Origin") - public String net_http_api_cors_allow_origin = ""; + public String net_http_api_cors_allow_origin = "*"; @ConfigDesc("Access-Control-Allow-Credentials") - public String net_http_api_cors_allow_credentials = "false"; + public String net_http_api_cors_allow_credentials = "true"; @ConfigDesc("size of webapp connection pool to collector") public int net_webapp_tcp_client_pool_size = 12; @@ -392,8 +392,8 @@ private void apply() { this.net_http_api_enabled = getBoolean("net_http_api_enabled", false); this.net_http_api_swagger_enabled = getBoolean("net_http_api_swagger_enabled", false); this.net_http_api_swagger_host_ip = getValue("net_http_api_swagger_host_ip", ""); - this.net_http_api_cors_allow_origin = getValue("net_http_api_cors_allow_origin", ""); - this.net_http_api_cors_allow_credentials = getValue("net_http_api_cors_allow_credentials", "false"); + this.net_http_api_cors_allow_origin = getValue("net_http_api_cors_allow_origin", "*"); + this.net_http_api_cors_allow_credentials = getValue("net_http_api_cors_allow_credentials", "true"); this.net_webapp_tcp_client_pool_size = getInt("net_webapp_tcp_client_pool_size", 12); this.net_webapp_tcp_client_pool_timeout = getInt("net_webapp_tcp_client_pool_timeout", net_tcp_client_so_timeout_ms); diff --git a/scouter.server/src/main/java/scouter/server/http/HttpServer.java b/scouter.server/src/main/java/scouter/server/http/HttpServer.java index a1ed702cf..48bfe2c2b 100644 --- a/scouter.server/src/main/java/scouter/server/http/HttpServer.java +++ b/scouter.server/src/main/java/scouter/server/http/HttpServer.java @@ -96,6 +96,18 @@ public void run() { handlers.addHandler(context); server.setHandler(handlers); + if (conf.net_http_api_enabled) { + try { + Class c = Class.forName("scouterx.webapp.main.WebAppMain"); + c.getMethod("setWebSocketServer", ServletContextHandler.class).invoke(null, context); + } catch (Throwable e) { + Logger.println("Error while setWebSocketServer!"); + System.out.println("Error while setWebSocketServer!"); + Logger.printStackTrace(e); + e.printStackTrace(); + } + } + try { server.start(); server.join(); diff --git a/scouter.server/src/main/scala/scouter/server/Logger.scala b/scouter.server/src/main/scala/scouter/server/Logger.scala index 20791c495..a3637c26a 100644 --- a/scouter.server/src/main/scala/scouter/server/Logger.scala +++ b/scouter.server/src/main/scala/scouter/server/Logger.scala @@ -219,8 +219,9 @@ object Logger { try { val d = DateUtil.yyyymmdd(date) val fileUnit = DateUtil.getDateUnit(d) - if (nowUnit - fileUnit > DateUtil.MILLIS_PER_DAY * conf.log_keep_days) { + if (nowUnit - fileUnit > conf.log_keep_days) { files(i).delete() + Logger.println("[scouter] delete log file : " + files(i).getAbsolutePath) } } catch { case e: Exception => diff --git a/scouter.server/src/main/scala/scouter/server/db/io/IndexKeyFile2.scala b/scouter.server/src/main/scala/scouter/server/db/io/IndexKeyFile2.scala index 72602e5bf..2e7bd1119 100644 --- a/scouter.server/src/main/scala/scouter/server/db/io/IndexKeyFile2.scala +++ b/scouter.server/src/main/scala/scouter/server/db/io/IndexKeyFile2.scala @@ -73,14 +73,17 @@ class IndexKeyFile2(_path: String, hashSize: Int = 1) extends IClose { while (realKeyPos > 0) { val oKey = this.keyFile.getKey(realKeyPos); if (CompareUtil.equals(oKey, key)) { - return this.keyFile.update(realKeyPos, ttl, key, value); + val result = this.keyFile.update(realKeyPos, ttl, key, value); + if(!result) { + return put(key, value, ttl); + } } realKeyPos = this.keyFile.getPrevPos(realKeyPos); looping += 1; } if(looping > conf.log_index_traversal_warning_count) { - Logger.println("S161", 10, "[warn] Too many index deep searching. " + DataInputX.toLong(key, 0)); + Logger.println("S161", 10, "[warn] Too many index deep searching. " + new String(key, "UTF8")); } } catch { case e: IOException => @@ -116,7 +119,7 @@ class IndexKeyFile2(_path: String, hashSize: Int = 1) extends IClose { looping += 1; } if(looping > conf.log_index_traversal_warning_count) { - Logger.println("S161", 10, "[warn] Too many index deep searching. " + DataInputX.toLong(key, 0)); + Logger.println("S161", 10, "[warn] Too many index deep searching. " + new String(key, "UTF8")); } } catch { case e: IOException => @@ -150,7 +153,7 @@ class IndexKeyFile2(_path: String, hashSize: Int = 1) extends IClose { looping += 1; } if(looping > conf.log_index_traversal_warning_count) { - Logger.println("S152", 10, "[warn] Too many index deep searching. " + DataInputX.toLong(key, 0)); + Logger.println("S152", 10, "[warn] Too many index deep searching. " + new String(key, "UTF8")); } } catch { case e: IOException => @@ -223,6 +226,7 @@ class IndexKeyFile2(_path: String, hashSize: Int = 1) extends IClose { return 0; } + //TODO not yet implemented def read(handler: (Array[Byte], Array[Byte]) => Any) { if (this.keyFile == null) return @@ -244,6 +248,7 @@ class IndexKeyFile2(_path: String, hashSize: Int = 1) extends IClose { } } + //TODO not yet implemented def read(handler: (Array[Byte], Array[Byte]) => Any, reader: (Long)=>Array[Byte]) { if (this.keyFile == null) return ; diff --git a/scouter.server/src/main/scala/scouter/server/netio/service/handle/XLogService.scala b/scouter.server/src/main/scala/scouter/server/netio/service/handle/XLogService.scala index 3b36774b2..14d59095b 100644 --- a/scouter.server/src/main/scala/scouter/server/netio/service/handle/XLogService.scala +++ b/scouter.server/src/main/scala/scouter/server/netio/service/handle/XLogService.scala @@ -357,6 +357,32 @@ class XLogService { } } + @ServiceHandler(RequestCmd.XLOG_LOAD_BY_TXIDS) + def loadByTxIds(din: DataInputX, dout: DataOutputX, login: Boolean) { + val param = din.readMapPack() + val date = param.getText("date") + val txidLv = param.getList("txid") + + var loadCount = 0 + try { + EnumerScala.foreach(txidLv, (txidValue: DecimalValue) => { + loadCount += 1; + + if (loadCount >= Configure.getInstance().req_search_xlog_max_count) { + return; + } + val xbytes = XLogRD.getByTxid(date, txidValue.longValue()) + if(xbytes != null) { + dout.writeByte(TcpFlag.HasNEXT) + dout.write(xbytes) + dout.flush() + } + }) + } catch { + case e: Exception => {} + } + } + @ServiceHandler(RequestCmd.XLOG_LOAD_BY_GXID) def loadByGxId(din: DataInputX, dout: DataOutputX, login: Boolean) { val param = din.readMapPack(); diff --git a/scouter.webapp/pom.xml b/scouter.webapp/pom.xml index f9cbeba13..ca98e82f0 100644 --- a/scouter.webapp/pom.xml +++ b/scouter.webapp/pom.xml @@ -5,7 +5,7 @@ scouter-parent io.github.scouter-project - 1.8.3 + 1.8.4 4.0.0 @@ -45,6 +45,23 @@ jetty-servlet ${jetty.version} + + + + + + + + + + + + + + org.eclipse.jetty.websocket + javax-websocket-server-impl + ${jetty.version} + org.glassfish.jersey.containers jersey-container-servlet-core @@ -122,6 +139,18 @@ 1.10.19 test + + org.powermock + powermock-module-junit4 + 1.7.3 + test + + + org.powermock + powermock-api-mockito + 1.7.3 + test + diff --git a/scouter.webapp/src/main/java/scouterx/webapp/framework/client/server/Server.java b/scouter.webapp/src/main/java/scouterx/webapp/framework/client/server/Server.java index 8107fbfa8..5342ddf87 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/framework/client/server/Server.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/framework/client/server/Server.java @@ -27,7 +27,7 @@ import scouterx.webapp.framework.configure.ConfigureManager; @Slf4j -public final class Server { +public class Server { ConfigureAdaptor conf = ConfigureManager.getConfigure(); final private int id; diff --git a/scouter.webapp/src/main/java/scouterx/webapp/framework/configure/StandAloneConfigure.java b/scouter.webapp/src/main/java/scouterx/webapp/framework/configure/StandAloneConfigure.java index ff0c48d62..33906182b 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/framework/configure/StandAloneConfigure.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/framework/configure/StandAloneConfigure.java @@ -83,9 +83,9 @@ protected final static synchronized StandAloneConfigure getInstance() { @ConfigDesc("Swagger option of host's ip or domain to call APIs.") public String net_http_api_swagger_host_ip = ""; @ConfigDesc("API CORS support for Access-Control-Allow-Origin") - public String net_http_api_cors_allow_origin = ""; + public String net_http_api_cors_allow_origin = "*"; @ConfigDesc("Access-Control-Allow-Credentials") - public String net_http_api_cors_allow_credentials = "false"; + public String net_http_api_cors_allow_credentials = "true"; @ConfigDesc("Log directory") public String log_dir = "./logs"; @@ -187,8 +187,8 @@ private void apply() { this.net_http_api_swagger_enabled = getBoolean("net_http_api_swagger_enabled", false); this.net_http_api_swagger_host_ip = getValue("net_http_api_swagger_host_ip", ""); - this.net_http_api_cors_allow_origin = getValue("net_http_api_cors_allow_origin", ""); - this.net_http_api_cors_allow_credentials = getValue("net_http_api_cors_allow_credentials", "false"); + this.net_http_api_cors_allow_origin = getValue("net_http_api_cors_allow_origin", "*"); + this.net_http_api_cors_allow_credentials = getValue("net_http_api_cors_allow_credentials", "true"); this.log_dir = getValue("log_dir", "./logs"); diff --git a/scouter.webapp/src/main/java/scouterx/webapp/framework/exception/ErrorState.java b/scouter.webapp/src/main/java/scouterx/webapp/framework/exception/ErrorState.java index 820afdf27..3149aa107 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/framework/exception/ErrorState.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/framework/exception/ErrorState.java @@ -38,8 +38,8 @@ public enum ErrorState { ILLEGAL_KEY_ACCESS(Response.Status.BAD_REQUEST, Response.Status.BAD_REQUEST.getStatusCode(), "illegal key access error"), - LOGIN_REQUIRED(Response.Status.FORBIDDEN, Response.Status.FORBIDDEN.getStatusCode(), "login required."), - LOGIN_FAIL(Response.Status.UNAUTHORIZED, Response.Status.UNAUTHORIZED.getStatusCode(), "id or password is incorrect."), + LOGIN_REQUIRED(Response.Status.UNAUTHORIZED, Response.Status.UNAUTHORIZED.getStatusCode(), "login required."), + LOGIN_FAIL(Response.Status.NOT_FOUND, Response.Status.NOT_FOUND.getStatusCode(), "id or password is incorrect."), SESSION_EXPIRED(Response.Status.UNAUTHORIZED, Response.Status.UNAUTHORIZED.getStatusCode(), "authorization token or session is expired."), NOT_IMPLEMENTED(Response.Status.NOT_IMPLEMENTED, Response.Status.NOT_IMPLEMENTED.getStatusCode(), "This API is not yet implemented."), VALIDATE_ERROR(Response.Status.BAD_REQUEST, Response.Status.BAD_REQUEST.getStatusCode(), "fail to validate input parameters. : "), diff --git a/scouter.webapp/src/main/java/scouterx/webapp/framework/filter/CorsFilter.java b/scouter.webapp/src/main/java/scouterx/webapp/framework/filter/CorsFilter.java index 5a03a8ad6..d22acbae5 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/framework/filter/CorsFilter.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/framework/filter/CorsFilter.java @@ -52,6 +52,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha } httpServletResponse.addHeader("Access-Control-Allow-Origin", allowOrigin); httpServletResponse.addHeader("Access-Control-Allow-Credentials", allowCredentials); + httpServletResponse.addHeader("Access-Control-Max-Age", "600"); httpServletResponse.addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); httpServletResponse.addHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, PATCH"); diff --git a/scouter.webapp/src/main/java/scouterx/webapp/framework/session/UserToken.java b/scouter.webapp/src/main/java/scouterx/webapp/framework/session/UserToken.java index a19f9abf9..1213d7f50 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/framework/session/UserToken.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/framework/session/UserToken.java @@ -29,41 +29,45 @@ @Getter @Setter public class UserToken { - String id; + String userId; String token; long footprintSec; //unix timestamp int serverId; boolean fromSession; private UserToken(String id, String token, int serverId) { - this.id = id; + this.userId = id; this.token = token; this.footprintSec = 0; this.serverId = serverId; } public String getStoreKey() { - return id; + return userId; } public String toStoreValue() { - return "V1." + footprintSec + "." + token + "." + id; + return "V1." + footprintSec + "." + token + "." + userId; } public String toBearerToken() { - return "V1." + token + "." + Hexa32.toString32(serverId) + "." + id; + return "V1." + token + "." + Hexa32.toString32(serverId) + "." + userId; } public boolean isExpired(int timeoutSec) { return footprintSec + timeoutSec < System.currentTimeMillis() / 1000; } + public boolean isNotExpired(int timeoutSec) { + return !isExpired(timeoutSec); + } + public boolean needToBeRenewed(int touchThresholdSec) { return footprintSec + touchThresholdSec < System.currentTimeMillis() / 1000; } public UserToken renew() { - return UserToken.newToken(id, token, serverId); + return UserToken.newToken(userId, token, serverId); } public static UserToken fromBearerToken(String token) { diff --git a/scouter.webapp/src/main/java/scouterx/webapp/framework/session/UserTokenCache.java b/scouter.webapp/src/main/java/scouterx/webapp/framework/session/UserTokenCache.java index ece400a5e..243a40362 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/framework/session/UserTokenCache.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/framework/session/UserTokenCache.java @@ -33,11 +33,23 @@ public static UserTokenCache getInstance() { return instance; } - public void put(String userId, UserToken token) { - cache.put(userId, token); + public UserToken get(UserToken condition) { + return get(condition.getToken()); } - public UserToken get(String userId) { - return cache.get(userId); + public void put(UserToken token) { + put(token.getToken(), token); + } + + public void putAsRecent(UserToken token) { + cache.putLast(token.getToken(), token); + } + + private void put(String key, UserToken token) { + cache.put(key, token); + } + + private UserToken get(String key) { + return cache.get(key); } } diff --git a/scouter.webapp/src/main/java/scouterx/webapp/framework/util/ZZ.java b/scouter.webapp/src/main/java/scouterx/webapp/framework/util/ZZ.java index 52a8fc7c9..d31ff443b 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/framework/util/ZZ.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/framework/util/ZZ.java @@ -94,6 +94,12 @@ public static List splitParamAsInteger(String org) { return Arrays.stream(items).map(Integer::parseInt).collect(Collectors.toList()); } + public static List splitParamAsLong(String org) { + org = stripFirstLastBracket(org); + String[] items = StringUtils.split(org, COMMA); + return Arrays.stream(items).map(Long::parseLong).collect(Collectors.toList()); + } + public static Set splitParamAsIntegerSet(String org) { org = stripFirstLastBracket(org); String[] items = StringUtils.split(org, COMMA); diff --git a/scouter.webapp/src/main/java/scouterx/webapp/layer/consumer/XLogConsumer.java b/scouter.webapp/src/main/java/scouterx/webapp/layer/consumer/XLogConsumer.java index 2af6a2e5f..157ba9857 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/layer/consumer/XLogConsumer.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/layer/consumer/XLogConsumer.java @@ -29,11 +29,12 @@ import scouterx.webapp.framework.client.net.TcpProxy; import scouterx.webapp.model.XLogData; import scouterx.webapp.model.scouter.SXLog; +import scouterx.webapp.request.GxidXLogRequest; +import scouterx.webapp.request.MultiXLogRequest; import scouterx.webapp.request.PageableXLogRequest; import scouterx.webapp.request.RealTimeXLogRequest; import scouterx.webapp.request.SearchXLogRequest; import scouterx.webapp.request.SingleXLogRequest; -import scouterx.webapp.request.GxidXLogRequest; import scouterx.webapp.view.PageableXLogView; import scouterx.webapp.view.RealTimeXLogView; @@ -41,6 +42,8 @@ import java.util.List; import java.util.stream.Collectors; +import static scouter.lang.constants.ParamConstant.XLOG_TXID; + /** * @author Gun Lee (gunlee01@gmail.com) on 2017. 8. 27. */ @@ -52,7 +55,7 @@ public class XLogConsumer { */ public void handleRealTimeXLog(final RealTimeXLogRequest xLogRequest, final INetReader reader) { boolean isFirst = false; - int firstRetrieveLimit = 5000; + int firstRetrieveLimit = 10000; if (xLogRequest.getXLogLoop() == 0 && xLogRequest.getXLogIndex() == 0) { isFirst = true; @@ -87,7 +90,7 @@ public void handlePageableXLog(final PageableXLogRequest pageableXLogRequest, fi MapPack paramPack = new MapPack(); paramPack.put(ParamConstant.DATE, pageableXLogRequest.getYyyymmdd()); paramPack.put(ParamConstant.XLOG_START_TIME, pageableXLogRequest.getStartTimeMillis()); - paramPack.put(ParamConstant.XLOG_TXID, pageableXLogRequest.getLastTxid()); + paramPack.put(XLOG_TXID, pageableXLogRequest.getLastTxid()); paramPack.put(ParamConstant.XLOG_END_TIME, pageableXLogRequest.getEndTimeMillis()); paramPack.put(ParamConstant.XLOG_LAST_BUCKET_TIME, pageableXLogRequest.getLastXLogTime()); paramPack.put(ParamConstant.XLOG_PAGE_COUNT, pageableXLogRequest.getPageCount()); @@ -162,7 +165,7 @@ public SXLog retrieveByTxidAsXLog(final SingleXLogRequest singleXLogRequest) { * * @param xlogRequest */ - public List retrieveXLogsByGxid(final GxidXLogRequest xlogRequest) { + public List retrieveXLogListByGxid(final GxidXLogRequest xlogRequest) { return retrieveXLogPacksByGxid(xlogRequest).stream() .map(pack -> (XLogPack) pack) .map(SXLog::of) @@ -175,18 +178,30 @@ public List retrieveXLogsByGxid(final GxidXLogRequest xlogRequest) { * * @param xLogRequest */ - public List retrieveXLogDatasByGxid(final GxidXLogRequest xLogRequest) { + public List retrieveXLogDataListByGxid(final GxidXLogRequest xLogRequest) { return retrieveXLogPacksByGxid(xLogRequest).stream() .map(pack -> (XLogPack) pack) .map(pack -> XLogData.of(pack, xLogRequest.getServerId())) .collect(Collectors.toList()); } + /** + * retrieve XLog Data List by txids + * + * @param multiXLogRequest + */ + public List retrieveXLogDataListByTxids(final MultiXLogRequest multiXLogRequest) { + return retrieveXLogPacksByTxids(multiXLogRequest).stream() + .map(pack -> (XLogPack) pack) + .map(pack -> XLogData.of(pack, multiXLogRequest.getServerId())) + .collect(Collectors.toList()); + } + private XLogPack retrieveByTxid(final SingleXLogRequest singleXLogRequest) { MapPack param = new MapPack(); param.put(ParamConstant.DATE, singleXLogRequest.getYyyymmdd()); - param.put(ParamConstant.XLOG_TXID, singleXLogRequest.getTxid()); + param.put(XLOG_TXID, singleXLogRequest.getTxid()); XLogPack pack; try (TcpProxy tcpProxy = TcpProxy.getTcpProxy(singleXLogRequest.getServerId())) { @@ -208,6 +223,19 @@ private List retrieveXLogPacksByGxid(GxidXLogRequest xlogRequest) { return results; } + private List retrieveXLogPacksByTxids(MultiXLogRequest xlogRequest) { + MapPack param = new MapPack(); + param.put(ParamConstant.DATE, xlogRequest.getYyyymmdd()); + ListValue xlogLv = param.newList(XLOG_TXID); + xlogRequest.getTxidList().forEach(xlogLv::add); + + List results; + try (TcpProxy tcpProxy = TcpProxy.getTcpProxy(xlogRequest.getServerId())) { + results = tcpProxy.process(RequestCmd.XLOG_LOAD_BY_TXIDS, param); + } + return results; + } + private List searchXLogPackList(final SearchXLogRequest searchXLogRequest) { MapPack paramPack = new MapPack(); paramPack.put(ParamConstant.DATE,searchXLogRequest.getYyyymmdd()); diff --git a/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/PrivateKvStoreController.java b/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/PrivateKvStoreController.java index 441defc43..e4424fac7 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/PrivateKvStoreController.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/PrivateKvStoreController.java @@ -133,7 +133,7 @@ private String getPrivateKeyPrefix() { if (userToken == null) { ErrorState.LOGIN_REQUIRED.newBizException(); } - return "]" + userToken.getId() + ":"; + return "]" + userToken.getUserId() + ":"; } private String toPrivateKey(String key) { diff --git a/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/XLogController.java b/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/XLogController.java index 4d4114407..d9a5c0e50 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/XLogController.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/XLogController.java @@ -153,7 +153,7 @@ public CommonResultView retrieveSingleXLog(@Valid @BeanParam SingleXLogRe @Consumes(MediaType.APPLICATION_JSON) public CommonResultView> retrieveXLogsByGxid(@Valid @BeanParam GxidXLogRequest gxidRequest) { gxidRequest.validate(); - List xLogs = xLogService.retrieveXLogsByGxid(gxidRequest); + List xLogs = xLogService.retrieveXLogListByGxid(gxidRequest); return CommonResultView.success(xLogs); } diff --git a/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/XLogDataController.java b/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/XLogDataController.java index 83622ed2e..e6a1f34a5 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/XLogDataController.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/layer/controller/XLogDataController.java @@ -37,6 +37,7 @@ import scouterx.webapp.model.XLogData; import scouterx.webapp.model.XLogPackWrapper; import scouterx.webapp.request.GxidXLogRequest; +import scouterx.webapp.request.MultiXLogRequest; import scouterx.webapp.request.PageableXLogRequest; import scouterx.webapp.request.RealTimeXLogDataRequest; import scouterx.webapp.request.SearchXLogRequest; @@ -177,7 +178,7 @@ public Response streamPageableXLog(@Valid @BeanParam PageableXLogRequest xLogReq /** * request xlog by txid - * uri : /xlog-data/{yyyymmdd}/{txid} @see {@link SingleXLogRequest} + * uri : /{yyyymmdd}/{txid} @see {@link SingleXLogRequest} * * @param singleXlogRequest */ @@ -191,6 +192,21 @@ public CommonResultView retrieveSingleXLog(@Valid @BeanParam SingleXLo return CommonResultView.success(xLogData); } + /** + * request xlog by txid + * uri : /{yyyymmdd}/multi @see {@link MultiXLogRequest} + * + * @param multiXLogRequest + */ + @GET + @Path("/{yyyymmdd}/multi/{txidList}") + @Consumes(MediaType.APPLICATION_JSON) + public CommonResultView> retrieveXLogDataListByTxids(@Valid @BeanParam MultiXLogRequest multiXLogRequest) { + List xLogs = xLogService.retrieveXLogDataListByTxids(multiXLogRequest); + + return CommonResultView.success(xLogs); + } + /** * request xlogs by gxid * uri : /{yyyymmdd}/gxid/{gxid} @see {@link GxidXLogRequest} @@ -200,9 +216,9 @@ public CommonResultView retrieveSingleXLog(@Valid @BeanParam SingleXLo @GET @Path("/{yyyymmdd}/gxid/{gxid}") @Consumes(MediaType.APPLICATION_JSON) - public CommonResultView> retrieveXLogDatasByGxid(@Valid @BeanParam GxidXLogRequest gxidRequest) { + public CommonResultView> retrieveXLogDataListByGxid(@Valid @BeanParam GxidXLogRequest gxidRequest) { gxidRequest.validate(); - List xLogs = xLogService.retrieveXLogDatasByGxid(gxidRequest); + List xLogs = xLogService.retrieveXLogDataListByGxid(gxidRequest); return CommonResultView.success(xLogs); } diff --git a/scouter.webapp/src/main/java/scouterx/webapp/layer/service/UserTokenService.java b/scouter.webapp/src/main/java/scouterx/webapp/layer/service/UserTokenService.java index e1347bdc1..5f7abc3c0 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/layer/service/UserTokenService.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/layer/service/UserTokenService.java @@ -29,9 +29,14 @@ import scouterx.webapp.framework.session.UserTokenCache; import scouterx.webapp.model.scouter.SUser; +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + /** * @author Gun Lee (gunlee01@gmail.com) on 2017. 8. 27. - * + *

* It use scouter kv store in which data only can be added not delete or modify. * (We will make it better in a latter version.) */ @@ -40,8 +45,8 @@ public class UserTokenService { private ConfigureAdaptor conf = ConfigureManager.getConfigure(); private final static float TOUCH_RATE = 0.1f; - private int sessionExpireSec = (int) (conf.getNetHttpApiSessionTimeout() * (1.0f + TOUCH_RATE)); - private int SessionTouchThresholdSec = (int) (conf.getNetHttpApiSessionTimeout() * TOUCH_RATE); + public int sessionExpireSec = (int) (conf.getNetHttpApiSessionTimeout() * (1.0f + TOUCH_RATE)); + public int SessionTouchThresholdSec = (int) (conf.getNetHttpApiSessionTimeout() * TOUCH_RATE); private SessionIdGenerator sessionIdGenerator = new SessionIdGenerator(); private CustomKvStoreService customKvStoreService = new CustomKvStoreService(); @@ -51,8 +56,11 @@ public class UserTokenService { */ public String publishToken(final Server server, final SUser user) { UserToken userToken = UserToken.newToken(user.getId(), sessionIdGenerator.generateSessionId(), server.getId()); - UserTokenCache.getInstance().put(user.getId(), userToken); - customKvStoreService.set(SESSION_STORE, userToken.getStoreKey(), userToken.toStoreValue(), sessionExpireSec, server); + UserTokenCache.getInstance().put(userToken); + + String mergedStoreValue = getAndMergeToStoredValue(userToken); + customKvStoreService.set(SESSION_STORE, userToken.getUserId(), mergedStoreValue, sessionExpireSec, server); + return userToken.toBearerToken(); } @@ -60,17 +68,14 @@ public String publishToken(final Server server, final SUser user) { * check user session & renew token's footprint if valid */ public void validateToken(UserToken token) { - UserToken tokenTrusted = UserTokenCache.getInstance().get(token.getId()); + UserToken tokenTrusted = UserTokenCache.getInstance().get(token); if (tokenTrusted == null) { - String stored = customKvStoreService.get(SESSION_STORE, token.getStoreKey(), ServerManager.getInstance().getServerIfNullDefault(token.getServerId())); - if (StringUtils.isNotBlank(stored)) { - tokenTrusted = UserToken.fromStoreValue(stored, token.getServerId()); - if (tokenTrusted != null) { - UserTokenCache.getInstance().put(tokenTrusted.getId(), tokenTrusted); - } + tokenTrusted = getStoredMatchedToken(token); + if (tokenTrusted != null) { + UserTokenCache.getInstance().put(tokenTrusted); } } - if (tokenTrusted == null || !tokenTrusted.getToken().equals(token.getToken()) || tokenTrusted.isExpired(sessionExpireSec)) { + if (tokenTrusted == null || tokenTrusted.isExpired(sessionExpireSec)) { throw ErrorState.SESSION_EXPIRED.newBizException(); } if (tokenTrusted.needToBeRenewed(SessionTouchThresholdSec)) { @@ -82,9 +87,40 @@ public void validateToken(UserToken token) { * renew token's footprint */ private void touchToken(UserToken token) { - UserToken userToken = token.renew(); - UserTokenCache.getInstance().put(userToken.getId(), userToken); - customKvStoreService.set(SESSION_STORE, userToken.getStoreKey(), userToken.toStoreValue(), sessionExpireSec, - ServerManager.getInstance().getServer(token.getServerId())); + UserToken renewedToken = token.renew(); + + UserTokenCache.getInstance().putAsRecent(renewedToken); + String mergedStoreValue = getAndMergeToStoredValue(renewedToken); + + customKvStoreService.set(SESSION_STORE, renewedToken.getUserId(), mergedStoreValue, sessionExpireSec, ServerManager.getInstance().getServer(renewedToken.getServerId())); + } + + private UserToken getStoredMatchedToken(UserToken userToken) { + String tokens = customKvStoreService.get(SESSION_STORE, userToken.getUserId(), ServerManager.getInstance().getServerIfNullDefault(userToken.getServerId())); + Map userTokenMap = Arrays.stream(tokens.split(":")) + .map(v -> UserToken.fromStoreValue(v, 0)) + .collect(Collectors.toMap(UserToken::getToken, Function.identity())); + + return userTokenMap.get(userToken.getToken()); + } + + String getAndMergeToStoredValue(UserToken userToken) { + String tokens = customKvStoreService.get(SESSION_STORE, userToken.getUserId(), ServerManager.getInstance().getServerIfNullDefault(userToken.getServerId())); + return mergeStoredTokensWith(tokens, userToken); + } + + String mergeStoredTokensWith(String tokens, UserToken userToken) { + if (StringUtils.isBlank(tokens)) { + return userToken.toStoreValue(); + } + Map userTokenMap = Arrays.stream(tokens.split(":")) + .map(v -> UserToken.fromStoreValue(v, 0)) + .filter(v -> v.isNotExpired(sessionExpireSec)) + .collect(Collectors.toMap(UserToken::getToken, Function.identity())); + + userTokenMap.put(userToken.getToken(), userToken); + return userTokenMap.values().stream() + .map(UserToken::toStoreValue) + .collect(Collectors.joining(":")); } } diff --git a/scouter.webapp/src/main/java/scouterx/webapp/layer/service/XLogService.java b/scouter.webapp/src/main/java/scouterx/webapp/layer/service/XLogService.java index 189deff28..7828a5bd4 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/layer/service/XLogService.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/layer/service/XLogService.java @@ -22,6 +22,7 @@ import scouterx.webapp.layer.consumer.XLogConsumer; import scouterx.webapp.model.XLogData; import scouterx.webapp.model.scouter.SXLog; +import scouterx.webapp.request.MultiXLogRequest; import scouterx.webapp.request.SearchXLogRequest; import scouterx.webapp.request.PageableXLogRequest; import scouterx.webapp.request.RealTimeXLogRequest; @@ -88,16 +89,24 @@ public SXLog retrieveSingleXLog(final SingleXLogRequest singleXlogRequest) { /** * retrieve Xlog List by gxid */ - public List retrieveXLogsByGxid(final GxidXLogRequest xlogRequest) { - return xLogConsumer.retrieveXLogsByGxid(xlogRequest); + public List retrieveXLogListByGxid(final GxidXLogRequest xlogRequest) { + return xLogConsumer.retrieveXLogListByGxid(xlogRequest); } /** * retrieve Xlog data List by gxid */ - public List retrieveXLogDatasByGxid(final GxidXLogRequest xlogRequest) { - return xLogConsumer.retrieveXLogDatasByGxid(xlogRequest); + public List retrieveXLogDataListByGxid(final GxidXLogRequest xlogRequest) { + return xLogConsumer.retrieveXLogDataListByGxid(xlogRequest); } + /** + * retrieve Xlog data List by txids + */ + public List retrieveXLogDataListByTxids(final MultiXLogRequest multiXLogRequest) { + return xLogConsumer.retrieveXLogDataListByTxids(multiXLogRequest); + } + + } diff --git a/scouter.webapp/src/main/java/scouterx/webapp/layer/websock/BasicSocket.java b/scouter.webapp/src/main/java/scouterx/webapp/layer/websock/BasicSocket.java new file mode 100644 index 000000000..19e12e85d --- /dev/null +++ b/scouter.webapp/src/main/java/scouterx/webapp/layer/websock/BasicSocket.java @@ -0,0 +1,52 @@ +package scouterx.webapp.layer.websock; + +import lombok.extern.slf4j.Slf4j; + +import javax.websocket.CloseReason; +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.RemoteEndpoint; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; + +@ServerEndpoint("/basic") +@Slf4j +public class BasicSocket +{ + private Session session; + private RemoteEndpoint.Async remote; + + @OnClose + public void onWebSocketClose(CloseReason close) + { + this.session = null; + this.remote = null; + log.info("WebSocket Close: {} - {}",close.getCloseCode(),close.getReasonPhrase()); + } + + @OnOpen + public void onWebSocketOpen(Session session) + { + this.session = session; + this.remote = this.session.getAsyncRemote(); + log.info("WebSocket Connect: {}",session); + this.remote.sendText("You are now connected to " + this.getClass().getName()); + } + + @OnError + public void onWebSocketError(Throwable cause) + { + log.warn("WebSocket Error",cause); + } + + @OnMessage + public String onWebSocketText(String message) + { + log.info("Echoing back text message [{}]",message); + // Using shortcut approach to sending messages. + // You could use a void method and use remote.sendText() + return message; + } +} diff --git a/scouter.webapp/src/main/java/scouterx/webapp/main/WebAppMain.java b/scouter.webapp/src/main/java/scouterx/webapp/main/WebAppMain.java index c3c531cf4..465bb4ec5 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/main/WebAppMain.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/main/WebAppMain.java @@ -26,6 +26,7 @@ import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; import org.glassfish.jersey.servlet.ServletContainer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,9 +45,13 @@ import scouterx.webapp.framework.filter.LoggingInitServletFilter; import scouterx.webapp.framework.filter.NoCacheFilter; import scouterx.webapp.framework.filter.ReleaseResourceFilter; +import scouterx.webapp.layer.websock.BasicSocket; import scouterx.webapp.swagger.Bootstrap; import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; import javax.ws.rs.core.Application; import java.io.File; import java.util.EnumSet; @@ -90,6 +95,8 @@ public static void main(String[] args) throws Exception { handlers.addHandler(servletContextHandler); server.setHandler(handlers); + setWebSocketServer(servletContextHandler); + try { server.start(); @@ -99,6 +106,14 @@ public static void main(String[] args) throws Exception { } } + public static void setWebSocketServer(ServletContextHandler servletContextHandler) throws ServletException, DeploymentException { + // Add javax.websocket support + ServerContainer container = WebSocketServerContainerInitializer.configureContext(servletContextHandler); + container.setDefaultMaxSessionIdleTimeout(7*24*3600*1000); + // Add echo endpoint to server container + container.addEndpoint(BasicSocket.class); + } + private static ServletContextHandler setWebHttpApiHandler () { ConfigureAdaptor conf = ConfigureManager.getConfigure(); @@ -241,12 +256,14 @@ private static void initializeLogDir() { * (This method also can be invoked from scouter.server's Http Server when this webapp runs as an embedded mode.) * */ - public static ServletContextHandler setWebAppContext() { + public static ServletContextHandler setWebAppContext() throws ServletException, DeploymentException { //The case - embedded mode (run in-process of scouter server) if (!standAloneMode) { initializeLogDir(); connectScouterCollector(); } - return setWebHttpApiHandler(); + ServletContextHandler handler = setWebHttpApiHandler(); + + return handler; } } diff --git a/scouter.webapp/src/main/java/scouterx/webapp/model/ProfileStepData.java b/scouter.webapp/src/main/java/scouterx/webapp/model/ProfileStepData.java index 64ef2aa2d..f500a71dd 100644 --- a/scouter.webapp/src/main/java/scouterx/webapp/model/ProfileStepData.java +++ b/scouter.webapp/src/main/java/scouterx/webapp/model/ProfileStepData.java @@ -25,13 +25,16 @@ import scouter.lang.step.DispatchStep; import scouter.lang.step.DumpStep; import scouter.lang.step.HashedMessageStep; +import scouter.lang.step.MessageStep; import scouter.lang.step.MethodStep; import scouter.lang.step.ParameterizedMessageStep; +import scouter.lang.step.SocketStep; import scouter.lang.step.SqlStep; import scouter.lang.step.Step; import scouter.lang.step.StepEnum; import scouter.lang.step.ThreadCallPossibleStep; import scouter.lang.step.ThreadSubmitStep; +import scouter.util.IPUtil; import scouterx.webapp.framework.client.model.TextLoader; import scouterx.webapp.framework.client.model.TextModel; import scouterx.webapp.framework.client.model.TextTypeEnum; @@ -125,7 +128,8 @@ private static String getStepMainValue(Step step, long date, int serverId) { mainValue = textTypeEnum.getTextModel().getTextIfNullDefault(date, ((HashedMessageStep) step).getHash(), serverId); break; case PARAMETERIZED_MESSAGE: - mainValue = textTypeEnum.getTextModel().getTextIfNullDefault(date, ((ParameterizedMessageStep) step).getHash(), serverId); + ParameterizedMessageStep pmStep = (ParameterizedMessageStep) step; + mainValue = pmStep.buildMessasge(textTypeEnum.getTextModel().getTextIfNullDefault(date, pmStep.getHash(), serverId)); break; case DISPATCH: mainValue = textTypeEnum.getTextModel().getTextIfNullDefault(date, ((DispatchStep) step).getHash(), serverId); @@ -136,7 +140,10 @@ private static String getStepMainValue(Step step, long date, int serverId) { case DUMP: break; case MESSAGE: + mainValue = ((MessageStep) step).getMessage(); + break; case SOCKET: + mainValue = IPUtil.toString(((SocketStep) step).getIpaddr()); default: break; } @@ -152,7 +159,7 @@ private static List getStepAdditionalValue(Step step, long date, int ser case DUMP: DumpStep dumpStep = (DumpStep) step; for (int stackHash : dumpStep.stacks) { - valueList.add(textTypeEnum.getTextModel().getTextIfNullDefault(date, stackHash, serverId)); + valueList.add(TextTypeEnum.STACK_ELEMENT.getTextModel().getTextIfNullDefault(date, stackHash, serverId)); } break; @@ -220,7 +227,7 @@ private static void addAdditionalValueHashesToTextLoader(Step step, TextLoader t case DUMP: DumpStep dumpStep = (DumpStep) step; for (int stackHash : dumpStep.stacks) { - textLoader.addTextHash(textTypeEnum, stackHash); + textLoader.addTextHash(TextTypeEnum.STACK_ELEMENT, stackHash); } break; diff --git a/scouter.webapp/src/main/java/scouterx/webapp/request/MultiXLogRequest.java b/scouter.webapp/src/main/java/scouterx/webapp/request/MultiXLogRequest.java new file mode 100644 index 000000000..2b0c9bd30 --- /dev/null +++ b/scouter.webapp/src/main/java/scouterx/webapp/request/MultiXLogRequest.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 scouterx.webapp.request; + +import lombok.Getter; +import lombok.Setter; +import scouterx.webapp.framework.client.server.ServerManager; +import scouterx.webapp.framework.util.ZZ; + +import javax.validation.constraints.NotNull; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; +import java.util.List; + +/** + * Created by jaco.ryu on 2017. 10. 13.. + */ +@Getter +@Setter +public class MultiXLogRequest { + int serverId; + + List txidList; + + @NotNull + @PathParam("yyyymmdd") + String yyyymmdd; + + @PathParam("txidList") + public void setTxidList(String txids) { + this.txidList = ZZ.splitParamAsLong(txids); + } + + @QueryParam("serverId") + public void setServerId(int serverId) { + this.serverId = ServerManager.getInstance().getServerIfNullDefault(serverId).getId(); + } + + +} diff --git a/scouter.webapp/src/main/resources/webroot/test/ws/index.html b/scouter.webapp/src/main/resources/webroot/test/ws/index.html new file mode 100644 index 000000000..2a8eb355a --- /dev/null +++ b/scouter.webapp/src/main/resources/webroot/test/ws/index.html @@ -0,0 +1,21 @@ + + + Jetty WebSocket Echo Examples + + + + + Jetty WebSocket Echo Examples #console +

+
+ + + +
+ + + \ No newline at end of file diff --git a/scouter.webapp/src/main/resources/webroot/test/ws/main.css b/scouter.webapp/src/main/resources/webroot/test/ws/main.css new file mode 100644 index 000000000..9eebead46 --- /dev/null +++ b/scouter.webapp/src/main/resources/webroot/test/ws/main.css @@ -0,0 +1,29 @@ +body { + font-family: sans-serif; +} + +div { + border: 0px solid black; +} + +div#console { + clear: both; + width: 40em; + height: 20em; + overflow: auto; + background-color: #f0f0f0; + padding: 4px; + border: 1px solid black; +} + +div#console .info { + color: black; +} + +div#console .client { + color: blue; +} + +div#console .server { + color: magenta; +} diff --git a/scouter.webapp/src/main/resources/webroot/test/ws/websocket.js b/scouter.webapp/src/main/resources/webroot/test/ws/websocket.js new file mode 100644 index 000000000..2619d9228 --- /dev/null +++ b/scouter.webapp/src/main/resources/webroot/test/ws/websocket.js @@ -0,0 +1,125 @@ +if (!window.WebSocket && window.MozWebSocket) { + window.WebSocket = window.MozWebSocket; +} + +if (!window.WebSocket) { + alert("WebSocket not supported by this browser"); +} + +function $() { + return document.getElementById(arguments[0]); +} +function $F() { + return document.getElementById(arguments[0]).value; +} + +function getKeyCode(ev) { + if (window.event) + return window.event.keyCode; + return ev.keyCode; +} + +var wstool = { + connect : function() { + var location = 'ws://' + document.location.host + "/basic"; + + wstool.info("Document URI: " + document.location); + wstool.info("WS URI: " + location); + + this._scount = 0; + + try { + this._ws = new WebSocket(location); + this._ws.onopen = this._onopen; + this._ws.onmessage = this._onmessage; + this._ws.onclose = this._onclose; + } catch (exception) { + wstool.info("Connect Error: " + exception); + } + }, + + close : function() { + this._ws.close(1000); + }, + + _out : function(css, message) { + var console = $('console'); + var spanText = document.createElement('span'); + spanText.className = 'text ' + css; + spanText.innerHTML = message; + var lineBreak = document.createElement('br'); + console.appendChild(spanText); + console.appendChild(lineBreak); + console.scrollTop = console.scrollHeight - console.clientHeight; + }, + + setState : function(enabled) { + $('connect').disabled = enabled; + $('close').disabled = !enabled; + $('hello').disabled = !enabled; + }, + + info : function(message) { + wstool._out("info", message); + }, + + error : function(message) { + wstool._out("error", message); + }, + + infoc : function(message) { + wstool._out("client", "[c] " + message); + }, + + infos : function(message) { + this._scount++; + wstool._out("server", "[s" + this._scount + "] " + message); + }, + + _onopen : function() { + wstool.setState(true); + wstool.info("Websocket Connected"); + }, + + _send : function(message) { + if (this._ws) { + this._ws.send(message); + wstool.infoc(message); + } + }, + + write : function(text) { + wstool._send(text); + }, + + _onmessage : function(m) { + if (m.data) { + wstool.infos(m.data); + } + }, + + _onclose : function(closeEvent) { + this._ws = null; + wstool.setState(false); + wstool.info("Websocket Closed"); + wstool.info(" .wasClean = " + closeEvent.wasClean); + + var codeMap = {}; + codeMap[1000] = "(NORMAL)"; + codeMap[1001] = "(ENDPOINT_GOING_AWAY)"; + codeMap[1002] = "(PROTOCOL_ERROR)"; + codeMap[1003] = "(UNSUPPORTED_DATA)"; + codeMap[1004] = "(UNUSED/RESERVED)"; + codeMap[1005] = "(INTERNAL/NO_CODE_PRESENT)"; + codeMap[1006] = "(INTERNAL/ABNORMAL_CLOSE)"; + codeMap[1007] = "(BAD_DATA)"; + codeMap[1008] = "(POLICY_VIOLATION)"; + codeMap[1009] = "(MESSAGE_TOO_BIG)"; + codeMap[1010] = "(HANDSHAKE/EXT_FAILURE)"; + codeMap[1011] = "(SERVER/UNEXPECTED_CONDITION)"; + codeMap[1015] = "(INTERNAL/TLS_ERROR)"; + var codeStr = codeMap[closeEvent.code]; + wstool.info(" .code = " + closeEvent.code + " " + codeStr); + wstool.info(" .reason = " + closeEvent.reason); + } +}; diff --git a/scouter.webapp/src/test/java/scouterx/webapp/framework/session/UserTokenTest.java b/scouter.webapp/src/test/java/scouterx/webapp/framework/session/UserTokenTest.java index 97888c7c5..2c5837f06 100644 --- a/scouter.webapp/src/test/java/scouterx/webapp/framework/session/UserTokenTest.java +++ b/scouter.webapp/src/test/java/scouterx/webapp/framework/session/UserTokenTest.java @@ -33,7 +33,7 @@ public void toStoreValue_test() { UserToken unmarshalled = UserToken.fromStoreValue(toStoreValue, token.getServerId()); assertEquals(unmarshalled.getFootprintSec(), token.getFootprintSec()); - assertEquals(unmarshalled.getId(), token.getId()); + assertEquals(unmarshalled.getUserId(), token.getUserId()); assertEquals(unmarshalled.getToken(), token.getToken()); } @@ -45,7 +45,7 @@ public void toBearerToken_test() { assertTrue(token.getFootprintSec() > 0); assertTrue(unmarshalled.getFootprintSec() == 0); - assertEquals(unmarshalled.getId(), token.getId()); + assertEquals(unmarshalled.getUserId(), token.getUserId()); assertEquals(unmarshalled.getToken(), token.getToken()); } diff --git a/scouter.webapp/src/test/java/scouterx/webapp/layer/service/UserTokenServiceTest.java b/scouter.webapp/src/test/java/scouterx/webapp/layer/service/UserTokenServiceTest.java new file mode 100644 index 000000000..c27e2f628 --- /dev/null +++ b/scouter.webapp/src/test/java/scouterx/webapp/layer/service/UserTokenServiceTest.java @@ -0,0 +1,113 @@ +/* + * 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 scouterx.webapp.layer.service; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import scouterx.lib3.tomcat.SessionIdGenerator; +import scouterx.webapp.framework.client.server.Server; +import scouterx.webapp.framework.client.server.ServerManager; +import scouterx.webapp.framework.session.UserToken; +import scouterx.webapp.model.scouter.SUser; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.when; + +/** + * @author Gun Lee (gunlee01@gmail.com) on 2018. 3. 11. + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ServerManager.class}) +public class UserTokenServiceTest { + String vutUserId = "junit-user"; + + @Mock + CustomKvStoreService customKvStoreService; + @Mock + Server server; + @Mock + ServerManager serverManager; + + SessionIdGenerator sessionIdGenerator = new SessionIdGenerator(); + + @InjectMocks + UserTokenService sut = new UserTokenService(); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mockStatic(ServerManager.class); + when(ServerManager.getInstance()).thenReturn(serverManager); + } + + @Test + public void publishToken() { + String bearer = sut.publishToken(server, new SUser(vutUserId)); + UserToken fromBearer = UserToken.fromBearerToken(bearer); + assertEquals(vutUserId, fromBearer.getUserId()); + } + + @Test + public void validateToken() { + String bearer = sut.publishToken(server, new SUser(vutUserId)); + UserToken fromBearer = UserToken.fromBearerToken(bearer); + + sut.validateToken(fromBearer); + } + + @Test + public void getAndMergeToStoredValue() { + UserToken userToken = UserToken.newToken(vutUserId, sessionIdGenerator.generateSessionId(), server.getId()); + + UserToken token0 = UserToken.newToken(vutUserId, sessionIdGenerator.generateSessionId(), server.getId()); + UserToken token1 = UserToken.newToken(vutUserId, sessionIdGenerator.generateSessionId(), server.getId()); + UserToken token2 = UserToken.newToken(vutUserId, sessionIdGenerator.generateSessionId(), server.getId()); + + String tokens = token0.toStoreValue() + ":" + token1.toStoreValue() + ":" + token2.toStoreValue(); + String merged = sut.mergeStoredTokensWith(tokens, userToken); + + assertTrue(merged.contains(userToken.getToken())); + } + + @Test + public void getAndMergeToStoredValue_with_expired_tokens() { + UserToken userToken = UserToken.newToken(vutUserId, sessionIdGenerator.generateSessionId(), server.getId()); + + UserToken token0 = UserToken.newToken(vutUserId, sessionIdGenerator.generateSessionId(), server.getId()); + UserToken token1 = UserToken.newToken(vutUserId, sessionIdGenerator.generateSessionId(), server.getId()); + UserToken token2 = UserToken.newToken(vutUserId, sessionIdGenerator.generateSessionId(), server.getId()); + + token0.setFootprintSec(System.currentTimeMillis()/1000L - sut.sessionExpireSec - 1); + token1.setFootprintSec(System.currentTimeMillis()/1000L - sut.sessionExpireSec - 1); + token2.setFootprintSec(System.currentTimeMillis()/1000L - sut.sessionExpireSec - 1); + + String tokens = token0.toStoreValue() + ":" + token1.toStoreValue() + ":" + token2.toStoreValue(); + String merged = sut.mergeStoredTokensWith(tokens, userToken); + + assertEquals(merged, userToken.toStoreValue()); + } +} \ No newline at end of file