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

View File

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

View File

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

View File

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

View File

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

View File

@ -35,6 +35,8 @@
//! * examples: `"localhost"`, `"0.0.0.0"`, `"1.2.3.4"` //! * examples: `"localhost"`, `"0.0.0.0"`, `"1.2.3.4"`
//! * **port**: _[integer]_ a port number to listen on //! * **port**: _[integer]_ a port number to listen on
//! * examples: `"8000"`, `"80"`, `"4242"` //! * 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"`, //! * **log**: _[string]_ how much information to log; one of `"normal"`,
//! `"debug"`, or `"critical"` //! `"debug"`, or `"critical"`
//! * **session_key**: _[string]_ a 192-bit base64 encoded string (32 //! * **session_key**: _[string]_ a 192-bit base64 encoded string (32
@ -58,11 +60,13 @@
//! [development] //! [development]
//! address = "localhost" //! address = "localhost"
//! port = 8000 //! port = 8000
//! workers = max(number_of_cpus, 2)
//! log = "normal" //! log = "normal"
//! //!
//! [staging] //! [staging]
//! address = "0.0.0.0" //! address = "0.0.0.0"
//! port = 80 //! port = 80
//! workers = max(number_of_cpus, 2)
//! log = "normal" //! log = "normal"
//! # don't use this key! generate your own and keep it private! //! # don't use this key! generate your own and keep it private!
//! session_key = "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5" //! session_key = "VheMwXIBygSmOlZAhuWl2B+zgvTN3WW5"
@ -70,11 +74,16 @@
//! [production] //! [production]
//! address = "0.0.0.0" //! address = "0.0.0.0"
//! port = 80 //! port = 80
//! workers = max(number_of_cpus, 2)
//! log = "critical" //! log = "critical"
//! # don't use this key! generate your own and keep it private! //! # don't use this key! generate your own and keep it private!
//! session_key = "adL5fFIPmZBrlyHk2YT4NLV3YCk2gFXz" //! 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 //! The "global" pseudo-environment can be used to set and/or override
//! configuration parameters globally. A parameter defined in a `[global]` table //! configuration parameters globally. A parameter defined in a `[global]` table
//! sets, or overrides if already present, that parameter in every environment. //! sets, or overrides if already present, that parameter in every environment.
@ -502,6 +511,7 @@ mod test {
let config_str = r#" let config_str = r#"
address = "1.2.3.4" address = "1.2.3.4"
port = 7810 port = 7810
workers = 21
log = "critical" log = "critical"
session_key = "01234567890123456789012345678901" session_key = "01234567890123456789012345678901"
template_dir = "mine" template_dir = "mine"
@ -512,6 +522,7 @@ mod test {
let mut expected = default_config(Development) let mut expected = default_config(Development)
.address("1.2.3.4") .address("1.2.3.4")
.port(7810) .port(7810)
.workers(21)
.log_level(LoggingLevel::Critical) .log_level(LoggingLevel::Critical)
.session_key("01234567890123456789012345678901") .session_key("01234567890123456789012345678901")
.extra("template_dir", "mine") .extra("template_dir", "mine")
@ -655,6 +666,66 @@ mod test {
"#.to_string(), TEST_CONFIG_FILENAME).is_err()); "#.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] #[test]
fn test_good_log_levels() { fn test_good_log_levels() {
// Take the lock so changing the environment doesn't cause races. // 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 hyper;
extern crate url; extern crate url;
extern crate toml; extern crate toml;
extern crate num_cpus;
#[cfg(test)] #[macro_use] extern crate lazy_static; #[cfg(test)] #[macro_use] extern crate lazy_static;

View File

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