From 6e7054f3b5429ea53418d73d463169f228adcebd Mon Sep 17 00:00:00 2001 From: xrb114 Date: Wed, 11 Mar 2026 09:59:42 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E7=BF=BB=E8=AF=91=E5=B8=AE=E5=8A=A9?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E5=92=8C=E7=BB=9F=E8=AE=A1=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E4=B8=BA=E4=B8=AD=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib.rs | 122 +++++++++++++++++++++++++++---------------------- src/monitor.rs | 24 +++++----- src/printer.rs | 38 +++++++-------- 3 files changed, 99 insertions(+), 85 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0cea571e..63624b17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,14 +59,14 @@ static GLOBAL: Jemalloc = Jemalloc; #[command(arg_required_else_help(true))] #[command(styles = clap_cargo::style::CLAP_STYLING)] pub struct Opts { - #[arg(help = "Target URL or file with multiple URLs.")] + #[arg(help = "目标url或者包含多个url的文件.")] url: Option, #[arg(long = "completions", hide = true)] pub completions: Option, #[arg( - help = "Number of requests to run. Accepts plain numbers or suffixes: k = 1,000, m = 1,000,000 (e.g. 10k, 1m).", + help = "要发送的请求数量。支持数字后缀:k = 1,000,m = 1,000,000(例如 10k,1m)。[默认: 200].", short = 'n', default_value = "200", conflicts_with = "duration", @@ -74,36 +74,40 @@ pub struct Opts { )] n_requests: usize, #[arg( - help = "Number of connections to run concurrently. You may should increase limit to number of open files for larger `-c`.", + help = "要发送的连接数。您可能需要增加限制以支持更大的 `-c`。", short = 'c', default_value = "50" )] n_connections: usize, #[arg( - help = "Number of parallel requests to send on HTTP/2. `oha` will run c * p concurrent workers in total.", + help = "要发送的并行请求数量。`oha` 将总共运行 c * p 个并发工作线程。", short = 'p', default_value = "1" )] n_http2_parallel: usize, #[arg( - help = "Duration of application to send requests. -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.", + help = "发送请求的持续时间。 + 在 HTTP/1 中,当达到持续时间后,正在进行的请求会被终止, + 并计为 “aborted due to deadline”(因超时截止被中止)。 + 可以使用 `-w` 选项改变该行为。 + 当前在 HTTP/2 中,当达到持续时间后,正在进行的请求会等待完成, + `-w` 选项会被忽略。 + 示例: + -z 10s + -z 3m", short = 'z', conflicts_with = "n_requests" )] duration: Option, #[arg( - help = "When the duration is reached, ongoing requests are waited", + help = "在达到持续时间后,等待正在进行的请求完成", short, long, default_value = "false", requires = "duration" )] wait_ongoing_requests_after_deadline: bool, - #[arg(help = "Rate limit for all, in queries per second (QPS)", short = 'q', conflicts_with_all = ["burst_duration", "burst_requests"])] + #[arg(help = "所有请求的速率限制,单位为每秒查询次数(QPS)", short = 'q', conflicts_with_all = ["burst_duration", "burst_requests"])] query_per_second: Option, #[arg( help = "Introduce delay between a predefined number of requests. @@ -123,42 +127,42 @@ Note: If qps is specified, burst will be ignored", burst_requests: Option, #[arg( - help = "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 do not work well. See https://docs.rs/rand_regex/latest/rand_regex/struct.Regex.html for details of syntax.", + help = "使用 rand_regex crate 生成 URL,但每个查询中的点被禁用。例如 http://127.0.0.1/[a-z][a-z][0-9]。目前动态方案、主机和端口与保持连接不兼容。详情请见 https://docs.rs/rand_regex/latest/rand_regex/struct.Regex.html。", default_value = "false", long )] rand_regex_url: bool, #[arg( - help = "Read the URLs to query from a file", + help = "从文件中读取要查询的 URLs", default_value = "false", long )] urls_from_file: bool, #[arg( - help = "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.", + help = "`--rand-regex-url` 参数的最大重复次数用于 x*, x+, x{n,} 操作符。[默认: 4]", default_value = "4", long, requires = "rand_regex_url" )] max_repeat: u32, #[arg( - help = "Dump target Urls times to debug --rand-regex-url", + help = "输出生成的 URL 次,用于调试 --rand-regex-url", long )] dump_urls: Option, #[arg( - help = "Correct latency to avoid coordinated omission problem. It's ignored if -q is not set.", + help = "修正延迟以避免 coordinated omission 问题如果未设置 -q,则该选项会被忽略", long = "latency-correction" )] latency_correction: bool, - #[arg(help = "No realtime tui", long = "no-tui")] + #[arg(help = "非实时 tui", long = "no-tui")] no_tui: bool, - #[arg(help = "Frame per second for tui.", default_value = "16", long = "fps")] + #[arg(help = " TUI帧率.", default_value = "16", long = "fps")] fps: usize, #[arg( - help = "HTTP method", + help = "HTTP 方法", short = 'm', long = "method", default_value = "GET" @@ -180,107 +184,109 @@ Note: If qps is specified, burst will be ignored", default_value = "5s" )] connect_timeout: humantime::Duration, - #[arg(help = "HTTP Accept Header.", short = 'A')] + #[arg(help = "HTTP 接受头.", short = 'A')] accept_header: Option, - #[arg(help = "HTTP request body.", short = 'd', conflicts_with_all = ["body_path", "body_path_lines", "form"])] + #[arg(help = "HTTP 请求体.", short = 'd', conflicts_with_all = ["body_path", "body_path_lines", "form"])] body_string: Option, - #[arg(help = "HTTP request body from file.", short = 'D', conflicts_with_all = ["body_string", "body_path_lines", "form"])] + #[arg(help = "HTTP 请求体来自文件.", short = 'D', conflicts_with_all = ["body_string", "body_path_lines", "form"])] body_path: Option, - #[arg(help = "HTTP request body from file line by line.", short = 'Z', conflicts_with_all = ["body_string", "body_path", "form"])] + #[arg(help = "HTTP 请求体来自文件,逐行读取.", short = 'Z', conflicts_with_all = ["body_string", "body_path", "form"])] body_path_lines: Option, #[arg( - help = "Specify HTTP multipart POST data (curl compatible). Examples: -F 'name=value' -F 'file=@path/to/file'", + help = "HTTP 表单数据. 格式与 curl 的 -F 选项相同,例如: -F \"field=value\" 或 -F \"file=@path/to/file\"", short = 'F', long = "form", conflicts_with_all = ["body_string", "body_path", "body_path_lines"] )] form: Vec, - #[arg(help = "Content-Type.", short = 'T')] + #[arg(help = "链接类型.", short = 'T')] content_type: Option, #[arg( - help = "Basic authentication (username:password), or AWS credentials (access_key:secret_key)", + help = "Basic 认证 (username:password), 或 AWS 凭证 (access_key:secret_key)", short = 'a' )] basic_auth: Option, - #[arg(help = "AWS session token", long = "aws-session")] + #[arg(help = "AWS 会话 token", long = "aws-session")] aws_session: Option, #[arg( - help = "AWS SigV4 signing params (format: aws:amz:region:service)", + help = " AWS SigV4 签名参数,格式:aws:amz:region:service", long = "aws-sigv4" )] aws_sigv4: Option, - #[arg(help = "HTTP proxy", short = 'x')] + #[arg(help = "HTTP 代理", short = 'x')] proxy: Option, #[arg( - help = "HTTP version to connect to proxy. Available values 0.9, 1.0, 1.1, 2.", + help = "连接代理使用的 HTTP 版本可选:0.9, 1.0, 1.1, 2", long = "proxy-http-version" )] proxy_http_version: Option, #[arg( - help = "Use HTTP/2 to connect to proxy. Shorthand for --proxy-http-version=2", + help = "使用 HTTP/2 连接代理等同于:--proxy-http-version=2", long = "proxy-http2" )] proxy_http2: bool, #[arg( - help = "HTTP version. Available values 0.9, 1.0, 1.1, 2, 3", + help = " HTTP 版本. 可选: 0.9, 1.0, 1.1, 2, 3", long = "http-version" )] http_version: Option, - #[arg(help = "Use HTTP/2. Shorthand for --http-version=2", long = "http2")] + #[arg(help = "使用 HTTP/2. 等同于: --http-version=2", long = "http2")] http2: bool, - #[arg(help = "HTTP Host header", long = "host")] + #[arg(help = "HTTP Host 头", long = "host")] host: Option, - #[arg(help = "Disable compression.", long = "disable-compression")] + #[arg(help = "禁用压缩.", long = "disable-compression")] disable_compression: bool, #[arg( - help = "Limit for number of Redirect. Set 0 for no redirection. Redirection isn't supported for HTTP/2.", + help = "重定向最大次数设置 0 表示不允许重定向HTTP/2 不支持重定向 HTTP/2.", default_value = "10", short = 'r', long = "redirect" )] redirect: usize, #[arg( - help = "Disable keep-alive, prevents re-use of TCP connections between different HTTP requests. This isn't supported for HTTP/2.", + help = "禁用 keep-alive,防止在不同的 HTTP 请求之间重用 TCP 连接。此功能不支持 HTTP/2。", long = "disable-keepalive" )] disable_keepalive: bool, #[arg( - help = "*Not* perform a DNS lookup at beginning to cache it", + help = "*不*在开始时执行 DNS 查找以缓存它", long = "no-pre-lookup", default_value = "false" )] no_pre_lookup: bool, - #[arg(help = "Lookup only ipv6.", long = "ipv6")] + #[arg(help = "仅解析 IPv6.", long = "ipv6")] ipv6: bool, - #[arg(help = "Lookup only ipv4.", long = "ipv4")] + #[arg(help = "仅解析 IPv4.", long = "ipv4")] ipv4: bool, #[arg( - help = "(TLS) Use the specified certificate file to verify the peer. Native certificate store is used even if this argument is specified.", + help = "(TLS) 使用指定证书验证服务器即使指定该参数,也会使用系统证书库.", long )] cacert: Option, #[arg( - help = "(TLS) Use the specified client certificate file. --key must be also specified", + help = "(TLS) 客户端证书文件必须同时指定 --key", long, requires = "key" )] cert: Option, #[arg( - help = "(TLS) Use the specified client key file. --cert must be also specified", + help = "(TLS) 客户端私钥文件必须同时指定 --cert", long, requires = "cert" )] key: Option, - #[arg(help = "Accept invalid certs.", long = "insecure")] + #[arg(help = "接受无效证书.", long = "insecure")] insecure: bool, #[arg( - help = "Override DNS resolution and default port numbers with strings like 'example.org:443:localhost:8443' -Note: if used several times for the same host:port:target_host:target_port, a random choice is made", + help = "覆盖 DNS 解析和默认端口格式: + example.org:443:localhost:8443 + 如果同一 host:port 指定多个目标, + 会随机选择", long = "connect-to" )] connect_to: Vec, #[arg( - help = "Disable the color scheme.", + help = "禁用颜色方案.", alias = "disable-color", long = "no-color", env = "NO_COLOR" @@ -288,7 +294,7 @@ Note: if used several times for the same host:port:target_host:target_port, a ra no_color: bool, #[cfg(unix)] #[arg( - help = "Connect to a unix socket instead of the domain in the URL. Only for non-HTTPS URLs.", + help = "通过 Unix Socket 连接仅适用于非 HTTPS URL.", long = "unix-socket", group = "socket-type" )] @@ -302,30 +308,38 @@ Note: if used several times for the same host:port:target_host:target_port, a ra )] vsock_addr: Option, #[arg( - help = "Include a response status code successful or not successful breakdown for the time histogram and distribution statistics", + help = "在统计中显示成功请求的响应时间分布和直方图", long = "stats-success-breakdown" )] stats_success_breakdown: bool, #[arg( - help = "Write succeeded requests to sqlite database url E.G test.db", + help = "将成功请求写入 sqlite 数据库示例:test.db", long = "db-url" )] db_url: Option, #[arg( long, - help = "Perform a single request and dump the request and response" + help = "发送单个请求并输出请求与响应" )] debug: bool, #[arg( - help = "Output file to write the results to. If not specified, results are written to stdout.", + help = "输出文件,用于写入结果。如果未指定,则结果将写入 stdout。", long, short )] output: Option, - #[arg(help = "Output format", long, default_value = "text")] + #[arg(help = "输出格式", long, default_value = "text")] output_format: Option, #[arg( - help = "Time unit to be used. If not specified, the time unit is determined automatically. This option affects only text format.", + help = "指定时间单位如果未指定,会自动选择仅影响 text 输出格式 + 可选: + ns + us + ms + s + m + h +", long, short = 'u' )] diff --git a/src/monitor.rs b/src/monitor.rs index eb6fd3f1..ca11bb10 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -215,7 +215,7 @@ impl Monitor { EndLine::NumQuery(n) => format!("{} / {}", all.len(), n), }; let gauge = Gauge::default() - .block(Block::default().title("Progress").borders(Borders::ALL)) + .block(Block::default().title("进度").borders(Borders::ALL)) .gauge_style(Style::default().fg(colors.light_blue.unwrap_or(Color::White))) .label(Span::raw(gauge_label)) .ratio(progress); @@ -244,21 +244,21 @@ impl Monitor { .collect(); let stats_text = vec![ - Line::from(format!("Requests : {}", last_1_timescale.len())), + Line::from(format!("请求 : {}", last_1_timescale.len())), Line::from(vec![Span::styled( - format!("Slowest: {:.4} secs", last_1_minmaxmean.max(),), + format!("最慢: {:.4} secs", last_1_minmaxmean.max(),), Style::default().fg(colors.yellow.unwrap_or(Color::Reset)), )]), Line::from(vec![Span::styled( - format!("Fastest: {:.4} secs", last_1_minmaxmean.min(),), + format!("最快: {:.4} secs", last_1_minmaxmean.min(),), Style::default().fg(colors.green.unwrap_or(Color::Reset)), )]), Line::from(vec![Span::styled( - format!("Average: {:.4} secs", last_1_minmaxmean.mean(),), + format!("平均: {:.4} secs", last_1_minmaxmean.mean(),), Style::default().fg(colors.light_blue.unwrap_or(Color::Reset)), )]), Line::from(format!( - "Data: {:.2}", + "数据: {:.2}", Byte::from_u64( last_1_timescale .iter() @@ -270,7 +270,7 @@ impl Monitor { #[cfg(unix)] // Note: Windows can open 255 * 255 * 255 files. So not showing on windows is OK. Line::from(format!( - "Number of open files: {} / {}", + "打开的文件数: {} / {}", nofile .map(|c| c.to_string()) .unwrap_or_else(|_| "Error".to_string()), @@ -280,7 +280,7 @@ impl Monitor { .unwrap_or_else(|_| "Unknown".to_string()) )), ]; - let stats_title = format!("Stats for last {timescale}"); + let stats_title = format!("统计时间 {timescale}"); let stats = Paragraph::new(stats_text).block( Block::default() .title(Span::raw(stats_title)) @@ -300,13 +300,13 @@ impl Monitor { .collect::>(); let stats2 = Paragraph::new(stats2_text).block( Block::default() - .title("Status code distribution") + .title("状态码分布") .borders(Borders::ALL), ); f.render_widget(stats2, mid[1]); let mut error_v: Vec<(String, usize)> = - all.error_distribution().clone().into_iter().collect(); + all.error_distribution().clone().into_iter().collect (); error_v.sort_by_key(|t| std::cmp::Reverse(t.1)); let errors_text = error_v .into_iter() @@ -314,7 +314,7 @@ impl Monitor { .collect::>(); let errors = Paragraph::new(errors_text).block( Block::default() - .title("Error distribution") + .title("Error") .borders(Borders::ALL), ); f.render_widget(errors, row4[2]); @@ -379,7 +379,7 @@ impl Monitor { let resp_histo = BarChart::default() .block( Block::default() - .title("Response time histogram") + .title("响应时间") .style( Style::default() .fg(colors.yellow.unwrap_or(Color::Reset)) diff --git a/src/printer.rs b/src/printer.rs index 5587e9f4..4e4a21dc 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -454,13 +454,13 @@ fn print_summary( let style = StyleScheme { style_enabled: !disable_style, }; - writeln!(w, "{}", style.heading("Summary:"))?; + writeln!(w, "{}", style.heading("摘要:"))?; let success_rate = 100.0 * res.success_rate(); writeln!( w, "{}", style.success_rate( - &format!(" Success rate:\t{success_rate:.2}%"), + &format!(" 成功率:\t{success_rate:.2}%"), success_rate ) )?; @@ -481,7 +481,7 @@ fn print_summary( w, "{}", style.slowest(&format!( - " Slowest:\t{:.4} {timescale}", + " 最慢:\t{:.4} {timescale}", latency_stat.max() / timescale.as_secs_f64() )) )?; @@ -489,7 +489,7 @@ fn print_summary( w, "{}", style.fastest(&format!( - " Fastest:\t{:.4} {timescale}", + " 最快:\t{:.4} {timescale}", latency_stat.min() / timescale.as_secs_f64() )) )?; @@ -497,32 +497,32 @@ fn print_summary( w, "{}", style.average(&format!( - " Average:\t{:.4} {timescale}", + " 平均值:\t{:.4} {timescale}", latency_stat.mean() / timescale.as_secs_f64() )) )?; writeln!( w, - " Requests/sec:\t{:.4}", + " 每秒请求速度:\t{:.4}", res.len() as f64 / total_duration.as_secs_f64() )?; writeln!(w)?; writeln!( w, - " Total data:\t{:.2}", + " 总数据:\t{:.2}", Byte::from_u64(res.total_data() as u64).get_appropriate_unit(byte_unit::UnitType::Binary) )?; if let Some(size) = res .size_per_request() .map(|n| Byte::from_u64(n).get_appropriate_unit(byte_unit::UnitType::Binary)) { - writeln!(w, " Size/request:\t{size:.2}")?; + writeln!(w, " 请求大小:\t{size:.2}")?; } else { - writeln!(w, " Size/request:\tNaN")?; + writeln!(w, " 请求大小:\tNaN")?; } writeln!( w, - " Size/sec:\t{:.2}", + " 每秒大小:\t{:.2}", Byte::from_u64((res.total_data() as f64 / total_duration.as_secs_f64()) as u64) .get_appropriate_unit(byte_unit::UnitType::Binary) )?; @@ -530,11 +530,11 @@ fn print_summary( let duration_all_statistics = res.duration_all_statistics(); - writeln!(w, "{}", style.heading("Response time histogram:"))?; + writeln!(w, "{}", style.heading("响应时间直方图:"))?; print_histogram(w, &duration_all_statistics.histogram, style, timescale)?; writeln!(w)?; - writeln!(w, "{}", style.heading("Response time distribution:"))?; + writeln!(w, "{}", style.heading("响应时间分布:"))?; print_distribution(w, &duration_all_statistics.percentiles, style, timescale)?; writeln!(w)?; @@ -544,7 +544,7 @@ fn print_summary( writeln!( w, "{}", - style.heading("Response time histogram (2xx only):") + style.heading("响应时间直方图 (仅 2xx):") )?; print_histogram(w, &durations_successful_statics.histogram, style, timescale)?; writeln!(w)?; @@ -552,7 +552,7 @@ fn print_summary( writeln!( w, "{}", - style.heading("Response time distribution (2xx only):") + style.heading("响应时间分布 (仅 2xx):") )?; print_distribution( w, @@ -567,7 +567,7 @@ fn print_summary( writeln!( w, "{}", - style.heading("Response time histogram (4xx + 5xx only):") + style.heading("响应时间直方图 (4xx + 5xx only):") )?; print_histogram(w, &durations_not_successful.histogram, style, timescale)?; writeln!(w)?; @@ -575,7 +575,7 @@ fn print_summary( writeln!( w, "{}", - style.heading("Response time distribution (4xx + 5xx only):") + style.heading("响应时间分布 (4xx + 5xx only):") )?; print_distribution(w, &durations_not_successful.percentiles, style, timescale)?; writeln!(w)?; @@ -588,7 +588,7 @@ fn print_summary( writeln!( w, "{}", - style.heading("Details (average, fastest, slowest):") + style.heading("详情 (平均,最快,最慢):") )?; writeln!( @@ -612,7 +612,7 @@ fn print_summary( let mut status_v: Vec<(http::StatusCode, usize)> = status_dist.into_iter().collect(); status_v.sort_by_key(|t| std::cmp::Reverse(t.1)); - writeln!(w, "{}", style.heading("Status code distribution:"))?; + writeln!(w, "{}", style.heading("状态码分布:"))?; for (status, count) in status_v { writeln!( @@ -634,7 +634,7 @@ fn print_summary( if !error_v.is_empty() { writeln!(w)?; - writeln!(w, "Error distribution:")?; + writeln!(w, "Error:")?; for (error, count) in error_v { writeln!(w, " [{count}] {error}")?; } From 45af1ead40be8ee58725952a4ddb480d42d31423 Mon Sep 17 00:00:00 2001 From: xrb114 Date: Wed, 11 Mar 2026 10:35:35 +0800 Subject: [PATCH 2/4] fix(lib): update comments and user-agent format --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 63624b17..a5b4e41f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ use rand_regex::Regex; use ratatui::crossterm; use result_data::ResultData; use std::{ - env, + //env, fs::File, io::{BufRead, BufReader, Read}, path::{Path, PathBuf}, @@ -494,12 +494,12 @@ pub async fn run(mut opts: Opts) -> anyhow::Result<()> { ); } - // User agent + // User-agent headers .entry(http::header::USER_AGENT) .or_insert(HeaderValue::from_static(concat!( - "oha/", - env!("CARGO_PKG_VERSION") + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36/", + //env!("CARGO_PKG_VERSION") ))); if let Some(h) = opts.accept_header { From bc0d01700863681af2272ea09b636939052b387b Mon Sep 17 00:00:00 2001 From: xrb114 Date: Wed, 11 Mar 2026 10:49:57 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E7=BF=BB=E8=AF=91=20README.md=20=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E5=AE=89=E8=A3=85=E8=AF=B4=E6=98=8E=E5=92=8C=E7=94=A8?= =?UTF-8?q?=E6=B3=95=E9=83=A8=E5=88=86=E4=B8=BA=E4=B8=AD=E6=96=87=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 586 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 443 insertions(+), 143 deletions(-) diff --git a/README.md b/README.md index 791b3b2a..f95ce4fc 100644 --- a/README.md +++ b/README.md @@ -7,104 +7,118 @@ [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/hatoo) -oha is a tiny program that sends some load to a web application and show realtime tui inspired by [rakyll/hey](https://github.com/rakyll/hey). +oha 是一个小型程序,用于向 Web 应用发送一些负载,并展示受 [rakyll/hey](https://github.com/rakyll/hey) 启发的实时 TUI。 -This program is written in Rust and powered by [tokio](https://github.com/tokio-rs/tokio) and beautiful tui by [ratatui](https://github.com/ratatui-org/ratatui). +该程序使用 Rust 编写,并由 [tokio](https://github.com/tokio-rs/tokio) 提供支持,并使用 [ratatui](https://github.com/ratatui-org/ratatui) 展示美观的 TUI。 ![demo](demo.gif) -# Installation +# 安装 -This program is built on stable Rust, with both `make` and `cmake` prerequisites to install via cargo. +该程序基于稳定的 Rust 构建,通过 Cargo 安装需要 `make` 和 `cmake` 作为先决条件。 - cargo install oha +``` +cargo install oha +``` -You can optionally build oha against [native-tls](https://github.com/sfackler/rust-native-tls) instead of [rustls](https://github.com/rustls/rustls). +您可以选择将 oha 构建在 [native-tls](https://github.com/sfackler/rust-native-tls) 上而不是 [rustls](https://github.com/rustls/rustls)。 - cargo install --no-default-features --features native-tls oha +``` +cargo install --no-default-features --features native-tls oha +``` -You can enable VSOCK support by enabling `vsock` feature. +您可以通过启用 `vsock` 功能来启用 VSOCK 支持。 - cargo install --features vsock oha +``` +cargo install --features vsock oha +``` -You can enable experimental HTTP3 support by enabling the `http3` feature. This uses the [H3](https://github.com/hyperium/h3/r) library by the developers of Hyper. -It will remain experimental as long as H3 is experimental. It currently depends on using `rustls` for TLS. +您可以通过启用 `http3` 功能来启用实验性的 HTTP3 支持。这使用了由 Hyper 开发者提供的 [H3](https://github.com/hyperium/h3/r) 库。 +只要 H3 仍处于实验阶段,它就会保持实验性。目前它依赖于使用 rustls 进行 TLS。 -## Download pre-built binary +## 下载预构建二进制文件 -You can download pre-built binary from [Release page](https://github.com/hatoo/oha/releases) for each version and from [Publish workflow](https://github.com/hatoo/oha/actions/workflows/release.yml) and [Publish PGO workflow](https://github.com/hatoo/oha/actions/workflows/release-pgo.yml) for each commit. +您可以从每个版本的 [发布页面](https://github.com/hatoo/oha/releases) 或每次提交的 [发布工作流](https://github.com/hatoo/oha/actions/workflows/release.yml) 和 [发布 PGO 工作流](https://github.com/hatoo/oha/actions/workflows/release-pgo.yml) 下载预构建的二进制文件。 -## On Arch Linux +## 在 Arch Linux 上 - pacman -S oha +``` +pacman -S oha +``` -## On macOS (Homebrew) +## 在 macOS (Homebrew) 上 - brew install oha +``` +brew install oha +``` -## On Windows (winget) +## 在 Windows (winget) 上 - winget install hatoo.oha +``` +winget install hatoo.oha +``` -## On Debian ([Azlux's repository](http://packages.azlux.fr/)) +## 在 Debian ([Azlux's repository](http://packages.azlux.fr/)) 上 - echo "deb [signed-by=/usr/share/keyrings/azlux-archive-keyring.gpg] http://packages.azlux.fr/debian/ stable main" | sudo tee /etc/apt/sources.list.d/azlux.list - sudo wget -O /usr/share/keyrings/azlux-archive-keyring.gpg https://azlux.fr/repo.gpg - apt update - apt install oha +``` +echo "deb [signed-by=/usr/share/keyrings/azlux-archive-keyring.gpg] http://packages.azlux.fr/debian/ stable main" | sudo tee /etc/apt/sources.list.d/azlux.list +sudo wget -O /usr/share/keyrings/azlux-archive-keyring.gpg https://azlux.fr/repo.gpg +apt update +apt install oha +``` ## X-CMD (Linux, macOS, Windows WSL/GitBash) -You can install with [x-cmd](https://www.x-cmd.com). +您可以使用 [x-cmd](https://www.x-cmd.com) 安装。 ```sh x env use oha ``` -## Containerized +## 容器化 -### Official Docker image +### 官方 Docker 镜像 [ghcr.io/hatoo/oha](https://github.com/hatoo/oha/pkgs/container/oha) -### Build image locally +### 本地构建镜像 -You can also build and create a container image including oha +您也可以构建并创建包含 oha 的容器镜像 ```sh docker build -t hatoo/oha:latest . ``` -Then you can use oha directly through the container +然后您可以通过容器直接使用 oha ```sh docker run --rm -it --network=host hatoo/oha:latest https://example.com:3000 ``` -## Profile-Guided Optimization (PGO) +## 基于配置文件的优化 (PGO) -You can build `oha` with PGO by using the following commands: +您可以通过以下命令使用 PGO 构建 `oha`: ```sh bun run pgo.js ``` -And the binary will be available at `target/[target-triple]/pgo/oha`. +并且二进制文件将位于 `target/[target-triple]/pgo/oha`。 -**Note**: Please keep in mind that in order to run the aforementioned command, -you need to have installed `cargo-pgo` cargo package. +**注意**:请记住,为了运行上述命令, +您需要安装 `cargo-pgo` cargo 包。 -You can install it via `cargo install cargo-pgo`. +您可以通过 `cargo install cargo-pgo` 安装它。 -# Platform +# 平台 -- Linux - Tested on Ubuntu 18.04 gnome-terminal -- Windows 10 - Tested on Windows Powershell -- MacOS - Tested on iTerm2 +- Linux - 在 Ubuntu 18.04 gnome-terminal 上测试 +- Windows 10 - 在 Windows Powershell 上测试 +- MacOS - 在 iTerm2 上测试 -# Usage +# 用法 -`-q` option works different from [rakyll/hey](https://github.com/rakyll/hey). It's set overall query per second instead of for each workers. +`-q` 选项的工作方式与 [rakyll/hey](https://github.com/rakyll/hey) 不同。它是设置总体每秒查询次数,而不是针对每个工作线程。 ```sh Ohayou(おはよう), HTTP load generator, inspired by rakyll/hey with tui animation. @@ -112,190 +126,190 @@ Ohayou(おはよう), HTTP load generator, inspired by rakyll/hey with tui anima Usage: oha [OPTIONS] Arguments: - Target URL or file with multiple URLs. + 目标 URL 或包含多个 URL 的文件。 Options: -n - Number of requests to run. Accepts plain numbers or suffixes: k = 1,000, m = 1,000,000 (e.g. 10k, 1m). [default: 200] + 要运行的请求数量。接受纯数字或后缀:k = 1,000, m = 1,000,000 (例如 10k, 1m)。 [默认值: 200] -c - Number of connections to run concurrently. You may should increase limit to number of open files for larger `-c`. [default: 50] + 要并发运行的连接数。对于较大的 `-c`,您可能需要增加对打开文件数量的限制。 [默认值: 50] -p - Number of parallel requests to send on HTTP/2. `oha` will run c * p concurrent workers in total. [default: 1] + 在 HTTP/2 上发送的并行请求数量。`oha` 将总共运行 c * p 个并发工作线程。 [默认值: 1] -z - Duration of application to send requests. - 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. + 应用程序发送请求的持续时间。 + 在 HTTP/1 上,当达到持续时间时,正在进行的请求会被中止并计为“因截止时间而中止” + 您可以使用 `-w` 选项更改此行为。 + 当前,在 HTTP/2 上,当达到持续时间时,会等待正在进行的请求。`-w` 选项被忽略。 + 示例:-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) + 总体查询速率限制,单位为每秒查询次数 (QPS) --burst-delay - Introduce delay between a predefined number of requests. - Note: If qps is specified, burst will be ignored + 在预定义数量的请求之间引入延迟。 + 注意:如果指定了 qps,则会忽略 burst --burst-rate - Rates of requests for burst. Default is 1 - Note: If qps is specified, burst will be ignored + 突发请求的速率。默认为 1 + 注意:如果指定了 qps,则会忽略 burst --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 do not work well. See https://docs.rs/rand_regex/latest/rand_regex/struct.Regex.html for details of syntax. + 使用 rand_regex crate 生成 URL,但点在此查询中被禁用 例如 http://127.0.0.1/[a-z][a-z][0-9]。当前动态方案、主机和端口与 keep-alive 不能很好地工作。有关语法细节,请参见 https://docs.rs/rand_regex/latest/rand_regex/struct.Regex.html。 --urls-from-file - Read the URLs to query from a file + 从文件读取要查询的 URL --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] + '--rand-regex-url' 的参数。max_repeat 参数给出了 x*, x+ 和 x{n,} 操作符将变成的最大额外重复次数。 [默认值: 4] --dump-urls - Dump target Urls times to debug --rand-regex-url + 将目标 URL 转储 次以调试 --rand-regex-url --latency-correction - Correct latency to avoid coordinated omission problem. It's ignored if -q is not set. + 校正延迟以避免协调遗漏问题。如果未设置 -q,则忽略此选项。 --no-tui - No realtime tui + 无实时 TUI --fps - Frame per second for tui. [default: 16] + TUI 的帧率。 [默认值: 16] -m, --method - HTTP method [default: GET] + HTTP 方法 [默认值: GET] -H - Custom HTTP header. Examples: -H "foo: bar" + 自定义 HTTP 头。示例:-H "foo: bar" --proxy-header - Custom Proxy HTTP header. Examples: --proxy-header "foo: bar" + 自定义代理 HTTP 头。示例:--proxy-header "foo: bar" -t - Timeout for each request. Default to infinite. + 每个请求的超时时间。默认为无限。 --connect-timeout - Timeout for establishing a new connection. Default to 5s. [default: 5s] + 建立新连接的超时时间。默认为 5s。 [默认值: 5s] -A - HTTP Accept Header. + HTTP Accept 头。 -d - HTTP request body. + HTTP 请求体。 -D - HTTP request body from file. + 从文件读取 HTTP 请求体。 -Z - HTTP request body from file line by line. + 逐行从文件读取 HTTP 请求体。 -F, --form
- Specify HTTP multipart POST data (curl compatible). Examples: -F 'name=value' -F 'file=@path/to/file' + 指定 HTTP multipart POST 数据 (与 curl 兼容)。示例:-F 'name=value' -F 'file=@path/to/file' -T - Content-Type. + 内容类型。 -a - Basic authentication (username:password), or AWS credentials (access_key:secret_key) + 基本认证 (用户名:密码),或 AWS 凭证 (访问密钥:私有密钥) --aws-session - AWS session token + AWS 会话令牌 --aws-sigv4 - AWS SigV4 signing params (format: aws:amz:region:service) + AWS SigV4 签名参数 (格式: aws:amz:region:service) -x - HTTP proxy + HTTP 代理 --proxy-http-version - HTTP version to connect to proxy. Available values 0.9, 1.0, 1.1, 2. + 连接到代理的 HTTP 版本。可用值 0.9, 1.0, 1.1, 2。 --proxy-http2 - Use HTTP/2 to connect to proxy. Shorthand for --proxy-http-version=2 + 使用 HTTP/2 连接代理。相当于 --proxy-http-version=2 --http-version - HTTP version. Available values 0.9, 1.0, 1.1, 2, 3 + HTTP 版本。可用值 0.9, 1.0, 1.1, 2, 3 --http2 - Use HTTP/2. Shorthand for --http-version=2 + 使用 HTTP/2。相当于 --http-version=2 --host - HTTP Host header + HTTP Host 头 --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] + 重定向次数限制。设置 0 表示不重定向。HTTP/2 不支持重定向。 [默认值: 10] --disable-keepalive - Disable keep-alive, prevents re-use of TCP connections between different HTTP requests. This isn't supported for HTTP/2. + 禁用 keep-alive,防止在不同 HTTP 请求之间重用 TCP 连接。HTTP/2 不支持此功能。 --no-pre-lookup - *Not* perform a DNS lookup at beginning to cache it + *不* 在开始时执行 DNS 查找以缓存它 --ipv6 - Lookup only ipv6. + 仅查找 ipv6。 --ipv4 - Lookup only ipv4. + 仅查找 ipv4。 --cacert - (TLS) Use the specified certificate file to verify the peer. Native certificate store is used even if this argument is specified. + (TLS) 使用指定的证书文件验证对等方。即使指定了此参数,也会使用本机证书存储。 --cert - (TLS) Use the specified client certificate file. --key must be also specified + (TLS) 使用指定的客户端证书文件。必须同时指定 --key --key - (TLS) Use the specified client key file. --cert must be also specified + (TLS) 使用指定的客户端密钥文件。必须同时指定 --cert --insecure - Accept invalid certs. + 接受无效证书。 --connect-to - Override DNS resolution and default port numbers with strings like 'example.org:443:localhost:8443' - Note: if used several times for the same host:port:target_host:target_port, a random choice is made + 使用类似 'example.org:443:localhost:8443' 的字符串覆盖 DNS 解析和默认端口号 + 注意:如果对同一 host:port:target_host:target_port 多次使用,将随机选择 --no-color - Disable the color scheme. [env: NO_COLOR=] + 禁用配色方案。 [环境变量: NO_COLOR=] --unix-socket - Connect to a unix socket instead of the domain in the URL. Only for non-HTTPS URLs. + 连接到 Unix 套接字而不是 URL 中的域。仅适用于非 HTTPS URL。 --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 + 将成功的请求写入 sqlite 数据库 URL,例如 test.db --debug - Perform a single request and dump the request and response + 执行单个请求并转储请求和响应 -o, --output - Output file to write the results to. If not specified, results are written to stdout. + 将结果写入的输出文件。如果未指定,则结果写入标准输出。 --output-format - Output format [default: text] [possible values: text, json, csv, quiet] + 输出格式 [默认值: text] [可能的值: text, json, csv, quiet] -u, --time-unit - Time unit to be used. If not specified, the time unit is determined automatically. This option affects only text format. [possible values: ns, us, ms, s, m, h] + 要使用的时间单位。如果未指定,则自动确定时间单位。此选项仅影响文本格式。 [可能的值: ns, us, ms, s, m, h] -h, --help - Print help + 打印帮助 -V, --version - Print version + 打印版本 ``` -# Performance +# 性能 -`oha` uses faster implementation when `--no-tui` option is set and both `-q` and `--burst-delay` are not set because it can avoid overhead to gather data realtime. +当设置了 `--no-tui` 选项且未设置 `-q` 和 `--burst-delay` 时,`oha` 会使用更快的实现,因为它可以避免实时收集数据的开销。 -# Output +# 输出 -By default `oha` outputs a text summary of the results. +默认情况下,`oha` 输出结果的文本摘要。 -`oha` prints JSON summary output when `--output-format json` option is set. -The schema of JSON output is defined in [schema.json](./schema.json). +当设置了 `--output-format json` 选项时,`oha` 会打印 JSON 摘要输出。 +JSON 输出的模式定义在 [schema.json](./schema.json) 中。 -When `--output-format csv` is used result of each request is printed as a line of comma separated values. +当使用 `--output-format csv` 时,每个请求的结果将以逗号分隔值的形式打印为一行。 -# Tips +# 提示 -## Stress test in more realistic condition +## 在更现实的条件下进行压力测试 -`oha` uses default options inherited from [rakyll/hey](https://github.com/rakyll/hey) but you may need to change options to stress test in more realistic condition. +`oha` 使用从 [rakyll/hey](https://github.com/rakyll/hey) 继承的默认选项,但您可能需要更改选项以在更现实的条件下进行压力测试。 -I suggest to run `oha` with following options. +我建议使用以下选项运行 `oha`。 ```sh -oha <-z or -n> -c -q --latency-correction --disable-keepalive +oha <-z or -n> -c <并发连接数> -q <每秒查询数> --latency-correction --disable-keepalive <目标地址> ``` - --disable-keepalive - - In real, user doesn't query same URL using [Keep-Alive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Keep-Alive). You may want to run without `Keep-Alive`. + + 在现实中,用户不会使用 [Keep-Alive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Keep-Alive) 查询同一个 URL。您可能希望在没有 Keep-Alive 的情况下运行。 - --latency-correction + + 您可以使用 `--latency-correction` 来避免 `协调遗漏问题`。 - You can avoid `Coordinated Omission Problem` by using `--latency-correction`. +## 突发功能 -## Burst feature - -You can use `--burst-delay` along with `--burst-rate` option to introduce delay between a defined number of requests. +您可以使用 `--burst-delay` 与 `--burst-rate` 选项一起,在定义数量的请求之间引入延迟。 ```sh oha -n 10 --burst-delay 2s --burst-rate 4 ``` -In this particular scenario, every 2 seconds, 4 requests will be processed, and after 6s the total of 10 requests will be processed. -*NOTE: If you don't set `--burst-rate` option, the amount is default to 1* +在这种特定场景中,每 2 秒处理 4 个请求,6 秒后总共处理 10 个请求。 +*注意:如果您不设置 `--burst-rate` 选项,则数量默认为 1* -## Dynamic url feature +## 动态 URL 功能 -You can use `--rand-regex-url` option to generate random url for each connection. +您可以使用 `--rand-regex-url` 选项为每个连接生成随机 URL。 ```sh oha --rand-regex-url http://127.0.0.1/[a-z][a-z][0-9] ``` -Each Urls are generated by [rand_regex](https://github.com/kennytm/rand_regex) crate but regex's dot is disabled since it's not useful for this purpose and it's very inconvenient if url's dots are interpreted as regex's dot. +每个 URL 都由 [rand_regex](https://github.com/kennytm/rand_regex) crate 生成,但由于对于此目的来说不实用,且如果 URL 的点被解释为正则表达式的点会非常不便,所以正则表达式的点已被禁用。 -Optionally you can set `--max-repeat` option to limit max repeat count for each regex. e.g http://127.0.0.1/[a-z]* with `--max-repeat 4` will generate url like http://127.0.0.1/[a-z]{0,4} +您可以选择设置 `--max-repeat` 选项来限制每个正则表达式的最大重复次数。例如,`http://127.0.0.1/[a-z]*` 与 `--max-repeat 4` 一起使用将生成类似 `http://127.0.0.1/[a-z]{0,4}` 的 URL。 -Currently dynamic scheme, host and port with keep-alive are not works well. +目前,带有 keep-alive 的动态方案、主机和端口无法正常工作。 -## URLs from file feature +## 从文件读取 URL 功能 -You can use `--urls-from-file` to read the target URLs from a file. Each line of this file needs to contain one valid URL as in the example below. +您可以使用 `--urls-from-file` 从文件读取目标 URL。该文件的每一行都需要包含一个有效的 URL,如下例所示。 ``` http://domain.tld/foo/bar @@ -305,18 +319,304 @@ http://domain.tld/foo/bar?q=test http://domain.tld/foo ``` -Such a file can for example be created from an access log to generate a more realistic load distribution over the different pages of a server. +例如,可以从访问日志创建此类文件,以在服务器的不同页面上生成更真实的负载分布。 + +当使用这种 URL 规范时,每个请求都会随机访问文件中给出的 URL。 + +# 贡献 + +欢迎帮助我们! + +以下是一些需要改进的领域。 + +- 编写测试 +- 改进 TUI 设计。 + - 显示更多信息? +- 提升速度 + - 我是 tokio 新手。我认为在查询调度方面还有一些优化空间。 + +桌面平板移动/微信 +oha (おはよう) +GitHub Actions +Crates.io +Arch Linux +Homebrew + +ko-fi + +oha 是一个小型程序,用于向 Web 应用发送一些负载,并展示受 rakyll/hey 启发的实时 TUI。 + +该程序使用 Rust 编写,并由 tokio 提供支持,并使用 ratatui 展示美观的 TUI。 + +演示 + +安装 +该程序基于稳定的 Rust 构建,通过 Cargo 安装需要 make 和 cmake 作为先决条件。 + +cargo install oha + +您可以选择将 oha 构建在 native-tls 上而不是 rustls。 + +cargo install --no-default-features --features native-tls oha + +您可以通过启用 vsock 功能来启用 VSOCK 支持。 + +cargo install --features vsock oha + +您可以通过启用 http3 功能来启用实验性的 HTTP3 支持。这使用了由 Hyper 开发者提供的 H3 库。 +只要 H3 仍处于实验阶段,它就会保持实验性。目前它依赖于使用 rustls 进行 TLS。 + +下载预构建二进制文件 +您可以从每个版本的发布页面或每次提交的发布工作流和发布 PGO 工作流下载预构建的二进制文件。 + +在 Arch Linux 上 +pacman -S oha + +在 macOS (Homebrew) 上 +brew install oha + +在 Windows (winget) 上 +winget install hatoo.oha + +在 Debian (Azlux's repository) 上 +echo "deb [signed-by=/usr/share/keyrings/azlux-archive-keyring.gpg] http://packages.azlux.fr/debian/ stable main" | sudo tee /etc/apt/sources.list.d/azlux.list +sudo wget -O /usr/share/keyrings/azlux-archive-keyring.gpg https://azlux.fr/repo.gpg +apt update +apt install oha + +X-CMD (Linux, macOS, Windows WSL/GitBash) +您可以使用 x-cmd 安装。 + +x env use oha + +容器化 +官方 Docker 镜像 +ghcr.io/hatoo/oha + +本地构建镜像 +您也可以构建并创建包含 oha 的容器镜像 + +docker build -t hatoo/oha:latest . + +然后您可以通过容器直接使用 oha + +docker run --rm -it --network=host hatoo/oha:latest https://example.com:3000 + +基于配置文件的优化 (PGO) +您可以通过以下命令使用 PGO 构建 oha: + +bun run pgo.js + +并且二进制文件将位于 target/[target-triple]/pgo/oha。 + +注意:请记住,为了运行上述命令, +您需要安装 cargo-pgo cargo 包。 + +您可以通过 cargo install cargo-pgo 安装它。 + +平台 +Linux - 在 Ubuntu 18.04 gnome-terminal 上测试 +Windows 10 - 在 Windows Powershell 上测试 +MacOS - 在 iTerm2 上测试 +用法 +-q 选项的工作方式与 rakyll/hey 不同。它是设置总体每秒查询次数,而不是针对每个工作线程。 + +Ohayou(おはよう), HTTP load generator, inspired by rakyll/hey with tui animation. + +Usage: oha [OPTIONS] + +Arguments: +目标 URL 或包含多个 URL 的文件。 + +Options: +-n +要运行的请求数量。接受纯数字或后缀:k = 1,000, m = 1,000,000 (例如 10k, 1m)。 [默认值: 200] +-c +要并发运行的连接数。对于较大的 `-c`,您可能需要增加对打开文件数量的限制。 [默认值: 50] +-p +在 HTTP/2 上发送的并行请求数量。`oha` 将总共运行 c * p 个并发工作线程。 [默认值: 1] +-z +应用程序发送请求的持续时间。 +在 HTTP/1 上,当达到持续时间时,正在进行的请求会被中止并计为“因截止时间而中止” +您可以使用 `-w` 选项更改此行为。 +当前,在 HTTP/2 上,当达到持续时间时,会等待正在进行的请求。`-w` 选项被忽略。 +示例:-z 10s -z 3m。 +-w, --wait-ongoing-requests-after-deadline +当达到持续时间时,等待正在进行的请求 +-q +总体查询速率限制,单位为每秒查询次数 (QPS) +--burst-delay +在预定义数量的请求之间引入延迟。 +注意:如果指定了 qps,则会忽略 burst +--burst-rate +突发请求的速率。默认为 1 +注意:如果指定了 qps,则会忽略 burst +--rand-regex-url +使用 rand_regex crate 生成 URL,但点在此查询中被禁用 例如 http://127.0.0.1/[a-z][a-z][0-9]。当前动态方案、主机和端口与 keep-alive 不能很好地工作。有关语法细节,请参见 https://docs.rs/rand_regex/latest/rand_regex/struct.Regex.html。 +--urls-from-file +从文件读取要查询的 URL +--max-repeat +'--rand-regex-url' 的参数。max_repeat 参数给出了 x*, x+ 和 x{n,} 操作符将变成的最大额外重复次数。 [默认值: 4] +--dump-urls +将目标 URL 转储 次以调试 --rand-regex-url +--latency-correction +校正延迟以避免协调遗漏问题。如果未设置 -q,则忽略此选项。 +--no-tui +无实时 TUI +--fps +TUI 的帧率。 [默认值: 16] +-m, --method +HTTP 方法 [默认值: GET] +-H +自定义 HTTP 头。示例:-H "foo: bar" +--proxy-header +自定义代理 HTTP 头。示例:--proxy-header "foo: bar" +-t +每个请求的超时时间。默认为无限。 +--connect-timeout +建立新连接的超时时间。默认为 5s。 [默认值: 5s] +-A +HTTP Accept 头。 +-d +HTTP 请求体。 +-D +从文件读取 HTTP 请求体。 +-Z +逐行从文件读取 HTTP 请求体。 +-F, --form +指定 HTTP multipart POST 数据 (与 curl 兼容)。示例:-F 'name=value' -F 'file=@path/to/file' +-T +内容类型。 +-a +基本认证 (用户名:密码),或 AWS 凭证 (访问密钥:私有密钥) +--aws-session +AWS 会话令牌 +--aws-sigv4 +AWS SigV4 签名参数 (格式: aws:amz:region:service) +-x +HTTP 代理 +--proxy-http-version +连接到代理的 HTTP 版本。可用值 0.9, 1.0, 1.1, 2。 +--proxy-http2 +使用 HTTP/2 连接代理。相当于 --proxy-http-version=2 +--http-version +HTTP 版本。可用值 0.9, 1.0, 1.1, 2, 3 +--http2 +使用 HTTP/2。相当于 --http-version=2 +--host +HTTP Host 头 +--disable-compression +禁用压缩。 +-r, --redirect +重定向次数限制。设置 0 表示不重定向。HTTP/2 不支持重定向。 [默认值: 10] +--disable-keepalive +禁用 keep-alive,防止在不同 HTTP 请求之间重用 TCP 连接。HTTP/2 不支持此功能。 +--no-pre-lookup +*不* 在开始时执行 DNS 查找以缓存它 +--ipv6 +仅查找 ipv6。 +--ipv4 +仅查找 ipv4。 +--cacert +(TLS) 使用指定的证书文件验证对等方。即使指定了此参数,也会使用本机证书存储。 +--cert +(TLS) 使用指定的客户端证书文件。必须同时指定 --key +--key +(TLS) 使用指定的客户端密钥文件。必须同时指定 --cert +--insecure +接受无效证书。 +--connect-to +使用类似 'example.org:443:localhost:8443' 的字符串覆盖 DNS 解析和默认端口号 +注意:如果对同一 host:port:target_host:target_port 多次使用,将随机选择 +--no-color +禁用配色方案。 [环境变量: NO_COLOR=] +--unix-socket +连接到 Unix 套接字而不是 URL 中的域。仅适用于非 HTTPS URL。 +--stats-success-breakdown +在时间直方图和分布统计信息中包含响应状态码成功或失败的分解 +--db-url +将成功的请求写入 sqlite 数据库 URL,例如 test.db +--debug +执行单个请求并转储请求和响应 +-o, --output +将结果写入的输出文件。如果未指定,则结果写入标准输出。 +--output-format +输出格式 [默认值: text] [可能的值: text, json, csv, quiet] +-u, --time-unit +要使用的时间单位。如果未指定,则自动确定时间单位。此选项仅影响文本格式。 [可能的值: ns, us, ms, s, m, h] +-h, --help +打印帮助 +-V, --version +打印版本 + +性能 +当设置了 --no-tui 选项且未设置 -q 和 --burst-delay 时,oha 会使用更快的实现,因为它可以避免实时收集数据的开销。 + +输出 +默认情况下 oha 输出结果的文本摘要。 + +当设置了 --output-format json 选项时,oha 会打印 JSON 摘要输出。 +JSON 输出的模式定义在 schema.json 中。 + +当使用 --output-format csv 时,每个请求的结果将以逗号分隔值的形式打印为一行。 + +提示 +在更现实的条件下进行压力测试 +oha 使用从 rakyll/hey 继承的默认选项,但您可能需要更改选项以在更现实的条件下进行压力测试。 + +我建议使用以下选项运行 oha。 + +oha <-z or -n> -c <并发连接数> -q <每秒查询数> --latency-correction --disable-keepalive <目标地址> + +--disable-keepalive + +在现实中,用户不会使用 Keep-Alive 查询同一个 URL。您可能希望在没有 Keep-Alive 的情况下运行。 + +--latency-correction + +您可以使用 --latency-correction 来避免协调遗漏问题。 + +突发功能 +您可以使用 --burst-delay 与 --burst-rate 选项一起,在定义数量的请求之间引入延迟。 + +oha -n 10 --burst-delay 2s --burst-rate 4 + +在这种特定场景中,每 2 秒处理 4 个请求,6 秒后总共处理 10 个请求。 +注意:如果您不设置 --burst-rate 选项,则数量默认为 1 + +动态 URL 功能 +您可以使用 --rand-regex-url 选项为每个连接生成随机 URL。 + +oha --rand-regex-url http://127.0.0.1/[a-z][a-z][0-9] + +每个 URL 都由 rand_regex crate 生成,但由于对于此目的来说不实用,且如果 URL 的点被解释为正则表达式的点会非常不便,所以正则表达式的点已被禁用。 + +您可以选择设置 --max-repeat 选项来限制每个正则表达式的最大重复次数。例如,`http://127.0.0.1/[a-z]*` 与 `--max-repeat 4` 一起使用将生成类似 `http://127.0.0.1/[a-z]{0,4}` 的 URL。 + +目前,带有 keep-alive 的动态方案、主机和端口无法正常工作。 + +从文件读取 URL 功能 +您可以使用 --urls-from-file 从文件读取目标 URL。该文件的每一行都需要包含一个有效的 URL,如下例所示。 + +http://domain.tld/foo/bar +http://domain.tld/assets/vendors-node_modules_highlight_js_lib_index_js-node_modules_tanstack_react-query_build_modern-3fdf40-591fb51c8a6e.js +http://domain.tld/images/test.png +http://domain.tld/foo/bar?q=test +http://domain.tld/foo + +例如,可以从访问日志创建此类文件,以在服务器的不同页面上生成更真实的负载分布。 + +当使用这种 URL 规范时,每个请求都会随机访问文件中给出的 URL。 -When this type of URL specification is used, every request goes to a random URL given in the file. +贡献 +欢迎帮助我们! -# Contribution +以下是一些需要改进的领域。 -Feel free to help us! +编写测试 +改进 TUI 设计。 +显示更多信息? +提升速度 +我是 tokio 新手。我认为在查询调度方面还有一些优化空间。 -Here are some areas which need improving. -- Write tests -- Improve tui design. - - Show more information? -- Improve speed - - I'm new to tokio. I think there are some space to optimize query scheduling. From 2a8950e0568865b750df7a5cdb8bc235400542d8 Mon Sep 17 00:00:00 2001 From: xrb114 Date: Wed, 18 Mar 2026 10:26:25 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=E6=8F=90=E5=8D=87=E4=BA=86=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a5b4e41f..a506d3d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,16 +110,14 @@ pub struct Opts { #[arg(help = "所有请求的速率限制,单位为每秒查询次数(QPS)", short = 'q', conflicts_with_all = ["burst_duration", "burst_requests"])] query_per_second: Option, #[arg( - help = "Introduce delay between a predefined number of requests. -Note: If qps is specified, burst will be ignored", + help = "在预定义数量的请求之间引入延迟。注意:如果指定了qps,则burst将被忽略。", long = "burst-delay", requires = "burst_requests", conflicts_with = "query_per_second" )] burst_duration: Option, #[arg( - help = "Rates of requests for burst. Default is 1 -Note: If qps is specified, burst will be ignored", + help = "突发请求的速率。默认值为1注意:如果指定了qps,则burst将被忽略。", long = "burst-rate", requires = "burst_duration", conflicts_with = "query_per_second" @@ -168,18 +166,18 @@ Note: If qps is specified, burst will be ignored", default_value = "GET" )] method: http::Method, - #[arg(help = "Custom HTTP header. Examples: -H \"foo: bar\"", short = 'H', value_parser = parse_header)] + #[arg(help = "自定义HTTP头。示例:: -H \"foo: bar\"", short = 'H', value_parser = parse_header)] headers: Vec<(HeaderName, HeaderValue)>, #[arg( - help = "Custom Proxy HTTP header. Examples: --proxy-header \"foo: bar\"", + help = "自定义代理HTTP头。示例: --proxy-header \"foo: bar\"", long = "proxy-header", value_parser = parse_header )] proxy_headers: Vec<(HeaderName, HeaderValue)>, - #[arg(help = "Timeout for each request. Default to infinite.", short = 't')] + #[arg(help = "每个请求的超时时间。默认为无限。", short = 't')] timeout: Option, #[arg( - help = "Timeout for establishing a new connection. Default to 5s.", + help = "建立新连接的超时时间。默认为 5s。", long = "connect-timeout", default_value = "5s" )]