rocketmq_protocol/protocol/admin/
consume_stats_list.rs1use std::collections::HashMap;
16
17use cheetah_string::CheetahString;
18use serde::Deserialize;
19use serde::Serialize;
20
21use super::consume_stats::normalize_nonstandard_offset_table_keys;
22use super::consume_stats::ConsumeStats;
23use crate::protocol::RemotingDeserializable;
24
25#[derive(Serialize, Deserialize)]
26pub struct ConsumeStatsList {
27 #[serde(rename = "consumeStatsList")]
28 pub consume_stats_list: Vec<HashMap<CheetahString, Vec<ConsumeStats>>>,
29
30 #[serde(rename = "brokerAddr")]
31 pub broker_addr: Option<CheetahString>,
32
33 #[serde(rename = "totalDiff")]
34 pub total_diff: i64,
35
36 #[serde(rename = "totalInflightDiff")]
37 pub total_inflight_diff: i64,
38}
39
40impl ConsumeStatsList {
41 pub fn encode_java_compatible(&self) -> rocketmq_error::Result<Vec<u8>> {
42 Ok(self.to_java_compatible_json()?.into_bytes())
43 }
44
45 pub fn to_java_compatible_json(&self) -> rocketmq_error::Result<String> {
46 let mut body = String::new();
47 body.push_str("{\"consumeStatsList\":[");
48
49 for (list_index, group_map) in self.consume_stats_list.iter().enumerate() {
50 if list_index > 0 {
51 body.push(',');
52 }
53 body.push('{');
54 for (group_index, (group, stats_list)) in group_map.iter().enumerate() {
55 if group_index > 0 {
56 body.push(',');
57 }
58 body.push_str(
59 &serde_json::to_string(group)
60 .map_err(|error| crate::error::serialization_source("serialize", "JSON", error))?,
61 );
62 body.push(':');
63 body.push('[');
64 for (stats_index, stats) in stats_list.iter().enumerate() {
65 if stats_index > 0 {
66 body.push(',');
67 }
68 body.push_str(&stats.to_java_compatible_json()?);
69 }
70 body.push(']');
71 }
72 body.push('}');
73 }
74
75 body.push_str("],\"brokerAddr\":");
76 match &self.broker_addr {
77 Some(broker_addr) => body.push_str(
78 &serde_json::to_string(broker_addr)
79 .map_err(|error| crate::error::serialization_source("serialize", "JSON", error))?,
80 ),
81 None => body.push_str("null"),
82 }
83 body.push_str(",\"totalDiff\":");
84 body.push_str(&self.total_diff.to_string());
85 body.push_str(",\"totalInflightDiff\":");
86 body.push_str(&self.total_inflight_diff.to_string());
87 body.push('}');
88 Ok(body)
89 }
90
91 pub fn decode(body: &[u8]) -> rocketmq_error::Result<Self> {
92 match <Self as RemotingDeserializable>::decode(body) {
93 Ok(stats) => Ok(stats),
94 Err(error) => {
95 let Ok(raw_body) = std::str::from_utf8(body) else {
96 return Err(error);
97 };
98 let normalized_body = normalize_nonstandard_offset_table_keys(raw_body);
99 if normalized_body == raw_body {
100 return Err(error);
101 }
102 <Self as RemotingDeserializable>::decode_str(&normalized_body)
103 }
104 }
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use serde_json;
111
112 use super::*;
113
114 #[test]
115 fn consume_status_list_serialization() {
116 let mut map = HashMap::new();
117 let consume_stats_list = vec![ConsumeStats {
118 offset_table: HashMap::new(),
119 consume_tps: 1.2,
120 }];
121 map.insert(CheetahString::from("group1"), consume_stats_list);
122 let consume_status_list = ConsumeStatsList {
123 consume_stats_list: vec![map],
124 broker_addr: Some(CheetahString::from("addr")),
125 total_diff: 2,
126 total_inflight_diff: 1,
127 };
128 let serialized = serde_json::to_string(&consume_status_list).unwrap();
129 assert!(serialized.contains("\"consumeStatsList\""));
130 let deserialized: ConsumeStatsList = serde_json::from_str(&serialized).unwrap();
131 assert_eq!(deserialized.broker_addr.unwrap(), CheetahString::from("addr"));
132 assert_eq!(deserialized.total_diff, 2);
133 assert_eq!(deserialized.total_inflight_diff, 1);
134 let a = deserialized.consume_stats_list[0]
135 .get(&CheetahString::from("group1"))
136 .unwrap();
137 assert_eq!(a[0].consume_tps, 1.2);
138 }
139
140 #[test]
141 fn consume_status_list_java_compatible_serialization() {
142 use rocketmq_model::message::MessageQueue;
143
144 let mut stats = ConsumeStats::new();
145 stats
146 .get_offset_table_mut()
147 .insert(MessageQueue::from_parts("TopicTest", "broker-a", 0), Default::default());
148 let mut map = HashMap::new();
149 map.insert(CheetahString::from("group1"), vec![stats]);
150 let list = ConsumeStatsList {
151 consume_stats_list: vec![map],
152 broker_addr: Some(CheetahString::from("127.0.0.1:10911")),
153 total_diff: 0,
154 total_inflight_diff: 0,
155 };
156
157 let encoded = String::from_utf8(list.encode_java_compatible().expect("encode consume stats list"))
158 .expect("utf8 consume stats list");
159
160 assert!(encoded.contains(r#""consumeStatsList":[{"group1":[{"offsetTable":{{"topic":"TopicTest""#));
161 assert!(!encoded.contains(r#""{\"topic\""#));
162
163 let decoded = ConsumeStatsList::decode(encoded.as_bytes()).expect("decode consume stats list");
164 let group = decoded.consume_stats_list[0]
165 .get(&CheetahString::from("group1"))
166 .expect("group1 stats");
167 assert_eq!(group[0].offset_table.len(), 1);
168 }
169}