mirror of
https://github.com/hanzoai/redis.git
synced 2026-10-30 18:25:12 +00:00
Fixes: test faulure in https://github.com/redis/redis/actions/runs/26698983853/job/78688240089 While investigating the failure, I added temporary debug output: ```tcl puts "cpu_time_array=$cpu_time_array" puts "num_returned_cpu=$num_returned_cpu" puts "res=$res" ``` The failing SAMPLE 1000 run produced: ``` cpu_time_array=key_010 3 key_000 3 key_015 2 key_005 2 key_013 2 key_056 2 key_054 2 key_046 1 key_044 1 key_049 1 num_returned_cpu=10 res=5 ``` The test becomes statistically unstable at SAMPLE 1000. With only ~50 sampled operations out of 50k requests, the accumulated CPU times collapse into a very narrow range (observed: 1–3 µs), causing frequent ties between hot and cold keys near the Top-K cutoff. The resulting failures are driven by sampling variance rather than HOTKEYS correctness. Since the test already covers sampling behavior with ratios 1, 100, and 500, removing the 1000 case preserves coverage while eliminating a configuration that is too sparse to produce stable, reproducible assertions.
748 lines
28 KiB
Tcl
748 lines
28 KiB
Tcl
# Helper function to convert flat array response to dict
|
|
proc hotkeys_array_to_dict {arr} {
|
|
set result {}
|
|
for {set i 0} {$i < [llength $arr]} {incr i 2} {
|
|
set key [lindex $arr $i]
|
|
set val [lindex $arr [expr {$i + 1}]]
|
|
dict set result $key $val
|
|
}
|
|
return $result
|
|
}
|
|
|
|
start_server {tags {external:skip "hotkeys"}} {
|
|
test {HOTKEYS START - METRICS required} {
|
|
catch {r hotkeys start} err
|
|
assert_match "*METRICS parameter is required*" $err
|
|
}
|
|
|
|
test {HOTKEYS START - METRICS with CPU only} {
|
|
assert_equal {OK} [r hotkeys start METRICS 1 CPU]
|
|
r set key1 value1
|
|
assert_equal {OK} [r hotkeys stop]
|
|
|
|
set result [lindex [r hotkeys get] 0]
|
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
|
|
assert [dict exists $result "total-cpu-time-user-ms"]
|
|
assert [dict exists $result "total-cpu-time-sys-ms"]
|
|
assert [dict exists $result "by-cpu-time-us"]
|
|
assert {![dict exists $result "total-net-bytes"]}
|
|
assert {![dict exists $result "by-net-bytes"]}
|
|
|
|
assert_equal {OK} [r hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS START - METRICS with NET only} {
|
|
assert_equal {OK} [r hotkeys start METRICS 1 NET]
|
|
r set key1 value1
|
|
assert_equal {OK} [r hotkeys stop]
|
|
|
|
set result [lindex [r hotkeys get] 0]
|
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
|
|
assert [dict exists $result "total-net-bytes"]
|
|
assert [dict exists $result "by-net-bytes"]
|
|
assert {![dict exists $result "total-cpu-time-user-ms"]}
|
|
assert {![dict exists $result "total-cpu-time-sys-ms"]}
|
|
assert {![dict exists $result "by-cpu-time-us"]}
|
|
|
|
assert_equal {OK} [r hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS START - METRICS with both CPU and NET} {
|
|
assert_equal {OK} [r hotkeys start METRICS 2 CPU NET]
|
|
r set key1 value1
|
|
assert_equal {OK} [r hotkeys stop]
|
|
|
|
set result [lindex [r hotkeys get] 0]
|
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
|
|
assert [dict exists $result "total-cpu-time-user-ms"]
|
|
assert [dict exists $result "total-cpu-time-sys-ms"]
|
|
assert [dict exists $result "by-cpu-time-us"]
|
|
assert [dict exists $result "total-net-bytes"]
|
|
assert [dict exists $result "by-net-bytes"]
|
|
|
|
assert_equal {OK} [r hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS START - Error: session already started} {
|
|
assert_equal {OK} [r hotkeys start METRICS 1 CPU]
|
|
catch {r hotkeys start METRICS 1 NET} err
|
|
assert_match "*hotkey tracking session already in progress*" $err
|
|
assert_equal {OK} [r hotkeys stop]
|
|
assert_equal {OK} [r hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS START - Error: invalid METRICS count} {
|
|
catch {r hotkeys start METRICS 0} err
|
|
assert_match "*METRICS count*" $err
|
|
catch {r hotkeys start METRICS -1} err
|
|
assert_match "*METRICS count*" $err
|
|
}
|
|
|
|
test {HOTKEYS START - Error: METRICS count mismatch} {
|
|
catch {r hotkeys start METRICS 2 CPU} err
|
|
assert_match "*METRICS count does not match number of metric types provided*" $err
|
|
catch {r hotkeys start METRICS 1 CPU NET} err
|
|
assert_match "*syntax error*" $err
|
|
catch {r hotkeys start METRICS 3 CPU NET} err
|
|
assert_match "*METRICS count*" $err
|
|
}
|
|
|
|
test {HOTKEYS START - Error: METRICS invalid metrics} {
|
|
catch {r hotkeys start METRICS 1 GPU} err
|
|
assert_match "*METRICS no valid metrics*" $err
|
|
catch {r hotkeys start METRICS 2 GPU NYET} err
|
|
assert_match "*METRICS no valid metrics*" $err
|
|
|
|
# Allowing invalid metrics gives us forward-compatibility
|
|
assert_equal {OK} [r hotkeys start METRICS 2 GPU CPU]
|
|
|
|
assert_equal {OK} [r hotkeys stop]
|
|
assert_equal {OK} [r hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS START - Error: METRICS same parameter} {
|
|
catch {r hotkeys start METRICS 2 CPU CPU} err
|
|
assert_match "*METRICS CPU*" $err
|
|
catch {r hotkeys start METRICS 2 NET NET} err
|
|
assert_match "*METRICS NET*" $err
|
|
}
|
|
|
|
|
|
test {HOTKEYS START - with COUNT parameter} {
|
|
assert_equal {OK} [r hotkeys start METRICS 2 CPU NET COUNT 20]
|
|
|
|
for {set i 0} {$i < 30} {incr i} {
|
|
r set "key_$i" "value_$i"
|
|
}
|
|
|
|
assert_equal {OK} [r hotkeys stop]
|
|
|
|
set result [lindex [r hotkeys get] 0]
|
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
|
|
set cpu_array [dict get $result "by-cpu-time-us"]
|
|
set net_array [dict get $result "by-net-bytes"]
|
|
|
|
set cpu_count [expr {[llength $cpu_array] / 2}]
|
|
set net_count [expr {[llength $net_array] / 2}]
|
|
|
|
assert_lessthan_equal $cpu_count 20
|
|
assert_lessthan_equal $net_count 20
|
|
|
|
assert_equal {OK} [r hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS START - Error: COUNT out of range} {
|
|
catch {r hotkeys start METRICS 1 CPU COUNT 0} err
|
|
assert_match "*COUNT must be between 1 and 64*" $err
|
|
catch {r hotkeys start METRICS 1 CPU COUNT 100} err
|
|
assert_match "*COUNT must be between 1 and 64*" $err
|
|
}
|
|
|
|
test {HOTKEYS START - with DURATION parameter} {
|
|
assert_equal {OK} [r hotkeys start METRICS 1 CPU DURATION 1]
|
|
after 1500
|
|
|
|
set result [lindex [r hotkeys get] 0]
|
|
if {[llength $result] > 0 && [lindex $result 0] eq "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
assert_equal 0 [dict get $result "tracking-active"]
|
|
|
|
assert_equal {OK} [r hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS START - with SAMPLE parameter} {
|
|
assert_equal {OK} [r hotkeys start METRICS 2 CPU NET SAMPLE 10]
|
|
assert_equal {OK} [r hotkeys stop]
|
|
assert_equal {OK} [r hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS START - Error: SAMPLE ratio invalid} {
|
|
catch {r hotkeys start METRICS 1 CPU SAMPLE 0} err
|
|
assert_match "*SAMPLE ratio must be positive*" $err
|
|
}
|
|
|
|
test {HOTKEYS START - Error: SLOTS not allowed in non-cluster mode} {
|
|
catch {r hotkeys start METRICS 1 CPU SLOTS 2 0 5} err
|
|
assert_match "*SLOTS parameter cannot be used in non-cluster mode*" $err
|
|
} {} {cluster:skip}
|
|
|
|
test {HOTKEYS STOP - basic functionality} {
|
|
assert_equal {OK} [r hotkeys start METRICS 2 CPU NET]
|
|
assert_equal {OK} [r hotkeys stop]
|
|
|
|
set result [lindex [r hotkeys get] 0]
|
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
assert_equal 0 [dict get $result "tracking-active"]
|
|
|
|
assert_equal {OK} [r hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS RESET - basic functionality} {
|
|
assert_equal {OK} [r hotkeys start METRICS 1 CPU]
|
|
assert_equal {OK} [r hotkeys stop]
|
|
assert_equal {OK} [r hotkeys reset]
|
|
# After reset, GET should return nil
|
|
set result [lindex [r hotkeys get] 0]
|
|
assert_equal {} $result
|
|
}
|
|
|
|
test {HOTKEYS RESET - Error: session in progress} {
|
|
assert_equal {OK} [r hotkeys start METRICS 1 CPU]
|
|
catch {r hotkeys reset} err
|
|
assert_match "*hotkey tracking session in progress, stop tracking first*" $err
|
|
assert_equal {OK} [r hotkeys stop]
|
|
assert_equal {OK} [r hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS GET - returns nil when not started} {
|
|
set result [lindex [r hotkeys get] 0]
|
|
assert_equal {} $result
|
|
}
|
|
|
|
test {HOTKEYS GET - sample-ratio field} {
|
|
assert_equal {OK} [r hotkeys start METRICS 2 CPU NET SAMPLE 5]
|
|
assert_equal {OK} [r hotkeys stop]
|
|
|
|
set result [lindex [r hotkeys get] 0]
|
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
assert_equal 5 [dict get $result "sample-ratio"]
|
|
|
|
assert_equal {OK} [r hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS - nested commands} {
|
|
assert_equal {OK} [r hotkeys start METRICS 1 NET]
|
|
r eval "redis.call('set', 'x', 1)" 1 x
|
|
r eval "redis.call('set', 'y', 1)" 1 y
|
|
r eval "redis.call('set', 'x', 2)" 1 x
|
|
r eval "redis.call('set', 'x', 3)" 1 x
|
|
|
|
set result [lindex [r hotkeys get] 0]
|
|
set result [dict get $result "by-net-bytes"]
|
|
assert [dict exists $result "x"]
|
|
assert [dict exists $result "y"]
|
|
|
|
assert_equal {OK} [r hotkeys stop]
|
|
assert_equal {OK} [r hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS - commands inside MULTI/EXEC} {
|
|
set key1 "key1\{t\}"
|
|
set key2 "key2\{t\}"
|
|
|
|
assert_equal {OK} [r hotkeys start METRICS 2 CPU NET]
|
|
r multi
|
|
# Send multiple commands to avoid <1us cpu for $key2 which we assert
|
|
# at end of test
|
|
for {set i 0} {$i < 30} {incr i} {
|
|
r set $key1 value1
|
|
r set $key2 value1
|
|
r set $key1 value2
|
|
r set $key1 value3
|
|
}
|
|
r exec
|
|
|
|
assert_equal {OK} [r hotkeys stop]
|
|
set result [lindex [r hotkeys get] 0]
|
|
assert_equal {OK} [r hotkeys reset]
|
|
|
|
# Check NET metrics
|
|
set net_result [dict get $result "by-net-bytes"]
|
|
# Both keys should be tracked from within the MULTI/EXEC block
|
|
assert [dict exists $net_result $key1]
|
|
assert [dict exists $net_result $key2]
|
|
# key1 should have more bytes than key2 since it's accessed more times
|
|
assert {[dict get $net_result $key1] > [dict get $net_result $key2]}
|
|
|
|
# Check CPU metrics
|
|
set cpu_result [dict get $result "by-cpu-time-us"]
|
|
# Both keys should be tracked from within the MULTI/EXEC block
|
|
assert [dict exists $cpu_result $key1]
|
|
assert [dict exists $cpu_result $key2]
|
|
}
|
|
|
|
test {HOTKEYS - EVAL inside MULTI/EXEC with nested calls} {
|
|
set key1 "evalkey1\{t\}"
|
|
set key2 "evalkey2\{t\}"
|
|
|
|
assert_equal {OK} [r hotkeys start METRICS 1 NET]
|
|
r multi
|
|
r eval {redis.call('set', KEYS[1], 'value1')} 1 $key1
|
|
r eval {redis.call('set', KEYS[1], 'value2'); redis.call('set', KEYS[1], 'value3')} 1 $key1
|
|
r eval {redis.call('set', KEYS[1], 'value4')} 1 $key2
|
|
r exec
|
|
|
|
assert_equal {OK} [r hotkeys stop]
|
|
set result [lindex [r hotkeys get] 0]
|
|
assert_equal {OK} [r hotkeys reset]
|
|
|
|
# Check NET metrics - both keys should be tracked through EVAL commands
|
|
set net_result [dict get $result "by-net-bytes"]
|
|
assert [dict exists $net_result $key1]
|
|
assert [dict exists $net_result $key2]
|
|
# key1 should have more bytes than key2 since it's accessed by more EVAL commands
|
|
assert {[dict get $net_result $key1] > [dict get $net_result $key2]}
|
|
}
|
|
|
|
test {HOTKEYS GET - no conditional fields without selected slots} {
|
|
assert_equal {OK} [r hotkeys start METRICS 2 CPU NET SAMPLE 10]
|
|
r set key1 value1
|
|
assert_equal {OK} [r hotkeys stop]
|
|
|
|
set result [lindex [r hotkeys get] 0]
|
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
|
|
# Should NOT have selected-slots conditional fields
|
|
assert {![dict exists $result "sampled-commands-selected-slots-us"]}
|
|
assert {![dict exists $result "all-commands-selected-slots-us"]}
|
|
assert {![dict exists $result "net-bytes-sampled-commands-selected-slots"]}
|
|
assert {![dict exists $result "net-bytes-all-commands-selected-slots"]}
|
|
|
|
# Should have all-slots fields
|
|
assert [dict exists $result "all-commands-all-slots-us"]
|
|
assert [dict exists $result "net-bytes-all-commands-all-slots"]
|
|
|
|
assert_equal {OK} [r hotkeys reset]
|
|
}
|
|
|
|
foreach sample_ratio {1 100 500} {
|
|
test "HOTKEYS detection with biased key access, sample ratio = $sample_ratio" {
|
|
# Generate 100 random keys
|
|
set all_keys {}
|
|
for {set i 0} {$i < 100} {incr i} {
|
|
lappend all_keys "key_[format %03d $i]"
|
|
}
|
|
|
|
# Choose 20 keys to bias towards. These will be out hot keys
|
|
set hot_keys {}
|
|
for {set i 0} {$i < 20} {incr i} {
|
|
lappend hot_keys [lindex $all_keys $i]
|
|
}
|
|
|
|
assert_equal {OK} [r hotkeys start METRICS 2 CPU NET SAMPLE $sample_ratio]
|
|
|
|
# Biasing towards the 20 chosen keys when sending commands
|
|
set total_commands 50000
|
|
for {set i 0} {$i < $total_commands} {incr i} {
|
|
set rand [expr {rand()}]
|
|
if {$rand < 0.8} {
|
|
set key [lindex $hot_keys [expr {int(rand() * 20)}]]
|
|
} else {
|
|
set key [lindex $all_keys [expr {20 + int(rand() * 80)}]]
|
|
}
|
|
r set $key "value_$i"
|
|
}
|
|
|
|
assert_equal {OK} [r hotkeys stop]
|
|
|
|
set result [lindex [r hotkeys get] 0]
|
|
assert_not_equal $result {}
|
|
|
|
# Convert to dict if it's a flat array
|
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
|
|
set cpu_time_array [dict get $result "by-cpu-time-us"]
|
|
set net_bytes_array [dict get $result "by-net-bytes"]
|
|
|
|
set returned_cpu_keys {}
|
|
for {set i 0} {$i < [llength $cpu_time_array]} {incr i 2} {
|
|
lappend returned_cpu_keys [lindex $cpu_time_array $i]
|
|
}
|
|
|
|
# Check that most of returned keys (based on cpu time) are from our
|
|
# hot_keys list
|
|
set num_returned_cpu [llength $returned_cpu_keys]
|
|
assert_lessthan_equal $num_returned_cpu 10
|
|
assert_morethan $num_returned_cpu 0
|
|
|
|
set res 0
|
|
foreach key $returned_cpu_keys {
|
|
if {[lsearch -exact $hot_keys $key] >= 0} {
|
|
incr res
|
|
}
|
|
}
|
|
assert_morethan $res 5
|
|
|
|
set returned_net_keys {}
|
|
for {set i 0} {$i < [llength $net_bytes_array]} {incr i 2} {
|
|
lappend returned_net_keys [lindex $net_bytes_array $i]
|
|
}
|
|
|
|
# Same as cpu-time but for net-bytes
|
|
set num_returned_net [llength $returned_net_keys]
|
|
assert_lessthan_equal $num_returned_net 10
|
|
assert_morethan $num_returned_net 0
|
|
|
|
set res_net 0
|
|
foreach key $returned_net_keys {
|
|
if {[lsearch -exact $hot_keys $key] >= 0} {
|
|
incr res_net
|
|
}
|
|
}
|
|
assert_morethan $res_net 5
|
|
|
|
assert_equal {OK} [r hotkeys reset]
|
|
}
|
|
}
|
|
}
|
|
|
|
start_server {tags {external:skip "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 [lindex [r hotkeys get] 0]
|
|
|
|
# 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]
|
|
}
|
|
|
|
test {HOTKEYS GET - selected-slots returns full range in non-cluster mode} {
|
|
assert_equal {OK} [r hotkeys start METRICS 1 CPU]
|
|
assert_equal {OK} [r hotkeys stop]
|
|
|
|
set result [lindex [r hotkeys get] 0]
|
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
set slots [dict get $result "selected-slots"]
|
|
# Should return single range [[0, 16383]]
|
|
assert_equal 1 [llength $slots]
|
|
set range [lindex $slots 0]
|
|
assert_equal 2 [llength $range]
|
|
assert_equal 0 [lindex $range 0]
|
|
assert_equal 16383 [lindex $range 1]
|
|
|
|
assert_equal {OK} [r hotkeys reset]
|
|
}
|
|
}
|
|
|
|
start_cluster 1 0 {tags {external:skip cluster hotkeys}} {
|
|
|
|
test {HOTKEYS START - with SLOTS parameter in cluster mode} {
|
|
assert_equal {OK} [R 0 hotkeys start METRICS 2 CPU NET SLOTS 2 0 5]
|
|
assert_equal {OK} [R 0 hotkeys stop]
|
|
assert_equal {OK} [R 0 hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS START - Error: SLOTS count mismatch} {
|
|
catch {R 0 hotkeys start METRICS 1 CPU SLOTS 2 0} err
|
|
assert_match "*not enough slot numbers provided*" $err
|
|
}
|
|
|
|
test {HOTKEYS START - Error: duplicate slots} {
|
|
catch {R 0 hotkeys start METRICS 1 CPU SLOTS 2 0 0} err
|
|
assert_match "*duplicate slot number*" $err
|
|
}
|
|
|
|
test {HOTKEYS START - Error: SLOTS already specified} {
|
|
catch {R 0 hotkeys start METRICS 1 CPU SLOTS 1 0 SLOTS 1 5} err
|
|
assert_match "*SLOTS parameter already specified*" $err
|
|
}
|
|
|
|
test {HOTKEYS START - Error: invalid slot - negative value} {
|
|
catch {R 0 hotkeys start METRICS 1 CPU SLOTS 1 -1} err
|
|
assert_match "*Invalid or out of range slot*" $err
|
|
}
|
|
|
|
test {HOTKEYS START - Error: invalid slot - out of range} {
|
|
catch {R 0 hotkeys start METRICS 1 CPU SLOTS 1 16384} err
|
|
assert_match "*Invalid or out of range slot*" $err
|
|
}
|
|
|
|
test {HOTKEYS START - Error: invalid slot - non-integer} {
|
|
catch {R 0 hotkeys start METRICS 1 CPU SLOTS 1 abc} err
|
|
assert_match "*Invalid or out of range slot*" $err
|
|
}
|
|
|
|
test {HOTKEYS GET - selected-slots field with individual slots} {
|
|
assert_equal {OK} [R 0 hotkeys start METRICS 2 CPU NET SLOTS 2 0 5]
|
|
assert_equal {OK} [R 0 hotkeys stop]
|
|
|
|
set result [lindex [R 0 hotkeys get] 0]
|
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
set slots [dict get $result "selected-slots"]
|
|
# Two individual slots should return two 1-element arrays
|
|
assert_equal 2 [llength $slots]
|
|
assert_equal {0} [lindex $slots 0]
|
|
assert_equal {5} [lindex $slots 1]
|
|
|
|
assert_equal {OK} [R 0 hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS GET - selected-slots with unordered input slots are sorted} {
|
|
# Slots 10,5,1,0,6,2 should become [[0,2], [5,6], [10]]
|
|
assert_equal {OK} [R 0 hotkeys start METRICS 1 CPU SLOTS 6 10 5 1 0 6 2]
|
|
assert_equal {OK} [R 0 hotkeys stop]
|
|
|
|
set result [lindex [R 0 hotkeys get] 0]
|
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
set slots [dict get $result "selected-slots"]
|
|
assert_equal 3 [llength $slots]
|
|
assert_equal {0 2} [lindex $slots 0]
|
|
assert_equal {5 6} [lindex $slots 1]
|
|
assert_equal {10} [lindex $slots 2]
|
|
|
|
assert_equal {OK} [R 0 hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS GET - selected-slots returns node's slot ranges when no SLOTS specified in cluster mode} {
|
|
# In a 1-node cluster, the node owns all slots [0-16383]
|
|
assert_equal {OK} [R 0 hotkeys start METRICS 1 CPU]
|
|
assert_equal {OK} [R 0 hotkeys stop]
|
|
|
|
set result [lindex [R 0 hotkeys get] 0]
|
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
set slots [dict get $result "selected-slots"]
|
|
# 1-node cluster owns all slots, should return [[0, 16383]]
|
|
assert_equal 1 [llength $slots]
|
|
set range [lindex $slots 0]
|
|
assert_equal 2 [llength $range]
|
|
assert_equal 0 [lindex $range 0]
|
|
assert_equal 16383 [lindex $range 1]
|
|
|
|
assert_equal {OK} [R 0 hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS GET - conditional fields with sample_ratio > 1 and selected slots} {
|
|
assert_equal {OK} [R 0 hotkeys start METRICS 2 CPU NET SAMPLE 10 SLOTS 1 0]
|
|
R 0 set "{06S}key1" value1
|
|
assert_equal {OK} [R 0 hotkeys stop]
|
|
|
|
set result [lindex [R 0 hotkeys get] 0]
|
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
|
|
# Should have conditional fields
|
|
assert [dict exists $result "sampled-commands-selected-slots-us"]
|
|
assert [dict exists $result "all-commands-selected-slots-us"]
|
|
assert [dict exists $result "net-bytes-sampled-commands-selected-slots"]
|
|
assert [dict exists $result "net-bytes-all-commands-selected-slots"]
|
|
|
|
assert_equal {OK} [R 0 hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS GET - no conditional fields with sample_ratio = 1} {
|
|
assert_equal {OK} [R 0 hotkeys start METRICS 2 CPU NET SLOTS 1 0]
|
|
R 0 set "{06S}key1" value1
|
|
assert_equal {OK} [R 0 hotkeys stop]
|
|
|
|
set result [lindex [R 0 hotkeys get] 0]
|
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
|
|
# Should NOT have sampled-commands fields (sample_ratio = 1)
|
|
assert {![dict exists $result "sampled-commands-selected-slots-us"]}
|
|
assert {![dict exists $result "net-bytes-sampled-commands-selected-slots"]}
|
|
|
|
# Should have all-commands-selected-slots fields
|
|
assert [dict exists $result "all-commands-selected-slots-us"]
|
|
assert [dict exists $result "net-bytes-all-commands-selected-slots"]
|
|
|
|
assert_equal {OK} [R 0 hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS - tracks only keys in selected slots} {
|
|
# Get slots for keys with different hash tags
|
|
set key_slot0 "{06S}key"
|
|
set slot0 [R 0 cluster keyslot $key_slot0]
|
|
|
|
set key_other "{zzz}key"
|
|
set slot_other [R 0 cluster keyslot $key_other]
|
|
|
|
# Start tracking only slot 0
|
|
assert_equal {OK} [R 0 hotkeys start METRICS 1 NET SLOTS 1 $slot0]
|
|
|
|
# Set keys in both slots
|
|
for {set i 0} {$i < 100} {incr i} {
|
|
R 0 set "${key_slot0}_$i" "value_$i"
|
|
R 0 set "${key_other}_$i" "value_$i"
|
|
}
|
|
|
|
assert_equal {OK} [R 0 hotkeys stop]
|
|
|
|
set result [lindex [R 0 hotkeys get] 0]
|
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
|
|
# Check that by-net-bytes only contains keys from slot 0
|
|
set net_bytes_array [dict get $result "by-net-bytes"]
|
|
for {set i 0} {$i < [llength $net_bytes_array]} {incr i 2} {
|
|
set key [lindex $net_bytes_array $i]
|
|
# Keys should contain the slot0 hash tag
|
|
assert_match "*{06S}*" $key
|
|
}
|
|
|
|
assert_equal {OK} [R 0 hotkeys reset]
|
|
}
|
|
|
|
test {HOTKEYS - multiple selected slots} {
|
|
# Get slots for keys with different hash tags
|
|
set key_slot0 "{06S}key"
|
|
set slot0 [R 0 cluster keyslot $key_slot0]
|
|
|
|
set key_slot1 "{4oi}key"
|
|
set slot1 [R 0 cluster keyslot $key_slot1]
|
|
|
|
set key_other "{zzz}key"
|
|
set slot_other [R 0 cluster keyslot $key_other]
|
|
|
|
# Start tracking slots 0 and 1
|
|
assert_equal {OK} [R 0 hotkeys start METRICS 1 NET SLOTS 2 $slot0 $slot1]
|
|
|
|
# Set keys in all three slots
|
|
for {set i 0} {$i < 50} {incr i} {
|
|
R 0 set "${key_slot0}_$i" "value_$i"
|
|
R 0 set "${key_slot1}_$i" "value_$i"
|
|
R 0 set "${key_other}_$i" "value_$i"
|
|
}
|
|
|
|
assert_equal {OK} [R 0 hotkeys stop]
|
|
|
|
set result [lindex [R 0 hotkeys get] 0]
|
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
|
|
# Verify selected-slots contains both slots
|
|
set slots [dict get $result "selected-slots"]
|
|
assert_equal 2 [llength $slots]
|
|
|
|
# Check that by-net-bytes contains keys from both selected slots
|
|
set net_bytes_array [dict get $result "by-net-bytes"]
|
|
set found_slot0 0
|
|
set found_slot1 0
|
|
for {set i 0} {$i < [llength $net_bytes_array]} {incr i 2} {
|
|
set key [lindex $net_bytes_array $i]
|
|
if {[string match "*{06S}*" $key]} {
|
|
set found_slot0 1
|
|
}
|
|
if {[string match "*{4oi}*" $key]} {
|
|
set found_slot1 1
|
|
}
|
|
# Keys should NOT contain the other hash tag
|
|
assert {![string match "*{zzz}*" $key]}
|
|
}
|
|
|
|
# Should have found keys from both selected slots
|
|
assert_equal 1 $found_slot0
|
|
assert_equal 1 $found_slot1
|
|
|
|
assert_equal {OK} [R 0 hotkeys reset]
|
|
}
|
|
}
|
|
|
|
start_cluster 2 0 {tags {external:skip cluster hotkeys}} {
|
|
|
|
test {HOTKEYS START - Error: slot not handled by this node} {
|
|
# In a 2-master cluster, each node handles half the slots.
|
|
# Node 0 handles slots 0-8191, Node 1 handles slots 8192-16383.
|
|
# Try to use a slot that belongs to node 1 on node 0.
|
|
catch {R 0 hotkeys start METRICS 1 CPU SLOTS 1 8192} err
|
|
assert_match "*slot 8192 not handled by this node*" $err
|
|
catch {R 1 hotkeys start METRICS 1 CPU SLOTS 1 0} err
|
|
assert_match "*slot 0 not handled by this node*" $err
|
|
}
|
|
|
|
test {HOTKEYS GET - selected-slots returns each node's slot ranges in multi-node cluster} {
|
|
# In a 2-master cluster:
|
|
# Node 0 handles slots 0-8191
|
|
# Node 1 handles slots 8192-16383
|
|
|
|
# Test node 0
|
|
assert_equal {OK} [R 0 hotkeys start METRICS 1 CPU]
|
|
assert_equal {OK} [R 0 hotkeys stop]
|
|
|
|
set result [lindex [R 0 hotkeys get] 0]
|
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
set slots [dict get $result "selected-slots"]
|
|
# Node 0 should return [[0, 8191]]
|
|
assert_equal 1 [llength $slots]
|
|
set range [lindex $slots 0]
|
|
assert_equal 2 [llength $range]
|
|
assert_equal 0 [lindex $range 0]
|
|
assert_equal 8191 [lindex $range 1]
|
|
|
|
assert_equal {OK} [R 0 hotkeys reset]
|
|
|
|
# Test node 1
|
|
assert_equal {OK} [R 1 hotkeys start METRICS 1 CPU]
|
|
assert_equal {OK} [R 1 hotkeys stop]
|
|
|
|
set result [lindex [R 1 hotkeys get] 0]
|
|
if {[llength $result] > 0 && [lindex $result 0] ne "tracking-active"} {
|
|
set result [hotkeys_array_to_dict $result]
|
|
}
|
|
set slots [dict get $result "selected-slots"]
|
|
# Node 1 should return [[8192, 16383]]
|
|
assert_equal 1 [llength $slots]
|
|
set range [lindex $slots 0]
|
|
assert_equal 2 [llength $range]
|
|
assert_equal 8192 [lindex $range 0]
|
|
assert_equal 16383 [lindex $range 1]
|
|
|
|
assert_equal {OK} [R 1 hotkeys reset]
|
|
}
|
|
}
|