diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml new file mode 100644 index 00000000..ab1893f4 --- /dev/null +++ b/benchmarks/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rocket-benchmarks" +version = "0.0.0" +edition = "2018" +publish = false + +[workspace] + +[dependencies] +rocket = { path = "../core/lib/" } +criterion = "0.3" +criterion-macro = "0.3" diff --git a/benchmarks/src/main.rs b/benchmarks/src/main.rs new file mode 100644 index 00000000..c0e4f470 --- /dev/null +++ b/benchmarks/src/main.rs @@ -0,0 +1,11 @@ +#![feature(custom_test_frameworks)] +#![test_runner(criterion::runner)] + +#[cfg_attr(test, macro_use)] +extern crate criterion_macro; + +#[cfg(test)] mod routing; + +pub fn main() { + eprintln!("help: cargo bench"); +} diff --git a/benchmarks/src/routing.rs b/benchmarks/src/routing.rs new file mode 100644 index 00000000..bbbc6a44 --- /dev/null +++ b/benchmarks/src/routing.rs @@ -0,0 +1,119 @@ +use criterion::Criterion; + +use rocket::{handler, Request, Data, Route, Config}; +use rocket::http::{Method, RawStr, ContentType, Accept, Status}; +use rocket::local::blocking::{Client, LocalRequest}; + +fn dummy_handler<'r>(req: &'r Request, _: Data) -> handler::HandlerFuture<'r> { + handler::Outcome::from(req, ()).pin() +} + +fn parse_routes_table(table: &str) -> Vec { + let mut routes = vec![]; + for line in table.split("\n").filter(|s| !s.is_empty()) { + let mut components = line.split(" "); + let method: Method = components.next().expect("c").parse().expect("method"); + let uri: &str = components.next().unwrap(); + + let (mut rank, mut name, mut format) = (None, None, None); + for component in components { + match component { + c if c.starts_with('[') => rank = c.trim_matches(&['[', ']'][..]).parse().ok(), + c if c.starts_with('(') => name = Some(c.trim_matches(&['(', ')'][..])), + c => format = c.parse().ok(), + } + } + + let mut route = Route::new(method, uri, dummy_handler); + if let Some(rank) = rank { + route.rank = rank; + } + + route.format = format; + route.name = name.map(|s| s.to_string().into()); + routes.push(route); + } + + routes +} + +fn generate_matching_requests<'c>(client: &'c Client, routes: &[Route]) -> Vec> { + fn staticify_segment(segment: &RawStr) -> &str { + segment.as_str().trim_matches(&['<', '>', '.', '_'][..]) + } + + fn request_for_route<'c>(client: &'c Client, route: &Route) -> LocalRequest<'c> { + let path = route.uri.raw_path_segments() + .map(staticify_segment) + .collect::>() + .join("/"); + + let query = route.uri.raw_query_segments() + .map(staticify_segment) + .collect::>() + .join("&"); + + let uri = format!("/{}?{}", path, query); + let mut req = client.req(route.method, uri); + if let Some(ref format) = route.format { + if route.method.supports_payload() { + req.add_header(ContentType::from(format.clone())); + } else { + req.add_header(Accept::from(format.clone())); + } + } + + req + } + + routes.iter() + .map(|route| request_for_route(client, route)) + .collect() +} + +fn client(routes: Vec) -> Client { + let config = Config { + profile: Config::RELEASE_PROFILE, + log_level: rocket::config::LogLevel::Off, + cli_colors: false, + ctrlc: false, + ..Default::default() + }; + + match Client::untracked(rocket::custom(config).mount("/", routes)) { + Ok(client) => client, + Err(e) => { + drop(e); + panic!("bad launch") + } + } +} + +#[criterion] +pub fn bench_rust_lang_routes(c: &mut Criterion) { + let table = include_str!("../static/rust-lang.routes"); + let routes = parse_routes_table(table); + let client = client(routes.clone()); + let requests = generate_matching_requests(&client, &routes); + c.bench_function("rust-lang.routes", |b| b.iter(|| { + for request in requests.clone() { + let response = request.dispatch(); + assert_eq!(response.status(), Status::Ok); + } + })); +} + +#[criterion] +pub fn bench_bitwarden_routes(c: &mut Criterion) { + let table = include_str!("../static/bitwarden_rs.routes"); + let routes = parse_routes_table(table); + let client = client(routes.clone()); + let requests = generate_matching_requests(&client, &routes); + c.bench_function("bitwarden_rs.routes", |b| b.iter(|| { + for request in requests.clone() { + let response = request.dispatch(); + assert_eq!(response.status(), Status::Ok); + } + })); +} + diff --git a/benchmarks/static/bitwarden_rs.routes b/benchmarks/static/bitwarden_rs.routes new file mode 100644 index 00000000..1fc78752 --- /dev/null +++ b/benchmarks/static/bitwarden_rs.routes @@ -0,0 +1,157 @@ +GET /attachments// (attachments) +GET /alive (alive) +GET /bwrs_static/ (static_files) +POST /api/accounts/register (register) +GET /api/accounts/profile (profile) +PUT /api/accounts/profile (put_profile) +POST /api/accounts/profile (post_profile) +GET /api/users//public-key (get_public_keys) +POST /api/accounts/keys (post_keys) +POST /api/accounts/password (post_password) +POST /api/accounts/kdf (post_kdf) +POST /api/accounts/key (post_rotatekey) +POST /api/accounts/security-stamp (post_sstamp) +POST /api/accounts/email-token (post_email_token) +POST /api/accounts/email (post_email) +POST /api/accounts/verify-email (post_verify_email) +POST /api/accounts/verify-email-token (post_verify_email_token) +POST /api/accounts/delete-recover (post_delete_recover) +POST /api/accounts/delete-recover-token (post_delete_recover_token) +DELETE /api/accounts (delete_account) +POST /api/accounts/delete (post_delete_account) +GET /api/accounts/revision-date (revision_date) +POST /api/accounts/password-hint (password_hint) +POST /api/accounts/prelogin (prelogin) +POST /api/accounts/verify-password (verify_password) +GET /api/sync? (sync) +GET /api/ciphers (get_ciphers) +GET /api/ciphers/ (get_cipher) +GET /api/ciphers//admin (get_cipher_admin) +GET /api/ciphers//details (get_cipher_details) +POST /api/ciphers (post_ciphers) +PUT /api/ciphers//admin (put_cipher_admin) +POST /api/ciphers/admin (post_ciphers_admin) +POST /api/ciphers/create (post_ciphers_create) +POST /api/ciphers/import (post_ciphers_import) +POST /api/ciphers//attachment multipart/form-data (post_attachment) +POST /api/ciphers//attachment-admin multipart/form-data (post_attachment_admin) +POST /api/ciphers//attachment//share multipart/form-data (post_attachment_share) +POST /api/ciphers//attachment//delete (delete_attachment_post) +POST /api/ciphers//attachment//delete-admin (delete_attachment_post_admin) +DELETE /api/ciphers//attachment/ (delete_attachment) +DELETE /api/ciphers//attachment//admin (delete_attachment_admin) +POST /api/ciphers//admin (post_cipher_admin) +POST /api/ciphers//share (post_cipher_share) +PUT /api/ciphers//share (put_cipher_share) +PUT /api/ciphers/share (put_cipher_share_selected) +POST /api/ciphers/ (post_cipher) +PUT /api/ciphers/ (put_cipher) +POST /api/ciphers//delete (delete_cipher_post) +POST /api/ciphers//delete-admin (delete_cipher_post_admin) +PUT /api/ciphers//delete (delete_cipher_put) +PUT /api/ciphers//delete-admin (delete_cipher_put_admin) +DELETE /api/ciphers/ (delete_cipher) +DELETE /api/ciphers//admin (delete_cipher_admin) +DELETE /api/ciphers (delete_cipher_selected) +POST /api/ciphers/delete (delete_cipher_selected_post) +PUT /api/ciphers/delete (delete_cipher_selected_put) +DELETE /api/ciphers/admin (delete_cipher_selected_admin) +POST /api/ciphers/delete-admin (delete_cipher_selected_post_admin) +PUT /api/ciphers/delete-admin (delete_cipher_selected_put_admin) +PUT /api/ciphers//restore (restore_cipher_put) +PUT /api/ciphers//restore-admin (restore_cipher_put_admin) +PUT /api/ciphers/restore (restore_cipher_selected) +POST /api/ciphers/purge? (delete_all) +POST /api/ciphers/move (move_cipher_selected) +PUT /api/ciphers/move (move_cipher_selected_put) +PUT /api/ciphers//collections (put_collections_update) +POST /api/ciphers//collections (post_collections_update) +POST /api/ciphers//collections-admin (post_collections_admin) +PUT /api/ciphers//collections-admin (put_collections_admin) +GET /api/folders (get_folders) +GET /api/folders/ (get_folder) +POST /api/folders (post_folders) +POST /api/folders/ (post_folder) +PUT /api/folders/ (put_folder) +POST /api/folders//delete (delete_folder_post) +DELETE /api/folders/ (delete_folder) +GET /api/organizations/ (get_organization) +POST /api/organizations (create_organization) +DELETE /api/organizations/ (delete_organization) +POST /api/organizations//delete (post_delete_organization) +POST /api/organizations//leave (leave_organization) +GET /api/collections (get_user_collections) +GET /api/organizations//collections (get_org_collections) +GET /api/organizations//collections//details (get_org_collection_detail) +GET /api/organizations//collections//users (get_collection_users) +PUT /api/organizations//collections//users (put_collection_users) +PUT /api/organizations/ (put_organization) +POST /api/organizations/ (post_organization) +POST /api/organizations//collections (post_organization_collections) +DELETE /api/organizations//collections//user/ (delete_organization_collection_user) +POST /api/organizations//collections//delete-user/ (post_organization_collection_delete_user) +POST /api/organizations//collections/ (post_organization_collection_update) +PUT /api/organizations//collections/ (put_organization_collection_update) +DELETE /api/organizations//collections/ (delete_organization_collection) +POST /api/organizations//collections//delete (post_organization_collection_delete) +GET /api/ciphers/organization-details? (get_org_details) +GET /api/organizations//users (get_org_users) +POST /api/organizations//users/invite (send_invite) +POST /api/organizations//users//reinvite (reinvite_user) +POST /api/organizations//users//confirm (confirm_invite) +POST /api/organizations/<_org_id>/users/<_org_user_id>/accept (accept_invite) +GET /api/organizations//users/ (get_user) +POST /api/organizations//users/ [1] (edit_user) +PUT /api/organizations//users/ (put_organization_user) +DELETE /api/organizations//users/ (delete_user) +POST /api/organizations//users//delete (post_delete_user) +POST /api/ciphers/import-organization? (post_org_import) +GET /api/organizations//policies (list_policies) +GET /api/organizations//policies/token? (list_policies_token) +GET /api/organizations//policies/ (get_policy) +PUT /api/organizations//policies/ (put_policy) +GET /api/organizations//tax (get_organization_tax) +GET /api/plans (get_plans) +GET /api/plans/sales-tax-rates (get_plans_tax_rates) +POST /api/organizations//import (import) +GET /api/two-factor (get_twofactor) +POST /api/two-factor/get-recover (get_recover) +POST /api/two-factor/recover (recover) +POST /api/two-factor/disable (disable_twofactor) +PUT /api/two-factor/disable (disable_twofactor_put) +POST /api/two-factor/get-authenticator (generate_authenticator) +POST /api/two-factor/authenticator (activate_authenticator) +PUT /api/two-factor/authenticator (activate_authenticator_put) +POST /api/two-factor/get-duo (get_duo) +POST /api/two-factor/duo (activate_duo) +PUT /api/two-factor/duo (activate_duo_put) +POST /api/two-factor/get-email (get_email) +POST /api/two-factor/send-email-login (send_email_login) +POST /api/two-factor/send-email (send_email) +PUT /api/two-factor/email (email) +POST /api/two-factor/get-u2f (generate_u2f) +POST /api/two-factor/get-u2f-challenge (generate_u2f_challenge) +POST /api/two-factor/u2f (activate_u2f) +PUT /api/two-factor/u2f (activate_u2f_put) +DELETE /api/two-factor/u2f (delete_u2f) +POST /api/two-factor/get-yubikey (generate_yubikey) +POST /api/two-factor/yubikey (activate_yubikey) +PUT /api/two-factor/yubikey (activate_yubikey_put) +POST /api/sends (post_send) +POST /api/sends/file multipart/form-data (post_send_file) +POST /api/sends/access/ (post_access) +POST /api/sends//access/file/ (post_access_file) +PUT /api/sends/ (put_send) +DELETE /api/sends/ (delete_send) +PUT /api/sends//remove-password (put_remove_password) +PUT /api/devices/identifier//clear-token (clear_device_token) +PUT /api/devices/identifier//token (put_device_token) +GET /api/settings/domains (get_eq_domains) +POST /api/settings/domains (post_eq_domains) +PUT /api/settings/domains (put_eq_domains) +GET /api/hibp/breach? (hibp_breach) +GET /admin (admin_disabled) +POST /identity/connect/token (login) +GET /icons//icon.png (icon) +POST /notifications/hub/negotiate (negotiate) +GET /notifications/hub (websockets_err) diff --git a/benchmarks/static/rust-lang.routes b/benchmarks/static/rust-lang.routes new file mode 100644 index 00000000..7f8cf7f8 --- /dev/null +++ b/benchmarks/static/rust-lang.routes @@ -0,0 +1,25 @@ +GET / (index) +GET / (category) +GET /governance (governance) +GET /governance/
/ [2] (team) +GET /production/users (production) +GET /sponsors (sponsors) +GET // [4] (subject) +GET /static/ (files) +GET /robots.txt (robots_txt) +GET /logos/ (logos) +GET /components/<_file..> (components) +GET / [3] (index_locale) +GET // [11] (category_locale) +GET //governance [10] (governance_locale) +GET //governance/
/ [12] (team_locale) +GET //production/users [10] (production_locale) +GET //sponsors [10] (sponsors_locale) +GET /// [14] (subject_locale) +GET //components/<_file..> [12] (components_locale) +GET / [19] (redirect) +GET /pdfs/ (redirect_pdfs) +GET /en-US (redirect_bare_en_us) +GET /<_locale> [20] (redirect_bare_locale) +GET /en-US/ (redirect_en_us) +GET /<_locale>/ [20] (redirect_locale) diff --git a/core/http/src/header/content_type.rs b/core/http/src/header/content_type.rs index 36bcbfdf..77654ba1 100644 --- a/core/http/src/header/content_type.rs +++ b/core/http/src/header/content_type.rs @@ -356,6 +356,12 @@ impl FromStr for ContentType { } } +impl From for ContentType { + fn from(media_type: MediaType) -> Self { + ContentType(media_type) + } +} + impl fmt::Display for ContentType { /// Formats the ContentType as an HTTP Content-Type value. /// diff --git a/core/lib/Cargo.toml b/core/lib/Cargo.toml index faf5e6c1..bd04bb30 100644 --- a/core/lib/Cargo.toml +++ b/core/lib/Cargo.toml @@ -72,18 +72,5 @@ yansi = "0.5" version_check = "0.9.1" [dev-dependencies] -bencher = "0.1" figment = { version = "0.10", features = ["test"] } pretty_assertions = "0.7" - -[[bench]] -name = "format-routing" -harness = false - -[[bench]] -name = "ranked-routing" -harness = false - -[[bench]] -name = "simple-routing" -harness = false diff --git a/core/lib/benches/format-routing.rs b/core/lib/benches/format-routing.rs deleted file mode 100644 index 877be120..00000000 --- a/core/lib/benches/format-routing.rs +++ /dev/null @@ -1,55 +0,0 @@ -#[macro_use] extern crate rocket; -#[macro_use] extern crate bencher; - -use rocket::local::blocking::Client; - -#[get("/", format = "application/json")] -fn get() -> &'static str { "get" } - -#[post("/", format = "application/json")] -fn post() -> &'static str { "post" } - -fn rocket() -> rocket::Rocket { - let config = rocket::Config { - log_level: rocket::config::LogLevel::Off, - ..rocket::Config::debug_default() - }; - - rocket::custom(config).mount("/", routes![get, post]) -} - -use bencher::Bencher; -use rocket::http::{Accept, ContentType}; - -fn accept_format(b: &mut Bencher) { - let client = Client::tracked(rocket()).unwrap(); - let request = client.get("/").header(Accept::JSON); - b.iter(|| { request.clone().dispatch(); }); -} - -fn wrong_accept_format(b: &mut Bencher) { - let client = Client::tracked(rocket()).unwrap(); - let request = client.get("/").header(Accept::HTML); - b.iter(|| { request.clone().dispatch(); }); -} - -fn content_type_format(b: &mut Bencher) { - let client = Client::tracked(rocket()).unwrap(); - let request = client.post("/").header(ContentType::JSON); - b.iter(|| { request.clone().dispatch(); }); -} - -fn wrong_content_type_format(b: &mut Bencher) { - let client = Client::tracked(rocket()).unwrap(); - let request = client.post("/").header(ContentType::Plain); - b.iter(|| { request.clone().dispatch(); }); -} - -benchmark_main!(benches); -benchmark_group! { - benches, - accept_format, - wrong_accept_format, - content_type_format, - wrong_content_type_format, -} diff --git a/core/lib/benches/ranked-routing.rs b/core/lib/benches/ranked-routing.rs deleted file mode 100644 index c11d327c..00000000 --- a/core/lib/benches/ranked-routing.rs +++ /dev/null @@ -1,72 +0,0 @@ -#[macro_use] extern crate rocket; -#[macro_use] extern crate bencher; - -#[get("/", format = "application/json", rank = 1)] -fn get() -> &'static str { "json" } - -#[get("/", format = "text/html", rank = 2)] -fn get2() -> &'static str { "html" } - -#[get("/", format = "text/plain", rank = 3)] -fn get3() -> &'static str { "plain" } - -#[post("/", format = "application/json")] -fn post() -> &'static str { "json" } - -#[post("/", format = "text/html")] -fn post2() -> &'static str { "html" } - -#[post("/", format = "text/plain")] -fn post3() -> &'static str { "plain" } - -fn rocket() -> rocket::Rocket { - let config = rocket::Config { - log_level: rocket::config::LogLevel::Off, - ..rocket::Config::debug_default() - }; - - rocket::custom(config) - .mount("/", routes![get, get2, get3]) - .mount("/", routes![post, post2, post3]) -} - -use bencher::Bencher; -use rocket::local::blocking::Client; -use rocket::http::{Accept, ContentType}; - -fn accept_format(b: &mut Bencher) { - let client = Client::tracked(rocket()).unwrap(); - let requests = vec![ - client.get("/").header(Accept::JSON), - client.get("/").header(Accept::HTML), - client.get("/").header(Accept::Plain), - ]; - - b.iter(|| { - for request in &requests { - request.clone().dispatch(); - } - }); -} - -fn content_type_format(b: &mut Bencher) { - let client = Client::tracked(rocket()).unwrap(); - let requests = vec![ - client.post("/").header(ContentType::JSON), - client.post("/").header(ContentType::HTML), - client.post("/").header(ContentType::Plain), - ]; - - b.iter(|| { - for request in &requests { - request.clone().dispatch(); - } - }); -} - -benchmark_main!(benches); -benchmark_group! { - benches, - accept_format, - content_type_format, -} diff --git a/core/lib/benches/simple-routing.rs b/core/lib/benches/simple-routing.rs deleted file mode 100644 index 55ea7b07..00000000 --- a/core/lib/benches/simple-routing.rs +++ /dev/null @@ -1,122 +0,0 @@ -#[macro_use] extern crate rocket; -#[macro_use] extern crate bencher; - -#[get("/")] -fn hello_world() -> &'static str { "Hello, world!" } - -#[put("/")] -fn put_index() -> &'static str { "index" } - -#[post("/")] -fn post_index() -> &'static str { "index" } - -#[get("/a")] -fn index_a() -> &'static str { "index" } - -#[get("/b")] -fn index_b() -> &'static str { "index" } - -#[get("/c")] -fn index_c() -> &'static str { "index" } - -#[get("/<_a>")] -fn index_dyn_a(_a: &str) -> &'static str { "index" } - -fn hello_world_rocket() -> rocket::Rocket { - let config = rocket::Config { - log_level: rocket::config::LogLevel::Off, - ..rocket::Config::debug_default() - }; - - rocket::custom(config).mount("/", routes![hello_world]) -} - -fn rocket() -> rocket::Rocket { - hello_world_rocket() - .mount("/", routes![ - put_index, post_index, index_a, index_b, index_c, index_dyn_a - ]) -} - -use bencher::Bencher; -use rocket::local::blocking::Client; - -fn bench_hello_world(b: &mut Bencher) { - let client = Client::tracked(hello_world_rocket()).unwrap(); - - b.iter(|| { - client.get("/").dispatch(); - }); -} - -fn bench_single_get_index(b: &mut Bencher) { - let client = Client::tracked(rocket()).unwrap(); - - b.iter(|| { - client.get("/").dispatch(); - }); -} - -fn bench_get_put_post_index(b: &mut Bencher) { - let client = Client::tracked(rocket()).unwrap(); - - // Hold all of the requests we're going to make during the benchmark. - let mut requests = vec![]; - requests.push(client.get("/")); - requests.push(client.put("/")); - requests.push(client.post("/")); - - b.iter(|| { - for request in &requests { - request.clone().dispatch(); - } - }); -} - -fn bench_dynamic(b: &mut Bencher) { - let client = Client::tracked(rocket()).unwrap(); - - // Hold all of the requests we're going to make during the benchmark. - let mut requests = vec![]; - requests.push(client.get("/abc")); - requests.push(client.get("/abcdefg")); - requests.push(client.get("/123")); - - b.iter(|| { - for request in &requests { - request.clone().dispatch(); - } - }); -} - -fn bench_simple_routing(b: &mut Bencher) { - let client = Client::tracked(rocket()).unwrap(); - - // Hold all of the requests we're going to make during the benchmark. - let mut requests = vec![]; - for route in client.rocket().routes() { - let request = client.req(route.method, route.uri.path()); - requests.push(request); - } - - // A few more for the dynamic route. - requests.push(client.get("/abc")); - requests.push(client.get("/abcdefg")); - requests.push(client.get("/123")); - - b.iter(|| { - for request in &requests { - request.clone().dispatch(); - } - }); -} - -benchmark_main!(benches); -benchmark_group! { - benches, - bench_hello_world, - bench_single_get_index, - bench_get_put_post_index, - bench_dynamic, - bench_simple_routing, -}