From 7143e8593c9fd9169a8107a8a1acd9ca56dac412 Mon Sep 17 00:00:00 2001
From: Nikita Koksharov <nkoksharov@redisson.pro>
Date: Thu, 8 Nov 2018 19:04:31 +0300
Subject: [PATCH] Feature - RObject.getSizeInMemory method added #1726

---
 .../org/redisson/RedissonBloomFilter.java     |  6 +++++
 .../RedissonBoundedBlockingQueue.java         |  6 +++++
 .../org/redisson/RedissonDelayedQueue.java    |  6 +++++
 .../java/org/redisson/RedissonFairLock.java   |  7 ++++++
 .../redisson/RedissonListMultimapCache.java   |  5 ++++
 .../redisson/RedissonListMultimapValues.java  |  6 +++++
 .../org/redisson/RedissonLocalCachedMap.java  |  5 ++++
 .../java/org/redisson/RedissonMapCache.java   |  6 +++++
 .../org/redisson/RedissonMultimapCache.java   |  6 +++++
 .../java/org/redisson/RedissonObject.java     | 24 +++++++++++++++++++
 .../RedissonPermitExpirableSemaphore.java     |  7 ++++++
 .../redisson/RedissonSetMultimapCache.java    |  5 ++++
 .../redisson/RedissonSetMultimapValues.java   |  5 ++++
 .../main/java/org/redisson/api/RObject.java   |  7 ++++++
 .../java/org/redisson/api/RObjectAsync.java   |  7 ++++++
 .../client/protocol/RedisCommands.java        |  1 +
 .../java/org/redisson/RedissonBucketTest.java |  7 ++++++
 .../org/redisson/RedissonMapCacheTest.java    | 10 ++++++++
 18 files changed, 126 insertions(+)

diff --git a/redisson/src/main/java/org/redisson/RedissonBloomFilter.java b/redisson/src/main/java/org/redisson/RedissonBloomFilter.java
index 590a5bb7b..76dc5345c 100644
--- a/redisson/src/main/java/org/redisson/RedissonBloomFilter.java
+++ b/redisson/src/main/java/org/redisson/RedissonBloomFilter.java
@@ -220,6 +220,12 @@ public class RedissonBloomFilter<T> extends RedissonExpirable implements RBloomF
         return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), configName);
     }
 
+    @Override
+    public RFuture<Long> sizeInMemoryAsync() {
+        List<Object> keys = Arrays.<Object>asList(getName(), configName);
+        return super.sizeInMemoryAsync(keys);
+    }
+    
     private void readConfig() {
         RFuture<Map<String, String>> future = commandExecutor.readAsync(configName, StringCodec.INSTANCE,
                 new RedisCommand<Map<Object, Object>>("HGETALL", new ObjectMapReplayDecoder()), configName);
diff --git a/redisson/src/main/java/org/redisson/RedissonBoundedBlockingQueue.java b/redisson/src/main/java/org/redisson/RedissonBoundedBlockingQueue.java
index 9f8584393..01ed0700c 100644
--- a/redisson/src/main/java/org/redisson/RedissonBoundedBlockingQueue.java
+++ b/redisson/src/main/java/org/redisson/RedissonBoundedBlockingQueue.java
@@ -370,6 +370,12 @@ public class RedissonBoundedBlockingQueue<V> extends RedissonQueue<V> implements
     public RFuture<Boolean> deleteAsync() {
         return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), getSemaphoreName());
     }
+    
+    @Override
+    public RFuture<Long> sizeInMemoryAsync() {
+        List<Object> keys = Arrays.<Object>asList(getName(), getSemaphoreName());
+        return super.sizeInMemoryAsync(keys);
+    }
 
     @Override
     public RFuture<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
