You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by dh...@apache.org on 2019/07/22 23:21:32 UTC

[geode] branch GEODE-7001-region-entry-count updated (bf7d876 -> 8993596)

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

dhemery pushed a change to branch GEODE-7001-region-entry-count
in repository https://gitbox.apache.org/repos/asf/geode.git.


 discard bf7d876  Add data policy to region entry count gauge
 discard 94a1d3d  Clean up earlier changes
 discard e164f65  Partitioned redundant region test uses more servers
 discard 5587b6e  Teardown stops all started servers
 discard 44a1e50  Add test for partitioned region with redundancy
 discard 0e2fa6f  Remove CachePerfStats.getEntries()
 discard f8864a2  Try keeping entry count accumulator in RegionPerfStats
 discard 63d33eb  Fix partitioned region test to assert total entry count
 discard c77586a  Add second region to region entry meter test
 discard 88c8612  Add TODO statements
 discard 1526f8c  Modify acceptance test to specify single region.
 discard 31354fc  Reorganize CachePerfStats/RegionPerfStats constructors
 discard 93a6c10  Make regionEntriesGaugeShowsCountOfReplicateRegionValuesInServer pass
 discard d9494ee  Add jmx manager configuration to locator and failing assertions
 discard 1c1356e  Rename RegionEntriesGaugeTest and use rules
 discard 9e3b644  Split the test of a region into two tests
 discard c8dcc76  Acceptance test fails now
 discard 3987c38  WIP - Added acceptance test
 discard 85af930  WIP - A new test
     new 8993596  Add region entry count gauge

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (bf7d876)
            \
             N -- N -- N   refs/heads/GEODE-7001-region-entry-count (8993596)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:


[geode] 01/01: Add region entry count gauge

Posted by dh...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dhemery pushed a commit to branch GEODE-7001-region-entry-count
in repository https://gitbox.apache.org/repos/asf/geode.git

commit 899359642f918385ec20eb1b420a8397ce071fda
Author: Michael Oleske <mo...@pivotal.io>
AuthorDate: Mon Jul 8 16:58:11 2019 -0700

    Add region entry count gauge
    
    Add 'member.region.entries' gauge in RegionPerfStats
    - Add region.name and data.policy tags
    - Add an AtomicLong to track the entry count
    - Configure the member.region.entries gauge to fetch from the AtomicLong
    - Configure the 'entryCount' stat to be supplied by the AtomicLong
    
    Also refactored the following:
    - Reorganize CachePerfStats/RegionPerfStats constructors
    - Remove CachePerfStats.getEntries()
    - Remove use of CachePerfStats.getEntries() from
      PartitionedRegionStatus.
    - Add @Override to ValidatingStatisticsType methods in StatisticsTypeImpl
    - Extracted invokeSuppliers() to new SuppliableStatistics interface and
      rename as updateSuppliedValues()
    - Move responsibility to prepend 'RegionStats-' onto region statistics
      textID into the classes that create RegionPerfStats and
      CachePerfStats.
    
    Co-authored-by: Michael Oleske <mo...@pivotal.io>
    Co-authored-by: Kirk Lund <kl...@apache.org>
    Co-Authored-By: Mark Hanson <mh...@pivotal.io>
---
 .../geode/metrics/RegionEntriesGaugeTest.java      | 336 +++++++++++++++++++++
 .../cache/PartitionedRegionStatsDUnitTest.java     |   3 +-
 .../geode/internal/cache/CachePerfStats.java       |  68 ++---
 .../geode/internal/cache/DummyCachePerfStats.java  |  22 +-
 .../apache/geode/internal/cache/LocalRegion.java   |   7 +-
 .../internal/cache/PartitionedRegionDataStore.java |   4 +-
 .../internal/cache/PartitionedRegionHelper.java    |   2 +-
 .../internal/cache/PartitionedRegionStatus.java    |  12 -
 .../geode/internal/cache/RegionPerfStats.java      |  44 ++-
 .../internal/cache/wan/AbstractGatewaySender.java  |   3 +-
 .../geode/internal/statistics/CallbackSampler.java |   2 +-
 .../geode/internal/statistics/StatisticsImpl.java  |  15 +-
 .../internal/statistics/StatisticsTypeImpl.java    |   4 +-
 .../internal/statistics/SuppliableStatistics.java  |  35 +++
 .../statistics/ValidatingStatisticsType.java       |  24 ++
 .../management/internal/FederatingManager.java     |   3 +-
 .../geode/management/internal/LocalManager.java    |   3 +-
 .../geode/internal/cache/CachePerfStatsTest.java   |  20 +-
 .../geode/internal/cache/RegionPerfStatsTest.java  | 174 ++++++++++-
 .../internal/statistics/CallbackSamplerTest.java   |   4 +-
 .../internal/statistics/StatisticsImplTest.java    |  12 +-
 .../bean/stats/MemberLevelStatsTest.java           |   4 +-
 22 files changed, 691 insertions(+), 110 deletions(-)

