1use crate::errors::ParsingError;
129use crate::types::{FromRedisValue, RedisWrite, ToRedisArgs, Value};
130use std::collections::HashMap;
131
132pub const HOTKEYS_COUNT_MIN: u64 = 1;
134pub const HOTKEYS_COUNT_MAX: u64 = 64;
136
137#[derive(Clone, Debug)]
164#[non_exhaustive]
165pub struct HotkeysOptions {
166 cpu: bool,
168 net: bool,
170 count_k: Option<u64>,
172 duration_secs: Option<u64>,
174 sample_ratio: Option<u64>,
176 slots: Option<Vec<u16>>,
178}
179
180impl HotkeysOptions {
181 pub fn new_with_cpu() -> Self {
195 Self {
196 cpu: true,
197 net: false,
198 count_k: None,
199 duration_secs: None,
200 sample_ratio: None,
201 slots: None,
202 }
203 }
204
205 pub fn new_with_net() -> Self {
219 Self {
220 cpu: false,
221 net: true,
222 count_k: None,
223 duration_secs: None,
224 sample_ratio: None,
225 slots: None,
226 }
227 }
228
229 pub fn and_cpu(mut self) -> Self {
233 self.cpu = true;
234 self
235 }
236
237 pub fn and_net(mut self) -> Self {
241 self.net = true;
242 self
243 }
244
245 fn metrics_count(&self) -> u64 {
247 self.cpu as u64 + self.net as u64
248 }
249
250 pub fn with_count(mut self, k: u64) -> Result<Self, String> {
258 if !(HOTKEYS_COUNT_MIN..=HOTKEYS_COUNT_MAX).contains(&k) {
259 return Err(format!(
260 "COUNT must be between {HOTKEYS_COUNT_MIN} and {HOTKEYS_COUNT_MAX}, got: {k}"
261 ));
262 }
263 self.count_k = Some(k);
264 Ok(self)
265 }
266
267 pub fn with_duration_secs(mut self, seconds: u64) -> Self {
272 self.duration_secs = Some(seconds);
273 self
274 }
275
276 pub fn with_sample_ratio(mut self, ratio: u64) -> Self {
282 self.sample_ratio = Some(ratio);
283 self
284 }
285
286 pub fn with_slots(mut self, slots: Vec<u16>) -> Self {
307 self.slots = Some(slots);
308 self
309 }
310}
311
312impl ToRedisArgs for HotkeysOptions {
313 fn write_redis_args<W>(&self, out: &mut W)
314 where
315 W: ?Sized + RedisWrite,
316 {
317 out.write_arg(b"METRICS");
319 out.write_arg_fmt(self.metrics_count());
320
321 if self.cpu {
322 out.write_arg(b"CPU");
323 }
324
325 if self.net {
326 out.write_arg(b"NET");
327 }
328
329 if let Some(k) = self.count_k {
331 out.write_arg(b"COUNT");
332 out.write_arg_fmt(k);
333 }
334
335 if let Some(secs) = self.duration_secs {
337 out.write_arg(b"DURATION");
338 out.write_arg_fmt(secs);
339 }
340
341 if let Some(ratio) = self.sample_ratio {
343 out.write_arg(b"SAMPLE");
344 out.write_arg_fmt(ratio);
345 }
346
347 if let Some(ref slots) = self.slots {
349 out.write_arg(b"SLOTS");
350 out.write_arg_fmt(slots.len());
351 for slot in slots {
352 out.write_arg_fmt(slot);
353 }
354 }
355 }
356
357 fn num_of_args(&self) -> usize {
358 let mut n = 2;
360 n += self.cpu as usize;
361 n += self.net as usize;
362 if self.count_k.is_some() {
363 n += 2;
364 }
365 if self.duration_secs.is_some() {
366 n += 2;
367 }
368 if self.sample_ratio.is_some() {
369 n += 2;
370 }
371 if let Some(ref slots) = self.slots {
372 n += 2 + slots.len();
374 }
375 n
376 }
377}
378
379#[derive(Debug, Clone, PartialEq)]
381#[non_exhaustive]
382pub struct HotKeyEntry {
383 pub key: String,
385 pub value: u64,
387}
388
389#[derive(Debug, Clone, PartialEq)]
391#[non_exhaustive]
392pub struct SlotRange {
393 pub start: u16,
395 pub end: u16,
397}
398
399#[derive(Debug, Clone, PartialEq, Default)]
405#[non_exhaustive]
406pub struct HotkeysResponse {
407 pub tracking_active: bool,
409 pub sample_ratio: u64,
411 pub selected_slots: Vec<SlotRange>,
413 pub all_commands_all_slots_us: u64,
415 pub net_bytes_all_commands_all_slots: u64,
417 pub collection_start_time_unix_ms: u64,
419 pub collection_duration_ms: u64,
421 pub total_cpu_time_user_ms: Option<u64>,
423 pub total_cpu_time_sys_ms: Option<u64>,
425 pub total_net_bytes: Option<u64>,
427 pub by_cpu_time_us: Option<Vec<HotKeyEntry>>,
429 pub by_net_bytes: Option<Vec<HotKeyEntry>>,
431
432 pub sampled_commands_selected_slots_us: Option<u64>,
435 pub all_commands_selected_slots_us: Option<u64>,
437 pub net_bytes_sampled_commands_selected_slots: Option<u64>,
439 pub net_bytes_all_commands_selected_slots: Option<u64>,
441}
442
443fn strip_quotes(s: String) -> String {
445 if s.len() >= 2 && s.starts_with('"') && s.ends_with('"') {
446 s[1..s.len() - 1].to_string()
447 } else {
448 s
449 }
450}
451
452fn parse_hotkey_entries(arr: &[Value]) -> Result<Vec<HotKeyEntry>, ParsingError> {
454 use crate::types::from_redis_value_ref;
455
456 let mut entries = Vec::with_capacity(arr.len() / 2);
457
458 let mut iter = arr.iter();
459 while let Some(key_val) = iter.next() {
460 let key: String = from_redis_value_ref(key_val)?;
461 let key = strip_quotes(key);
463 let value: u64 = iter
464 .next()
465 .ok_or_else(|| ParsingError::from("Expected value after key in hotkey entry"))
466 .and_then(from_redis_value_ref)?;
467
468 entries.push(HotKeyEntry { key, value });
469 }
470
471 Ok(entries)
472}
473
474fn parse_slot_ranges(arr: &[Value]) -> Result<Vec<SlotRange>, ParsingError> {
476 use crate::types::from_redis_value_ref;
477
478 let mut ranges = Vec::with_capacity(arr.len());
479
480 for item in arr {
481 let Value::Array(range_arr) = item else {
482 crate::errors::invalid_type_error!("Expected array for slot range", item);
483 };
484
485 match range_arr.len() {
486 1 => {
487 let slot: u16 = from_redis_value_ref(&range_arr[0])?;
488 ranges.push(SlotRange {
489 start: slot,
490 end: slot,
491 });
492 }
493 n if n >= 2 => {
494 let start: u16 = from_redis_value_ref(&range_arr[0])?;
495 let end: u16 = from_redis_value_ref(&range_arr[1])?;
496 ranges.push(SlotRange { start, end });
497 }
498 _ => crate::errors::invalid_type_error!("Empty slot range entry", range_arr),
499 }
500 }
501
502 Ok(ranges)
503}
504
505impl FromRedisValue for HotkeysResponse {
506 fn from_redis_value(v: Value) -> Result<Self, ParsingError> {
507 use crate::types::from_redis_value;
508
509 let v = match v {
516 Value::Array(mut arr) if arr.len() == 1 => arr.remove(0),
517 other => other,
518 };
519
520 let mut fields: HashMap<String, Value> = match v {
523 Value::Array(arr) => {
524 let mut map = HashMap::new();
526 let mut iter = arr.into_iter();
527 while let Some(key) = iter.next() {
528 let key_str: String = from_redis_value(key)?;
529 let key_str = strip_quotes(key_str);
531 if let Some(val) = iter.next() {
532 map.insert(key_str, val);
533 }
534 }
535 map
536 }
537 Value::Map(pairs) => {
538 let mut map = HashMap::new();
540 for (k, v) in pairs {
541 let key_str: String = from_redis_value(k)?;
542 let key_str = strip_quotes(key_str);
543 map.insert(key_str, v);
544 }
545 map
546 }
547 _ => {
548 crate::errors::invalid_type_error!(
549 "Expected array or map response for HOTKEYS GET",
550 v
551 );
552 }
553 };
554
555 let mut response = HotkeysResponse::default();
556
557 if let Some(v) = fields.remove("tracking-active") {
559 response.tracking_active = from_redis_value::<i64>(v)? != 0;
560 }
561
562 if let Some(v) = fields.remove("sample-ratio") {
563 response.sample_ratio = from_redis_value(v)?;
564 }
565
566 if let Some(Value::Array(arr)) = fields.remove("selected-slots") {
567 response.selected_slots = parse_slot_ranges(&arr)?;
568 }
569
570 if let Some(v) = fields.remove("all-commands-all-slots-us") {
571 response.all_commands_all_slots_us = from_redis_value(v)?;
572 }
573
574 if let Some(v) = fields.remove("net-bytes-all-commands-all-slots") {
575 response.net_bytes_all_commands_all_slots = from_redis_value(v)?;
576 }
577
578 if let Some(v) = fields.remove("collection-start-time-unix-ms") {
579 response.collection_start_time_unix_ms = from_redis_value(v)?;
580 }
581
582 if let Some(v) = fields.remove("collection-duration-ms") {
583 response.collection_duration_ms = from_redis_value(v)?;
584 }
585
586 if let Some(v) = fields.remove("total-cpu-time-user-ms") {
588 response.total_cpu_time_user_ms = Some(from_redis_value(v)?);
589 }
590
591 if let Some(v) = fields.remove("total-cpu-time-sys-ms") {
592 response.total_cpu_time_sys_ms = Some(from_redis_value(v)?);
593 }
594
595 if let Some(Value::Array(arr)) = fields.remove("by-cpu-time-us") {
596 response.by_cpu_time_us = Some(parse_hotkey_entries(&arr)?);
597 }
598
599 if let Some(v) = fields.remove("total-net-bytes") {
601 response.total_net_bytes = Some(from_redis_value(v)?);
602 }
603
604 if let Some(Value::Array(arr)) = fields.remove("by-net-bytes") {
605 response.by_net_bytes = Some(parse_hotkey_entries(&arr)?);
606 }
607
608 if let Some(v) = fields.remove("sampled-commands-selected-slots-us") {
610 response.sampled_commands_selected_slots_us = Some(from_redis_value(v)?);
611 }
612
613 if let Some(v) = fields.remove("all-commands-selected-slots-us") {
614 response.all_commands_selected_slots_us = Some(from_redis_value(v)?);
615 }
616
617 if let Some(v) = fields.remove("net-bytes-sampled-commands-selected-slots") {
618 response.net_bytes_sampled_commands_selected_slots = Some(from_redis_value(v)?);
619 }
620
621 if let Some(v) = fields.remove("net-bytes-all-commands-selected-slots") {
622 response.net_bytes_all_commands_selected_slots = Some(from_redis_value(v)?);
623 }
624
625 Ok(response)
626 }
627}
628
629#[cfg(test)]
630mod tests {
631 use super::*;
632
633 #[test]
634 fn test_hotkeys_options_cpu_constructor() {
635 let opts = HotkeysOptions::new_with_cpu();
636 assert_eq!(opts.num_of_args(), 3); let args = opts.to_redis_args();
638 assert_eq!(args.len(), 3);
639 assert_eq!(args[0], b"METRICS");
640 assert_eq!(args[1], b"1");
641 assert_eq!(args[2], b"CPU");
642 }
643
644 #[test]
645 fn test_hotkeys_options_net_constructor() {
646 let opts = HotkeysOptions::new_with_net();
647 assert_eq!(opts.num_of_args(), 3); let args = opts.to_redis_args();
649 assert_eq!(args.len(), 3);
650 assert_eq!(args[0], b"METRICS");
651 assert_eq!(args[1], b"1");
652 assert_eq!(args[2], b"NET");
653 }
654
655 #[test]
656 fn test_hotkeys_options_cpu_and_net() {
657 let opts = HotkeysOptions::new_with_cpu().and_net();
658 assert_eq!(opts.num_of_args(), 4); let args = opts.to_redis_args();
660 assert_eq!(args.len(), 4);
661 assert_eq!(args[0], b"METRICS");
662 assert_eq!(args[1], b"2");
663 assert_eq!(args[2], b"CPU");
664 assert_eq!(args[3], b"NET");
665 }
666
667 #[test]
668 fn test_hotkeys_options_net_and_cpu() {
669 let opts = HotkeysOptions::new_with_net().and_cpu();
670 assert_eq!(opts.num_of_args(), 4); let args = opts.to_redis_args();
672 assert_eq!(args.len(), 4);
673 assert_eq!(args[0], b"METRICS");
674 assert_eq!(args[1], b"2");
675 assert_eq!(args[2], b"CPU");
677 assert_eq!(args[3], b"NET");
678 }
679
680 #[test]
681 fn test_hotkeys_options_with_duration() {
682 let opts = HotkeysOptions::new_with_cpu().with_duration_secs(60);
683 assert_eq!(opts.num_of_args(), 5); let args = opts.to_redis_args();
685 assert_eq!(args.len(), 5);
686 assert_eq!(args[0], b"METRICS");
687 assert_eq!(args[1], b"1");
688 assert_eq!(args[2], b"CPU");
689 assert_eq!(args[3], b"DURATION");
690 assert_eq!(args[4], b"60");
691 }
692
693 #[test]
694 fn test_hotkeys_options_with_count() {
695 let opts = HotkeysOptions::new_with_cpu().with_count(50).unwrap();
696 assert_eq!(opts.num_of_args(), 5); let args = opts.to_redis_args();
698 assert_eq!(args.len(), 5);
699 assert_eq!(args[0], b"METRICS");
700 assert_eq!(args[1], b"1");
701 assert_eq!(args[2], b"CPU");
702 assert_eq!(args[3], b"COUNT");
703 assert_eq!(args[4], b"50");
704 }
705
706 #[test]
707 fn test_hotkeys_options_with_count_min_valid() {
708 let opts = HotkeysOptions::new_with_cpu()
709 .with_count(HOTKEYS_COUNT_MIN)
710 .unwrap();
711 let args = opts.to_redis_args();
712 assert_eq!(args[3], b"COUNT");
713 assert_eq!(args[4], HOTKEYS_COUNT_MIN.to_string().as_bytes());
714 }
715
716 #[test]
717 fn test_hotkeys_options_with_count_max_valid() {
718 let opts = HotkeysOptions::new_with_cpu()
719 .with_count(HOTKEYS_COUNT_MAX)
720 .unwrap();
721 let args = opts.to_redis_args();
722 assert_eq!(args[3], b"COUNT");
723 assert_eq!(args[4], HOTKEYS_COUNT_MAX.to_string().as_bytes());
724 }
725
726 #[test]
727 fn test_hotkeys_options_with_count_too_low() {
728 let result = HotkeysOptions::new_with_cpu().with_count(HOTKEYS_COUNT_MIN - 1);
729 assert!(result.is_err());
730 assert!(result.unwrap_err().contains(&format!(
731 "COUNT must be between {HOTKEYS_COUNT_MIN} and {HOTKEYS_COUNT_MAX}"
732 )));
733 }
734
735 #[test]
736 fn test_hotkeys_options_with_count_too_high() {
737 let result = HotkeysOptions::new_with_cpu().with_count(HOTKEYS_COUNT_MAX + 1);
738 assert!(result.is_err());
739 assert!(result.unwrap_err().contains(&format!(
740 "COUNT must be between {HOTKEYS_COUNT_MIN} and {HOTKEYS_COUNT_MAX}"
741 )));
742 }
743
744 #[test]
745 fn test_hotkeys_options_with_sample() {
746 let opts = HotkeysOptions::new_with_cpu().with_sample_ratio(1000);
747 assert_eq!(opts.num_of_args(), 5); let args = opts.to_redis_args();
749 assert_eq!(args.len(), 5);
750 assert_eq!(args[0], b"METRICS");
751 assert_eq!(args[1], b"1");
752 assert_eq!(args[2], b"CPU");
753 assert_eq!(args[3], b"SAMPLE");
754 assert_eq!(args[4], b"1000");
755 }
756
757 #[test]
758 fn test_hotkeys_options_with_slots() {
759 let opts = HotkeysOptions::new_with_cpu().with_slots(vec![0, 100, 200]);
760 assert_eq!(opts.num_of_args(), 8); let args = opts.to_redis_args();
762 assert_eq!(args.len(), 8);
763 assert_eq!(args[0], b"METRICS");
764 assert_eq!(args[1], b"1");
765 assert_eq!(args[2], b"CPU");
766 assert_eq!(args[3], b"SLOTS");
767 assert_eq!(args[4], b"3");
768 assert_eq!(args[5], b"0");
769 assert_eq!(args[6], b"100");
770 assert_eq!(args[7], b"200");
771 }
772
773 #[test]
774 fn test_hotkeys_options_full() {
775 let opts = HotkeysOptions::new_with_cpu()
776 .and_net()
777 .with_count(50)
778 .unwrap()
779 .with_duration_secs(120)
780 .with_sample_ratio(500);
781 assert_eq!(opts.num_of_args(), 10);
783 let args = opts.to_redis_args();
784 assert_eq!(args[0], b"METRICS");
785 assert_eq!(args[1], b"2");
786 assert_eq!(args[2], b"CPU");
787 assert_eq!(args[3], b"NET");
788 assert_eq!(args[4], b"COUNT");
789 assert_eq!(args[5], b"50");
790 assert_eq!(args[6], b"DURATION");
791 assert_eq!(args[7], b"120");
792 assert_eq!(args[8], b"SAMPLE");
793 assert_eq!(args[9], b"500");
794 }
795
796 #[test]
797 fn test_hotkeys_response_parsing_resp2() {
798 use crate::Value;
799
800 let response = Value::Array(vec![
802 Value::BulkString(b"tracking-active".to_vec()),
803 Value::Int(1),
804 Value::BulkString(b"sample-ratio".to_vec()),
805 Value::Int(1),
806 Value::BulkString(b"selected-slots".to_vec()),
807 Value::Array(vec![Value::Array(vec![Value::Int(0), Value::Int(16383)])]),
808 Value::BulkString(b"all-commands-all-slots-us".to_vec()),
809 Value::Int(5000),
810 Value::BulkString(b"net-bytes-all-commands-all-slots".to_vec()),
811 Value::Int(2048),
812 Value::BulkString(b"collection-start-time-unix-ms".to_vec()),
813 Value::Int(1700000000000),
814 Value::BulkString(b"collection-duration-ms".to_vec()),
815 Value::Int(10000),
816 Value::BulkString(b"total-cpu-time-user-ms".to_vec()),
817 Value::Int(100),
818 Value::BulkString(b"total-cpu-time-sys-ms".to_vec()),
819 Value::Int(50),
820 Value::BulkString(b"by-cpu-time-us".to_vec()),
821 Value::Array(vec![
822 Value::BulkString(b"key1".to_vec()),
823 Value::Int(1500),
824 Value::BulkString(b"key2".to_vec()),
825 Value::Int(750),
826 ]),
827 ]);
828
829 let result = HotkeysResponse::from_redis_value(response).unwrap();
830
831 assert!(result.tracking_active);
832 assert_eq!(result.sample_ratio, 1);
833 assert_eq!(result.selected_slots.len(), 1);
834 assert_eq!(result.selected_slots[0].start, 0);
835 assert_eq!(result.selected_slots[0].end, 16383);
836 assert_eq!(result.all_commands_all_slots_us, 5000);
837 assert_eq!(result.net_bytes_all_commands_all_slots, 2048);
838 assert_eq!(result.collection_start_time_unix_ms, 1700000000000);
839 assert_eq!(result.collection_duration_ms, 10000);
840 assert_eq!(result.total_cpu_time_user_ms, Some(100));
841 assert_eq!(result.total_cpu_time_sys_ms, Some(50));
842
843 let cpu_keys = result.by_cpu_time_us.unwrap();
844 assert_eq!(cpu_keys.len(), 2);
845 assert_eq!(cpu_keys[0].key, "key1");
846 assert_eq!(cpu_keys[0].value, 1500);
847 assert_eq!(cpu_keys[1].key, "key2");
848 assert_eq!(cpu_keys[1].value, 750);
849 }
850
851 #[test]
852 fn test_hotkeys_response_parsing_resp3() {
853 use crate::Value;
854
855 let response = Value::Map(vec![
857 (
858 Value::BulkString(b"tracking-active".to_vec()),
859 Value::Int(1),
860 ),
861 (Value::BulkString(b"sample-ratio".to_vec()), Value::Int(1)),
862 (
863 Value::BulkString(b"selected-slots".to_vec()),
864 Value::Array(vec![Value::Array(vec![Value::Int(0), Value::Int(16383)])]),
865 ),
866 (
867 Value::BulkString(b"all-commands-all-slots-us".to_vec()),
868 Value::Int(5000),
869 ),
870 (
871 Value::BulkString(b"all-commands-selected-slots-us".to_vec()),
872 Value::Int(4000),
873 ),
874 (
875 Value::BulkString(b"net-bytes-all-commands-all-slots".to_vec()),
876 Value::Int(2048),
877 ),
878 (
879 Value::BulkString(b"net-bytes-all-commands-selected-slots".to_vec()),
880 Value::Int(1024),
881 ),
882 (
883 Value::BulkString(b"collection-start-time-unix-ms".to_vec()),
884 Value::Int(1700000000000),
885 ),
886 (
887 Value::BulkString(b"collection-duration-ms".to_vec()),
888 Value::Int(10000),
889 ),
890 (
891 Value::BulkString(b"total-cpu-time-user-ms".to_vec()),
892 Value::Int(100),
893 ),
894 (
895 Value::BulkString(b"total-cpu-time-sys-ms".to_vec()),
896 Value::Int(50),
897 ),
898 (
899 Value::BulkString(b"by-cpu-time-us".to_vec()),
900 Value::Array(vec![
901 Value::BulkString(b"key1".to_vec()),
902 Value::Int(1500),
903 Value::BulkString(b"key2".to_vec()),
904 Value::Int(750),
905 ]),
906 ),
907 ]);
908
909 let result = HotkeysResponse::from_redis_value(response).unwrap();
910
911 assert!(result.tracking_active);
912 assert_eq!(result.sample_ratio, 1);
913 assert_eq!(result.selected_slots.len(), 1);
914 assert_eq!(result.selected_slots[0].start, 0);
915 assert_eq!(result.selected_slots[0].end, 16383);
916 assert_eq!(result.all_commands_all_slots_us, 5000);
917 assert_eq!(result.all_commands_selected_slots_us, Some(4000));
918 assert_eq!(result.net_bytes_all_commands_all_slots, 2048);
919 assert_eq!(result.net_bytes_all_commands_selected_slots, Some(1024));
920 assert_eq!(result.collection_start_time_unix_ms, 1700000000000);
921 assert_eq!(result.collection_duration_ms, 10000);
922 assert_eq!(result.total_cpu_time_user_ms, Some(100));
923 assert_eq!(result.total_cpu_time_sys_ms, Some(50));
924
925 let cpu_keys = result.by_cpu_time_us.unwrap();
926 assert_eq!(cpu_keys.len(), 2);
927 assert_eq!(cpu_keys[0].key, "key1");
928 assert_eq!(cpu_keys[0].value, 1500);
929 assert_eq!(cpu_keys[1].key, "key2");
930 assert_eq!(cpu_keys[1].value, 750);
931 }
932
933 #[test]
934 fn test_hotkeys_response_parsing_with_net() {
935 use crate::Value;
936
937 let response = Value::Array(vec![
938 Value::BulkString(b"tracking-active".to_vec()),
939 Value::Int(0),
940 Value::BulkString(b"sample-ratio".to_vec()),
941 Value::Int(1),
942 Value::BulkString(b"selected-slots".to_vec()),
943 Value::Array(vec![]),
944 Value::BulkString(b"all-commands-all-slots-us".to_vec()),
945 Value::Int(0),
946 Value::BulkString(b"net-bytes-all-commands-all-slots".to_vec()),
947 Value::Int(4096),
948 Value::BulkString(b"collection-start-time-unix-ms".to_vec()),
949 Value::Int(1700000000000),
950 Value::BulkString(b"collection-duration-ms".to_vec()),
951 Value::Int(5000),
952 Value::BulkString(b"total-net-bytes".to_vec()),
953 Value::Int(8192),
954 Value::BulkString(b"by-net-bytes".to_vec()),
955 Value::Array(vec![
956 Value::BulkString(b"bigkey".to_vec()),
957 Value::Int(4096),
958 Value::BulkString(b"smallkey".to_vec()),
959 Value::Int(256),
960 ]),
961 ]);
962
963 let result = HotkeysResponse::from_redis_value(response).unwrap();
964
965 assert!(!result.tracking_active);
966 assert_eq!(result.total_net_bytes, Some(8192));
967
968 let net_keys = result.by_net_bytes.unwrap();
969 assert_eq!(net_keys.len(), 2);
970 assert_eq!(net_keys[0].key, "bigkey");
971 assert_eq!(net_keys[0].value, 4096);
972 assert_eq!(net_keys[1].key, "smallkey");
973 assert_eq!(net_keys[1].value, 256);
974 }
975
976 #[test]
977 fn test_hotkeys_response_nil() {
978 use crate::Value;
979 use crate::types::from_redis_value;
980
981 let response = Value::Nil;
986 let result: Option<HotkeysResponse> = from_redis_value(response).unwrap();
987 assert!(result.is_none());
988 }
989}