Support analyzing cache related spans to provide metrics and slow commands for cache services from client side. (#9622)

* Support analyzing cache related spans to provide metrics and slow commands for cache services from client side
* Optimize virtual database, fix dynamic config watcher NPE when default value is null 
* [UI] Add virtual cache dashboard
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index 562d1c5..d89f7dc 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -12,6 +12,8 @@
 * [**Breaking Change**] Change the LAL script format(Add layer property).
 * Adapt ElasticSearch 8.1+, migrate from removed APIs to recommended APIs.
 * Support monitoring MySQL slow SQLs.
+* Support analyzing cache related spans to provide metrics and slow commands for cache services from client side
+* Optimize virtual database, fix dynamic config watcher NPE when default value is null 
 * Remove physical index existing check and keep template existing check only to avoid meaningless `retry wait`
   in `no-init` mode.
 * Make sure instance list ordered in TTL processor to avoid TTL timer never runs.
@@ -32,6 +34,7 @@
 * Enhance the process topology graph to support dragging nodes.
 * UI-template: Fix metrics calculation in `general-service/mesh-service/faas-function` top-list dashboard.
 * Update MySQL dashboard to visualize collected slow SQLs.
+* Add virtual cache dashboard
 
 #### Documentation
 
diff --git a/docs/en/concepts-and-designs/scope-definitions.md b/docs/en/concepts-and-designs/scope-definitions.md
index e5ca483..540ed4b 100644
--- a/docs/en/concepts-and-designs/scope-definitions.md
+++ b/docs/en/concepts-and-designs/scope-definitions.md
@@ -286,3 +286,54 @@
 | type | The type of the event, `Normal` or `Error`. | | string|
 | message | The message of the event. | | string |
 | parameters | The parameters in the `message`, see [parameters](event.md#parameters). | | string |
+
+
+### SCOPE `DatabaseAccess`
+
+This calculates the metrics data from each request of cache system.
+
+| Name | Remarks | Group Key | Type | 
+|---|---|---|---|
+| name | The service name of virtual database service. |  | string |
+| databaseTypeId | The ID of the component used in this call. | | int |
+| latency | The time taken by each request. | | int(in ms)|
+| status | Indicates the success or failure of the request.| | boolean |
+
+### SCOPE `DatabaseSlowStatement`
+
+This calculates the metrics data from slow request of cache system , which is used for `write` or `read` operation.
+
+| Name | Remarks | Group Key | Type | 
+|---|---|---|---|
+| databaseServiceId | The service id of virtual cache service. |  | string |
+| statement | The sql statement . | | string |
+| latency | The time taken by each request. | | int(in ms)|
+| traceId | The traceId of this slow statement| | string|
+
+
+### SCOPE `CacheAccess`
+
+This calculates the metrics data from each request of cache system.
+
+| Name | Remarks | Group Key | Type | 
+|---|---|---|---|
+| name | The service name of virtual cache service. |  | string |
+| cacheTypeId | The ID of the component used in this call. | | int |
+| latency | The time taken by each request. | | int(in ms)|
+| status | Indicates the success or failure of the request.| | boolean |
+| op | Indicates this access is used for `write` or `read` | | string |
+
+
+### SCOPE `CacheSlowAccess`
+
+This calculates the metrics data from slow request of cache system , which is used for `write` or `read` operation.
+
+| Name | Remarks | Group Key | Type | 
+|---|---|---|---|
+| cacheServiceId | The service id of virtual cache service. |  | string |
+| command | The cache command . | | string |
+| key | The cache command key. | | string|
+| latency | The time taken by each request. | | int(in ms)|
+| traceId | The traceId of this slow access| | string|
+| status | Indicates the success or failure of the request.| | boolean |
+| op | Indicates this access is used for `write` or `read` | | string |
\ No newline at end of file
diff --git a/docs/en/setup/backend/configuration-vocabulary.md b/docs/en/setup/backend/configuration-vocabulary.md
index 868f0b9..d48cf7a 100644
--- a/docs/en/setup/backend/configuration-vocabulary.md
+++ b/docs/en/setup/backend/configuration-vocabulary.md
@@ -154,6 +154,8 @@
 | -                       | -             | segmentStatusAnalysisStrategy                                                                                                                                            | Determines the final segment status from span status. Available values are `FROM_SPAN_STATUS` , `FROM_ENTRY_SPAN`, and `FROM_FIRST_SPAN`. `FROM_SPAN_STATUS` indicates that the segment status would be error if any span has an error status. `FROM_ENTRY_SPAN` means that the segment status would only be determined by the status of entry spans. `FROM_FIRST_SPAN` means that the segment status would only be determined by the status of the first span. | SW_SEGMENT_STATUS_ANALYSIS_STRATEGY               | FROM_SPAN_STATUS                                                        |
 | -                       | -             | noUpstreamRealAddressAgents                                                                                                                                              | Exit spans with the component in the list would not generate client-side instance relation metrics, since some tracing plugins (e.g. Nginx-LUA and Envoy) can't collect the real peer IP address.                                                                                                                                                                                                                                                               | SW_NO_UPSTREAM_REAL_ADDRESS                       | 6000,9000                                                               |
 | -                       | -             | meterAnalyzerActiveFiles                                                                                                                                                 | Indicates which files could be instrumented and analyzed. Multiple files are split by ",".                                                                                                                                                                                                                                                                                                                                                                      | SW_METER_ANALYZER_ACTIVE_FILES                    ||     |
+| -                       | -             | slowCacheWriteThreshold                                                                                                                                                  | The threshold of slow command which is used for writing operation (in milliseconds).                                                                                                                                                                                                                                                                                                                                                                                       | SW_SLOW_CACHE_WRITE_THRESHOLD                     |  `default:20,redis:10`                                                           |
+| -                       | -             | slowCacheReadThreshold                                                                                                                                                   | The threshold of slow command which is used for reading (getting) operation (in milliseconds).                                                                                                                                                                                                                                                                                                                                                                             | SW_SLOW_CACHE_READ_THRESHOLD                      |  `default:20,redis:10`                                                           |
 | receiver-sharing-server | default       | Sharing server provides new gRPC and restful servers for data collection. Ana designates that servers in the core module are to be used for internal communication only. | -                                                                                                                                                                                                                                                                                                                                                                                                                                                               | -                                                 |                                                                         |
 | -                       | -             | restHost                                                                                                                                                                 | Binding IP of RESTful services. Services include GraphQL query and HTTP data report.                                                                                                                                                                                                                                                                                                                                                                            | SW_RECEIVER_SHARING_REST_HOST                     | -                                                                       |
 | -                       | -             | restPort                                                                                                                                                                 | Binding port of RESTful services.                                                                                                                                                                                                                                                                                                                                                                                                                               | SW_RECEIVER_SHARING_REST_PORT                     | -                                                                       |
diff --git a/docs/en/setup/backend/slow-cache-command.md b/docs/en/setup/backend/slow-cache-command.md
new file mode 100644
index 0000000..dfad3bd
--- /dev/null
+++ b/docs/en/setup/backend/slow-cache-command.md
@@ -0,0 +1,13 @@
+# Slow Cache Command
+Slow Cache command are sensitive for you to identify bottlenecks of a system which relies on cache system.
+
+Slow Cache command are based on sampling. Right now, the core samples are the top 50 slowest every 10 minutes.
+Note that the duration of these command must be slower than the threshold.
+
+Here's the format of the settings (in milliseconds):
+> cache-type:thresholdValue,cache-type2:thresholdValue2
+
+The default settings are `default:20,redis:10`. `Reserved Cache type` is **default**, which is the default threshold for all
+cache types, unless set explicitly.
+
+**Note**: The threshold should not be set too small, like `1ms`. Although it works in theory, OAP performance issues may arise if your system statement access time is usually more than 1ms.
diff --git a/docs/en/setup/service-agent/virtual-cache.md b/docs/en/setup/service-agent/virtual-cache.md
new file mode 100644
index 0000000..f7a7e8b
--- /dev/null
+++ b/docs/en/setup/service-agent/virtual-cache.md
@@ -0,0 +1,16 @@
+# Virtual Cache
+
+Virtual cache represent the cache nodes detected by [server agents' plugins](server-agents.md). The performance
+metrics of the cache are also from the Cache client-side perspective.
+
+For example, Redis plugins in the Java agent could detect the latency of command
+As a result, SkyWalking would show traffic, latency, success rate, and sampled slow operations(write/read) powered by backend analysis capabilities in this dashboard.
+
+The cache operation span should have
+- It is an **Exit** or **Local** span
+- **Span's layer == CACHE**
+- Tag key = `cache.type`, value = The type of cache system , e.g. redis
+- Tag key = `cache.op`, value = the operation of command , indicates the command is used for `write` or `read` operation
+- Tag key = `cache.command`, value = the cache command , e.g. get,set,del
+- Tag key = `cache.key`, value = the cache key
+- If the cache system is in-memory (e.g. Guava-cache), agents' plugin would create a local span usually, and the span's peer would be null ,otherwise the peer is the network address(IP or domain) of Cache server.
\ No newline at end of file
diff --git a/docs/menu.yml b/docs/menu.yml
index c29719d..b8f8546 100644
--- a/docs/menu.yml
+++ b/docs/menu.yml
@@ -109,6 +109,8 @@
                 path: "/en/setup/backend/trace-sampling"
               - name: "Detect Slow Database Statement"
                 path: "/en/setup/backend/slow-db-statement"
+              - name: "Detect Slow Cache Command"
+                path: "/en/setup/backend/slow-cache-command"
               - name: "Message Queue Performance"
                 path: "/en/setup/backend/mq"
               - name: "Uninstrumented Gateways"
@@ -159,6 +161,8 @@
                 path: "/en/setup/service-agent/agent-compatibility"
               - name: "Virtual Database"
                 path: "/en/setup/service-agent/virtual-database"
+              - name: "Virtual Cache"
+                path: "/en/setup/service-agent/virtual-cache"
           - name: "Service Mesh"
             catalog:
               - name: "Observe Service Mesh"
diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/AnalyzerModuleConfig.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/AnalyzerModuleConfig.java
index ed9c5ad..db6b38b 100644
--- a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/AnalyzerModuleConfig.java
+++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/AnalyzerModuleConfig.java
@@ -23,6 +23,8 @@
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.skywalking.oap.server.analyzer.provider.trace.CacheReadLatencyThresholdsAndWatcher;
+import org.apache.skywalking.oap.server.analyzer.provider.trace.CacheWriteLatencyThresholdsAndWatcher;
 import org.apache.skywalking.oap.server.analyzer.provider.trace.DBLatencyThresholdsAndWatcher;
 import org.apache.skywalking.oap.server.analyzer.provider.trace.TraceSamplingPolicyWatcher;
 import org.apache.skywalking.oap.server.analyzer.provider.trace.UninstrumentedGatewaysConfig;
@@ -60,6 +62,23 @@
     @Setter
     @Getter
     private DBLatencyThresholdsAndWatcher dbLatencyThresholdsAndWatcher;
+
+    @Setter
+    @Getter
+    private String slowCacheWriteThreshold = "default:20,redis:10";
+
+    @Setter
+    @Getter
+    private CacheWriteLatencyThresholdsAndWatcher cacheWriteLatencyThresholdsAndWatcher;
+
+    @Setter
+    @Getter
+    private String slowCacheReadThreshold = "default:20,redis:10";
+
+    @Setter
+    @Getter
+    private CacheReadLatencyThresholdsAndWatcher cacheReadLatencyThresholdsAndWatcher;
+
     @Setter
     @Getter
     private UninstrumentedGatewaysConfig uninstrumentedGatewaysConfig;
diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/AnalyzerModuleProvider.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/AnalyzerModuleProvider.java
index f2ee017..09d3832 100644
--- a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/AnalyzerModuleProvider.java
+++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/AnalyzerModuleProvider.java
@@ -18,14 +18,14 @@
 
 package org.apache.skywalking.oap.server.analyzer.provider;
 
-import java.util.List;
-
 import lombok.Getter;
 import org.apache.skywalking.oap.server.analyzer.module.AnalyzerModule;
 import org.apache.skywalking.oap.server.analyzer.provider.meter.config.MeterConfig;
 import org.apache.skywalking.oap.server.analyzer.provider.meter.config.MeterConfigs;
 import org.apache.skywalking.oap.server.analyzer.provider.meter.process.IMeterProcessService;
 import org.apache.skywalking.oap.server.analyzer.provider.meter.process.MeterProcessService;
+import org.apache.skywalking.oap.server.analyzer.provider.trace.CacheReadLatencyThresholdsAndWatcher;
+import org.apache.skywalking.oap.server.analyzer.provider.trace.CacheWriteLatencyThresholdsAndWatcher;
 import org.apache.skywalking.oap.server.analyzer.provider.trace.DBLatencyThresholdsAndWatcher;
 import org.apache.skywalking.oap.server.analyzer.provider.trace.TraceSamplingPolicyWatcher;
 import org.apache.skywalking.oap.server.analyzer.provider.trace.UninstrumentedGatewaysConfig;
@@ -33,9 +33,10 @@
 import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.SegmentParserListenerManager;
 import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.SegmentParserServiceImpl;
 import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.EndpointDepFromCrossThreadAnalysisListener;
-import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.RPCAnalysisListener;
 import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.NetworkAddressAliasMappingListener;
+import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.RPCAnalysisListener;
 import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.SegmentAnalysisListener;
+import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.VirtualServiceAnalysisListener;
 import org.apache.skywalking.oap.server.configuration.api.ConfigurationModule;
 import org.apache.skywalking.oap.server.configuration.api.DynamicConfigurationService;
 import org.apache.skywalking.oap.server.core.CoreModule;
@@ -48,11 +49,15 @@
 import org.apache.skywalking.oap.server.library.module.ServiceNotProvidedException;
 import org.apache.skywalking.oap.server.telemetry.TelemetryModule;
 
+import java.util.List;
+
 public class AnalyzerModuleProvider extends ModuleProvider {
     @Getter
     private final AnalyzerModuleConfig moduleConfig;
     @Getter
-    private DBLatencyThresholdsAndWatcher thresholds;
+    private DBLatencyThresholdsAndWatcher dbLatencyThresholdsAndWatcher;
+    private CacheReadLatencyThresholdsAndWatcher cacheReadLatencyThresholdsAndWatcher;
+    private CacheWriteLatencyThresholdsAndWatcher cacheWriteLatencyThresholdsAndWatcher;
     @Getter
     private UninstrumentedGatewaysConfig uninstrumentedGatewaysConfig;
     @Getter
@@ -85,15 +90,17 @@
 
     @Override
     public void prepare() throws ServiceNotProvidedException, ModuleStartException {
-        thresholds = new DBLatencyThresholdsAndWatcher(moduleConfig.getSlowDBAccessThreshold(), this);
-
+        dbLatencyThresholdsAndWatcher = new DBLatencyThresholdsAndWatcher(moduleConfig.getSlowDBAccessThreshold(), this);
         uninstrumentedGatewaysConfig = new UninstrumentedGatewaysConfig(this);
-
         traceSamplingPolicyWatcher = new TraceSamplingPolicyWatcher(moduleConfig, this);
+        cacheReadLatencyThresholdsAndWatcher = new CacheReadLatencyThresholdsAndWatcher(moduleConfig.getSlowCacheReadThreshold(), this);
+        cacheWriteLatencyThresholdsAndWatcher = new CacheWriteLatencyThresholdsAndWatcher(moduleConfig.getSlowCacheWriteThreshold(), this);
 
-        moduleConfig.setDbLatencyThresholdsAndWatcher(thresholds);
+        moduleConfig.setDbLatencyThresholdsAndWatcher(dbLatencyThresholdsAndWatcher);
         moduleConfig.setUninstrumentedGatewaysConfig(uninstrumentedGatewaysConfig);
         moduleConfig.setTraceSamplingPolicyWatcher(traceSamplingPolicyWatcher);
+        moduleConfig.setCacheReadLatencyThresholdsAndWatcher(cacheReadLatencyThresholdsAndWatcher);
+        moduleConfig.setCacheWriteLatencyThresholdsAndWatcher(cacheWriteLatencyThresholdsAndWatcher);
 
         segmentParserService = new SegmentParserServiceImpl(getManager(), moduleConfig);
         this.registerServiceImplementation(ISegmentParserService.class, segmentParserService);
@@ -116,9 +123,11 @@
                                                                               .provider()
                                                                               .getService(
                                                                                   DynamicConfigurationService.class);
-        dynamicConfigurationService.registerConfigChangeWatcher(thresholds);
+        dynamicConfigurationService.registerConfigChangeWatcher(dbLatencyThresholdsAndWatcher);
         dynamicConfigurationService.registerConfigChangeWatcher(uninstrumentedGatewaysConfig);
         dynamicConfigurationService.registerConfigChangeWatcher(traceSamplingPolicyWatcher);
+        dynamicConfigurationService.registerConfigChangeWatcher(cacheReadLatencyThresholdsAndWatcher);
+        dynamicConfigurationService.registerConfigChangeWatcher(cacheWriteLatencyThresholdsAndWatcher);
 
         segmentParserService.setListenerManager(listenerManager());
 
@@ -147,6 +156,7 @@
             listenerManager.add(new NetworkAddressAliasMappingListener.Factory(getManager()));
         }
         listenerManager.add(new SegmentAnalysisListener.Factory(getManager(), moduleConfig));
+        listenerManager.add(new VirtualServiceAnalysisListener.Factory(getManager()));
 
         return listenerManager;
     }
diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/CacheReadLatencyThresholdsAndWatcher.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/CacheReadLatencyThresholdsAndWatcher.java
new file mode 100644
index 0000000..a891eb7
--- /dev/null
+++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/CacheReadLatencyThresholdsAndWatcher.java
@@ -0,0 +1,80 @@
+/*
+ * 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.oap.server.analyzer.provider.trace;
+
+import com.google.common.base.Splitter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.skywalking.oap.server.analyzer.module.AnalyzerModule;
+import org.apache.skywalking.oap.server.configuration.api.ConfigChangeWatcher;
+import org.apache.skywalking.oap.server.library.module.ModuleProvider;
+
+public class CacheReadLatencyThresholdsAndWatcher extends ConfigChangeWatcher {
+    private AtomicReference<Map<String, Integer>> thresholds;
+    private final String initialSettingsString;
+    private volatile String dynamicSettingsString;
+
+    public CacheReadLatencyThresholdsAndWatcher(String config, ModuleProvider provider) {
+        super(AnalyzerModule.NAME, provider, "slowCacheReadThreshold");
+        thresholds = new AtomicReference<>(new HashMap<>());
+        initialSettingsString = config;
+
+        activeSetting(config);
+    }
+
+    private void activeSetting(String config) {
+        Map<String, Integer> newThresholds = new HashMap<>();
+        List<String> settings = Splitter.on(',').splitToList(config);
+        for (String setting : settings) {
+            List<String> typeValue = Splitter.on(":").splitToList(setting);
+            if (typeValue.size() == 2) {
+                newThresholds.put(typeValue.get(0).trim().toLowerCase(), Integer.parseInt(typeValue.get(1).trim()));
+            }
+        }
+        thresholds.set(newThresholds);
+    }
+
+    public int getThreshold(String type) {
+        type = type.toLowerCase();
+        if (thresholds.get().containsKey(type)) {
+            return thresholds.get().get(type);
+        } else {
+            return Optional.ofNullable(thresholds.get().get("default")).orElse(Integer.MAX_VALUE);
+        }
+    }
+
+    @Override
+    public void notify(ConfigChangeEvent value) {
+        if (EventType.DELETE.equals(value.getEventType())) {
+            dynamicSettingsString = null;
+            activeSetting(initialSettingsString);
+        } else {
+            dynamicSettingsString = value.getNewValue();
+            activeSetting(value.getNewValue());
+        }
+    }
+
+    @Override
+    public String value() {
+        return dynamicSettingsString;
+    }
+}
diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/CacheWriteLatencyThresholdsAndWatcher.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/CacheWriteLatencyThresholdsAndWatcher.java
new file mode 100644
index 0000000..5678e22
--- /dev/null
+++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/CacheWriteLatencyThresholdsAndWatcher.java
@@ -0,0 +1,80 @@
+/*
+ * 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.oap.server.analyzer.provider.trace;
+
+import com.google.common.base.Splitter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.skywalking.oap.server.analyzer.module.AnalyzerModule;
+import org.apache.skywalking.oap.server.configuration.api.ConfigChangeWatcher;
+import org.apache.skywalking.oap.server.library.module.ModuleProvider;
+
+public class CacheWriteLatencyThresholdsAndWatcher extends ConfigChangeWatcher {
+    private AtomicReference<Map<String, Integer>> thresholds;
+    private final String initialSettingsString;
+    private volatile String dynamicSettingsString;
+
+    public CacheWriteLatencyThresholdsAndWatcher(String config, ModuleProvider provider) {
+        super(AnalyzerModule.NAME, provider, "slowCacheWriteThreshold");
+        thresholds = new AtomicReference<>(new HashMap<>());
+        initialSettingsString = config;
+
+        activeSetting(config);
+    }
+
+    private void activeSetting(String config) {
+        Map<String, Integer> newThresholds = new HashMap<>();
+        List<String> settings = Splitter.on(',').splitToList(config);
+        for (String setting : settings) {
+            List<String> typeValue = Splitter.on(":").splitToList(setting);
+            if (typeValue.size() == 2) {
+                newThresholds.put(typeValue.get(0).trim().toLowerCase(), Integer.parseInt(typeValue.get(1).trim()));
+            }
+        }
+        thresholds.set(newThresholds);
+    }
+
+    public int getThreshold(String type) {
+        type = type.toLowerCase();
+        if (thresholds.get().containsKey(type)) {
+            return thresholds.get().get(type);
+        } else {
+            return Optional.ofNullable(thresholds.get().get("default")).orElse(Integer.MAX_VALUE);
+        }
+    }
+
+    @Override
+    public void notify(ConfigChangeEvent value) {
+        if (EventType.DELETE.equals(value.getEventType())) {
+            dynamicSettingsString = null;
+            activeSetting(initialSettingsString);
+        } else {
+            dynamicSettingsString = value.getNewValue();
+            activeSetting(value.getNewValue());
+        }
+    }
+
+    @Override
+    public String value() {
+        return dynamicSettingsString;
+    }
+}
diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/DBLatencyThresholdsAndWatcher.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/DBLatencyThresholdsAndWatcher.java
index f9ebb3a..82e13a5 100644
--- a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/DBLatencyThresholdsAndWatcher.java
+++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/DBLatencyThresholdsAndWatcher.java
@@ -20,6 +20,7 @@
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.atomic.AtomicReference;
 import org.apache.skywalking.oap.server.analyzer.module.AnalyzerModule;
 import org.apache.skywalking.oap.server.configuration.api.ConfigChangeWatcher;
@@ -56,7 +57,7 @@
         if (thresholds.get().containsKey(type)) {
             return thresholds.get().get(type);
         } else {
-            return thresholds.get().get("default");
+            return Optional.ofNullable(thresholds.get().get("default")).orElse(Integer.MAX_VALUE);
         }
     }
 
diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/SpanTags.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/SpanTags.java
index 6260ed0..62643c8 100644
--- a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/SpanTags.java
+++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/SpanTags.java
@@ -37,6 +37,14 @@
 
     public static final String DB_TYPE = "db.type";
 
+    public static final String CACHE_TYPE = "cache.type";
+
+    public static final String CACHE_OP = "cache.op";
+
+    public static final String CACHE_CMD = "cache.cmd";
+
+    public static final String CACHE_KEY = "cache.key";
+
     /**
      * Tag, x-le(extension logic endpoint) series tag. Value is JSON format.
      * <pre>
diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/RPCAnalysisListener.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/RPCAnalysisListener.java
index d73d085..f951d98 100644
--- a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/RPCAnalysisListener.java
+++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/RPCAnalysisListener.java
@@ -20,18 +20,14 @@
 
 import com.google.gson.Gson;
 import com.google.gson.JsonObject;
-import java.util.ArrayList;
-import java.util.List;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.skywalking.apm.network.common.v3.KeyStringValuePair;
 import org.apache.skywalking.apm.network.language.agent.v3.SegmentObject;
 import org.apache.skywalking.apm.network.language.agent.v3.SegmentReference;
 import org.apache.skywalking.apm.network.language.agent.v3.SpanLayer;
 import org.apache.skywalking.apm.network.language.agent.v3.SpanObject;
 import org.apache.skywalking.apm.network.language.agent.v3.SpanType;
 import org.apache.skywalking.oap.server.analyzer.provider.AnalyzerModuleConfig;
-import org.apache.skywalking.oap.server.analyzer.provider.trace.DBLatencyThresholdsAndWatcher;
 import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.SpanTags;
 import org.apache.skywalking.oap.server.core.Const;
 import org.apache.skywalking.oap.server.core.CoreModule;
@@ -50,6 +46,9 @@
 import org.apache.skywalking.oap.server.library.module.ModuleManager;
 import org.apache.skywalking.oap.server.library.util.StringUtil;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import static org.apache.skywalking.oap.server.analyzer.provider.trace.parser.SpanTags.LOGIC_ENDPOINT;
 
 /**
@@ -60,7 +59,6 @@
 public class RPCAnalysisListener extends CommonAnalysisListener implements EntryAnalysisListener, ExitAnalysisListener, LocalAnalysisListener {
     private final List<RPCTrafficSourceBuilder> callingInTraffic = new ArrayList<>(10);
     private final List<RPCTrafficSourceBuilder> callingOutTraffic = new ArrayList<>(10);
-    private final List<DatabaseSlowStatementBuilder> dbSlowStatementBuilders = new ArrayList<>(10);
     private final List<EndpointSourceBuilder> logicEndpointBuilders = new ArrayList<>(10);
     private final Gson gson = new Gson();
     private final SourceReceiver sourceReceiver;
@@ -197,45 +195,6 @@
         sourceBuilder.setComponentId(span.getComponentId());
         setPublicAttrs(sourceBuilder, span);
         callingOutTraffic.add(sourceBuilder);
-
-        if (RequestType.DATABASE.equals(sourceBuilder.getType())) {
-            boolean isSlowDBAccess = false;
-
-            DatabaseSlowStatementBuilder slowStatementBuilder = new DatabaseSlowStatementBuilder(namingControl);
-            slowStatementBuilder.setServiceName(networkAddress);
-            slowStatementBuilder.setId(segmentObject.getTraceSegmentId() + "-" + span.getSpanId());
-            slowStatementBuilder.setLatency(sourceBuilder.getLatency());
-            slowStatementBuilder.setTimeBucket(TimeBucket.getRecordTimeBucket(span.getStartTime()));
-            slowStatementBuilder.setTraceId(segmentObject.getTraceId());
-            for (KeyStringValuePair tag : span.getTagsList()) {
-                if (SpanTags.DB_STATEMENT.equals(tag.getKey())) {
-                    String sqlStatement = tag.getValue();
-                    if (StringUtil.isNotEmpty(sqlStatement)) {
-                        if (sqlStatement.length() > config.getMaxSlowSQLLength()) {
-                            slowStatementBuilder.setStatement(sqlStatement.substring(0, config.getMaxSlowSQLLength()));
-                        } else {
-                            slowStatementBuilder.setStatement(sqlStatement);
-                        }
-                    }
-                } else if (SpanTags.DB_TYPE.equals(tag.getKey())) {
-                    String dbType = tag.getValue();
-                    DBLatencyThresholdsAndWatcher thresholds = config.getDbLatencyThresholdsAndWatcher();
-                    int threshold = thresholds.getThreshold(dbType);
-                    if (sourceBuilder.getLatency() > threshold) {
-                        isSlowDBAccess = true;
-                    }
-                }
-            }
-
-            if (StringUtil.isEmpty(slowStatementBuilder.getStatement())) {
-                String statement = StringUtil.isEmpty(
-                    span.getOperationName()) ? "[No statement]" : "[No statement]/" + span.getOperationName();
-                slowStatementBuilder.setStatement(statement);
-            }
-            if (isSlowDBAccess) {
-                dbSlowStatementBuilders.add(slowStatementBuilder);
-            }
-        }
     }
 
     private void setPublicAttrs(RPCTrafficSourceBuilder sourceBuilder, SpanObject span) {
@@ -318,17 +277,7 @@
             if (serviceInstanceRelation != null) {
                 sourceReceiver.receive(serviceInstanceRelation);
             }
-            if (RequestType.DATABASE.equals(callingOut.getType())) {
-                sourceReceiver.receive(callingOut.toServiceMeta());
-                sourceReceiver.receive(callingOut.toDatabaseAccess());
-            }
         });
-
-        dbSlowStatementBuilders.forEach(dbSlowStatBuilder -> {
-            dbSlowStatBuilder.prepare();
-            sourceReceiver.receive(dbSlowStatBuilder.toDatabaseSlowStatement());
-        });
-
         logicEndpointBuilders.forEach(logicEndpointBuilder -> {
             logicEndpointBuilder.prepare();
             sourceReceiver.receive(logicEndpointBuilder.toEndpoint());
diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/RPCTrafficSourceBuilder.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/RPCTrafficSourceBuilder.java
index af36503..2f67592 100644
--- a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/RPCTrafficSourceBuilder.java
+++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/RPCTrafficSourceBuilder.java
@@ -22,13 +22,10 @@
 import lombok.Setter;
 import org.apache.skywalking.oap.server.core.analysis.Layer;
 import org.apache.skywalking.oap.server.core.config.NamingControl;
-import org.apache.skywalking.oap.server.core.source.DatabaseAccess;
 import org.apache.skywalking.oap.server.core.source.EndpointRelation;
-import org.apache.skywalking.oap.server.core.source.RequestType;
 import org.apache.skywalking.oap.server.core.source.Service;
 import org.apache.skywalking.oap.server.core.source.ServiceInstance;
 import org.apache.skywalking.oap.server.core.source.ServiceInstanceRelation;
-import org.apache.skywalking.oap.server.core.source.ServiceMeta;
 import org.apache.skywalking.oap.server.core.source.ServiceRelation;
 import org.apache.skywalking.oap.server.library.util.StringUtil;
 
@@ -204,33 +201,4 @@
         endpointRelation.setTimeBucket(timeBucket);
         return endpointRelation;
     }
-
-    /**
-     * Service meta is only for building the service list, but wouldn't be same as {@link #toService()}, which could
-     * generate traffic and metrics both.
-     */
-    ServiceMeta toServiceMeta() {
-        ServiceMeta service = new ServiceMeta();
-        service.setName(destServiceName);
-        service.setLayer(destLayer);
-        service.setLayer(destLayer);
-        service.setTimeBucket(timeBucket);
-        return service;
-    }
-
-    /**
-     * Database traffic metrics source. The metrics base on the OAL scripts.
-     */
-    DatabaseAccess toDatabaseAccess() {
-        if (!RequestType.DATABASE.equals(type)) {
-            return null;
-        }
-        DatabaseAccess databaseAccess = new DatabaseAccess();
-        databaseAccess.setDatabaseTypeId(componentId);
-        databaseAccess.setLatency(latency);
-        databaseAccess.setName(destServiceName);
-        databaseAccess.setStatus(status);
-        databaseAccess.setTimeBucket(timeBucket);
-        return databaseAccess;
-    }
 }
diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/VirtualServiceAnalysisListener.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/VirtualServiceAnalysisListener.java
new file mode 100644
index 0000000..54e5950
--- /dev/null
+++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/VirtualServiceAnalysisListener.java
@@ -0,0 +1,89 @@
+/*
+ * 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.oap.server.analyzer.provider.trace.parser.listener;
+
+import lombok.RequiredArgsConstructor;
+import org.apache.skywalking.apm.network.language.agent.v3.SegmentObject;
+import org.apache.skywalking.apm.network.language.agent.v3.SpanObject;
+import org.apache.skywalking.oap.server.analyzer.provider.AnalyzerModuleConfig;
+import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.vservice.VirtualCacheProcessor;
+import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.vservice.VirtualDatabaseProcessor;
+import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.vservice.VirtualServiceProcessor;
+import org.apache.skywalking.oap.server.core.CoreModule;
+import org.apache.skywalking.oap.server.core.config.NamingControl;
+import org.apache.skywalking.oap.server.core.source.SourceReceiver;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Virtual Service represent remote service
+ */
+
+@RequiredArgsConstructor
+public class VirtualServiceAnalysisListener implements ExitAnalysisListener, LocalAnalysisListener {
+
+    private final SourceReceiver sourceReceiver;
+    private final List<VirtualServiceProcessor> virtualServiceProcessors;
+
+    @Override
+    public void build() {
+        virtualServiceProcessors.forEach(p -> p.emitTo(sourceReceiver::receive));
+    }
+
+    @Override
+    public boolean containsPoint(Point point) {
+        return point == Point.Local || point == Point.Exit;
+    }
+
+    @Override
+    public void parseExit(SpanObject span, SegmentObject segmentObject) {
+        virtualServiceProcessors.forEach(p -> p.prepareVSIfNecessary(span, segmentObject));
+    }
+
+    @Override
+    public void parseLocal(SpanObject span, SegmentObject segmentObject) {
+        virtualServiceProcessors.forEach(p -> p.prepareVSIfNecessary(span, segmentObject));
+    }
+
+    public static class Factory implements AnalysisListenerFactory {
+        private final SourceReceiver sourceReceiver;
+        private final NamingControl namingControl;
+
+        public Factory(ModuleManager moduleManager) {
+            this.sourceReceiver = moduleManager.find(CoreModule.NAME).provider().getService(SourceReceiver.class);
+            this.namingControl = moduleManager.find(CoreModule.NAME)
+                    .provider()
+                    .getService(NamingControl.class);
+        }
+
+        @Override
+        public AnalysisListener create(ModuleManager moduleManager, AnalyzerModuleConfig config) {
+            return new VirtualServiceAnalysisListener(sourceReceiver,
+                    Arrays.asList(
+                            new VirtualCacheProcessor(namingControl, config),
+                            new VirtualDatabaseProcessor(namingControl, config)
+                    )
+            );
+        }
+    }
+
+}
+
diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/vservice/VirtualCacheProcessor.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/vservice/VirtualCacheProcessor.java
new file mode 100644
index 0000000..c550ed3
--- /dev/null
+++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/vservice/VirtualCacheProcessor.java
@@ -0,0 +1,128 @@
+/*
+ * 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.oap.server.analyzer.provider.trace.parser.listener.vservice;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.apm.network.common.v3.KeyStringValuePair;
+import org.apache.skywalking.apm.network.language.agent.v3.SegmentObject;
+import org.apache.skywalking.apm.network.language.agent.v3.SpanLayer;
+import org.apache.skywalking.apm.network.language.agent.v3.SpanObject;
+import org.apache.skywalking.oap.server.analyzer.provider.AnalyzerModuleConfig;
+import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.SpanTags;
+import org.apache.skywalking.oap.server.core.analysis.IDManager;
+import org.apache.skywalking.oap.server.core.analysis.Layer;
+import org.apache.skywalking.oap.server.core.analysis.TimeBucket;
+import org.apache.skywalking.oap.server.core.config.NamingControl;
+import org.apache.skywalking.oap.server.core.source.ServiceMeta;
+import org.apache.skywalking.oap.server.core.source.Source;
+import org.apache.skywalking.oap.server.core.source.VirtualCacheAccess;
+import org.apache.skywalking.oap.server.core.source.VirtualCacheOperation;
+import org.apache.skywalking.oap.server.core.source.VirtualCacheSlowAccess;
+import org.apache.skywalking.oap.server.library.util.StringUtil;
+
+@Slf4j
+@RequiredArgsConstructor
+public class VirtualCacheProcessor implements VirtualServiceProcessor {
+
+    private final NamingControl namingControl;
+
+    private final AnalyzerModuleConfig config;
+
+    private final List<Source> sourceList = new ArrayList<>();
+
+    @Override
+    public void prepareVSIfNecessary(SpanObject span, SegmentObject segmentObject) {
+        if (span.getSpanLayer() != SpanLayer.Cache) {
+            return;
+        }
+        Map<String, String> tags = span.getTagsList().stream()
+                                       .collect(
+                                           Collectors.toMap(KeyStringValuePair::getKey, KeyStringValuePair::getValue));
+
+        String cacheType = tags.get(SpanTags.CACHE_TYPE);
+        if (StringUtil.isBlank(cacheType)) {
+            return;
+        }
+        cacheType = cacheType.toLowerCase();
+        String peer = span.getPeer();
+        // peer is blank if it's a local span.
+        if (StringUtil.isBlank(peer)) {
+            peer = tags.get(SpanTags.CACHE_TYPE) + "-local";
+        }
+        long timeBucket = TimeBucket.getMinuteTimeBucket(span.getStartTime());
+        String serviceName = namingControl.formatServiceName(peer);
+        int latency = (int) (span.getEndTime() - span.getStartTime());
+        sourceList.add(parseServiceMeta(serviceName, timeBucket));
+        VirtualCacheOperation op = parseOperation(tags.get(SpanTags.CACHE_OP));
+        if ((op == VirtualCacheOperation.Write && latency > config.getCacheWriteLatencyThresholdsAndWatcher()
+                                                                  .getThreshold(cacheType))
+            || (op == VirtualCacheOperation.Read && latency > config.getCacheReadLatencyThresholdsAndWatcher()
+                                                                    .getThreshold(cacheType))) {
+            VirtualCacheSlowAccess slowAccess = new VirtualCacheSlowAccess();
+            slowAccess.setCacheServiceId(IDManager.ServiceID.buildId(serviceName, false));
+            slowAccess.setLatency(latency);
+            slowAccess.setId(segmentObject.getTraceSegmentId() + "-" + span.getSpanId());
+            slowAccess.setStatus(!span.getIsError());
+            slowAccess.setTraceId(segmentObject.getTraceId());
+            slowAccess.setCommand(tags.get(SpanTags.CACHE_CMD));
+            slowAccess.setKey(tags.get(SpanTags.CACHE_KEY));
+            slowAccess.setTimeBucket(TimeBucket.getRecordTimeBucket(span.getStartTime()));
+            slowAccess.setOperation(op);
+            sourceList.add(slowAccess);
+        }
+        VirtualCacheAccess access = new VirtualCacheAccess();
+        access.setCacheTypeId(span.getComponentId());
+        access.setLatency(latency);
+        access.setName(serviceName);
+        access.setStatus(!span.getIsError());
+        access.setTimeBucket(timeBucket);
+        access.setOperation(op);
+        sourceList.add(access);
+    }
+
+    private ServiceMeta parseServiceMeta(String serviceName, long timeBucket) {
+        ServiceMeta service = new ServiceMeta();
+        service.setName(serviceName);
+        service.setLayer(Layer.VIRTUAL_CACHE);
+        service.setTimeBucket(timeBucket);
+        return service;
+    }
+
+    private VirtualCacheOperation parseOperation(String op) {
+        if ("write".equals(op)) {
+            return VirtualCacheOperation.Write;
+        }
+        if ("read".equals(op)) {
+            return VirtualCacheOperation.Read;
+        }
+        return VirtualCacheOperation.Others;
+    }
+
+    @Override
+    public void emitTo(Consumer<Source> consumer) {
+        sourceList.forEach(consumer);
+    }
+
+}
diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/vservice/VirtualDatabaseProcessor.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/vservice/VirtualDatabaseProcessor.java
new file mode 100644
index 0000000..103303c
--- /dev/null
+++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/vservice/VirtualDatabaseProcessor.java
@@ -0,0 +1,122 @@
+/*
+ * 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.oap.server.analyzer.provider.trace.parser.listener.vservice;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.apm.network.common.v3.KeyStringValuePair;
+import org.apache.skywalking.apm.network.language.agent.v3.SegmentObject;
+import org.apache.skywalking.apm.network.language.agent.v3.SpanLayer;
+import org.apache.skywalking.apm.network.language.agent.v3.SpanObject;
+import org.apache.skywalking.oap.server.analyzer.provider.AnalyzerModuleConfig;
+import org.apache.skywalking.oap.server.analyzer.provider.trace.DBLatencyThresholdsAndWatcher;
+import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.SpanTags;
+import org.apache.skywalking.oap.server.core.analysis.IDManager;
+import org.apache.skywalking.oap.server.core.analysis.Layer;
+import org.apache.skywalking.oap.server.core.analysis.TimeBucket;
+import org.apache.skywalking.oap.server.core.config.NamingControl;
+import org.apache.skywalking.oap.server.core.source.DatabaseAccess;
+import org.apache.skywalking.oap.server.core.source.DatabaseSlowStatement;
+import org.apache.skywalking.oap.server.core.source.ServiceMeta;
+import org.apache.skywalking.oap.server.core.source.Source;
+import org.apache.skywalking.oap.server.library.util.StringUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+@Slf4j
+@RequiredArgsConstructor
+public class VirtualDatabaseProcessor implements VirtualServiceProcessor {
+
+    private final NamingControl namingControl;
+
+    private final AnalyzerModuleConfig config;
+
+    private List<Source> recordList = new ArrayList<>();
+
+    @Override
+    public void prepareVSIfNecessary(SpanObject span, SegmentObject segmentObject) {
+        if (span.getSpanLayer() != SpanLayer.Database) {
+            return;
+        }
+        String peer = span.getPeer();
+        long timeBucket = TimeBucket.getMinuteTimeBucket(span.getStartTime());
+        String serviceName = namingControl.formatServiceName(peer);
+        int latency = (int) (span.getEndTime() - span.getStartTime());
+        recordList.add(toServiceMeta(serviceName, timeBucket));
+        recordList.add(toDatabaseAccess(span, serviceName, timeBucket, latency));
+
+        readStatementIfSlow(span.getTagsList(), latency).ifPresent(statement -> {
+            DatabaseSlowStatement dbSlowStat = new DatabaseSlowStatement();
+            dbSlowStat.setId(segmentObject.getTraceSegmentId() + "-" + span.getSpanId());
+            dbSlowStat.setTraceId(segmentObject.getTraceId());
+            dbSlowStat.setDatabaseServiceId(IDManager.ServiceID.buildId(serviceName, false));
+            dbSlowStat.setStatement(statement);
+            dbSlowStat.setLatency(latency);
+            dbSlowStat.setTimeBucket(TimeBucket.getRecordTimeBucket(span.getStartTime()));
+            recordList.add(dbSlowStat);
+        });
+    }
+
+    private Optional<String> readStatementIfSlow(List<KeyStringValuePair> tags, int latency) {
+        String statement = null;
+        boolean isSlowDBAccess = false;
+        for (KeyStringValuePair tag : tags) {
+            if (SpanTags.DB_STATEMENT.equals(tag.getKey())) {
+                statement = StringUtil.cut(tag.getValue(), config.getMaxSlowSQLLength());
+            } else if (SpanTags.DB_TYPE.equals(tag.getKey())) {
+                String dbType = tag.getValue();
+                DBLatencyThresholdsAndWatcher thresholds = config.getDbLatencyThresholdsAndWatcher();
+                int threshold = thresholds.getThreshold(dbType);
+                if (latency > threshold) {
+                    isSlowDBAccess = true;
+                }
+            }
+        }
+        if (isSlowDBAccess) {
+            return Optional.ofNullable(statement).filter(StringUtil::isNotBlank);
+        }
+        return Optional.empty();
+    }
+
+    private ServiceMeta toServiceMeta(String serviceName, Long timeBucket) {
+        ServiceMeta service = new ServiceMeta();
+        service.setName(serviceName);
+        service.setLayer(Layer.VIRTUAL_DATABASE);
+        service.setTimeBucket(timeBucket);
+        return service;
+    }
+
+    private DatabaseAccess toDatabaseAccess(SpanObject span, String serviceName, long timeBucket, int latency) {
+        DatabaseAccess databaseAccess = new DatabaseAccess();
+        databaseAccess.setDatabaseTypeId(span.getComponentId());
+        databaseAccess.setLatency(latency);
+        databaseAccess.setName(serviceName);
+        databaseAccess.setStatus(!span.getIsError());
+        databaseAccess.setTimeBucket(timeBucket);
+        return databaseAccess;
+    }
+
+    @Override
+    public void emitTo(Consumer<Source> consumer) {
+        recordList.forEach(consumer);
+    }
+}
diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/vservice/VirtualServiceProcessor.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/vservice/VirtualServiceProcessor.java
new file mode 100644
index 0000000..4f8edbe
--- /dev/null
+++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/vservice/VirtualServiceProcessor.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.oap.server.analyzer.provider.trace.parser.listener.vservice;
+
+import org.apache.skywalking.apm.network.language.agent.v3.SegmentObject;
+import org.apache.skywalking.apm.network.language.agent.v3.SpanObject;
+import org.apache.skywalking.oap.server.core.source.Source;
+
+import java.util.function.Consumer;
+
+/**
+ * Virtual Service represent remote service
+ */
+public interface VirtualServiceProcessor {
+
+    /**
+     * Parse virtual service metadata and metrics data  if the span it's appropriate
+     */
+    void prepareVSIfNecessary(final SpanObject span, final SegmentObject segmentObject);
+
+    /**
+     * Emit collected metadata , metrics data to consumer
+     */
+    void emitTo(Consumer<Source> consumer);
+}
+
diff --git a/oap-server/analyzer/agent-analyzer/src/test/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/vservice/VirtualCacheProcessorTest.java b/oap-server/analyzer/agent-analyzer/src/test/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/vservice/VirtualCacheProcessorTest.java
new file mode 100644
index 0000000..d72754e
--- /dev/null
+++ b/oap-server/analyzer/agent-analyzer/src/test/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/vservice/VirtualCacheProcessorTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.oap.server.analyzer.provider.trace.parser.listener.vservice;
+
+import com.google.protobuf.ByteString;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.skywalking.apm.network.common.v3.KeyStringValuePair;
+import org.apache.skywalking.apm.network.language.agent.v3.SegmentObject;
+import org.apache.skywalking.apm.network.language.agent.v3.SpanLayer;
+import org.apache.skywalking.apm.network.language.agent.v3.SpanObject;
+import org.apache.skywalking.apm.network.language.agent.v3.SpanType;
+import org.apache.skywalking.oap.server.analyzer.provider.AnalyzerModuleConfig;
+import org.apache.skywalking.oap.server.analyzer.provider.trace.CacheReadLatencyThresholdsAndWatcher;
+import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.SpanTags;
+import org.apache.skywalking.oap.server.core.analysis.Layer;
+import org.apache.skywalking.oap.server.core.config.NamingControl;
+import org.apache.skywalking.oap.server.core.config.group.EndpointNameGrouping;
+import org.apache.skywalking.oap.server.core.source.ServiceMeta;
+import org.apache.skywalking.oap.server.core.source.Source;
+import org.apache.skywalking.oap.server.core.source.VirtualCacheAccess;
+import org.apache.skywalking.oap.server.core.source.VirtualCacheOperation;
+import org.apache.skywalking.oap.server.core.source.VirtualCacheSlowAccess;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class VirtualCacheProcessorTest {
+
+    @Test
+    public void testEmptySpan() {
+        SpanObject spanObject = SpanObject.newBuilder().setSpanLayer(SpanLayer.Cache).build();
+        SegmentObject segmentObject = SegmentObject.newBuilder().build();
+        VirtualCacheProcessor cacheVirtualServiceProcessor = buildCacheVirtualServiceProcessor();
+        cacheVirtualServiceProcessor.prepareVSIfNecessary(spanObject, segmentObject);
+        ArrayList<Source> sources = new ArrayList<>();
+        cacheVirtualServiceProcessor.emitTo(sources::add);
+        Assert.assertTrue(sources.isEmpty());
+    }
+
+    @Test
+    public void testExitSpan() {
+        SpanObject spanObject = SpanObject.newBuilder()
+                                          .setSpanLayer(SpanLayer.Cache)
+                                          .setSpanId(0)
+                                          .addAllTags(buildTags())
+                                          .setSpanType(SpanType.Exit)
+                                          .setPeerBytes(
+                                              ByteString.copyFrom("127.0.0.1:6379".getBytes(StandardCharsets.UTF_8)))
+                                          .setStartTime(getTimeInMillis("2022-09-12 14:13:12.790"))
+                                          .setEndTime(getTimeInMillis("2022-09-12 14:13:13.790"))
+                                          .build();
+        SegmentObject segmentObject = SegmentObject.newBuilder().setTraceId("trace-id").build();
+        VirtualCacheProcessor cacheVirtualServiceProcessor = buildCacheVirtualServiceProcessor();
+        cacheVirtualServiceProcessor.prepareVSIfNecessary(spanObject, segmentObject);
+        ArrayList<Source> sources = new ArrayList<>();
+        cacheVirtualServiceProcessor.emitTo(sources::add);
+        Assert.assertEquals(sources.size(), 3);
+
+        ServiceMeta serviceMeta = (ServiceMeta) sources.get(0);
+        Assert.assertEquals("127.0.0.1:6379", serviceMeta.getName());
+        Assert.assertEquals(202209121413L, serviceMeta.getTimeBucket());
+        Assert.assertEquals(Layer.VIRTUAL_CACHE, serviceMeta.getLayer());
+
+        VirtualCacheSlowAccess slowAccess = (VirtualCacheSlowAccess) sources.get(1);
+        Assert.assertEquals("MTI3LjAuMC4xOjYzNzk=.0", slowAccess.getCacheServiceId());
+        Assert.assertEquals(1000, slowAccess.getLatency());
+        Assert.assertEquals(20220912141312L, slowAccess.getTimeBucket());
+        Assert.assertEquals(VirtualCacheOperation.Read, slowAccess.getOperation());
+        Assert.assertNotNull(slowAccess.getTraceId());
+        Assert.assertNotNull(slowAccess.getCommand());
+        Assert.assertNotNull(slowAccess.getKey());
+
+        VirtualCacheAccess cacheAccess = (VirtualCacheAccess) sources.get(2);
+        Assert.assertEquals("127.0.0.1:6379", cacheAccess.getName());
+        Assert.assertEquals(1000, cacheAccess.getLatency());
+        Assert.assertEquals(202209121413L, cacheAccess.getTimeBucket());
+        Assert.assertNotNull(cacheAccess.getOperation());
+    }
+
+    @Test
+    public void testExitSpanLessThreshold() {
+        SpanObject spanObject = SpanObject.newBuilder()
+                                          .setSpanLayer(SpanLayer.Cache)
+                                          .setSpanId(0)
+                                          .addAllTags(buildTags())
+                                          .setSpanType(SpanType.Exit)
+                                          .setPeerBytes(
+                                              ByteString.copyFrom("127.0.0.1:6379".getBytes(StandardCharsets.UTF_8)))
+                                          .setStartTime(getTimeInMillis("2022-09-12 14:13:12.790"))
+                                          .setEndTime(getTimeInMillis("2022-09-12 14:13:12.793"))
+                                          .build();
+        SegmentObject segmentObject = SegmentObject.newBuilder().build();
+        VirtualCacheProcessor cacheVirtualServiceProcessor = buildCacheVirtualServiceProcessor();
+        cacheVirtualServiceProcessor.prepareVSIfNecessary(spanObject, segmentObject);
+        ArrayList<Source> sources = new ArrayList<>();
+        cacheVirtualServiceProcessor.emitTo(sources::add);
+        Assert.assertEquals(sources.size(), 2);
+
+        ServiceMeta serviceMeta = (ServiceMeta) sources.get(0);
+        Assert.assertEquals("127.0.0.1:6379", serviceMeta.getName());
+        Assert.assertEquals(202209121413L, serviceMeta.getTimeBucket());
+        Assert.assertEquals(Layer.VIRTUAL_CACHE, serviceMeta.getLayer());
+
+        VirtualCacheAccess cacheAccess = (VirtualCacheAccess) sources.get(1);
+        Assert.assertEquals("127.0.0.1:6379", cacheAccess.getName());
+        Assert.assertEquals(3, cacheAccess.getLatency());
+        Assert.assertEquals(202209121413L, cacheAccess.getTimeBucket());
+        Assert.assertNotNull(cacheAccess.getOperation());
+    }
+
+    @Test
+    public void testLocalSpan() {
+        SpanObject spanObject = SpanObject.newBuilder()
+                                          .setSpanLayer(SpanLayer.Cache)
+                                          .setSpanId(0)
+                                          .addAllTags(buildTags())
+                                          .setSpanType(SpanType.Local)
+                                          .setStartTime(getTimeInMillis("2022-09-12 14:13:12.790"))
+                                          .setEndTime(getTimeInMillis("2022-09-12 14:13:13.790"))
+                                          .build();
+        SegmentObject segmentObject = SegmentObject.newBuilder().build();
+        VirtualCacheProcessor cacheVirtualServiceProcessor = buildCacheVirtualServiceProcessor();
+        cacheVirtualServiceProcessor.prepareVSIfNecessary(spanObject, segmentObject);
+        ArrayList<Source> sources = new ArrayList<>();
+        cacheVirtualServiceProcessor.emitTo(sources::add);
+        Assert.assertEquals(sources.size(), 3);
+
+        ServiceMeta serviceMeta = (ServiceMeta) sources.get(0);
+        Assert.assertEquals("redis-local", serviceMeta.getName());
+        Assert.assertEquals(202209121413L, serviceMeta.getTimeBucket());
+        Assert.assertEquals(Layer.VIRTUAL_CACHE, serviceMeta.getLayer());
+
+        VirtualCacheSlowAccess slowAccess = (VirtualCacheSlowAccess) sources.get(1);
+        Assert.assertEquals("cmVkaXMtbG9jYWw=.0", slowAccess.getCacheServiceId());
+        Assert.assertEquals(1000, slowAccess.getLatency());
+        Assert.assertEquals(20220912141312L, slowAccess.getTimeBucket());
+        Assert.assertEquals(VirtualCacheOperation.Read, slowAccess.getOperation());
+        Assert.assertNotNull(slowAccess.getTraceId());
+        Assert.assertNotNull(slowAccess.getCommand());
+        Assert.assertNotNull(slowAccess.getKey());
+
+        VirtualCacheAccess cacheAccess = (VirtualCacheAccess) sources.get(2);
+        Assert.assertEquals("redis-local", cacheAccess.getName());
+        Assert.assertEquals(1000, cacheAccess.getLatency());
+        Assert.assertEquals(202209121413L, cacheAccess.getTimeBucket());
+        Assert.assertNotNull(cacheAccess.getOperation());
+    }
+
+    private long getTimeInMillis(String s) {
+        return DateTime.parse(s, DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS")).getMillis();
+    }
+
+    private List<KeyStringValuePair> buildTags() {
+        return Arrays.asList(
+            KeyStringValuePair.newBuilder().setKey(SpanTags.CACHE_KEY).setValue("test_key").build(),
+            KeyStringValuePair.newBuilder().setKey(SpanTags.CACHE_TYPE).setValue("redis").build(),
+            KeyStringValuePair.newBuilder().setKey(SpanTags.CACHE_CMD).setValue("get").build(),
+            KeyStringValuePair.newBuilder().setKey(SpanTags.CACHE_OP).setValue("read").build()
+
+        );
+    }
+
+    private VirtualCacheProcessor buildCacheVirtualServiceProcessor() {
+        NamingControl namingControl = new NamingControl(512, 512, 512, new EndpointNameGrouping());
+        AnalyzerModuleConfig config = new AnalyzerModuleConfig();
+        config.setCacheReadLatencyThresholdsAndWatcher(new CacheReadLatencyThresholdsAndWatcher("default:10", null));
+        return new VirtualCacheProcessor(namingControl, config);
+    }
+
+}
diff --git a/oap-server/analyzer/agent-analyzer/src/test/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/vservice/VirtualDatabaseProcessorTest.java b/oap-server/analyzer/agent-analyzer/src/test/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/vservice/VirtualDatabaseProcessorTest.java
new file mode 100644
index 0000000..0dbcc59
--- /dev/null
+++ b/oap-server/analyzer/agent-analyzer/src/test/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/vservice/VirtualDatabaseProcessorTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.oap.server.analyzer.provider.trace.parser.listener.vservice;
+
+import com.google.protobuf.ByteString;
+import org.apache.skywalking.apm.network.common.v3.KeyStringValuePair;
+import org.apache.skywalking.apm.network.language.agent.v3.SegmentObject;
+import org.apache.skywalking.apm.network.language.agent.v3.SpanLayer;
+import org.apache.skywalking.apm.network.language.agent.v3.SpanObject;
+import org.apache.skywalking.apm.network.language.agent.v3.SpanType;
+import org.apache.skywalking.oap.server.analyzer.provider.AnalyzerModuleConfig;
+import org.apache.skywalking.oap.server.analyzer.provider.trace.DBLatencyThresholdsAndWatcher;
+import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.SpanTags;
+import org.apache.skywalking.oap.server.core.analysis.Layer;
+import org.apache.skywalking.oap.server.core.config.NamingControl;
+import org.apache.skywalking.oap.server.core.config.group.EndpointNameGrouping;
+import org.apache.skywalking.oap.server.core.source.DatabaseAccess;
+import org.apache.skywalking.oap.server.core.source.DatabaseSlowStatement;
+import org.apache.skywalking.oap.server.core.source.ServiceMeta;
+import org.apache.skywalking.oap.server.core.source.Source;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormat;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class VirtualDatabaseProcessorTest {
+
+    @Test
+    public void testEmptySpan() {
+        SpanObject spanObject = SpanObject.newBuilder().setSpanLayer(SpanLayer.Cache).build();
+        SegmentObject segmentObject = SegmentObject.newBuilder().build();
+        VirtualDatabaseProcessor processor = buildVirtualServiceProcessor();
+        processor.prepareVSIfNecessary(spanObject, segmentObject);
+        ArrayList<Source> sources = new ArrayList<>();
+        processor.emitTo(sources::add);
+        Assert.assertTrue(sources.isEmpty());
+    }
+
+    @Test
+    public void testExitSpan() {
+        SpanObject spanObject = SpanObject.newBuilder()
+                .setSpanLayer(SpanLayer.Database)
+                .setSpanId(0)
+                .addAllTags(buildTags())
+                .setSpanType(SpanType.Exit)
+                .setPeerBytes(ByteString.copyFrom("127.0.0.1:3306".getBytes(StandardCharsets.UTF_8)))
+                .setStartTime(getTimeInMillis("2022-09-12 14:13:12.790"))
+                .setEndTime(getTimeInMillis("2022-09-12 14:13:13.790"))
+                .build();
+        SegmentObject segmentObject = SegmentObject.newBuilder()
+                .setTraceId("trace-id-1")
+                .build();
+        VirtualDatabaseProcessor processor = buildVirtualServiceProcessor();
+        processor.prepareVSIfNecessary(spanObject, segmentObject);
+        ArrayList<Source> sources = new ArrayList<>();
+        processor.emitTo(sources::add);
+        Assert.assertEquals(sources.size(), 3);
+
+        ServiceMeta serviceMeta = (ServiceMeta) sources.get(0);
+        Assert.assertEquals("127.0.0.1:3306", serviceMeta.getName());
+        Assert.assertEquals(202209121413L, serviceMeta.getTimeBucket());
+        Assert.assertEquals(Layer.VIRTUAL_DATABASE, serviceMeta.getLayer());
+
+        DatabaseAccess databaseAccess = (DatabaseAccess) sources.get(1);
+        Assert.assertEquals("127.0.0.1:3306", databaseAccess.getName());
+        Assert.assertEquals(1000, databaseAccess.getLatency());
+        Assert.assertEquals(202209121413L, databaseAccess.getTimeBucket());
+
+        DatabaseSlowStatement slowStatement = (DatabaseSlowStatement) sources.get(2);
+        Assert.assertEquals("MTI3LjAuMC4xOjMzMDY=.0", slowStatement.getDatabaseServiceId());
+        Assert.assertEquals(1000, slowStatement.getLatency());
+        Assert.assertEquals(20220912141312L, slowStatement.getTimeBucket());
+        Assert.assertEquals("trace-id-1", slowStatement.getTraceId());
+    }
+
+    @Test
+    public void testExitSpanLessThreshold() {
+        SpanObject spanObject = SpanObject.newBuilder()
+                .setSpanLayer(SpanLayer.Database)
+                .setSpanId(0)
+                .addAllTags(buildTags())
+                .setSpanType(SpanType.Exit)
+                .setPeerBytes(ByteString.copyFrom("127.0.0.1:3306".getBytes(StandardCharsets.UTF_8)))
+                .setStartTime(getTimeInMillis("2022-09-12 14:13:12.790"))
+                .setEndTime(getTimeInMillis("2022-09-12 14:13:12.793"))
+                .build();
+        SegmentObject segmentObject = SegmentObject.newBuilder().build();
+        VirtualDatabaseProcessor processor = buildVirtualServiceProcessor();
+        processor.prepareVSIfNecessary(spanObject, segmentObject);
+        ArrayList<Source> sources = new ArrayList<>();
+        processor.emitTo(sources::add);
+        Assert.assertEquals(sources.size(), 2);
+
+        ServiceMeta serviceMeta = (ServiceMeta) sources.get(0);
+        Assert.assertEquals("127.0.0.1:3306", serviceMeta.getName());
+        Assert.assertEquals(202209121413L, serviceMeta.getTimeBucket());
+        Assert.assertEquals(Layer.VIRTUAL_DATABASE, serviceMeta.getLayer());
+
+        DatabaseAccess databaseAccess = (DatabaseAccess) sources.get(1);
+
+        Assert.assertEquals("127.0.0.1:3306", databaseAccess.getName());
+        Assert.assertEquals(3, databaseAccess.getLatency());
+        Assert.assertEquals(202209121413L, databaseAccess.getTimeBucket());
+    }
+
+    private long getTimeInMillis(String s) {
+        return DateTime.parse(s, DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS")).getMillis();
+    }
+
+    private List<KeyStringValuePair> buildTags() {
+        return Arrays.asList(
+                KeyStringValuePair.newBuilder().setKey(SpanTags.DB_STATEMENT).setValue("select * from dual").build(),
+                KeyStringValuePair.newBuilder().setKey(SpanTags.DB_TYPE).setValue("Mysql").build()
+        );
+    }
+
+    private VirtualDatabaseProcessor buildVirtualServiceProcessor() {
+        NamingControl namingControl = new NamingControl(512, 512, 512, new EndpointNameGrouping());
+        AnalyzerModuleConfig config = new AnalyzerModuleConfig();
+        config.setDbLatencyThresholdsAndWatcher(new DBLatencyThresholdsAndWatcher("default:10", null));
+        return new VirtualDatabaseProcessor(namingControl, config);
+    }
+
+}
diff --git a/oap-server/oal-grammar/src/main/antlr4/org/apache/skywalking/oal/rt/grammar/OALLexer.g4 b/oap-server/oal-grammar/src/main/antlr4/org/apache/skywalking/oal/rt/grammar/OALLexer.g4
index f8cd022..655439d 100644
--- a/oap-server/oal-grammar/src/main/antlr4/org/apache/skywalking/oal/rt/grammar/OALLexer.g4
+++ b/oap-server/oal-grammar/src/main/antlr4/org/apache/skywalking/oal/rt/grammar/OALLexer.g4
@@ -45,6 +45,8 @@
 SRC_SERVICE_INSTANCE_CLR_THREAD: 'ServiceInstanceCLRThread';
 SRC_ENVOY_INSTANCE_METRIC: 'EnvoyInstanceMetric';
 SRC_EVENT: 'Event';
+SRC_CACHE_ACCESS: 'VirtualCacheAccess';
+
 
 // Browser keywords
 SRC_BROWSER_APP_PERF: 'BrowserAppPerf';
diff --git a/oap-server/oal-grammar/src/main/antlr4/org/apache/skywalking/oal/rt/grammar/OALParser.g4 b/oap-server/oal-grammar/src/main/antlr4/org/apache/skywalking/oal/rt/grammar/OALParser.g4
index 43f2c0e..0c565ef 100644
--- a/oap-server/oal-grammar/src/main/antlr4/org/apache/skywalking/oal/rt/grammar/OALParser.g4
+++ b/oap-server/oal-grammar/src/main/antlr4/org/apache/skywalking/oal/rt/grammar/OALParser.g4
@@ -50,7 +50,7 @@
     ;
 
 source
-    : SRC_SERVICE | SRC_DATABASE_ACCESS | SRC_SERVICE_INSTANCE | SRC_ENDPOINT |
+    : SRC_SERVICE | SRC_DATABASE_ACCESS | SRC_SERVICE_INSTANCE | SRC_ENDPOINT | SRC_CACHE_ACCESS |
       SRC_SERVICE_RELATION | SRC_SERVICE_INSTANCE_RELATION | SRC_ENDPOINT_RELATION |
       SRC_SERVICE_INSTANCE_CLR_CPU | SRC_SERVICE_INSTANCE_CLR_GC | SRC_SERVICE_INSTANCE_CLR_THREAD |
       SRC_SERVICE_INSTANCE_JVM_CPU | SRC_SERVICE_INSTANCE_JVM_MEMORY | SRC_SERVICE_INSTANCE_JVM_MEMORY_POOL | SRC_SERVICE_INSTANCE_JVM_GC | SRC_SERVICE_INSTANCE_JVM_THREAD | SRC_SERVICE_INSTANCE_JVM_CLASS |// JVM source of service instance
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/cache/CacheSlowAccessDispatcher.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/cache/CacheSlowAccessDispatcher.java
new file mode 100644
index 0000000..5d5e412
--- /dev/null
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/cache/CacheSlowAccessDispatcher.java
@@ -0,0 +1,51 @@
+/*
+ * 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.oap.server.core.analysis.manual.cache;
+
+import org.apache.skywalking.oap.server.core.analysis.SourceDispatcher;
+import org.apache.skywalking.oap.server.core.analysis.worker.TopNStreamProcessor;
+import org.apache.skywalking.oap.server.core.source.VirtualCacheOperation;
+import org.apache.skywalking.oap.server.core.source.VirtualCacheSlowAccess;
+
+public class CacheSlowAccessDispatcher implements SourceDispatcher<VirtualCacheSlowAccess> {
+
+    @Override
+    public void dispatch(VirtualCacheSlowAccess source) {
+        // There are only two kinds of Operation : write or read .Refer VirtualCacheProcessor#prepareVSIfNecessary
+        if (source.getOperation() == VirtualCacheOperation.Read) {
+            TopNCacheReadCommand readCommand = new TopNCacheReadCommand();
+            readCommand.setId(source.getId());
+            readCommand.setCommand(source.getCommand() + " " + source.getKey());
+            readCommand.setLatency(source.getLatency());
+            readCommand.setTraceId(source.getTraceId());
+            readCommand.setServiceId(source.getCacheServiceId());
+            readCommand.setTimeBucket(source.getTimeBucket());
+            TopNStreamProcessor.getInstance().in(readCommand);
+        } else if (source.getOperation() == VirtualCacheOperation.Write) {
+            TopNCacheWriteCommand writeCommand = new TopNCacheWriteCommand();
+            writeCommand.setId(source.getId());
+            writeCommand.setCommand(source.getCommand() + " " + source.getKey());
+            writeCommand.setLatency(source.getLatency());
+            writeCommand.setTraceId(source.getTraceId());
+            writeCommand.setServiceId(source.getCacheServiceId());
+            writeCommand.setTimeBucket(source.getTimeBucket());
+            TopNStreamProcessor.getInstance().in(writeCommand);
+        }
+    }
+}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/cache/TopNCacheReadCommand.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/cache/TopNCacheReadCommand.java
new file mode 100644
index 0000000..42ecf57
--- /dev/null
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/cache/TopNCacheReadCommand.java
@@ -0,0 +1,88 @@
+/*
+ * 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.oap.server.core.analysis.manual.cache;
+
+import java.util.Objects;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.skywalking.oap.server.core.analysis.Stream;
+import org.apache.skywalking.oap.server.core.analysis.topn.TopN;
+import org.apache.skywalking.oap.server.core.analysis.worker.TopNStreamProcessor;
+import org.apache.skywalking.oap.server.core.source.DefaultScopeDefine;
+import org.apache.skywalking.oap.server.core.storage.annotation.Column;
+import org.apache.skywalking.oap.server.core.storage.type.Convert2Entity;
+import org.apache.skywalking.oap.server.core.storage.type.Convert2Storage;
+import org.apache.skywalking.oap.server.core.storage.type.StorageBuilder;
+
+/**
+ * Database TopN statement, including Database SQL statement, mongoDB and Redis commands.
+ */
+@Stream(name = TopNCacheReadCommand.INDEX_NAME, scopeId = DefaultScopeDefine.CACHE_SLOW_ACCESS, builder = TopNCacheReadCommand.Builder.class, processor = TopNStreamProcessor.class)
+public class TopNCacheReadCommand extends TopN {
+    public static final String INDEX_NAME = "top_n_cache_read_command";
+
+    @Setter
+    private String id;
+    @Getter
+    @Setter
+    @Column(columnName = STATEMENT, length = 2000, lengthEnvVariable = "SW_SLOW_DB_THRESHOLD", storageOnly = true)
+    private String command;
+
+    @Override
+    public String id() {
+        return id;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        TopNCacheReadCommand statement = (TopNCacheReadCommand) o;
+        return Objects.equals(getServiceId(), statement.getServiceId());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getServiceId());
+    }
+
+    public static class Builder implements StorageBuilder<TopNCacheReadCommand> {
+        @Override
+        public TopNCacheReadCommand storage2Entity(final Convert2Entity converter) {
+            TopNCacheReadCommand statement = new TopNCacheReadCommand();
+            statement.setCommand((String) converter.get(STATEMENT));
+            statement.setTraceId((String) converter.get(TRACE_ID));
+            statement.setLatency(((Number) converter.get(LATENCY)).longValue());
+            statement.setServiceId((String) converter.get(SERVICE_ID));
+            statement.setTimeBucket(((Number) converter.get(TIME_BUCKET)).longValue());
+            return statement;
+        }
+
+        @Override
+        public void entity2Storage(final TopNCacheReadCommand storageData, final Convert2Storage converter) {
+            converter.accept(STATEMENT, storageData.getCommand());
+            converter.accept(TRACE_ID, storageData.getTraceId());
+            converter.accept(LATENCY, storageData.getLatency());
+            converter.accept(SERVICE_ID, storageData.getServiceId());
+            converter.accept(TIME_BUCKET, storageData.getTimeBucket());
+        }
+    }
+}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/cache/TopNCacheWriteCommand.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/cache/TopNCacheWriteCommand.java
new file mode 100644
index 0000000..7e06ecd
--- /dev/null
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/cache/TopNCacheWriteCommand.java
@@ -0,0 +1,89 @@
+/*
+ * 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.oap.server.core.analysis.manual.cache;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.skywalking.oap.server.core.analysis.Stream;
+import org.apache.skywalking.oap.server.core.analysis.topn.TopN;
+import org.apache.skywalking.oap.server.core.analysis.worker.TopNStreamProcessor;
+import org.apache.skywalking.oap.server.core.source.DefaultScopeDefine;
+import org.apache.skywalking.oap.server.core.storage.annotation.Column;
+import org.apache.skywalking.oap.server.core.storage.type.Convert2Entity;
+import org.apache.skywalking.oap.server.core.storage.type.Convert2Storage;
+import org.apache.skywalking.oap.server.core.storage.type.StorageBuilder;
+
+import java.util.Objects;
+
+/**
+ * Database TopN statement, including Database SQL statement, mongoDB and Redis commands.
+ */
+@Stream(name = TopNCacheWriteCommand.INDEX_NAME, scopeId = DefaultScopeDefine.CACHE_SLOW_ACCESS, builder = TopNCacheWriteCommand.Builder.class, processor = TopNStreamProcessor.class)
+public class TopNCacheWriteCommand extends TopN {
+    public static final String INDEX_NAME = "top_n_cache_write_command";
+
+    @Setter
+    private String id;
+    @Getter
+    @Setter
+    @Column(columnName = STATEMENT, length = 2000, lengthEnvVariable = "SW_SLOW_DB_THRESHOLD", storageOnly = true)
+    private String command;
+
+    @Override
+    public String id() {
+        return id;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        TopNCacheWriteCommand statement = (TopNCacheWriteCommand) o;
+        return Objects.equals(getServiceId(), statement.getServiceId());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getServiceId());
+    }
+
+    public static class Builder implements StorageBuilder<TopNCacheWriteCommand> {
+        @Override
+        public TopNCacheWriteCommand storage2Entity(final Convert2Entity converter) {
+            TopNCacheWriteCommand statement = new TopNCacheWriteCommand();
+            statement.setCommand((String) converter.get(STATEMENT));
+            statement.setTraceId((String) converter.get(TRACE_ID));
+            statement.setLatency(((Number) converter.get(LATENCY)).longValue());
+            statement.setServiceId((String) converter.get(SERVICE_ID));
+            statement.setTimeBucket(((Number) converter.get(TIME_BUCKET)).longValue());
+            return statement;
+        }
+
+        @Override
+        public void entity2Storage(final TopNCacheWriteCommand storageData, final Convert2Storage converter) {
+            converter.accept(STATEMENT, storageData.getCommand());
+            converter.accept(TRACE_ID, storageData.getTraceId());
+            converter.accept(LATENCY, storageData.getLatency());
+            converter.accept(SERVICE_ID, storageData.getServiceId());
+            converter.accept(TIME_BUCKET, storageData.getTimeBucket());
+        }
+    }
+}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/database/TopNDatabaseStatement.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/database/TopNDatabaseStatement.java
index ff07480..1b0fe89 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/database/TopNDatabaseStatement.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/database/TopNDatabaseStatement.java
@@ -56,7 +56,7 @@
         if (o == null || getClass() != o.getClass())
             return false;
         TopNDatabaseStatement statement = (TopNDatabaseStatement) o;
-        return getServiceId() == statement.getServiceId();
+        return Objects.equals(getServiceId(), statement.getServiceId());
     }
 
     @Override
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/management/ui/template/UITemplateInitializer.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/management/ui/template/UITemplateInitializer.java
index aad1bfd..2fb8770 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/management/ui/template/UITemplateInitializer.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/management/ui/template/UITemplateInitializer.java
@@ -54,6 +54,7 @@
         Layer.BROWSER.name(),
         Layer.SO11Y_OAP.name(),
         Layer.VIRTUAL_DATABASE.name(),
+        Layer.VIRTUAL_CACHE.name(),
         Layer.K8S_SERVICE.name(),
         Layer.SO11Y_SATELLITE.name(),
         Layer.FAAS.name(),
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/DefaultScopeDefine.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/DefaultScopeDefine.java
index 4b8af1f..05d6e9a 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/DefaultScopeDefine.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/DefaultScopeDefine.java
@@ -106,6 +106,8 @@
     public static final int ZIPKIN_SERVICE_SPAN = 52;
     public static final int ZIPKIN_SERVICE_RELATION = 53;
     public static final int PROCESS_RELATION = 54;
+    public static final int CACHE_ACCESS = 55;
+    public static final int CACHE_SLOW_ACCESS = 56;
 
     /**
      * Catalog of scope, the metrics processor could use this to group all generated metrics by oal rt.
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/VirtualCacheAccess.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/VirtualCacheAccess.java
new file mode 100644
index 0000000..73b14f7
--- /dev/null
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/VirtualCacheAccess.java
@@ -0,0 +1,58 @@
+/*
+ * 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.oap.server.core.source;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.skywalking.oap.server.core.analysis.IDManager;
+
+import static org.apache.skywalking.oap.server.core.source.DefaultScopeDefine.CACHE_ACCESS;
+import static org.apache.skywalking.oap.server.core.source.DefaultScopeDefine.SERVICE_CATALOG_NAME;
+
+@ScopeDeclaration(id = CACHE_ACCESS, name = "VirtualCacheAccess", catalog = SERVICE_CATALOG_NAME)
+@ScopeDefaultColumn.VirtualColumnDefinition(fieldName = "entityId", columnName = "entity_id", isID = true, type = String.class)
+public class VirtualCacheAccess extends Source {
+
+    @Override
+    public int scope() {
+        return CACHE_ACCESS;
+    }
+
+    @Override
+    public String getEntityId() {
+        return IDManager.ServiceID.buildId(name, false);
+    }
+
+    @Getter
+    @Setter
+    @ScopeDefaultColumn.DefinedByField(columnName = "name", requireDynamicActive = true)
+    private String name;
+    @Getter
+    @Setter
+    private int cacheTypeId;
+    @Getter
+    @Setter
+    private int latency;
+    @Getter
+    @Setter
+    private boolean status;
+    @Getter
+    @Setter
+    private VirtualCacheOperation operation;
+}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/VirtualCacheOperation.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/VirtualCacheOperation.java
new file mode 100644
index 0000000..fe8ad34
--- /dev/null
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/VirtualCacheOperation.java
@@ -0,0 +1,28 @@
+/*
+ * 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.oap.server.core.source;
+
+/**
+ * Represents an operation of cache access
+ */
+public enum VirtualCacheOperation {
+    Write,
+    Read,
+    Others,
+}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/VirtualCacheSlowAccess.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/VirtualCacheSlowAccess.java
new file mode 100644
index 0000000..863102e
--- /dev/null
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/VirtualCacheSlowAccess.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.oap.server.core.source;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.skywalking.oap.server.core.Const;
+
+import static org.apache.skywalking.oap.server.core.source.DefaultScopeDefine.CACHE_SLOW_ACCESS;
+import static org.apache.skywalking.oap.server.core.source.DefaultScopeDefine.SERVICE_CATALOG_NAME;
+
+@ScopeDeclaration(id = CACHE_SLOW_ACCESS, name = "VirtualCacheSlowAccess", catalog = SERVICE_CATALOG_NAME)
+public class VirtualCacheSlowAccess extends Source {
+    @Getter
+    @Setter
+    private String id;
+    @Getter
+    @Setter
+    private String cacheServiceId;
+    @Getter
+    @Setter
+    private String command;
+    @Getter
+    @Setter
+    private String key;
+    @Getter
+    @Setter
+    private long latency;
+    @Getter
+    @Setter
+    private String traceId;
+
+    @Getter
+    @Setter
+    private boolean status;
+
+    @Getter
+    @Setter
+    private VirtualCacheOperation operation;
+
+    @Override
+    public int scope() {
+        return DefaultScopeDefine.CACHE_SLOW_ACCESS;
+    }
+
+    @Override
+    public String getEntityId() {
+        return Const.EMPTY_STRING;
+    }
+
+}
diff --git a/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/RPCAnalysisListenerTest.java b/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/RPCAnalysisListenerTest.java
index 93f9287..0cf4c7d 100644
--- a/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/RPCAnalysisListenerTest.java
+++ b/oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/trace/provider/parser/listener/RPCAnalysisListenerTest.java
@@ -19,7 +19,6 @@
 package org.apache.skywalking.oap.server.receiver.trace.provider.parser.listener;
 
 import com.google.gson.JsonObject;
-import java.util.List;
 import org.apache.skywalking.apm.network.common.v3.KeyStringValuePair;
 import org.apache.skywalking.apm.network.language.agent.v3.RefType;
 import org.apache.skywalking.apm.network.language.agent.v3.SegmentObject;
@@ -34,19 +33,16 @@
 import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.RPCAnalysisListener;
 import org.apache.skywalking.oap.server.core.Const;
 import org.apache.skywalking.oap.server.core.analysis.IDManager;
-import org.apache.skywalking.oap.server.core.analysis.Layer;
 import org.apache.skywalking.oap.server.core.analysis.manual.networkalias.NetworkAddressAlias;
 import org.apache.skywalking.oap.server.core.cache.NetworkAddressAliasCache;
 import org.apache.skywalking.oap.server.core.config.NamingControl;
 import org.apache.skywalking.oap.server.core.config.group.EndpointNameGrouping;
-import org.apache.skywalking.oap.server.core.source.DatabaseAccess;
 import org.apache.skywalking.oap.server.core.source.Endpoint;
 import org.apache.skywalking.oap.server.core.source.EndpointRelation;
 import org.apache.skywalking.oap.server.core.source.ISource;
 import org.apache.skywalking.oap.server.core.source.Service;
 import org.apache.skywalking.oap.server.core.source.ServiceInstance;
 import org.apache.skywalking.oap.server.core.source.ServiceInstanceRelation;
-import org.apache.skywalking.oap.server.core.source.ServiceMeta;
 import org.apache.skywalking.oap.server.core.source.ServiceRelation;
 import org.junit.Assert;
 import org.junit.Before;
@@ -55,6 +51,8 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
+
 import static org.apache.skywalking.oap.server.analyzer.provider.trace.parser.SpanTags.LOGIC_ENDPOINT;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.when;
@@ -418,18 +416,13 @@
         listener.build();
 
         final List<ISource> receivedSources = mockReceiver.getReceivedSources();
-        Assert.assertEquals(4, receivedSources.size());
+        Assert.assertEquals(2, receivedSources.size());
         final ServiceRelation serviceRelation = (ServiceRelation) receivedSources.get(0);
         final ServiceInstanceRelation serviceInstanceRelation = (ServiceInstanceRelation) receivedSources.get(1);
-        final ServiceMeta serviceMeta = (ServiceMeta) receivedSources.get(2);
-        final DatabaseAccess databaseAccess = (DatabaseAccess) receivedSources.get(3);
         Assert.assertEquals("mock-service", serviceRelation.getSourceServiceName());
         Assert.assertEquals("127.0.0.1:8080", serviceRelation.getDestServiceName());
         Assert.assertEquals("mock-instance", serviceInstanceRelation.getSourceServiceInstanceName());
         Assert.assertEquals("127.0.0.1:8080", serviceInstanceRelation.getDestServiceInstanceName());
-        Assert.assertEquals("127.0.0.1:8080", serviceMeta.getName());
-        Assert.assertEquals(Layer.VIRTUAL_DATABASE, serviceMeta.getLayer());
-        Assert.assertEquals("127.0.0.1:8080", databaseAccess.getName());
     }
 
     /**
diff --git a/oap-server/server-starter/src/main/resources/application.yml b/oap-server/server-starter/src/main/resources/application.yml
index f52ecaf..85f53bb 100644
--- a/oap-server/server-starter/src/main/resources/application.yml
+++ b/oap-server/server-starter/src/main/resources/application.yml
@@ -235,6 +235,8 @@
     # Exit spans with the component in the list would not generate the client-side instance relation metrics.
     noUpstreamRealAddressAgents: ${SW_NO_UPSTREAM_REAL_ADDRESS:6000,9000}
     meterAnalyzerActiveFiles: ${SW_METER_ANALYZER_ACTIVE_FILES:datasource,threadpool,satellite} # Which files could be meter analyzed, files split by ","
+    slowCacheReadThreshold: ${SW_SLOW_CACHE_SLOW_READ_THRESHOLD:default:20,redis:10} # The slow cache write operation thresholds. Unit ms.
+    slowCacheWriteThreshold: ${SW_SLOW_CACHE_SLOW_WRITE_THRESHOLD:default:20,redis:10} # The slow cache write operation thresholds. Unit ms.
 
 log-analyzer:
   selector: ${SW_LOG_ANALYZER:default}
diff --git a/oap-server/server-starter/src/main/resources/oal/core.oal b/oap-server/server-starter/src/main/resources/oal/core.oal
index 9d11a0b..3bf7757 100755
--- a/oap-server/server-starter/src/main/resources/oal/core.oal
+++ b/oap-server/server-starter/src/main/resources/oal/core.oal
@@ -71,3 +71,18 @@
 database_access_sla = from(DatabaseAccess.*).percent(status == true);
 database_access_cpm = from(DatabaseAccess.*).cpm();
 database_access_percentile = from(DatabaseAccess.latency).percentile(10);
+
+cache_read_resp_time = from(VirtualCacheAccess.latency).filter(operation == VirtualCacheOperation.Read).longAvg();
+cache_read_sla = from(VirtualCacheAccess.*).filter(operation == VirtualCacheOperation.Read).percent(status == true);
+cache_read_cpm = from(VirtualCacheAccess.*).filter(operation == VirtualCacheOperation.Read).cpm();
+cache_read_percentile = from(VirtualCacheAccess.latency).filter(operation == VirtualCacheOperation.Read).percentile(10);
+
+cache_write_resp_time = from(VirtualCacheAccess.latency).filter(operation == VirtualCacheOperation.Write).longAvg();
+cache_write_sla = from(VirtualCacheAccess.*).filter(operation == VirtualCacheOperation.Write).percent(status == true);
+cache_write_cpm = from(VirtualCacheAccess.*).filter(operation == VirtualCacheOperation.Write).cpm();
+cache_write_percentile = from(VirtualCacheAccess.latency).filter(operation == VirtualCacheOperation.Write).percentile(10);
+
+cache_access_resp_time = from(VirtualCacheAccess.latency).longAvg();
+cache_access_sla = from(VirtualCacheAccess.*).percent(status == true);
+cache_access_cpm = from(VirtualCacheAccess.*).cpm();
+cache_access_percentile = from(VirtualCacheAccess.latency).percentile(10);
\ No newline at end of file
diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/virtual_cache/virtual-cache-root.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/virtual_cache/virtual-cache-root.json
new file mode 100644
index 0000000..33e86f8
--- /dev/null
+++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/virtual_cache/virtual-cache-root.json
@@ -0,0 +1,101 @@
+/**
+ * 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.
+ */
+
+[
+  {
+    "id": "Virtual-Cache-Root",
+    "configuration": {
+      "children": [
+        {
+          "x": 0,
+          "y": 2,
+          "w": 24,
+          "h": 52,
+          "i": "0",
+          "type": "Widget",
+          "widget": {
+            "title": "Virtual Cache"
+          },
+          "graph": {
+            "type": "ServiceList",
+            "dashboardName": "Virtual-Cache-Service",
+            "fontSize": 12,
+            "showXAxis": false,
+            "showYAxis": false,
+            "showGroup": false
+          },
+          "metrics": [
+            "cache_access_resp_time",
+            "cache_access_sla",
+            "cache_access_cpm"
+          ],
+          "metricTypes": [
+            "readMetricsValues",
+            "readMetricsValues",
+            "readMetricsValues"
+          ],
+          "moved": false,
+          "metricConfig": [
+            {
+              "unit": "ms",
+              "label": "Access Latency",
+              "calculation": "average"
+            },
+            {
+              "label": "Successful Access Rate",
+              "unit": "%",
+              "calculation": "percentageAvg"
+            },
+            {
+              "label": "Access Traffic",
+              "unit": "calls / min",
+              "calculation": "average"
+            }
+          ]
+        },
+        {
+          "x": 0,
+          "y": 0,
+          "w": 24,
+          "h": 2,
+          "i": "100",
+          "type": "Text",
+          "metricTypes": [
+            ""
+          ],
+          "metrics": [
+            ""
+          ],
+          "graph": {
+            "fontColor": "blue",
+            "backgroundColor": "white",
+            "content": "Observe the Virtual Cache which is conjectured by language agent through various plugins.",
+            "fontSize": 14,
+            "textAlign": "left",
+            "url": "https://skywalking.apache.org/docs/main/latest/en/setup/service-agent/virtual-cache/"
+          },
+          "moved": false
+        }
+      ],
+      "id": "Virtual-Cache-Root",
+      "layer": "VIRTUAL_CACHE",
+      "entity": "All",
+      "name": "Virtual-Cache-Root",
+      "isRoot": true
+    }
+  }
+]
diff --git a/oap-server/server-starter/src/main/resources/ui-initialized-templates/virtual_cache/virtual-cache-service.json b/oap-server/server-starter/src/main/resources/ui-initialized-templates/virtual_cache/virtual-cache-service.json
new file mode 100644
index 0000000..2e62604
--- /dev/null
+++ b/oap-server/server-starter/src/main/resources/ui-initialized-templates/virtual_cache/virtual-cache-service.json
@@ -0,0 +1,622 @@
+/**
+ * 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.
+ */
+
+[
+  {
+    "id": "Virtual-Cache-Service",
+    "configuration": {
+      "children": [
+        {
+          "x": 0,
+          "y": 0,
+          "w": 6,
+          "h": 13,
+          "i": "1",
+          "type": "Widget",
+          "widget": {
+            "title": "Access Avg Response Time (ms)",
+            "name": "Access_Avg_Response"
+          },
+          "graph": {
+            "type": "Line",
+            "step": false,
+            "smooth": false,
+            "showSymbol": false,
+            "showXAxis": true,
+            "showYAxis": true
+          },
+          "metrics": [
+            "cache_access_resp_time"
+          ],
+          "metricTypes": [
+            "readMetricsValues"
+          ],
+          "value": "6",
+          "label": "Access_Avg_Response",
+          "filters": {
+            "dataIndex": 17,
+            "sourceId": "4"
+          },
+          "associate": [
+            {
+              "widgetId": "2"
+            },
+            {
+              "widgetId": "3"
+            },
+            {
+              "widgetId": "4"
+            }
+          ]
+        },
+
+        {
+          "x": 6,
+          "y": 0,
+          "w": 6,
+          "h": 13,
+          "i": "2",
+          "type": "Widget",
+          "widget": {
+            "title": "Access Successful Rate (%)",
+            "name": "Access_Successful_Rate"
+          },
+          "graph": {
+            "type": "Line",
+            "step": false,
+            "smooth": false,
+            "showSymbol": false,
+            "showXAxis": true,
+            "showYAxis": true
+          },
+          "metrics": [
+            "cache_access_sla"
+          ],
+          "metricTypes": [
+            "readMetricsValues"
+          ],
+          "metricConfig": [
+            {
+              "calculation": "percentage"
+            }
+          ],
+          "value": "3",
+          "label": "Successful_Rate",
+          "filters": {
+            "dataIndex": 17,
+            "sourceId": "4"
+          },
+          "associate": [
+            {
+              "widgetId": "1"
+            },
+            {
+              "widgetId": "3"
+            },
+            {
+              "widgetId": "4"
+            }
+          ]
+        },
+        {
+          "x": 12,
+          "y": 0,
+          "w": 6,
+          "h": 13,
+          "i": "3",
+          "type": "Widget",
+          "widget": {
+            "title": "Access Traffic (calls / min)",
+            "name": "Access_Traffic"
+          },
+          "graph": {
+            "type": "Line",
+            "step": false,
+            "smooth": false,
+            "showSymbol": false,
+            "showXAxis": true,
+            "showYAxis": true
+          },
+          "metrics": [
+            "cache_access_cpm"
+          ],
+          "metricTypes": [
+            "readMetricsValues"
+          ],
+          "value": "4",
+          "label": "Cache_Traffic",
+          "associate": [
+            {
+              "widgetId": "1"
+            },
+            {
+              "widgetId": "2"
+            },
+            {
+              "widgetId": "4"
+            }
+          ]
+        },
+        {
+          "x": 18,
+          "y": 0,
+          "w": 6,
+          "h": 13,
+          "i": "4",
+          "type": "Widget",
+          "widget": {
+            "title": "Access Latency Percentile (ms)",
+            "name": "Access_Latency_Percentile"
+          },
+          "graph": {
+            "type": "Line",
+            "step": false,
+            "smooth": false,
+            "showSymbol": false,
+            "showXAxis": true,
+            "showYAxis": true
+          },
+          "metrics": [
+            "cache_access_percentile"
+          ],
+          "metricTypes": [
+            "readLabeledMetricsValues"
+          ],
+          "metricConfig": [
+            {
+              "label": "P50, P75, P90, P95, P99",
+              "labelsIndex": "0,1,2,3,4"
+            }
+          ],
+          "value": "5",
+          "label": "5",
+          "associate": [
+            {
+              "widgetId": "1"
+            },
+            {
+              "widgetId": "2"
+            },
+            {
+              "widgetId": "3"
+            }
+          ],
+          "filters": {
+            "dataIndex": 17,
+            "sourceId": "4"
+          }
+        },
+
+
+
+
+        {
+          "x": 0,
+          "y": 13,
+          "w": 6,
+          "h": 13,
+          "i": "5",
+          "type": "Widget",
+          "widget": {
+            "title": "Read Avg Response Time (ms)",
+            "name": "Read_Avg_Response"
+          },
+          "graph": {
+            "type": "Line",
+            "step": false,
+            "smooth": false,
+            "showSymbol": false,
+            "showXAxis": true,
+            "showYAxis": true
+          },
+          "metrics": [
+            "cache_read_resp_time"
+          ],
+          "metricTypes": [
+            "readMetricsValues"
+          ],
+          "value": "6",
+          "label": "Cache_Read_Avg_Response",
+          "filters": {
+            "dataIndex": 17,
+            "sourceId": "4"
+          },
+          "associate": [
+            {
+              "widgetId": "6"
+            },
+            {
+              "widgetId": "7"
+            },
+            {
+              "widgetId": "8"
+            }
+          ]
+        },
+
+        {
+          "x": 6,
+          "y": 13,
+          "w": 6,
+          "h": 13,
+          "i": "6",
+          "type": "Widget",
+          "widget": {
+            "title": "Read Successful Rate (%)",
+            "name": "Read_Successful_Rate"
+          },
+          "graph": {
+            "type": "Line",
+            "step": false,
+            "smooth": false,
+            "showSymbol": false,
+            "showXAxis": true,
+            "showYAxis": true
+          },
+          "metrics": [
+            "cache_read_sla"
+          ],
+          "metricTypes": [
+            "readMetricsValues"
+          ],
+          "metricConfig": [
+            {
+              "calculation": "percentage"
+            }
+          ],
+          "value": "3",
+          "label": "Successful_Rate",
+          "associate": [
+            {
+              "widgetId": "5"
+            },
+            {
+              "widgetId": "7"
+            },
+            {
+              "widgetId": "8"
+            }
+          ],
+          "filters": {
+            "dataIndex": 17,
+            "sourceId": "4"
+          }
+        },
+        {
+          "x": 12,
+          "y": 13,
+          "w": 6,
+          "h": 13,
+          "i": "9",
+          "type": "Widget",
+          "widget": {
+            "title": "Write Avg Response Time (ms)",
+            "name": "Write_Avg_Response"
+          },
+          "graph": {
+            "type": "Line",
+            "step": false,
+            "smooth": false,
+            "showSymbol": false,
+            "showXAxis": true,
+            "showYAxis": true
+          },
+          "metrics": [
+            "cache_write_resp_time"
+          ],
+          "metricTypes": [
+            "readMetricsValues"
+          ],
+          "value": "6",
+          "label": "Read_Avg_Response",
+          "filters": {
+            "dataIndex": 17,
+            "sourceId": "4"
+          },
+          "associate": [
+            {
+              "widgetId": "10"
+            },
+            {
+              "widgetId": "11"
+            },
+            {
+              "widgetId": "12"
+            }
+          ]
+        },
+
+        {
+          "x": 18,
+          "y": 13,
+          "w": 6,
+          "h": 13,
+          "i": "10",
+          "type": "Widget",
+          "widget": {
+            "title": "Write Successful Rate (%)",
+            "name": "Write_Successful_Rate"
+          },
+          "graph": {
+            "type": "Line",
+            "step": false,
+            "smooth": false,
+            "showSymbol": false,
+            "showXAxis": true,
+            "showYAxis": true
+          },
+          "metrics": [
+            "cache_write_sla"
+          ],
+          "metricTypes": [
+            "readMetricsValues"
+          ],
+          "metricConfig": [
+            {
+              "calculation": "percentage"
+            }
+          ],
+          "value": "3",
+          "label": "Successful_Rate",
+          "filters": {
+            "dataIndex": 17,
+            "sourceId": "4"
+          },
+          "associate": [
+            {
+              "widgetId": "9"
+            },
+            {
+              "widgetId": "11"
+            },
+            {
+              "widgetId": "12"
+            }
+          ]
+        },
+        {
+          "x": 0,
+          "y": 26,
+          "w": 6,
+          "h": 13,
+          "i": "7",
+          "type": "Widget",
+          "widget": {
+            "title": "Read Traffic (calls / min)",
+            "name": "Read_Traffic"
+          },
+          "graph": {
+            "type": "Line",
+            "step": false,
+            "smooth": false,
+            "showSymbol": false,
+            "showXAxis": true,
+            "showYAxis": true
+          },
+          "metrics": [
+            "cache_read_cpm"
+          ],
+          "metricTypes": [
+            "readMetricsValues"
+          ],
+          "value": "4",
+          "label": "Cache_Traffic",
+          "associate": [
+            {
+              "widgetId": "5"
+            },
+            {
+              "widgetId": "6"
+            },
+            {
+              "widgetId": "8"
+            }
+          ]
+        },
+        {
+          "x": 6,
+          "y": 26,
+          "w": 6,
+          "h": 13,
+          "i": "8",
+          "type": "Widget",
+          "widget": {
+            "title": "Read Latency Percentile (ms)",
+            "name": "Read_Latency_Percentile"
+          },
+          "graph": {
+            "type": "Line",
+            "step": false,
+            "smooth": false,
+            "showSymbol": false,
+            "showXAxis": true,
+            "showYAxis": true
+          },
+          "metrics": [
+            "cache_read_percentile"
+          ],
+          "metricTypes": [
+            "readLabeledMetricsValues"
+          ],
+          "metricConfig": [
+            {
+              "label": "P50, P75, P90, P95, P99",
+              "labelsIndex": "0,1,2,3,4"
+            }
+          ],
+          "value": "5",
+          "label": "5",
+          "associate": [
+            {
+              "widgetId": "5"
+            },
+            {
+              "widgetId": "6"
+            },
+            {
+              "widgetId": "7"
+            }
+          ],
+          "filters": {
+            "dataIndex": 17,
+            "sourceId": "4"
+          }
+        },
+
+        {
+          "x": 12,
+          "y": 26,
+          "w": 6,
+          "h": 13,
+          "i": "11",
+          "type": "Widget",
+          "widget": {
+            "title": "Write Traffic (calls / min)",
+            "name": "Write_Traffic"
+          },
+          "graph": {
+            "type": "Line",
+            "step": false,
+            "smooth": false,
+            "showSymbol": false,
+            "showXAxis": true,
+            "showYAxis": true
+          },
+          "metrics": [
+            "cache_write_cpm"
+          ],
+          "metricTypes": [
+            "readMetricsValues"
+          ],
+          "value": "4",
+          "label": "Database_Traffic",
+          "associate": [
+            {
+              "widgetId": "9"
+            },
+            {
+              "widgetId": "10"
+            },
+            {
+              "widgetId": "12"
+            }
+          ]
+        },
+        {
+          "x": 18,
+          "y": 26,
+          "w": 6,
+          "h": 13,
+          "i": "12",
+          "type": "Widget",
+          "widget": {
+            "title": "Write Latency Percentile (ms)",
+            "name": "Write_Latency_Percentile"
+          },
+          "graph": {
+            "type": "Line",
+            "step": false,
+            "smooth": false,
+            "showSymbol": false,
+            "showXAxis": true,
+            "showYAxis": true
+          },
+          "metrics": [
+            "cache_write_percentile"
+          ],
+          "metricTypes": [
+            "readLabeledMetricsValues"
+          ],
+          "metricConfig": [
+            {
+              "label": "P50, P75, P90, P95, P99",
+              "labelsIndex": "0,1,2,3,4"
+            }
+          ],
+          "value": "5",
+          "label": "5",
+          "associate": [
+            {
+              "widgetId": "9"
+            },
+            {
+              "widgetId": "10"
+            },
+            {
+              "widgetId": "11"
+            }
+          ],
+          "filters": {
+            "dataIndex": 17,
+            "sourceId": "4"
+          }
+        },
+        {
+          "x": 0,
+          "y": 39,
+          "w": 12,
+          "h": 24,
+          "i": "13",
+          "type": "Widget",
+          "widget": {
+            "title": "Slow Read Command (ms)"
+          },
+          "graph": {
+            "type": "TopList",
+            "color": "purple"
+          },
+          "metrics": [
+            "top_n_cache_read_command"
+          ],
+          "metricTypes": [
+            "readSampledRecords"
+          ],
+          "value": "2",
+          "label": "2"
+        },
+        {
+          "x": 12,
+          "y": 39,
+          "w": 12,
+          "h": 24,
+          "i": "14",
+          "type": "Widget",
+          "widget": {
+            "title": "Slow Write Command (ms)"
+          },
+          "graph": {
+            "type": "TopList",
+            "color": "purple"
+          },
+          "metrics": [
+            "top_n_cache_write_command"
+          ],
+          "metricTypes": [
+            "readSampledRecords"
+          ],
+          "value": "2",
+          "label": "2"
+        }
+      ],
+      "layer": "VIRTUAL_CACHE",
+      "entity": "Service",
+      "name": "Virtual-Cache-Service",
+      "id": "Virtual-Cache-Service",
+      "isRoot": false
+    }
+  }
+]
diff --git a/skywalking-ui b/skywalking-ui
index 26817e9..214b34d 160000
--- a/skywalking-ui
+++ b/skywalking-ui
@@ -1 +1 @@
-Subproject commit 26817e9f927dd07fb439f415227bf371f45a9645
+Subproject commit 214b34ddfd259dffcd343a6775c7c05c62ae788b
diff --git a/test/e2e-v2/cases/meter/docker-compose.yml b/test/e2e-v2/cases/meter/docker-compose.yml
index 7bb194b..a537a9c 100644
--- a/test/e2e-v2/cases/meter/docker-compose.yml
+++ b/test/e2e-v2/cases/meter/docker-compose.yml
@@ -19,6 +19,10 @@
   oap:
     environment:
       SW_METER_ANALYZER_ACTIVE_FILES: spring-sleuth,batch-meter
