summaryrefslogtreecommitdiff
path: root/examples/redis-unstable/src/hotkeys.c
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 22:52:54 +0100
committerMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 22:52:54 +0100
commitdcacc00e3750300617ba6e16eb346713f91a783a (patch)
tree38e2d4fb5ed9d119711d4295c6eda4b014af73fd /examples/redis-unstable/src/hotkeys.c
parent58dac10aeb8f5a041c46bddbeaf4c7966a99b998 (diff)
downloadcrep-dcacc00e3750300617ba6e16eb346713f91a783a.tar.gz
Remove testing data
Diffstat (limited to 'examples/redis-unstable/src/hotkeys.c')
-rw-r--r--examples/redis-unstable/src/hotkeys.c614
1 files changed, 0 insertions, 614 deletions
diff --git a/examples/redis-unstable/src/hotkeys.c b/examples/redis-unstable/src/hotkeys.c
deleted file mode 100644
index 9b6726f..0000000
--- a/examples/redis-unstable/src/hotkeys.c
+++ /dev/null
@@ -1,614 +0,0 @@
-/* Hotkey tracking related functionality
- *
- * Copyright (c) 2026-Present, Redis Ltd.
- * All rights reserved.
- *
- * Licensed under your choice of (a) the Redis Source Available License 2.0
- * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
- * GNU Affero General Public License v3 (AGPLv3).
- */
-
-#include "server.h"
-#include "chk.h"
-#include "cluster.h"
-#include <sys/resource.h>
-
-static inline int nearestNextPowerOf2(unsigned int count) {
- if (count <= 1) return 1;
- return 1 << (32 - __builtin_clz(count-1));
-}
-
-/* Initialize the hotkeys structure and start tracking. If tracking keys in
- * specific slots is desired the user should pass along an already allocated and
- * populated slots array. The hotkeys structure takes ownership of the array and
- * will free it upon release. On failure the slots memory is released. */
-hotkeyStats *hotkeyStatsCreate(int count, int duration, int sample_ratio,
- int *slots, int slots_count, uint64_t tracked_metrics)
-{
- serverAssert(tracked_metrics & (HOTKEYS_TRACK_CPU | HOTKEYS_TRACK_NET));
-
- hotkeyStats *hotkeys = zcalloc(sizeof(hotkeyStats));
-
- /* We track count * 10 keys for better accuracy. Numbuckets is roughly 10
- * times the elements we track (actually num_buckets == 7-8 * count is
- * enough) again for better accuracy. Note the CHK implementation uses a
- * power of 2 numbuckets for better cache locality. */
- if (tracked_metrics & HOTKEYS_TRACK_CPU)
- hotkeys->cpu = chkTopKCreate(count * 10, nearestNextPowerOf2((unsigned)count * 100), 1.08);
-
- if (tracked_metrics & HOTKEYS_TRACK_NET)
- hotkeys->net = chkTopKCreate(count * 10, nearestNextPowerOf2((unsigned)count * 100), 1.08);
-
- hotkeys->tracked_metrics = tracked_metrics;
- hotkeys->tracking_count = count;
- hotkeys->duration = duration;
- hotkeys->sample_ratio = sample_ratio;
- hotkeys->slots = slots;
- hotkeys->numslots = slots_count;
- hotkeys->active = 1;
- hotkeys->keys_result = (getKeysResult)GETKEYS_RESULT_INIT;
- hotkeys->start = server.mstime;
-
- /* Store initial rusage for CPU time tracking */
- struct rusage rusage;
- getrusage(RUSAGE_SELF, &rusage);
- hotkeys->ru_utime = rusage.ru_utime;
- hotkeys->ru_stime = rusage.ru_stime;
-
- return hotkeys;
-}
-
-void hotkeyStatsRelease(hotkeyStats *hotkeys) {
- if (!hotkeys) return;
- if (hotkeys->cpu) chkTopKRelease(hotkeys->cpu);
- if (hotkeys->net) chkTopKRelease(hotkeys->net);
- zfree(hotkeys->slots);
- getKeysFreeResult(&hotkeys->keys_result);
-
- zfree(hotkeys);
-}
-
-/* Helper function for hotkey tracking to check if a slot is in the selected
- * slots list. If numslots is 0 then all slots are selected. */
-static inline int isSlotSelected(hotkeyStats *hotkeys, int slot) {
- if (hotkeys->numslots == 0) return 1;
- for (int i = 0; i < hotkeys->numslots; i++) {
- if (hotkeys->slots[i] == slot) return 1;
- }
- return 0;
-}
-
-/* Preparation for updates of the hotkeyStats for the current command, f.e
- * cache the current client and the getKeysResult. */
-void hotkeyStatsPreCurrentCmd(hotkeyStats *hotkeys, client *c) {
- if (!hotkeys || !hotkeys->active) return;
-
- robj **argv = c->original_argv ? c->original_argv : c->argv;
- int argc = c->original_argv ? c->original_argc : c->argc;
-
- hotkeys->keys_result = (getKeysResult)GETKEYS_RESULT_INIT;
- if (getKeysFromCommandWithSpecs(c->realcmd, argv, argc, GET_KEYSPEC_DEFAULT,
- &hotkeys->keys_result) == 0)
- {
- return;
- }
-
- /* Check if command is sampled */
- hotkeys->is_sampled = 1;
- if (hotkeys->sample_ratio > 1 &&
- (double)rand() / RAND_MAX >= 1.0 / hotkeys->sample_ratio)
- {
- hotkeys->is_sampled = 0;
- }
-
- hotkeys->is_in_selected_slots = isSlotSelected(hotkeys, c->slot);
-
- hotkeys->current_client = c;
-}
-
-/* Update the hotkeyStats with passed metrics. This can be called multiple times
- * between the calls to hotkeyStatsPreCurrentCmd and hotkeyStatsPostCurrentCmd */
-void hotkeyStatsUpdateCurrentCmd(hotkeyStats *hotkeys, hotkeyMetrics metrics) {
- if (!hotkeys || !hotkeys->active) return;
- if (hotkeys->keys_result.numkeys == 0) return;
-
- /* Don't update stats for nested calls */
- if (server.execution_nesting) return;
-
- serverAssert(hotkeys->current_client);
-
- int numkeys = hotkeys->keys_result.numkeys;
- uint64_t duration_per_key = metrics.cpu_time_usec / numkeys;
- uint64_t total_bytes = metrics.net_bytes;
- uint64_t bytes_per_key = total_bytes / numkeys;
-
- /* Update statistics counters */
- hotkeys->time_all_commands_all_slots += metrics.cpu_time_usec;
- hotkeys->net_bytes_all_commands_all_slots += total_bytes;
-
- if (hotkeys->is_in_selected_slots) {
- hotkeys->time_all_commands_selected_slots += metrics.cpu_time_usec;
- hotkeys->net_bytes_all_commands_selected_slots += total_bytes;
-
- if (hotkeys->is_sampled && hotkeys->sample_ratio > 1) {
- hotkeys->time_sampled_commands_selected_slots += metrics.cpu_time_usec;
- hotkeys->net_bytes_sampled_commands_selected_slots += total_bytes;
- }
- }
-
- /* Only add keys to topK structure if command was sampled and is in selected
- * slots. */
- if (!hotkeys->is_sampled || !hotkeys->is_in_selected_slots) {
- return;
- }
-
- mstime_t start_time = ustime();
-
- /* Keys we've cached in the keys_result only track positions in the client's
- * argv array so we must fetch it. */
- client *c = hotkeys->current_client;
- robj **argv = c->original_argv ? c->original_argv : c->argv;
-
- /* Add all keys to topK structure */
- for (int i = 0; i < numkeys; ++i) {
- int pos = hotkeys->keys_result.keys[i].pos;
-
- if (hotkeys->tracked_metrics & HOTKEYS_TRACK_CPU) {
- sds ret = chkTopKUpdate(hotkeys->cpu, argv[pos]->ptr, sdslen(argv[pos]->ptr), duration_per_key);
- if (ret) sdsfree(ret);
- }
-
- if (hotkeys->tracked_metrics & HOTKEYS_TRACK_NET) {
- sds ret = chkTopKUpdate(hotkeys->net, argv[pos]->ptr, sdslen(argv[pos]->ptr), bytes_per_key);
- if (ret) sdsfree(ret);
- }
- }
-
- /* Track CPU time spent updating the topk structures. */
- mstime_t end_time = ustime();
- hotkeys->cpu_time += (end_time - start_time)/1000;
-}
-
-/* Some cleanup work for hotkeyStats after the command has finished execution */
-void hotkeyStatsPostCurrentCmd(hotkeyStats *hotkeys) {
- if (!hotkeys || !hotkeys->active) return;
-
- getKeysFreeResult(&hotkeys->keys_result);
- hotkeys->keys_result = (getKeysResult)GETKEYS_RESULT_INIT;
-
- hotkeys->current_client = NULL;
- hotkeys->is_sampled = 0;
- hotkeys->is_in_selected_slots = 0;
-}
-
-size_t hotkeysGetMemoryUsage(hotkeyStats *hotkeys) {
- if (!hotkeys) return 0;
-
- size_t memory_usage = sizeof(hotkeyStats);
- if (hotkeys->cpu) {
- memory_usage += chkTopKGetMemoryUsage(hotkeys->cpu);
- }
- if (hotkeys->net) {
- memory_usage += chkTopKGetMemoryUsage(hotkeys->net);
- }
- /* Add memory for slots array if present */
- if (hotkeys->slots) {
- memory_usage += sizeof(int) * hotkeys->numslots;
- }
-
- return memory_usage;
-}
-
-static int64_t time_diff_ms(struct timeval a, struct timeval b) {
- int64_t sec = (int64_t)(a.tv_sec - b.tv_sec);
- int64_t usec = (int64_t)(a.tv_usec - b.tv_usec);
-
- if (usec < 0) {
- sec--;
- usec += 1000000;
- }
-
- return sec * 1000 + usec / 1000;
-}
-
-/* HOTKEYS command implementation
- *
- * HOTKEYS START
- * <METRICS count [CPU] [NET]>
- * [COUNT k]
- * [DURATION duration]
- * [SAMPLE ratio]
- * [SLOTS count slot…]
- * HOTKEYS STOP
- * HOTKEYS RESET
- * HOTKEYS GET
- */
-void hotkeysCommand(client *c) {
- if (c->argc < 2) {
- addReplyError(c, "HOTKEYS subcommand required");
- return;
- }
-
- char *sub = c->argv[1]->ptr;
-
- if (!strcasecmp(sub, "START")) {
- /* HOTKEYS START
- * <METRICS count [CPU] [NET]>
- * [COUNT k]
- * [DURATION seconds]
- * [SAMPLE ratio]
- * [SLOTS count slot…] */
- /* Return error if a session is already started */
- if (server.hotkeys && server.hotkeys->active) {
- addReplyError(c, "hotkey tracking session already in progress");
- return;
- }
-
- /* METRICS is required and must be the first argument */
- if (c->argc < 4 || strcasecmp(c->argv[2]->ptr, "METRICS")) {
- addReplyError(c, "METRICS parameter is required");
- return;
- }
-
- long metrics_count;
- char errmsg[128];
- snprintf(errmsg, 128, "METRICS count must be > 0 and <= %d", HOTKEYS_METRICS_COUNT);
- if (getRangeLongFromObjectOrReply(c, c->argv[3], 1, HOTKEYS_METRICS_COUNT,
- &metrics_count, errmsg) != C_OK)
- {
- return;
- }
-
- uint64_t tracked_metrics = 0;
-
- int j = 4;
-
- /* Parse CPU and NET tokens */
- int metrics_parsed = 0;
- int valid_metrics = 0;
- while (j < c->argc && metrics_parsed < metrics_count) {
- if (!strcasecmp(c->argv[j]->ptr, "CPU")) {
- if (tracked_metrics & HOTKEYS_TRACK_CPU) {
- addReplyError(c, "METRICS CPU defined more than once!");
- return;
- }
- tracked_metrics |= HOTKEYS_TRACK_CPU;
- ++valid_metrics;
- } else if (!strcasecmp(c->argv[j]->ptr, "NET")) {
- if (tracked_metrics & HOTKEYS_TRACK_NET) {
- addReplyError(c, "METRICS NET defined more than once!");
- return;
- }
- tracked_metrics |= HOTKEYS_TRACK_NET;
- ++valid_metrics;
- }
- ++metrics_parsed;
- ++j;
- }
-
- if (metrics_parsed != metrics_count) {
- addReplyError(c, "METRICS count does not match number of metric types provided");
- return;
- }
-
- if (valid_metrics == 0) {
- addReplyError(c, "METRICS no valid metrics passed. Supported: CPU|NET");
- return;
- }
-
- int count = 10; /* default */
- long duration = 0; /* default: no auto-stop */
- int sample_ratio = 1; /* default: track every key */
- int slots_count = 0;
- int *slots = NULL;
- while (j < c->argc) {
- int moreargs = (c->argc-1) - j;
- if (moreargs && !strcasecmp(c->argv[j]->ptr, "COUNT")) {
- long count_val;
- if (getRangeLongFromObjectOrReply(c, c->argv[j+1], 1, 64,
- &count_val, "COUNT must be between 1 and 64") != C_OK)
- {
- zfree(slots);
- return;
- }
- count = (int)count_val;
- j += 2;
- } else if (moreargs && !strcasecmp(c->argv[j]->ptr, "DURATION")) {
- /* Arbitrary 1 million seconds limit, so we don't overflow the
- * duration member which is kept in milliseconds */
- if (getRangeLongFromObjectOrReply(c, c->argv[j+1], 1, 1000000,
- &duration, "DURATION must be between 1 and 1000000") != C_OK)
- {
- zfree(slots);
- return;
- }
- duration *= 1000;
- j += 2;
- } else if (moreargs && !strcasecmp(c->argv[j]->ptr, "SAMPLE")) {
- long ratio_val;
- if (getRangeLongFromObjectOrReply(c, c->argv[j+1], 1, INT_MAX,
- &ratio_val, "SAMPLE ratio must be positive") != C_OK)
- {
- zfree(slots);
- return;
- }
- sample_ratio = (int)ratio_val;
- j += 2;
- } else if (moreargs && !strcasecmp(c->argv[j]->ptr, "SLOTS")) {
- if (slots) {
- addReplyError(c, "SLOTS parameter already specified");
- zfree(slots);
- return;
- }
- long slots_count_val;
- char msg[64];
- snprintf(msg, 64, "SLOTS count must be between 1 and %d",
- CLUSTER_SLOTS);
- if (getRangeLongFromObjectOrReply(c, c->argv[j+1], 1,
- CLUSTER_SLOTS, &slots_count_val, msg) != C_OK)
- {
- return;
- }
- slots_count = (int)slots_count_val;
-
- /* Parse slot numbers */
- if (j + 1 + slots_count >= c->argc) {
- addReplyError(c, "not enough slot numbers provided");
- return;
- }
- slots = zmalloc(sizeof(int) * slots_count);
- for (int i = 0; i < slots_count; i++) {
- long slot_val;
- if ((slot_val = getSlotOrReply(c, c->argv[j+2+i])) == -1) {
- zfree(slots);
- return;
- }
- /* Check for duplicate slot indices */
- for (int k = 0; k < i; ++k) {
- if (slots[k] == slot_val) {
- addReplyError(c, "duplicate slot number");
- zfree(slots);
- return;
- }
- }
-
- slots[i] = (int)slot_val;
- }
- j += 2 + slots_count;
- } else {
- addReplyError(c, "syntax error");
- if (slots) zfree(slots);
- return;
- }
- }
-
- hotkeyStats *hotkeys = hotkeyStatsCreate(count, duration, sample_ratio,
- slots, slots_count, tracked_metrics);
-
- hotkeyStatsRelease(server.hotkeys);
- server.hotkeys = hotkeys;
-
- addReply(c, shared.ok);
-
- } else if (!strcasecmp(sub, "STOP")) {
- /* HOTKEYS STOP */
- if (c->argc != 2) {
- addReplyError(c, "wrong number of arguments for 'hotkeys|stop' command");
- return;
- }
-
- if (!server.hotkeys || !server.hotkeys->active) {
- addReplyNull(c);
- return;
- }
-
- server.hotkeys->active = 0;
- server.hotkeys->duration = server.mstime - server.hotkeys->start;
- addReply(c, shared.ok);
-
- } else if (!strcasecmp(sub, "GET")) {
- /* HOTKEYS GET */
- if (c->argc != 2) {
- addReplyError(c, "wrong number of arguments for 'hotkeys|get' command");
- return;
- }
-
- /* If no tracking is started, return (nil) */
- if (!server.hotkeys) {
- addReplyNull(c);
- return;
- }
-
- serverAssert(server.hotkeys->tracked_metrics);
-
- /* Calculate duration */
- int duration = 0;
- if (!server.hotkeys->active) {
- duration = server.hotkeys->duration;
- } else {
- duration = server.mstime - server.hotkeys->start;
- }
-
- /* Get total CPU time using rusage (RUSAGE_SELF) -
- * only if CPU tracking is enabled */
- uint64_t total_cpu_user_msec = 0;
- uint64_t total_cpu_sys_msec = 0;
- if (server.hotkeys->tracked_metrics & HOTKEYS_TRACK_CPU) {
- struct rusage current_ru;
- getrusage(RUSAGE_SELF, &current_ru);
-
- /* Calculate difference in user and sys time */
- total_cpu_user_msec = time_diff_ms(current_ru.ru_utime, server.hotkeys->ru_utime);
- total_cpu_sys_msec = time_diff_ms(current_ru.ru_stime, server.hotkeys->ru_stime);
- }
-
- /* Get totals and lists for enabled metrics */
- uint64_t total_net_bytes = 0;
- chkHeapBucket *cpu = NULL;
- chkHeapBucket *net = NULL;
- int cpu_count = 0;
- int net_count = 0;
-
- if (server.hotkeys->tracked_metrics & HOTKEYS_TRACK_CPU) {
- cpu = chkTopKList(server.hotkeys->cpu);
- for (int i = 0; i < server.hotkeys->tracking_count; ++i) {
- if (cpu[i].count == 0) break;
- cpu_count++;
- }
- }
-
- if (server.hotkeys->tracked_metrics & HOTKEYS_TRACK_NET) {
- total_net_bytes = server.hotkeys->net->total;
- net = chkTopKList(server.hotkeys->net);
- for (int i = 0; i < server.hotkeys->tracking_count; ++i) {
- if (net[i].count == 0) break;
- net_count++;
- }
- }
-
- int has_selected_slots = (server.hotkeys->numslots > 0);
- int has_sampling = (server.hotkeys->sample_ratio > 1);
-
- int total_len = 14;
- void *arraylenptr = addReplyDeferredLen(c);
-
- /* tracking-active */
- addReplyBulkCString(c, "tracking-active");
- addReplyLongLong(c, server.hotkeys->active ? 1 : 0);
-
- /* sample-ratio */
- addReplyBulkCString(c, "sample-ratio");
- addReplyLongLong(c, server.hotkeys->sample_ratio);
-
- /* selected-slots */
- addReplyBulkCString(c, "selected-slots");
- addReplyArrayLen(c, server.hotkeys->numslots);
- for (int i = 0; i < server.hotkeys->numslots; i++) {
- addReplyLongLong(c, server.hotkeys->slots[i]);
- }
-
- /* sampled-command-selected-slots-ms (conditional) */
- if (has_sampling && has_selected_slots) {
- addReplyBulkCString(c, "sampled-command-selected-slots-ms");
- addReplyLongLong(c, server.hotkeys->time_sampled_commands_selected_slots / 1000);
-
- total_len += 2;
- }
-
- /* all-commands-selected-slots-ms (conditional) */
- if (has_selected_slots) {
- addReplyBulkCString(c, "all-commands-selected-slots-ms");
- addReplyLongLong(c, server.hotkeys->time_all_commands_selected_slots / 1000);
-
- total_len += 2;
- }
-
- /* all-commands-all-slots-ms */
- addReplyBulkCString(c, "all-commands-all-slots-ms");
- addReplyLongLong(c, server.hotkeys->time_all_commands_all_slots / 1000);
-
- /* net-bytes-sampled-commands-selected-slots (conditional) */
- if (has_sampling && has_selected_slots) {
- addReplyBulkCString(c, "net-bytes-sampled-commands-selected-slots");
- addReplyLongLong(c, server.hotkeys->net_bytes_sampled_commands_selected_slots);
-
- total_len += 2;
- }
-
- /* net-bytes-all-commands-selected-slots (conditional) */
- if (has_selected_slots) {
- addReplyBulkCString(c, "net-bytes-all-commands-selected-slots");
- addReplyLongLong(c,
- server.hotkeys->net_bytes_all_commands_selected_slots);
-
- total_len += 2;
- }
-
- /* net-bytes-all-commands-all-slots */
- addReplyBulkCString(c, "net-bytes-all-commands-all-slots");
- addReplyLongLong(c, server.hotkeys->net_bytes_all_commands_all_slots);
-
- /* collection-start-time-unix-ms */
- addReplyBulkCString(c, "collection-start-time-unix-ms");
- addReplyLongLong(c, server.hotkeys->start);
-
- /* collection-duration-ms */
- addReplyBulkCString(c, "collection-duration-ms");
- addReplyLongLong(c, duration);
-
- /* total-cpu-time-user-ms (in milliseconds) - only if CPU tracking is enabled */
- if (server.hotkeys->tracked_metrics & HOTKEYS_TRACK_CPU) {
- addReplyBulkCString(c, "total-cpu-time-user-ms");
- addReplyLongLong(c, total_cpu_user_msec);
-
- /* total-cpu-time-sys-ms (in milliseconds) */
- addReplyBulkCString(c, "total-cpu-time-sys-ms");
- addReplyLongLong(c, total_cpu_sys_msec);
-
- total_len += 4;
- }
-
- /* total-net-bytes - only if NET tracking is enabled */
- if (server.hotkeys->tracked_metrics & HOTKEYS_TRACK_NET) {
- addReplyBulkCString(c, "total-net-bytes");
- addReplyLongLong(c, total_net_bytes);
-
- total_len += 2;
- }
-
- /* by-cpu-time - only if CPU tracking is enabled */
- if (server.hotkeys->tracked_metrics & HOTKEYS_TRACK_CPU) {
- addReplyBulkCString(c, "by-cpu-time");
- /* Nested array of key-value pairs */
- addReplyArrayLen(c, 2 * cpu_count);
- for (int i = 0; i < cpu_count; ++i) {
- addReplyBulkCBuffer(c, cpu[i].item, sdslen(cpu[i].item));
- /* Return raw microsec value */
- addReplyLongLong(c, cpu[i].count);
- }
- zfree(cpu);
-
- total_len += 2;
- }
-
- /* by-net-bytes - only if NET tracking is enabled */
- if (server.hotkeys->tracked_metrics & HOTKEYS_TRACK_NET) {
- addReplyBulkCString(c, "by-net-bytes");
- /* Nested array of key-value pairs */
- addReplyArrayLen(c, 2 * net_count);
- for (int i = 0; i < net_count; ++i) {
- addReplyBulkCBuffer(c, net[i].item, sdslen(net[i].item));
- /* Return raw byte value */
- addReplyLongLong(c, net[i].count);
- }
- zfree(net);
-
- total_len += 2;
- }
-
- setDeferredArrayLen(c, arraylenptr, total_len);
-
- } else if (!strcasecmp(sub, "RESET")) {
- /* HOTKEYS RESET */
- if (c->argc != 2) {
- addReplyError(c,
- "wrong number of arguments for 'hotkeys|reset' command");
- return;
- }
-
- /* Return error if session is in progress and not yet completed */
- if (server.hotkeys && server.hotkeys->active) {
- addReplyError(c,
- "hotkey tracking session in progress, stop tracking first");
- return;
- }
-
- /* Release the resources used for hotkey tracking */
- hotkeyStatsRelease(server.hotkeys);
- server.hotkeys = NULL;
-
- addReply(c, shared.ok);
- } else {
- addReplyError(c, "unknown subcommand or wrong number of arguments");
- }
-}