diff options
author | Stuart Stock <stuart@int08h.com> | 2019-02-09 08:04:28 -0600 |
---|---|---|
committer | Stuart Stock <stuart@int08h.com> | 2019-02-09 08:04:28 -0600 |
commit | a5fc96c00d6edc23811933e73b0175e8bab1ccb1 (patch) | |
tree | 32a5e9058407151f7f639b555c7bd3b5b114df56 /src/stats | |
parent | 194808137ffe18f33bb804b82475a64117d5b247 (diff) | |
download | roughenough-a5fc96c00d6edc23811933e73b0175e8bab1ccb1.zip |
Refactor stats to distinct mod and separate impls
Diffstat (limited to 'src/stats')
-rw-r--r-- | src/stats/aggregated.rs | 103 | ||||
-rw-r--r-- | src/stats/mod.rs | 268 | ||||
-rw-r--r-- | src/stats/per_client.rs | 171 |
3 files changed, 296 insertions, 246 deletions
diff --git a/src/stats/aggregated.rs b/src/stats/aggregated.rs index e69de29..afff109 100644 --- a/src/stats/aggregated.rs +++ b/src/stats/aggregated.rs @@ -0,0 +1,103 @@ +// Copyright 2017-2019 int08h LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use hashbrown::HashMap; +use hashbrown::hash_map::Iter; + +use std::net::IpAddr; + +use crate::stats::ClientStatEntry; +use crate::stats::ServerStats; + +/// +/// Implementation of `ServerStats` that provides high-level aggregated client statistics. No +/// per-client statistic are maintained and runtime memory use is constant. +/// +#[allow(dead_code)] +pub struct AggregatedStats { + valid_requests: u64, + invalid_requests: u64, + health_checks: u64, + responses_sent: u64, + bytes_sent: usize, + empty_map: HashMap<IpAddr, ClientStatEntry>, +} + +impl AggregatedStats { + + #[allow(dead_code)] + pub fn new() -> Self { + AggregatedStats { + valid_requests: 0, + invalid_requests: 0, + health_checks: 0, + responses_sent: 0, + bytes_sent: 0, + empty_map: HashMap::new() + } + } +} + +impl ServerStats for AggregatedStats { + fn add_valid_request(&mut self, _: &IpAddr) { + self.valid_requests += 1 + } + + fn add_invalid_request(&mut self, _: &IpAddr) { + self.invalid_requests += 1 + } + + fn add_health_check(&mut self, _: &IpAddr) { + self.health_checks += 1 + } + + fn add_response(&mut self, _: &IpAddr, bytes_sent: usize) { + self.bytes_sent += bytes_sent; + self.responses_sent += 1; + } + + fn total_valid_requests(&self) -> u64 { + self.valid_requests + } + + fn total_invalid_requests(&self) -> u64 { + self.invalid_requests + } + + fn total_health_checks(&self) -> u64 { + self.health_checks + } + + fn total_responses_sent(&self) -> u64 { + self.responses_sent + } + + fn total_bytes_sent(&self) -> usize { + self.bytes_sent + } + + fn total_unique_clients(&self) -> u64 { + 0 + } + + fn get_client_stats(&self, _addr: &IpAddr) -> Option<&ClientStatEntry> { + None + } + + fn iter(&self) -> Iter<IpAddr, ClientStatEntry> { + self.empty_map.iter() + } + + fn clear(&mut self) {} +} diff --git a/src/stats/mod.rs b/src/stats/mod.rs index 013b7ea..29e7ad8 100644 --- a/src/stats/mod.rs +++ b/src/stats/mod.rs @@ -16,41 +16,15 @@ //! Facilities for tracking client requests to the server //! -use hashbrown::HashMap; -use hashbrown::hash_map::Iter; - -use std::net::IpAddr; - -/// -/// Implementations of this trait record client activity -/// -pub trait ServerStats { - fn add_valid_request(&mut self, addr: &IpAddr); - - fn add_invalid_request(&mut self, addr: &IpAddr); - - fn add_health_check(&mut self, addr: &IpAddr); - - fn add_response(&mut self, addr: &IpAddr, bytes_sent: usize); +mod aggregated; +mod per_client; - fn total_valid_requests(&self) -> u64; +pub use crate::stats::aggregated::AggregatedStats; +pub use crate::stats::per_client::PerClientStats; - fn total_invalid_requests(&self) -> u64; - - fn total_health_checks(&self) -> u64; - - fn total_responses_sent(&self) -> u64; - - fn total_bytes_sent(&self) -> usize; - - fn total_unique_clients(&self) -> u64; - - fn get_client_stats(&self, addr: &IpAddr) -> Option<&ClientStatEntry>; - - fn iter(&self) -> Iter<IpAddr, ClientStatEntry>; +use hashbrown::hash_map::Iter; - fn clear(&mut self); -} +use std::net::IpAddr; /// /// Specific metrics tracked per each client @@ -77,234 +51,36 @@ impl ClientStatEntry { } /// -/// Implementation of `ServerStats` that provides granular per-client request/response counts. -/// -/// Maintains a maximum of `MAX_CLIENTS` unique entries to bound memory use. Excess -/// entries beyond `MAX_CLIENTS` are ignored and `num_overflows` is incremented. +/// Implementations of this trait record client activity /// -pub struct PerClientStats { - clients: HashMap<IpAddr, ClientStatEntry>, - num_overflows: u64, - max_clients: usize, -} - -impl PerClientStats { - - /// Maximum number of stats entries to maintain to prevent - /// unbounded memory growth. - pub const MAX_CLIENTS: usize = 1_000_000; - - pub fn new() -> Self { - PerClientStats { - clients: HashMap::with_capacity(128), - num_overflows: 0, - max_clients: PerClientStats::MAX_CLIENTS, - } - } - - // visible for testing - #[cfg(test)] - fn with_limit(limit: usize) -> Self { - PerClientStats { - clients: HashMap::with_capacity(128), - num_overflows: 0, - max_clients: limit, - } - } - - #[inline] - fn too_many_entries(&mut self) -> bool { - let too_big = self.clients.len() >= self.max_clients; - - if too_big { - self.num_overflows += 1; - } - - return too_big; - } - - #[allow(dead_code)] - pub fn num_overflows(&self) -> u64 { - self.num_overflows - } -} - -impl ServerStats for PerClientStats { - fn add_valid_request(&mut self, addr: &IpAddr) { - if self.too_many_entries() { - return; - } - self.clients - .entry(*addr) - .or_insert_with(ClientStatEntry::new) - .valid_requests += 1; - } - - fn add_invalid_request(&mut self, addr: &IpAddr) { - if self.too_many_entries() { - return; - } - self.clients - .entry(*addr) - .or_insert_with(ClientStatEntry::new) - .invalid_requests += 1; - } - - fn add_health_check(&mut self, addr: &IpAddr) { - if self.too_many_entries() { - return; - } - self.clients - .entry(*addr) - .or_insert_with(ClientStatEntry::new) - .health_checks += 1; - } - - fn add_response(&mut self, addr: &IpAddr, bytes_sent: usize) { - if self.too_many_entries() { - return; - } - let entry = self.clients - .entry(*addr) - .or_insert_with(ClientStatEntry::new); - - entry.responses_sent += 1; - entry.bytes_sent += bytes_sent; - } +pub trait ServerStats { + fn add_valid_request(&mut self, addr: &IpAddr); - fn total_valid_requests(&self) -> u64 { - self.clients - .values() - .map(|&v| v.valid_requests) - .sum() - } + fn add_invalid_request(&mut self, addr: &IpAddr); - fn total_invalid_requests(&self) -> u64 { - self.clients - .values() - .map(|&v| v.invalid_requests) - .sum() - } + fn add_health_check(&mut self, addr: &IpAddr); - fn total_health_checks(&self) -> u64 { - self.clients - .values() - .map(|&v| v.health_checks) - .sum() - } + fn add_response(&mut self, addr: &IpAddr, bytes_sent: usize); - fn total_responses_sent(&self) -> u64 { - self.clients - .values() - .map(|&v| v.responses_sent) - .sum() - } + fn total_valid_requests(&self) -> u64; - fn total_bytes_sent(&self) -> usize { - self.clients - .values() - .map(|&v| v.bytes_sent) - .sum() - } + fn total_invalid_requests(&self) -> u64; - fn total_unique_clients(&self) -> u64 { - self.clients.len() as u64 - } + fn total_health_checks(&self) -> u64; - fn get_client_stats(&self, addr: &IpAddr) -> Option<&ClientStatEntry> { - self.clients.get(addr) - } + fn total_responses_sent(&self) -> u64; - fn iter(&self) -> Iter<IpAddr, ClientStatEntry> { - self.clients.iter() - } + fn total_bytes_sent(&self) -> usize; - fn clear(&mut self) { - self.clients.clear(); - self.num_overflows = 0; - } -} + fn total_unique_clients(&self) -> u64; -/// -/// Implementation of `ServerStats` that provides high-level aggregated server statistics. -/// -#[allow(dead_code)] -pub struct AggregatedStats { - valid_requests: u64, - invalid_requests: u64, - health_checks: u64, - responses_sent: u64, - bytes_sent: usize, - empty_map: HashMap<IpAddr, ClientStatEntry>, -} + fn get_client_stats(&self, addr: &IpAddr) -> Option<&ClientStatEntry>; -impl AggregatedStats { + fn iter(&self) -> Iter<IpAddr, ClientStatEntry>; - #[allow(dead_code)] - pub fn new() -> Self { - AggregatedStats { - valid_requests: 0, - invalid_requests: 0, - health_checks: 0, - responses_sent: 0, - bytes_sent: 0, - empty_map: HashMap::new() - } - } + fn clear(&mut self); } -impl ServerStats for AggregatedStats { - fn add_valid_request(&mut self, _: &IpAddr) { - self.valid_requests += 1 - } - - fn add_invalid_request(&mut self, _: &IpAddr) { - self.invalid_requests += 1 - } - - fn add_health_check(&mut self, _: &IpAddr) { - self.health_checks += 1 - } - - fn add_response(&mut self, _: &IpAddr, bytes_sent: usize) { - self.bytes_sent += bytes_sent; - self.responses_sent += 1; - } - - fn total_valid_requests(&self) -> u64 { - self.valid_requests - } - - fn total_invalid_requests(&self) -> u64 { - self.invalid_requests - } - - fn total_health_checks(&self) -> u64 { - self.health_checks - } - - fn total_responses_sent(&self) -> u64 { - self.responses_sent - } - - fn total_bytes_sent(&self) -> usize { - self.bytes_sent - } - - fn total_unique_clients(&self) -> u64 { - 0 - } - - fn get_client_stats(&self, _addr: &IpAddr) -> Option<&ClientStatEntry> { - None - } - - fn iter(&self) -> Iter<IpAddr, ClientStatEntry> { - self.empty_map.iter() - } - - fn clear(&mut self) {} -} #[cfg(test)] mod test { @@ -355,7 +131,7 @@ mod test { stats.add_response(&ip, 2048); stats.add_response(&ip, 1024); - let entry = stats.get_stats(&ip).unwrap(); + let entry = stats.get_client_stats(&ip).unwrap(); assert_eq!(entry.valid_requests, 1); assert_eq!(entry.invalid_requests, 0); assert_eq!(entry.responses_sent, 2); diff --git a/src/stats/per_client.rs b/src/stats/per_client.rs index e69de29..0799a76 100644 --- a/src/stats/per_client.rs +++ b/src/stats/per_client.rs @@ -0,0 +1,171 @@ +// Copyright 2017-2019 int08h LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use hashbrown::HashMap; +use hashbrown::hash_map::Iter; + +use std::net::IpAddr; + +use crate::stats::ClientStatEntry; +use crate::stats::ServerStats; + +/// +/// Implementation of `ServerStats` that provides granular per-client request/response counts. +/// +/// Each unique client address is used to key a hashmap. A maximum of `MAX_CLIENTS` entries +/// are kept in the map to bound memory use. Excess entries beyond `MAX_CLIENTS` are ignored +/// and `num_overflows` is incremented. +/// +pub struct PerClientStats { + clients: HashMap<IpAddr, ClientStatEntry>, + num_overflows: u64, + max_clients: usize, +} + +impl PerClientStats { + + /// Maximum number of entries to prevent unbounded memory growth. + pub const MAX_CLIENTS: usize = 1_000_000; + + pub fn new() -> Self { + PerClientStats { + clients: HashMap::with_capacity(128), + num_overflows: 0, + max_clients: PerClientStats::MAX_CLIENTS, + } + } + + // visible for testing + #[cfg(test)] + pub fn with_limit(limit: usize) -> Self { + PerClientStats { + clients: HashMap::with_capacity(128), + num_overflows: 0, + max_clients: limit, + } + } + + #[inline] + fn too_many_entries(&mut self) -> bool { + let too_big = self.clients.len() >= self.max_clients; + + if too_big { + self.num_overflows += 1; + } + + return too_big; + } + + #[allow(dead_code)] + pub fn num_overflows(&self) -> u64 { + self.num_overflows + } +} + +impl ServerStats for PerClientStats { + fn add_valid_request(&mut self, addr: &IpAddr) { + if self.too_many_entries() { + return; + } + self.clients + .entry(*addr) + .or_insert_with(ClientStatEntry::new) + .valid_requests += 1; + } + + fn add_invalid_request(&mut self, addr: &IpAddr) { + if self.too_many_entries() { + return; + } + self.clients + .entry(*addr) + .or_insert_with(ClientStatEntry::new) + .invalid_requests += 1; + } + + fn add_health_check(&mut self, addr: &IpAddr) { + if self.too_many_entries() { + return; + } + self.clients + .entry(*addr) + .or_insert_with(ClientStatEntry::new) + .health_checks += 1; + } + + fn add_response(&mut self, addr: &IpAddr, bytes_sent: usize) { + if self.too_many_entries() { + return; + } + let entry = self.clients + .entry(*addr) + .or_insert_with(ClientStatEntry::new); + + entry.responses_sent += 1; + entry.bytes_sent += bytes_sent; + } + + fn total_valid_requests(&self) -> u64 { + self.clients + .values() + .map(|&v| v.valid_requests) + .sum() + } + + fn total_invalid_requests(&self) -> u64 { + self.clients + .values() + .map(|&v| v.invalid_requests) + .sum() + } + + fn total_health_checks(&self) -> u64 { + self.clients + .values() + .map(|&v| v.health_checks) + .sum() + } + + fn total_responses_sent(&self) -> u64 { + self.clients + .values() + .map(|&v| v.responses_sent) + .sum() + } + + fn total_bytes_sent(&self) -> usize { + self.clients + .values() + .map(|&v| v.bytes_sent) + .sum() + } + + fn total_unique_clients(&self) -> u64 { + self.clients.len() as u64 + } + + fn get_client_stats(&self, addr: &IpAddr) -> Option<&ClientStatEntry> { + self.clients.get(addr) + } + + fn iter(&self) -> Iter<IpAddr, ClientStatEntry> { + self.clients.iter() + } + + fn clear(&mut self) { + self.clients.clear(); + self.num_overflows = 0; + } +} + |