diff --git a/src/commands/hotkeys-get.json b/src/commands/hotkeys-get.json index 0f1500b93..039f34054 100644 --- a/src/commands/hotkeys-get.json +++ b/src/commands/hotkeys-get.json @@ -14,31 +14,98 @@ "reply_schema": { "oneOf": [ { - "description": "Flat array with various metrics(tracking-activated, sample-ratio, selected-slots, time/network statistics), collection info (collection-start-time-unix-ms, collection-duration-ms, total-cpu-time-user-ms, total-cpu-time-sys-ms, total-net-bytes), and the requested lists of Top-K hotkeys (available metrics: by-cpu-time-us, by-net-bytes) where at most K hotkeys are returned.", - "type": "array", - "items": { - "oneOf": [ - { - "type": "string" - }, - { + "description": "Map with various metrics (tracking-active, sample-ratio, selected-slots, time/network statistics), collection info (collection-start-time-unix-ms, collection-duration-ms, total-cpu-time-user-ms, total-cpu-time-sys-ms, total-net-bytes), and the requested lists of Top-K hotkeys (available metrics: by-cpu-time-us, by-net-bytes) where at most K hotkeys are returned.", + "type": "object", + "properties": { + "tracking-active": { + "type": "integer", + "description": "Whether hotkey tracking is currently active (1) or stopped (0)." + }, + "sample-ratio": { + "type": "integer", + "description": "The sampling ratio used for tracking." + }, + "selected-slots": { + "type": "array", + "items": { "type": "integer" }, - { - "type": "array", - "items": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "integer" - } - ] - } - } - ] - } + "description": "Array of slot numbers being tracked (empty if tracking all slots)." + }, + "sampled-command-selected-slots-us": { + "type": "integer", + "description": "CPU time in microseconds for sampled commands in selected slots (only present when sampling and slots are configured)." + }, + "all-commands-selected-slots-us": { + "type": "integer", + "description": "CPU time in microseconds for all commands in selected slots (only present when slots are configured)." + }, + "all-commands-all-slots-us": { + "type": "integer", + "description": "CPU time in microseconds for all commands across all slots." + }, + "net-bytes-sampled-commands-selected-slots": { + "type": "integer", + "description": "Network bytes for sampled commands in selected slots (only present when sampling and slots are configured)." + }, + "net-bytes-all-commands-selected-slots": { + "type": "integer", + "description": "Network bytes for all commands in selected slots (only present when slots are configured)." + }, + "net-bytes-all-commands-all-slots": { + "type": "integer", + "description": "Network bytes for all commands across all slots." + }, + "collection-start-time-unix-ms": { + "type": "integer", + "description": "Unix timestamp in milliseconds when collection started." + }, + "collection-duration-ms": { + "type": "integer", + "description": "Duration of collection in milliseconds." + }, + "total-cpu-time-user-ms": { + "type": "integer", + "description": "Total user CPU time in milliseconds (only present when CPU tracking is enabled)." + }, + "total-cpu-time-sys-ms": { + "type": "integer", + "description": "Total system CPU time in milliseconds (only present when CPU tracking is enabled)." + }, + "total-net-bytes": { + "type": "integer", + "description": "Total network bytes (only present when NET tracking is enabled)." + }, + "by-cpu-time-us": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "description": "Flat array of key-value pairs (key1, cpu_time1, key2, cpu_time2, ...) for top-K hotkeys by CPU time in microseconds (only present when CPU tracking is enabled)." + }, + "by-net-bytes": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "description": "Flat array of key-value pairs (key1, bytes1, key2, bytes2, ...) for top-K hotkeys by network bytes (only present when NET tracking is enabled)." + } + }, + "additionalProperties": false }, { "description": "If no tracking is started", diff --git a/src/hotkeys.c b/src/hotkeys.c index eced58f20..5efee85d4 100644 --- a/src/hotkeys.c +++ b/src/hotkeys.c @@ -474,7 +474,7 @@ void hotkeysCommand(client *c) { int has_selected_slots = (server.hotkeys->numslots > 0); int has_sampling = (server.hotkeys->sample_ratio > 1); - int total_len = 14; + int total_len = 7; void *arraylenptr = addReplyDeferredLen(c); /* tracking-active */ @@ -497,7 +497,7 @@ void hotkeysCommand(client *c) { addReplyBulkCString(c, "sampled-command-selected-slots-us"); addReplyLongLong(c, server.hotkeys->time_sampled_commands_selected_slots); - total_len += 2; + total_len++; } /* all-commands-selected-slots-us (conditional) */ @@ -505,7 +505,7 @@ void hotkeysCommand(client *c) { addReplyBulkCString(c, "all-commands-selected-slots-us"); addReplyLongLong(c, server.hotkeys->time_all_commands_selected_slots); - total_len += 2; + ++total_len; } /* all-commands-all-slots-us */ @@ -517,7 +517,7 @@ void hotkeysCommand(client *c) { addReplyBulkCString(c, "net-bytes-sampled-commands-selected-slots"); addReplyLongLong(c, server.hotkeys->net_bytes_sampled_commands_selected_slots); - total_len += 2; + ++total_len; } /* net-bytes-all-commands-selected-slots (conditional) */ @@ -526,7 +526,7 @@ void hotkeysCommand(client *c) { addReplyLongLong(c, server.hotkeys->net_bytes_all_commands_selected_slots); - total_len += 2; + ++total_len; } /* net-bytes-all-commands-all-slots */ @@ -550,7 +550,7 @@ void hotkeysCommand(client *c) { addReplyBulkCString(c, "total-cpu-time-sys-ms"); addReplyLongLong(c, total_cpu_sys_msec); - total_len += 4; + total_len += 2; } /* total-net-bytes - only if NET tracking is enabled */ @@ -558,7 +558,7 @@ void hotkeysCommand(client *c) { addReplyBulkCString(c, "total-net-bytes"); addReplyLongLong(c, total_net_bytes); - total_len += 2; + ++total_len; } /* by-cpu-time-us - only if CPU tracking is enabled */ @@ -573,7 +573,7 @@ void hotkeysCommand(client *c) { } zfree(cpu); - total_len += 2; + ++total_len; } /* by-net-bytes - only if NET tracking is enabled */ @@ -588,10 +588,10 @@ void hotkeysCommand(client *c) { } zfree(net); - total_len += 2; + ++total_len; } - setDeferredArrayLen(c, arraylenptr, total_len); + setDeferredMapLen(c, arraylenptr, total_len); } else if (!strcasecmp(sub, "RESET")) { /* HOTKEYS RESET */ diff --git a/tests/unit/hotkeys.tcl b/tests/unit/hotkeys.tcl index 757bdce93..51fe5e64d 100644 --- a/tests/unit/hotkeys.tcl +++ b/tests/unit/hotkeys.tcl @@ -351,6 +351,47 @@ start_server {tags {"hotkeys"}} { } } +start_server {tags {"hotkeys"}} { + test {HOTKEYS GET - RESP3 returns map with flat array values for hotkeys} { + r hello 3 + + assert_equal {OK} [r hotkeys start METRICS 2 CPU NET] + r set testkey testvalue + assert_equal {OK} [r hotkeys stop] + + set result [r hotkeys get] + + # In RESP3, the outer result is a native map (dict) + assert [dict exists $result "tracking-active"] + assert [dict exists $result "sample-ratio"] + assert [dict exists $result "selected-slots"] + assert [dict exists $result "by-cpu-time-us"] + assert [dict exists $result "by-net-bytes"] + + # Verify by-cpu-time-us is a flat array [key1, val1, key2, val2, ...] + set cpu_array [dict get $result "by-cpu-time-us"] + # Flat array length should be even (key-value pairs) + assert {[llength $cpu_array] % 2 == 0} + # First element is the key name (string), second is the value (integer) + set first_key [lindex $cpu_array 0] + set first_val [lindex $cpu_array 1] + assert_equal "testkey" $first_key + assert {[string is integer $first_val]} + + # Verify by-net-bytes is a flat array [key1, val1, key2, val2, ...] + set net_array [dict get $result "by-net-bytes"] + # Flat array length should be even (key-value pairs) + assert {[llength $net_array] % 2 == 0} + # First element is the key name (string), second is the value (integer) + set first_key [lindex $net_array 0] + set first_val [lindex $net_array 1] + assert_equal "testkey" $first_key + assert {[string is integer $first_val]} + + assert_equal {OK} [r hotkeys reset] + } +} + start_cluster 1 0 {tags {external:skip cluster hotkeys}} { test {HOTKEYS START - with SLOTS parameter in cluster mode} {