diff --git a/geode-assembly/src/acceptanceTest/java/org/apache/geode/metrics/RegionEntriesGaugeTest.java b/geode-assembly/src/acceptanceTest/java/org/apache/geode/metrics/RegionEntriesGaugeTest.java
new file mode 100644
index 0000000..7623827
--- /dev/null
+++ b/geode-assembly/src/acceptanceTest/java/org/apache/geode/metrics/RegionEntriesGaugeTest.java
@@ -0,0 +1,336 @@
+/*
+ * 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.metrics;
+
+import static java.util.Arrays.asList;
+import static java.util.stream.Collectors.toList;
+import static org.apache.geode.cache.RegionShortcut.PARTITION;
+import static org.apache.geode.cache.RegionShortcut.PARTITION_REDUNDANT;
+import static org.apache.geode.cache.RegionShortcut.REPLICATE;
+import static org.apache.geode.cache.client.ClientRegionShortcut.PROXY;
+import static org.apache.geode.test.awaitility.GeodeAwaitility.await;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+import io.micrometer.core.instrument.Gauge;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.TemporaryFolder;
+
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.client.ClientCache;
+import org.apache.geode.cache.client.ClientCacheFactory;
+import org.apache.geode.cache.execute.Function;
+import org.apache.geode.cache.execute.FunctionContext;
+import org.apache.geode.internal.AvailablePortHelper;
+import org.apache.geode.metrics.rules.MetricsPublishingServiceJarRule;
+import org.apache.geode.metrics.rules.SingleFunctionJarRule;
+import org.apache.geode.test.junit.categories.MetricsTest;
+import org.apache.geode.test.junit.rules.gfsh.GfshExecution;
+import org.apache.geode.test.junit.rules.gfsh.GfshRule;
+
+@Category(MetricsTest.class)
+public class RegionEntriesGaugeTest {
+
+  @Rule
+  public GfshRule gfshRule = new GfshRule();
+
+  @Rule
+  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  // TODO: refactor to JarRule
+  @Rule
+  public MetricsPublishingServiceJarRule metricsPublishingServiceJarRule =
+      new MetricsPublishingServiceJarRule("metrics-publishing-service.jar",
+          SimpleMetricsPublishingService.class);
+
+  @Rule
+  public SingleFunctionJarRule functionJarRule =
+      new SingleFunctionJarRule("function.jar", GetMemberRegionEntriesGaugeFunction.class);
+
+  private static final String SPACE = " ";
+
+  private final List<File> serverFolders = new ArrayList<>();
+  private File folderForLocator;
+  private ClientCache clientCache;
+  private String connectToLocatorCommand;
+  private String locatorString;
+
+  @Before
+  public void startMembers() throws Exception {
+    int[] availablePorts = AvailablePortHelper.getRandomAvailableTCPPorts(5);
+    int locatorPort = availablePorts[0];
+    int locatorHttpPort = availablePorts[1];
+    int locatorJmxPort = availablePorts[2];
+    int serverPort1 = availablePorts[3];
+    int serverPort2 = availablePorts[4];
+
+    locatorString = "localhost[" + locatorPort + "]";
+
+    folderForLocator = temporaryFolder.newFolder("locator");
+    File folderForServer1 = temporaryFolder.newFolder("server1");
+    File folderForServer2 = temporaryFolder.newFolder("server2");
+    serverFolders.add(folderForServer1);
+    serverFolders.add(folderForServer2);
+
+    String startLocatorCommand = String.join(SPACE,
+        "start locator",
+        "--name=" + "locator",
+        "--dir=" + folderForLocator.getAbsolutePath(),
+        "--port=" + locatorPort,
+        "--J=-Dgemfire.jmx-manager-start=true",
+        "--J=-Dgemfire.jmx-manager-http-port=" + locatorHttpPort,
+        "--J=-Dgemfire.jmx-manager-port=" + locatorJmxPort);
+
+    String startServer1Command = startServerCommand("server1", serverPort1, folderForServer1);
+    String startServer2Command = startServerCommand("server2", serverPort2, folderForServer2);
+
+    gfshRule.execute(startLocatorCommand, startServer1Command, startServer2Command);
+
+    connectToLocatorCommand = "connect --locator=" + locatorString;
+
+    String deployCommand = functionJarRule.deployCommand();
+    String listFunctionsCommand = "list functions";
+
+    gfshRule.execute(connectToLocatorCommand, deployCommand, listFunctionsCommand);
+
+    clientCache = new ClientCacheFactory().addPoolLocator("localhost", locatorPort).create();
+  }
+
+  @After
+  public void stopMembers() {
+    clientCache.close();
+
+    List<String> commands = new ArrayList<>();
+    commands.add(connectToLocatorCommand);
+
+    serverFolders.stream()
+        .map(RegionEntriesGaugeTest::stopServerCommand)
+        .forEach(commands::add);
+
+    commands.add("stop locator --dir=" + folderForLocator.getAbsolutePath());
+
+    gfshRule.execute(commands.toArray(new String[0]));
+  }
+
+  private static String stopServerCommand(File folder) {
+    return "stop server --dir=" + folder.getAbsolutePath();
+  }
+
+  @Test
+  public void regionEntriesGaugeShowsCountOfReplicateRegionValuesInServer() {
+    Region<String, String> otherRegion = createRegion(REPLICATE.name(), "otherRegionName");
+    otherRegion.put("other-region-key", "other-region-value");
+
+    String regionName = "replicateRegion";
+    Region<String, String> regionOfInterest = createRegion(REPLICATE.name(), regionName);
+
+    List<String> keys = asList("a", "b", "c", "d", "e", "f", "g", "h");
+    for (String key : keys) {
+      regionOfInterest.put(key, key);
+    }
+    regionOfInterest.destroy(keys.get(0));
+    int expectedNumberOfEntries = keys.size() - 1;
+
+    String getGaugeValueCommand = memberRegionEntryGaugeValueCommand(regionName);
+
+    await().untilAsserted(() -> {
+      GfshExecution execution = gfshRule.execute(connectToLocatorCommand, getGaugeValueCommand);
+      List<Integer> entryCounts = linesOf(execution.getOutputText())
+          .filter(s -> s.startsWith("server"))
+          .map(RegionEntriesGaugeTest::extractEntryCount)
+          .collect(toList());
+
+      assertThat(entryCounts)
+          .as("Number of entries reported by each server")
+          .allMatch(i -> i == expectedNumberOfEntries, "equals " + expectedNumberOfEntries);
+    });
+  }
+
+  @Test
+  public void regionEntriesGaugeShowsCountOfPartitionedRegionValuesInEachServer() {
+    String regionName = "partitionedRegion";
+
+    Region<String, String> region = createRegion(PARTITION.name(), regionName);
+
+    List<String> keys = asList("a", "b", "c", "d", "e", "f", "g", "h");
+    for (String key : keys) {
+      region.put(key, key);
+    }
+
+    region.destroy(keys.get(0));
+
+    int expectedNumberOfEntries = keys.size() - 1;
+
+    String getGaugeValueCommand = memberRegionEntryGaugeValueCommand(regionName);
+
+    await().untilAsserted(() -> {
+      GfshExecution execution = gfshRule.execute(connectToLocatorCommand, getGaugeValueCommand);
+      int sum = linesOf(execution.getOutputText())
+          .filter(s -> s.startsWith("server"))
+          .mapToInt(RegionEntriesGaugeTest::extractEntryCount)
+          .sum();
+
+      assertThat(sum)
+          .as("total number of entries on all servers")
+          .isEqualTo(expectedNumberOfEntries);
+    });
+  }
+
+  @Test
+  public void regionEntriesGaugeShowsCountOfPartitionedRedundantRegionValuesInEachServer()
+      throws IOException {
+    int numberOfRedundantCopies = 1;
+    String regionName = "partitionedRegion";
+
+    int server3Port = AvailablePortHelper.getRandomAvailableTCPPort();
+
+    File folderForServer3 = temporaryFolder.newFolder("server3");
+    serverFolders.add(folderForServer3);
+
+    String startServer3Command = startServerCommand("server3", server3Port, folderForServer3);
+    gfshRule.execute(connectToLocatorCommand, startServer3Command);
+
+    Region<String, String> region =
+        createPartitionedRegionWithRedundancy(regionName, numberOfRedundantCopies);
+
+    List<String> keys = asList("a", "b", "c", "d", "e", "f", "g", "h");
+    for (String key : keys) {
+      region.put(key, key);
+    }
+
+    region.destroy(keys.get(0));
+
+    String getGaugeValueCommand = memberRegionEntryGaugeValueCommand(regionName);
+
+    int numberOfEntries = keys.size() - 1;
+    int totalNumberOfCopies = numberOfRedundantCopies + 1;
+    int expectedNumberOfEntries = numberOfEntries * totalNumberOfCopies;
+
+    await()
+        .atMost(10, TimeUnit.SECONDS)
+        .untilAsserted(() -> {
+          GfshExecution execution =
+              gfshRule.execute(connectToLocatorCommand, getGaugeValueCommand);
+          int totalEntryCount = linesOf(execution.getOutputText())
+              .filter(s -> s.startsWith("server"))
+              .mapToInt(RegionEntriesGaugeTest::extractEntryCount)
+              .sum();
+
+          assertThat(totalEntryCount)
+              .as("total number of entries on all servers")
+              .isEqualTo(expectedNumberOfEntries);
+        });
+  }
+
+  private static int extractEntryCount(String serverEntryCountLine) {
+    String entryCountExtractor = ".*\\[(\\d+).*].*";
+    Pattern entryCountPattern = Pattern.compile(entryCountExtractor);
+    Matcher matcher = entryCountPattern.matcher(serverEntryCountLine);
+    assertThat(matcher.matches())
+        .as(serverEntryCountLine)
+        .withFailMessage("does not match " + entryCountExtractor)
+        .isTrue();
+    String matchedGroup = matcher.group(1);
+    return Integer.valueOf(matchedGroup);
+  }
+
+  private static Stream<String> linesOf(String text) {
+    return new BufferedReader(new StringReader(text)).lines();
+  }
+
+  private Region<String, String> createPartitionedRegionWithRedundancy(String regionName,
+      int numberOfRedundantCopies) {
+
+    String createRegionCommand = String.join(SPACE,
+        "create region",
+        "--name=" + regionName,
+        "--redundant-copies=" + numberOfRedundantCopies,
+        "--type=" + PARTITION_REDUNDANT.name());
+
+    gfshRule.execute(connectToLocatorCommand, createRegionCommand);
+
+    return clientCache.<String, String>createClientRegionFactory(PROXY).create(regionName);
+  }
+
+  private Region<String, String> createRegion(String regionType, String regionName) {
+
+    String createRegionCommand = String.join(SPACE,
+        "create region",
+        "--name=" + regionName,
+        "--type=" + regionType);
+
+    gfshRule.execute(connectToLocatorCommand, createRegionCommand);
+
+    return clientCache.<String, String>createClientRegionFactory(PROXY).create(regionName);
+  }
+
+  private String startServerCommand(String serverName, int serverPort, File folderForServer) {
+    return String.join(SPACE,
+        "start server",
+        "--name=" + serverName,
+        "--dir=" + folderForServer.getAbsolutePath(),
+        "--server-port=" + serverPort,
+        "--locators=" + locatorString,
+        "--classpath=" + metricsPublishingServiceJarRule.absolutePath());
+  }
+
+  private static String memberRegionEntryGaugeValueCommand(String regionName) {
+    return String.join(SPACE,
+        "execute function",
+        "--id=" + GetMemberRegionEntriesGaugeFunction.ID,
+        "--arguments=" + regionName);
+  }
+
+  public static class GetMemberRegionEntriesGaugeFunction implements Function<String[]> {
+
+    private static final String ID = "GetMemberRegionEntriesGaugeFunction";
+
+    @Override
+    public void execute(FunctionContext<String[]> context) {
+      String regionName = context.getArguments()[0];
+
+      Gauge memberRegionEntriesGauge = SimpleMetricsPublishingService.getRegistry()
+          .find("member.region.entries")
+          .tag("region.name", regionName)
+          .gauge();
+
+      Object result = memberRegionEntriesGauge == null
+          ? "Meter not found."
+          : memberRegionEntriesGauge.value();
+
+      context.getResultSender().lastResult(result);
+    }
+
+    @Override
+    public String getId() {
+      return ID;
+    }
+  }
+}
diff --git a/geode-core/src/distributedTest/java/org/apache/geode/internal/cache/PartitionedRegionStatsDUnitTest.java b/geode-core/src/distributedTest/java/org/apache/geode/internal/cache/PartitionedRegionStatsDUnitTest.java
index d05ca9c..bf66968 100644
--- a/geode-core/src/distributedTest/java/org/apache/geode/internal/cache/PartitionedRegionStatsDUnitTest.java
+++ b/geode-core/src/distributedTest/java/org/apache/geode/internal/cache/PartitionedRegionStatsDUnitTest.java
@@ -264,7 +264,8 @@ public class PartitionedRegionStatsDUnitTest extends CacheTestCase {
     CachePerfStats cachePerfStats = region.getCachePerfStats();
 
     assertThat(stats.getDataStoreEntryCount()).isEqualTo(expectedCount);
-    assertThat(cachePerfStats.getEntries()).isEqualTo(expectedCount);
+    long actualCount = cachePerfStats.stats.getLong(CachePerfStats.entryCountId);
+    assertThat(actualCount).isEqualTo(expectedCount);
   }
 
   private void validateTotalNumBucketsCount(final String regionName, final int expectedCount) {
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 ffb6180..83a55ce 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
@@ -624,35 +624,39 @@ public class CachePerfStats {
 
   private final LongSupplier clock;
 
-  /**
-   * Created specially for bug 39348. Should not be invoked in any other case.
-   */
-  public CachePerfStats() {
-    this(null);
+  public CachePerfStats(StatisticsFactory factory) {
+    this(factory, "cachePerfStats");
   }
 
-  /**
-   * Creates a new <code>CachePerfStats</code> and registers itself with the given statistics
-   * factory.
-   */
-  public CachePerfStats(StatisticsFactory factory) {
-    this(factory, "cachePerfStats", enableClockStats ? NanoTimer::getTime : () -> 0);
+  @VisibleForTesting
+  public CachePerfStats(StatisticsFactory factory, LongSupplier clock) {
+    this(factory, "cachePerfStats", clock);
   }
 
-  /**
-   * Creates a new <code>CachePerfStats</code> and registers itself with the given statistics
-   * factory.
-   */
-  public CachePerfStats(StatisticsFactory factory, String regionName) {
-    this(factory, "RegionStats-" + regionName, enableClockStats ? NanoTimer::getTime : () -> 0);
+  public CachePerfStats(StatisticsFactory factory, String textId) {
+    this(factory, textId, createClock());
   }
 
-  @VisibleForTesting
-  public CachePerfStats(StatisticsFactory factory, String textId, LongSupplier clock) {
-    stats = factory == null ? null : factory.createAtomicStatistics(type, textId);
+  CachePerfStats(StatisticsFactory factory, String textId, LongSupplier clock) {
+    this(createStatistics(factory, textId), clock);
+  }
+
+  private CachePerfStats(Statistics stats, LongSupplier clock) {
+    this.stats = stats;
     this.clock = clock;
   }
 
+  private static Statistics createStatistics(StatisticsFactory factory, String textId) {
+    if (factory == null) {
+      return null;
+    }
+    return factory.createAtomicStatistics(type, textId);
+  }
+
+  private static LongSupplier createClock() {
+    return enableClockStats ? NanoTimer::getTime : () -> 0;
+  }
+
   /**
    * Returns the current NanoTime or, if clock stats are disabled, zero.
    *
@@ -668,6 +672,15 @@ public class CachePerfStats {
     return type;
   }
 
+  /**
+   * Returns the Statistics instance that stores the cache perf stats.
+   *
+   * @since GemFire 3.5
+   */
+  public Statistics getStats() {
+    return stats;
+  }
+
   protected long getTime() {
     return clock.getAsLong();
   }
@@ -1273,7 +1286,7 @@ public class CachePerfStats {
    *
    * @since GemFire 3.5
    */
-  void close() {
+  protected void close() {
     stats.close();
   }
 
@@ -1310,10 +1323,6 @@ public class CachePerfStats {
     stats.incLong(entryCountId, delta);
   }
 
-  public long getEntries() {
-    return stats.getLong(entryCountId);
-  }
-
   public void incRetries() {
     stats.incInt(retriesId, 1);
   }
@@ -1363,15 +1372,6 @@ public class CachePerfStats {
   }
 
   /**
-   * Returns the Statistics instance that stores the cache perf stats.
-   *
-   * @since GemFire 3.5
-   */
-  public Statistics getStats() {
-    return stats;
-  }
-
-  /**
    * Returns a helper object so that the event pool can record its stats to the proper cache perf
    * stats.
    *
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/DummyCachePerfStats.java b/geode-core/src/main/java/org/apache/geode/internal/cache/DummyCachePerfStats.java
index 9a1505b..c4aadfc 100755
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/DummyCachePerfStats.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/DummyCachePerfStats.java
@@ -22,14 +22,9 @@ import org.apache.geode.distributed.internal.PoolStatHelper;
  */
 public class DummyCachePerfStats extends CachePerfStats {
 
-  // ////////////////////// Constructors ////////////////////////
-
-  /**
-   * Creates a new <code>DummyCachePerfStats</code>
-   */
-  public DummyCachePerfStats() {}
-
-  // //////////////////// Accessing Stats //////////////////////
+  DummyCachePerfStats() {
+    super(null);
+  }
 
   @Override
   public int getLoadsCompleted() {
@@ -328,10 +323,10 @@ public class DummyCachePerfStats extends CachePerfStats {
   @Override
   public void txRollback(long opTime, long txLifeTime, int txChanges) {}
 
-  // //// Special Instance Methods /////
-
   @Override
-  void close() {}
+  protected void close() {
+    // nothing
+  }
 
   @Override
   public boolean isClosed() {
@@ -359,11 +354,6 @@ public class DummyCachePerfStats extends CachePerfStats {
   public void incEntryCount(int delta) {}
 
   @Override
-  public long getEntries() {
-    return 0;
-  }
-
-  @Override
   public void incRetries() {}
 
   @Override
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/LocalRegion.java b/geode-core/src/main/java/org/apache/geode/internal/cache/LocalRegion.java
index 548bbe8..b5f5764 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/LocalRegion.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/LocalRegion.java
@@ -597,9 +597,10 @@ public class LocalRegion extends AbstractRegion implements LoaderHelperFactory,
         cachePerfStats = cache.getCachePerfStats();
       } else {
         hasOwnStats = true;
-        cachePerfStats = new RegionPerfStats(
-            cache.getInternalDistributedSystem().getStatisticsManager(), cache.getCachePerfStats(),
-            regionName);
+        cachePerfStats =
+            new RegionPerfStats(cache.getInternalDistributedSystem().getStatisticsManager(),
+                "RegionStats-" + regionName, cache.getCachePerfStats(), regionName, getDataPolicy(),
+                cache.getMeterRegistry());
       }
     }
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegionDataStore.java b/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegionDataStore.java
index 8c73c41..732357e 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegionDataStore.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegionDataStore.java
@@ -204,7 +204,9 @@ public class PartitionedRegionDataStore implements HasCachePerfStats {
     // this.bucketStats = new CachePerfStats(pr.getSystem(), "partition-" + pr.getName());
     this.bucketStats =
         new RegionPerfStats(pr.getCache().getInternalDistributedSystem().getStatisticsManager(),
-            pr.getCachePerfStats(), "partition-" + pr.getName());
+            "RegionStats-partition-" + pr.getName(), pr.getCachePerfStats(), pr.getName(),
+            pr.getDataPolicy(),
+            pr.getCache().getMeterRegistry());
     this.keysOfInterest = new ConcurrentHashMap();
   }
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegionHelper.java b/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegionHelper.java
index 23eda2d..b83c4fa 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegionHelper.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegionHelper.java
@@ -268,7 +268,7 @@ public class PartitionedRegionHelper {
       final HasCachePerfStats prMetaStatsHolder = new HasCachePerfStats() {
         @Override
         public CachePerfStats getCachePerfStats() {
-          return new CachePerfStats(cache.getDistributedSystem(), "partitionMetaData");
+          return new CachePerfStats(cache.getDistributedSystem(), "RegionStats-partitionMetaData");
         }
       };
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegionStatus.java b/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegionStatus.java
index 902662a..d6f2d0a 100755
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegionStatus.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegionStatus.java
@@ -31,14 +31,6 @@ public class PartitionedRegionStatus extends RegionStatus {
     initialize(region);
   }
 
-  public int getNumberOfLocalEntries() {
-    return this.numberOfLocalEntries;
-  }
-
-  protected void setNumberOfLocalEntries(int numberOfLocalEntries) {
-    this.numberOfLocalEntries = numberOfLocalEntries;
-  }
-
   @Override
   public long getHeapSize() {
     return this.heapSize;
@@ -55,14 +47,10 @@ public class PartitionedRegionStatus extends RegionStatus {
     // in this VM), get the number of entries and heap size. Else,
     // set these to 0.
     PartitionedRegionDataStore ds = region.getDataStore();
-    int numLocalEntries = 0;
     long heapSize = 0;
     if (ds != null) {
-      CachePerfStats cpStats = ds.getCachePerfStats();
-      numLocalEntries = (int) cpStats.getEntries();
       heapSize = ds.currentAllocatedMemory();
     }
-    setNumberOfLocalEntries(numLocalEntries);
     setHeapSize(heapSize);
   }
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/RegionPerfStats.java b/geode-core/src/main/java/org/apache/geode/internal/cache/RegionPerfStats.java
index f67104d..59eb422 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/RegionPerfStats.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/RegionPerfStats.java
@@ -14,27 +14,55 @@
  */
 package org.apache.geode.internal.cache;
 
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.LongSupplier;
 
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.MeterRegistry;
+
 import org.apache.geode.StatisticsFactory;
 import org.apache.geode.annotations.VisibleForTesting;
+import org.apache.geode.cache.DataPolicy;
 import org.apache.geode.internal.NanoTimer;
 
 class RegionPerfStats extends CachePerfStats {
 
   private final CachePerfStats cachePerfStats;
+  private final MeterRegistry meterRegistry;
+  private final Gauge entriesGauge;
+  private final AtomicLong entryCount;
 
-  RegionPerfStats(StatisticsFactory statisticsFactory, CachePerfStats cachePerfStats,
-      String regionName) {
-    this(statisticsFactory, cachePerfStats, regionName,
-        enableClockStats ? NanoTimer::getTime : () -> 0);
+  RegionPerfStats(StatisticsFactory statisticsFactory, String textId, CachePerfStats cachePerfStats,
+      String regionName, DataPolicy dataPolicy, MeterRegistry meterRegistry) {
+    this(statisticsFactory, textId, createClock(), cachePerfStats, regionName, dataPolicy,
+        meterRegistry);
   }
 
   @VisibleForTesting
-  RegionPerfStats(StatisticsFactory statisticsFactory, CachePerfStats cachePerfStats,
-      String regionName, LongSupplier clock) {
-    super(statisticsFactory, "RegionStats-" + regionName, clock);
+  RegionPerfStats(StatisticsFactory statisticsFactory, String textId, LongSupplier clock,
+      CachePerfStats cachePerfStats, String regionName, DataPolicy dataPolicy,
+      MeterRegistry meterRegistry) {
+    super(statisticsFactory, textId, clock);
     this.cachePerfStats = cachePerfStats;
+    this.meterRegistry = meterRegistry;
+    entryCount = new AtomicLong();
+    entriesGauge = Gauge.builder("member.region.entries", entryCount::get)
+        .description("Current number of entries in the region.")
+        .tag("region.name", regionName)
+        .tag("data.policy", dataPolicy.toString())
+        .baseUnit("entries")
+        .register(meterRegistry);
+    stats.setLongSupplier(entryCountId, entryCount::get);
+  }
+
+  private static LongSupplier createClock() {
+    return enableClockStats ? NanoTimer::getTime : () -> 0;
+  }
+
+  @Override
+  protected void close() {
+    meterRegistry.remove(entriesGauge);
+    super.close();
   }
 
   @Override
@@ -463,7 +491,7 @@ class RegionPerfStats extends CachePerfStats {
 
   @Override
   public void incEntryCount(int delta) {
-    stats.incLong(entryCountId, delta);
+    entryCount.addAndGet(delta);
     cachePerfStats.incEntryCount(delta);
   }
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/wan/AbstractGatewaySender.java b/geode-core/src/main/java/org/apache/geode/internal/cache/wan/AbstractGatewaySender.java
index 8563e8d..3c4411f 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/wan/AbstractGatewaySender.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/wan/AbstractGatewaySender.java
@@ -1256,7 +1256,8 @@ public abstract class AbstractGatewaySender implements InternalGatewaySender, Di
       final HasCachePerfStats statsHolder = new HasCachePerfStats() {
         @Override
         public CachePerfStats getCachePerfStats() {
-          return new CachePerfStats(cache.getDistributedSystem(), META_DATA_REGION_NAME);
+          return new CachePerfStats(cache.getDistributedSystem(),
+              "RegionStats-" + META_DATA_REGION_NAME);
         }
       };
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/statistics/CallbackSampler.java b/geode-core/src/main/java/org/apache/geode/internal/statistics/CallbackSampler.java
index f50c30e..d5ad2f0 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/statistics/CallbackSampler.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/statistics/CallbackSampler.java
@@ -64,7 +64,7 @@ public class CallbackSampler {
     try {
       for (Statistics stats : statisticsManager.getStatsList()) {
         StatisticsImpl statistics = (StatisticsImpl) stats;
-        errors += statistics.invokeSuppliers();
+        errors += statistics.updateSuppliedValues();
         suppliers += statistics.getSupplierCount();
       }
     } catch (VirtualMachineError e) {
diff --git a/geode-core/src/main/java/org/apache/geode/internal/statistics/StatisticsImpl.java b/geode-core/src/main/java/org/apache/geode/internal/statistics/StatisticsImpl.java
index 72cb9ff..125296a 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/statistics/StatisticsImpl.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/statistics/StatisticsImpl.java
@@ -40,12 +40,12 @@ import org.apache.geode.internal.util.concurrent.CopyOnWriteHashMap;
  *
  * @since GemFire 3.0
  */
-public abstract class StatisticsImpl implements Statistics {
+public abstract class StatisticsImpl implements SuppliableStatistics {
 
   private static final Logger logger = LogService.getLogger();
 
   /** The type of this statistics instance */
-  protected final StatisticsTypeImpl type;
+  protected final ValidatingStatisticsType type;
 
   /** The display name of this statistics instance */
   private final String textId;
@@ -127,7 +127,7 @@ public abstract class StatisticsImpl implements Statistics {
    */
   StatisticsImpl(StatisticsType type, String textId, long numericId, long uniqueId,
       int osStatFlags, StatisticsManager statisticsManager, StatisticsLogger statisticsLogger) {
-    this.type = (StatisticsTypeImpl) type;
+    this.type = (ValidatingStatisticsType) type;
     this.textId = StringUtils.isEmpty(textId) ? statisticsManager.getName() : textId;
     this.numericId = numericId == 0 ? statisticsManager.getPid() : numericId;
     this.uniqueId = uniqueId;
@@ -504,13 +504,8 @@ public abstract class StatisticsImpl implements Statistics {
     // nothing needed in this impl.
   }
 
-  /**
-   * Invoke sample suppliers to retrieve the current value for the suppler controlled sets and
-   * update the stats to reflect the supplied values.
-   *
-   * @return the number of callback errors that occurred while sampling stats
-   */
-  int invokeSuppliers() {
+  @Override
+  public int updateSuppliedValues() {
     int errors = 0;
     for (Map.Entry<Integer, IntSupplier> entry : intSuppliers.entrySet()) {
       try {
diff --git a/geode-core/src/main/java/org/apache/geode/internal/statistics/StatisticsTypeImpl.java b/geode-core/src/main/java/org/apache/geode/internal/statistics/StatisticsTypeImpl.java
index 19608b4..7eb1e04 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/statistics/StatisticsTypeImpl.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/statistics/StatisticsTypeImpl.java
@@ -32,7 +32,7 @@ import org.apache.geode.annotations.Immutable;
  * @since GemFire 3.0
  */
 @Immutable
-public class StatisticsTypeImpl implements StatisticsType {
+public class StatisticsTypeImpl implements ValidatingStatisticsType {
 
   /** The name of this statistics type */
   private final String name;
@@ -221,10 +221,12 @@ public class StatisticsTypeImpl implements StatisticsType {
     return true;
   }
 
+  @Override
   public boolean isValidLongId(int id) {
     return id < longStatCount;
   }
 
+  @Override
   public boolean isValidDoubleId(int id) {
     return longStatCount <= id && id < longStatCount + doubleStatCount;
   }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/statistics/SuppliableStatistics.java b/geode-core/src/main/java/org/apache/geode/internal/statistics/SuppliableStatistics.java
new file mode 100644
index 0000000..0f01907
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/statistics/SuppliableStatistics.java
@@ -0,0 +1,35 @@
+/*
+ * 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.internal.statistics;
+
+import org.apache.geode.Statistics;
+
+public interface SuppliableStatistics extends Statistics {
+
+  /**
+   * Cast {@code Statistics} to {@code SuppliableStatistics}.
+   */
+  static SuppliableStatistics toSuppliableStatistics(Statistics statistics) {
+    return (SuppliableStatistics) statistics;
+  }
+
+  /**
+   * Invoke sample suppliers to retrieve the current value for the supplier controlled sets and
+   * update the stats to reflect the supplied values.
+   *
+   * @return the number of callback errors that occurred while sampling stats
+   */
+  int updateSuppliedValues();
+}
diff --git a/geode-core/src/main/java/org/apache/geode/internal/statistics/ValidatingStatisticsType.java b/geode-core/src/main/java/org/apache/geode/internal/statistics/ValidatingStatisticsType.java
new file mode 100644
index 0000000..a6bd059
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/statistics/ValidatingStatisticsType.java
@@ -0,0 +1,24 @@
+/*
+ * 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.internal.statistics;
+
+import org.apache.geode.StatisticsType;
+
+public interface ValidatingStatisticsType extends StatisticsType {
+
+  boolean isValidLongId(int id);
+
+  boolean isValidDoubleId(int id);
+}
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/FederatingManager.java b/geode-core/src/main/java/org/apache/geode/management/internal/FederatingManager.java
index 0fdc80d..12e70ba 100755
--- a/geode-core/src/main/java/org/apache/geode/management/internal/FederatingManager.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/FederatingManager.java
@@ -374,7 +374,8 @@ public class FederatingManager extends Manager {
 
             // Create anonymous stats holder for Management Regions
             HasCachePerfStats monitoringRegionStats =
-                () -> new CachePerfStats(cache.getDistributedSystem(), "managementRegionStats");
+                () -> new CachePerfStats(cache.getDistributedSystem(),
+                    "RegionStats-managementRegionStats");
 
             internalRegionArguments.setCachePerfStatsHolder(monitoringRegionStats);
 
diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/LocalManager.java b/geode-core/src/main/java/org/apache/geode/management/internal/LocalManager.java
index 38c3821..bb0cc86 100644
--- a/geode-core/src/main/java/org/apache/geode/management/internal/LocalManager.java
+++ b/geode-core/src/main/java/org/apache/geode/management/internal/LocalManager.java
@@ -118,7 +118,8 @@ public class LocalManager extends Manager {
         final HasCachePerfStats monitoringRegionStats = new HasCachePerfStats() {
           @Override
           public CachePerfStats getCachePerfStats() {
-            return new CachePerfStats(cache.getDistributedSystem(), "managementRegionStats");
+            return new CachePerfStats(cache.getDistributedSystem(),
+                "RegionStats-managementRegionStats");
           }
         };
 
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 4ae2c01..3ae5ee2 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
@@ -26,6 +26,7 @@ import static org.apache.geode.internal.cache.CachePerfStats.deltaUpdatesId;
 import static org.apache.geode.internal.cache.CachePerfStats.deltasPreparedId;
 import static org.apache.geode.internal.cache.CachePerfStats.deltasSentId;
 import static org.apache.geode.internal.cache.CachePerfStats.destroysId;
+import static org.apache.geode.internal.cache.CachePerfStats.entryCountId;
 import static org.apache.geode.internal.cache.CachePerfStats.evictorJobsCompletedId;
 import static org.apache.geode.internal.cache.CachePerfStats.evictorJobsStartedId;
 import static org.apache.geode.internal.cache.CachePerfStats.getInitialImagesCompletedId;
@@ -53,7 +54,6 @@ import static org.apache.geode.internal.cache.CachePerfStats.updatesId;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import org.junit.After;
@@ -84,13 +84,13 @@ public class CachePerfStatsTest {
     StatisticsManager statisticsManager = mock(StatisticsManager.class);
     StatisticsFactory statisticsFactory = mock(StatisticsFactory.class);
 
-    statistics = spy(new StripedStatisticsImpl(statisticsType, TEXT_ID, 1, 1, statisticsManager));
+    statistics = new StripedStatisticsImpl(statisticsType, TEXT_ID, 1, 1, statisticsManager);
 
     when(statisticsFactory.createAtomicStatistics(eq(statisticsType), eq(TEXT_ID)))
         .thenReturn(statistics);
 
     CachePerfStats.enableClockStats = true;
-    cachePerfStats = new CachePerfStats(statisticsFactory, TEXT_ID, () -> CLOCK_TIME);
+    cachePerfStats = new CachePerfStats(statisticsFactory, () -> CLOCK_TIME);
   }
 
   @After
@@ -1125,4 +1125,18 @@ public class CachePerfStatsTest {
 
     assertThat(cachePerfStats.getDeltaFullValuesRequested()).isNegative();
   }
+
+  @Test
+  public void incEntryCount_whenDeltaIsPositive_increasesTheEntryCountStat() {
+    cachePerfStats.incEntryCount(2);
+
+    assertThat(statistics.getLong(entryCountId)).isEqualTo(2);
+  }
+
+  @Test
+  public void incEntryCount_whenDeltaIsNegative_decreasesTheEntryCountStat() {
+    cachePerfStats.incEntryCount(-2);
+
+    assertThat(statistics.getLong(entryCountId)).isEqualTo(-2);
+  }
 }
diff --git a/geode-core/src/test/java/org/apache/geode/internal/cache/RegionPerfStatsTest.java b/geode-core/src/test/java/org/apache/geode/internal/cache/RegionPerfStatsTest.java
index 30520c1..b4ce7e0 100644
--- a/geode-core/src/test/java/org/apache/geode/internal/cache/RegionPerfStatsTest.java
+++ b/geode-core/src/test/java/org/apache/geode/internal/cache/RegionPerfStatsTest.java
@@ -16,35 +16,195 @@ package org.apache.geode.internal.cache;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.quality.Strictness.STRICT_STUBS;
 
+import java.util.function.LongSupplier;
+
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Timer;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
+import org.apache.geode.Statistics;
 import org.apache.geode.StatisticsFactory;
+import org.apache.geode.cache.DataPolicy;
 
 public class RegionPerfStatsTest {
 
+  private static final String REGION_NAME = "region1";
+  private static final String TEXT_ID = "textId";
+  private static final DataPolicy DATA_POLICY = DataPolicy.PERSISTENT_REPLICATE;
+
+  private MeterRegistry meterRegistry;
   private StatisticsFactory statisticsFactory;
   private CachePerfStats cachePerfStats;
 
+  private RegionPerfStats regionPerfStats;
+  private RegionPerfStats regionPerfStats2;
+  private Statistics statistics;
+
+  @Rule
+  public MockitoRule mockitoRule = MockitoJUnit.rule().strictness(STRICT_STUBS);
+
   @Before
   public void setUp() {
-    statisticsFactory = mock(StatisticsFactory.class);
+    meterRegistry = new SimpleMeterRegistry();
     cachePerfStats = mock(CachePerfStats.class);
+    statisticsFactory = mock(StatisticsFactory.class);
+    statistics = mock(Statistics.class);
+
+    when(statisticsFactory.createAtomicStatistics(any(), any())).thenReturn(statistics);
+
+    regionPerfStats =
+        new RegionPerfStats(statisticsFactory, TEXT_ID, cachePerfStats, REGION_NAME, DATA_POLICY,
+            meterRegistry);
+  }
+
+  @After
+  public void closeStats() {
+    if (regionPerfStats != null) {
+      regionPerfStats.close();
+    }
+    if (regionPerfStats2 != null) {
+      regionPerfStats2.close();
+    }
+  }
+
+  @Test
+  public void createsStatisticsUsingTextId() {
+    StatisticsFactory statisticsFactory = mock(StatisticsFactory.class);
+    when(statisticsFactory.createAtomicStatistics(any(), any())).thenReturn(mock(Statistics.class));
+
+    new RegionPerfStats(statisticsFactory, TEXT_ID, cachePerfStats, REGION_NAME, DataPolicy.NORMAL,
+        meterRegistry);
+
+    verify(statisticsFactory).createAtomicStatistics(any(), eq(TEXT_ID));
   }
 
   @Test
-  public void textIdIsRegionStatsHyphenRegionName() throws Exception {
-    String theRegionName = "TheRegionName";
+  public void constructor_createsEntriesGauge_taggedWithRegionName() {
+    Gauge entriesGauge = meterRegistry
+        .find("member.region.entries")
+        .gauge();
+
+    assertThat(entriesGauge.getId().getTag("region.name"))
+        .as("region.name tag")
+        .isEqualTo(REGION_NAME);
+  }
+
+  @Test
+  public void constructor_createsEntriesGauge_taggedWithDataPolicy() {
+    Gauge entriesGauge = meterRegistry
+        .find("member.region.entries")
+        .gauge();
+    assertThat(entriesGauge.getId().getTag("data.policy"))
+        .as("data.policy tag")
+        .isEqualTo(DATA_POLICY.toString());
+  }
 
-    new RegionPerfStats(statisticsFactory, cachePerfStats, theRegionName, () -> 0);
+  @Test
+  public void suppliesEntryCountStatWithAccumulatedEntryCount() {
+    ArgumentCaptor<LongSupplier> supplierCaptor = ArgumentCaptor.forClass(LongSupplier.class);
+
+    verify(statistics).setLongSupplier(eq(RegionPerfStats.entryCountId), supplierCaptor.capture());
+
+    LongSupplier capturedSupplier = supplierCaptor.getValue();
+    assertThat(capturedSupplier.getAsLong())
+        .as("Initial value of supplier")
+        .isEqualTo(0);
+
+    regionPerfStats.incEntryCount(3);
+    regionPerfStats.incEntryCount(7);
+    regionPerfStats.incEntryCount(-4);
+
+    assertThat(capturedSupplier.getAsLong())
+        .as("Accumulated value of supplier")
+        .isEqualTo(6);
+  }
 
-    ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
-    verify(statisticsFactory).createAtomicStatistics(any(), captor.capture());
+  @Test
+  public void incEntryCount_incrementsCachePerfStatsEntryCount() {
+    regionPerfStats.incEntryCount(2);
+
+    verify(cachePerfStats).incEntryCount(2);
+  }
+
+  @Test
+  public void incEntryCount_updatesMeterForThisRegion() {
+    regionPerfStats.incEntryCount(1);
+    regionPerfStats.incEntryCount(3);
+    regionPerfStats.incEntryCount(-1);
+
+    Gauge entriesGauge = meterRegistry
+        .find("member.region.entries")
+        .tag("region.name", REGION_NAME)
+        .gauge();
+    assertThat(entriesGauge.value()).isEqualTo(3);
+  }
+
+  @Test
+  public void incEntryCount_doesNotUpdateMeterForOtherRegion() {
+    regionPerfStats2 =
+        new RegionPerfStats(statisticsFactory, "region2", () -> 0, cachePerfStats, "region2",
+            DataPolicy.NORMAL, meterRegistry);
+
+    regionPerfStats.incEntryCount(1);
+    regionPerfStats.incEntryCount(3);
+    regionPerfStats.incEntryCount(-1);
+
+    Gauge region2EntriesGauge = meterRegistry
+        .find("member.region.entries")
+        .tag("region.name", "region2")
+        .gauge();
+    assertThat(region2EntriesGauge.value()).isZero();
+  }
+
+  @Test
+  public void close_removesItsOwnMetersFromTheRegistry() {
+    assertThat(meterNamed("member.region.entries"))
+        .as("entries gauge before closing the stats")
+        .isNotNull();
+
+    regionPerfStats.close();
+
+    assertThat(meterNamed("member.region.entries"))
+        .as("entries gauge after closing the stats")
+        .isNull();
+
+    regionPerfStats = null;
+  }
+
+  @Test
+  public void close_doesNotRemoveMetersItDoesNotOwn() {
+    String foreignMeterName = "some.meter.not.created.by.the.gateway.receiver.stats";
+
+    Timer.builder(foreignMeterName)
+        .register(meterRegistry);
+
+    regionPerfStats.close();
+
+    assertThat(meterNamed(foreignMeterName))
+        .as("foreign meter after closing the stats")
+        .isNotNull();
+
+    regionPerfStats = null;
+  }
 
-    assertThat(captor.getValue()).isEqualTo("RegionStats-" + theRegionName);
+  private Meter meterNamed(String meterName) {
+    return meterRegistry
+        .find(meterName)
+        .meter();
   }
 }
diff --git a/geode-core/src/test/java/org/apache/geode/internal/statistics/CallbackSamplerTest.java b/geode-core/src/test/java/org/apache/geode/internal/statistics/CallbackSamplerTest.java
index 4cfcd52..a392f1f 100644
--- a/geode-core/src/test/java/org/apache/geode/internal/statistics/CallbackSamplerTest.java
+++ b/geode-core/src/test/java/org/apache/geode/internal/statistics/CallbackSamplerTest.java
@@ -61,8 +61,8 @@ public class CallbackSamplerTest {
 
     StatisticsImpl stats1 = mock(StatisticsImpl.class);
     StatisticsImpl stats2 = mock(StatisticsImpl.class);
-    when(stats1.invokeSuppliers()).thenReturn(3);
-    when(stats2.invokeSuppliers()).thenReturn(2);
+    when(stats1.updateSuppliedValues()).thenReturn(3);
+    when(stats2.updateSuppliedValues()).thenReturn(2);
     when(stats1.getSupplierCount()).thenReturn(7);
     when(stats2.getSupplierCount()).thenReturn(8);
     when(statisticsManager.getStatsList()).thenReturn(Arrays.asList(stats1, stats2));
diff --git a/geode-core/src/test/java/org/apache/geode/internal/statistics/StatisticsImplTest.java b/geode-core/src/test/java/org/apache/geode/internal/statistics/StatisticsImplTest.java
index 35c43cb..0d3f9c3 100644
--- a/geode-core/src/test/java/org/apache/geode/internal/statistics/StatisticsImplTest.java
+++ b/geode-core/src/test/java/org/apache/geode/internal/statistics/StatisticsImplTest.java
@@ -69,7 +69,7 @@ public class StatisticsImplTest {
     IntSupplier intSupplier = mock(IntSupplier.class);
     when(intSupplier.getAsInt()).thenReturn(23);
     statistics.setIntSupplier(4, intSupplier);
-    assertThat(statistics.invokeSuppliers()).isEqualTo(0);
+    assertThat(statistics.updateSuppliedValues()).isEqualTo(0);
 
     verify(intSupplier).getAsInt();
     assertThat(statistics.getInt(4)).isEqualTo(23);
@@ -80,7 +80,7 @@ public class StatisticsImplTest {
     LongSupplier longSupplier = mock(LongSupplier.class);
     when(longSupplier.getAsLong()).thenReturn(23L);
     statistics.setLongSupplier(4, longSupplier);
-    assertThat(statistics.invokeSuppliers()).isEqualTo(0);
+    assertThat(statistics.updateSuppliedValues()).isEqualTo(0);
 
     verify(longSupplier).getAsLong();
     assertThat(statistics.getLong(4)).isEqualTo(23L);
@@ -91,7 +91,7 @@ public class StatisticsImplTest {
     DoubleSupplier doubleSupplier = mock(DoubleSupplier.class);
     when(doubleSupplier.getAsDouble()).thenReturn(23.3);
     statistics.setDoubleSupplier(4, doubleSupplier);
-    assertThat(statistics.invokeSuppliers()).isEqualTo(0);
+    assertThat(statistics.updateSuppliedValues()).isEqualTo(0);
 
     verify(doubleSupplier).getAsDouble();
     assertThat(statistics.getDouble(4)).isEqualTo(23.3);
@@ -109,7 +109,7 @@ public class StatisticsImplTest {
     IntSupplier throwingSupplier = mock(IntSupplier.class);
     when(throwingSupplier.getAsInt()).thenThrow(NullPointerException.class);
     statistics.setIntSupplier(4, throwingSupplier);
-    assertThat(statistics.invokeSuppliers()).isEqualTo(1);
+    assertThat(statistics.updateSuppliedValues()).isEqualTo(1);
 
     verify(throwingSupplier).getAsInt();
   }
@@ -123,13 +123,13 @@ public class StatisticsImplTest {
     IntSupplier throwingSupplier = mock(IntSupplier.class);
     when(throwingSupplier.getAsInt()).thenThrow(NullPointerException.class);
     statistics.setIntSupplier(4, throwingSupplier);
-    assertThat(statistics.invokeSuppliers()).isEqualTo(1);
+    assertThat(statistics.updateSuppliedValues()).isEqualTo(1);
 
     // String message, Object p0, Object p1, Object p2
     verify(statisticsLogger).logWarning(anyString(), isNull(), anyInt(),
         isA(NullPointerException.class));
 
-    assertThat(statistics.invokeSuppliers()).isEqualTo(1);
+    assertThat(statistics.updateSuppliedValues()).isEqualTo(1);
 
     // Make sure the logger isn't invoked again
     verify(statisticsLogger).logWarning(anyString(), isNull(), anyInt(),
diff --git a/geode-core/src/test/java/org/apache/geode/management/bean/stats/MemberLevelStatsTest.java b/geode-core/src/test/java/org/apache/geode/management/bean/stats/MemberLevelStatsTest.java
index b04aad1..21e166d 100644
--- a/geode-core/src/test/java/org/apache/geode/management/bean/stats/MemberLevelStatsTest.java
+++ b/geode-core/src/test/java/org/apache/geode/management/bean/stats/MemberLevelStatsTest.java
@@ -14,6 +14,7 @@
  */
 package org.apache.geode.management.bean.stats;
 
+import static org.apache.geode.internal.statistics.SuppliableStatistics.toSuppliableStatistics;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -86,7 +87,7 @@ public class MemberLevelStatsTest {
 
     statSampler.start();
 
-    cachePerfStats = new CachePerfStats(statisticsManager, "cachePerfStats", clockTime::get);
+    cachePerfStats = new CachePerfStats(statisticsManager, clockTime::get);
     funcServiceStats = new FunctionServiceStats(statisticsManager, "FunctionExecution",
         clockTime::get);
     distributionStats =
@@ -340,6 +341,7 @@ public class MemberLevelStatsTest {
   }
 
   private void sampleStats() {
+    toSuppliableStatistics(cachePerfStats.getStats()).updateSuppliedValues();
     statSampler.getSampleCollector().sample(clockTime.get());
   }