futu_gateway_core/bridge/push_health/
snapshot.rs1use std::fmt::Write as _;
2
3#[derive(Debug, Clone, serde::Serialize)]
5pub struct PushHealthSnapshot {
6 pub push_stream_healthy: bool,
7 pub last_push_received_at_ms: i64,
8 pub last_push_by_sub_type: Vec<PushSubTypeWatermark>,
9 pub consecutive_parse_errors: u32,
10 pub total_parse_errors: u64,
11 pub total_pushes_received: u64,
12 pub resubscribe_triggers: u64,
13 pub circuit_breaker_trips: u64,
14 pub circuit_breaker_tripped_at_ms: i64,
15 pub is_circuit_tripped_now: bool,
16 pub orphan_orders_detected: u64,
17 pub last_orphan_scan_at_ms: i64,
18 pub last_resubscribe_at_ms: i64,
19 pub f2_circuit_tripped_total: u64,
21 pub trade_requery_queue_dropped_total: u64,
24 pub last_trade_requery_drop_at_ms: i64,
27 pub last_resubscribe_partial_at_ms: i64,
30 pub resubscribe_partial_total: u64,
33 pub last_missed_keys_count: u32,
36 pub degraded_reason: Option<String>,
39}
40
41#[derive(Debug, Clone, serde::Serialize)]
43pub struct PushSubTypeWatermark {
44 pub sub_type: i32,
45 pub last_push_received_at_ms: i64,
46}
47
48impl PushHealthSnapshot {
49 #[must_use]
51 pub fn render_prometheus(&self) -> String {
52 let mut s = String::with_capacity(4096);
53
54 s.push_str(
55 "# HELP futu_gateway_push_health_stream_healthy Push stream health from PushHealth snapshot (1=healthy,0=degraded)\n# TYPE futu_gateway_push_health_stream_healthy gauge\n",
56 );
57 let healthy = u8::from(self.push_stream_healthy);
58 let degraded = u8::from(self.degraded_reason.is_some());
59 let _ = writeln!(s, "futu_gateway_push_health_stream_healthy {healthy}");
60
61 s.push_str(
62 "# HELP futu_gateway_push_health_last_push_received_at_ms Last successful push timestamp in Unix milliseconds (0=never)\n# TYPE futu_gateway_push_health_last_push_received_at_ms gauge\n",
63 );
64 let _ = writeln!(
65 s,
66 "futu_gateway_push_health_last_push_received_at_ms {}",
67 self.last_push_received_at_ms
68 );
69
70 s.push_str(
71 "# HELP futu_gateway_push_health_last_push_by_sub_type_at_ms Last regular QOT push dispatch timestamp by FTAPI SubType in Unix milliseconds\n# TYPE futu_gateway_push_health_last_push_by_sub_type_at_ms gauge\n",
72 );
73 for item in &self.last_push_by_sub_type {
74 let _ = writeln!(
75 s,
76 "futu_gateway_push_health_last_push_by_sub_type_at_ms{{sub_type=\"{}\"}} {}",
77 item.sub_type, item.last_push_received_at_ms
78 );
79 }
80
81 s.push_str(
82 "# HELP futu_gateway_push_health_consecutive_parse_errors Consecutive push parse errors\n# TYPE futu_gateway_push_health_consecutive_parse_errors gauge\n",
83 );
84 let _ = writeln!(
85 s,
86 "futu_gateway_push_health_consecutive_parse_errors {}",
87 self.consecutive_parse_errors
88 );
89
90 s.push_str(
91 "# HELP futu_gateway_push_health_total_parse_errors_total Total push parse errors\n# TYPE futu_gateway_push_health_total_parse_errors_total counter\n",
92 );
93 let _ = writeln!(
94 s,
95 "futu_gateway_push_health_total_parse_errors_total {}",
96 self.total_parse_errors
97 );
98
99 s.push_str(
100 "# HELP futu_gateway_push_health_total_pushes_received_total Total successfully parsed pushes\n# TYPE futu_gateway_push_health_total_pushes_received_total counter\n",
101 );
102 let _ = writeln!(
103 s,
104 "futu_gateway_push_health_total_pushes_received_total {}",
105 self.total_pushes_received
106 );
107
108 s.push_str(
109 "# HELP futu_gateway_push_health_resubscribe_triggers_total Push-health re-subscribe triggers\n# TYPE futu_gateway_push_health_resubscribe_triggers_total counter\n",
110 );
111 let _ = writeln!(
112 s,
113 "futu_gateway_push_health_resubscribe_triggers_total {}",
114 self.resubscribe_triggers
115 );
116
117 s.push_str(
118 "# HELP futu_gateway_push_health_circuit_breaker_trips_total Push parse circuit-breaker trips\n# TYPE futu_gateway_push_health_circuit_breaker_trips_total counter\n",
119 );
120 let _ = writeln!(
121 s,
122 "futu_gateway_push_health_circuit_breaker_trips_total {}",
123 self.circuit_breaker_trips
124 );
125
126 s.push_str(
127 "# HELP futu_gateway_push_health_circuit_breaker_tripped_at_ms Push parse circuit-breaker trip timestamp in Unix milliseconds (0=not tripped)\n# TYPE futu_gateway_push_health_circuit_breaker_tripped_at_ms gauge\n",
128 );
129 let _ = writeln!(
130 s,
131 "futu_gateway_push_health_circuit_breaker_tripped_at_ms {}",
132 self.circuit_breaker_tripped_at_ms
133 );
134
135 s.push_str(
136 "# HELP futu_gateway_push_health_circuit_breaker_tripped Push parse circuit-breaker current state (1=tripped,0=closed)\n# TYPE futu_gateway_push_health_circuit_breaker_tripped gauge\n",
137 );
138 let _ = writeln!(
139 s,
140 "futu_gateway_push_health_circuit_breaker_tripped {}",
141 u8::from(self.is_circuit_tripped_now)
142 );
143
144 s.push_str(
145 "# HELP futu_gateway_push_health_orphan_orders_detected_total Orphan in-flight orders detected by push health scan\n# TYPE futu_gateway_push_health_orphan_orders_detected_total counter\n",
146 );
147 let _ = writeln!(
148 s,
149 "futu_gateway_push_health_orphan_orders_detected_total {}",
150 self.orphan_orders_detected
151 );
152
153 s.push_str(
154 "# HELP futu_gateway_push_health_last_orphan_scan_at_ms Last orphan scan timestamp in Unix milliseconds (0=never)\n# TYPE futu_gateway_push_health_last_orphan_scan_at_ms gauge\n",
155 );
156 let _ = writeln!(
157 s,
158 "futu_gateway_push_health_last_orphan_scan_at_ms {}",
159 self.last_orphan_scan_at_ms
160 );
161
162 s.push_str(
163 "# HELP futu_gateway_push_health_last_resubscribe_at_ms Last re-subscribe timestamp in Unix milliseconds (0=never)\n# TYPE futu_gateway_push_health_last_resubscribe_at_ms gauge\n",
164 );
165 let _ = writeln!(
166 s,
167 "futu_gateway_push_health_last_resubscribe_at_ms {}",
168 self.last_resubscribe_at_ms
169 );
170
171 s.push_str(
172 "# HELP futu_gateway_push_health_f2_circuit_tripped_total TradeReQuery F2 account-level circuit trips\n# TYPE futu_gateway_push_health_f2_circuit_tripped_total counter\n",
173 );
174 let _ = writeln!(
175 s,
176 "futu_gateway_push_health_f2_circuit_tripped_total {}",
177 self.f2_circuit_tripped_total
178 );
179
180 s.push_str(
181 "# HELP futu_gateway_push_health_trade_requery_queue_dropped_total TradeReQuery notices dropped because worker queue was full or closed\n# TYPE futu_gateway_push_health_trade_requery_queue_dropped_total counter\n",
182 );
183 let _ = writeln!(
184 s,
185 "futu_gateway_push_health_trade_requery_queue_dropped_total {}",
186 self.trade_requery_queue_dropped_total
187 );
188
189 s.push_str(
190 "# HELP futu_gateway_push_health_last_trade_requery_drop_at_ms Last TradeReQuery queue drop timestamp in Unix milliseconds (0=never)\n# TYPE futu_gateway_push_health_last_trade_requery_drop_at_ms gauge\n",
191 );
192 let _ = writeln!(
193 s,
194 "futu_gateway_push_health_last_trade_requery_drop_at_ms {}",
195 self.last_trade_requery_drop_at_ms
196 );
197
198 s.push_str(
199 "# HELP futu_gateway_push_health_last_resubscribe_partial_at_ms Last partial re-subscribe failure timestamp in Unix milliseconds (0=never)\n# TYPE futu_gateway_push_health_last_resubscribe_partial_at_ms gauge\n",
200 );
201 let _ = writeln!(
202 s,
203 "futu_gateway_push_health_last_resubscribe_partial_at_ms {}",
204 self.last_resubscribe_partial_at_ms
205 );
206
207 s.push_str(
208 "# HELP futu_gateway_push_health_resubscribe_partial_total Partial re-subscribe failures\n# TYPE futu_gateway_push_health_resubscribe_partial_total counter\n",
209 );
210 let _ = writeln!(
211 s,
212 "futu_gateway_push_health_resubscribe_partial_total {}",
213 self.resubscribe_partial_total
214 );
215
216 s.push_str(
217 "# HELP futu_gateway_push_health_last_missed_keys_count Missing keys from the last partial re-subscribe\n# TYPE futu_gateway_push_health_last_missed_keys_count gauge\n",
218 );
219 let _ = writeln!(
220 s,
221 "futu_gateway_push_health_last_missed_keys_count {}",
222 self.last_missed_keys_count
223 );
224
225 s.push_str(
226 "# HELP futu_gateway_push_health_degraded Push health degraded flag derived from degraded_reason (1=degraded,0=healthy)\n# TYPE futu_gateway_push_health_degraded gauge\n",
227 );
228 let _ = writeln!(s, "futu_gateway_push_health_degraded {degraded}");
229
230 s
231 }
232}