+      #virtual cache test case
+      SW_SLOW_CACHE_SLOW_WRITE_THRESHOLD: default:-1
+      SW_SLOW_CACHE_SLOW_READ_THRESHOLD: default:-1
+      SW_CORE_TOPN_REPORT_PERIOD: 1 # TopN period
     extends:
       file: ../../script/docker-compose/base-compose.yml
       service: oap
@@ -33,12 +37,13 @@
       service: provider
     environment:
       SW_METER_REPORT_INTERVAL: 5
+    # Activate guava-cache plugin for virtual cache test case
+    command: ["bash" , "-c" , "cp /skywalking/agent/optional-plugins/apm-guava-cache-plugin*.jar /skywalking/agent/plugins/ && java -jar /services_provider.jar"]
     depends_on:
       oap:
         condition: service_healthy
     ports:
       - 9090
-
   sender:
     image: "eclipse-temurin:8-jre"
     volumes:
diff --git a/test/e2e-v2/cases/meter/e2e.yaml b/test/e2e-v2/cases/meter/e2e.yaml
index 46780e7..0dd74fc 100644
--- a/test/e2e-v2/cases/meter/e2e.yaml
+++ b/test/e2e-v2/cases/meter/e2e.yaml
@@ -40,7 +40,8 @@
 
 verify:
   retry:
-    count: 20
+    # The  total time must max than 1min
+    count: 30
     interval: 3s
   cases:
     - includes:
diff --git a/test/e2e-v2/cases/meter/expected/metrics-has-value-percentile.yml b/test/e2e-v2/cases/meter/expected/metrics-has-value-percentile.yml
new file mode 100644
index 0000000..b126c5d
--- /dev/null
+++ b/test/e2e-v2/cases/meter/expected/metrics-has-value-percentile.yml
@@ -0,0 +1,47 @@
+# 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.
+
+{{- contains . }}
+- key: 0
+  value:
+  {{- contains .value }}
+  - key: {{ notEmpty .key }}
+    value: {{ ge .value 0 }}
+  {{- end }}
+- key: 1
+  value:
+  {{- contains .value }}
+  - key: {{ notEmpty .key }}
+    value: {{ ge .value 0 }}
+  {{- end }}
+- key: 2
+  value:
+  {{- contains .value }}
+  - key: {{ notEmpty .key }}
+    value: {{ ge .value 0 }}
+  {{- end }}
+- key: 3
+  value:
+  {{- contains .value }}
+  - key: {{ notEmpty .key }}
+    value: {{ ge .value 0 }}
+  {{- end }}
+- key: 4
+  value:
+  {{- contains .value }}
+  - key: {{ notEmpty .key }}
+    value: {{ ge .value 0 }}
+  {{- end }}
+{{- end }}
diff --git a/test/e2e-v2/cases/meter/expected/metrics-has-value0.yml b/test/e2e-v2/cases/meter/expected/metrics-has-value0.yml
new file mode 100644
index 0000000..ce1f3d8
--- /dev/null
+++ b/test/e2e-v2/cases/meter/expected/metrics-has-value0.yml
@@ -0,0 +1,19 @@
+# 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.
+
+{{- contains . }}
+- key: {{ notEmpty .key }}
+  value: {{ ge .value 0 }}
+{{- end }}
diff --git a/test/e2e-v2/cases/meter/expected/record-has-value.yml b/test/e2e-v2/cases/meter/expected/record-has-value.yml
new file mode 100644
index 0000000..76e239c
--- /dev/null
+++ b/test/e2e-v2/cases/meter/expected/record-has-value.yml
@@ -0,0 +1,29 @@
+# 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.
+
+{{- contains . }}
+- key: 0
+  value:
+  {{- contains .value }}
+  - key: name
+    value: {{ notEmpty .value}}
+  - key: id
+    value: ""
+  - key: value
+    value: {{ notEmpty .value}}
+  - key: refid
+    value:
+  {{- end }}
+{{- end }}
\ No newline at end of file
diff --git a/test/e2e-v2/cases/meter/expected/service.yml b/test/e2e-v2/cases/meter/expected/service.yml
index 75d0916..c8338ac 100644
--- a/test/e2e-v2/cases/meter/expected/service.yml
+++ b/test/e2e-v2/cases/meter/expected/service.yml
@@ -21,4 +21,20 @@
   normal: true
   layers:
     - GENERAL