diff --git a/redisson/src/main/java/org/redisson/RedissonDelayedQueue.java b/redisson/src/main/java/org/redisson/RedissonDelayedQueue.java
index 3f12023bf..73077d56a 100644
--- a/redisson/src/main/java/org/redisson/RedissonDelayedQueue.java
+++ b/redisson/src/main/java/org/redisson/RedissonDelayedQueue.java
@@ -401,6 +401,12 @@ public class RedissonDelayedQueue<V> extends RedissonExpirable implements RDelay
         return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, queueName, timeoutSetName);
     }
     
+    @Override
+    public RFuture<Long> sizeInMemoryAsync() {
+        List<Object> keys = Arrays.<Object>asList(queueName, timeoutSetName);
+        return super.sizeInMemoryAsync(keys);
+    }
+    
     @Override
     public RFuture<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
         return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
diff --git a/redisson/src/main/java/org/redisson/RedissonFairLock.java b/redisson/src/main/java/org/redisson/RedissonFairLock.java
index 9c5a6b0a2..407a1fdd3 100644
--- a/redisson/src/main/java/org/redisson/RedissonFairLock.java
+++ b/redisson/src/main/java/org/redisson/RedissonFairLock.java
@@ -16,6 +16,7 @@
 package org.redisson;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Condition;
 
@@ -230,6 +231,12 @@ public class RedissonFairLock extends RedissonLock implements RLock {
         return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), threadsQueueName, timeoutSetName);
     }
     
+    @Override
+    public RFuture<Long> sizeInMemoryAsync() {
+        List<Object> keys = Arrays.<Object>asList(getName(), threadsQueueName, timeoutSetName);
+        return super.sizeInMemoryAsync(keys);
+    }
+    
     @Override
     public RFuture<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
         return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
diff --git a/redisson/src/main/java/org/redisson/RedissonListMultimapCache.java b/redisson/src/main/java/org/redisson/RedissonListMultimapCache.java
index eccdef969..6c1b96554 100644
--- a/redisson/src/main/java/org/redisson/RedissonListMultimapCache.java
+++ b/redisson/src/main/java/org/redisson/RedissonListMultimapCache.java
@@ -192,6 +192,11 @@ public class RedissonListMultimapCache<K, V> extends RedissonListMultimap<K, V>
     public RFuture<Boolean> expireKeyAsync(K key, long timeToLive, TimeUnit timeUnit) {
         return baseCache.expireKeyAsync(key, timeToLive, timeUnit);
     }
+    
+    @Override
+    public RFuture<Long> sizeInMemoryAsync() {
+        return baseCache.sizeInMemoryAsync();
+    }
 
     @Override
     public RFuture<Boolean> deleteAsync() {
diff --git a/redisson/src/main/java/org/redisson/RedissonListMultimapValues.java b/redisson/src/main/java/org/redisson/RedissonListMultimapValues.java
index 4b0bbe617..bf961c1a8 100644
--- a/redisson/src/main/java/org/redisson/RedissonListMultimapValues.java
+++ b/redisson/src/main/java/org/redisson/RedissonListMultimapValues.java
@@ -92,6 +92,12 @@ public class RedissonListMultimapValues<V> extends RedissonExpirable implements
         throw new UnsupportedOperationException("This operation is not supported for SetMultimap values Set");
     }
     
