summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/config/file.rs4
-rw-r--r--src/config/mod.rs2
-rw-r--r--src/server.rs6
-rw-r--r--src/stats/aggregated.rs103
-rw-r--r--src/stats/mod.rs268
-rw-r--r--src/stats/per_client.rs171
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;
+ }
+}
+