+
+- id: {{ b64enc "localhost:-1" }}.0
+  name: localhost:-1
+  group: ""
+  shortname: localhost:-1
+  normal: false
+  layers:
+    - VIRTUAL_DATABASE
+
+- id: {{ b64enc "GuavaCache-local" }}.0
+  name: GuavaCache-local
+  group: ""
+  shortname: GuavaCache-local
+  normal: false
+  layers:
+    - VIRTUAL_CACHE
 {{- end }}
diff --git a/test/e2e-v2/cases/meter/meter-cases.yaml b/test/e2e-v2/cases/meter/meter-cases.yaml
index e3bae75..0fa6fe3 100644
--- a/test/e2e-v2/cases/meter/meter-cases.yaml
+++ b/test/e2e-v2/cases/meter/meter-cases.yaml
@@ -31,4 +31,21 @@
         curl -s -XPOST http://${sender_host}:${sender_9093}/sendBatchMetrics > /dev/null;
         sleep 10;
         swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear --name=batch_test --instance-name=test-instance --service-name=test-service |yq e 'to_entries' -
-      expected: expected/metrics-has-value.yml
\ No newline at end of file
+      expected: expected/metrics-has-value.yml
+    # virtual cache
+    - query: |
+        swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear --name=cache_read_resp_time  --service-id=R3VhdmFDYWNoZS1sb2NhbA==.0  | yq e 'to_entries' -
+      expected: expected/metrics-has-value0.yml
+    - query: |
+        swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear --name=cache_write_sla  --service-id=R3VhdmFDYWNoZS1sb2NhbA==.0  | yq e 'to_entries' -
+      expected: expected/metrics-has-value.yml
+    - query: |
+        swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear --name=cache_access_cpm  --service-id=R3VhdmFDYWNoZS1sb2NhbA==.0  | yq e 'to_entries' -
+      expected: expected/metrics-has-value.yml
+    - query: |
+        swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics sampled-record  --name=top_n_cache_write_command    --service-id=R3VhdmFDYWNoZS1sb2NhbA==.0 |  yq e 'to_entries | with(.[] ; .value=(.value | to_entries))' -
+      expected: expected/record-has-value.yml
+      # virtual database
+    - query: |
+        swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql metrics linear --name=database_access_resp_time  --service-id=bG9jYWxob3N0Oi0x.0  | yq e 'to_entries' -
+      expected: expected/metrics-has-value0.yml
\ No newline at end of file
diff --git a/test/e2e-v2/cases/storage/storage-cases.yaml b/test/e2e-v2/cases/storage/storage-cases.yaml
index b1ac0b2..0dcd9f3 100644
--- a/test/e2e-v2/cases/storage/storage-cases.yaml
+++ b/test/e2e-v2/cases/storage/storage-cases.yaml
@@ -47,7 +47,7 @@
   - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql trace ls
     expected: expected/traces-list.yml
   # negative tags search: relationship should be logical AND instead of logical OR
