Add workers config parameter.

This commit is contained in:
Sergio Benitez 2017-01-12 02:38:14 -08:00
parent fb24ee315c
commit ddda8fe79b
8 changed files with 152 additions and 42 deletions

View File

@ -147,8 +147,9 @@ Apache License, Version 2.0, without any additional terms or conditions.
Rocket is designed to be performant. At this time, its performance is
[bottlenecked by the Hyper HTTP
library](https://github.com/SergioBenitez/Rocket/issues/17). Even so, Rocket
currently performs _better_ than the latest version of Hyper on a simple "Hello,
world!" benchmark. Rocket also performs better than the Iron web framework:
currently performs _significantly better_ than the latest version of
asynchronous Hyper on a simple "Hello, world!" benchmark. Rocket also performs
_significantly better_ than the Iron web framework:
**Machine Specs:**
@ -157,45 +158,45 @@ world!" benchmark. Rocket also performs better than the Iron web framework:
* **Processor:** Intel Xeon X5675 @ 3.07GHz
* **Operating System:** Mac OS X v10.11.6
**Rocket v0.1.4** (8 LOC) results (best of 3, +/- 1000 req/s, +/- 5us latency):
**Rocket v0.2-rc** (8 LOC) results (best of 3, +/- 2000 req/s, +/- 5us latency):
Running 10s test @ http://localhost:80
2 threads and 10 connections
1 threads and 18 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 162.40us 28.41us 377.00us 69.21%
Req/Sec 29.88k 1.29k 33.72k 70.30%
600412 requests in 10.10s, 83.60MB read
Requests/sec: 59448.48
Transfer/sec: 8.28MB
Latency 153.01us 42.25us 449.00us 75.54%
Req/Sec 75.58k 11.75k 90.22k 54.46%
758044 requests in 10.10s, 105.55MB read
Requests/sec: 75051.28
Transfer/sec: 10.45MB
**Hyper v0.10.0-a.0** (46 LOC) results (best of 3, +/- 2000 req/s, +/- 10us latency):
**Hyper v0.11.0-a.0 (1/12/2016)** (46 LOC) results (best of 3, +/- 5000 req/s, +/- 30us latency):
Running 10s test @ http://localhost:80
2 threads and 10 connections
1 threads and 18 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 175.12us 40.38us 429.00us 70.79%
Req/Sec 28.00k 2.41k 36.79k 72.28%
562692 requests in 10.10s, 81.57MB read
Requests/sec: 55715.98
Transfer/sec: 8.08MB
Latency 287.81us 77.09us 606.00us 70.47%
Req/Sec 59.94k 6.01k 79.72k 71.00%
596231 requests in 10.00s, 83.02MB read
Requests/sec: 59621.32
Transfer/sec: 8.30MB
**Iron v0.4.0** (11 LOC) results (best of 3, +/- 1000 req/s, +/- 5us latency):
**Iron v0.5.0** (11 LOC) results (best of 3, +/- 1000 req/s, +/- 5us latency):
Running 10s test @ http://localhost:80
2 threads and 10 connections
1 threads and 18 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 189.93us 40.05us 2.06ms 67.57%
Req/Sec 25.80k 2.26k 34.01k 77.72%
518575 requests in 10.10s, 64.79MB read
Requests/sec: 51346.00
Transfer/sec: 6.41MB
Latency 512.36us 5.57ms 149.99ms 99.60%
Req/Sec 58.25k 11.61k 70.47k 46.00%
579227 requests in 10.00s, 80.65MB read
Requests/sec: 57920.73
Transfer/sec: 8.06MB
**Summary:**
* Rocket throughput higher by 6.7% (higher is better) compared to Hyper.
* Rocket throughput higher by 15.8% (higher is better) compared to Iron.
* Rocket latency lower by 7.3% (lower is better) compared to Hyper.
* Rocket latency lower by 14.5% (lower is better) compared to Iron.
* Rocket throughput higher by 25.9% (higher is better) compared to Hyper.
* Rocket throughput higher by 29.6% (higher is better) compared to Iron.
* Rocket latency lower by 46.8% (lower is better) compared to Hyper.
* Rocket latency lower by 70.1% (lower is better) compared to Iron.
### Future Improvements

View File

@ -4,6 +4,7 @@
[development]
address = "localhost"
port = 8000
workers = 1
log = "normal"
hi = "Hello!"
is_extra = true
@ -12,12 +13,14 @@ is_extra = true
address = "0.0.0.0"
port = 80
log = "normal"
workers = 8
# don't use this key! generate your own and keep it private!
session_key = "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5"
[production]
address = "0.0.0.0"
port = 80
workers = 12
log = "critical"
# don't use this key! generate your own and keep it private!
session_key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz"

View File

@ -18,6 +18,7 @@ term-painter = "^0.2"
log = "^0.3"
url = "^1"
toml = "^0.2"
num_cpus = "1"
# cookie = "^0.3"
[dependencies.hyper]

View File

@ -13,6 +13,8 @@ pub struct ConfigBuilder {
pub address: String,
/// The port to serve on.
pub port: u16,
/// The number of workers to run in parallel.
pub workers: u16,
/// How much information to log.
pub log_level: LoggingLevel,
/// The session key.
@ -30,6 +32,7 @@ impl ConfigBuilder {
environment: config.environment,
address: config.address,
port: config.port,
workers: config.workers,
log_level: config.log_level,
session_key: None,
extras: config.extras,
@ -50,6 +53,13 @@ impl ConfigBuilder {
self
}
/// Sets the `workers` in `self` to `workers` and returns the structure.
#[inline(always)]
pub fn workers(mut self, workers: u16) -> Self {
self.workers = workers;
self
}
/// Sets the `log_level` in `self` to `log_level` and returns the structure.
#[inline(always)]
pub fn log_level(mut self, log_level: LoggingLevel) -> Self {
@ -84,6 +94,7 @@ impl ConfigBuilder {
let mut config = Config::new(self.environment)?;
config.set_address(self.address)?;
config.set_port(self.port);
config.set_workers(self.workers);
config.set_log_level(self.log_level);
config.set_extras(self.extras);

View File

@ -9,22 +9,25 @@ use std::env;
use config::Environment::*;
use config::{self, Value, ConfigBuilder, Environment, ConfigError};
use num_cpus;
use logger::LoggingLevel;
/// The core configuration structure.
pub struct Config {
/// The environment that this configuration corresponds to.
pub environment: Environment,
/// The address to serve on.
pub address: String,
/// The port to serve on.
pub port: u16,
/// The number of workers to run concurrently.
pub workers: u16,
/// How much information to log.
pub log_level: LoggingLevel,
/// The environment that this configuration corresponds to.
pub environment: Environment,
/// The path to the configuration file this config belongs to.
pub config_path: PathBuf,
/// Extra parameters that aren't part of Rocket's core config.
pub extras: HashMap<String, Value>,
/// The path to the configuration file this config belongs to.
pub config_path: PathBuf,
/// The session key.
session_key: RwLock<Option<String>>,
}
@ -75,37 +78,43 @@ impl Config {
"Configuration files must be rooted in a directory."));
}
// Note: This may truncate if num_cpus::get() > u16::max. That's okay.
let default_workers = ::std::cmp::max(num_cpus::get(), 2) as u16;
Ok(match env {
Development => {
Config {
environment: Development,
address: "localhost".to_string(),
port: 8000,
workers: default_workers,
log_level: LoggingLevel::Normal,
session_key: RwLock::new(None),
extras: HashMap::new(),
environment: env,
config_path: config_path,
}
}
Staging => {
Config {
environment: Staging,
address: "0.0.0.0".to_string(),
port: 80,
workers: default_workers,
log_level: LoggingLevel::Normal,
session_key: RwLock::new(None),
extras: HashMap::new(),
environment: env,
config_path: config_path,
}
}
Production => {
Config {
environment: Production,
address: "0.0.0.0".to_string(),
port: 80,
workers: default_workers,
log_level: LoggingLevel::Critical,
session_key: RwLock::new(None),
extras: HashMap::new(),
environment: env,
config_path: config_path,
}
}
@ -132,8 +141,9 @@ impl Config {
///
/// * **address**: String
/// * **port**: Integer (16-bit unsigned)
/// * **session_key**: String (192-bit base64)
/// * **workers**: Integer (16-bit unsigned)
/// * **log**: String
/// * **session_key**: String (192-bit base64)
///
pub fn set(&mut self, name: &str, val: &Value) -> config::Result<()> {
if name == "address" {
@ -141,13 +151,18 @@ impl Config {
self.set_address(address_str)?;
} else if name == "port" {
let port = parse!(self, name, val, as_integer, "an integer")?;
if port < 0 {
return Err(self.bad_type(name, val.type_str(), "an unsigned integer"));
} else if port > (u16::max_value() as i64) {
if port < 0 || port > (u16::max_value() as i64) {
return Err(self.bad_type(name, val.type_str(), "a 16-bit unsigned integer"))
}
self.set_port(port as u16);
} else if name == "workers" {
let workers = parse!(self, name, val, as_integer, "an integer")?;
if workers < 0 || workers > (u16::max_value() as i64) {
return Err(self.bad_type(name, val.type_str(), "a 16-bit unsigned integer"));
}
self.set_workers(workers as u16);
} else if name == "session_key" {
let key = parse!(self, name, val, as_str, "a string")?;
self.set_session_key(key)?;
@ -178,7 +193,11 @@ impl Config {
}
pub fn set_port(&mut self, port: u16) {
self.port = port as u16;
self.port = port;
}
pub fn set_workers(&mut self, workers: u16) {
self.workers = workers;
}
pub fn set_session_key<K: Into<String>>(&mut self, key: K) -> config::Result<()> {
@ -284,8 +303,8 @@ impl Config {
impl fmt::Debug for Config {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Config[{}] {{ address: {}, port: {}, log_level: {:?}",
self.environment, self.address, self.port, self.log_level)?;
write!(f, "Config[{}] {{ address: {}, port: {}, workers: {}, log: {:?}",
self.environment, self.address, self.port, self.workers, self.log_level)?;
for (key, value) in self.extras() {
write!(f, ", {}: {}", key, value)?;
@ -300,6 +319,7 @@ impl PartialEq for Config {
fn eq(&self, other: &Config) -> bool {
self.address == other.address
&& self.port == other.port
&& self.workers == other.workers
&& self.log_level == other.log_level
&& self.environment == other.environment
&& self.extras == other.extras

View File

@ -35,6 +35,8 @@
//! * examples: `"localhost"`, `"0.0.0.0"`, `"1.2.3.4"`
//! * **port**: _[integer]_ a port number to listen on
//! * examples: `"8000"`, `"80"`, `"4242"`
//! * **workers**: _[integer]_ the number of concurrent workers to use
//! * examples: `"12"`, `"1"`, `"4"`
//! * **log**: _[string]_ how much information to log; one of `"normal"`,
//! `"debug"`, or `"critical"`
//! * **session_key**: _[string]_ a 192-bit base64 encoded string (32
@ -58,11 +60,13 @@
//! [development]
//! address = "localhost"
//! port = 8000
//! workers = max(number_of_cpus, 2)
//! log = "normal"
//!
//! [staging]
//! address = "0.0.0.0"
//! port = 80
//! workers = max(number_of_cpus, 2)
//! log = "normal"
//! # don't use this key! generate your own and keep it private!
//! session_key = "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5"
@ -70,11 +74,16 @@
//! [production]
//! address = "0.0.0.0"
//! port = 80
//! workers = max(number_of_cpus, 2)
//! log = "critical"
//! # don't use this key! generate your own and keep it private!
//! session_key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz"
//! ```
//!
//! The `workers` parameter is computed by Rocket automatically; the value above
//! is not valid TOML syntax. When manually specifying the number of workers,
//! the value should be an integer: `workers = 10`.
//!
//! The "global" pseudo-environment can be used to set and/or override
//! configuration parameters globally. A parameter defined in a `[global]` table
//! sets, or overrides if already present, that parameter in every environment.
@ -502,6 +511,7 @@ mod test {
let config_str = r#"
address = "1.2.3.4"
port = 7810
workers = 21
log = "critical"
session_key = "01234567890123456789012345678901"
template_dir = "mine"
@ -512,6 +522,7 @@ mod test {
let mut expected = default_config(Development)
.address("1.2.3.4")
.port(7810)
.workers(21)
.log_level(LoggingLevel::Critical)
.session_key("01234567890123456789012345678901")
.extra("template_dir", "mine")
@ -655,6 +666,66 @@ mod test {
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
}
#[test]
fn test_good_workers_values() {
// Take the lock so changing the environment doesn't cause races.
let _env_lock = ENV_LOCK.lock().unwrap();
env::set_var(CONFIG_ENV, "stage");
check_config!(RocketConfig::parse(r#"
[stage]
workers = 1
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).workers(1)
});
check_config!(RocketConfig::parse(r#"
[stage]
workers = 300
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).workers(300)
});
check_config!(RocketConfig::parse(r#"
[stage]
workers = 65535
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).workers(65535)
});
}
#[test]
fn test_bad_workers_values() {
// Take the lock so changing the environment doesn't cause races.
let _env_lock = ENV_LOCK.lock().unwrap();
env::remove_var(CONFIG_ENV);
assert!(RocketConfig::parse(r#"
[development]
workers = true
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
[production]
workers = "hello"
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
[staging]
workers = -1
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
[staging]
workers = 65536
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
[staging]
workers = 105836
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
}
#[test]
fn test_good_log_levels() {
// Take the lock so changing the environment doesn't cause races.

View File

@ -95,6 +95,7 @@ extern crate term_painter;
extern crate hyper;
extern crate url;
extern crate toml;
extern crate num_cpus;
#[cfg(test)] #[macro_use] extern crate lazy_static;

View File

@ -315,6 +315,7 @@ impl Rocket {
White.paint(&config.address),
White.paint(&config.port));
info_!("logging: {:?}", White.paint(config.log_level));
info_!("workers: {}", White.paint(config.workers));
let session_key = config.take_session_key();
if session_key.is_some() {
@ -485,6 +486,7 @@ impl Rocket {
White.bold().paint("http://"),
White.bold().paint(&full_addr));
server.handle(self).unwrap();
let threads = self.config.workers as usize;
server.handle_threads(self, threads).unwrap();
}
}