You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by kl...@apache.org on 2020/08/18 16:16:32 UTC

[geode] branch develop updated: GEODE-8425: Add new stats for handling netsearch

This is an automated email from the ASF dual-hosted git repository.

klund pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/develop by this push:
     new 28790ca  GEODE-8425: Add new stats for handling netsearch
28790ca is described below

commit 28790ca86d977d97ab224ec4118ea2a0c1e2a763
Author: Kirk Lund <kl...@apache.org>
AuthorDate: Thu Aug 6 10:51:54 2020 -0700

    GEODE-8425: Add new stats for handling netsearch
---
 .../ReplicateRegionNetsearchDistributedTest.java   | 545 +++++++++++++++++++++
 .../geode/internal/cache/CachePerfStats.java       |  75 ++-
 .../cache/SearchLoadAndWriteProcessor.java         |  12 +-
 .../geode/internal/cache/CachePerfStatsTest.java   | 109 ++++-
 .../geode/test/dunit/rules/DistributedRule.java    |   5 +
 5 files changed, 742 insertions(+), 4 deletions(-)

diff --git a/geode-core/src/distributedTest/java/org/apache/geode/cache/ReplicateRegionNetsearchDistributedTest.java b/geode-core/src/distributedTest/java/org/apache/geode/cache/ReplicateRegionNetsearchDistributedTest.java
new file mode 100644
index 0000000..e9a6ed3
--- /dev/null
+++ b/geode-core/src/distributedTest/java/org/apache/geode/cache/ReplicateRegionNetsearchDistributedTest.java
@@ -0,0 +1,545 @@
+/*
+ * 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.geode.cache;
+
+import static org.apache.geode.cache.EvictionAttributes.createLRUEntryAttributes;
+import static org.apache.geode.distributed.ConfigurationProperties.ENABLE_CLUSTER_CONFIGURATION;
+import static org.apache.geode.distributed.ConfigurationProperties.LOCATORS;
+import static org.apache.geode.internal.util.ArrayUtils.asList;
+import static org.apache.geode.test.dunit.VM.getVM;
+import static org.apache.geode.test.dunit.rules.DistributedRule.getLocatorPort;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.File;
+import java.io.Serializable;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import org.apache.geode.cache.client.ClientCache;
+import org.apache.geode.cache.client.ClientCacheFactory;
+import org.apache.geode.cache.client.ClientRegionShortcut;
+import org.apache.geode.distributed.ServerLauncher;
+import org.apache.geode.internal.cache.CachePerfStats;
+import org.apache.geode.internal.cache.InternalRegion;
+import org.apache.geode.test.dunit.VM;
+import org.apache.geode.test.dunit.rules.DistributedReference;
+import org.apache.geode.test.dunit.rules.DistributedRule;
+import org.apache.geode.test.junit.rules.serializable.SerializableTemporaryFolder;
+
+@SuppressWarnings("serial")
+public class ReplicateRegionNetsearchDistributedTest implements Serializable {
+
+  private static final String REPLICATE_1_NAME = "replicate1";
+  private static final String REPLICATE_2_NAME = "replicate2";
+  private static final String PROXY_NAME = "proxy";
+  private static final String REGION_NAME = "region";
+
+  private VM replicate1;
+  private VM replicate2;
+  private VM proxy;
+  private VM client;
+
+  private File replicate1Dir;
+  private File replicate2Dir;
+  private File proxyDir;
+
+  @Rule
+  public DistributedRule distributedRule = new DistributedRule();
+  @Rule
+  public DistributedReference<ServerLauncher> serverLauncher = new DistributedReference<>();
+  @Rule
+  public DistributedReference<ClientCache> clientCache = new DistributedReference<>();
+  @Rule
+  public SerializableTemporaryFolder temporaryFolder = new SerializableTemporaryFolder();
+
+  @Before
+  public void setUp() throws Exception {
+    replicate1 = getVM(0);
+    replicate2 = getVM(1);
+    proxy = getVM(2);
+    client = getVM(3);
+
+    replicate1Dir = temporaryFolder.newFolder(REPLICATE_1_NAME);
+    replicate2Dir = temporaryFolder.newFolder(REPLICATE_2_NAME);
+    proxyDir = temporaryFolder.newFolder(PROXY_NAME);
+
+    int locatorPort = getLocatorPort();
+
+    replicate1.invoke(() -> {
+      serverLauncher.set(startServer(REPLICATE_1_NAME, replicate1Dir, locatorPort));
+    });
+    replicate2.invoke(() -> {
+      serverLauncher.set(startServer(REPLICATE_2_NAME, replicate2Dir, locatorPort));
+    });
+    proxy.invoke(() -> {
+      serverLauncher.set(startServer(PROXY_NAME, proxyDir, locatorPort));
+    });
+  }
+
+  @Test
+  public void proxyReplicateDoesNetsearchFromFullReplicate() {
+    replicate1.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache()
+          .<String, String>createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+
+      region.put("key-1", "value-1");
+    });
+
+    proxy.invoke(() -> {
+      serverLauncher.get().getCache()
+          .<String, String>createRegionFactory(RegionShortcut.REPLICATE_PROXY)
+          .create(REGION_NAME);
+    });
+
+    proxy.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache().getRegion(REGION_NAME);
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isZero();
+
+      String value = region.get("key-1");
+
+      assertThat(value).isEqualTo("value-1");
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isOne();
+      assertThat(region.getAttributes().getPartitionAttributes()).isNull();
+    });
+  }
+
+  @Test
+  public void fullReplicateDoesNotPerformNetsearch() {
+    replicate1.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache()
+          .<String, String>createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+
+      region.put("key-1", "value-1");
+    });
+
+    proxy.invoke(() -> {
+      serverLauncher.get().getCache()
+          .<String, String>createRegionFactory(RegionShortcut.REPLICATE_PROXY)
+          .create(REGION_NAME);
+    });
+
+    replicate1.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache().getRegion(REGION_NAME);
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isZero();
+
+      String value = region.get("key-1");
+
+      assertThat(value).isEqualTo("value-1");
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isZero();
+    });
+  }
+
+  @Test
+  public void replicateWithExpirationDoesNetsearchOnMiss() {
+    replicate1.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache()
+          .<String, String>createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+
+      region.put("key-1", "value-1");
+      region.put("key-2", "value-2");
+    });
+
+    replicate2.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache()
+          .<String, String>createRegionFactory(RegionShortcut.REPLICATE)
+          .setEvictionAttributes(createLRUEntryAttributes(1))
+          .create(REGION_NAME);
+
+      assertThat(region).hasSize(1);
+
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(regionPerfStats.getGets()).isZero();
+      assertThat(regionPerfStats.getGetInitialImagesCompleted()).isOne();
+      assertThat(regionPerfStats.getMisses()).isZero();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+    });
+
+    replicate1.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache().getRegion(REGION_NAME);
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(regionPerfStats.getGets()).isZero();
+      assertThat(regionPerfStats.getGetInitialImagesCompleted()).isZero();
+      assertThat(regionPerfStats.getMisses()).isZero();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+    });
+
+    replicate2.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache().getRegion(REGION_NAME);
+
+      assertThat(region).hasSize(1);
+
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(regionPerfStats.getGets()).isZero();
+      assertThat(regionPerfStats.getGetInitialImagesCompleted()).isOne();
+      assertThat(regionPerfStats.getMisses()).isZero();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+
+      String value = region.get("key-1");
+
+      assertThat(value).isEqualTo("value-1");
+
+      assertThat(region).hasSize(1);
+
+      assertThat(regionPerfStats.getGets()).isOne();
+      assertThat(regionPerfStats.getGetInitialImagesCompleted()).isOne();
+      assertThat(regionPerfStats.getMisses()).isOne();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isOne();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+    });
+
+    replicate1.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache().getRegion(REGION_NAME);
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(regionPerfStats.getGets()).isZero();
+      assertThat(regionPerfStats.getGetInitialImagesCompleted()).isZero();
+      assertThat(regionPerfStats.getMisses()).isZero();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isOne();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+    });
+
+    replicate2.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache().getRegion(REGION_NAME);
+
+      assertThat(region).hasSize(1);
+
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(regionPerfStats.getGets()).isOne();
+      assertThat(regionPerfStats.getGetInitialImagesCompleted()).isOne();
+      assertThat(regionPerfStats.getMisses()).isOne();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isOne();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+
+      String value = region.get("key-1");
+
+      assertThat(value).isEqualTo("value-1");
+
+      assertThat(region).hasSize(1);
+
+      assertThat(regionPerfStats.getGets()).isEqualTo(2);
+      assertThat(regionPerfStats.getGetInitialImagesCompleted()).isOne();
+      assertThat(regionPerfStats.getMisses()).isOne();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isOne();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+    });
+
+    replicate2.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache().getRegion(REGION_NAME);
+
+      assertThat(region).hasSize(1);
+
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(regionPerfStats.getGets()).isEqualTo(2);
+      assertThat(regionPerfStats.getGetInitialImagesCompleted()).isOne();
+      assertThat(regionPerfStats.getMisses()).isOne();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isOne();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+
+      String value = region.get("key-2");
+
+      assertThat(value).isEqualTo("value-2");
+
+      assertThat(region).hasSize(1);
+
+      assertThat(regionPerfStats.getGets()).isEqualTo(3);
+      assertThat(regionPerfStats.getGetInitialImagesCompleted()).isOne();
+      assertThat(regionPerfStats.getMisses()).isEqualTo(2);
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isEqualTo(2);
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+    });
+
+    replicate1.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache().getRegion(REGION_NAME);
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(regionPerfStats.getGets()).isZero();
+      assertThat(regionPerfStats.getGetInitialImagesCompleted()).isZero();
+      assertThat(regionPerfStats.getMisses()).isZero();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isEqualTo(2);
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+    });
+  }
+
+  @Test
+  public void proxyReplicateDoesNetsearchFromOnlyOneFullReplicate() {
+    replicate1.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache()
+          .<String, String>createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+
+      region.put("key-1", "value-1");
+      region.put("key-2", "value-2");
+
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(regionPerfStats.getGets()).isZero();
+      assertThat(regionPerfStats.getGetInitialImagesCompleted()).isZero();
+      assertThat(regionPerfStats.getMisses()).isZero();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+    });
+
+    replicate2.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache()
+          .<String, String>createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(regionPerfStats.getGets()).isZero();
+      assertThat(regionPerfStats.getGetInitialImagesCompleted()).isOne();
+      assertThat(regionPerfStats.getMisses()).isZero();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+    });
+
+    proxy.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache()
+          .<String, String>createRegionFactory(RegionShortcut.REPLICATE_PROXY)
+          .create(REGION_NAME);
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(regionPerfStats.getGets()).isZero();
+      assertThat(regionPerfStats.getGetInitialImagesCompleted()).isZero();
+      assertThat(regionPerfStats.getMisses()).isZero();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+    });
+
+    for (VM vm : asList(replicate1, replicate2)) {
+      vm.invoke(() -> {
+        Region<String, String> region = serverLauncher.get().getCache().getRegion(REGION_NAME);
+        CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+        assertThat(regionPerfStats.getGets()).isZero();
+        assertThat(regionPerfStats.getMisses()).isZero();
+        assertThat(regionPerfStats.getNetsearchesCompleted()).isZero();
+        assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+      });
+    }
+
+    proxy.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache().getRegion(REGION_NAME);
+
+      assertThat(region.get("key-1")).isEqualTo("value-1");
+
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(regionPerfStats.getGets()).isOne();
+      assertThat(regionPerfStats.getMisses()).isOne();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isOne();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+    });
+
+    long handlingNetsearchesCompletedInReplicate1 = replicate1.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache().getRegion(REGION_NAME);
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+      return regionPerfStats.getHandlingNetsearchesCompleted();
+    });
+
+    long handlingNetsearchesCompletedInReplicate2 = replicate2.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache().getRegion(REGION_NAME);
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+      return regionPerfStats.getHandlingNetsearchesCompleted();
+    });
+
+    // only one replicate should have been used to handle the netsearch
+    assertThat(handlingNetsearchesCompletedInReplicate1 + handlingNetsearchesCompletedInReplicate2)
+        .isOne();
+
+    proxy.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache().getRegion(REGION_NAME);
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(regionPerfStats.getGets()).isOne();
+      assertThat(regionPerfStats.getMisses()).isOne();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isOne();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+    });
+  }
+
+  @Test
+  public void clientGetFromProxyReplicateDoesNetsearchFromFullReplicate() {
+    replicate1.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache()
+          .<String, String>createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+
+      region.put("key-1", "value-1");
+    });
+
+    replicate2.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache()
+          .<String, String>createRegionFactory(RegionShortcut.REPLICATE)
+          .create(REGION_NAME);
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(regionPerfStats.getGets()).isZero();
+      assertThat(regionPerfStats.getGetInitialImagesCompleted()).isOne();
+      assertThat(regionPerfStats.getMisses()).isZero();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+    });
+
+    int proxyServerPort = proxy.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache()
+          .<String, String>createRegionFactory(RegionShortcut.REPLICATE_PROXY)
+          .create(REGION_NAME);
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(regionPerfStats.getGets()).isZero();
+      assertThat(regionPerfStats.getGetInitialImagesCompleted()).isZero();
+      assertThat(regionPerfStats.getMisses()).isZero();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+
+      return serverLauncher.get().getCache().getCacheServers().get(0).getPort();
+    });
+
+    client.invoke(() -> {
+      clientCache.set(new ClientCacheFactory()
+          .addPoolServer("localhost", proxyServerPort)
+          .create());
+      Region<String, String> region = clientCache.get()
+          .<String, String>createClientRegionFactory(ClientRegionShortcut.PROXY)
+          .create(REGION_NAME);
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(regionPerfStats.getGets()).isZero();
+      assertThat(regionPerfStats.getGetInitialImagesCompleted()).isZero();
+      assertThat(regionPerfStats.getMisses()).isZero();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+    });
+
+    for (VM vm : asList(replicate1, replicate2, proxy)) {
+      vm.invoke(() -> {
+        Region<String, String> region = serverLauncher.get().getCache().getRegion(REGION_NAME);
+        CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+        assertThat(regionPerfStats.getGets()).isZero();
+        assertThat(regionPerfStats.getMisses()).isZero();
+        assertThat(regionPerfStats.getNetsearchesCompleted()).isZero();
+        assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+        assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+      });
+    }
+
+    client.invoke(() -> {
+      Region<String, String> region = clientCache.get().getRegion(REGION_NAME);
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(region.get("key-1")).isEqualTo("value-1");
+
+      assertThat(regionPerfStats.getGets()).isOne();
+      assertThat(regionPerfStats.getMisses()).isOne();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+    });
+
+    for (VM vm : asList(replicate1, replicate2)) {
+      vm.invoke(() -> {
+        Region<String, String> region = serverLauncher.get().getCache().getRegion(REGION_NAME);
+        CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+        assertThat(regionPerfStats.getGets()).isZero();
+        assertThat(regionPerfStats.getMisses()).isZero();
+        assertThat(regionPerfStats.getNetsearchesCompleted()).isZero();
+        assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+      });
+    }
+
+    long handlingNetsearchesCompletedInReplicate1 = replicate1.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache().getRegion(REGION_NAME);
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+      return regionPerfStats.getHandlingNetsearchesCompleted();
+    });
+
+    long handlingNetsearchesCompletedInReplicate2 = replicate2.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache().getRegion(REGION_NAME);
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+      return regionPerfStats.getHandlingNetsearchesCompleted();
+    });
+
+    // only one replicate should have been used to handle the netsearch
+    assertThat(handlingNetsearchesCompletedInReplicate1 + handlingNetsearchesCompletedInReplicate2)
+        .isOne();
+
+    proxy.invoke(() -> {
+      Region<String, String> region = serverLauncher.get().getCache().getRegion(REGION_NAME);
+      CachePerfStats regionPerfStats = getRegionPerfStats(region);
+
+      assertThat(regionPerfStats.getGets()).isOne();
+      assertThat(regionPerfStats.getMisses()).isOne();
+      assertThat(regionPerfStats.getNetsearchesCompleted()).isOne();
+      assertThat(regionPerfStats.getHandlingNetsearchesCompleted()).isZero();
+      assertThat(regionPerfStats.getHandlingNetsearchesFailed()).isZero();
+    });
+  }
+
+  private ServerLauncher startServer(String serverName, File serverDir, int locatorPort) {
+    ServerLauncher serverLauncher = new ServerLauncher.Builder()
+        .setMemberName(serverName)
+        .setWorkingDirectory(serverDir.getAbsolutePath())
+        .setServerPort(0)
+        .set(LOCATORS, "localHost[" + locatorPort + "]")
+        .set(ENABLE_CLUSTER_CONFIGURATION, "false")
+        .build();
+
+    serverLauncher.start();
+
+    return serverLauncher;
+  }
+
+  private CachePerfStats getRegionPerfStats(Region<?, ?> region) {
+    return ((InternalRegion) region).getRegionPerfStats();
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/CachePerfStats.java b/geode-core/src/main/java/org/apache/geode/internal/cache/CachePerfStats.java
index 7371ec0..5bbca52 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/CachePerfStats.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/CachePerfStats.java
@@ -21,6 +21,7 @@ import org.apache.geode.StatisticsFactory;
 import org.apache.geode.StatisticsType;
 import org.apache.geode.StatisticsTypeFactory;
 import org.apache.geode.annotations.Immutable;
+import org.apache.geode.annotations.VisibleForTesting;
 import org.apache.geode.distributed.internal.PoolStatHelper;
 import org.apache.geode.distributed.internal.QueueStatHelper;
 import org.apache.geode.internal.NanoTimer;
@@ -155,6 +156,17 @@ public class CachePerfStats {
   static final int compressionPreCompressedBytesId;
   static final int compressionPostCompressedBytesId;
 
+  @VisibleForTesting
+  static final int handlingNetsearchesInProgressId;
+  @VisibleForTesting
+  static final int handlingNetsearchesCompletedId;
+  @VisibleForTesting
+  static final int handlingNetsearchesTimeId;
+  @VisibleForTesting
+  static final int handlingNetsearchesFailedId;
+  @VisibleForTesting
+  static final int handlingNetsearchesFailedTimeId;
+
   static {
     StatisticsTypeFactory f = StatisticsTypeFactoryImpl.singleton();
 
@@ -332,6 +344,17 @@ public class CachePerfStats {
     final String evictByCriteria_evaluationTimeDesc =
         "Total time taken for evaluation of user expression during eviction";
 
+    final String handlingNetsearchesInProgressDesc =
+        "Current number of threads handling a network search initiated by a remote cache.";
+    final String handlingNetsearchesCompletedDesc =
+        "Total number of times handling a network search initiated by a remote cache completed with success.";
+    final String handlingNetsearchesTimeDesc =
+        "Total time spent handling successful network searches for remote caches.";
+    final String handlingNetsearchesFailedDesc =
+        "Total number of times handling a network search initiated by a remote cache failed without success.";
+    final String handlingNetsearchesFailedTimeDesc =
+        "Total time spent handling failed network searches for remote caches.";
+
     type = f.createType("CachePerfStats", "Statistics about GemFire cache performance",
         new StatisticDescriptor[] {
             f.createIntGauge("loadsInProgress", loadsInProgressDesc, "operations"),
@@ -491,7 +514,19 @@ public class CachePerfStats {
             f.createLongCounter("evictByCriteria_evaluations", evictByCriteria_evaluationsDesc,
                 "operations"),
             f.createLongCounter("evictByCriteria_evaluationTime",
-                evictByCriteria_evaluationTimeDesc, "nanoseconds")});
+                evictByCriteria_evaluationTimeDesc, "nanoseconds"),
+
+            f.createLongGauge("handlingNetsearchesInProgress", handlingNetsearchesInProgressDesc,
+                "operations"),
+            f.createLongCounter("handlingNetsearchesCompleted", handlingNetsearchesCompletedDesc,
+                "operations"),
+            f.createLongCounter("handlingNetsearchesTime", handlingNetsearchesTimeDesc,
+                "nanoseconds"),
+            f.createLongCounter("handlingNetsearchesFailed", handlingNetsearchesFailedDesc,
+                "operations"),
+            f.createLongCounter("handlingNetsearchesFailedTime", handlingNetsearchesFailedTimeDesc,
+                "nanoseconds")
+        });
 
     loadsInProgressId = type.nameToId("loadsInProgress");
     loadsCompletedId = type.nameToId("loadsCompleted");
@@ -612,6 +647,12 @@ public class CachePerfStats {
     compressionDecompressionsId = type.nameToId("decompressions");
     compressionPreCompressedBytesId = type.nameToId("preCompressedBytes");
     compressionPostCompressedBytesId = type.nameToId("postCompressedBytes");
+
+    handlingNetsearchesInProgressId = type.nameToId("handlingNetsearchesInProgress");
+    handlingNetsearchesCompletedId = type.nameToId("handlingNetsearchesCompleted");
+    handlingNetsearchesTimeId = type.nameToId("handlingNetsearchesTime");
+    handlingNetsearchesFailedId = type.nameToId("handlingNetsearchesFailed");
+    handlingNetsearchesFailedTimeId = type.nameToId("handlingNetsearchesFailedTime");
   }
 
   /** The Statistics object that we delegate most behavior to */
