HotkeysInfo.java
package redis.clients.jedis.resps;
import redis.clients.jedis.Builder;
import redis.clients.jedis.util.KeyValue;
import java.io.Serializable;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static redis.clients.jedis.BuilderFactory.*;
/**
* Response object for the HOTKEYS GET command. Contains statistics about hot keys tracked by CPU
* time and network bytes.
*/
public class HotkeysInfo implements Serializable {
private static final long serialVersionUID = 1L;
// Field names from the response
public static final String TRACKING_ACTIVE = "tracking-active";
public static final String SAMPLE_RATIO = "sample-ratio";
public static final String SELECTED_SLOTS = "selected-slots";
public static final String SAMPLED_COMMANDS_SELECTED_SLOTS_US = "sampled-commands-selected-slots-us";
public static final String ALL_COMMANDS_SELECTED_SLOTS_US = "all-commands-selected-slots-us";
public static final String ALL_COMMANDS_ALL_SLOTS_US = "all-commands-all-slots-us";
public static final String NET_BYTES_SAMPLED_COMMANDS_SELECTED_SLOTS = "net-bytes-sampled-commands-selected-slots";
public static final String NET_BYTES_ALL_COMMANDS_SELECTED_SLOTS = "net-bytes-all-commands-selected-slots";
public static final String NET_BYTES_ALL_COMMANDS_ALL_SLOTS = "net-bytes-all-commands-all-slots";
public static final String COLLECTION_START_TIME_UNIX_MS = "collection-start-time-unix-ms";
public static final String COLLECTION_DURATION_MS = "collection-duration-ms";
public static final String TOTAL_CPU_TIME_USER_MS = "total-cpu-time-user-ms";
public static final String TOTAL_CPU_TIME_SYS_MS = "total-cpu-time-sys-ms";
public static final String TOTAL_NET_BYTES = "total-net-bytes";
public static final String BY_CPU_TIME_US = "by-cpu-time-us";
public static final String BY_NET_BYTES = "by-net-bytes";
private final boolean trackingActive;
private final long sampleRatio;
private final List<int[]> selectedSlots; // List of [start, end] slot ranges
private final Long sampledCommandSelectedSlotsUs;
private final Long allCommandsSelectedSlotsUs;
private final long allCommandsAllSlotsUs;
private final Long netBytesSampledCommandsSelectedSlots;
private final Long netBytesAllCommandsSelectedSlots;
private final long netBytesAllCommandsAllSlots;
private final long collectionStartTimeUnixMs;
private final long collectionDurationMs;
private final long totalCpuTimeUserMs;
private final long totalCpuTimeSysMs;
private final long totalNetBytes;
private final Map<String, Long> byCpuTimeUs;
private final Map<String, Long> byNetBytes;
public HotkeysInfo(boolean trackingActive, long sampleRatio, List<int[]> selectedSlots,
Long sampledCommandSelectedSlotsUs, Long allCommandsSelectedSlotsUs,
long allCommandsAllSlotsUs, Long netBytesSampledCommandsSelectedSlots,
Long netBytesAllCommandsSelectedSlots, long netBytesAllCommandsAllSlots,
long collectionStartTimeUnixMs, long collectionDurationMs, long totalCpuTimeUserMs,
long totalCpuTimeSysMs, long totalNetBytes, Map<String, Long> byCpuTimeUs,
Map<String, Long> byNetBytes) {
this.trackingActive = trackingActive;
this.sampleRatio = sampleRatio;
this.selectedSlots = selectedSlots;
this.sampledCommandSelectedSlotsUs = sampledCommandSelectedSlotsUs;
this.allCommandsSelectedSlotsUs = allCommandsSelectedSlotsUs;
this.allCommandsAllSlotsUs = allCommandsAllSlotsUs;
this.netBytesSampledCommandsSelectedSlots = netBytesSampledCommandsSelectedSlots;
this.netBytesAllCommandsSelectedSlots = netBytesAllCommandsSelectedSlots;
this.netBytesAllCommandsAllSlots = netBytesAllCommandsAllSlots;
this.collectionStartTimeUnixMs = collectionStartTimeUnixMs;
this.collectionDurationMs = collectionDurationMs;
this.totalCpuTimeUserMs = totalCpuTimeUserMs;
this.totalCpuTimeSysMs = totalCpuTimeSysMs;
this.totalNetBytes = totalNetBytes;
this.byCpuTimeUs = byCpuTimeUs;
this.byNetBytes = byNetBytes;
}
public boolean isTrackingActive() {
return trackingActive;
}
public long getSampleRatio() {
return sampleRatio;
}
/**
* Returns the selected slots. Each element is an int array that can be:
* <ul>
* <li>A single slot: {@code [slot]} (array with 1 element)</li>
* <li>A slot range: {@code [start, end]} (array with 2 elements, inclusive)</li>
* </ul>
* @return list of slot entries, empty if all slots are selected
*/
public List<int[]> getSelectedSlots() {
return selectedSlots;
}
public Long getSampledCommandSelectedSlotsUs() {
return sampledCommandSelectedSlotsUs;
}
public Long getAllCommandsSelectedSlotsUs() {
return allCommandsSelectedSlotsUs;
}
public long getAllCommandsAllSlotsUs() {
return allCommandsAllSlotsUs;
}
public Long getNetBytesSampledCommandsSelectedSlots() {
return netBytesSampledCommandsSelectedSlots;
}
public Long getNetBytesAllCommandsSelectedSlots() {
return netBytesAllCommandsSelectedSlots;
}
public long getNetBytesAllCommandsAllSlots() {
return netBytesAllCommandsAllSlots;
}
public long getCollectionStartTimeUnixMs() {
return collectionStartTimeUnixMs;
}
public long getCollectionDurationMs() {
return collectionDurationMs;
}
public long getTotalCpuTimeUserMs() {
return totalCpuTimeUserMs;
}
public long getTotalCpuTimeSysMs() {
return totalCpuTimeSysMs;
}
public long getTotalNetBytes() {
return totalNetBytes;
}
public Map<String, Long> getByCpuTimeUs() {
return byCpuTimeUs;
}
public Map<String, Long> getByNetBytes() {
return byNetBytes;
}
@SuppressWarnings("unchecked")
private static Map<String, Long> parseKeyValueMap(Object data) {
if (data == null) {
return Collections.emptyMap();
}
List<?> list = (List<?>) data;
if (list.isEmpty()) {
return Collections.emptyMap();
}
Map<String, Long> result = new LinkedHashMap<>();
if (list.get(0) instanceof KeyValue) {
for (KeyValue<?, ?> kv : (List<KeyValue<?, ?>>) list) {
result.put(STRING.build(kv.getKey()), LONG.build(kv.getValue()));
}
} else {
// RESP2 format: alternating key-value pairs
for (int i = 0; i < list.size(); i += 2) {
result.put(STRING.build(list.get(i)), LONG.build(list.get(i + 1)));
}
}
return result;
}
/**
* Parse selected-slots which is an array of slot entries. Each entry can be:
* <ul>
* <li>A single slot: [slot] (array with 1 element)</li>
* <li>A slot range: [start, end] (array with 2 elements)</li>
* </ul>
* Example: [[0, 2], [100]] means slots 0-2 (range) and slot 100 (single).
*/
@SuppressWarnings("unchecked")
private static List<int[]> parseSlotRanges(Object data) {
if (data == null) {
return Collections.emptyList();
}
List<?> list = (List<?>) data;
if (list.isEmpty()) {
return Collections.emptyList();
}
List<int[]> result = new java.util.ArrayList<>(list.size());
for (Object item : list) {
if (item instanceof List) {
List<?> range = (List<?>) item;
if (range.size() == 1) {
// Single slot
int slot = LONG.build(range.get(0)).intValue();
result.add(new int[] { slot });
} else if (range.size() == 2) {
// Slot range
int start = LONG.build(range.get(0)).intValue();
int end = LONG.build(range.get(1)).intValue();
result.add(new int[] { start, end });
}
}
}
return result;
}
public static final Builder<HotkeysInfo> HOTKEYS_INFO_BUILDER = new Builder<HotkeysInfo>() {
@Override
@SuppressWarnings("unchecked")
public HotkeysInfo build(Object data) {
if (data == null) {
return null;
}
List<?> list = (List<?>) data;
if (list.isEmpty()) {
return null;
}
// Check if the response is wrapped in an outer array (single element that is a List)
// This happens when Redis returns [[key1, val1, key2, val2, ...]]
if (list.size() == 1 && list.get(0) instanceof List) {
list = (List<?>) list.get(0);
if (list.isEmpty()) {
return null;
}
}
boolean trackingActive = false;
long sampleRatio = 1;
List<int[]> selectedSlots = Collections.emptyList();
Long sampledCommandSelectedSlotsUs = null;
Long allCommandsSelectedSlotsUs = null;
long allCommandsAllSlotsUs = 0;
Long netBytesSampledCommandsSelectedSlots = null;
Long netBytesAllCommandsSelectedSlots = null;
long netBytesAllCommandsAllSlots = 0;
long collectionStartTimeUnixMs = 0;
long collectionDurationMs = 0;
long totalCpuTimeUserMs = 0;
long totalCpuTimeSysMs = 0;
long totalNetBytes = 0;
Map<String, Long> byCpuTimeUs = Collections.emptyMap();
Map<String, Long> byNetBytes = Collections.emptyMap();
if (list.get(0) instanceof KeyValue) {
// RESP3 format
for (KeyValue<?, ?> kv : (List<KeyValue<?, ?>>) list) {
String key = STRING.build(kv.getKey());
Object value = kv.getValue();
switch (key) {
case TRACKING_ACTIVE:
trackingActive = LONG.build(value) == 1;
break;
case SAMPLE_RATIO:
sampleRatio = LONG.build(value);
break;
case SELECTED_SLOTS:
selectedSlots = parseSlotRanges(value);
break;
case SAMPLED_COMMANDS_SELECTED_SLOTS_US:
sampledCommandSelectedSlotsUs = LONG.build(value);
break;
case ALL_COMMANDS_SELECTED_SLOTS_US:
allCommandsSelectedSlotsUs = LONG.build(value);
break;
case ALL_COMMANDS_ALL_SLOTS_US:
allCommandsAllSlotsUs = LONG.build(value);
break;
case NET_BYTES_SAMPLED_COMMANDS_SELECTED_SLOTS:
netBytesSampledCommandsSelectedSlots = LONG.build(value);
break;
case NET_BYTES_ALL_COMMANDS_SELECTED_SLOTS:
netBytesAllCommandsSelectedSlots = LONG.build(value);
break;
case NET_BYTES_ALL_COMMANDS_ALL_SLOTS:
netBytesAllCommandsAllSlots = LONG.build(value);
break;
case COLLECTION_START_TIME_UNIX_MS:
collectionStartTimeUnixMs = LONG.build(value);
break;
case COLLECTION_DURATION_MS:
collectionDurationMs = LONG.build(value);
break;
case TOTAL_CPU_TIME_USER_MS:
totalCpuTimeUserMs = LONG.build(value);
break;
case TOTAL_CPU_TIME_SYS_MS:
totalCpuTimeSysMs = LONG.build(value);
break;
case TOTAL_NET_BYTES:
totalNetBytes = LONG.build(value);
break;
case BY_CPU_TIME_US:
byCpuTimeUs = parseKeyValueMap(value);
break;
case BY_NET_BYTES:
byNetBytes = parseKeyValueMap(value);
break;
}
}
} else {
// RESP2 format: alternating key-value pairs
for (int i = 0; i < list.size(); i += 2) {
String key = STRING.build(list.get(i));
Object value = list.get(i + 1);
switch (key) {
case TRACKING_ACTIVE:
trackingActive = LONG.build(value) == 1;
break;
case SAMPLE_RATIO:
sampleRatio = LONG.build(value);
break;
case SELECTED_SLOTS:
selectedSlots = parseSlotRanges(value);
break;
case SAMPLED_COMMANDS_SELECTED_SLOTS_US:
sampledCommandSelectedSlotsUs = LONG.build(value);
break;
case ALL_COMMANDS_SELECTED_SLOTS_US:
allCommandsSelectedSlotsUs = LONG.build(value);
break;
case ALL_COMMANDS_ALL_SLOTS_US:
allCommandsAllSlotsUs = LONG.build(value);
break;
case NET_BYTES_SAMPLED_COMMANDS_SELECTED_SLOTS:
netBytesSampledCommandsSelectedSlots = LONG.build(value);
break;
case NET_BYTES_ALL_COMMANDS_SELECTED_SLOTS:
netBytesAllCommandsSelectedSlots = LONG.build(value);
break;
case NET_BYTES_ALL_COMMANDS_ALL_SLOTS:
netBytesAllCommandsAllSlots = LONG.build(value);
break;
case COLLECTION_START_TIME_UNIX_MS:
collectionStartTimeUnixMs = LONG.build(value);
break;
case COLLECTION_DURATION_MS:
collectionDurationMs = LONG.build(value);
break;
case TOTAL_CPU_TIME_USER_MS:
totalCpuTimeUserMs = LONG.build(value);
break;
case TOTAL_CPU_TIME_SYS_MS:
totalCpuTimeSysMs = LONG.build(value);
break;
case TOTAL_NET_BYTES:
totalNetBytes = LONG.build(value);
break;
case BY_CPU_TIME_US:
byCpuTimeUs = parseKeyValueMap(value);
break;
case BY_NET_BYTES:
byNetBytes = parseKeyValueMap(value);
break;
}
}
}
return new HotkeysInfo(trackingActive, sampleRatio, selectedSlots,
sampledCommandSelectedSlotsUs, allCommandsSelectedSlotsUs, allCommandsAllSlotsUs,
netBytesSampledCommandsSelectedSlots, netBytesAllCommandsSelectedSlots,
netBytesAllCommandsAllSlots, collectionStartTimeUnixMs, collectionDurationMs,
totalCpuTimeUserMs, totalCpuTimeSysMs, totalNetBytes, byCpuTimeUs, byNetBytes);
}
};
}