GEODE-9291 Benchmarks for sorted sets (#158)

Basic ZADD and ZRANGE benchmarks for redis sorted sets.
diff --git a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/HgetRedisTask.java b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/HgetRedisTask.java
index 6b1a3a5..84e434e 100644
--- a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/HgetRedisTask.java
+++ b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/HgetRedisTask.java
@@ -17,8 +17,8 @@
 
 package org.apache.geode.benchmark.redis.tasks;
 
-import static org.apache.geode.benchmark.redis.tasks.RedisHash.toField;
-import static org.apache.geode.benchmark.redis.tasks.RedisHash.toKey;
+import static org.apache.geode.benchmark.redis.tasks.RedisSplitKey.toPart;
+import static org.apache.geode.benchmark.redis.tasks.RedisSplitKey.toKey;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.io.Serializable;
@@ -62,7 +62,7 @@
     final long k = keyRange.random();
 
     final String key = keyCache.valueOf(toKey(k));
-    final String field = keyCache.valueOf(toField(k));
+    final String field = keyCache.valueOf(toPart(k));
     final String value = redisClient.hget(key, field);
     if (validate) {
       assertThat(value).isEqualTo(field);
diff --git a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/HsetRedisTask.java b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/HsetRedisTask.java
index 6c902e8..96b3a21 100644
--- a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/HsetRedisTask.java
+++ b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/HsetRedisTask.java
@@ -18,8 +18,8 @@
 package org.apache.geode.benchmark.redis.tasks;
 
 
-import static org.apache.geode.benchmark.redis.tasks.RedisHash.toField;
-import static org.apache.geode.benchmark.redis.tasks.RedisHash.toKey;
+import static org.apache.geode.benchmark.redis.tasks.RedisSplitKey.toPart;
+import static org.apache.geode.benchmark.redis.tasks.RedisSplitKey.toKey;
 
 import java.io.Serializable;
 import java.util.Map;
@@ -58,7 +58,7 @@
   public boolean test(final Map<Object, Object> ctx) throws Exception {
     final long k = keyRange.random();
     final String key = keyCache.valueOf(toKey(k));
-    final String field = keyCache.valueOf(toField(k));
+    final String field = keyCache.valueOf(toPart(k));
     final String value = keyCache.valueOf(k);
     redisClient.hset(key, field, value);
     return true;
diff --git a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/JedisClientManager.java b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/JedisClientManager.java
index ffaae83..bb6af11 100644
--- a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/JedisClientManager.java
+++ b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/JedisClientManager.java
@@ -58,6 +58,16 @@
     }
 
     @Override
+    public long zadd(String key, double score,  String value) {
+      return jedisCluster.zadd(key, score, value);
+    }
+
+    @Override
+    public Set<String> zrange(String key, long start, long stop) {
+      return jedisCluster.zrange(key, start, stop);
+    }
+
+    @Override
     public void flushdb() {
       Set<String> seen = new HashSet<>();
       for (int i = 0; i < HASHSLOTS; ++i) {
diff --git a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/LettuceClientManager.java b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/LettuceClientManager.java
index bd38216..028b430 100644
--- a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/LettuceClientManager.java
+++ b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/LettuceClientManager.java
@@ -19,7 +19,9 @@
 
 import java.net.InetSocketAddress;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 import io.lettuce.core.ReadFrom;
@@ -68,6 +70,16 @@
     }
 
     @Override
+    public long zadd(String key, double score, String value) {
+      return redisAdvancedClusterCommands.get().zadd(key, score, value);
+    }
+
+    @Override
+    public Set<String> zrange(String key, long start, long stop) {
+      return new HashSet<>(redisAdvancedClusterCommands.get().zrange(key, start, stop));
+    }
+
+    @Override
     public void flushdb() {
       redisAdvancedClusterCommands.get().flushdb();
     }
diff --git a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/PrePopulateRedisHash.java b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/PrePopulateRedisHash.java
index c36371d..14299e4 100644
--- a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/PrePopulateRedisHash.java
+++ b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/PrePopulateRedisHash.java
@@ -18,8 +18,8 @@
 package org.apache.geode.benchmark.redis.tasks;
 
 import static java.lang.String.valueOf;
-import static org.apache.geode.benchmark.redis.tasks.RedisHash.toField;
-import static org.apache.geode.benchmark.redis.tasks.RedisHash.toKey;
+import static org.apache.geode.benchmark.redis.tasks.RedisSplitKey.toPart;
+import static org.apache.geode.benchmark.redis.tasks.RedisSplitKey.toKey;
 
 import org.apache.geode.benchmark.LongRange;
 
@@ -33,7 +33,7 @@
 
   @Override
   protected void prepopulate(final RedisClient redisClient, final long key) {
-    final String value = valueOf(toField(key));
+    final String value = valueOf(toPart(key));
     redisClient.hset(valueOf(toKey(key)), value, value);
   }
 }
diff --git a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/PrePopulateRedisSortedSet.java b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/PrePopulateRedisSortedSet.java
new file mode 100644
index 0000000..9de9ff6
--- /dev/null
+++ b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/PrePopulateRedisSortedSet.java
@@ -0,0 +1,40 @@
+/*
+ * 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.benchmark.redis.tasks;
+
+import static java.lang.String.valueOf;
+import static org.apache.geode.benchmark.redis.tasks.RedisSplitKey.toPart;
+import static org.apache.geode.benchmark.redis.tasks.RedisSplitKey.toKey;
+
+import org.apache.geode.benchmark.LongRange;
+
+public class PrePopulateRedisSortedSet extends AbstractPrePopulate {
+
+  public PrePopulateRedisSortedSet(
+      final RedisClientManager redisClientManager,
+      final LongRange keyRangeToPrepopulate) {
+    super(redisClientManager, keyRangeToPrepopulate);
+  }
+
+  @Override
+  protected void prepopulate(final RedisClient redisClient, final long key) {
+    final long score = toPart(key);
+    final String value = valueOf(score);
+    redisClient.zadd(valueOf(toKey(key)), score, value);
+  }
+}
diff --git a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/RedisClient.java b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/RedisClient.java
index 6155536..732a332 100644
--- a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/RedisClient.java
+++ b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/RedisClient.java
@@ -15,6 +15,8 @@
 
 package org.apache.geode.benchmark.redis.tasks;
 
+import java.util.Set;
+
 public interface RedisClient {
   String get(String key);
 
@@ -25,4 +27,8 @@
   boolean hset(String key, String field, String value);
 
   void flushdb();
+
+  long zadd(String key, double score, String value);
+
+  Set<String> zrange(String key, long start, long stop);
 }
diff --git a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/RedisHash.java b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/RedisSplitKey.java
similarity index 79%
rename from geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/RedisHash.java
rename to geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/RedisSplitKey.java
index 3aecb0b..b32525d 100644
--- a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/RedisHash.java
+++ b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/RedisSplitKey.java
@@ -16,15 +16,17 @@
 package org.apache.geode.benchmark.redis.tasks;
 
 /**
- * Split keyspace into Redis hash key and field parts.
+ * Split keyspace into a sublevel of keys.
  */
-public class RedisHash {
+public class RedisSplitKey {
+
+  public static long NUM_PARTS_PER_KEY = 1000;
 
   public static long toKey(final long key) {
-    return key / 1000;
+    return key / NUM_PARTS_PER_KEY;
   }
 
-  public static long toField(final long key) {
-    return key % 1000;
+  public static long toPart(final long key) {
+    return key % NUM_PARTS_PER_KEY;
   }
 }
diff --git a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/ZaddRedisTask.java b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/ZaddRedisTask.java
new file mode 100644
index 0000000..5016c66
--- /dev/null
+++ b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/ZaddRedisTask.java
@@ -0,0 +1,68 @@
+/*
+ * 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.benchmark.redis.tasks;
+
+
+import static org.apache.geode.benchmark.redis.tasks.RedisSplitKey.toPart;
+import static org.apache.geode.benchmark.redis.tasks.RedisSplitKey.toKey;
+
+import java.io.Serializable;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.yardstickframework.BenchmarkConfiguration;
+import org.yardstickframework.BenchmarkDriverAdapter;
+
+import org.apache.geode.benchmark.LongRange;
+
+public class ZaddRedisTask extends BenchmarkDriverAdapter implements Serializable {
+  private static final Logger logger = LoggerFactory.getLogger(ZaddRedisTask.class);
+
+  private final RedisClientManager redisClientManager;
+  private final LongRange keyRange;
+
+  private transient LongStringCache keyCache;
+  private transient RedisClient redisClient;
+
+  public ZaddRedisTask(final RedisClientManager redisClientManager, final LongRange keyRange) {
+    logger.info("Initialized: keyRange={}", keyRange);
+    this.redisClientManager = redisClientManager;
+    this.keyRange = keyRange;
+  }
+
+  @Override
+  public void setUp(final BenchmarkConfiguration cfg) throws Exception {
+    super.setUp(cfg);
+
+    keyCache = new LongStringCache(keyRange);
+    redisClient = redisClientManager.get();
+  }
+
+  @Override
+  public boolean test(final Map<Object, Object> ctx) throws Exception {
+    final long k = keyRange.random();
+
+    final String key = keyCache.valueOf(toKey(k));
+    final long score = toPart(k);
+    final String value = keyCache.valueOf(score);
+    redisClient.zadd(key, score, value);
+    return true;
+  }
+
+}
diff --git a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/ZrangeRedisTask.java b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/ZrangeRedisTask.java
new file mode 100644
index 0000000..641388b
--- /dev/null
+++ b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tasks/ZrangeRedisTask.java
@@ -0,0 +1,91 @@
+/*
+ * 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.benchmark.redis.tasks;
+
+import static org.apache.geode.benchmark.redis.tasks.RedisSplitKey.toKey;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.Collectors;
+import java.util.stream.LongStream;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.yardstickframework.BenchmarkConfiguration;
+import org.yardstickframework.BenchmarkDriverAdapter;
+
+import org.apache.geode.benchmark.LongRange;
+
+public class ZrangeRedisTask extends BenchmarkDriverAdapter implements Serializable {
+  private static final Logger logger = LoggerFactory.getLogger(ZrangeRedisTask.class);
+
+  private final RedisClientManager redisClientManager;
+  private final LongRange keyRange;
+  private final boolean validate;
+
+  private transient LongStringCache keyCache;
+  private transient RedisClient redisClient;
+
+  public ZrangeRedisTask(final RedisClientManager redisClientManager, final LongRange keyRange,
+                         final boolean validate) {
+    logger.info("Initialized: keyRange={}, validate={}", keyRange, validate);
+    this.redisClientManager = redisClientManager;
+    this.keyRange = keyRange;
+    this.validate = validate;
+  }
+
+  @Override
+  public void setUp(final BenchmarkConfiguration cfg) throws Exception {
+    super.setUp(cfg);
+
+    keyCache = new LongStringCache(keyRange);
+    redisClient = redisClientManager.get();
+  }
+
+  @Override
+  public boolean test(final Map<Object, Object> ctx) throws Exception {
+    final long k = keyRange.random();
+
+    final String key = keyCache.valueOf(toKey(k));
+
+    final long start = ThreadLocalRandom.current()
+        .nextLong(0, RedisSplitKey.NUM_PARTS_PER_KEY);
+    final long len = ThreadLocalRandom.current()
+        .nextLong(0, RedisSplitKey.NUM_PARTS_PER_KEY - start);
+    final long stop = start + len;
+
+    final Set<String> values = redisClient.zrange(key, start, stop);
+    if (validate) {
+      final LongRange range =
+          new LongRange(start, stop);
+
+      final Set<String> expectedValues =
+          LongStream.range(range.getMin(), range.getMax())
+              .boxed()
+              .map(keyCache::valueOf)
+              .collect(Collectors.toSet());
+
+      assertThat(values).isEqualTo(expectedValues);
+    }
+    return true;
+  }
+
+}
diff --git a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tests/RedisZaddBenchmark.java b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tests/RedisZaddBenchmark.java
new file mode 100644
index 0000000..a4ea713
--- /dev/null
+++ b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tests/RedisZaddBenchmark.java
@@ -0,0 +1,41 @@
+/*
+ * 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.benchmark.redis.tests;
+
+import static org.apache.geode.benchmark.Config.before;
+import static org.apache.geode.benchmark.Config.workload;
+import static org.apache.geode.benchmark.topology.Roles.CLIENT;
+
+import org.apache.geode.benchmark.redis.tasks.HsetRedisTask;
+import org.apache.geode.benchmark.redis.tasks.PrePopulateRedisHash;
+import org.apache.geode.benchmark.redis.tasks.PrePopulateRedisSortedSet;
+import org.apache.geode.benchmark.redis.tasks.ZaddRedisTask;
+import org.apache.geode.perftest.TestConfig;
+
+public class RedisZaddBenchmark extends RedisBenchmark {
+
+  @Override
+  public TestConfig configure() {
+    final TestConfig config = super.configure();
+
+    before(config, new PrePopulateRedisSortedSet(redisClientManager, keyRange), CLIENT);
+    workload(config, new ZaddRedisTask(redisClientManager, keyRange),
+        CLIENT);
+    return config;
+  }
+}
diff --git a/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tests/RedisZrangeBenchmark.java b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tests/RedisZrangeBenchmark.java
new file mode 100644
index 0000000..c2474af
--- /dev/null
+++ b/geode-benchmarks/src/main/java/org/apache/geode/benchmark/redis/tests/RedisZrangeBenchmark.java
@@ -0,0 +1,39 @@
+/*
+ * 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.benchmark.redis.tests;
+
+import static org.apache.geode.benchmark.Config.before;
+import static org.apache.geode.benchmark.Config.workload;
+import static org.apache.geode.benchmark.topology.Roles.CLIENT;
+
+import org.apache.geode.benchmark.redis.tasks.PrePopulateRedisSortedSet;
+import org.apache.geode.benchmark.redis.tasks.ZrangeRedisTask;
+import org.apache.geode.perftest.TestConfig;
+
+public class RedisZrangeBenchmark extends RedisBenchmark {
+
+  @Override
+  public TestConfig configure() {
+    final TestConfig config = super.configure();
+
+    before(config, new PrePopulateRedisSortedSet(redisClientManager, keyRange), CLIENT);
+    workload(config, new ZrangeRedisTask(redisClientManager, keyRange, isValidationEnabled()),
+        CLIENT);
+    return config;
+  }
+}