+    @Override
+    public RFuture<Long> sizeInMemoryAsync() {
+        List<Object> keys = Arrays.<Object>asList(getName(), timeoutSetName);
+        return super.sizeInMemoryAsync(keys);
+    }
+    
     public RFuture<Boolean> deleteAsync() {
         return commandExecutor.evalWriteAsync(getName(), codec, RedisCommands.EVAL_BOOLEAN,
                 "local expireDate = 92233720368547758; " +
diff --git a/redisson/src/main/java/org/redisson/RedissonLocalCachedMap.java b/redisson/src/main/java/org/redisson/RedissonLocalCachedMap.java
index a4c18c7ec..00a985f8c 100644
--- a/redisson/src/main/java/org/redisson/RedissonLocalCachedMap.java
+++ b/redisson/src/main/java/org/redisson/RedissonLocalCachedMap.java
@@ -520,6 +520,11 @@ public class RedissonLocalCachedMap<K, V> extends RedissonMap<K, V> implements R
         return commandExecutor.writeAsync(getName(), codec, RedisCommands.HDEL, params.toArray());
     }
 
+    @Override
+    public RFuture<Long> sizeInMemoryAsync() {
+        List<Object> keys = Arrays.<Object>asList(getName(), listener.getUpdatesLogName());
+        return super.sizeInMemoryAsync(keys);
+    }
     
     @Override
     public RFuture<Boolean> deleteAsync() {
diff --git a/redisson/src/main/java/org/redisson/RedissonMapCache.java b/redisson/src/main/java/org/redisson/RedissonMapCache.java
index 4bab3fde2..ad31f1d0e 100644
--- a/redisson/src/main/java/org/redisson/RedissonMapCache.java
+++ b/redisson/src/main/java/org/redisson/RedissonMapCache.java
@@ -1975,6 +1975,12 @@ public class RedissonMapCache<K, V> extends RedissonMap<K, V> implements RMapCac
         expiredTopic.removeListener(listenerId);
     }
 
+    @Override
+    public RFuture<Long> sizeInMemoryAsync() {
+        List<Object> keys = Arrays.<Object>asList(getName(), getTimeoutSetName(), getIdleSetName(), getLastAccessTimeSetName(), getOptionsName());
+        return super.sizeInMemoryAsync(keys);
+    }
+    
     @Override
     public RFuture<Boolean> deleteAsync() {
         return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, 
diff --git a/redisson/src/main/java/org/redisson/RedissonMultimapCache.java b/redisson/src/main/java/org/redisson/RedissonMultimapCache.java
index 9bdd3ef7e..43f7d9829 100644
--- a/redisson/src/main/java/org/redisson/RedissonMultimapCache.java
+++ b/redisson/src/main/java/org/redisson/RedissonMultimapCache.java
@@ -16,6 +16,7 @@
 package org.redisson;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 import org.redisson.api.RFuture;
@@ -62,6 +63,11 @@ public class RedissonMultimapCache<K> {
             ttlTimeout, ((RedissonObject)object).encodeMapKey(key));
     }
     
+    public RFuture<Long> sizeInMemoryAsync() {
+        List<Object> keys = Arrays.<Object>asList(object.getName(), timeoutSetName);
+        return ((RedissonObject)object).sizeInMemoryAsync(keys);
+    }
+    
     public RFuture<Boolean> deleteAsync() {
         return commandExecutor.evalWriteAsync(object.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN_AMOUNT,
                 "local entries = redis.call('hgetall', KEYS[1]); " +
diff --git a/redisson/src/main/java/org/redisson/RedissonObject.java b/redisson/src/main/java/org/redisson/RedissonObject.java
index 288f20410..b65a639e3 100644
--- a/redisson/src/main/java/org/redisson/RedissonObject.java
+++ b/redisson/src/main/java/org/redisson/RedissonObject.java
@@ -18,6 +18,7 @@ package org.redisson;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
@@ -97,6 +98,29 @@ public abstract class RedissonObject implements RObject {
     public void rename(String newName) {
         get(renameAsync(newName));
     }
+    
+    @Override
+    public RFuture<Long> sizeInMemoryAsync() {
+        return commandExecutor.writeAsync(getName(), RedisCommands.MEMORY_USAGE, getName());
+    }
+    
+    public final RFuture<Long> sizeInMemoryAsync(List<Object> keys) {
+        return commandExecutor.evalWriteAsync((String)keys.get(0), StringCodec.INSTANCE, RedisCommands.EVAL_LONG,
+                  "local total = 0;"
+                + "for j = 1, #KEYS, 1 do "
+                    + "local size = redis.call('memory', 'usage', KEYS[j]); "
+                    + "if size ~= false then "
+                        + "total = total + size;"
+                    + "end; "
+                + "end; "
+                + "return total; ", keys);
+
+    }
+    
+    @Override
+    public long sizeInMemory() {
+        return get(sizeInMemoryAsync());
+    }
 
     @Override
     public RFuture<Void> renameAsync(String newName) {
diff --git a/redisson/src/main/java/org/redisson/RedissonPermitExpirableSemaphore.java b/redisson/src/main/java/org/redisson/RedissonPermitExpirableSemaphore.java
index 8658692d6..21c2d2fab 100644
--- a/redisson/src/main/java/org/redisson/RedissonPermitExpirableSemaphore.java
+++ b/redisson/src/main/java/org/redisson/RedissonPermitExpirableSemaphore.java
@@ -16,6 +16,7 @@
 package org.redisson;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
@@ -624,6 +625,12 @@ public class RedissonPermitExpirableSemaphore extends RedissonExpirable implemen
                 Arrays.<Object>asList(getName(), getChannelName(), timeoutName), permitId, 1);
     }
     
+    @Override
+    public RFuture<Long> sizeInMemoryAsync() {
+        List<Object> keys = Arrays.<Object>asList(getName(), timeoutName);
+        return super.sizeInMemoryAsync(keys);
+    }
+    
     @Override
     public RFuture<Boolean> deleteAsync() {
         return commandExecutor.writeAsync(getName(), RedisCommands.DEL_OBJECTS, getName(), timeoutName);
diff --git a/redisson/src/main/java/org/redisson/RedissonSetMultimapCache.java b/redisson/src/main/java/org/redisson/RedissonSetMultimapCache.java
index 40d5f8939..0fb86f548 100644
--- a/redisson/src/main/java/org/redisson/RedissonSetMultimapCache.java
+++ b/redisson/src/main/java/org/redisson/RedissonSetMultimapCache.java
@@ -186,6 +186,11 @@ public class RedissonSetMultimapCache<K, V> extends RedissonSetMultimap<K, V> im
         return baseCache.expireKeyAsync(key, timeToLive, timeUnit);
     }
     
+    @Override
+    public RFuture<Long> sizeInMemoryAsync() {
+        return baseCache.sizeInMemoryAsync();
+    }
+    
     @Override
     public RFuture<Boolean> deleteAsync() {
         return baseCache.deleteAsync();
diff --git a/redisson/src/main/java/org/redisson/RedissonSetMultimapValues.java b/redisson/src/main/java/org/redisson/RedissonSetMultimapValues.java
index 013739775..5ab429cc3 100644
--- a/redisson/src/main/java/org/redisson/RedissonSetMultimapValues.java
+++ b/redisson/src/main/java/org/redisson/RedissonSetMultimapValues.java
@@ -122,6 +122,11 @@ public class RedissonSetMultimapValues<V> extends RedissonExpirable implements R
                 System.currentTimeMillis(), encodeMapKey(key));
     }
 
+    @Override
+    public RFuture<Long> sizeInMemoryAsync() {
+        List<Object> keys = Arrays.<Object>asList(getName(), timeoutSetName);
+        return super.sizeInMemoryAsync(keys);
+    }
 
     @Override
     public RFuture<Integer> sizeAsync() {
diff --git a/redisson/src/main/java/org/redisson/api/RObject.java b/redisson/src/main/java/org/redisson/api/RObject.java
index 942da09ef..9a08b44e9 100644
--- a/redisson/src/main/java/org/redisson/api/RObject.java
+++ b/redisson/src/main/java/org/redisson/api/RObject.java
@@ -27,6 +27,13 @@ import org.redisson.client.codec.Codec;
  */
 public interface RObject extends RObjectAsync {
 
+    /**
+     * Returns size of object in Redis memory
+     * 
+     * @return size of object
+     */
+    long sizeInMemory();
+    
     /**
      * Restores object using its state returned by {@link #dump()} method.
      * 
diff --git a/redisson/src/main/java/org/redisson/api/RObjectAsync.java b/redisson/src/main/java/org/redisson/api/RObjectAsync.java
index 63962f55f..93db42734 100644
--- a/redisson/src/main/java/org/redisson/api/RObjectAsync.java
+++ b/redisson/src/main/java/org/redisson/api/RObjectAsync.java
@@ -25,6 +25,13 @@ import java.util.concurrent.TimeUnit;
  */
 public interface RObjectAsync {
 
+    /**
+     * Returns size of object in Redis memory
+     * 
+     * @return size of object
+     */
+    RFuture<Long> sizeInMemoryAsync();
+    
     /**
      * Restores object using its state returned by {@link #dumpAsync()} method.
      * 
diff --git a/redisson/src/main/java/org/redisson/client/protocol/RedisCommands.java b/redisson/src/main/java/org/redisson/client/protocol/RedisCommands.java
index c3bbbdefe..4d4d14eeb 100644
--- a/redisson/src/main/java/org/redisson/client/protocol/RedisCommands.java
+++ b/redisson/src/main/java/org/redisson/client/protocol/RedisCommands.java
@@ -382,6 +382,7 @@ public interface RedisCommands {
     RedisStrictCommand<Boolean> EXISTS = new RedisStrictCommand<Boolean>("EXISTS", new BooleanReplayConvertor());
     RedisStrictCommand<Boolean> NOT_EXISTS = new RedisStrictCommand<Boolean>("EXISTS", new BooleanNumberReplayConvertor(1L));
 
+    RedisStrictCommand<Long> MEMORY_USAGE = new RedisStrictCommand<Long>("MEMORY", "USAGE", new LongReplayConvertor());
     RedisStrictCommand<Boolean> RENAMENX = new RedisStrictCommand<Boolean>("RENAMENX", new BooleanReplayConvertor());
     RedisStrictCommand<Void> RENAME = new RedisStrictCommand<Void>("RENAME", new VoidReplayConvertor());
     RedisStrictCommand<Boolean> MOVE = new RedisStrictCommand<Boolean>("MOVE", new BooleanReplayConvertor());
diff --git a/redisson/src/test/java/org/redisson/RedissonBucketTest.java b/redisson/src/test/java/org/redisson/RedissonBucketTest.java
index edc24b1d5..e05bd296c 100755
--- a/redisson/src/test/java/org/redisson/RedissonBucketTest.java
+++ b/redisson/src/test/java/org/redisson/RedissonBucketTest.java
@@ -17,6 +17,13 @@ import org.redisson.config.Config;
 
 public class RedissonBucketTest extends BaseTest {
 
+    @Test
+    public void testSizeInMemory() {
+        RBucket<Integer> al = redisson.getBucket("test");
+        al.set(1234);
+        assertThat(al.sizeInMemory()).isEqualTo(49);
+    }
+    
     @Test
     public void testDumpAndRestore() {
         RBucket<Integer> al = redisson.getBucket("test");
diff --git a/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java b/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java
index 9b6a48fdc..6df47372b 100644
--- a/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java
+++ b/redisson/src/test/java/org/redisson/RedissonMapCacheTest.java
@@ -70,6 +70,16 @@ public class RedissonMapCacheTest extends BaseMapTest {
         return redisson.getMapCache("test", options);        
     }
     
+    @Test
+    public void testSizeInMemory() {
+        RMapCache<Integer, Integer> map = redisson.getMapCache("test");
+        for (int i = 0; i < 10; i++) {
+            map.put(i, i, 5, TimeUnit.SECONDS);
+        }
+        
+        assertThat(map.sizeInMemory()).isGreaterThanOrEqualTo(466);
+    }
+    
     @Test
     public void testRemainTimeToLive() {
         RMapCache<String, String> map = redisson.getMapCache("test");