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

alberto 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 a350ed2  GEODE-10087: Enhance off-heap fragmentation visibility. 
(#7407)
a350ed2 is described below

commit a350ed22d912bee409e62fda3770dba91631d7a3
Author: Alberto Gomez <[email protected]>
AuthorDate: Wed Mar 30 13:26:07 2022 +0200

    GEODE-10087: Enhance off-heap fragmentation visibility. (#7407)
    
    * GEODE-10087: Enhance off-heap fragmentation visibility.
    
    As per RFC 
https://cwiki.apache.org/confluence/display/GEODE/Enhance+Off-heap+memory+fragmentation+visibility
    Geode's off-heap fragmentation visibility has been improved
    by adding a new stat: freedChunks as well as
    by the periodic update of the largestFragment stat.
    
    The new one stat will also be periodically updated
    with a default frequency of one hour that can be changed by the
    update-off-heap-stats-frequency-ms system property.
    
    Besides, the new stat and the largestFragment stat
    will be published via JMX.
    
    * GEODE-10087: Update after review
    
    * GEODE-10087: Rename method after review
---
 .../src/main/resources/japicmp_exceptions.json     |  6 +-
 .../MemberMXBeanAttributesDistributedTest.java     | 51 ++++++++++++++-
 .../geode/internal/offheap/FreeListManager.java    | 47 +++++++++++++-
 .../internal/offheap/MemoryAllocatorImpl.java      | 45 +++++++++++---
 .../geode/internal/offheap/OffHeapMemoryStats.java |  4 ++
 .../geode/internal/offheap/OffHeapStorage.java     | 31 +++++++++-
 .../offheap/OffHeapStoredObjectAddressStack.java   | 11 ++++
 .../org/apache/geode/management/MemberMXBean.java  |  6 ++
 .../management/internal/beans/MemberMBean.java     | 12 ++++
 .../internal/beans/MemberMBeanBridge.java          | 30 +++++++++
 .../OffHeapStorageNonRuntimeStatsJUnitTest.java    | 72 ++++++++++++++++++++++
 .../internal/cli/commands/ShowMetricsCommand.java  |  7 +++
 .../internal/offheap/NullOffHeapMemoryStats.java   |  8 +++
 13 files changed, 316 insertions(+), 14 deletions(-)

diff --git a/buildSrc/src/main/resources/japicmp_exceptions.json 
b/buildSrc/src/main/resources/japicmp_exceptions.json
index be20d4c..0fc957c 100755
--- a/buildSrc/src/main/resources/japicmp_exceptions.json
+++ b/buildSrc/src/main/resources/japicmp_exceptions.json
@@ -1,9 +1,13 @@
 {
   "Class 
org.apache.geode.management.builder.GeodeClusterManagementServiceBuilder": 
"Moved internal class to fix split packages between geode-core and 
geode-management",
   "Class org.apache.geode.management.api.ClusterManagementOperation": "Fixed 
missing @Experimental annotation",
+  "Class org.apache.geode.management.MemberMXBean": "Added new stats",
   "Method 
org.apache.geode.management.api.ClusterManagementOperation.getEndpoint()": 
"Fixed missing @Experimental annotation",
   "Method 
org.apache.geode.management.api.ClusterManagementOperation.getOperator()": 
"Fixed missing @Experimental annotation",
   "Class org.apache.geode.cache.query.IndexStatistics": "Added new methods.",
   "Method 
org.apache.geode.cache.query.IndexStatistics.getNumberOfBucketIndexesLong()": 
"Added new methods.",
-  "Method 
org.apache.geode.cache.query.IndexStatistics.getReadLockCountLong()": "Added 
new methods."
+  "Method 
org.apache.geode.cache.query.IndexStatistics.getReadLockCountLong()": "Added 
new methods.",
+  "Method org.apache.geode.management.MemberMXBean.getOffHeapFragments()": 
"Added new stat",
+  "Method org.apache.geode.management.MemberMXBean.getOffHeapFreedChunks()": 
"Added new stat",
+  "Method 
org.apache.geode.management.MemberMXBean.getOffHeapLargestFragment()": "Added 
new stat"
 }
diff --git 
a/geode-core/src/distributedTest/java/org/apache/geode/management/MemberMXBeanAttributesDistributedTest.java
 
b/geode-core/src/distributedTest/java/org/apache/geode/management/MemberMXBeanAttributesDistributedTest.java
index 22cff50..b890d99 100644
--- 
a/geode-core/src/distributedTest/java/org/apache/geode/management/MemberMXBeanAttributesDistributedTest.java
+++ 
b/geode-core/src/distributedTest/java/org/apache/geode/management/MemberMXBeanAttributesDistributedTest.java
@@ -23,6 +23,7 @@ import static 
org.apache.geode.distributed.ConfigurationProperties.HTTP_SERVICE_
 import static org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER;
 import static 
org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_PORT;
 import static 
org.apache.geode.distributed.ConfigurationProperties.JMX_MANAGER_START;
+import static 
org.apache.geode.distributed.ConfigurationProperties.OFF_HEAP_MEMORY_SIZE;
 import static 
org.apache.geode.distributed.ConfigurationProperties.STATISTIC_SAMPLE_RATE;
 import static 
org.apache.geode.distributed.ConfigurationProperties.STATISTIC_SAMPLING_ENABLED;
 import static 
org.apache.geode.internal.process.ProcessUtils.identifyPidAsUnchecked;
@@ -45,6 +46,7 @@ import org.apache.geode.internal.statistics.HostStatSampler;
 import org.apache.geode.internal.statistics.SampleCollector;
 import org.apache.geode.management.internal.SystemManagementService;
 import org.apache.geode.test.dunit.rules.DistributedRestoreSystemProperties;
+import org.apache.geode.util.internal.GeodeGlossary;
 
 /**
  * Distributed tests for {@link MemberMXBean} attributes.
@@ -162,6 +164,50 @@ public class MemberMXBeanAttributesDistributedTest extends 
CacheTestCase {
     assertThat(memberMXBean.isManagerCreated()).isFalse();
   }
 
+  @Test
+  public void testOffHeapMemoryAttributes() {
+    MemberMXBean memberMXBean = getSystemManagementService().getMemberMXBean();
+    sampleStatistics();
+
+    int initialLargestFragment = (int) (((4096 * BYTES_PER_MEGABYTE) / 2) - 1);
+    assertThat(memberMXBean.getOffHeapFragments()).isEqualTo(2);
+    
assertThat(memberMXBean.getOffHeapLargestFragment()).isEqualTo(initialLargestFragment);
+    assertThat(memberMXBean.getOffHeapFreedChunks()).isEqualTo(0);
+
+    RegionFactory regionFactory =
+        getCache().createRegionFactory(PARTITION_REDUNDANT);
+    regionFactory.setConcurrencyChecksEnabled(false);
+    regionFactory.setOffHeap(true);
+
+    regionFactory.create("testPRRegion1");
+    Region region1 = getCache().getRegion(SEPARATOR + "testPRRegion1");
+
+    // fill first fragment
+    int hugeAllocations = 100;
+    for (int i = 0; i < hugeAllocations; i++) {
+      region1.put(i + 10, new byte[initialLargestFragment / hugeAllocations]);
+    }
+    for (int i = 0; i < hugeAllocations; i++) {
+      region1.remove(i + 10);
+    }
+
+    region1.put(1, new byte[100]);
+    region1.remove(1);
+    // Release the memory of the object so that the next allocation reuses the 
freed chunk
+    region1.put(2, new byte[100]);
+    region1.remove(2);
+    region1.put(3, new byte[200]);
+    region1.remove(3);
+
+    sampleStatistics();
+
+    assertThat(memberMXBean.getOffHeapFragments()).isEqualTo(2);
+    await().untilAsserted(() -> 
assertThat(memberMXBean.getOffHeapLargestFragment())
+        .isLessThan(initialLargestFragment));
+    await().untilAsserted(
+        () -> 
assertThat(memberMXBean.getOffHeapFreedChunks()).isEqualTo(hugeAllocations + 
2));
+  }
+
   @Override
   public Properties getDistributedSystemProperties() {
     Properties props = new Properties();
@@ -174,7 +220,10 @@ public class MemberMXBeanAttributesDistributedTest extends 
CacheTestCase {
   }
 
   private void createMember() {
-    getCache(getDistributedSystemProperties());
+    Properties props = getDistributedSystemProperties();
+    props.setProperty(OFF_HEAP_MEMORY_SIZE, "4096");
+    System.setProperty(GeodeGlossary.GEMFIRE_PREFIX + 
"off-heap-stats-update-frequency-ms", "1000");
+    getCache(props);
   }
 
   private void createManager() {
diff --git 
a/geode-core/src/main/java/org/apache/geode/internal/offheap/FreeListManager.java
 
b/geode-core/src/main/java/org/apache/geode/internal/offheap/FreeListManager.java
index c7154be..04c84f5 100644
--- 
a/geode-core/src/main/java/org/apache/geode/internal/offheap/FreeListManager.java
+++ 
b/geode-core/src/main/java/org/apache/geode/internal/offheap/FreeListManager.java
@@ -252,8 +252,10 @@ public class FreeListManager {
     OffHeapMemoryStats stats = ma.getStats();
     lw.info("OutOfOffHeapMemory allocating size of " + chunkSize + ". 
allocated="
         + allocatedSize.get() + " defragmentations=" + 
defragmentationCount.get()
-        + " objects=" + stats.getObjects() + " free=" + stats.getFreeMemory() 
+ " fragments="
-        + stats.getFragments() + " largestFragment=" + 
stats.getLargestFragment()
+        + " objects=" + stats.getObjects() + " free=" + stats.getFreeMemory()
+        + " freedChunks=" + stats.getFreedChunks()
+        + " fragments=" + stats.getFragments()
+        + " largestFragment=" + stats.getLargestFragment()
         + " fragmentation=" + stats.getFragmentation());
     logFragmentState(lw);
     logTinyState(lw);
@@ -518,10 +520,51 @@ public class FreeListManager {
     ma.getStats().setLargestFragment(largestFragment);
     ma.getStats().setFragments(tmp.size());
     ma.getStats().setFragmentation(getFragmentation());
+    ma.getStats().setFreedChunks(0);
 
     return result;
   }
 
+  public void updateNonRealTimeStats() {
+    ma.getStats().setLargestFragment(largestFragmentSize());
+    ma.getStats().setFreedChunks(getFreedChunks());
+  }
+
+  public int getFreedChunks() {
+    int elementCountFromTinyFreeLists =
+        getElementCountFromTinyFreeLists();
+    int elementCountFromHugeFreeLists =
+        getElementCountFromHugeFreeLists();
+
+    return elementCountFromTinyFreeLists + elementCountFromHugeFreeLists;
+  }
+
+  private int getElementCountFromTinyFreeLists() {
+    int fragmentCount = 0;
+    for (int i = 0; i < tinyFreeLists.length(); i++) {
+      OffHeapStoredObjectAddressStack cl = tinyFreeLists.get(i);
+      if (cl != null) {
+        fragmentCount += cl.size();
+      }
+    }
+    return fragmentCount;
+  }
+
+  private int getElementCountFromHugeFreeLists() {
+    return hugeChunkSet.size();
+  }
+
+  private int largestFragmentSize() {
+    int largestFreeSpaceFromFragments = 0;
+    for (Fragment f : fragmentList) {
+      int fragmentFreeSpace = f.freeSpace();
+      if (fragmentFreeSpace > largestFreeSpaceFromFragments) {
+        largestFreeSpaceFromFragments = fragmentFreeSpace;
+      }
+    }
+    return largestFreeSpaceFromFragments;
+  }
+
   /**
    * Unit tests override this method to get better test coverage
    */
diff --git 
a/geode-core/src/main/java/org/apache/geode/internal/offheap/MemoryAllocatorImpl.java
 
b/geode-core/src/main/java/org/apache/geode/internal/offheap/MemoryAllocatorImpl.java
index 54dd458..4e433e4 100644
--- 
a/geode-core/src/main/java/org/apache/geode/internal/offheap/MemoryAllocatorImpl.java
+++ 
b/geode-core/src/main/java/org/apache/geode/internal/offheap/MemoryAllocatorImpl.java
@@ -20,6 +20,9 @@ import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.logging.log4j.Logger;
@@ -33,8 +36,10 @@ import org.apache.geode.internal.cache.InternalRegion;
 import org.apache.geode.internal.cache.PartitionedRegion;
 import org.apache.geode.internal.cache.PartitionedRegionDataStore;
 import org.apache.geode.internal.cache.RegionEntry;
+import org.apache.geode.internal.lang.SystemProperty;
 import org.apache.geode.internal.offheap.annotations.OffHeapIdentifier;
 import org.apache.geode.internal.offheap.annotations.Unretained;
+import org.apache.geode.logging.internal.executors.LoggingExecutors;
 import org.apache.geode.logging.internal.log4j.api.LogService;
 import org.apache.geode.util.internal.GeodeGlossary;
 
@@ -55,6 +60,14 @@ public class MemoryAllocatorImpl implements MemoryAllocator {
   public static final String FREE_OFF_HEAP_MEMORY_PROPERTY =
       GeodeGlossary.GEMFIRE_PREFIX + "free-off-heap-memory";
 
+  public static final int UPDATE_OFF_HEAP_STATS_FREQUENCY_MS =
+      SystemProperty.getProductIntegerProperty(
+          "off-heap-stats-update-frequency-ms").orElse(3600000);
+
+  private final ScheduledExecutorService updateNonRealTimeStatsExecutor;
+
+  private final ScheduledFuture<?> updateNonRealTimeStatsFuture;
+
   private volatile OffHeapMemoryStats stats;
 
   private volatile OutOfOffHeapMemoryListener ooohml;
@@ -84,14 +97,21 @@ public class MemoryAllocatorImpl implements MemoryAllocator 
{
       Boolean.getBoolean(GeodeGlossary.GEMFIRE_PREFIX + 
"OFF_HEAP_DO_EXPENSIVE_VALIDATION");
 
   public static MemoryAllocator create(OutOfOffHeapMemoryListener ooohml, 
OffHeapMemoryStats stats,
+      int slabCount, long offHeapMemorySize, long maxSlabSize,
+      int updateOffHeapStatsFrequencyMs) {
+    return create(ooohml, stats, slabCount, offHeapMemorySize, maxSlabSize, 
null,
+        SlabImpl::new, updateOffHeapStatsFrequencyMs);
+  }
+
+  public static MemoryAllocator create(OutOfOffHeapMemoryListener ooohml, 
OffHeapMemoryStats stats,
       int slabCount, long offHeapMemorySize, long maxSlabSize) {
     return create(ooohml, stats, slabCount, offHeapMemorySize, maxSlabSize, 
null,
-        SlabImpl::new);
+        SlabImpl::new, UPDATE_OFF_HEAP_STATS_FREQUENCY_MS);
   }
 
   private static MemoryAllocatorImpl create(OutOfOffHeapMemoryListener ooohml,
       OffHeapMemoryStats stats, int slabCount, long offHeapMemorySize, long 
maxSlabSize,
-      Slab[] slabs, SlabFactory slabFactory) {
+      Slab[] slabs, SlabFactory slabFactory, int 
updateOffHeapStatsFrequencyMs) {
     MemoryAllocatorImpl result = singleton;
     boolean created = false;
     try {
@@ -135,7 +155,7 @@ public class MemoryAllocatorImpl implements MemoryAllocator 
{
           }
         }
 
-        result = new MemoryAllocatorImpl(ooohml, stats, slabs);
+        result = new MemoryAllocatorImpl(ooohml, stats, slabs, 
updateOffHeapStatsFrequencyMs);
         singleton = result;
         LifecycleListener.invokeAfterCreate(result);
         created = true;
@@ -156,7 +176,8 @@ public class MemoryAllocatorImpl implements MemoryAllocator 
{
   static MemoryAllocatorImpl createForUnitTest(OutOfOffHeapMemoryListener 
ooohml,
       OffHeapMemoryStats stats, int slabCount, long offHeapMemorySize, long 
maxSlabSize,
       SlabFactory memChunkFactory) {
-    return create(ooohml, stats, slabCount, offHeapMemorySize, maxSlabSize, 
null, memChunkFactory);
+    return create(ooohml, stats, slabCount, offHeapMemorySize, maxSlabSize, 
null, memChunkFactory,
+        UPDATE_OFF_HEAP_STATS_FREQUENCY_MS);
   }
 
   public static MemoryAllocatorImpl 
createForUnitTest(OutOfOffHeapMemoryListener oooml,
@@ -174,7 +195,8 @@ public class MemoryAllocatorImpl implements MemoryAllocator 
{
         }
       }
     }
-    return create(oooml, stats, slabCount, offHeapMemorySize, maxSlabSize, 
slabs, null);
+    return create(oooml, stats, slabCount, offHeapMemorySize, maxSlabSize, 
slabs, null,
+        UPDATE_OFF_HEAP_STATS_FREQUENCY_MS);
   }
 
 
@@ -200,11 +222,11 @@ public class MemoryAllocatorImpl implements 
MemoryAllocator {
   }
 
   private MemoryAllocatorImpl(final OutOfOffHeapMemoryListener oooml,
-      final OffHeapMemoryStats stats, final Slab[] slabs) {
+      final OffHeapMemoryStats stats, final Slab[] slabs,
+      int updateOffHeapStatsFrequencyMs) {
     if (oooml == null) {
       throw new IllegalArgumentException("OutOfOffHeapMemoryListener is null");
     }
-
     ooohml = oooml;
     this.stats = stats;
 
@@ -216,6 +238,12 @@ public class MemoryAllocatorImpl implements 
MemoryAllocator {
 
     this.stats.incMaxMemory(freeList.getTotalMemory());
     this.stats.incFreeMemory(freeList.getTotalMemory());
+
+    updateNonRealTimeStatsExecutor =
+        LoggingExecutors.newSingleThreadScheduledExecutor("Update Freelist 
Stats thread");
+    updateNonRealTimeStatsFuture =
+        
updateNonRealTimeStatsExecutor.scheduleAtFixedRate(freeList::updateNonRealTimeStats,
 0,
+            updateOffHeapStatsFrequencyMs, TimeUnit.MILLISECONDS);
   }
 
   public List<OffHeapStoredObject> getLostChunks(InternalCache cache) {
@@ -379,6 +407,8 @@ public class MemoryAllocatorImpl implements MemoryAllocator 
{
     if (setClosed()) {
       freeList.freeSlabs();
       stats.close();
+      updateNonRealTimeStatsFuture.cancel(true);
+      updateNonRealTimeStatsExecutor.shutdown();
       singleton = null;
     }
   }
@@ -510,5 +540,4 @@ public class MemoryAllocatorImpl implements MemoryAllocator 
{
   public MemoryInspector getMemoryInspector() {
     return memoryInspector;
   }
-
 }
diff --git 
a/geode-core/src/main/java/org/apache/geode/internal/offheap/OffHeapMemoryStats.java
 
b/geode-core/src/main/java/org/apache/geode/internal/offheap/OffHeapMemoryStats.java
index 08e716d..b539798 100755
--- 
a/geode-core/src/main/java/org/apache/geode/internal/offheap/OffHeapMemoryStats.java
+++ 
b/geode-core/src/main/java/org/apache/geode/internal/offheap/OffHeapMemoryStats.java
@@ -43,6 +43,8 @@ public interface OffHeapMemoryStats {
 
   void setFragmentation(int value);
 
+  void setFreedChunks(long value);
+
   long getFreeMemory();
 
   long getMaxMemory();
@@ -59,6 +61,8 @@ public interface OffHeapMemoryStats {
 
   long getFragments();
 
+  long getFreedChunks();
+
   int getLargestFragment();
 
   int getFragmentation();
diff --git 
a/geode-core/src/main/java/org/apache/geode/internal/offheap/OffHeapStorage.java
 
b/geode-core/src/main/java/org/apache/geode/internal/offheap/OffHeapStorage.java
index a5ef246..755fef9 100755
--- 
a/geode-core/src/main/java/org/apache/geode/internal/offheap/OffHeapStorage.java
+++ 
b/geode-core/src/main/java/org/apache/geode/internal/offheap/OffHeapStorage.java
@@ -61,6 +61,7 @@ public class OffHeapStorage implements OffHeapMemoryStats {
   private static final int defragmentationTimeId;
   private static final int fragmentationId;
   private static final int defragmentationsInProgressId;
+  private static final int freedChunksId;
   // NOTE!!!! When adding new stats make sure and update the initialize method 
on this class
 
   // creates and registers the statistics type
@@ -78,10 +79,12 @@ public class OffHeapStorage implements OffHeapMemoryStats {
         "The percentage of off-heap free memory that is fragmented.  Updated 
every time a defragmentation is performed.";
     final String fragmentsDesc =
         "The number of fragments of free off-heap memory. Updated every time a 
defragmentation is done.";
+    final String freedChunksDesc =
+        "The number of off-heap memory chunks that have been freed since the 
last defragmentation and that are not currently being used to store an object 
in the off-heap memory space. Updated every time a defragmentation is done and 
periodically according to off-heap-stats-update-frequency-ms system property 
(default 3600 seconds).";
     final String freeMemoryDesc =
         "The amount of off-heap memory, in bytes, that is not being used.";
     final String largestFragmentDesc =
-        "The largest fragment of memory found by the last defragmentation of 
off heap memory. Updated every time a defragmentation is done.";
+        "The largest fragment of off-heap memory that can be used to allocate 
an object. Updated every time a defragmentation is done and periodically 
according to off-heap-stats-update-frequency-ms system property (default 3600 
seconds)";
     final String objectsDesc = "The number of objects stored in off-heap 
memory.";
     final String readsDesc =
         "The total number of reads of off-heap memory. Only reads of a full 
object increment this statistic. If only a part of the object is read this 
statistic is not incremented.";
@@ -94,6 +97,7 @@ public class OffHeapStorage implements OffHeapMemoryStats {
     final String defragmentationTime = "defragmentationTime";
     final String fragmentation = "fragmentation";
     final String fragments = "fragments";
+    final String freedChunks = "freedChunks";
     final String freeMemory = "freeMemory";
     final String largestFragment = "largestFragment";
     final String objects = "objects";
@@ -108,6 +112,7 @@ public class OffHeapStorage implements OffHeapMemoryStats {
             f.createLongCounter(defragmentationTime, defragmentationTimeDesc, 
"nanoseconds", false),
             f.createIntGauge(fragmentation, fragmentationDesc, "percentage"),
             f.createLongGauge(fragments, fragmentsDesc, "fragments"),
+            f.createLongGauge(freedChunks, freedChunksDesc, "freedChunks"),
             f.createLongGauge(freeMemory, freeMemoryDesc, "bytes"),
             f.createIntGauge(largestFragment, largestFragmentDesc, "bytes"),
             f.createIntGauge(objects, objectsDesc, "objects"),
@@ -116,6 +121,7 @@ public class OffHeapStorage implements OffHeapMemoryStats {
 
     usedMemoryId = statsType.nameToId(usedMemory);
     defragmentationId = statsType.nameToId(defragmentations);
+    freedChunksId = statsType.nameToId(freedChunks);
     defragmentationsInProgressId = 
statsType.nameToId(defragmentationsInProgress);
     defragmentationTimeId = statsType.nameToId(defragmentationTime);
     fragmentationId = statsType.nameToId(fragmentation);
@@ -220,7 +226,6 @@ public class OffHeapStorage implements OffHeapMemoryStats {
       OutOfOffHeapMemoryListener ooohml) {
     final OffHeapMemoryStats stats = new OffHeapStorage(sf);
 
-    // determine off-heap and slab sizes
     final long maxSlabSize = calcMaxSlabSize(offHeapMemorySize);
 
     final int slabCount = calcSlabCount(maxSlabSize, offHeapMemorySize);
@@ -228,6 +233,18 @@ public class OffHeapStorage implements OffHeapMemoryStats {
     return MemoryAllocatorImpl.create(ooohml, stats, slabCount, 
offHeapMemorySize, maxSlabSize);
   }
 
+  static MemoryAllocator basicCreateOffHeapStorage(StatisticsFactory sf, long 
offHeapMemorySize,
+      OutOfOffHeapMemoryListener ooohml, int updateOffHeapStatsFrequencyMs) {
+    final OffHeapMemoryStats stats = new OffHeapStorage(sf);
+
+    final long maxSlabSize = calcMaxSlabSize(offHeapMemorySize);
+
+    final int slabCount = calcSlabCount(maxSlabSize, offHeapMemorySize);
+
+    return MemoryAllocatorImpl.create(ooohml, stats, slabCount, 
offHeapMemorySize, maxSlabSize,
+        updateOffHeapStatsFrequencyMs);
+  }
+
   private static final long MAX_SLAB_SIZE = Integer.MAX_VALUE;
   static final long MIN_SLAB_SIZE = 1024;
 
@@ -343,6 +360,11 @@ public class OffHeapStorage implements OffHeapMemoryStats {
   }
 
   @Override
+  public long getFreedChunks() {
+    return this.stats.getLong(freedChunksId);
+  }
+
+  @Override
   public void setLargestFragment(int value) {
     stats.setInt(largestFragmentId, value);
   }
@@ -383,6 +405,11 @@ public class OffHeapStorage implements OffHeapMemoryStats {
   }
 
   @Override
+  public void setFreedChunks(long value) {
+    stats.setLong(freedChunksId, value);
+  }
+
+  @Override
   public int getFragmentation() {
     return stats.getInt(fragmentationId);
   }
diff --git 
a/geode-core/src/main/java/org/apache/geode/internal/offheap/OffHeapStoredObjectAddressStack.java
 
b/geode-core/src/main/java/org/apache/geode/internal/offheap/OffHeapStoredObjectAddressStack.java
index ae4d31c..86efa42 100644
--- 
a/geode-core/src/main/java/org/apache/geode/internal/offheap/OffHeapStoredObjectAddressStack.java
+++ 
b/geode-core/src/main/java/org/apache/geode/internal/offheap/OffHeapStoredObjectAddressStack.java
@@ -27,6 +27,9 @@ public class OffHeapStoredObjectAddressStack implements 
LongStack {
   // Ok to read without sync but must be synced on write
   private volatile long topAddr;
 
+  // Ok to read without sync but must be synced on write
+  private volatile int size = 0;
+
   public OffHeapStoredObjectAddressStack(long addr) {
     if (addr != 0L) {
       MemoryAllocatorImpl.validateAddress(addr);
@@ -48,9 +51,14 @@ public class OffHeapStoredObjectAddressStack implements 
LongStack {
     synchronized (this) {
       OffHeapStoredObject.setNext(e, topAddr);
       topAddr = e;
+      size++;
     }
   }
 
+  public int size() {
+    return size;
+  }
+
   @Override
   public long poll() {
     long result;
@@ -58,7 +66,9 @@ public class OffHeapStoredObjectAddressStack implements 
LongStack {
       result = topAddr;
       if (result != 0L) {
         topAddr = OffHeapStoredObject.getNext(result);
+        size--;
       }
+
     }
     return result;
   }
@@ -81,6 +91,7 @@ public class OffHeapStoredObjectAddressStack implements 
LongStack {
       if (result != 0L) {
         topAddr = 0L;
       }
+      size = 0;
     }
     return result;
   }
diff --git 
a/geode-core/src/main/java/org/apache/geode/management/MemberMXBean.java 
b/geode-core/src/main/java/org/apache/geode/management/MemberMXBean.java
index f81a272..c077cad 100644
--- a/geode-core/src/main/java/org/apache/geode/management/MemberMXBean.java
+++ b/geode-core/src/main/java/org/apache/geode/management/MemberMXBean.java
@@ -877,6 +877,12 @@ public interface MemberMXBean {
    */
   int getOffHeapFragmentation();
 
+  long getOffHeapFragments();
+
+  long getOffHeapFreedChunks();
+
+  int getOffHeapLargestFragment();
+
   /**
    * Returns the total time spent compacting in milliseconds.
    */
diff --git 
a/geode-core/src/main/java/org/apache/geode/management/internal/beans/MemberMBean.java
 
b/geode-core/src/main/java/org/apache/geode/management/internal/beans/MemberMBean.java
index fab63c0..045e6b3 100644
--- 
a/geode-core/src/main/java/org/apache/geode/management/internal/beans/MemberMBean.java
+++ 
b/geode-core/src/main/java/org/apache/geode/management/internal/beans/MemberMBean.java
@@ -696,6 +696,18 @@ public class MemberMBean extends 
NotificationBroadcasterSupport implements Membe
     return bridge.getOffHeapFragmentation();
   }
 
+  public long getOffHeapFragments() {
+    return bridge.getOffHeapFragments();
+  }
+
+  public long getOffHeapFreedChunks() {
+    return bridge.getOffHeapFreedChunks();
+  }
+
+  public int getOffHeapLargestFragment() {
+    return bridge.getOffHeapLargestFragment();
+  }
+
   @Override
   public long getOffHeapCompactionTime() {
     return bridge.getOffHeapCompactionTime();
diff --git 
a/geode-core/src/main/java/org/apache/geode/management/internal/beans/MemberMBeanBridge.java
 
b/geode-core/src/main/java/org/apache/geode/management/internal/beans/MemberMBeanBridge.java
index 8128e8e..8746e66 100644
--- 
a/geode-core/src/main/java/org/apache/geode/management/internal/beans/MemberMBeanBridge.java
+++ 
b/geode-core/src/main/java/org/apache/geode/management/internal/beans/MemberMBeanBridge.java
@@ -1352,6 +1352,36 @@ public class MemberMBeanBridge {
     return 0;
   }
 
+  long getOffHeapFragments() {
+    OffHeapMemoryStats stats = getOffHeapStats();
+
+    if (null != stats) {
+      return stats.getFragments();
+    }
+
+    return 0;
+  }
+
+  long getOffHeapFreedChunks() {
+    OffHeapMemoryStats stats = getOffHeapStats();
+
+    if (null != stats) {
+      return stats.getFreedChunks();
+    }
+
+    return 0;
+  }
+
+  int getOffHeapLargestFragment() {
+    OffHeapMemoryStats stats = getOffHeapStats();
+
+    if (null != stats) {
+      return stats.getLargestFragment();
+    }
+
+    return 0;
+  }
+
   long getOffHeapCompactionTime() {
     OffHeapMemoryStats stats = getOffHeapStats();
 
diff --git 
a/geode-core/src/test/java/org/apache/geode/internal/offheap/OffHeapStorageNonRuntimeStatsJUnitTest.java
 
b/geode-core/src/test/java/org/apache/geode/internal/offheap/OffHeapStorageNonRuntimeStatsJUnitTest.java
new file mode 100755
index 0000000..2aecc7b
--- /dev/null
+++ 
b/geode-core/src/test/java/org/apache/geode/internal/offheap/OffHeapStorageNonRuntimeStatsJUnitTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.offheap;
+
+import static org.apache.geode.test.awaitility.GeodeAwaitility.await;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Test;
+
+import org.apache.geode.StatisticsFactory;
+import org.apache.geode.internal.statistics.LocalStatisticsFactory;
+
+public class OffHeapStorageNonRuntimeStatsJUnitTest {
+
+  @Test
+  public void testUpdateNonRealTimeOffHeapStorageStats() {
+    StatisticsFactory localStatsFactory = new LocalStatisticsFactory(null);
+    OutOfOffHeapMemoryListener ooohml = mock(OutOfOffHeapMemoryListener.class);
+    MemoryAllocator ma =
+        OffHeapStorage.basicCreateOffHeapStorage(localStatsFactory, 1024 * 
1024, ooohml, 100);
+    try {
+      OffHeapMemoryStats stats = ma.getStats();
+
+      assertThat(stats.getFreeMemory()).isEqualTo(1024 * 1024);
+      assertThat(stats.getMaxMemory()).isEqualTo(1024 * 1024);
+      assertThat(stats.getUsedMemory()).isEqualTo(0);
+      assertThat(stats.getFragments()).isEqualTo(1);
+      assertThat(stats.getLargestFragment()).isEqualTo(1024 * 1024);
+      assertThat(stats.getFreedChunks()).isEqualTo(0);
+
+      FreeListManager freeListManager = ((MemoryAllocatorImpl) 
ma).getFreeListManager();
+      StoredObject storedObject1 = ma.allocate(10);
+      // Release the memory of the object so that the next allocation reuses 
the freed chunk
+      ReferenceCounter.release(storedObject1.getAddress(), freeListManager);
+      StoredObject storedObject2 = ma.allocate(10);
+      StoredObject storedObject3 = ma.allocate(10);
+
+      assertThat(stats.getFreeMemory()).isLessThan(1024 * 1024);
+      assertThat(stats.getUsedMemory()).isGreaterThan(0);
+      await().untilAsserted(() -> 
assertThat(stats.getLargestFragment()).isLessThan(1024 * 1024));
+      await().untilAsserted(() -> 
assertThat(stats.getFreedChunks()).isEqualTo(0));
+
+      ReferenceCounter.release(storedObject3.getAddress(), freeListManager);
+      ReferenceCounter.release(storedObject2.getAddress(), freeListManager);
+
+      assertThat(stats.getFreeMemory()).isEqualTo(1024 * 1024);
+      assertThat(stats.getUsedMemory()).isEqualTo(0);
+      await().untilAsserted(() -> 
assertThat(stats.getLargestFragment()).isLessThan(1024 * 1024));
+      await().untilAsserted(() -> 
assertThat(stats.getFreedChunks()).isEqualTo(2));
+    } finally {
+      System.setProperty(MemoryAllocatorImpl.FREE_OFF_HEAP_MEMORY_PROPERTY, 
"true");
+      try {
+        ma.close();
+      } finally {
+        
System.clearProperty(MemoryAllocatorImpl.FREE_OFF_HEAP_MEMORY_PROPERTY);
+      }
+    }
+  }
+}
diff --git 
a/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/ShowMetricsCommand.java
 
b/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/ShowMetricsCommand.java
index 8ad698d..ce269cb 100644
--- 
a/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/ShowMetricsCommand.java
+++ 
b/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/ShowMetricsCommand.java
@@ -502,6 +502,13 @@ public class ShowMetricsCommand extends GfshCommand {
       writeToTableAndCsv(metricsTable, "", "objects", 
memberMxBean.getOffHeapObjects(), csvBuilder);
       writeToTableAndCsv(metricsTable, "", "fragmentation", 
memberMxBean.getOffHeapFragmentation(),
           csvBuilder);
+      writeToTableAndCsv(metricsTable, "", "largestFragment",
+          memberMxBean.getOffHeapLargestFragment(),
+          csvBuilder);
+      writeToTableAndCsv(metricsTable, "", "fragments", 
memberMxBean.getOffHeapFragments(),
+          csvBuilder);
+      writeToTableAndCsv(metricsTable, "", "freedChunks", 
memberMxBean.getOffHeapFreedChunks(),
+          csvBuilder);
       writeToTableAndCsv(metricsTable, "", "compactionTime",
           memberMxBean.getOffHeapCompactionTime(), csvBuilder);
     }
diff --git 
a/geode-junit/src/main/java/org/apache/geode/internal/offheap/NullOffHeapMemoryStats.java
 
b/geode-junit/src/main/java/org/apache/geode/internal/offheap/NullOffHeapMemoryStats.java
index bf17f87..7b2a134 100755
--- 
a/geode-junit/src/main/java/org/apache/geode/internal/offheap/NullOffHeapMemoryStats.java
+++ 
b/geode-junit/src/main/java/org/apache/geode/internal/offheap/NullOffHeapMemoryStats.java
@@ -88,6 +88,11 @@ public class NullOffHeapMemoryStats implements 
OffHeapMemoryStats {
   }
 
   @Override
+  public long getFreedChunks() {
+    return 0;
+  }
+
+  @Override
   public void setLargestFragment(int value) {}
 
   @Override
@@ -107,6 +112,9 @@ public class NullOffHeapMemoryStats implements 
OffHeapMemoryStats {
   public void setFragmentation(int value) {}
 
   @Override
+  public void setFreedChunks(long value) {}
+
+  @Override
   public int getFragmentation() {
     return 0;
   }

Reply via email to