-  - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql trace ls --tags http.method=POST,http.status_code=200
+  - query: swctl --display yaml --base-url=http://${oap_host}:${oap_12800}/graphql trace ls --tags http.method=POST,http.status_code=201
     expected: expected/empty-traces-list.yml
   # trace detail
   - query: |
diff --git a/test/e2e-v2/java-test-service/e2e-service-provider/pom.xml b/test/e2e-v2/java-test-service/e2e-service-provider/pom.xml
index cc43d40..5f535cc 100644
--- a/test/e2e-v2/java-test-service/e2e-service-provider/pom.xml
+++ b/test/e2e-v2/java-test-service/e2e-service-provider/pom.xml
@@ -105,6 +105,12 @@
             <artifactId>apm-toolkit-trace</artifactId>
             <version>${sw.version}</version>
         </dependency>
+
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>23.0</version>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/test/e2e-v2/java-test-service/e2e-service-provider/src/main/java/org/apache/skywalking/e2e/controller/UserController.java b/test/e2e-v2/java-test-service/e2e-service-provider/src/main/java/org/apache/skywalking/e2e/controller/UserController.java
index c7a3105..55d4cf4 100644
--- a/test/e2e-v2/java-test-service/e2e-service-provider/src/main/java/org/apache/skywalking/e2e/controller/UserController.java
+++ b/test/e2e-v2/java-test-service/e2e-service-provider/src/main/java/org/apache/skywalking/e2e/controller/UserController.java
@@ -18,10 +18,13 @@
 
 package org.apache.skywalking.e2e.controller;
 
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
 import lombok.RequiredArgsConstructor;
 import org.apache.skywalking.apm.toolkit.trace.TraceContext;
 import org.apache.skywalking.e2e.User;
 import org.apache.skywalking.e2e.UserRepo;
