From b379e3a8f3a4c2871322266481487d7e5f619deb Mon Sep 17 00:00:00 2001 From: Jintao Date: Wed, 22 Jul 2026 21:07:26 +0800 Subject: [PATCH 1/2] Add bidirectional bandwidth diagnostics --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- CHANGELOG.md | 18 ++ README.md | 23 ++- README.zh-CN.md | 20 ++- bin/ipcheck | 244 +++++++++++++++++++++++--- package.json | 2 +- test/smoke.sh | 60 ++++++- 7 files changed, 335 insertions(+), 34 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b8d6a7d..21b3291 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -12,7 +12,7 @@ body: id: version attributes: label: ipcheck version - placeholder: ipcheck 0.4.0 + placeholder: ipcheck 0.5.0 validations: required: true - type: dropdown diff --git a/CHANGELOG.md b/CHANGELOG.md index ab51c6e..7fb9221 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,23 @@ All notable changes to ipcheck are documented here. The project follows [Semantic Versioning](https://semver.org/). +## [0.5.0] - 2026-07-22 + +### Added + +- A safe 1 MB zero-filled Cloudflare upload test alongside the existing 2 MB + reference download. +- Separate download/upload ratings, test-path disclosure, and plain-language + bandwidth advice in human and Markdown reports. +- Structured `bandwidth.download` and `bandwidth.upload` JSON objects while + preserving the existing download fields for compatibility. +- `--no-upload` to skip only the upload measurement. + +### Changed + +- The bandwidth section now clearly separates throughput from AI service TTFB. +- macOS `networkQuality --system` reports both download and upload throughput. + ## [0.4.0] - 2026-07-22 ### Added @@ -54,6 +71,7 @@ All notable changes to ipcheck are documented here. The project follows - Homebrew and direct-download packaging. - Median/P95 TTFB, jitter, reference bandwidth, and macOS `networkQuality`. +[0.5.0]: https://github.com/jacklv-coder/ipcheck/compare/v0.4.0...v0.5.0 [0.4.0]: https://github.com/jacklv-coder/ipcheck/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/jacklv-coder/ipcheck/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/jacklv-coder/ipcheck/releases/tag/v0.2.0 diff --git a/README.md b/README.md index 7ea48d8..1dfcab2 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ color-coded progress and finish with a direct answer to “Ready to code?”. ```text $ ipcheck --quick -ipcheck v0.4.0 — AI coding network diagnostics +ipcheck v0.5.0 — AI coding network diagnostics Developer verdict Ready to code? YES, WITH CAUTION @@ -32,6 +32,11 @@ Service results Claude Code GOOD The service path is reachable with acceptable latency and jitter. Result: GOOD + +Network bandwidth + Download 80.0 Mbps FAST Cloudflare, up to 2 MB + Upload 16.0 Mbps FAST Cloudflare, up to 1 MB zero-filled + Advice Bandwidth is sufficient for everyday AI-assisted development. ``` ## Why ipcheck @@ -67,7 +72,7 @@ brew install ipcheck ```bash mkdir -p "$HOME/.local/bin" -curl -fsSL https://raw.githubusercontent.com/jacklv-coder/ipcheck/v0.4.0/bin/ipcheck \ +curl -fsSL https://raw.githubusercontent.com/jacklv-coder/ipcheck/v0.5.0/bin/ipcheck \ -o "$HOME/.local/bin/ipcheck" chmod +x "$HOME/.local/bin/ipcheck" ``` @@ -108,6 +113,7 @@ ipcheck --system ipcheck --endpoint https://your-gateway.example.com/health ipcheck --lang zh ipcheck --no-progress --no-color +ipcheck --no-upload ``` The human and Markdown reports default to the terminal/system language. @@ -117,6 +123,19 @@ and diagnostic reasons remain stable English for automation. Progress is written to stderr only for human output and can be controlled with `IPCHECK_PROGRESS=auto|always|never`. +The service table measures time to first byte and jitter; it is not a transfer +speed test. The separate bandwidth section downloads up to 2 MB and uploads up to 1 MB of +zero-filled data through the reported proxy/network path. It rates throughput +for common development work without allowing bandwidth to hide a slow or +unstable AI service. Use `--no-upload` to skip only the upload or +`--no-bandwidth` to skip both directions. + +Bandwidth ratings use development-oriented thresholds: download is `FAST` at +25 Mbps or higher and `ADEQUATE` from 5 Mbps; upload is `FAST` at 10 Mbps or +higher and `ADEQUATE` from 2 Mbps. Lower measurements are `SLOW`. The optional +`--system` test delegates to macOS `networkQuality`, which can transfer +substantially more data than ipcheck's capped Cloudflare checks. + Run `ipcheck --help` for the complete option and environment-variable list. ## Supported clients and routes diff --git a/README.zh-CN.md b/README.zh-CN.md index e1918cd..e0a747b 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -16,7 +16,7 @@ ```text $ ipcheck --quick -ipcheck v0.4.0 — AI 编程网络诊断 +ipcheck v0.5.0 — AI 编程网络诊断 开发建议 现在适合开发吗?可以,但会有些慢 @@ -31,6 +31,11 @@ Service results Claude Code GOOD Result: GOOD + +网络带宽 + 下载 80.0 Mbps 快 Cloudflare,最多 2 MB + 上传 16.0 Mbps 快 Cloudflare,最多 1 MB 零字节 + 建议 当前带宽足以支持日常 AI 辅助开发。 ``` ## 核心能力 @@ -61,7 +66,7 @@ brew install ipcheck ```bash mkdir -p "$HOME/.local/bin" -curl -fsSL https://raw.githubusercontent.com/jacklv-coder/ipcheck/v0.4.0/bin/ipcheck \ +curl -fsSL https://raw.githubusercontent.com/jacklv-coder/ipcheck/v0.5.0/bin/ipcheck \ -o "$HOME/.local/bin/ipcheck" chmod +x "$HOME/.local/bin/ipcheck" ``` @@ -100,6 +105,7 @@ ipcheck --system ipcheck --endpoint https://your-gateway.example.com/health ipcheck --lang en ipcheck --no-progress --no-color +ipcheck --no-upload ``` 终端和 Markdown 报告默认跟随终端/系统语言,目前支持中文和英文。可以使用 @@ -107,6 +113,16 @@ ipcheck --no-progress --no-color 枚举值和诊断原因始终保持英文,方便自动化脚本稳定解析。实时进度只会在普通 终端报告中写入 stderr,可通过 `IPCHECK_PROGRESS=auto|always|never` 控制。 +“服务链路”测量的是首字节延迟和抖动,并不是下载速度。独立的“网络带宽”模块 +会通过报告中注明的代理/网络路径下载最多 2 MB,并上传最多 1 MB 全零测试数据,然后按 +日常开发需求分别评级。带宽不会掩盖 AI 服务本身的高延迟或不稳定。使用 +`--no-upload` 只跳过上传,使用 `--no-bandwidth` 同时跳过下载和上传。 + +评级采用面向开发场景的阈值:下载达到 25 Mbps 为“快”,达到 5 Mbps 为 +“够用”;上传达到 10 Mbps 为“快”,达到 2 Mbps 为“够用”;低于这些范围为 +“慢”。可选的 `--system` 会交给 macOS `networkQuality` 测试,消耗的数据量 +可能明显高于 ipcheck 自带的限量 Cloudflare 测试。 + 运行 `ipcheck --help` 可以查看完整参数。 ## 支持的客户端与配置 diff --git a/bin/ipcheck b/bin/ipcheck index 51e8695..b5a11c3 100755 --- a/bin/ipcheck +++ b/bin/ipcheck @@ -10,7 +10,7 @@ unset LC_ALL LC_NUMERIC=C export LC_NUMERIC -VERSION="0.4.0" +VERSION="0.5.0" SCHEMA_VERSION=1 SAMPLES=3 TIMEOUT=15 @@ -18,8 +18,11 @@ SERVICE_MODE="auto" CLI_ENDPOINTS="" CLI_ENDPOINTS_SET=0 BANDWIDTH_URL="https://speed.cloudflare.com/__down?bytes=2000000" +UPLOAD_URL="https://speed.cloudflare.com/__up" +UPLOAD_BYTES=1000000 MODE="human" RUN_BANDWIDTH=1 +RUN_UPLOAD=1 RUN_SYSTEM=0 COLOR_MODE="auto" PROGRESS_MODE="${IPCHECK_PROGRESS:-auto}" @@ -39,8 +42,9 @@ Options: --endpoint URL Test custom GET endpoint(s) instead of client defaults. --samples N Requests per endpoint (default: 3). --timeout SECONDS Per-request timeout (default: 15). - --quick Use 1 sample, an 8s timeout, and skip bandwidth. - --no-bandwidth Skip the 2 MB reference download test. + --quick Use 1 sample, an 8s timeout, and skip bandwidth tests. + --no-bandwidth Skip reference download and upload tests. + --no-upload Skip only the 1 MB reference upload test. --system Also run macOS networkQuality when available. --color Always use terminal colors in human output. --no-color Disable terminal colors. @@ -133,8 +137,10 @@ while [ "$#" -gt 0 ]; do SAMPLES=1 TIMEOUT=8 RUN_BANDWIDTH=0 + RUN_UPLOAD=0 ;; - --no-bandwidth) RUN_BANDWIDTH=0 ;; + --no-bandwidth) RUN_BANDWIDTH=0; RUN_UPLOAD=0 ;; + --no-upload) RUN_UPLOAD=0 ;; --system) RUN_SYSTEM=1 ;; --color) COLOR_MODE="always" ;; --no-color) COLOR_MODE="never" ;; @@ -738,6 +744,42 @@ format_bits_mbps() { awk -v value="$1" 'BEGIN { printf "%.1f Mbps", value / 1000000 }' } +bandwidth_rating() { + local direction speed + direction="$1" + speed="$2" + if [ "$direction" = "download" ]; then + awk -v speed="$speed" 'BEGIN { + mbps = speed * 8 / 1000000 + if (mbps >= 25) print "fast" + else if (mbps >= 5) print "adequate" + else print "slow" + }' + else + awk -v speed="$speed" 'BEGIN { + mbps = speed * 8 / 1000000 + if (mbps >= 10) print "fast" + else if (mbps >= 2) print "adequate" + else print "slow" + }' + fi +} + +localized_bandwidth_rating() { + case "$UI_LANG:$1" in + zh:fast) printf '快' ;; + zh:adequate) printf '够用' ;; + zh:slow) printf '慢' ;; + zh:partial) printf '估算' ;; + zh:unavailable) printf '不可用' ;; + *:fast) printf 'FAST' ;; + *:adequate) printf 'ADEQUATE' ;; + *:slow) printf 'SLOW' ;; + *:partial) printf 'ESTIMATE' ;; + *) printf 'UNAVAILABLE' ;; + esac +} + endpoint_count=0 TAB=$(printf '\t') if [ "$UI_LANG" = "zh" ]; then @@ -811,6 +853,9 @@ bandwidth_speed=0 bandwidth_code=000 bandwidth_valid=0 bandwidth_bytes=0 +bandwidth_complete=false +bandwidth_exit=0 +download_rating="unavailable" if [ "$RUN_BANDWIDTH" -eq 1 ]; then if [ "$UI_LANG" = "zh" ]; then progress_note "●" "正在测试参考下载带宽(2 MB)…" @@ -823,14 +868,15 @@ if [ "$RUN_BANDWIDTH" -eq 1 ]; then -w '%{http_code}\t%{size_download}\t%{speed_download}' \ --url "$BANDWIDTH_URL" 2>/dev/null) bandwidth_exit=$? - if [ "$bandwidth_exit" -eq 0 ]; then + if [ -n "$bandwidth_metrics" ]; then IFS="$TAB" read -r bandwidth_code bandwidth_bytes bandwidth_speed < 0 && speed > 0) }'; then + if awk -v bytes="$bandwidth_bytes" -v speed="$bandwidth_speed" 'BEGIN { exit !(bytes >= 65536 && speed > 0) }'; then bandwidth_valid=1 + [ "$bandwidth_exit" -ne 0 ] || bandwidth_complete=true fi ;; esac @@ -840,11 +886,14 @@ EOF bandwidth_speed=0 fi if [ "$bandwidth_valid" -eq 1 ]; then + download_rating=$(bandwidth_rating download "$bandwidth_speed") + [ "$bandwidth_complete" = true ] || download_rating="partial" if [ "$UI_LANG" = "zh" ]; then - progress_finish ok "Cloudflare" "参考带宽 $(format_mbps "$bandwidth_speed")" + download_progress="下载 $(format_mbps "$bandwidth_speed")($(localized_bandwidth_rating "$download_rating"))" else - progress_finish ok "Cloudflare" "reference bandwidth $(format_mbps "$bandwidth_speed")" + download_progress="download $(format_mbps "$bandwidth_speed") ($(localized_bandwidth_rating "$download_rating"))" fi + if [ "$download_rating" = "slow" ] || [ "$download_rating" = "partial" ]; then progress_finish warn "Cloudflare" "$download_progress"; else progress_finish ok "Cloudflare" "$download_progress"; fi elif [ "$UI_LANG" = "zh" ]; then progress_finish warn "Cloudflare" "参考带宽测试不可用" else @@ -852,16 +901,131 @@ EOF fi fi +upload_speed=0 +upload_code=000 +upload_valid=0 +upload_bytes=0 +upload_complete=false +upload_rating="unavailable" +if [ "$RUN_UPLOAD" -eq 1 ]; then + if [ "$UI_LANG" = "zh" ]; then + progress_note "●" "正在测试参考上传带宽(1 MB 零字节)…" + else + progress_note "●" "Testing reference upload bandwidth (1 MB of zero bytes)…" + fi + upload_file="$WORK_DIR/upload.bin" + if head -c "$UPLOAD_BYTES" /dev/zero > "$upload_file" 2>/dev/null; then + upload_metrics=$(curl_for_service "custom" --proto '=https' --http1.1 -sS -o /dev/null \ + -A "ipcheck/$VERSION" -X POST -H 'content-type: application/octet-stream' -H 'Expect:' \ + --data-binary "@$upload_file" \ + --connect-timeout "$TIMEOUT" --max-time "$TIMEOUT" \ + -w '%{http_code}\t%{size_upload}\t%{speed_upload}' \ + --url "$UPLOAD_URL" 2>/dev/null) + upload_exit=$? + if [ -n "$upload_metrics" ]; then + IFS="$TAB" read -r upload_code upload_bytes upload_speed <= 65536 && speed > 0) }'; then + upload_valid=1 + [ "$upload_exit" -ne 0 ] || upload_complete=true + fi + ;; + esac + fi + fi + if [ "$upload_valid" -eq 0 ]; then + upload_bytes=0 + upload_speed=0 + else + upload_rating=$(bandwidth_rating upload "$upload_speed") + [ "$upload_complete" = true ] || upload_rating="partial" + fi + if [ "$upload_valid" -eq 1 ]; then + if [ "$UI_LANG" = "zh" ]; then + upload_progress="上传 $(format_mbps "$upload_speed")($(localized_bandwidth_rating "$upload_rating"))" + else + upload_progress="upload $(format_mbps "$upload_speed") ($(localized_bandwidth_rating "$upload_rating"))" + fi + if [ "$upload_rating" = "slow" ] || [ "$upload_rating" = "partial" ]; then progress_finish warn "Cloudflare" "$upload_progress"; else progress_finish ok "Cloudflare" "$upload_progress"; fi + elif [ "$UI_LANG" = "zh" ]; then + progress_finish warn "Cloudflare" "参考上传测试不可用" + else + progress_finish warn "Cloudflare" "reference upload unavailable" + fi +fi + +download_mbps=$(awk -v value="$bandwidth_speed" 'BEGIN { printf "%.1f", value * 8 / 1000000 }') +upload_mbps=$(awk -v value="$upload_speed" 'BEGIN { printf "%.1f", value * 8 / 1000000 }') + +# Preserve the pre-v0.5 top-level bandwidth fields: a curl failure never +# exposed partial transfer data there. Partial estimates live only in the new +# bandwidth.download object. +legacy_bandwidth_available=false +legacy_bandwidth_code=000 +legacy_bandwidth_bytes=0 +legacy_bandwidth_speed=0 +if [ "$RUN_BANDWIDTH" -eq 1 ] && [ "$bandwidth_exit" -eq 0 ]; then + legacy_bandwidth_code="$bandwidth_code" + if [ "$bandwidth_valid" -eq 1 ] && [ "$bandwidth_complete" = true ]; then + legacy_bandwidth_available=true + legacy_bandwidth_bytes="$bandwidth_bytes" + legacy_bandwidth_speed="$bandwidth_speed" + fi +fi + +if [ "$UI_LANG" = "zh" ]; then + if [ "$bandwidth_valid" -eq 0 ] && [ "$upload_valid" -eq 0 ]; then + bandwidth_advice="未获得有效的带宽样本,不能据此判断下载或上传速度。" + elif { [ "$bandwidth_valid" -eq 1 ] && [ "$bandwidth_complete" = false ]; } || { [ "$upload_valid" -eq 1 ] && [ "$upload_complete" = false ]; }; then + bandwidth_advice="测速在达到时间上限前未完成;显示值仅为部分样本估算,不能据此认定带宽充足。" + elif [ "$RUN_BANDWIDTH" -eq 1 ] && [ "$bandwidth_valid" -eq 0 ]; then + bandwidth_advice="下载测试不可用;当前建议仅依据上传结果。" + elif [ "$RUN_UPLOAD" -eq 1 ] && [ "$upload_valid" -eq 0 ]; then + bandwidth_advice="上传测试不可用;当前建议仅依据下载结果。" + elif [ "$download_rating" = "slow" ] && [ "$upload_rating" = "slow" ]; then + bandwidth_advice="下载和上传都偏慢;安装依赖及发送大型代码上下文可能需要较长等待。" + elif [ "$download_rating" = "slow" ]; then + bandwidth_advice="下载偏慢;拉取仓库和安装依赖可能需要较长等待。" + elif [ "$upload_rating" = "slow" ]; then + bandwidth_advice="上传偏慢;发送大型代码上下文可能需要较长等待。" + else + bandwidth_advice="当前带宽足以支持日常 AI 辅助开发;交互体验仍主要取决于服务延迟。" + fi +else + if [ "$bandwidth_valid" -eq 0 ] && [ "$upload_valid" -eq 0 ]; then + bandwidth_advice="No valid bandwidth sample was collected, so download and upload speed cannot be rated." + elif { [ "$bandwidth_valid" -eq 1 ] && [ "$bandwidth_complete" = false ]; } || { [ "$upload_valid" -eq 1 ] && [ "$upload_complete" = false ]; }; then + bandwidth_advice="The test did not finish before its time limit; shown rates are partial estimates and do not prove bandwidth is sufficient." + elif [ "$RUN_BANDWIDTH" -eq 1 ] && [ "$bandwidth_valid" -eq 0 ]; then + bandwidth_advice="The download test was unavailable; this advice is based on upload only." + elif [ "$RUN_UPLOAD" -eq 1 ] && [ "$upload_valid" -eq 0 ]; then + bandwidth_advice="The upload test was unavailable; this advice is based on download only." + elif [ "$download_rating" = "slow" ] && [ "$upload_rating" = "slow" ]; then + bandwidth_advice="Download and upload are slow; dependencies and large code contexts may take noticeably longer." + elif [ "$download_rating" = "slow" ]; then + bandwidth_advice="Download is slow; repository fetches and dependency installs may take longer." + elif [ "$upload_rating" = "slow" ]; then + bandwidth_advice="Upload is slow; sending large code contexts may take longer." + else + bandwidth_advice="Bandwidth is sufficient for everyday AI-assisted development; service latency remains the main interactive factor." + fi +fi + system_interface="" system_rtt="" system_throughput="" +system_upload_throughput="" system_proxy_state="" if [ "$RUN_SYSTEM" -eq 1 ] && command -v networkQuality >/dev/null 2>&1; then system_file="$WORK_DIR/networkQuality.json" - networkQuality -c -u -M "$TIMEOUT" > "$system_file" 2>/dev/null || true + networkQuality -c -s -M "$TIMEOUT" > "$system_file" 2>/dev/null || true system_interface=$(sed -n 's/.*"interface_name" *: *"\([^"]*\)".*/\1/p' "$system_file" | head -n 1) system_rtt=$(sed -n 's/.*"base_rtt" *: *\([0-9.]*\).*/\1/p' "$system_file" | head -n 1) system_throughput=$(sed -n 's/.*"dl_throughput" *: *\([0-9.]*\).*/\1/p' "$system_file" | head -n 1) + system_upload_throughput=$(sed -n 's/.*"ul_throughput" *: *\([0-9.]*\).*/\1/p' "$system_file" | head -n 1) system_proxy_state=$(sed -n 's/.*"proxy_state" *: *"\([^"]*\)".*/\1/p' "$system_file" | head -n 1) fi @@ -1101,7 +1265,11 @@ render_human() { printf ' %s!%s %s\n' "$C_YELLOW" "$C_RESET" "$(localized_warning "$warning")" done < "$WARNINGS_FILE" fi - if [ "$UI_LANG" = "zh" ]; then printf '\n%s服务链路%s\n' "$C_BOLD" "$C_RESET"; else printf '\n%sService paths%s\n' "$C_BOLD" "$C_RESET"; fi + if [ "$UI_LANG" = "zh" ]; then + printf '\n%sAI 服务延迟%s\n 首字节延迟(TTFB):中位/P95 越低越好;抖动越低越稳定。\n' "$C_BOLD" "$C_RESET" + else + printf '\n%sAI service latency%s\n Time to first byte (TTFB): lower median/P95 is better; lower jitter is more stable.\n' "$C_BOLD" "$C_RESET" + fi while IFS="$TAB" read -r index service label endpoint probe_type priority; do sample_file="$WORK_DIR/endpoint_${index}.tsv" summary=$(awk -F '\t' -v idx="$index" '$1 == idx { print $6 "\t" $8 "\t" $9 "\t" $10; exit }' "$ENDPOINT_SUMMARY_FILE") @@ -1124,19 +1292,35 @@ EOF fi done < "$INDEXED_PROBES_FILE" - if [ "$UI_LANG" = "zh" ]; then printf '\n%s服务结论%s\n' "$C_BOLD" "$C_RESET"; else printf '\n%sService results%s\n' "$C_BOLD" "$C_RESET"; fi + if [ "$UI_LANG" = "zh" ]; then printf '\n%sAI 服务结论%s\n' "$C_BOLD" "$C_RESET"; else printf '\n%sAI service results%s\n' "$C_BOLD" "$C_RESET"; fi while IFS="$TAB" read -r service result reason success_rate ttfb jitter; do printf ' %-12s %-7s %s\n' "$(service_display "$service")" "$(printf '%s' "$result" | tr '[:lower:]' '[:upper:]')" "$(localized_reason "$reason")" done < "$SERVICE_RESULT_FILE" - if [ "$UI_LANG" = "zh" ]; then printf '\n%s参考带宽%s\n' "$C_BOLD" "$C_RESET"; else printf '\n%sReference bandwidth%s\n' "$C_BOLD" "$C_RESET"; fi + if [ "$UI_LANG" = "zh" ]; then printf '\n%s网络带宽%s\n' "$C_BOLD" "$C_RESET"; else printf '\n%sNetwork bandwidth%s\n' "$C_BOLD" "$C_RESET"; fi + case "$download_rating" in fast) download_color="$C_GREEN" ;; adequate|partial) download_color="$C_YELLOW" ;; *) download_color="$C_RED" ;; esac + case "$upload_rating" in fast) upload_color="$C_GREEN" ;; adequate|partial) upload_color="$C_YELLOW" ;; *) upload_color="$C_RED" ;; esac if [ "$RUN_BANDWIDTH" -eq 1 ] && [ "$bandwidth_valid" -eq 1 ]; then - if [ "$UI_LANG" = "zh" ]; then printf ' Cloudflare 下载:%s(HTTP %s;仅作参考)\n' "$(format_mbps "$bandwidth_speed")" "$bandwidth_code"; else printf ' Cloudflare download: %s (HTTP %s; reference only)\n' "$(format_mbps "$bandwidth_speed")" "$bandwidth_code"; fi + if [ "$UI_LANG" = "zh" ]; then printf ' 下载 %-12s %s%s%s Cloudflare,最多 2 MB\n' "$(format_mbps "$bandwidth_speed")" "$download_color" "$(localized_bandwidth_rating "$download_rating")" "$C_RESET"; else printf ' Download %-12s %s%s%s Cloudflare, up to 2 MB\n' "$(format_mbps "$bandwidth_speed")" "$download_color" "$(localized_bandwidth_rating "$download_rating")" "$C_RESET"; fi + elif [ "$RUN_BANDWIDTH" -eq 1 ]; then + if [ "$UI_LANG" = "zh" ]; then printf ' 下载 不可用\n'; else printf ' Download unavailable\n'; fi else - if [ "$UI_LANG" = "zh" ]; then printf ' 已跳过或不可用\n'; else printf ' skipped or unavailable\n'; fi + if [ "$UI_LANG" = "zh" ]; then printf ' 下载 已跳过\n'; else printf ' Download skipped\n'; fi + fi + if [ "$RUN_UPLOAD" -eq 1 ] && [ "$upload_valid" -eq 1 ]; then + if [ "$UI_LANG" = "zh" ]; then printf ' 上传 %-12s %s%s%s Cloudflare,最多 1 MB 零字节\n' "$(format_mbps "$upload_speed")" "$upload_color" "$(localized_bandwidth_rating "$upload_rating")" "$C_RESET"; else printf ' Upload %-12s %s%s%s Cloudflare, up to 1 MB zero-filled\n' "$(format_mbps "$upload_speed")" "$upload_color" "$(localized_bandwidth_rating "$upload_rating")" "$C_RESET"; fi + elif [ "$RUN_UPLOAD" -eq 1 ]; then + if [ "$UI_LANG" = "zh" ]; then printf ' 上传 不可用\n'; else printf ' Upload unavailable\n'; fi + else + if [ "$UI_LANG" = "zh" ]; then printf ' 上传 已跳过\n'; else printf ' Upload skipped\n'; fi + fi + if [ "$RUN_BANDWIDTH" -eq 1 ] || [ "$RUN_UPLOAD" -eq 1 ]; then + if [ "$UI_LANG" = "zh" ]; then printf ' 路径 %s\n 建议 %s\n' "$proxy_text" "$bandwidth_advice"; else printf ' Path %s\n Advice %s\n' "$proxy_text" "$bandwidth_advice"; fi fi if [ -n "$system_throughput" ]; then - printf ' macOS networkQuality: %s, RTT %.1f ms, interface %s' "$(format_bits_mbps "$system_throughput")" "$system_rtt" "$system_interface" + printf ' macOS networkQuality: down %s' "$(format_bits_mbps "$system_throughput")" + [ -z "$system_upload_throughput" ] || printf ', up %s' "$(format_bits_mbps "$system_upload_throughput")" + printf ', RTT %.1f ms, interface %s' "$system_rtt" "$system_interface" [ -n "$system_proxy_state" ] && printf ', proxy=%s' "$system_proxy_state" printf '\n' fi @@ -1181,10 +1365,10 @@ render_markdown() { done < "$WARNINGS_FILE" fi if [ "$UI_LANG" = "zh" ]; then - printf '\n## 服务端点\n\n' + printf '\n## AI 服务延迟(TTFB)\n\n' printf '| 客户端 | 端点 | 可达 | HTTP | 成功次数 | TTFB 中位数 | TTFB P95 | 抖动 |\n|---|---|---:|---:|---:|---:|---:|---:|\n' else - printf '\n## Endpoints\n\n' + printf '\n## AI service latency (TTFB)\n\n' printf '| Client | Endpoint | Reachable | HTTP | Successful samples | Median TTFB | P95 TTFB | Jitter |\n|---|---|---:|---:|---:|---:|---:|---:|\n' fi while IFS= read -r line; do @@ -1199,11 +1383,23 @@ render_markdown() { jitter=$(printf '%s' "$line" | sed -n 's/.*"jitter_ms":\([^,}]*\).*/\1/p') printf '| %s | %s | %s | %s | %s/%s | %.0f ms | %.0f ms | %.0f ms |\n' "$(service_display "$service")" "$label" "$reachable" "$http_code" "$successful_samples" "$samples" "$ttfb" "$p95" "$jitter" done < "$ENDPOINT_JSON_FILE" - if [ "$RUN_BANDWIDTH" -eq 1 ] && [ "$bandwidth_valid" -eq 1 ]; then - if [ "$UI_LANG" = "zh" ]; then printf '\n参考带宽:**%s**(Cloudflare;不是模型端点)。\n' "$(format_mbps "$bandwidth_speed")"; else printf '\nReference bandwidth: **%s** (Cloudflare; not a model endpoint).\n' "$(format_mbps "$bandwidth_speed")"; fi + if [ "$UI_LANG" = "zh" ]; then + printf '\n## 网络带宽\n\n' + printf '| 方向 | 速度 | 评级 | 测试数据 |\n|---|---:|---|---|\n' + if [ "$bandwidth_valid" -eq 1 ]; then printf '| 下载 | %s | %s | Cloudflare,最多 2 MB |\n' "$(format_mbps "$bandwidth_speed")" "$(localized_bandwidth_rating "$download_rating")"; else printf '| 下载 | — | 不可用或已跳过 | — |\n'; fi + if [ "$upload_valid" -eq 1 ]; then printf '| 上传 | %s | %s | Cloudflare,最多 1 MB 零字节 |\n' "$(format_mbps "$upload_speed")" "$(localized_bandwidth_rating "$upload_rating")"; else printf '| 上传 | — | 不可用或已跳过 | — |\n'; fi + printf '\n- 测速路径:`%s`\n- 建议:%s\n' "$proxy_text" "$bandwidth_advice" + else + printf '\n## Network bandwidth\n\n' + printf '| Direction | Speed | Rating | Test data |\n|---|---:|---|---|\n' + if [ "$bandwidth_valid" -eq 1 ]; then printf '| Download | %s | %s | Cloudflare, up to 2 MB |\n' "$(format_mbps "$bandwidth_speed")" "$(localized_bandwidth_rating "$download_rating")"; else printf '| Download | — | unavailable or skipped | — |\n'; fi + if [ "$upload_valid" -eq 1 ]; then printf '| Upload | %s | %s | Cloudflare, up to 1 MB zero-filled |\n' "$(format_mbps "$upload_speed")" "$(localized_bandwidth_rating "$upload_rating")"; else printf '| Upload | — | unavailable or skipped | — |\n'; fi + printf '\n- Test path: `%s`\n- Advice: %s\n' "$proxy_text" "$bandwidth_advice" fi if [ -n "$system_throughput" ]; then - printf "macOS networkQuality: **%s**, RTT %.1f ms, interface \`%s\`.\n" "$(format_bits_mbps "$system_throughput")" "$system_rtt" "$system_interface" + printf "macOS networkQuality: download **%s**" "$(format_bits_mbps "$system_throughput")" + [ -z "$system_upload_throughput" ] || printf ", upload **%s**" "$(format_bits_mbps "$system_upload_throughput")" + printf ", RTT %.1f ms, interface \`%s\`.\n" "$system_rtt" "$system_interface" fi if [ "$UI_LANG" = "zh" ]; then printf '\n> ipcheck 不会读取或传输 API 密钥,也不会发起计费模型请求。\n'; else printf '\n> ipcheck does not read or transmit API keys and does not make billable model requests.\n'; fi } @@ -1231,12 +1427,14 @@ render_json() { warnings_json="$warnings_json$separator\"$(json_escape "$warning")\"" separator="," done < "$WARNINGS_FILE" - printf '{"schema_version":%s,"version":"%s","timestamp":"%s","proxy":"%s","result":"%s","reason":"%s","developer_readiness":{"ready":%s,"level":"%s","summary":"%s","recommendation":"%s"},"reachable_endpoints":%s,"primary_success_rate_pct":%s,"primary_ttfb_median_ms":%s,"primary_jitter_ms":%s,"privacy":{"credentials_used":false,"billable_requests":false},"warnings":[%s],"bandwidth":{"enabled":%s,"available":%s,"http_code":"%s","bytes":%s,"bytes_per_second":%s},"system":{"enabled":%s,"interface":"%s","base_rtt_ms":%s,"download_bits_per_second":%s,"proxy_state":"%s"},"services":[%s],"endpoints":[%s]}\n' \ + printf '{"schema_version":%s,"version":"%s","timestamp":"%s","proxy":"%s","result":"%s","reason":"%s","developer_readiness":{"ready":%s,"level":"%s","summary":"%s","recommendation":"%s"},"reachable_endpoints":%s,"primary_success_rate_pct":%s,"primary_ttfb_median_ms":%s,"primary_jitter_ms":%s,"privacy":{"credentials_used":false,"billable_requests":false,"upload_payload":"zero-filled"},"warnings":[%s],"bandwidth":{"enabled":%s,"available":%s,"http_code":"%s","bytes":%s,"bytes_per_second":%s,"path":"%s","download":{"enabled":%s,"available":%s,"complete":%s,"http_code":"%s","bytes":%s,"bytes_per_second":%s,"mbps":%s,"rating":"%s"},"upload":{"enabled":%s,"available":%s,"complete":%s,"http_code":"%s","bytes":%s,"bytes_per_second":%s,"mbps":%s,"rating":"%s"}},"system":{"enabled":%s,"interface":"%s","base_rtt_ms":%s,"download_bits_per_second":%s,"upload_bits_per_second":%s,"proxy_state":"%s"},"services":[%s],"endpoints":[%s]}\n' \ "$SCHEMA_VERSION" "$VERSION" "$timestamp" "$(json_escape "$proxy_text")" "$overall" "$(json_escape "$overall_reason")" \ "$readiness_ready" "$readiness_level" "$(json_escape "$readiness_summary_en")" "$(json_escape "$readiness_recommendation_en")" \ "$reachable_count" "$primary_success_rate" "$primary_ttfb" "$primary_jitter" "$warnings_json" \ - "$([ "$RUN_BANDWIDTH" -eq 1 ] && printf true || printf false)" "$([ "$bandwidth_valid" -eq 1 ] && printf true || printf false)" "$bandwidth_code" "$bandwidth_bytes" "$bandwidth_speed" \ - "$([ -n "$system_throughput" ] && printf true || printf false)" "$(json_escape "$system_interface")" "${system_rtt:-0}" "${system_throughput:-0}" "$(json_escape "$system_proxy_state")" \ + "$([ "$RUN_BANDWIDTH" -eq 1 ] && printf true || printf false)" "$legacy_bandwidth_available" "$legacy_bandwidth_code" "$legacy_bandwidth_bytes" "$legacy_bandwidth_speed" "$(json_escape "$proxy_text")" \ + "$([ "$RUN_BANDWIDTH" -eq 1 ] && printf true || printf false)" "$([ "$bandwidth_valid" -eq 1 ] && printf true || printf false)" "$bandwidth_complete" "$bandwidth_code" "$bandwidth_bytes" "$bandwidth_speed" "$download_mbps" "$download_rating" \ + "$([ "$RUN_UPLOAD" -eq 1 ] && printf true || printf false)" "$([ "$upload_valid" -eq 1 ] && printf true || printf false)" "$upload_complete" "$upload_code" "$upload_bytes" "$upload_speed" "$upload_mbps" "$upload_rating" \ + "$([ -n "${system_throughput}${system_upload_throughput}" ] && printf true || printf false)" "$(json_escape "$system_interface")" "${system_rtt:-0}" "${system_throughput:-0}" "${system_upload_throughput:-0}" "$(json_escape "$system_proxy_state")" \ "$services_json" "$endpoints_json" } diff --git a/package.json b/package.json index 7f17426..ed81b72 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@jacklv-coder/ipcheck", "private": true, - "version": "0.4.0", + "version": "0.5.0", "description": "Diagnose proxy, latency, and API routes used by Codex and Claude Code.", "license": "Apache-2.0", "author": "Jintao", diff --git a/test/smoke.sh b/test/smoke.sh index 1bc4370..23dba4f 100755 --- a/test/smoke.sh +++ b/test/smoke.sh @@ -19,6 +19,7 @@ mkdir -p "$CODEX_FIXTURE" "$CLAUDE_FIXTURE" cat > "$STUB_DIR/curl" <<'EOF' #!/usr/bin/env bash is_bandwidth=0 +is_upload=0 is_blocked=0 is_flaky=0 is_proxy_auth=0 @@ -37,6 +38,7 @@ printf 'env:ALL_PROXY=%s\n' "${ALL_PROXY-}" >> "${IPCHECK_TEST_CURL_LOG:?}" for argument in "$@"; do printf '%s\n' "$argument" >> "${IPCHECK_TEST_CURL_LOG:?}" case "$argument" in + *__up*) is_upload=1 ;; *speed.cloudflare.com*) is_bandwidth=1 ;; *blocked.invalid*) is_blocked=1 ;; *flaky.invalid*) is_flaky=1 ;; @@ -60,9 +62,20 @@ elif [ "$is_proxy_auth" -eq 1 ]; then printf '407\t0.001\t0.002\t0.003\t0.050\t0.050\t100\t1000' elif [ "$is_blocked" -eq 1 ]; then exit 28 +elif [ "$is_upload" -eq 1 ]; then + upload_code=${IPCHECK_TEST_UPLOAD_CODE:-200} + upload_speed=${IPCHECK_TEST_UPLOAD_SPEED:-2000000} + upload_bytes=${IPCHECK_TEST_UPLOAD_BYTES:-1000000} + printf '%s\t%s\t%s' "$upload_code" "$upload_bytes" "$upload_speed" + upload_exit=${IPCHECK_TEST_UPLOAD_EXIT:-0} + [ "$upload_exit" -eq 0 ] || exit "$upload_exit" elif [ "$is_bandwidth" -eq 1 ]; then bandwidth_code=${IPCHECK_TEST_BANDWIDTH_CODE:-200} - printf '%s\t2000000\t10000000' "$bandwidth_code" + bandwidth_speed=${IPCHECK_TEST_BANDWIDTH_SPEED:-10000000} + bandwidth_bytes=${IPCHECK_TEST_BANDWIDTH_BYTES:-2000000} + printf '%s\t%s\t%s' "$bandwidth_code" "$bandwidth_bytes" "$bandwidth_speed" + bandwidth_exit=${IPCHECK_TEST_BANDWIDTH_EXIT:-0} + [ "$bandwidth_exit" -eq 0 ] || exit "$bandwidth_exit" elif [ "$is_not_found" -eq 1 ]; then printf '404\t0.001\t0.002\t0.003\t0.050\t0.050\t100\t1000' elif [ "$is_rate_limited" -eq 1 ]; then @@ -101,6 +114,7 @@ cat <<'JSON' { "base_rtt" : 25, "dl_throughput" : 10000000, + "ul_throughput" : 5000000, "interface_name" : "en0", "proxy_state" : "Non-Proxied" } @@ -158,7 +172,7 @@ run_ipcheck_direct() { bash -n "$PROJECT_DIR/bin/ipcheck" "$PROJECT_DIR/bin/ipcheck" --help | grep -q '^ipcheck - diagnose Codex and Claude Code' -[ "$("$PROJECT_DIR/bin/ipcheck" --version)" = "ipcheck 0.4.0" ] +[ "$("$PROJECT_DIR/bin/ipcheck" --version)" = "ipcheck 0.5.0" ] : > "$CURL_LOG" report=$(ANTHROPIC_AUTH_TOKEN="runtime-secret-must-never-appear" run_ipcheck --samples 3 --no-bandwidth --json) @@ -259,6 +273,8 @@ printf '%s\n' "$fair_human" | grep -q 'Ready to code? YES, WITH CAUTION' chinese_human=$(IPCHECK_LANG=zh run_ipcheck --samples 1 --no-bandwidth --no-progress --endpoint https://language.invalid) printf '%s\n' "$chinese_human" | grep -q '现在适合开发吗?适合' printf '%s\n' "$chinese_human" | grep -q '当前网络适合进行 AI 辅助开发' +printf '%s\n' "$chinese_human" | grep -q '^AI 服务延迟$' +printf '%s\n' "$chinese_human" | grep -q '首字节延迟(TTFB)' english_override=$(LANG=zh_CN.UTF-8 IPCHECK_LANG=en run_ipcheck --samples 1 --no-bandwidth --no-progress --endpoint https://language.invalid) printf '%s\n' "$english_override" | grep -q 'Ready to code? YES' @@ -423,18 +439,50 @@ grep -q '^env:ALL_PROXY=$' "$CURL_LOG" bandwidth_report=$(run_ipcheck --samples 1 --json) printf '%s\n' "$bandwidth_report" | grep -q '"bandwidth":{"enabled":true,"available":true,"http_code":"200"' +printf '%s\n' "$bandwidth_report" | grep -q '"download":{"enabled":true,"available":true,"complete":true,"http_code":"200","bytes":2000000,"bytes_per_second":10000000,"mbps":80.0,"rating":"fast"}' +printf '%s\n' "$bandwidth_report" | grep -q '"upload":{"enabled":true,"available":true,"complete":true,"http_code":"200","bytes":1000000,"bytes_per_second":2000000,"mbps":16.0,"rating":"fast"}' +printf '%s\n' "$bandwidth_report" | grep -q '"upload_payload":"zero-filled"' +bandwidth_human=$(IPCHECK_LANG=zh run_ipcheck --samples 1) +printf '%s\n' "$bandwidth_human" | grep -q '^网络带宽$' +printf '%s\n' "$bandwidth_human" | grep -q '下载 80.0 Mbps.*快.*Cloudflare,最多 2 MB' +printf '%s\n' "$bandwidth_human" | grep -q '上传 16.0 Mbps.*快.*Cloudflare,最多 1 MB 零字节' +printf '%s\n' "$bandwidth_human" | grep -q '路径 HTTPS_PROXY=http://127.0.0.1:1080' + +slow_upload_human=$(IPCHECK_TEST_UPLOAD_SPEED=100000 IPCHECK_LANG=en run_ipcheck --samples 1) +printf '%s\n' "$slow_upload_human" | grep -q 'Upload.*0.8 Mbps.*SLOW' +printf '%s\n' "$slow_upload_human" | grep -q 'Upload is slow; sending large code contexts may take longer.' invalid_bandwidth_report=$(IPCHECK_TEST_BANDWIDTH_CODE=407 run_ipcheck --samples 1 --json) -printf '%s\n' "$invalid_bandwidth_report" | grep -q '"bandwidth":{"enabled":true,"available":false,"http_code":"407","bytes":0,"bytes_per_second":0}' +printf '%s\n' "$invalid_bandwidth_report" | grep -q '"bandwidth":{"enabled":true,"available":false,"http_code":"407","bytes":0,"bytes_per_second":0,' +invalid_upload_report=$(IPCHECK_TEST_UPLOAD_CODE=503 run_ipcheck --samples 1 --json) +printf '%s\n' "$invalid_upload_report" | grep -q '"upload":{"enabled":true,"available":false,"complete":false,"http_code":"503","bytes":0,"bytes_per_second":0,"mbps":0.0,"rating":"unavailable"}' + +no_upload_report=$(run_ipcheck --samples 1 --no-upload --json) +printf '%s\n' "$no_upload_report" | grep -q '"upload":{"enabled":false,"available":false' + +partial_upload_report=$(IPCHECK_TEST_UPLOAD_BYTES=500000 IPCHECK_TEST_UPLOAD_EXIT=28 run_ipcheck --samples 1 --json) +printf '%s\n' "$partial_upload_report" | grep -q '"upload":{"enabled":true,"available":true,"complete":false,"http_code":"200","bytes":500000,"bytes_per_second":2000000,"mbps":16.0,"rating":"partial"}' + +partial_download_report=$(IPCHECK_TEST_BANDWIDTH_BYTES=500000 IPCHECK_TEST_BANDWIDTH_EXIT=28 run_ipcheck --samples 1 --json) +printf '%s\n' "$partial_download_report" | grep -q '"bandwidth":{"enabled":true,"available":false,"http_code":"000","bytes":0,"bytes_per_second":0,' +printf '%s\n' "$partial_download_report" | grep -q '"download":{"enabled":true,"available":true,"complete":false,"http_code":"200","bytes":500000,"bytes_per_second":10000000,"mbps":80.0,"rating":"partial"}' + +partial_human=$(IPCHECK_TEST_UPLOAD_BYTES=500000 IPCHECK_TEST_UPLOAD_EXIT=28 IPCHECK_LANG=en run_ipcheck --samples 1) +printf '%s\n' "$partial_human" | grep -q 'Upload.*16.0 Mbps.*ESTIMATE' +printf '%s\n' "$partial_human" | grep -q 'partial estimates and do not prove bandwidth is sufficient' +partial_markdown=$(IPCHECK_TEST_BANDWIDTH_BYTES=500000 IPCHECK_TEST_BANDWIDTH_EXIT=28 IPCHECK_LANG=en run_ipcheck --samples 1 --markdown) +printf '%s\n' "$partial_markdown" | grep -q '| Download | 80.0 Mbps | ESTIMATE |' +printf '%s\n' "$partial_markdown" | grep -q 'partial estimates and do not prove bandwidth is sufficient' grep -Fq -- "--max-time \"\$TIMEOUT\"" "$PROJECT_DIR/bin/ipcheck" -grep -Fq "networkQuality -c -u -M \"\$TIMEOUT\"" "$PROJECT_DIR/bin/ipcheck" +grep -Fq "networkQuality -c -s -M \"\$TIMEOUT\"" "$PROJECT_DIR/bin/ipcheck" grep -q 'download_bits_per_second' "$PROJECT_DIR/bin/ipcheck" grep -q 'value / 1000000' "$PROJECT_DIR/bin/ipcheck" system_report=$(run_ipcheck --samples 1 --no-bandwidth --system --json) printf '%s\n' "$system_report" | grep -q '"download_bits_per_second":10000000' +printf '%s\n' "$system_report" | grep -q '"upload_bits_per_second":5000000' system_human=$(run_ipcheck --samples 1 --no-bandwidth --system) -printf '%s\n' "$system_human" | grep -q 'macOS networkQuality: 10.0 Mbps' +printf '%s\n' "$system_human" | grep -q 'macOS networkQuality: down 10.0 Mbps, up 5.0 Mbps' set +e blocked_report=$(run_ipcheck --samples 1 --no-bandwidth --endpoint https://blocked.invalid --json) @@ -452,10 +500,12 @@ printf '%s\n' "$markdown" | grep -q '| Jitter |' chinese_markdown=$(IPCHECK_LANG=zh run_ipcheck --samples 1 --no-bandwidth --markdown) printf '%s\n' "$chinese_markdown" | grep -q '^# ipcheck:AI 编程网络报告' printf '%s\n' "$chinese_markdown" | grep -q '现在适合开发吗?' +printf '%s\n' "$chinese_markdown" | grep -q '^## AI 服务延迟(TTFB)$' quick_report=$(run_ipcheck --quick --json) printf '%s\n' "$quick_report" | grep -q '"samples":1' printf '%s\n' "$quick_report" | grep -q '"bandwidth":{"enabled":false' +printf '%s\n' "$quick_report" | grep -q '"upload":{"enabled":false' set +e run_ipcheck --service invalid >/dev/null 2>&1 From f048762ee893d0c47b1ed40cc15d6c73b6fc0324 Mon Sep 17 00:00:00 2001 From: Jintao Date: Wed, 22 Jul 2026 22:07:12 +0800 Subject: [PATCH 2/2] Fix Markdown output lint --- bin/ipcheck | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/ipcheck b/bin/ipcheck index b5a11c3..52135e1 100755 --- a/bin/ipcheck +++ b/bin/ipcheck @@ -1388,13 +1388,13 @@ render_markdown() { printf '| 方向 | 速度 | 评级 | 测试数据 |\n|---|---:|---|---|\n' if [ "$bandwidth_valid" -eq 1 ]; then printf '| 下载 | %s | %s | Cloudflare,最多 2 MB |\n' "$(format_mbps "$bandwidth_speed")" "$(localized_bandwidth_rating "$download_rating")"; else printf '| 下载 | — | 不可用或已跳过 | — |\n'; fi if [ "$upload_valid" -eq 1 ]; then printf '| 上传 | %s | %s | Cloudflare,最多 1 MB 零字节 |\n' "$(format_mbps "$upload_speed")" "$(localized_bandwidth_rating "$upload_rating")"; else printf '| 上传 | — | 不可用或已跳过 | — |\n'; fi - printf '\n- 测速路径:`%s`\n- 建议:%s\n' "$proxy_text" "$bandwidth_advice" + printf '\n- 测速路径:%s%s%s\n- 建议:%s\n' '`' "$proxy_text" '`' "$bandwidth_advice" else printf '\n## Network bandwidth\n\n' printf '| Direction | Speed | Rating | Test data |\n|---|---:|---|---|\n' if [ "$bandwidth_valid" -eq 1 ]; then printf '| Download | %s | %s | Cloudflare, up to 2 MB |\n' "$(format_mbps "$bandwidth_speed")" "$(localized_bandwidth_rating "$download_rating")"; else printf '| Download | — | unavailable or skipped | — |\n'; fi if [ "$upload_valid" -eq 1 ]; then printf '| Upload | %s | %s | Cloudflare, up to 1 MB zero-filled |\n' "$(format_mbps "$upload_speed")" "$(localized_bandwidth_rating "$upload_rating")"; else printf '| Upload | — | unavailable or skipped | — |\n'; fi - printf '\n- Test path: `%s`\n- Advice: %s\n' "$proxy_text" "$bandwidth_advice" + printf '\n- Test path: %s%s%s\n- Advice: %s\n' '`' "$proxy_text" '`' "$bandwidth_advice" fi if [ -n "$system_throughput" ]; then printf "macOS networkQuality: download **%s**" "$(format_bits_mbps "$system_throughput")"