You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by ds...@apache.org on 2019/06/04 15:29:04 UTC

[geode] branch develop updated: GEODE-2685: support getStatistics on PartitionedRegion (#3576)

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

dschneider 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 f00fa83  GEODE-2685: support getStatistics on PartitionedRegion (#3576)
f00fa83 is described below

commit f00fa835e20d022b74abf358fca763c955e0d5fa
Author: albertogpz <al...@est.tech>
AuthorDate: Tue Jun 4 17:28:52 2019 +0200

    GEODE-2685: support getStatistics on PartitionedRegion (#3576)
    
    getStatistics for a partitioned region that is not a proxy will return a CacheStatistics whose methods always compute their results from local bucket state.
    For a proxy partitioned region The CacheStatistics methods will always return 0.
---
 .../CacheStatisticsPartitionedRegionDUnitTest.java | 505 +++++++++++++++++++++
 .../ShowMetricsCommandIntegrationTest.java         |   2 +-
 .../main/java/org/apache/geode/cache/Region.java   |   4 +-
 .../geode/internal/cache/PartitionedRegion.java    |  54 ++-
 .../dunit/cache/internal/JUnit4CacheTestCase.java  |  24 +
 5 files changed, 580 insertions(+), 9 deletions(-)

diff --git a/geode-core/src/distributedTest/java/org/apache/geode/cache30/CacheStatisticsPartitionedRegionDUnitTest.java b/geode-core/src/distributedTest/java/org/apache/geode/cache30/CacheStatisticsPartitionedRegionDUnitTest.java
new file mode 100644
index 0000000..743388a
--- /dev/null
+++ b/geode-core/src/distributedTest/java/org/apache/geode/cache30/CacheStatisticsPartitionedRegionDUnitTest.java
@@ -0,0 +1,505 @@
+/*
+ * 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.cache30;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import org.apache.geode.cache.AttributesFactory;
+import org.apache.geode.cache.CacheException;
+import org.apache.geode.cache.CacheStatistics;
+import org.apache.geode.cache.PartitionAttributesFactory;
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.StatisticsDisabledException;
+import org.apache.geode.internal.cache.InternalRegion;
+import org.apache.geode.test.dunit.SerializableRunnableIF;
+import org.apache.geode.test.dunit.VM;
+import org.apache.geode.test.dunit.cache.internal.JUnit4CacheTestCase;
+
+/**
+ * Tests the {@link CacheStatistics} that are maintained by a partitioned {@link Region} .
+ *
+ *
+ * @since GemFire 3.0
+ */
+
+public class CacheStatisticsPartitionedRegionDUnitTest extends JUnit4CacheTestCase {
+
+  public CacheStatisticsPartitionedRegionDUnitTest() {
+    super();
+  }
+
+  //////// Helper Methods
+
+  /**
+   * Asserts that two <code>long</code>s are equal concerning a delta.
+   */
+  public static void assertInRange(long start, long end, long actual) {
+    assertTrue("Expected: " + actual + " >= " + start, actual >= start);
+    assertTrue("Expected: " + actual + " <= " + end, actual <= end);
+  }
+
+  //////// Test methods
+
+  /**
+   * Tests that the {@link CacheStatistics#getHitCount hit count} and
+   * {@link CacheStatistics#getMissCount miss count} are updated properly for a partitioned region
+   * with only one server.
+   */
+  @Test
+  public void testHitMissCount() throws CacheException {
+    String name = this.getUniqueName();
+    Object key = "KEY"; // value exists
+    Object key2 = "KEY2"; // no entry
+    Object key3 = "KEY3"; // entry, invalid
+    Object value = "VALUE";
+
+    AttributesFactory factory = new AttributesFactory();
+    factory.setStatisticsEnabled(true);
+
+    Region region = createPartitionedRegion(name, factory.create());
+    CacheStatistics rStats = region.getStatistics();
+    assertEquals(0, rStats.getHitCount());
+    assertEquals(0, rStats.getMissCount());
+    assertEquals(0.0f, rStats.getHitRatio(), 0.0f);
+
+    region.get(key);
+    assertEquals(1, rStats.getMissCount());
+    assertEquals(0, rStats.getHitCount());
+    assertEquals(0.0f, rStats.getHitRatio(), 0.0f);
+
+    region.get(key);
+    assertEquals(2, rStats.getMissCount());
+    assertEquals(0, rStats.getHitCount());
+    assertEquals(0.0f, rStats.getHitRatio(), 0.0f);
+
+    rStats.resetCounts();
+    assertEquals(0, rStats.getMissCount());
+    assertEquals(0, rStats.getHitCount());
+    assertEquals(0.0f, rStats.getHitRatio(), 0.0f);
+
+    region.put(key, value);
+    assertEquals(0, rStats.getHitCount());
+    assertEquals(0, rStats.getMissCount());
+    assertEquals(0.0f, rStats.getHitRatio(), 0.0f);
+
+    region.get(key);
+    assertEquals(1, rStats.getHitCount());
+    assertEquals(0, rStats.getMissCount());
+    assertEquals(1.0f, rStats.getHitRatio(), 0.0f);
+
+    region.get(key2);
+    assertEquals(1, rStats.getMissCount());
+    assertEquals(1, rStats.getHitCount());
+    assertEquals(0.5f, rStats.getHitRatio(), 0.0f);
+
+    region.create(key3, null);
+    region.get(key3); // miss on existing entry
+    assertEquals(2, rStats.getMissCount());
+    assertEquals(1, rStats.getHitCount());
+    assertEquals(0.33f, rStats.getHitRatio(), 0.01f);
+
+    rStats.resetCounts();
+    assertEquals(0, rStats.getMissCount());
+    assertEquals(0, rStats.getHitCount());
+    assertEquals(0.0f, rStats.getHitRatio(), 0.0f);
+
+    region.invalidate(key);
+    region.get(key);
+
+    assertEquals(0, rStats.getHitCount());
+    assertEquals(1, rStats.getMissCount());
+    assertEquals(0.0f, rStats.getHitRatio(), 0.0f);
+  }
+
+  /**
+   * Tests that the {@linkplain CacheStatistics#getLastAccessedTime last access time} and
+   * {@link CacheStatistics#getLastModifiedTime last modified time} are updated appropriately for a
+   * partitioned region with only one server.
+   */
+  @Test
+  public void testTimeStats() throws CacheException, InterruptedException {
+    final long ESTAT_RES = 100; // the resolution, in ms, of entry stats
+    String name = this.getUniqueName();
+    Object key1 = Integer.valueOf(1);
+    Object key2 = Integer.valueOf(2);
+    Object missingKey = Integer.valueOf(999);
+    Object value = "VALUE";
+    long before;
+    long after;
+    long oldBefore;
+    long oldAfter;
+
+    AttributesFactory factory = new AttributesFactory();
+    // factory.setScope(Scope.LOCAL);
+    factory.setStatisticsEnabled(true);
+
+    Region region = createPartitionedRegion(name, factory.create());
+    CacheStatistics rStats = region.getStatistics();
+
+    assertEquals(0, rStats.getLastAccessedTime());
+    assertEquals(0, rStats.getLastModifiedTime());
+
+    before = ((InternalRegion) region).cacheTimeMillis();
+    // When the get is invoked, the lastAccessedTime and lastModified time
+    // are updated as a new BucketRegion is created.
+    region.get(missingKey);
+
+    after = ((InternalRegion) region).cacheTimeMillis();
+
+    assertInRange(before, after, rStats.getLastAccessedTime());
+    assertInRange(before, after, rStats.getLastModifiedTime());
+
+    oldBefore = before;
+    oldAfter = after;
+    waitForClockToChange(region);
+    before = ((InternalRegion) region).cacheTimeMillis();
+    region.get(missingKey);
+
+    after = ((InternalRegion) region).cacheTimeMillis();
+
+    assertInRange(before, after, rStats.getLastAccessedTime());
+    assertInRange(oldBefore, oldAfter, rStats.getLastModifiedTime());
+
+    waitForClockToChange(region);
+    before = ((InternalRegion) region).cacheTimeMillis();
+    region.put(key1, value);
+    CacheStatistics eStats = region.getEntry(key1).getStatistics();
+    after = ((InternalRegion) region).cacheTimeMillis();
+
+    assertInRange(before, after, rStats.getLastModifiedTime());
+    assertInRange(before, after, rStats.getLastAccessedTime());
+
+    assertInRange(before - ESTAT_RES, after + ESTAT_RES, eStats.getLastAccessedTime());
+    assertInRange(before - ESTAT_RES, after + ESTAT_RES, eStats.getLastModifiedTime());
+
+    oldBefore = before;
+    oldAfter = after;
+    waitForClockToChange(region);
+    before = ((InternalRegion) region).cacheTimeMillis();
+    region.get(key1);
+    // eStats must be obtained again. The previous object is not updated.
+    // Seems it just provides a snapshot at the moment it is received.
+    eStats = region.getEntry(key1).getStatistics();
+    after = ((InternalRegion) region).cacheTimeMillis();
+
+    assertInRange(before, after, rStats.getLastAccessedTime());
+    assertInRange(oldBefore, oldAfter, rStats.getLastModifiedTime());
+    assertInRange(before - ESTAT_RES, after + ESTAT_RES, eStats.getLastAccessedTime());
+    assertInRange(oldBefore - ESTAT_RES, oldAfter + ESTAT_RES, eStats.getLastModifiedTime());
+
+    long oldOldBefore = oldBefore;
+    long oldOldAfter = oldAfter;
+    oldBefore = before;
+    oldAfter = after;
+    waitForClockToChange(region, ESTAT_RES);
+    before = ((InternalRegion) region).cacheTimeMillis();
+    region.create(key2, null);
+    region.get(key2);
+    CacheStatistics eStats2 = region.getEntry(key2).getStatistics();
+    after = ((InternalRegion) region).cacheTimeMillis();
+
+    assertInRange(before, after, rStats.getLastModifiedTime());
+    assertInRange(before, after, rStats.getLastAccessedTime());
+
+    assertInRange(oldBefore - ESTAT_RES, oldAfter + ESTAT_RES, eStats.getLastAccessedTime());
+    assertInRange(oldOldBefore - ESTAT_RES, oldOldAfter + ESTAT_RES, eStats.getLastModifiedTime());
+
+    assertInRange(before - ESTAT_RES, after + ESTAT_RES, eStats2.getLastAccessedTime());
+    assertInRange(before - ESTAT_RES, after + ESTAT_RES, eStats2.getLastModifiedTime());
+
+    // Invalidation does not update the modification/access
+    // times
+    region.invalidate(key2);
+
+    assertInRange(before, after, rStats.getLastModifiedTime());
+    assertInRange(before - ESTAT_RES, after + ESTAT_RES, eStats2.getLastAccessedTime());
+    assertInRange(before - ESTAT_RES, after + ESTAT_RES, eStats2.getLastModifiedTime());
+    assertInRange(before, after, rStats.getLastAccessedTime());
+
+    region.destroy(key2);
+
+    assertInRange(before, after, rStats.getLastModifiedTime());
+    assertInRange(before, after, rStats.getLastAccessedTime());
+  }
+
+  private void waitForClockToChange(Region region) {
+    waitForClockToChange(region, 0);
+  }
+
+  /**
+   * Waits for the time in the <code>region</code> to step up
+   * beyond the current time in the region plus the number of
+   * milliseconds passed in the <code>millisecs</code> argument.
+   */
+  private void waitForClockToChange(Region region, long millisecs) {
+    long time = ((InternalRegion) region).cacheTimeMillis();
+    while (time >= ((InternalRegion) region).cacheTimeMillis() + millisecs) {
+    }
+  }
+
+
+  /** The last time an entry was accessed */
+  protected static volatile long lastAccessed;
+  protected static volatile long lastModified;
+
+
+  /**
+   * Tests that the {@link CacheStatistics#getHitCount hit count},
+   * {@link CacheStatistics#getMissCount miss count},
+   * {@linkplain CacheStatistics#getLastAccessedTime last access time}
+   * and {@link CacheStatistics#getLastModifiedTime last modified time} are updated
+   * properly for a partitioned region
+   * with more than one server.
+   */
+  @Test
+  public void testDistributedStats() {
+    final String name = this.getUniqueName();
+    final Object key0 = Integer.valueOf(0);
+    final Object key1 = Integer.valueOf(1);
+    final Object key2 = Integer.valueOf(2);
+    final Object key3 = Integer.valueOf(3);
+    final Object value = "VALUE";
+    final Object value1 = "VALUE1";
+    final Object value2 = "VALUE2";
+    final Object value3 = "VALUE3";
+    final Object notPresentKey1 = "NOT_PRESENT_1";
+    final Object notPresentKey2 = "NOT_PRESENT_2";
+    final Object notPresentKey3 = "NOT_PRESENT_3";
+    final Object notPresentKey4 = "NOT_PRESENT_4";
+
+    VM vm0 = VM.getVM(0);
+    VM vm1 = VM.getVM(1);
+
+    SerializableRunnableIF create = () -> {
+      AttributesFactory factory = new AttributesFactory();
+      factory.setEarlyAck(false);
+      factory.setStatisticsEnabled(true);
+      createPartitionedRegion(name, factory.create());
+    };
+
+    vm0.invoke(create);
+    vm1.invoke(create);
+
+    vm0.invoke(() -> {
+      Region region = getRootRegion(name);
+      CacheStatistics stats = region.getStatistics();
+      region.put(key0, value);
+      region.put(key1, value1);
+
+      assertTrue(stats.getLastModifiedTime() > lastModified);
+      assertEquals(0, stats.getHitCount());
+      assertEquals(0, stats.getMissCount());
+    });
+
+    vm1.invoke(() -> {
+      Region region = getRootRegion(name);
+      CacheStatistics stats = region.getStatistics();
+      region.put(key2, value3);
+      region.put(key3, value3);
+
+      assertTrue(stats.getLastModifiedTime() > lastModified);
+      assertEquals(0, stats.getHitCount());
+      assertEquals(0, stats.getMissCount());
+    });
+
+    vm0.invoke(() -> {
+      Region region = getRootRegion(name);
+      CacheStatistics stats = region.getStatistics();
+      lastAccessed = stats.getLastAccessedTime();
+      lastModified = stats.getLastModifiedTime();
+
+      Object result = region.get(key0);
+      region.get(key1);
+      region.get(key2);
+      region.get(key3);
+      region.get(notPresentKey1);
+      region.get(notPresentKey2);
+      region.get(notPresentKey3);
+      region.get(notPresentKey4);
+
+      assertEquals(value, result);
+      assertEquals(2, stats.getMissCount());
+      assertEquals(2, stats.getHitCount());
+      assertTrue(stats.getLastAccessedTime() > lastAccessed);
+    });
+
+    vm0.invoke(() -> {
+      Region region = getRootRegion(name);
+      CacheStatistics stats = region.getStatistics();
+      lastAccessed = stats.getLastAccessedTime();
+
+      Object result = region.get(key0);
+      region.get(key1);
+      region.get(key2);
+      region.get(key3);
+      region.get(notPresentKey1);
+      region.get(notPresentKey2);
+      region.get(notPresentKey3);
+      region.get(notPresentKey4);
+
+      assertEquals(value, result);
+      assertEquals(4, stats.getHitCount());
+      assertEquals(4, stats.getMissCount());
+      assertTrue(stats.getLastAccessedTime() > lastAccessed);
+    });
+
+    vm0.invoke(() -> {
+      Region region = getRootRegion(name);
+      CacheStatistics stats = region.getStatistics();
+      long before = ((InternalRegion) region).cacheTimeMillis();
+      region.put(key0, value);
+      region.put(key1, value1);
+      region.put(key2, value2);
+      region.put(key3, value3);
+      long after = ((InternalRegion) region).cacheTimeMillis();
+      assertTrue(before <= stats.getLastModifiedTime());
+      assertTrue(after >= stats.getLastModifiedTime());
+    });
+
+    vm1.invoke(() -> {
+      Region region = getRootRegion(name);
+      CacheStatistics stats = region.getStatistics();
+      long before = ((InternalRegion) region).cacheTimeMillis();
+      region.put(key0, value);
+      region.put(key1, value1);
+      region.put(key2, value2);
+      region.put(key3, value3);
+      long after = ((InternalRegion) region).cacheTimeMillis();
+      assertTrue(before <= stats.getLastModifiedTime());
+      assertTrue(after >= stats.getLastModifiedTime());
+    });
+
+    vm0.invoke(() -> {
+      Region region = getRootRegion(name);
+      CacheStatistics stats = region.getStatistics();
+      lastAccessed = stats.getLastAccessedTime();
+      lastModified = stats.getLastModifiedTime();
+      region.invalidate(key0);
+      region.invalidate(key1);
+      assertEquals(lastAccessed, stats.getLastAccessedTime());
+      assertEquals(lastModified, stats.getLastModifiedTime());
+      assertEquals(4, stats.getHitCount());
+      assertEquals(4, stats.getMissCount());
+    });
+
+    vm1.invoke(() -> {
+      Region region = getRootRegion(name);
+      CacheStatistics stats = region.getStatistics();
+      lastAccessed = stats.getLastAccessedTime();
+      lastModified = stats.getLastModifiedTime();
+      region.invalidate(key2);
+      region.invalidate(key3);
+
+      assertEquals(lastAccessed, stats.getLastAccessedTime());
+      assertEquals(lastModified, stats.getLastModifiedTime());
+      assertEquals(4, stats.getHitCount());
+      assertEquals(4, stats.getMissCount());
+    });
+
+    vm0.invoke(() -> {
+      Region region = getRootRegion(name);
+      CacheStatistics stats = region.getStatistics();
+      lastModified = stats.getLastModifiedTime();
+      region.destroy(key0);
+      region.destroy(key1);
+
+      assertEquals(lastModified, stats.getLastModifiedTime());
+      assertEquals(4, stats.getHitCount());
+      assertEquals(4, stats.getMissCount());
+    });
+
+    vm1.invoke(() -> {
+      Region region = getRootRegion(name);
+      CacheStatistics stats = region.getStatistics();
+      lastModified = stats.getLastModifiedTime();
+      region.destroy(key2);
+      region.destroy(key3);
+
+      assertEquals(lastModified, stats.getLastModifiedTime());
+      assertEquals(4, stats.getHitCount());
+      assertEquals(4, stats.getMissCount());
+    });
+
+  }
+
+  /**
+   * Tests that an attempt to get statistics when they are disabled results in a
+   * {@link StatisticsDisabledException}.
+   */
+  @Test
+  public void testDisabledStatistics() throws CacheException {
+    String name = this.getUniqueName();
+    Object key = "KEY";
+    Object value = "VALUE";
+
+    AttributesFactory factory = new AttributesFactory();
+    factory.setStatisticsEnabled(false);
+    Region region = createPartitionedRegion(name, factory.create());
+
+    assertThatThrownBy(() -> region.getStatistics())
+        .isInstanceOf(StatisticsDisabledException.class);
+
+    region.put(key, value);
+    Region.Entry entry = region.getEntry(key);
+
+    assertThatThrownBy(() -> entry.getStatistics()).isInstanceOf(StatisticsDisabledException.class);
+  }
+
+  /**
+   * Tests that the stats values for a Proxy PartitionedRegion are all 0.
+   */
+  @Test
+  public void testProxyRegionStats() throws CacheException {
+    String name = this.getUniqueName();
+    Object key = Integer.valueOf(0);
+    Object missingKey = Integer.valueOf(999);
+    Object value = "VALUE";
+
+    // Create the PARTITION_PROXY region
+    AttributesFactory proxyRegionAttrsFactory = new AttributesFactory();
+    proxyRegionAttrsFactory.setStatisticsEnabled(true);
+    PartitionAttributesFactory paf = new PartitionAttributesFactory();
+    // Setting LocalMaxMemory to 0 makes the region of PROXY type
+    paf.setLocalMaxMemory(0);
+    proxyRegionAttrsFactory.setPartitionAttributes(paf.create());
+    Region region = createPartitionedRegion(name, proxyRegionAttrsFactory.create());
+
+    // Create the region as not proxy on another server to be able to add entries.
+    VM vm0 = VM.getVM(0);
+    vm0.invoke(() -> {
+      AttributesFactory factory = new AttributesFactory();
+      factory.setStatisticsEnabled(true);
+      createPartitionedRegion(name, factory.create());
+    });
+
+    region.put(key, value);
+    region.get(key);
+    region.get(missingKey);
+
+    CacheStatistics stats = region.getStatistics();
+    assertEquals(0, stats.getLastModifiedTime());
+    assertEquals(0, stats.getLastAccessedTime());
+    assertEquals(0, stats.getHitCount());
+    assertEquals(0, stats.getMissCount());
+    assertEquals(0.0f, stats.getHitRatio(), 0.0f);
+
+    // Call reset counts to check that no exception is raised
+    stats.resetCounts();
+  }
+}
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ShowMetricsCommandIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ShowMetricsCommandIntegrationTest.java
index 77f8973..7e6ccf5 100644
--- a/geode-core/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ShowMetricsCommandIntegrationTest.java
+++ b/geode-core/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ShowMetricsCommandIntegrationTest.java
@@ -168,6 +168,6 @@ public class ShowMetricsCommandIntegrationTest {
     gfsh.executeAndAssertThat(cmd).statusIsSuccess();
     String cmd2 = "show metrics --member=" + MEMBER_NAME + " --region=region2";
     gfsh.executeAndAssertThat(cmd2).statusIsSuccess().tableHasRowWithValues("Category", "Metric",
-        "Value", "", "missCount", "-1");
+        "Value", "", "missCount", "0");
   }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/cache/Region.java b/geode-core/src/main/java/org/apache/geode/cache/Region.java
index 28f3d71..6413113 100644
--- a/geode-core/src/main/java/org/apache/geode/cache/Region.java
+++ b/geode-core/src/main/java/org/apache/geode/cache/Region.java
@@ -204,11 +204,11 @@ public interface Region<K, V> extends ConcurrentMap<K, V> {
   AttributesMutator<K, V> getAttributesMutator();
 
   /**
-   * Returns the <code>CacheStatistics</code> for this region.
+   * Returns the <code>CacheStatistics</code> for this region. If the region is a partitioned proxy
+   * region then the values for all the statistics will be 0.
    *
    * @return the <code>CacheStatistics</code> of this region
    * @throws StatisticsDisabledException if statistics have been disabled for this region
-   * @throws UnsupportedOperationException If the region is a partitioned region
    */
   CacheStatistics getStatistics() throws StatisticsDisabledException;
 
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegion.java b/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegion.java
index d2941d8..bfe605d 100755
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegion.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/PartitionedRegion.java
@@ -1857,13 +1857,55 @@ public class PartitionedRegion extends LocalRegion
     throw new UnsupportedOperationException();
   }
 
-  /**
-   * @since GemFire 5.0
-   * @throws UnsupportedOperationException OVERRIDES
-   */
   @Override
-  public CacheStatistics getStatistics() {
-    throw new UnsupportedOperationException();
+  public long getLastModifiedTime() {
+    if (!this.canStoreDataLocally()) {
+      return 0;
+    }
+    Set<BucketRegion> buckets = this.dataStore.getAllLocalBucketRegions();
+    long lastModifiedTime =
+        buckets.stream().map(x -> x.getLastModifiedTime()).reduce(0L, (a, b) -> a > b ? a : b);
+    return lastModifiedTime;
+  }
+
+  @Override
+  public long getLastAccessedTime() {
+    if (!this.canStoreDataLocally()) {
+      return 0;
+    }
+    Set<BucketRegion> buckets = this.dataStore.getAllLocalBucketRegions();
+    long lastAccessedTime =
+        buckets.stream().map(x -> x.getLastAccessedTime()).reduce(0L, (a, b) -> a > b ? a : b);
+    return lastAccessedTime;
+  }
+
+  @Override
+  public long getMissCount() {
+    if (!this.canStoreDataLocally()) {
+      return 0;
+    }
+    Set<BucketRegion> buckets = this.dataStore.getAllLocalBucketRegions();
+    return buckets.stream().map(x -> x.getMissCount()).reduce(0L, (a, b) -> a + b);
+  }
+
+  @Override
+  public long getHitCount() {
+    if (!this.canStoreDataLocally()) {
+      return 0;
+    }
+    Set<BucketRegion> buckets = this.dataStore.getAllLocalBucketRegions();
+    return buckets.stream().map(x -> x.getHitCount()).reduce(0L, (a, b) -> a + b);
+  }
+
+  @Override
+  public void resetCounts() {
+    if (!this.canStoreDataLocally()) {
+      return;
+    }
+    Set<BucketRegion> buckets = this.dataStore.getAllLocalBucketRegions();
+    for (BucketRegion bucket : buckets) {
+      bucket.resetCounts();
+    }
   }
 
   /**
diff --git a/geode-dunit/src/main/java/org/apache/geode/test/dunit/cache/internal/JUnit4CacheTestCase.java b/geode-dunit/src/main/java/org/apache/geode/test/dunit/cache/internal/JUnit4CacheTestCase.java
index 9a77bf2..954af45 100644
--- a/geode-dunit/src/main/java/org/apache/geode/test/dunit/cache/internal/JUnit4CacheTestCase.java
+++ b/geode-dunit/src/main/java/org/apache/geode/test/dunit/cache/internal/JUnit4CacheTestCase.java
@@ -32,6 +32,7 @@ import org.apache.geode.cache.Cache;
 import org.apache.geode.cache.CacheException;
 import org.apache.geode.cache.CacheFactory;
 import org.apache.geode.cache.CacheTransactionManager;
+import org.apache.geode.cache.DataPolicy;
 import org.apache.geode.cache.ExpirationAttributes;
 import org.apache.geode.cache.Region;
 import org.apache.geode.cache.RegionAttributes;
@@ -399,6 +400,29 @@ public abstract class JUnit4CacheTestCase extends JUnit4DistributedTestCase
     return createRegion(name, "root", attributes);
   }
 
+  public final <K, V> Region<K, V> createPartitionedRegion(final String rootName,
+      final RegionAttributes<K, V> attributes) throws CacheException {
+    Region<K, V> root = getRootRegion(rootName);
+    if (root == null) {
+      // don't put listeners on root region
+      AttributesFactory<K, V> attributesFactory = new AttributesFactory<>(attributes);
+      ExpirationAttributes expiration = ExpirationAttributes.DEFAULT;
+
+      attributesFactory.setCacheLoader(null);
+      attributesFactory.setCacheWriter(null);
+      attributesFactory.setPoolName(null);
+      attributesFactory.setDataPolicy(DataPolicy.PARTITION);
+      attributesFactory.setRegionTimeToLive(expiration);
+      attributesFactory.setEntryTimeToLive(expiration);
+      attributesFactory.setRegionIdleTimeout(expiration);
+      attributesFactory.setEntryIdleTimeout(expiration);
+
+      RegionAttributes<K, V> rootAttrs = attributesFactory.create();
+      root = createRootRegion(rootName, rootAttrs);
+    }
+    return root;
+  }
+
   public final <K, V> Region<K, V> createRegion(final String name, final String rootName,
       final RegionAttributes<K, V> attributes) throws CacheException {
     Region<K, V> root = getRootRegion(rootName);