diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 20e2bae6d2..e2078dd4ab 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -42,7 +42,7 @@ jobs:
build:
name: Java ${{ matrix.java-version }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}-latest
- timeout-minutes: 60
+ timeout-minutes: 90
needs: [ license ]
strategy:
fail-fast: true
diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml
index 0932d63cee..1abc057ad0 100644
--- a/.github/workflows/codeql.yaml
+++ b/.github/workflows/codeql.yaml
@@ -54,11 +54,11 @@ jobs:
java-version: 17
- name: Initialize CodeQL
- uses: github/codeql-action/init@v1
+ uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- run: ./mvnw -q -Dmaven.test.skip=true clean install || ./mvnw -q -Dmaven.test.skip=true clean install
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ uses: github/codeql-action/analyze@v3
diff --git a/.github/workflows/plugins-jdk17-test.1.yaml b/.github/workflows/plugins-jdk17-test.1.yaml
index 949e69cfa1..80ff29bb67 100644
--- a/.github/workflows/plugins-jdk17-test.1.yaml
+++ b/.github/workflows/plugins-jdk17-test.1.yaml
@@ -63,6 +63,7 @@ jobs:
- c3p0-0.9.0.x-0.9.1.x-scenario
- c3p0-0.9.2.x-0.10.x-scenario
- spring-scheduled-6.x-scenario
+ - caffeine-3.x-scenario
steps:
- uses: actions/checkout@v2
with:
diff --git a/CHANGES.md b/CHANGES.md
index 5e959990cd..8a74479321 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -25,6 +25,7 @@ Release Notes.
Plugin, Kotlin Coroutine Plugin, and Spring Gateway Plugin
* Change context and parent entry span propagation mechanism from gRPC ThreadLocal context to SkyWalking native dynamic
field as new propagation mechanism, to better support async scenarios.
+* Add Caffeine plugin as optional.
All issues and pull requests are [here](https://github.com/apache/skywalking/milestone/222?closed=1)
diff --git a/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java b/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java
index 101abcdb5e..e10b207ce5 100755
--- a/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java
+++ b/apm-protocol/apm-network/src/main/java/org/apache/skywalking/apm/network/trace/component/ComponentsDefine.java
@@ -259,4 +259,5 @@ public class ComponentsDefine {
public static final OfficialComponent SOLON_MVC = new OfficialComponent(158, "SolonMVC");
+ public static final OfficialComponent CAFFEINE = new OfficialComponent(160, "Caffeine");
}
diff --git a/apm-sniffer/config/agent.config b/apm-sniffer/config/agent.config
index 695e47349f..f27572a1e0 100755
--- a/apm-sniffer/config/agent.config
+++ b/apm-sniffer/config/agent.config
@@ -332,3 +332,7 @@ plugin.solon.http_params_length_threshold=${SW_PLUGIN_SOLON_HTTP_PARAMS_LENGTH_T
plugin.solon.include_http_headers=${SW_PLUGIN_SOLON_INCLUDE_HTTP_HEADERS:}
# Define the max length of collected HTTP body. The default value(=0) means not collecting.
plugin.solon.http_body_length_threshold=${SW_PLUGIN_SOLON_HTTP_BODY_LENGTH_THRESHOLD:0}
+# Specify which command should be converted to write operation
+plugin.caffeine.operation_mapping_write=${SW_PLUGIN_CAFFEINE_OPERATION_MAPPING_WRITE:put,putAll,remove,clear}
+# Specify which command should be converted to read operation
+plugin.caffeine.operation_mapping_read=${SW_PLUGIN_CAFFEINE_OPERATION_MAPPING_READ:getIfPresent,getAllPresent,computeIfAbsent}
diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/pom.xml b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/pom.xml
new file mode 100644
index 0000000000..3170ed81a2
--- /dev/null
+++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/pom.xml
@@ -0,0 +1,46 @@
+
+
+
+
+ 4.0.0
+
+ org.apache.skywalking
+ optional-plugins
+ 9.4.0-SNAPSHOT
+
+
+ apm-caffeine-3.x-plugin
+ jar
+ caffeine-3.x-plugin
+
+
+ UTF-8
+ 3.1.8
+
+
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+ ${caffeine.version}
+ provided
+
+
+
diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/AbstractCaffeineInterceptor.java b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/AbstractCaffeineInterceptor.java
new file mode 100644
index 0000000000..9c66cabd08
--- /dev/null
+++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/AbstractCaffeineInterceptor.java
@@ -0,0 +1,71 @@
+/*
+ * 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.caffeine.v3;
+
+import java.lang.reflect.Method;
+import org.apache.skywalking.apm.agent.core.context.ContextManager;
+import org.apache.skywalking.apm.agent.core.context.tag.Tags;
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
+import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
+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 org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
+
+import static org.apache.skywalking.apm.plugin.caffeine.v3.CaffeineOperationTransform.transformOperation;
+
+abstract public class AbstractCaffeineInterceptor implements InstanceMethodsAroundInterceptor {
+
+ protected AbstractSpan generateSpanInfo(String methodName) {
+ AbstractSpan span = ContextManager.createLocalSpan("Caffeine/" + methodName);
+ span.setComponent(ComponentsDefine.CAFFEINE);
+ Tags.CACHE_TYPE.set(span, ComponentsDefine.CAFFEINE.getName());
+ Tags.CACHE_CMD.set(span, methodName);
+ transformOperation(methodName).ifPresent(op -> Tags.CACHE_OP.set(span, op));
+ SpanLayer.asCache(span);
+ return span;
+ }
+
+ @Override
+ public void beforeMethod(final EnhancedInstance objInst,
+ final Method method,
+ final Object[] allArguments,
+ final Class>[] argumentsTypes,
+ final MethodInterceptResult result) throws Throwable {
+ }
+
+ @Override
+ public Object afterMethod(final EnhancedInstance objInst,
+ final Method method,
+ final Object[] allArguments,
+ final Class>[] argumentsTypes,
+ final Object ret) throws Throwable {
+ ContextManager.stopSpan();
+ return ret;
+ }
+
+ @Override
+ public void handleMethodException(final EnhancedInstance objInst,
+ final Method method,
+ final Object[] allArguments,
+ final Class>[] argumentsTypes,
+ final Throwable t) {
+ ContextManager.activeSpan().log(t);
+ }
+}
diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineIterableInterceptor.java b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineIterableInterceptor.java
new file mode 100644
index 0000000000..ef5860e46f
--- /dev/null
+++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineIterableInterceptor.java
@@ -0,0 +1,46 @@
+/*
+ * 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.caffeine.v3;
+
+import java.lang.reflect.Method;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+import org.apache.skywalking.apm.agent.core.context.tag.Tags;
+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.MethodInterceptResult;
+
+public class CaffeineIterableInterceptor extends AbstractCaffeineInterceptor {
+
+ @Override
+ public void beforeMethod(final EnhancedInstance objInst,
+ final Method method,
+ final Object[] allArguments,
+ final Class>[] argumentsTypes,
+ final MethodInterceptResult result) throws Throwable {
+ AbstractSpan span = generateSpanInfo(method.getName());
+ if (allArguments != null && allArguments.length > 0) {
+ String keys = StreamSupport
+ .stream(((Iterable>) allArguments[0]).spliterator(), false)
+ .map(String::valueOf)
+ .collect(Collectors.joining(","));
+ Tags.CACHE_KEY.set(span, keys);
+ }
+ }
+}
diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineMapInterceptor.java b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineMapInterceptor.java
new file mode 100644
index 0000000000..98258bbb38
--- /dev/null
+++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineMapInterceptor.java
@@ -0,0 +1,45 @@
+/*
+ * 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.caffeine.v3;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.skywalking.apm.agent.core.context.tag.Tags;
+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.MethodInterceptResult;
+
+public class CaffeineMapInterceptor extends AbstractCaffeineInterceptor {
+
+ @Override
+ public void beforeMethod(final EnhancedInstance objInst,
+ final Method method,
+ final Object[] allArguments,
+ final Class>[] argumentsTypes,
+ final MethodInterceptResult result) throws Throwable {
+ AbstractSpan span = generateSpanInfo(method.getName());
+ if (allArguments != null && allArguments.length > 0) {
+ String keys = ((Map, ?>) allArguments[0])
+ .keySet().stream().map(String::valueOf)
+ .collect(Collectors.joining(","));
+ Tags.CACHE_KEY.set(span, keys);
+ }
+ }
+}
diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineOperationTransform.java b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineOperationTransform.java
new file mode 100644
index 0000000000..0f4a128a92
--- /dev/null
+++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineOperationTransform.java
@@ -0,0 +1,34 @@
+/*
+ * 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.caffeine.v3;
+
+import java.util.Optional;
+
+public class CaffeineOperationTransform {
+
+ public static Optional transformOperation(String cmd) {
+ if (CaffeinePluginConfig.Plugin.Caffeine.OPERATION_MAPPING_READ.contains(cmd)) {
+ return Optional.of("read");
+ }
+ if (CaffeinePluginConfig.Plugin.Caffeine.OPERATION_MAPPING_WRITE.contains(cmd)) {
+ return Optional.of("write");
+ }
+ return Optional.empty();
+ }
+}
diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeinePluginConfig.java b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeinePluginConfig.java
new file mode 100644
index 0000000000..f775668bee
--- /dev/null
+++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeinePluginConfig.java
@@ -0,0 +1,50 @@
+/*
+ * 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.caffeine.v3;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.skywalking.apm.agent.core.boot.PluginConfig;
+
+/**
+ * Operation represent a cache span is "write" or "read" action , and "op"(operation) is tagged with key "cache.op"
+ * usually This config term define which command should be converted to write Operation .
+ *
+ * @see org.apache.skywalking.apm.agent.core.context.tag.Tags#CACHE_OP
+ * @see CaffeineOperationTransform#transformOperation(String)
+ */
+public class CaffeinePluginConfig {
+ public static class Plugin {
+ @PluginConfig(root = CaffeinePluginConfig.class)
+ public static class Caffeine {
+ public static Set OPERATION_MAPPING_WRITE = new HashSet<>(Arrays.asList(
+ "put",
+ "putAll",
+ "remove",
+ "clear"
+ ));
+ public static Set OPERATION_MAPPING_READ = new HashSet<>(Arrays.asList(
+ "getIfPresent",
+ "getAllPresent",
+ "computeIfAbsent"
+ ));
+ }
+ }
+}
\ No newline at end of file
diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineStringInterceptor.java b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineStringInterceptor.java
new file mode 100644
index 0000000000..82b34ca48d
--- /dev/null
+++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineStringInterceptor.java
@@ -0,0 +1,40 @@
+/*
+ * 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.caffeine.v3;
+
+import java.lang.reflect.Method;
+import org.apache.skywalking.apm.agent.core.context.tag.Tags;
+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.MethodInterceptResult;
+
+public class CaffeineStringInterceptor extends AbstractCaffeineInterceptor {
+
+ @Override
+ public void beforeMethod(final EnhancedInstance objInst,
+ final Method method,
+ final Object[] allArguments,
+ final Class>[] argumentsTypes,
+ final MethodInterceptResult result) throws Throwable {
+ AbstractSpan span = generateSpanInfo(method.getName());
+ if (allArguments != null && allArguments.length > 0 && allArguments[0] instanceof String) {
+ Tags.CACHE_KEY.set(span, allArguments[0].toString());
+ }
+ }
+}
diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/define/CaffeineInstrumentation.java b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/define/CaffeineInstrumentation.java
new file mode 100644
index 0000000000..50e5b3751a
--- /dev/null
+++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/caffeine/v3/define/CaffeineInstrumentation.java
@@ -0,0 +1,119 @@
+/*
+ * 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.caffeine.v3.define;
+
+import java.util.Map;
+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 net.bytebuddy.matcher.ElementMatchers.takesArgument;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+import static org.apache.skywalking.apm.agent.core.plugin.match.MultiClassNameMatch.byMultiClassMatch;
+
+public class CaffeineInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
+
+ public static final String BOUNDED_LOCAL_INTERCEPT_CLASS = "com.github.benmanes.caffeine.cache.BoundedLocalCache";
+ public static final String UNBOUNDED_LOCAL_INTERCEPT_CLASS = "com.github.benmanes.caffeine.cache.UnboundedLocalCache";
+ public static final String CAFFEINE_ITERABLE_ENHANCE_CLASS = "org.apache.skywalking.apm.plugin.caffeine.v3.CaffeineIterableInterceptor";
+ public static final String CAFFEINE_MAP_ENHANCE_CLASS = "org.apache.skywalking.apm.plugin.caffeine.v3.CaffeineMapInterceptor";
+ public static final String CAFFEINE_STRING_ENHANCE_CLASS = "org.apache.skywalking.apm.plugin.caffeine.v3.CaffeineStringInterceptor";
+
+ // read/write operations
+ public static final String GET_IF_PRESENT_METHOD = "getIfPresent";
+ public static final String GET_ALL_PRESENT_METHOD = "getAllPresent";
+ public static final String COMPUTE_IF_ABSENT_METHOD = "computeIfAbsent";
+ public static final String PUT_METHOD = "put";
+ public static final String PUT_ALL_METHOD = "putAll";
+ public static final String REMOVE_METHOD = "remove";
+ public static final String CLEAR_METHOD = "clear";
+
+ @Override
+ protected ClassMatch enhanceClass() {
+ return byMultiClassMatch(BOUNDED_LOCAL_INTERCEPT_CLASS, UNBOUNDED_LOCAL_INTERCEPT_CLASS);
+ }
+
+ @Override
+ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
+ return new ConstructorInterceptPoint[0];
+ }
+
+ @Override
+ public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
+ return new InstanceMethodsInterceptPoint[] {
+ new InstanceMethodsInterceptPoint() {
+ @Override
+ public ElementMatcher getMethodsMatcher() {
+ return named(GET_IF_PRESENT_METHOD)
+ .and(takesArguments(2))
+ .or(named(COMPUTE_IF_ABSENT_METHOD).and(takesArguments(4)))
+ .or(named(PUT_METHOD).and(takesArguments(2)))
+ .or(named(REMOVE_METHOD).and(takesArguments(1)))
+ .or(named(CLEAR_METHOD).and(takesArguments(0)));
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return CAFFEINE_STRING_ENHANCE_CLASS;
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return false;
+ }
+ },
+ new InstanceMethodsInterceptPoint() {
+ @Override
+ public ElementMatcher getMethodsMatcher() {
+ return named(GET_ALL_PRESENT_METHOD).and(takesArgument(0, Iterable.class));
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return CAFFEINE_ITERABLE_ENHANCE_CLASS;
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return false;
+ }
+ },
+ new InstanceMethodsInterceptPoint() {
+ @Override
+ public ElementMatcher getMethodsMatcher() {
+ return named(PUT_ALL_METHOD).and(takesArgument(0, Map.class));
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return CAFFEINE_MAP_ENHANCE_CLASS;
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return false;
+ }
+ }
+ };
+ }
+}
diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/resources/skywalking-plugin.def
new file mode 100644
index 0000000000..1edf1e3a0e
--- /dev/null
+++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/main/resources/skywalking-plugin.def
@@ -0,0 +1,17 @@
+# 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.
+
+caffeine-3.x=org.apache.skywalking.apm.plugin.caffeine.v3.define.CaffeineInstrumentation
diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineInterceptorTest.java b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineInterceptorTest.java
new file mode 100644
index 0000000000..7642ae110d
--- /dev/null
+++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/caffeine/v3/CaffeineInterceptorTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.caffeine.v3;
+
+import com.github.benmanes.caffeine.cache.Expiry;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment;
+import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule;
+import org.apache.skywalking.apm.agent.test.tools.SegmentStorage;
+import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint;
+import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import static org.hamcrest.CoreMatchers.is;
+
+@RunWith(TracingSegmentRunner.class)
+public class CaffeineInterceptorTest {
+
+ @SegmentStoragePoint
+ private SegmentStorage segmentStorage;
+
+ private CaffeineIterableInterceptor caffeineIterableInterceptor;
+ private CaffeineMapInterceptor caffeineMapInterceptor;
+ private CaffeineStringInterceptor caffeineStringInterceptor;
+ private Object[] operateObjectArguments;
+
+ private Exception exception;
+
+ private Method getAllPresentMethod;
+ private Method getIfPresentMethod;
+ private Method computeIfAbsentMethod;
+
+ private Method putMethod;
+ private Method putAllMethod;
+ private Method removeMethod;
+ private Method cleanMethod;
+
+ @Rule
+ public AgentServiceRule serviceRule = new AgentServiceRule();
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ @Before
+ public void setUp() throws Exception {
+ caffeineIterableInterceptor = new CaffeineIterableInterceptor();
+ caffeineMapInterceptor = new CaffeineMapInterceptor();
+ caffeineStringInterceptor = new CaffeineStringInterceptor();
+ exception = new Exception();
+ operateObjectArguments = new Object[] {"dataKey"};
+ Class> cache = Class.forName("com.github.benmanes.caffeine.cache.BoundedLocalCache");
+ getAllPresentMethod = cache.getDeclaredMethod("getAllPresent", Iterable.class);
+ getIfPresentMethod = cache.getDeclaredMethod("getIfPresent", Object.class, boolean.class);
+ computeIfAbsentMethod = cache.getDeclaredMethod(
+ "computeIfAbsent", Object.class, Function.class, boolean.class, boolean.class);
+ putMethod = cache.getDeclaredMethod("put", Object.class, Object.class, Expiry.class, boolean.class);
+ putAllMethod = cache.getDeclaredMethod("putAll", Map.class);
+ removeMethod = cache.getDeclaredMethod("remove", Object.class);
+ cleanMethod = cache.getDeclaredMethod("clear");
+ }
+
+ @Test
+ public void testGetAllPresentSuccess() throws Throwable {
+ caffeineIterableInterceptor.beforeMethod(null, getAllPresentMethod, null, null, null);
+ caffeineIterableInterceptor.handleMethodException(null, getAllPresentMethod, null, null, exception);
+ caffeineIterableInterceptor.afterMethod(null, getAllPresentMethod, null, null, null);
+ List traceSegments = segmentStorage.getTraceSegments();
+ Assert.assertThat(traceSegments.size(), is(1));
+ }
+
+ @Test
+ public void testGetIfPresentSuccess() throws Throwable {
+ caffeineStringInterceptor.beforeMethod(null, getIfPresentMethod, operateObjectArguments, null, null);
+ caffeineStringInterceptor.handleMethodException(
+ null, getIfPresentMethod, operateObjectArguments, null, exception);
+ caffeineStringInterceptor.afterMethod(null, getIfPresentMethod, operateObjectArguments, null, null);
+ List traceSegments = segmentStorage.getTraceSegments();
+ Assert.assertThat(traceSegments.size(), is(1));
+ }
+
+ @Test
+ public void testComputeIfAbsentMethodSuccess() throws Throwable {
+ caffeineStringInterceptor.beforeMethod(null, computeIfAbsentMethod, null, null, null);
+ caffeineStringInterceptor.handleMethodException(null, computeIfAbsentMethod, null, null, exception);
+ caffeineStringInterceptor.afterMethod(null, computeIfAbsentMethod, null, null, null);
+ List traceSegments = segmentStorage.getTraceSegments();
+ Assert.assertThat(traceSegments.size(), is(1));
+ }
+
+ @Test
+ public void testPutMethodSuccess() throws Throwable {
+ caffeineStringInterceptor.beforeMethod(null, putMethod, operateObjectArguments, null, null);
+ caffeineStringInterceptor.handleMethodException(null, putMethod, operateObjectArguments, null, exception);
+ caffeineStringInterceptor.afterMethod(null, putMethod, operateObjectArguments, null, null);
+ List traceSegments = segmentStorage.getTraceSegments();
+ Assert.assertThat(traceSegments.size(), is(1));
+ }
+
+ @Test
+ public void testPutAllMethodSuccess() throws Throwable {
+ caffeineMapInterceptor.beforeMethod(null, putAllMethod, null, null, null);
+ caffeineMapInterceptor.handleMethodException(null, putAllMethod, null, null, exception);
+ caffeineMapInterceptor.afterMethod(null, putAllMethod, null, null, null);
+ List traceSegments = segmentStorage.getTraceSegments();
+ Assert.assertThat(traceSegments.size(), is(1));
+ }
+
+ @Test
+ public void testRemoveMethodSuccess() throws Throwable {
+ caffeineStringInterceptor.beforeMethod(null, removeMethod, operateObjectArguments, null, null);
+ caffeineStringInterceptor.handleMethodException(null, removeMethod, operateObjectArguments, null, exception);
+ caffeineStringInterceptor.afterMethod(null, removeMethod, operateObjectArguments, null, null);
+ List traceSegments = segmentStorage.getTraceSegments();
+ Assert.assertThat(traceSegments.size(), is(1));
+ }
+
+ @Test
+ public void testClearMethodSuccess() throws Throwable {
+ caffeineStringInterceptor.beforeMethod(null, cleanMethod, null, null, null);
+ caffeineStringInterceptor.handleMethodException(null, cleanMethod, null, null, exception);
+ caffeineStringInterceptor.afterMethod(null, cleanMethod, null, null, null);
+ List traceSegments = segmentStorage.getTraceSegments();
+ Assert.assertThat(traceSegments.size(), is(1));
+ }
+}
diff --git a/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000000..1f0955d450
--- /dev/null
+++ b/apm-sniffer/optional-plugins/caffeine-3.x-plugin/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/apm-sniffer/optional-plugins/pom.xml b/apm-sniffer/optional-plugins/pom.xml
index 4a55525da8..6369484e82 100644
--- a/apm-sniffer/optional-plugins/pom.xml
+++ b/apm-sniffer/optional-plugins/pom.xml
@@ -59,6 +59,7 @@
trace-sampler-cpu-policy-pluginnacos-client-2.x-pluginnetty-http-4.1.x-plugin
+ caffeine-3.x-plugin
diff --git a/docs/en/setup/service-agent/java-agent/Plugin-list.md b/docs/en/setup/service-agent/java-agent/Plugin-list.md
index 2c7b51a6c3..847a81dbec 100644
--- a/docs/en/setup/service-agent/java-agent/Plugin-list.md
+++ b/docs/en/setup/service-agent/java-agent/Plugin-list.md
@@ -179,3 +179,4 @@
- activemq-artemis-jakarta-client-2.x
- c3p0-0.9.x
- solon-2.x
+- caffeine-3.x
diff --git a/docs/en/setup/service-agent/java-agent/Supported-list.md b/docs/en/setup/service-agent/java-agent/Supported-list.md
index fe3bcd231d..cd51b8d2ce 100644
--- a/docs/en/setup/service-agent/java-agent/Supported-list.md
+++ b/docs/en/setup/service-agent/java-agent/Supported-list.md
@@ -139,8 +139,9 @@ metrics based on the tracing data.
* JRE Callable and Runnable (Optional²)
* JRE ForkJoinPool (Optional²)
* Cache
- * [Ehcache](https://www.ehcache.org/) 2.x
+ * [Ehcache](https://www.ehcache.org/) 2.x (Optional²)
* [GuavaCache](https://github.com/google/guava) 18.x -> 23.x (Optional²)
+ * [Caffeine](https://github.com/ben-manes/caffeine) 3.x (Optional²)
* Kotlin
* [Coroutine](https://kotlinlang.org/docs/coroutines-overview.html) 1.0.1 -> 1.3.x (Optional²)
* GraphQL
diff --git a/docs/en/setup/service-agent/java-agent/configurations.md b/docs/en/setup/service-agent/java-agent/configurations.md
index 83837d33b7..9c6cc2d7f6 100644
--- a/docs/en/setup/service-agent/java-agent/configurations.md
+++ b/docs/en/setup/service-agent/java-agent/configurations.md
@@ -126,17 +126,18 @@ This is the properties list supported in `agent/config/agent.config`.
| `plugin.memcached.operation_mapping_read` | Specify which command should be converted to `read` operation | SW_PLUGIN_MEMCACHED_OPERATION_MAPPING_READ | `set,add,replace,append,prepend,cas,delete,touch,incr,decr` |
| `plugin.ehcache.operation_mapping_write` | Specify which command should be converted to `write` operation | SW_PLUGIN_EHCACHE_OPERATION_MAPPING_WRITE | `get,getAll,getQuiet,getKeys,getKeysWithExpiryCheck,getKeysNoDuplicateCheck,releaseRead,tryRead,getWithLoader,getAll,loadAll,getAllWithLoader` |
| `plugin.ehcache.operation_mapping_read` | Specify which command should be converted to `read` operation | SW_PLUGIN_EHCACHE_OPERATION_MAPPING_READ | `tryRemoveImmediately,remove,removeAndReturnElement,removeAll,removeQuiet,removeWithWriter,put,putAll,replace,removeQuiet,removeWithWriter,removeElement,removeAll,putWithWriter,putQuiet,putIfAbsent,putIfAbsent` |
-| `plugin.guavacache.operation_mapping_write` | Specify which command should be converted to `write` operation | SW_PLUGIN_GUAVACACHE_OPERATION_MAPPING_WRITE | `getIfPresent,get,getAllPresent,size` |
-| `plugin.guavacache.operation_mapping_read` | Specify which command should be converted to `read` operation | SW_PLUGIN_GUAVACACHE_OPERATION_MAPPING_READ | `put,putAll,invalidate,invalidateAll,invalidateAll,cleanUp`
-| `plugin.nettyhttp.collect_request_body` | This config item controls that whether the Netty-http plugin should collect the http body of the request. | SW_PLUGIN_NETTY_HTTP_COLLECT_REQUEST_BODY | `false` |
-| `plugin.nettyhttp.filter_length_limit` | When `COLLECT_REQUEST_BODY` is enabled, how many characters to keep and send to the OAP backend, use negative values to keep and send the complete body. | SW_PLUGIN_NETTY_HTTP_FILTER_LENGTH_LIMIT | `1024` |
-| `plugin.nettyhttp.supported_content_types_prefix` | When `COLLECT_REQUEST_BODY` is enabled and content-type start with `HTTP_SUPPORTED_CONTENT_TYPES_PREFIX`, collect the body of the request , multiple paths should be separated by `,` | SW_PLUGIN_NETTY_HTTP_SUPPORTED_CONTENT_TYPES_PREFIX | `application/json,text/` |
-| `plugin.rocketmqclient.collect_message_keys` | If set to true, the keys of messages would be collected by the plugin for RocketMQ Java client.
-| `plugin.rocketmqclient.collect_message_tags` | If set to true, the tags of messages would be collected by the plugin for RocketMQ Java client.
-| `plugin.solon.http_params_length_threshold` | Define the max length of collected HTTP parameters. The default value(=0) means not collecting. | SW_PLUGIN_SOLON_HTTP_PARAMS_LENGTH_THRESHOLD | `0` |
-| `plugin.solon.include_http_headers` | It controls what header data should be collected, values must be in lower case, if empty, no header data will be collected. default is empty. | SW_PLUGIN_SOLON_INCLUDE_HTTP_HEADERS | ``(No header would be collected) |
-| `plugin.solon.http_body_length_threshold` | Define the max length of collected HTTP body. The default value(=0) means not collecting. | SW_PLUGIN_SOLON_HTTP_BODY_LENGTH_THRESHOLD | `0` |
-
+| `plugin.guavacache.operation_mapping_write` | Specify which command should be converted to `write` operation | SW_PLUGIN_GUAVACACHE_OPERATION_MAPPING_WRITE | `put,putAll,invalidate,invalidateAll,invalidateAll,cleanUp` |
+| `plugin.guavacache.operation_mapping_read` | Specify which command should be converted to `read` operation | SW_PLUGIN_GUAVACACHE_OPERATION_MAPPING_READ | `getIfPresent,get,getAllPresent,size` |
+| `plugin.nettyhttp.collect_request_body` | This config item controls that whether the Netty-http plugin should collect the http body of the request. | SW_PLUGIN_NETTY_HTTP_COLLECT_REQUEST_BODY | `false` |
+| `plugin.nettyhttp.filter_length_limit` | When `COLLECT_REQUEST_BODY` is enabled, how many characters to keep and send to the OAP backend, use negative values to keep and send the complete body. | SW_PLUGIN_NETTY_HTTP_FILTER_LENGTH_LIMIT | `1024` |
+| `plugin.nettyhttp.supported_content_types_prefix` | When `COLLECT_REQUEST_BODY` is enabled and content-type start with `HTTP_SUPPORTED_CONTENT_TYPES_PREFIX`, collect the body of the request , multiple paths should be separated by `,` | SW_PLUGIN_NETTY_HTTP_SUPPORTED_CONTENT_TYPES_PREFIX | `application/json,text/` |
+| `plugin.rocketmqclient.collect_message_keys` | If set to true, the keys of messages would be collected by the plugin for RocketMQ Java client. | | |
+| `plugin.rocketmqclient.collect_message_tags` | If set to true, the tags of messages would be collected by the plugin for RocketMQ Java client. | | |
+| `plugin.solon.http_params_length_threshold` | Define the max length of collected HTTP parameters. The default value(=0) means not collecting. | SW_PLUGIN_SOLON_HTTP_PARAMS_LENGTH_THRESHOLD | `0` |
+| `plugin.solon.include_http_headers` | It controls what header data should be collected, values must be in lower case, if empty, no header data will be collected. default is empty. | SW_PLUGIN_SOLON_INCLUDE_HTTP_HEADERS | ``(No header would be collected) |
+| `plugin.solon.http_body_length_threshold` | Define the max length of collected HTTP body. The default value(=0) means not collecting. | SW_PLUGIN_SOLON_HTTP_BODY_LENGTH_THRESHOLD | `0` |
+| `plugin.caffeine.operation_mapping_write` | Specify which command should be converted to `write` operation | SW_PLUGIN_EHCACHE_OPERATION_MAPPING_WRITE | `put,putAll,remove,clear` |
+| `plugin.caffeine.operation_mapping_read` | Specify which command should be converted to `read` operation | SW_PLUGIN_EHCACHE_OPERATION_MAPPING_READ | `getIfPresent,getAllPresent,computeIfAbsent` |
# Reset Collection/Map type configurations as empty collection.
diff --git a/test/plugin/scenarios/caffeine-3.x-scenario/bin/startup.sh b/test/plugin/scenarios/caffeine-3.x-scenario/bin/startup.sh
new file mode 100755
index 0000000000..01883d186d
--- /dev/null
+++ b/test/plugin/scenarios/caffeine-3.x-scenario/bin/startup.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# 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.
+
+home="$(cd "$(dirname $0)"; pwd)"
+
+java -jar ${agent_opts} ${home}/../libs/caffeine-3.x-scenario.jar &
\ No newline at end of file
diff --git a/test/plugin/scenarios/caffeine-3.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/caffeine-3.x-scenario/config/expectedData.yaml
new file mode 100755
index 0000000000..133ede4755
--- /dev/null
+++ b/test/plugin/scenarios/caffeine-3.x-scenario/config/expectedData.yaml
@@ -0,0 +1,163 @@
+# 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.
+
+segmentItems:
+- serviceName: caffeine-3.x-scenario
+ segmentSize: nq 0
+ segments:
+ - segmentId: not null
+ spans:
+ - operationName: Caffeine/put
+ parentSpanId: 0
+ spanId: 1
+ spanLayer: Cache
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 160
+ isError: false
+ spanType: Local
+ peer: ''
+ tags:
+ - { key: cache.type, value: Caffeine }
+ - { key: cache.cmd, value: put }
+ - { key: cache.op, value: write }
+ - { key: cache.key, value: "1" }
+ skipAnalysis: 'false'
+ - operationName: Caffeine/putAll
+ parentSpanId: 0
+ spanId: 2
+ spanLayer: Cache
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 160
+ isError: false
+ spanType: Local
+ peer: ''
+ tags:
+ - { key: cache.type, value: Caffeine }
+ - { key: cache.cmd, value: putAll }
+ - { key: cache.op, value: write }
+ - { key: cache.key, value: "2" }
+ skipAnalysis: 'false'
+ - operationName: Caffeine/computeIfAbsent
+ parentSpanId: 0
+ spanId: 3
+ spanLayer: Cache
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 160
+ isError: false
+ spanType: Local
+ peer: ''
+ tags:
+ - { key: cache.type, value: Caffeine }
+ - { key: cache.cmd, value: computeIfAbsent }
+ - { key: cache.op, value: read }
+ - { key: cache.key, value: "1" }
+ - operationName: Caffeine/getIfPresent
+ parentSpanId: 0
+ spanId: 4
+ spanLayer: Cache
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 160
+ isError: false
+ spanType: Local
+ peer: ''
+ tags:
+ - { key: cache.type, value: Caffeine }
+ - { key: cache.cmd, value: getIfPresent }
+ - { key: cache.op, value: read }
+ - { key: cache.key, value: "1" }
+ skipAnalysis: 'false'
+ - operationName: Caffeine/getAllPresent
+ parentSpanId: 0
+ spanId: 5
+ spanLayer: Cache
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 160
+ isError: false
+ spanType: Local
+ peer: ''
+ skipAnalysis: 'false'
+ tags:
+ - { key: cache.type, value: Caffeine }
+ - { key: cache.cmd, value: getAllPresent }
+ - { key: cache.op, value: read }
+ - { key: cache.key, value: '2' }
+ - operationName: Caffeine/remove
+ parentSpanId: 0
+ spanId: 6
+ spanLayer: Cache
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 160
+ isError: false
+ spanType: Local
+ peer: ''
+ skipAnalysis: 'false'
+ tags:
+ - { key: cache.type, value: Caffeine }
+ - { key: cache.cmd, value: remove }
+ - { key: cache.op, value: write }
+ - { key: cache.key, value: '1' }
+ - operationName: Caffeine/remove
+ parentSpanId: 0
+ spanId: 7
+ spanLayer: Cache
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 160
+ isError: false
+ spanType: Local
+ peer: ''
+ skipAnalysis: 'false'
+ tags:
+ - { key: cache.type, value: Caffeine }
+ - { key: cache.cmd, value: remove }
+ - { key: cache.op, value: write }
+ - { key: cache.key, value: '2' }
+ - operationName: Caffeine/clear
+ parentSpanId: 0
+ spanId: 8
+ spanLayer: Cache
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 160
+ isError: false
+ spanType: Local
+ peer: ''
+ skipAnalysis: 'false'
+ tags:
+ - { key: cache.type, value: Caffeine }
+ - { key: cache.cmd, value: clear }
+ - { key: cache.op, value: write }
+ - operationName: GET:/caffeine-3.x-scenario/case/caffeine
+ parentSpanId: -1
+ spanId: 0
+ spanLayer: Http
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 1
+ isError: false
+ spanType: Entry
+ peer: ''
+ tags:
+ - { key: url, value: 'http://localhost:8080/caffeine-3.x-scenario/case/caffeine' }
+ - { key: http.method, value: GET }
+ - { key: http.status_code, value: '200' }
+ skipAnalysis: 'false'
diff --git a/test/plugin/scenarios/caffeine-3.x-scenario/configuration.yml b/test/plugin/scenarios/caffeine-3.x-scenario/configuration.yml
new file mode 100755
index 0000000000..00208eefa9
--- /dev/null
+++ b/test/plugin/scenarios/caffeine-3.x-scenario/configuration.yml
@@ -0,0 +1,22 @@
+# 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.
+
+type: jvm
+entryService: http://localhost:8080/caffeine-3.x-scenario/case/caffeine
+healthCheck: http://localhost:8080/caffeine-3.x-scenario/case/healthCheck
+startScript: ./bin/startup.sh
+runningMode: with_optional
+withPlugins: apm-caffeine-3.x-plugin-*.jar
\ No newline at end of file
diff --git a/test/plugin/scenarios/caffeine-3.x-scenario/pom.xml b/test/plugin/scenarios/caffeine-3.x-scenario/pom.xml
new file mode 100755
index 0000000000..a627109082
--- /dev/null
+++ b/test/plugin/scenarios/caffeine-3.x-scenario/pom.xml
@@ -0,0 +1,111 @@
+
+
+
+
+ org.apache.skywalking.apm.testcase
+ caffeine-3.x-scenario
+ 1.0.0
+ jar
+
+ 4.0.0
+
+
+ 17
+ UTF-8
+ 3.8.1
+ 6.0.2
+ ${test.framework.version}
+ 3.0.0
+ 3.1.8
+
+
+ skywalking-caffeine-3.x-scenario
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring.boot.version}
+ pom
+ import
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+ ${caffeine.version}
+
+
+
+
+ caffeine-3.x-scenario
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring.boot.version}
+
+
+
+ repackage
+
+
+
+
+
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+
+ ${compiler.version}
+ ${project.build.sourceEncoding}
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+ assemble
+ package
+
+ single
+
+
+
+ src/main/assembly/assembly.xml
+
+ ./target/
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/plugin/scenarios/caffeine-3.x-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/caffeine-3.x-scenario/src/main/assembly/assembly.xml
new file mode 100755
index 0000000000..382a68db61
--- /dev/null
+++ b/test/plugin/scenarios/caffeine-3.x-scenario/src/main/assembly/assembly.xml
@@ -0,0 +1,41 @@
+
+
+
+
+ zip
+
+
+
+
+ ./bin
+ 0775
+
+
+
+
+
+
+ ./libs
+ 0775
+
+
+
diff --git a/test/plugin/scenarios/caffeine-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/caffeine/Application.java b/test/plugin/scenarios/caffeine-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/caffeine/Application.java
new file mode 100644
index 0000000000..a4739cfbfe
--- /dev/null
+++ b/test/plugin/scenarios/caffeine-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/caffeine/Application.java
@@ -0,0 +1,30 @@
+/*
+ * 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.caffeine;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
diff --git a/test/plugin/scenarios/caffeine-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/caffeine/controller/CaffeineController.java b/test/plugin/scenarios/caffeine-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/caffeine/controller/CaffeineController.java
new file mode 100644
index 0000000000..f8bad1d288
--- /dev/null
+++ b/test/plugin/scenarios/caffeine-3.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/caffeine/controller/CaffeineController.java
@@ -0,0 +1,56 @@
+/*
+ * 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.caffeine.controller;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import java.util.HashMap;
+import java.util.Map;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class CaffeineController {
+
+ Cache