1use core::{fmt, ops::Deref};
2
3use csv::{Error as CsvError, ReaderBuilder};
4use serde::Deserialize;
5use serde_enum_str::Deserialize_enum_str;
6use serde_json::{Error as SerdeJsonError, Map, Value};
7
8use crate::formats::json;
9
10pub const SVNAME_FRONTEND: &str = "FRONTEND";
12pub const SVNAME_BACKEND: &str = "BACKEND";
13
14#[derive(Deserialize, Debug, Clone)]
16#[serde(tag = "type")]
17pub enum Statistic {
18 #[serde(rename = "0")]
19 Frontend(FrontendStatistic),
20 #[serde(rename = "1")]
21 Backend(BackendStatistic),
22 #[serde(rename = "2")]
23 Server(ServerStatistic),
24 #[serde(rename = "3")]
25 Listener(ListenerStatistic),
26}
27
28impl Statistic {
29 pub fn as_frontend(&self) -> Option<&FrontendStatistic> {
30 match self {
31 Self::Frontend(s) => Some(s),
32 _ => None,
33 }
34 }
35
36 pub fn as_backend(&self) -> Option<&BackendStatistic> {
37 match self {
38 Self::Backend(s) => Some(s),
39 _ => None,
40 }
41 }
42
43 pub fn as_server(&self) -> Option<&ServerStatistic> {
44 match self {
45 Self::Server(s) => Some(s),
46 _ => None,
47 }
48 }
49
50 pub fn as_listener(&self) -> Option<&ListenerStatistic> {
51 match self {
52 Self::Listener(s) => Some(s),
53 _ => None,
54 }
55 }
56}
57
58#[derive(Deserialize, Debug, Clone)]
60pub struct ListenerStatistic {
61 pub pxname: Box<str>,
62 pub svname: Box<str>,
63 pub scur: usize,
65 pub smax: usize,
66 pub slim: usize,
67 pub stot: usize,
68 pub bin: usize,
70 pub bout: usize,
71 pub dreq: usize,
73 pub dresp: usize,
74 pub ereq: usize,
76 pub status: Status,
78 pub pid: usize,
80 pub iid: usize,
81 pub sid: usize,
82 }
84
85#[derive(Deserialize, Debug, Clone)]
87pub struct FrontendStatistic {
88 pub pxname: Box<str>,
89 pub scur: usize,
92 pub smax: usize,
93 pub slim: usize,
94 pub stot: usize,
95 pub bin: usize,
97 pub bout: usize,
98 pub dreq: usize,
100 pub dresp: usize,
101 pub ereq: usize,
103 pub status: Status,
105 pub pid: usize,
107 pub iid: usize,
108 pub rate: usize,
110 pub rate_lim: usize,
111 pub rate_max: usize,
112 pub hrsp_1xx: Option<usize>,
114 pub hrsp_2xx: Option<usize>,
115 pub hrsp_3xx: Option<usize>,
116 pub hrsp_4xx: Option<usize>,
117 pub hrsp_5xx: Option<usize>,
118 pub hrsp_other: Option<usize>,
119 pub req_rate: Option<usize>,
121 pub req_rate_max: Option<usize>,
122 pub req_tot: Option<usize>,
123 }
125
126#[derive(Deserialize, Debug, Clone)]
128pub struct BackendStatistic {
129 pub pxname: Box<str>,
130 pub qcur: usize,
133 pub qmax: usize,
134 pub scur: usize,
136 pub smax: usize,
137 pub slim: usize,
138 pub stot: usize,
139 pub bin: usize,
141 pub bout: usize,
142 pub dreq: usize,
144 pub dresp: usize,
145 pub econ: usize,
147 pub eresp: usize,
148 pub wretr: usize,
150 pub wredis: usize,
151 pub status: Status,
153 pub weight: usize,
155 pub act: usize,
157 pub bck: usize,
158 pub chkdown: usize,
160 pub lastchg: usize,
161 pub downtime: Option<usize>,
163 pub pid: usize,
165 pub iid: usize,
166 pub lbtot: usize,
168 pub rate: usize,
170 pub rate_max: usize,
171 pub hrsp_1xx: Option<usize>,
173 pub hrsp_2xx: Option<usize>,
174 pub hrsp_3xx: Option<usize>,
175 pub hrsp_4xx: Option<usize>,
176 pub hrsp_5xx: Option<usize>,
177 pub hrsp_other: Option<usize>,
178 pub req_tot: Option<usize>,
180 pub cli_abrt: usize,
182 pub srv_abrt: usize,
183 }
185
186#[derive(Deserialize, Debug, Clone)]
188pub struct ServerStatistic {
189 pub pxname: Box<str>,
190 pub svname: Box<str>,
191 pub qcur: usize,
193 pub qmax: usize,
194 pub scur: usize,
196 pub smax: usize,
197 pub slim: Option<usize>,
198 pub stot: usize,
199 pub bin: usize,
201 pub bout: usize,
202 pub dresp: usize,
204 pub econ: usize,
206 pub eresp: usize,
207 pub wretr: usize,
209 pub wredis: usize,
210 pub status: Status,
212 pub weight: usize,
214 pub act: usize,
216 pub bck: usize,
217 pub chkfail: Option<usize>,
219 pub chkdown: Option<usize>,
220 pub lastchg: Option<usize>,
221 pub downtime: Option<usize>,
222 pub qlimit: Option<usize>,
224 pub pid: usize,
226 pub iid: usize,
227 pub sid: usize,
228 pub throttle: Option<usize>,
230 pub lbtot: usize,
232 pub tracked: Option<usize>,
234 pub rate: usize,
236 pub rate_max: usize,
237 pub check_status: Option<CheckStatus>,
239 pub check_code: Option<usize>,
241 pub check_duration: Option<usize>,
242 pub hrsp_1xx: Option<usize>,
244 pub hrsp_2xx: Option<usize>,
245 pub hrsp_3xx: Option<usize>,
246 pub hrsp_4xx: Option<usize>,
247 pub hrsp_5xx: Option<usize>,
248 pub hrsp_other: Option<usize>,
249 #[serde(default)]
252 pub hanafail: Box<str>,
253 pub cli_abrt: usize,
255 pub srv_abrt: usize,
256 }
259
260#[derive(Deserialize_enum_str, Debug, Clone, PartialEq)]
264#[serde(rename_all = "snake_case")]
265pub enum Status {
266 #[serde(rename = "UP")]
267 UP,
268 #[serde(rename = "DOWN")]
269 DOWN,
270 #[serde(rename = "OPEN")]
271 OPEN,
272 #[serde(other)]
273 Other(String),
274}
275
276#[derive(Deserialize_enum_str, Debug, Clone, PartialEq)]
277#[serde(rename_all = "snake_case")]
278pub enum CheckStatus {
279 #[serde(rename = "L4TOUT")]
280 L4TOUT,
281 #[serde(rename = "* L4TOUT")]
282 LastL4TOUT,
283 #[serde(rename = "L4CON")]
284 L4CON,
285 #[serde(rename = "* L4CON")]
286 LastL4CON,
287 #[serde(rename = "L7OK")]
288 L7OK,
289 #[serde(rename = "* L7OK")]
290 LastL7OK,
291 #[serde(other)]
292 Other(String),
293}
294
295#[derive(Debug, Clone)]
299pub struct Statistics(pub Vec<Statistic>);
300
301impl Deref for Statistics {
302 type Target = Vec<Statistic>;
303
304 fn deref(&self) -> &Self::Target {
305 &self.0
306 }
307}
308
309impl Statistics {
310 pub fn from_csv_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, StatisticsFromCsvBytesError> {
311 let bytes = bytes.as_ref();
312 if &bytes[..1] != b"#" {
313 return Err(StatisticsFromCsvBytesError::Other(
314 "The first line begins with a sharp ('#')",
315 ));
316 }
317
318 let mut rdr = ReaderBuilder::new()
319 .has_headers(false)
320 .from_reader(&bytes[1..]);
321 let mut iter = rdr.records();
322
323 let (header, header_names) = if let Some(record) = iter.next() {
325 let mut record = record.map_err(StatisticsFromCsvBytesError::CsvParseFailed)?;
326 record.trim();
327 let list: Vec<Box<str>> = record
328 .deserialize(None)
329 .map_err(StatisticsFromCsvBytesError::HeaderDeFailed)?;
330 (record, list)
331 } else {
332 return Err(StatisticsFromCsvBytesError::HeaderMissing);
333 };
334
335 let header_type_position = header_names
336 .iter()
337 .position(|x| *x == "type".into())
338 .ok_or(StatisticsFromCsvBytesError::HeaderNameMismatch(
339 "type missing",
340 ))?;
341 let header_svname_position = header_names
342 .iter()
343 .position(|x| *x == "svname".into())
344 .ok_or(StatisticsFromCsvBytesError::HeaderNameMismatch(
345 "svname missing",
346 ))?;
347
348 let mut inner = vec![];
349 for (i, record) in iter.enumerate() {
350 let record = record.map_err(StatisticsFromCsvBytesError::CsvParseFailed)?;
351
352 let r#type = record.get(header_type_position).ok_or_else(|| {
353 StatisticsFromCsvBytesError::RowValueMismatch(
354 format!(
355 "line:{} position:{} type missing",
356 i + 1,
357 header_type_position
358 )
359 .into(),
360 )
361 })?;
362 let svname = record.get(header_svname_position).ok_or_else(|| {
363 StatisticsFromCsvBytesError::RowValueMismatch(
364 format!(
365 "line:{} position:{} svname missing",
366 i + 1,
367 header_svname_position
368 )
369 .into(),
370 )
371 })?;
372
373 match r#type {
374 "0" => {
375 if svname != SVNAME_FRONTEND {
376 return Err(StatisticsFromCsvBytesError::RowValueMismatch(
377 format!(
378 "line:{} svname:{} svname should eq {}",
379 i + 1,
380 svname,
381 SVNAME_FRONTEND,
382 )
383 .into(),
384 ));
385 }
386
387 let row: FrontendStatistic = record
388 .deserialize(Some(&header))
389 .map_err(StatisticsFromCsvBytesError::RowDeFailed)?;
390 inner.push(Statistic::Frontend(row));
391 }
392 "1" => {
393 if svname != SVNAME_BACKEND {
394 return Err(StatisticsFromCsvBytesError::RowValueMismatch(
395 format!(
396 "line:{} svname:{} svname should eq {}",
397 i + 1,
398 svname,
399 SVNAME_BACKEND,
400 )
401 .into(),
402 ));
403 }
404
405 let row: BackendStatistic = record
406 .deserialize(Some(&header))
407 .map_err(StatisticsFromCsvBytesError::RowDeFailed)?;
408 inner.push(Statistic::Backend(row));
409 }
410 "2" => {
411 let row: ServerStatistic = record
412 .deserialize(Some(&header))
413 .map_err(StatisticsFromCsvBytesError::RowDeFailed)?;
414 inner.push(Statistic::Server(row));
415 }
416 "4" => {
417 let row: ListenerStatistic = record
418 .deserialize(Some(&header))
419 .map_err(StatisticsFromCsvBytesError::RowDeFailed)?;
420 inner.push(Statistic::Listener(row));
421 }
422 _ => return Err(StatisticsFromCsvBytesError::UnknownType),
423 }
424 }
425
426 Ok(Self(inner))
427 }
428
429 pub fn from_json_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, StatisticsFromJsonBytesError> {
430 let bytes = bytes.as_ref();
431
432 let output = serde_json::from_slice::<JsonOutput>(bytes)
433 .map_err(StatisticsFromJsonBytesError::DeOutputFailed)?;
434
435 let array: Vec<Value> = output
436 .0
437 .into_iter()
438 .map(|x| {
439 Value::Object(
440 x.into_iter()
441 .map(|y| {
442 let v = match y.field.name.as_ref() {
443 "type" => Value::from(y.value.value_to_string()),
444 _ => Value::from(&y.value),
445 };
446
447 (y.field.name.to_string(), v)
448 })
449 .collect::<Map<String, Value>>(),
450 )
451 })
452 .collect();
453
454 let inner = serde_json::from_value::<Vec<Statistic>>(Value::Array(array))
455 .map_err(StatisticsFromJsonBytesError::DeFailed)?;
456
457 Ok(Self(inner))
458 }
459}
460
461#[derive(Debug)]
463pub enum StatisticsFromCsvBytesError {
464 CsvParseFailed(CsvError),
465 HeaderMissing,
466 HeaderDeFailed(CsvError),
467 HeaderNameMismatch(&'static str),
468 RowDeFailed(CsvError),
469 RowValueMismatch(Box<str>),
470 UnknownType,
471 Other(&'static str),
472}
473
474impl fmt::Display for StatisticsFromCsvBytesError {
475 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
476 write!(f, "{:?}", self)
477 }
478}
479
480impl std::error::Error for StatisticsFromCsvBytesError {}
481
482#[derive(Debug)]
484pub enum StatisticsFromJsonBytesError {
485 DeOutputFailed(SerdeJsonError),
486 DeFailed(SerdeJsonError),
487}
488
489impl fmt::Display for StatisticsFromJsonBytesError {
490 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
491 write!(f, "{:?}", self)
492 }
493}
494
495impl std::error::Error for StatisticsFromJsonBytesError {}
496
497#[derive(Deserialize, Debug, Clone)]
501pub struct JsonOutput(pub Vec<Vec<JsonOutputItem>>);
502
503#[derive(Deserialize, Debug, Clone)]
504pub struct JsonOutputItem {
505 #[serde(rename = "objType")]
506 pub obj_type: Box<str>,
507 #[serde(rename = "proxyId")]
508 pub proxy_id: usize,
509 pub id: usize,
510 pub field: json::Field,
511 #[serde(rename = "processNum")]
512 pub process_num: usize,
513 pub tags: json::Tags,
514 pub value: json::Value,
515}
516
517#[cfg(test)]
518mod tests {
519 use super::*;
520
521 use std::fs;
522
523 #[test]
524 fn test_statistics_from_csv_bytes() {
525 let bytes = include_bytes!("../tests/files/2_5_5_show_stat.csv");
526
527 let statistics = Statistics::from_csv_bytes(bytes).unwrap();
528
529 assert_eq!(statistics.len(), 12);
530 assert_eq!(
531 statistics[0].as_frontend().unwrap().pxname,
532 "http-frontend".into()
533 );
534 assert_eq!(
535 statistics[1].as_server().unwrap().svname,
536 "http-backend-srv-1".into()
537 );
538 assert_eq!(
539 statistics[2].as_backend().unwrap().pxname,
540 "http-backend".into()
541 );
542 }
543
544 #[test]
545 fn test_statistics_from_csv_bytes_with_match_files() {
546 for entry in fs::read_dir("tests/files").unwrap() {
547 let entry = entry.unwrap();
548 let path = entry.path();
549
550 if path.is_file() && path.to_str().unwrap().ends_with("show_stat.csv") {
551 let bytes = fs::read(&path).unwrap();
552
553 let _ = Statistics::from_csv_bytes(bytes).unwrap();
554
555 println!("file {:?}", path);
556 }
557 }
558 }
559
560 #[test]
561 fn test_statistics_from_json_bytes() {
562 let bytes = include_bytes!("../tests/files/2_5_5_show_stat.json");
563
564 let statistics = Statistics::from_json_bytes(bytes).unwrap();
565
566 assert_eq!(statistics.len(), 12);
567 assert_eq!(
568 statistics[0].as_frontend().unwrap().pxname,
569 "http-frontend".into()
570 );
571 assert_eq!(
572 statistics[1].as_server().unwrap().svname,
573 "http-backend-srv-1".into()
574 );
575 assert_eq!(
576 statistics[2].as_backend().unwrap().pxname,
577 "http-backend".into()
578 );
579 }
580}