+import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -29,6 +32,9 @@
 import org.springframework.web.bind.annotation.RestController;
 import org.slf4j.LoggerFactory;
 
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Random;
 
 @RestController
@@ -37,6 +43,10 @@
 public class UserController {
     private static final org.slf4j.Logger LOGBACK_LOGGER = LoggerFactory.getLogger(UserController.class);
 
+    private final Cache<String, String> guavaCache = CacheBuilder.newBuilder()
+            .concurrencyLevel(Runtime.getRuntime().availableProcessors())
+            .build();
+
     private final UserRepo userRepo;
     private final int sleepMin = 500;
     private final int sleepMax = 1000;
@@ -51,6 +61,8 @@
     @PostMapping("/users")
     public User createAuthor(@RequestBody final User user) throws InterruptedException {
         Thread.sleep(randomSleepLong(sleepMin, sleepMax));
+        //virtual cache test case
+        testCacheService();
         return userRepo.save(user);
     }
 
@@ -68,4 +80,19 @@
         int randomNumber = rand.nextInt((max - min) + 1) + min;
         return randomNumber;
     }
+
+    private void testCacheService() {
+        String userInfo = guavaCache.getIfPresent("user_1");
+        if (!StringUtils.hasLength(userInfo)) {
+            guavaCache.put("user_1", "name:John,address:earth");
+        }
+        Map<String, String> users = new HashMap<>();
+        users.put("user_2", "name:Tom,address:earth");
+        users.put("user_3", "name:Jack,address:earth");
+        guavaCache.putAll(users);
+        guavaCache.getAllPresent(Arrays.asList("user_2", "user_3"));
+        guavaCache.invalidate("user_1");
+        guavaCache.invalidate("user_2");
+        guavaCache.invalidateAll();
+    }
 }
diff --git a/test/e2e-v2/script/env b/test/e2e-v2/script/env
index 79303a1..f916e78 100644
--- a/test/e2e-v2/script/env
+++ b/test/e2e-v2/script/env
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-SW_AGENT_JAVA_COMMIT=b83865f6cd9477f12c8ae8bc3379c31d7d105072
+SW_AGENT_JAVA_COMMIT=3f88d735ba2bfd1196aff946502447d4b14450c8
 SW_AGENT_SATELLITE_COMMIT=ea27a3f4e126a24775fe12e2aa2695bcb23d99c3
 SW_AGENT_NGINX_LUA_COMMIT=c3cee4841798a147d83b96a10914d4ac0e11d0aa
 SW_AGENT_NODEJS_COMMIT=2e7560518aff846befd4d6bc815fe5e38c704a11