diff --git a/CHANGES.md b/CHANGES.md index 8e1d7e30b..b73e3c78c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ Release Notes. * Fix NullPointerException with KafkaProducer.send(record). * Support config `agent.span_limit_per_segment` can be changed in the runtime. * Collect and report agent starting / shutdown events. +* Support jedis pipeline in jedis-2.x-plugin. #### OAP-Backend * Allow user-defined `JAVA_OPTS` in the startup script. diff --git a/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v2/PipelineSetClientMethodInterceptor.java b/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v2/PipelineSetClientMethodInterceptor.java new file mode 100644 index 000000000..2d8a0afaf --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v2/PipelineSetClientMethodInterceptor.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jedis.v2; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import redis.clients.jedis.Client; + +import java.lang.reflect.Method; + +public class PipelineSetClientMethodInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + Client client = (Client) allArguments[0]; + + objInst.setSkyWalkingDynamicField(client.getHost() + ":" + client.getPort()); + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + AbstractSpan span = ContextManager.activeSpan(); + span.log(t); + } +} \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v2/define/MultiKeyPipelineBaseInstrumentation.java b/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v2/define/MultiKeyPipelineBaseInstrumentation.java new file mode 100644 index 000000000..d7c4c008c --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v2/define/MultiKeyPipelineBaseInstrumentation.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jedis.v2.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.plugin.jedis.v2.RedisMethodMatch; + +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +public class MultiKeyPipelineBaseInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "redis.clients.jedis.MultiKeyPipelineBase"; + private static final String JEDIS_METHOD_INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.jedis.v2.JedisMethodInterceptor"; + + @Override + public ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return RedisMethodMatch.INSTANCE.getJedisMethodMatcher(); + } + + @Override + public String getMethodsInterceptor() { + return JEDIS_METHOD_INTERCEPT_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v2/define/PipelineBaseInstrumentation.java b/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v2/define/PipelineBaseInstrumentation.java new file mode 100644 index 000000000..114f5e108 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v2/define/PipelineBaseInstrumentation.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jedis.v2.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.plugin.jedis.v2.RedisMethodMatch; + +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +public class PipelineBaseInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "redis.clients.jedis.PipelineBase"; + private static final String JEDIS_METHOD_INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.jedis.v2.JedisMethodInterceptor"; + + @Override + public ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return RedisMethodMatch.INSTANCE.getJedisMethodMatcher(); + } + + @Override + public String getMethodsInterceptor() { + return JEDIS_METHOD_INTERCEPT_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v2/define/PipelineInstrumentation.java b/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v2/define/PipelineInstrumentation.java new file mode 100644 index 000000000..673df65dd --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jedis/v2/define/PipelineInstrumentation.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jedis.v2.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; + +public class PipelineInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "redis.clients.jedis.Pipeline"; + private static final String PIPELINE_SET_CLIENT_METHOD_INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.jedis.v2.PipelineSetClientMethodInterceptor"; + + @Override + public ClassMatch enhanceClass() { + return byName(ENHANCE_CLASS); + } + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return new ConstructorInterceptPoint[0]; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + + @Override + public ElementMatcher getMethodsMatcher() { + return named("setClient"); + } + + @Override + public String getMethodsInterceptor() { + return PIPELINE_SET_CLIENT_METHOD_INTERCEPT_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } +} diff --git a/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/main/resources/skywalking-plugin.def index 49eb102d1..fcff84cbe 100644 --- a/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/main/resources/skywalking-plugin.def +++ b/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/main/resources/skywalking-plugin.def @@ -16,3 +16,8 @@ jedis-2.x=org.apache.skywalking.apm.plugin.jedis.v2.define.JedisClusterInstrumentation jedis-2.x=org.apache.skywalking.apm.plugin.jedis.v2.define.JedisInstrumentation +jedis-2.x=org.apache.skywalking.apm.plugin.jedis.v2.define.PipelineInstrumentation +jedis-2.x=org.apache.skywalking.apm.plugin.jedis.v2.define.PipelineBaseInstrumentation +jedis-2.x=org.apache.skywalking.apm.plugin.jedis.v2.define.MultiKeyPipelineBaseInstrumentation + + diff --git a/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jedis/v2/PipelineSetClientMethodInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jedis/v2/PipelineSetClientMethodInterceptorTest.java new file mode 100644 index 000000000..137d33d24 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/jedis-2.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jedis/v2/PipelineSetClientMethodInterceptorTest.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.plugin.jedis.v2; + +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.modules.junit4.PowerMockRunner; +import redis.clients.jedis.Client; +import redis.clients.jedis.Pipeline; + +import java.lang.reflect.Method; + +import static org.mockito.Mockito.verify; + +@RunWith(PowerMockRunner.class) +public class PipelineSetClientMethodInterceptorTest { + + private PipelineSetClientMethodInterceptor interceptor; + + @Mock + private EnhancedInstance enhancedInstance; + + private Object[] allArgument; + + private Class[] argumentType; + + @Before + public void setUp() throws Exception { + interceptor = new PipelineSetClientMethodInterceptor(); + allArgument = new Object[] { + new Client("127.0.0.1", 6379) + }; + argumentType = new Class[] { + Client.class + }; + } + + @Test + public void onConstruct() throws Throwable { + interceptor.beforeMethod(enhancedInstance, getMockSetClientMethod(), allArgument, argumentType, null); + + verify(enhancedInstance).setSkyWalkingDynamicField("127.0.0.1:6379"); + } + + private Method getMockSetClientMethod() { + try { + return Pipeline.class.getMethod("setClient", Client.class); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + return null; + } + } +} \ No newline at end of file diff --git a/test/plugin/scenarios/jedis-scenario/config/expectedData.yaml b/test/plugin/scenarios/jedis-scenario/config/expectedData.yaml index 6dc94517c..4bb507413 100644 --- a/test/plugin/scenarios/jedis-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/jedis-scenario/config/expectedData.yaml @@ -79,6 +79,51 @@ segmentItems: - {key: db.type, value: Redis} - {key: db.statement, value: del a} skipAnalysis: 'false' + - operationName: Jedis/hset + operationId: 0 + parentSpanId: 0 + spanId: 5 + spanLayer: Cache + startTime: nq 0 + endTime: nq 0 + componentId: 30 + isError: false + spanType: Exit + peer: redis-server:6379 + tags: + - {key: db.type, value: Redis} + - {key: db.statement, value: hset a} + skipAnalysis: 'false' + - operationName: Jedis/hget + operationId: 0 + parentSpanId: 0 + spanId: 6 + spanLayer: Cache + startTime: nq 0 + endTime: nq 0 + componentId: 30 + isError: false + spanType: Exit + peer: redis-server:6379 + tags: + - {key: db.type, value: Redis} + - {key: db.statement, value: hget a} + skipAnalysis: 'false' + - operationName: Jedis/hdel + operationId: 0 + parentSpanId: 0 + spanId: 7 + spanLayer: Cache + startTime: nq 0 + endTime: nq 0 + componentId: 30 + isError: false + spanType: Exit + peer: redis-server:6379 + tags: + - {key: db.type, value: Redis} + - {key: db.statement, value: hdel a} + skipAnalysis: 'false' - operationName: /jedis-scenario/case/jedis-scenario operationId: 0 parentSpanId: -1 diff --git a/test/plugin/scenarios/jedis-scenario/src/main/java/org/apache/skywalking/apm/testcase/jedis/controller/CaseController.java b/test/plugin/scenarios/jedis-scenario/src/main/java/org/apache/skywalking/apm/testcase/jedis/controller/CaseController.java index e38b2b2a3..53a0f6c79 100644 --- a/test/plugin/scenarios/jedis-scenario/src/main/java/org/apache/skywalking/apm/testcase/jedis/controller/CaseController.java +++ b/test/plugin/scenarios/jedis-scenario/src/main/java/org/apache/skywalking/apm/testcase/jedis/controller/CaseController.java @@ -43,6 +43,10 @@ public class CaseController { command.get("a"); command.del("a"); } + + try (RedisPipelineCommandExecutor command = new RedisPipelineCommandExecutor(redisHost, redisPort)) { + command.pipelineExecute(); + } return SUCCESS; } diff --git a/test/plugin/scenarios/jedis-scenario/src/main/java/org/apache/skywalking/apm/testcase/jedis/controller/RedisPipelineCommandExecutor.java b/test/plugin/scenarios/jedis-scenario/src/main/java/org/apache/skywalking/apm/testcase/jedis/controller/RedisPipelineCommandExecutor.java new file mode 100644 index 000000000..f90a270ef --- /dev/null +++ b/test/plugin/scenarios/jedis-scenario/src/main/java/org/apache/skywalking/apm/testcase/jedis/controller/RedisPipelineCommandExecutor.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.skywalking.apm.testcase.jedis.controller; + +import redis.clients.jedis.Jedis; +import redis.clients.jedis.Pipeline; + +public class RedisPipelineCommandExecutor implements AutoCloseable { + private Jedis jedis; + + public RedisPipelineCommandExecutor(String host, Integer port) { + jedis = new Jedis(host, port); + } + + public void pipelineExecute() { + Pipeline pipeline = jedis.pipelined(); + pipeline.hset("a", "a", "a"); + pipeline.hget("a", "a"); + pipeline.hdel("a", "a"); + pipeline.syncAndReturnAll(); + } + + public void close() throws Exception { + jedis.close(); + } +}