@@ -1449,4 +1490,36 @@ public class CachePerfStats {
       stats.incLong(exportTimeId, getTime() - start);
     }
   }
+
+  /**
+   * @return the timestamp that marks the start of the operation
+   */
+  public long startHandlingNetsearch() {
+    stats.incLong(handlingNetsearchesInProgressId, 1);
+    return getTime();
+  }
+
+  /**
+   * @param start the timestamp taken when the operation started
+   * @param success true if handling the netsearch was successful
+   */
+  public void endHandlingNetsearch(long start, boolean success) {
+    long ts = getTime();
+    if (success) {
+      stats.incLong(handlingNetsearchesCompletedId, 1);
+      stats.incLong(handlingNetsearchesTimeId, ts - start);
+    } else {
+      stats.incLong(handlingNetsearchesFailedId, 1);
+      stats.incLong(handlingNetsearchesFailedTimeId, ts - start);
+    }
+    stats.incLong(handlingNetsearchesInProgressId, -1);
+  }
+
+  public long getHandlingNetsearchesCompleted() {
+    return stats.getLong(handlingNetsearchesCompletedId);
+  }
+
+  public long getHandlingNetsearchesFailed() {
+    return stats.getLong(handlingNetsearchesFailedId);
+  }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/SearchLoadAndWriteProcessor.java b/geode-core/src/main/java/org/apache/geode/internal/cache/SearchLoadAndWriteProcessor.java
index 1a5e3de..f9e8b73 100755
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/SearchLoadAndWriteProcessor.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/SearchLoadAndWriteProcessor.java
@@ -1866,10 +1866,16 @@ public class SearchLoadAndWriteProcessor implements MembershipListener {
       boolean authoritative = false;
       VersionTag versionTag = null;
 
+      InternalCache cache = dm.getExistingCache();
+
       final InitializationLevel oldLevel =
           LocalRegion.setThreadInitLevelRequirement(BEFORE_INITIAL_IMAGE);
+      LocalRegion region = (LocalRegion) cache.getRegion(this.regionName);
+      CachePerfStats stats =
+          region == null ? cache.getCachePerfStats() : region.getRegionPerfStats();
+      long startHandlingTime = stats.startHandlingNetsearch();
+      boolean handlingSuccess = false;
       try {
-        LocalRegion region = (LocalRegion) dm.getExistingCache().getRegion(this.regionName);
         if (region != null) {
           setClearCountReference(region);
           try {
@@ -1883,7 +1889,7 @@ public class SearchLoadAndWriteProcessor implements MembershipListener {
                   versionTag = versionStamp.asVersionTag();
                 }
                 Object eov = region.getNoLRU(this.key, false, true, true); // OFFHEAP: incrc, copy
-                                                                           // bytes, decrc
+                // bytes, decrc
                 if (eov != null) {
                   if (eov == Token.INVALID || eov == Token.LOCAL_INVALID) {
                     // nothing?
@@ -1916,6 +1922,7 @@ public class SearchLoadAndWriteProcessor implements MembershipListener {
                         ebvLen = ebv.length;
                       }
                     }
+                    handlingSuccess = true;
                   } else {
                     requestorTimedOut = true;
                   }
@@ -1953,6 +1960,7 @@ public class SearchLoadAndWriteProcessor implements MembershipListener {
         replyWithNull(dm);
       } finally {
         LocalRegion.setThreadInitLevelRequirement(oldLevel);
+        stats.endHandlingNetsearch(startHandlingTime, handlingSuccess);
       }
     }
 
diff --git a/geode-core/src/test/java/org/apache/geode/internal/cache/CachePerfStatsTest.java b/geode-core/src/test/java/org/apache/geode/internal/cache/CachePerfStatsTest.java
index ef96053..7a81fdd 100644
--- a/geode-core/src/test/java/org/apache/geode/internal/cache/CachePerfStatsTest.java
+++ b/geode-core/src/test/java/org/apache/geode/internal/cache/CachePerfStatsTest.java
@@ -32,6 +32,11 @@ import static org.apache.geode.internal.cache.CachePerfStats.evictorJobsStartedI
 import static org.apache.geode.internal.cache.CachePerfStats.getInitialImagesCompletedId;
 import static org.apache.geode.internal.cache.CachePerfStats.getTimeId;
 import static org.apache.geode.internal.cache.CachePerfStats.getsId;
+import static org.apache.geode.internal.cache.CachePerfStats.handlingNetsearchesCompletedId;
+import static org.apache.geode.internal.cache.CachePerfStats.handlingNetsearchesFailedId;
+import static org.apache.geode.internal.cache.CachePerfStats.handlingNetsearchesFailedTimeId;
+import static org.apache.geode.internal.cache.CachePerfStats.handlingNetsearchesInProgressId;
+import static org.apache.geode.internal.cache.CachePerfStats.handlingNetsearchesTimeId;
 import static org.apache.geode.internal.cache.CachePerfStats.indexUpdateCompletedId;
 import static org.apache.geode.internal.cache.CachePerfStats.invalidatesId;
 import static org.apache.geode.internal.cache.CachePerfStats.loadsCompletedId;
@@ -52,6 +57,7 @@ import static org.apache.geode.internal.cache.CachePerfStats.txRollbackChangesId
 import static org.apache.geode.internal.cache.CachePerfStats.txRollbacksId;
 import static org.apache.geode.internal.cache.CachePerfStats.updatesId;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -62,6 +68,7 @@ import org.junit.Test;
 import org.apache.geode.Statistics;
 import org.apache.geode.StatisticsFactory;
 import org.apache.geode.StatisticsType;
+import org.apache.geode.internal.statistics.StatisticsClock;
 import org.apache.geode.internal.statistics.StatisticsManager;
 import org.apache.geode.internal.statistics.StripedStatisticsImpl;
 
@@ -74,6 +81,7 @@ public class CachePerfStatsTest {
   private static final long CLOCK_TIME = 10;
 
   private Statistics statistics;
+  private StatisticsClock statisticsClock;
   private CachePerfStats cachePerfStats;
 
   @Before
@@ -84,11 +92,16 @@ public class CachePerfStatsTest {
     StatisticsFactory statisticsFactory = mock(StatisticsFactory.class);
 
     statistics = new StripedStatisticsImpl(statisticsType, TEXT_ID, 1, 1, statisticsManager);
+    statisticsClock = mock(StatisticsClock.class);
 
+    when(statisticsClock.isEnabled())
+        .thenReturn(true);
+    when(statisticsClock.getTime())
+        .thenReturn(CLOCK_TIME);
     when(statisticsFactory.createAtomicStatistics(eq(statisticsType), eq(TEXT_ID)))
         .thenReturn(statistics);
 
-    cachePerfStats = new CachePerfStats(statisticsFactory, TEXT_ID, () -> CLOCK_TIME);
+    cachePerfStats = new CachePerfStats(statisticsFactory, TEXT_ID, statisticsClock);
   }
 
   @Test
@@ -1132,4 +1145,98 @@ public class CachePerfStatsTest {
 
     assertThat(statistics.getLong(entryCountId)).isEqualTo(-2);
   }
+
+  @Test
+  public void handlingNetsearchesInProgressIsZeroByDefault() {
+    assertThat(statistics.getLong(handlingNetsearchesInProgressId)).isZero();
+  }
+
+  @Test
+  public void handlingNetsearchesCompletedIsZeroByDefault() {
+    assertThat(statistics.getLong(handlingNetsearchesCompletedId)).isZero();
+  }
+
+  @Test
+  public void handlingNetsearchesTimeIsZeroByDefault() {
+    assertThat(statistics.getLong(handlingNetsearchesTimeId)).isZero();
+  }
+
+  @Test
+  public void handlingNetsearchesFailedIsZeroByDefault() {
+    assertThat(statistics.getLong(handlingNetsearchesFailedId)).isZero();
+  }
+
+  @Test
+  public void handlingNetsearchesFailedTimeIsZeroByDefault() {
+    assertThat(statistics.getLong(handlingNetsearchesFailedTimeId)).isZero();
+  }
+
+  @Test
+  public void startHandlingNetsearchIncreasesHandlingNetsearchesInProgress() {
+    doReturn(1L, 10L).when(statisticsClock).getTime();
+
+    cachePerfStats.startHandlingNetsearch();
+
+    assertThat(statistics.getLong(handlingNetsearchesInProgressId)).isOne();
+  }
+
+  @Test
+  public void endHandlingNetsearchIncreasesHandlingNetsearchesCompletedIfSuccess() {
+    doReturn(1L, 10L).when(statisticsClock).getTime();
+    long startTime = cachePerfStats.startHandlingNetsearch();
+
+    cachePerfStats.endHandlingNetsearch(startTime, true);
+
+    assertThat(statistics.getLong(handlingNetsearchesCompletedId)).isOne();
+  }
+
+  @Test
+  public void endHandlingNetsearchIncreasesHandlingNetsearchesTimeIfSuccess() {
+    doReturn(1L, 10L).when(statisticsClock).getTime();
+    long startTime = cachePerfStats.startHandlingNetsearch();
+
+    cachePerfStats.endHandlingNetsearch(startTime, true);
+
+    assertThat(statistics.getLong(handlingNetsearchesTimeId)).isEqualTo(9);
+  }
+
+  @Test
+  public void endHandlingNetsearchIncreasesHandlingNetsearchesFailedIfNotSuccess() {
+    doReturn(1L, 10L).when(statisticsClock).getTime();
+    long startTime = cachePerfStats.startHandlingNetsearch();
+
+    cachePerfStats.endHandlingNetsearch(startTime, false);
+
+    assertThat(statistics.getLong(handlingNetsearchesFailedId)).isOne();
+  }
+
+  @Test
+  public void endHandlingNetsearchIncreasesHandlingNetsearchesFailedTimeIfNotSuccess() {
+    doReturn(1L, 10L).when(statisticsClock).getTime();
+    long startTime = cachePerfStats.startHandlingNetsearch();
+
+    cachePerfStats.endHandlingNetsearch(startTime, false);
+
+    assertThat(statistics.getLong(handlingNetsearchesFailedTimeId)).isEqualTo(9);
+  }
+
+  @Test
+  public void endHandlingNetsearchDecreasesHandlingNetsearchesInProgressIfSuccess() {
+    doReturn(1L, 10L).when(statisticsClock).getTime();
+    long startTime = cachePerfStats.startHandlingNetsearch();
+
+    cachePerfStats.endHandlingNetsearch(startTime, true);
+
+    assertThat(statistics.getLong(handlingNetsearchesInProgressId)).isZero();
+  }
+
+  @Test
+  public void endHandlingNetsearchDecreasesHandlingNetsearchesInProgressIfNotSuccess() {
+    doReturn(1L, 10L).when(statisticsClock).getTime();
+    long startTime = cachePerfStats.startHandlingNetsearch();
+
+    cachePerfStats.endHandlingNetsearch(startTime, false);
+
+    assertThat(statistics.getLong(handlingNetsearchesInProgressId)).isZero();
+  }
 }
diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/rules/DistributedRule.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/rules/DistributedRule.java
index 1943fcd..84872dc 100644
--- a/geode-dunit/src/main/java/org/apache/geode/test/dunit/rules/DistributedRule.java
+++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/rules/DistributedRule.java
@@ -40,6 +40,7 @@ import org.apache.geode.internal.net.SocketCreator;
 import org.apache.geode.internal.net.SocketCreatorFactory;
 import org.apache.geode.management.internal.cli.LogWrapper;
 import org.apache.geode.pdx.internal.TypeRegistry;
+import org.apache.geode.test.dunit.DistributedTestUtils;
 import org.apache.geode.test.dunit.IgnoredException;
 import org.apache.geode.test.dunit.internal.DUnitLauncher;
 import org.apache.geode.test.junit.rules.serializable.SerializableExternalResource;
@@ -152,6 +153,10 @@ public class DistributedRule extends AbstractDistributedRule {
     return DUnitLauncher.getDistributedSystemProperties();
   }
 
+  public static int getLocatorPort() {
+    return DistributedTestUtils.getLocatorPort();
+  }
+
   /**
    * Builds an instance of DistributedRule.
    */