use std::collections::HashMap;
use flowscope::driver::SlotMessage;
use flowscope::driver::{Driver, Event};
use flowscope::extract::{FiveTuple, FiveTupleKey};
use flowscope::http::{HttpMessage, HttpParser};
use flowscope::pcap::PcapFlowSource;
#[derive(Default, Clone, Copy)]
struct Bucket {
info: u32, success: u32, redirect: u32, client_err: u32, server_err: u32, }
impl Bucket {
fn add_status(&mut self, status: u16) {
match status / 100 {
1 => self.info += 1,
2 => self.success += 1,
3 => self.redirect += 1,
4 => self.client_err += 1,
5 => self.server_err += 1,
_ => {}
}
}
fn total(&self) -> u32 {
self.info + self.success + self.redirect + self.client_err + self.server_err
}
fn error_rate(&self) -> f64 {
let errs = self.client_err + self.server_err;
let total = self.total();
if total == 0 {
0.0
} else {
errs as f64 / total as f64
}
}
}
fn main() -> flowscope::Result<()> {
let path = std::env::args()
.nth(1)
.unwrap_or_else(|| "tests/data/http_session.pcap".to_string());
let mut builder = Driver::builder(FiveTuple::bidirectional());
let mut http_slot = builder.session_on_ports(HttpParser::default(), [80, 8080]);
let mut driver = builder.build();
let mut host_per_flow: HashMap<FiveTupleKey, String> = HashMap::new();
let mut buckets: HashMap<String, Bucket> = HashMap::new();
let mut events: Vec<Event<FiveTupleKey>> = Vec::new();
let mut msgs: Vec<SlotMessage<HttpMessage, FiveTupleKey>> = Vec::new();
for owned in PcapFlowSource::open(&path)?.views() {
let owned = owned?;
events.clear();
driver.track_into(&owned, &mut events);
msgs.clear();
http_slot.drain(&mut msgs);
for m in msgs.drain(..) {
handle(&mut host_per_flow, &mut buckets, m);
}
}
events.clear();
driver.finish_into(&mut events);
msgs.clear();
http_slot.drain(&mut msgs);
for m in msgs.drain(..) {
handle(&mut host_per_flow, &mut buckets, m);
}
let mut rows: Vec<_> = buckets.iter().collect();
rows.sort_by(|(_, a), (_, b)| b.error_rate().partial_cmp(&a.error_rate()).unwrap());
println!(
"{:<40} {:>5} {:>5} {:>5} {:>5} {:>5} {:>8}",
"host", "1xx", "2xx", "3xx", "4xx", "5xx", "err_rate"
);
for (host, b) in rows.iter().take(20) {
println!(
"{:<40} {:>5} {:>5} {:>5} {:>5} {:>5} {:>7.1}%",
truncate(host, 40),
b.info,
b.success,
b.redirect,
b.client_err,
b.server_err,
b.error_rate() * 100.0,
);
}
Ok(())
}
fn handle(
host_per_flow: &mut HashMap<FiveTupleKey, String>,
buckets: &mut HashMap<String, Bucket>,
m: SlotMessage<HttpMessage, FiveTupleKey>,
) {
match m.message {
HttpMessage::Request(req) => {
if let Some(host) = req.host() {
host_per_flow.insert(m.key, host.to_string());
}
}
HttpMessage::Response(resp) => {
let host = host_per_flow
.get(&m.key)
.cloned()
.unwrap_or_else(|| "(unknown)".to_string());
buckets.entry(host).or_default().add_status(resp.status);
}
}
}
fn truncate(s: &str, n: usize) -> String {
if s.len() <= n {
s.to_string()
} else {
format!("{}…", &s[..n - 1])
}
}