diff options
-rw-r--r-- | src/config/file.rs | 4 | ||||
-rw-r--r-- | src/config/mod.rs | 2 | ||||
-rw-r--r-- | src/server.rs | 6 | ||||
-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 |
6 files changed, 302 insertions, 252 deletions
diff --git a/src/config/file.rs b/src/config/file.rs index 60232f3..b71b610 100644 --- a/src/config/file.rs +++ b/src/config/file.rs @@ -26,7 +26,7 @@ use crate::Error; /// Read a Roughenough server configuration ([ServerConfig](trait.ServerConfig.html)) /// from a YAML file. /// -/// Example config: +/// Example minimal config: /// /// ```yaml /// interface: 127.0.0.1 @@ -83,7 +83,7 @@ impl FileConfig { "seed" => { let val = value.as_str().unwrap().to_string(); config.seed = hex::decode(val) - .expect("seed value invalid; 'seed' should be 32 byte hex value"); + .expect("seed value invalid; 'seed' must be a valid hex value"); } "status_interval" => { let val = value.as_i64().expect("status_interval value invalid"); diff --git a/src/config/mod.rs b/src/config/mod.rs index 6dc33d6..263fcb4 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -47,7 +47,7 @@ pub const DEFAULT_STATUS_INTERVAL: Duration = Duration::from_secs(600); /// Specifies parameters needed to configure a Roughenough server. /// /// Parameters labeled "**Required**" must always be provided and have no default value -/// while those labeled "**Optional**" provide default values that can be overridden. +/// while those labeled "**Optional**" provide sane default values that can be overridden. /// /// YAML Key | Environment Variable | Necessity | Description /// --- | --- | --- | --- diff --git a/src/server.rs b/src/server.rs index 6045191..b910e2a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -40,7 +40,7 @@ use crate::key::{LongTermKey, OnlineKey}; use crate::kms; use crate::merkle::MerkleTree; use crate::{Error, RtMessage, Tag, MIN_REQUEST_LENGTH}; -use crate::stats::{ClientStats, SimpleStats}; +use crate::stats::{ServerStats, PerClientStats}; macro_rules! check_ctrlc { ($keep_running:expr) => { @@ -90,7 +90,7 @@ pub struct Server { #[cfg(fuzzing)] fake_client_socket: UdpSocket, - stats: SimpleStats, + stats: PerClientStats, } impl Server { @@ -172,7 +172,7 @@ impl Server { #[cfg(fuzzing)] fake_client_socket: UdpSocket::bind(&"127.0.0.1:0".parse().unwrap()).unwrap(), - stats: SimpleStats::new(), + stats: PerClientStats::new(), } } 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; + } +} + |