From 15c254722862df4089ff8210d5f41d7874ec6c7a Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Mon, 24 Mar 2025 15:23:55 -0700 Subject: [PATCH 1/3] feat: add `--force-rm` flag to configure removing intermediate docker layers This commit makes it possible to turn off the removal of intermediate docker layers when building the tfb containers by adding a `--force-rm` flag to the tfb script. This is useful in situations where you want to inspect the intermediate layers for debugging purposes, or for caching builds of dependencies as a docker layer to speed up the build process. Note that the default behavior is to remove the intermediate layers to avoid filling up the disk with unused layers on the citrine server. Fixes: https://github.com/TechEmpower/FrameworkBenchmarks/issues/9718 --- toolset/run-tests.py | 4 ++++ toolset/utils/benchmark_config.py | 1 + toolset/utils/docker_helper.py | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/toolset/run-tests.py b/toolset/run-tests.py index a70fff3a490..a5716913cab 100644 --- a/toolset/run-tests.py +++ b/toolset/run-tests.py @@ -208,6 +208,10 @@ def main(argv=None): nargs='*', default=None, help='Extra docker arguments to be passed to the test container') + parser.add_argument( + '--force-rm', + default=True, + help='Remove intermediate docker containers after running.') # Network options parser.add_argument( diff --git a/toolset/utils/benchmark_config.py b/toolset/utils/benchmark_config.py index 48755e9f460..1a5dc339b0f 100755 --- a/toolset/utils/benchmark_config.py +++ b/toolset/utils/benchmark_config.py @@ -55,6 +55,7 @@ def __init__(self, args): self.cpuset_cpus = args.cpuset_cpus self.test_container_memory = args.test_container_memory self.extra_docker_runtime_args = args.extra_docker_runtime_args + self.force_rm_intermediate_docker_layers = args.force_rm if self.network_mode is None: self.network = 'tfb' diff --git a/toolset/utils/docker_helper.py b/toolset/utils/docker_helper.py index e48a910e99d..f25a9d133a0 100644 --- a/toolset/utils/docker_helper.py +++ b/toolset/utils/docker_helper.py @@ -39,7 +39,7 @@ def __build(self, base_url, path, build_log_file, log_prefix, dockerfile, path=path, dockerfile=dockerfile, tag=tag, - forcerm=True, + forcerm=self.benchmarker.config.force_rm_intermediate_docker_layers, timeout=3600, pull=True, buildargs=buildargs, From e8711902ccbbe05032e3e76902672f735d70a20f Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Mon, 24 Mar 2025 15:29:45 -0700 Subject: [PATCH 2/3] hyper: cache dependencies to reduce build time This change reduces the time it takes to build the hyper Docker image by caching the dependency builds. This is done by building a dummy binary before copying the source code into the image. --- frameworks/Rust/hyper/hyper.dockerfile | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/frameworks/Rust/hyper/hyper.dockerfile b/frameworks/Rust/hyper/hyper.dockerfile index b2f6b8cb6b3..4816011104f 100644 --- a/frameworks/Rust/hyper/hyper.dockerfile +++ b/frameworks/Rust/hyper/hyper.dockerfile @@ -1,8 +1,17 @@ FROM rust:1.85 AS hyper WORKDIR /src -COPY . . -RUN RUSTFLAGS="-C target-cpu=native" cargo install --path . --locked +ENV RUSTFLAGS="-C target-cpu=native" + +# Cache dependency builds (requires passing --force-rm False to tfb command) +COPY Cargo.toml Cargo.lock /src/ +RUN mkdir src \ + && echo "fn main() {println!(\"if you see this, the build broke\")}" > src/main.rs \ + && cargo build --release \ + && rm -rfv src/ target/release/hyper-techempower* target/release/deps/hyper_techempower* + +COPY . /src/ +RUN cargo install --path . --locked EXPOSE 8080 CMD ["hyper-techempower"] HEALTHCHECK CMD curl --fail http://localhost:8080/ping || exit 1 \ No newline at end of file From a50128f137861d9d995acf2a6d52b6af23f0580c Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Mon, 24 Mar 2025 20:47:10 -0700 Subject: [PATCH 3/3] hyper: simplify handlers - move the server header to the router and add it to all responses. - move the response boxing to the router simplifying the return types of each handler. https://github.com/hyperium/http-body/pull/150 will simplify the router code even futher to the following: ```rust let mut response = match request.uri().path() { "/ping" => ping()?.box_body(), "/json" => json::get()?.box_body(), "/db" => single_query::get().await?.box_body(), "/queries" => multiple_queries::get(request.uri().query()).await?.box_body(), "/fortunes" => fortunes::get().await?.box_body(), "/plaintext" => plaintext::get()?.box_body(), _ => not_found_error()?.box_body(), }; ``` --- frameworks/Rust/hyper/src/fortunes.rs | 16 ++++----- frameworks/Rust/hyper/src/json.rs | 15 ++++----- frameworks/Rust/hyper/src/main.rs | 33 +++++++++---------- frameworks/Rust/hyper/src/multiple_queries.rs | 19 ++++------- frameworks/Rust/hyper/src/plaintext.rs | 14 +++----- frameworks/Rust/hyper/src/single_query.rs | 20 +++++------ 6 files changed, 49 insertions(+), 68 deletions(-) diff --git a/frameworks/Rust/hyper/src/fortunes.rs b/frameworks/Rust/hyper/src/fortunes.rs index 0cdbd2f3ccf..46d9c269581 100644 --- a/frameworks/Rust/hyper/src/fortunes.rs +++ b/frameworks/Rust/hyper/src/fortunes.rs @@ -1,14 +1,12 @@ -use std::convert::Infallible; -use http::header::{CONTENT_LENGTH, CONTENT_TYPE, SERVER}; +use http::header::{CONTENT_LENGTH, CONTENT_TYPE}; use http::Response; -use http_body_util::combinators::BoxBody; -use http_body_util::{BodyExt, Full}; +use http_body_util::Full; use hyper::body::Bytes; use tokio_postgres::Row; use crate::db::POOL; -use crate::{Error, SERVER_HEADER, TEXT_HTML}; +use crate::{Error, Result, TEXT_HTML}; const QUERY: &str = "SELECT id, message FROM fortune"; @@ -26,18 +24,18 @@ impl From<&Row> for Fortune { } } -pub async fn get() -> crate::Result>> { +pub async fn get() -> Result>> { let fortunes = tell_fortune().await?; let content = FortunesTemplate { fortunes }.to_string(); + Response::builder() - .header(SERVER, SERVER_HEADER.clone()) .header(CONTENT_TYPE, TEXT_HTML.clone()) .header(CONTENT_LENGTH, content.len()) - .body(Full::from(content).boxed()) + .body(content.into()) .map_err(Error::from) } -async fn tell_fortune() -> crate::Result> { +async fn tell_fortune() -> Result> { let db = POOL.get().await?; let statement = db.prepare_cached(QUERY).await?; let rows = db.query(&statement, &[]).await?; diff --git a/frameworks/Rust/hyper/src/json.rs b/frameworks/Rust/hyper/src/json.rs index c80ad50c09a..70bdabc4d53 100644 --- a/frameworks/Rust/hyper/src/json.rs +++ b/frameworks/Rust/hyper/src/json.rs @@ -1,13 +1,10 @@ -use std::convert::Infallible; - -use http::header::{CONTENT_LENGTH, CONTENT_TYPE, SERVER}; +use http::header::{CONTENT_LENGTH, CONTENT_TYPE}; use http::Response; -use http_body_util::combinators::BoxBody; -use http_body_util::{BodyExt, Full}; +use http_body_util::Full; use hyper::body::Bytes; use serde::Serialize; -use crate::{Error, Result, APPLICATION_JSON, SERVER_HEADER}; +use crate::{Error, Result, APPLICATION_JSON}; #[derive(Serialize)] struct JsonResponse<'a> { @@ -18,12 +15,12 @@ static CONTENT: JsonResponse = JsonResponse { message: "Hello, world!", }; -pub fn get() -> Result>> { +pub fn get() -> Result>> { let content = serde_json::to_vec(&CONTENT)?; + Response::builder() - .header(SERVER, SERVER_HEADER.clone()) .header(CONTENT_TYPE, APPLICATION_JSON.clone()) .header(CONTENT_LENGTH, content.len()) - .body(Full::from(content).boxed()) + .body(content.into()) .map_err(Error::from) } diff --git a/frameworks/Rust/hyper/src/main.rs b/frameworks/Rust/hyper/src/main.rs index 81b79374172..cf4a652fc01 100644 --- a/frameworks/Rust/hyper/src/main.rs +++ b/frameworks/Rust/hyper/src/main.rs @@ -4,7 +4,7 @@ use std::{io, thread}; use clap::{Parser, ValueEnum}; use http_body_util::combinators::BoxBody; -use http_body_util::{BodyExt, Empty, Full}; +use http_body_util::Empty; use hyper::body::{Bytes, Incoming}; use hyper::header::{HeaderValue, SERVER}; use hyper::server::conn::http1; @@ -181,32 +181,31 @@ async fn accept_loop(handle: runtime::Handle, listener: TcpListener) -> Result<( /// Routes requests to the appropriate handler. async fn router(request: Request) -> Result>> { // The method is always GET, so we don't check it. - match request.uri().path() { - "/ping" => ping(), - "/json" => json::get(), - "/db" => single_query::get().await, - "/queries" => multiple_queries::get(request.uri().query()).await, - "/fortunes" => fortunes::get().await, - "/plaintext" => plaintext::get(), - _ => not_found_error(), - } + let mut response = match request.uri().path() { + "/ping" => ping()?.map(BoxBody::new), + "/json" => json::get()?.map(BoxBody::new), + "/db" => single_query::get().await?.map(BoxBody::new), + "/queries" => multiple_queries::get(request.uri().query()).await?.map(BoxBody::new), + "/fortunes" => fortunes::get().await?.map(BoxBody::new), + "/plaintext" => plaintext::get()?.map(BoxBody::new), + _ => not_found_error()?.map(BoxBody::new), + }; + response.headers_mut().insert(SERVER, SERVER_HEADER.clone()); + Ok(response) } /// A handler that returns a "pong" response. /// /// This handler is used to verify that the server is running and can respond to requests. It is /// used by the docker health check command. -fn ping() -> Result>> { - Response::builder() - .body(Full::from("pong").boxed()) - .map_err(Error::from) +fn ping() -> Result> { + Response::builder().body("pong".to_string()).map_err(Error::from) } /// A handler that returns a 404 response. -fn not_found_error() -> Result>> { +fn not_found_error() -> Result>> { Response::builder() .status(StatusCode::NOT_FOUND) - .header(SERVER, SERVER_HEADER.clone()) - .body(Empty::new().boxed()) + .body(Empty::new()) .map_err(Error::from) } diff --git a/frameworks/Rust/hyper/src/multiple_queries.rs b/frameworks/Rust/hyper/src/multiple_queries.rs index f9aa862acad..a4301bff496 100644 --- a/frameworks/Rust/hyper/src/multiple_queries.rs +++ b/frameworks/Rust/hyper/src/multiple_queries.rs @@ -1,15 +1,12 @@ -use std::convert::Infallible; - -use http::header::{CONTENT_LENGTH, CONTENT_TYPE, SERVER}; +use http::header::{CONTENT_LENGTH, CONTENT_TYPE}; use http::Response; -use http_body_util::combinators::BoxBody; -use http_body_util::{BodyExt, Full}; +use http_body_util::Full; use hyper::body::Bytes; use serde::Serialize; use tokio_postgres::Row; use crate::db::POOL; -use crate::{Error, Result, APPLICATION_JSON, SERVER_HEADER}; +use crate::{Error, Result, APPLICATION_JSON}; const QUERY: &str = "SELECT id, randomnumber FROM world WHERE id = $1"; @@ -28,21 +25,19 @@ impl From for World { } } -pub async fn get(query: Option<&str>) -> Result>> { +pub async fn get(query: Option<&str>) -> Result>> { let count = query .and_then(|query| query.strip_prefix("count=")) .and_then(|query| query.parse().ok()) .unwrap_or(1) .clamp(1, 500); - let worlds = query_worlds(count).await?; - let json = serde_json::to_vec(&worlds)?; + let content = serde_json::to_vec(&worlds)?; Response::builder() - .header(SERVER, SERVER_HEADER.clone()) .header(CONTENT_TYPE, APPLICATION_JSON.clone()) - .header(CONTENT_LENGTH, json.len()) - .body(Full::from(json).boxed()) + .header(CONTENT_LENGTH, content.len()) + .body(content.into()) .map_err(Error::from) } diff --git a/frameworks/Rust/hyper/src/plaintext.rs b/frameworks/Rust/hyper/src/plaintext.rs index e2a081d2d39..2e523adf41e 100644 --- a/frameworks/Rust/hyper/src/plaintext.rs +++ b/frameworks/Rust/hyper/src/plaintext.rs @@ -1,20 +1,16 @@ -use std::convert::Infallible; - -use http::header::{CONTENT_LENGTH, CONTENT_TYPE, SERVER}; +use http::header::{CONTENT_LENGTH, CONTENT_TYPE}; use http::Response; -use http_body_util::combinators::BoxBody; -use http_body_util::{BodyExt, Full}; +use http_body_util::Full; use hyper::body::Bytes; -use crate::{Error, Result, SERVER_HEADER, TEXT_PLAIN}; +use crate::{Error, Result, TEXT_PLAIN}; static CONTENT: &[u8] = b"Hello, world!"; -pub fn get() -> Result>> { +pub fn get() -> Result>> { Response::builder() - .header(SERVER, SERVER_HEADER.clone()) .header(CONTENT_TYPE, TEXT_PLAIN.clone()) .header(CONTENT_LENGTH, CONTENT.len()) - .body(Full::from(CONTENT).boxed()) + .body(CONTENT.into()) .map_err(Error::from) } diff --git a/frameworks/Rust/hyper/src/single_query.rs b/frameworks/Rust/hyper/src/single_query.rs index 62083dd4d3a..86d77b3666d 100644 --- a/frameworks/Rust/hyper/src/single_query.rs +++ b/frameworks/Rust/hyper/src/single_query.rs @@ -1,15 +1,12 @@ -use std::convert::Infallible; - -use http::header::{CONTENT_LENGTH, CONTENT_TYPE, SERVER}; +use http::header::{CONTENT_LENGTH, CONTENT_TYPE}; use http::Response; -use http_body_util::combinators::BoxBody; -use http_body_util::{BodyExt, Full}; +use http_body_util::Full; use hyper::body::Bytes; use serde::Serialize; use tokio_postgres::Row; use crate::db::POOL; -use crate::{Error, Result, APPLICATION_JSON, SERVER_HEADER}; +use crate::{Error, Result, APPLICATION_JSON}; static QUERY: &str = "SELECT id, randomnumber FROM world WHERE id = $1"; @@ -28,16 +25,15 @@ impl From for World { } } -pub async fn get() -> Result>> { +pub async fn get() -> Result>> { let id = fastrand::i32(1..10_000); let world = query_world(id).await?; - let json = serde_json::to_vec(&world)?; - + let content = serde_json::to_vec(&world)?; + Response::builder() - .header(SERVER, SERVER_HEADER.clone()) .header(CONTENT_TYPE, APPLICATION_JSON.clone()) - .header(CONTENT_LENGTH, json.len()) - .body(Full::from(json).boxed()) + .header(CONTENT_LENGTH, content.len()) + .body(content.into()) .map_err(Error::from) }