diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f0053d2..77bd99a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Unreleased +- Add `--wait-ongoing-requests-after-deadline` option +- Add `--db-url` option to save results to SQLite database +- Add `--dump-urls` option to debug random URL generation + # 1.4.5 (2024-05-29) - Some performance improvements diff --git a/README.md b/README.md index e08d18de..694618cd 100644 --- a/README.md +++ b/README.md @@ -90,48 +90,92 @@ Arguments: Target URL. Options: - -n Number of requests to run. [default: 200] - -c Number of connections to run concurrently. You may should increase limit to number of open files for larger `-c`. [default: 50] - -p Number of parallel requests to send on HTTP/2. `oha` will run c * p concurrent workers in total. [default: 1] - -z Duration of application to send requests. If duration is specified, n is ignored. - When the duration is reached, ongoing requests are aborted and counted as "aborted due to deadline" - Examples: -z 10s -z 3m. - -q Rate limit for all, in queries per second (QPS) - --burst-delay Introduce delay between a predefined number of requests. - Note: If qps is specified, burst will be ignored - --burst-rate Rates of requests for burst. Default is 1 - Note: If qps is specified, burst will be ignored - --rand-regex-url Generate URL by rand_regex crate but dot is disabled for each query e.g. http://127.0.0.1/[a-z][a-z][0-9]. Currently dynamic scheme, host and port with keep-alive are not works well. See https://docs.rs/rand_regex/latest/rand_regex/struct.Regex.html for details of syntax. - --max-repeat A parameter for the '--rand-regex-url'. The max_repeat parameter gives the maximum extra repeat counts the x*, x+ and x{n,} operators will become. [default: 4] - --latency-correction Correct latency to avoid coordinated omission problem. It's ignored if -q is not set. - --no-tui No realtime tui - -j, --json Print results as JSON - --fps Frame per second for tui. [default: 16] - -m, --method HTTP method [default: GET] - -H Custom HTTP header. Examples: -H "foo: bar" - -t Timeout for each request. Default to infinite. - -A HTTP Accept Header. - -d HTTP request body. - -D HTTP request body from file. - -T Content-Type. - -a Basic authentication, username:password - --http-version HTTP version. Available values 0.9, 1.0, 1.1. - --http2 Use HTTP/2. Shorthand for --http-version=2 - --host HTTP Host header - --disable-compression Disable compression. - -r, --redirect Limit for number of Redirect. Set 0 for no redirection. Redirection isn't supported for HTTP/2. [default: 10] - --disable-keepalive Disable keep-alive, prevents re-use of TCP connections between different HTTP requests. This isn't supported for HTTP/2. - --no-pre-lookup *Not* perform a DNS lookup at beginning to cache it - --ipv6 Lookup only ipv6. - --ipv4 Lookup only ipv4. - --insecure Accept invalid certs. - --connect-to Override DNS resolution and default port numbers with strings like 'example.org:443:localhost:8443' - --disable-color Disable the color scheme. - --unix-socket Connect to a unix socket instead of the domain in the URL. Only for non-HTTPS URLs. - --vsock-addr Connect to a VSOCK socket using 'cid:port' instead of the domain in the URL. Only for non-HTTPS URLs. - --stats-success-breakdown Include a response status code successful or not successful breakdown for the time histogram and distribution statistics - -h, --help Print help - -V, --version Print version + -n + Number of requests to run. [default: 200] + -c + Number of connections to run concurrently. You may should increase limit to number of open files for larger `-c`. [default: 50] + -p + Number of parallel requests to send on HTTP/2. `oha` will run c * p concurrent workers in total. [default: 1] + -z + Duration of application to send requests. If duration is specified, n is ignored. + On HTTP/1, When the duration is reached, ongoing requests are aborted and counted as "aborted due to deadline" + You can change this behavior with `-w` option. + Currently, on HTTP/2, When the duration is reached, ongoing requests are waited. `-w` option is ignored. + Examples: -z 10s -z 3m. + -w, --wait-ongoing-requests-after-deadline + When the duration is reached, ongoing requests are waited + -q + Rate limit for all, in queries per second (QPS) + --burst-delay + Introduce delay between a predefined number of requests. + Note: If qps is specified, burst will be ignored + --burst-rate + Rates of requests for burst. Default is 1 + Note: If qps is specified, burst will be ignored + --rand-regex-url + Generate URL by rand_regex crate but dot is disabled for each query e.g. http://127.0.0.1/[a-z][a-z][0-9]. Currently dynamic scheme, host and port with keep-alive are not works well. See https://docs.rs/rand_regex/latest/rand_regex/struct.Regex.html for details of syntax. + --max-repeat + A parameter for the '--rand-regex-url'. The max_repeat parameter gives the maximum extra repeat counts the x*, x+ and x{n,} operators will become. [default: 4] + --dump-urls + Dump target Urls times to debug --rand-regex-url + --latency-correction + Correct latency to avoid coordinated omission problem. It's ignored if -q is not set. + --no-tui + No realtime tui + -j, --json + Print results as JSON + --fps + Frame per second for tui. [default: 16] + -m, --method + HTTP method [default: GET] + -H + Custom HTTP header. Examples: -H "foo: bar" + -t + Timeout for each request. Default to infinite. + -A + HTTP Accept Header. + -d + HTTP request body. + -D + HTTP request body from file. + -T + Content-Type. + -a + Basic authentication, username:password + --http-version + HTTP version. Available values 0.9, 1.0, 1.1. + --http2 + Use HTTP/2. Shorthand for --http-version=2 + --host + HTTP Host header + --disable-compression + Disable compression. + -r, --redirect + Limit for number of Redirect. Set 0 for no redirection. Redirection isn't supported for HTTP/2. [default: 10] + --disable-keepalive + Disable keep-alive, prevents re-use of TCP connections between different HTTP requests. This isn't supported for HTTP/2. + --no-pre-lookup + *Not* perform a DNS lookup at beginning to cache it + --ipv6 + Lookup only ipv6. + --ipv4 + Lookup only ipv4. + --insecure + Accept invalid certs. + --connect-to + Override DNS resolution and default port numbers with strings like 'example.org:443:localhost:8443' + --disable-color + Disable the color scheme. + --unix-socket + Connect to a unix socket instead of the domain in the URL. Only for non-HTTPS URLs. + --stats-success-breakdown + Include a response status code successful or not successful breakdown for the time histogram and distribution statistics + --db-url + Write succeeded requests to sqlite database url E.G test.db + -h, --help + Print help + -V, --version + Print version ``` # JSON output diff --git a/src/client.rs b/src/client.rs index 24284264..cb766453 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1237,6 +1237,7 @@ pub async fn work_until( dead_line: std::time::Instant, n_connections: usize, n_http2_parallel: usize, + wait_ongoing_requests_after_deadline: bool, ) { let client = Arc::new(client); if client.is_http2() { @@ -1353,11 +1354,17 @@ pub async fn work_until( tokio::time::sleep_until(dead_line.into()).await; is_end.store(true, Relaxed); - for f in futures { - f.abort(); - if let Err(e) = f.await { - if e.is_cancelled() { - report_tx.send(Err(ClientError::Deadline)).unwrap(); + if wait_ongoing_requests_after_deadline { + for f in futures { + let _ = f.await; + } + } else { + for f in futures { + f.abort(); + if let Err(e) = f.await { + if e.is_cancelled() { + report_tx.send(Err(ClientError::Deadline)).unwrap(); + } } } } @@ -1365,6 +1372,7 @@ pub async fn work_until( } /// Run until dead_line by n workers limit to qps works in a second +#[allow(clippy::too_many_arguments)] pub async fn work_until_with_qps( client: Client, report_tx: flume::Sender>, @@ -1373,6 +1381,7 @@ pub async fn work_until_with_qps( dead_line: std::time::Instant, n_connections: usize, n_http2_parallel: usize, + wait_ongoing_requests_after_deadline: bool, ) { let rx = match query_limit { QueryLimit::Qps(qps) => { @@ -1530,11 +1539,17 @@ pub async fn work_until_with_qps( tokio::time::sleep_until(dead_line.into()).await; is_end.store(true, Relaxed); - for f in futures { - f.abort(); - if let Err(e) = f.await { - if e.is_cancelled() { - report_tx.send(Err(ClientError::Deadline)).unwrap(); + if wait_ongoing_requests_after_deadline { + for f in futures { + let _ = f.await; + } + } else { + for f in futures { + f.abort(); + if let Err(e) = f.await { + if e.is_cancelled() { + report_tx.send(Err(ClientError::Deadline)).unwrap(); + } } } } @@ -1542,6 +1557,7 @@ pub async fn work_until_with_qps( } /// Run until dead_line by n workers limit to qps works in a second with latency correction +#[allow(clippy::too_many_arguments)] pub async fn work_until_with_qps_latency_correction( client: Client, report_tx: flume::Sender>, @@ -1550,6 +1566,7 @@ pub async fn work_until_with_qps_latency_correction( dead_line: std::time::Instant, n_connections: usize, n_http2_parallel: usize, + wait_ongoing_requests_after_deadline: bool, ) { let (tx, rx) = flume::unbounded(); match query_limit { @@ -1706,11 +1723,17 @@ pub async fn work_until_with_qps_latency_correction( tokio::time::sleep_until(dead_line.into()).await; is_end.store(true, Relaxed); - for f in futures { - f.abort(); - if let Err(e) = f.await { - if e.is_cancelled() { - report_tx.send(Err(ClientError::Deadline)).unwrap(); + if wait_ongoing_requests_after_deadline { + for f in futures { + let _ = f.await; + } + } else { + for f in futures { + f.abort(); + if let Err(e) = f.await { + if e.is_cancelled() { + report_tx.send(Err(ClientError::Deadline)).unwrap(); + } } } } diff --git a/src/main.rs b/src/main.rs index 166bc6a8..b900bfae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,11 +55,20 @@ struct Opts { n_http2_parallel: usize, #[clap( help = "Duration of application to send requests. If duration is specified, n is ignored. -When the duration is reached, ongoing requests are aborted and counted as \"aborted due to deadline\" +On HTTP/1, When the duration is reached, ongoing requests are aborted and counted as \"aborted due to deadline\" +You can change this behavior with `-w` option. +Currently, on HTTP/2, When the duration is reached, ongoing requests are waited. `-w` option is ignored. Examples: -z 10s -z 3m.", short = 'z' )] duration: Option, + #[clap( + help = "When the duration is reached, ongoing requests are waited", + short, + long, + default_value = "false" + )] + wait_ongoing_requests_after_deadline: bool, #[clap(help = "Rate limit for all, in queries per second (QPS)", short = 'q')] query_per_second: Option, #[arg( @@ -516,6 +525,7 @@ async fn main() -> anyhow::Result<()> { start + duration.into(), opts.n_connections, opts.n_http2_parallel, + opts.wait_ongoing_requests_after_deadline, ) .await } @@ -532,6 +542,7 @@ async fn main() -> anyhow::Result<()> { start + duration.into(), opts.n_connections, opts.n_http2_parallel, + opts.wait_ongoing_requests_after_deadline, ) .await } else { @@ -546,6 +557,7 @@ async fn main() -> anyhow::Result<()> { start + duration.into(), opts.n_connections, opts.n_http2_parallel, + opts.wait_ongoing_requests_after_deadline, ) .await } @@ -561,6 +573,7 @@ async fn main() -> anyhow::Result<()> { start + duration.into(), opts.n_connections, opts.n_http2_parallel, + opts.wait_ongoing_requests_after_deadline, ) .await } else { @@ -572,6 +585,7 @@ async fn main() -> anyhow::Result<()> { start + duration.into(), opts.n_connections, opts.n_http2_parallel, + opts.wait_ongoing_requests_after_deadline, ) .await }