rocketmq_remoting/protocol/admin/
topic_stats_table.rs1use std::collections::HashMap;
16
17use rocketmq_common::common::message::message_queue::MessageQueue;
18use serde::Deserialize;
19use serde::Serialize;
20use serde_json_any_key::*;
21
22use crate::protocol::admin::consume_stats::normalize_nonstandard_offset_table_keys;
23use crate::protocol::admin::topic_offset::TopicOffset;
24use crate::protocol::RemotingDeserializable;
25
26#[derive(Debug, Clone, Serialize, Deserialize, Default)]
27#[serde(rename_all = "camelCase")]
28pub struct TopicStatsTable {
29 topic_put_tps: f64,
30 #[serde(with = "any_key_map")]
31 offset_table: HashMap<MessageQueue, TopicOffset>,
32}
33
34impl TopicStatsTable {
35 pub fn new() -> Self {
36 Self {
37 topic_put_tps: 0.0,
38 offset_table: HashMap::new(),
39 }
40 }
41
42 pub fn get_topic_put_tps(&self) -> f64 {
43 self.topic_put_tps
44 }
45
46 pub fn set_topic_put_tps(&mut self, topic_put_tps: f64) {
47 self.topic_put_tps = topic_put_tps;
48 }
49
50 pub fn get_offset_table(&self) -> &HashMap<MessageQueue, TopicOffset> {
51 &self.offset_table
52 }
53
54 pub fn get_offset_table_mut(&mut self) -> &mut HashMap<MessageQueue, TopicOffset> {
55 &mut self.offset_table
56 }
57
58 pub fn into_offset_table(self) -> HashMap<MessageQueue, TopicOffset> {
59 self.offset_table
60 }
61
62 pub fn set_offset_table(&mut self, offset_table: HashMap<MessageQueue, TopicOffset>) {
63 self.offset_table = offset_table;
64 }
65
66 pub fn decode(body: &[u8]) -> rocketmq_error::RocketMQResult<Self> {
67 match <Self as RemotingDeserializable>::decode(body) {
68 Ok(stats) => Ok(stats),
69 Err(error) => {
70 let Ok(raw_body) = std::str::from_utf8(body) else {
71 return Err(error);
72 };
73 let normalized_body = normalize_nonstandard_offset_table_keys(raw_body);
74 if normalized_body == raw_body {
75 return Err(error);
76 }
77 <Self as RemotingDeserializable>::decode_str(&normalized_body)
78 }
79 }
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 fn create_custom_mq(topic: &str, queue_id: i32) -> MessageQueue {
88 let json = format!(
89 r#"{{"topic": "{}", "brokerName": "default", "queueId": {}}}"#,
90 topic, queue_id
91 );
92 serde_json::from_str(&json).unwrap()
93 }
94
95 #[test]
96 fn test_topic_stats_table_initialization() {
97 let table = TopicStatsTable::new();
98 assert!(table.get_offset_table().is_empty());
99 }
100
101 #[test]
102 fn test_message_queue_default_new() {
103 let mq = MessageQueue::new();
104 let json = serde_json::to_value(&mq).unwrap();
105 assert_eq!(json["queueId"], 0);
106 assert_eq!(json["topic"], "");
107 }
108
109 #[test]
110 fn test_set_and_get_offsets() {
111 let mut table = TopicStatsTable::new();
112 let mut map = HashMap::new();
113
114 let mq = create_custom_mq("test_topic", 5);
115 let mut offset = TopicOffset::new();
116 offset.set_min_offset(100);
117 offset.set_max_offset(200);
118
119 map.insert(mq.clone(), offset);
120 table.set_offset_table(map);
121 let result_table = table.get_offset_table();
122 assert_eq!(result_table.len(), 1);
123
124 let retrieved_offset = result_table.get(&mq).expect("MQ should exist in map");
125 assert_eq!(retrieved_offset.get_min_offset(), 100);
126 assert_eq!(retrieved_offset.get_max_offset(), 200);
127 }
128
129 #[test]
130 fn test_serialization_cycle_with_any_key() {
131 let mut table = TopicStatsTable::new();
132 let mut map = HashMap::new();
133
134 let mq = create_custom_mq("order_topic", 1);
135 let mut offset = TopicOffset::new();
136 offset.set_last_update_timestamp(11111111);
137
138 map.insert(mq, offset);
139 table.set_offset_table(map);
140
141 let serialized = serde_json::to_string(&table).expect("Serialization failed");
142
143 assert!(serialized.contains("offsetTable"));
144
145 let deserialized: TopicStatsTable = serde_json::from_str(&serialized).expect("Deserialization failed");
146 assert_eq!(deserialized.get_offset_table().len(), 1);
147
148 let offset_val = deserialized.get_offset_table().values().next().unwrap().clone();
149 assert_eq!(offset_val.get_last_update_timestamp(), 11111111);
150 }
151
152 #[test]
153 fn test_decode_java_fastjson_message_queue_keys() {
154 let body = br#"{"offsetTable":{{"topic":"TBW102","brokerName":"broker-a","queueId":0}:{"minOffset":0,"maxOffset":0,"lastUpdateTimestamp":0}},"topicPutTps":1.5}"#;
155
156 let table = TopicStatsTable::decode(body).expect("decode Java fastjson topic stats");
157
158 let queue = MessageQueue::from_parts("TBW102", "broker-a", 0);
159 let offset = table
160 .get_offset_table()
161 .get(&queue)
162 .expect("queue offset should decode");
163 assert_eq!(offset.get_min_offset(), 0);
164 assert_eq!(offset.get_max_offset(), 0);
165 assert_eq!(table.get_topic_put_tps(), 1.5);
166 }
167
168 #[test]
169 fn test_topic_offset_display_format() {
170 let mut offset = TopicOffset::new();
171 offset.set_min_offset(5);
172 offset.set_max_offset(15);
173
174 let output = format!("{}", offset);
175 let expected = "TopicOffset{min_offset=5, max_offset=15, last_update_timestamp=0}";
176 assert_eq!(output, expected);
177 }
178}