Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
379 changes: 343 additions & 36 deletions .github/workflows/build.yml

Large diffs are not rendered by default.

34 changes: 27 additions & 7 deletions docs/scripts/install/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -77,25 +77,45 @@ fi
BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"
TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT
CHECKSUM_FILE="winload-checksums-${VERSION}.txt"

echo "📥 Downloading ${CHECKSUM_FILE}..."
curl -fSL -o "${TMP_DIR}/${CHECKSUM_FILE}" "${BASE_URL}/${CHECKSUM_FILE}"

download_and_verify() {
local asset="$1"
local target="${TMP_DIR}/${asset}"
local checksum

echo "📥 Downloading ${asset}..."
curl -fSL -o "$target" "${BASE_URL}/${asset}"
checksum=$(awk -v asset="$asset" '$2 == asset { print $1; exit }' "${TMP_DIR}/${CHECKSUM_FILE}")
if [ -z "$checksum" ]; then
echo "❌ No SHA-256 checksum found for ${asset}."
exit 1
fi
if ! printf '%s %s\n' "$checksum" "$target" | sha256sum --check --status; then
echo "❌ SHA-256 verification failed for ${asset}."
exit 1
fi
echo "✅ SHA-256 verified: ${asset}"
}

if [ "$PKG_MGR" = "termux" ]; then
ANDROID_ASSET="winload-android-${ARCH_NAME}-${VERSION}"
echo "📥 Downloading ${ANDROID_ASSET}..."
curl -fSL -o "${TMP_DIR}/winload" "${BASE_URL}/${ANDROID_ASSET}"
download_and_verify "$ANDROID_ASSET"
echo "📦 Installing to ${PREFIX}/bin/..."
install -Dm755 "${TMP_DIR}/winload" "${PREFIX}/bin/winload"
install -Dm755 "${TMP_DIR}/${ANDROID_ASSET}" "${PREFIX}/bin/winload"
elif [ "$PKG_MGR" = "apt" ]; then
PLATFORM="linux-${ARCH_NAME}"
PKG_FILE="winload-${PLATFORM}-${VERSION}.deb"
echo "📥 Downloading ${PKG_FILE}..."
curl -fSL -o "${TMP_DIR}/${PKG_FILE}" "${BASE_URL}/${PKG_FILE}"
download_and_verify "$PKG_FILE"
echo "📦 Installing via apt..."
sudo dpkg -i "${TMP_DIR}/${PKG_FILE}" || sudo apt-get install -f -y
elif [ "$PKG_MGR" = "dnf" ]; then
PLATFORM="linux-${ARCH_NAME}"
PKG_FILE="winload-${PLATFORM}-${VERSION}.rpm"
echo "📥 Downloading ${PKG_FILE}..."
curl -fSL -o "${TMP_DIR}/${PKG_FILE}" "${BASE_URL}/${PKG_FILE}"
download_and_verify "$PKG_FILE"
echo "📦 Installing via dnf..."
sudo dnf install -y "${TMP_DIR}/${PKG_FILE}"
fi
Expand Down
34 changes: 27 additions & 7 deletions docs/scripts/install/install_gitee.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,25 +84,45 @@ fi
BASE_URL="https://gitee.com/${OWNER}/${REPO}/releases/download/${VERSION}"
TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT
CHECKSUM_FILE="winload-checksums-${VERSION}.txt"

echo "📥 正在下载 ${CHECKSUM_FILE}..."
curl -fSL -o "${TMP_DIR}/${CHECKSUM_FILE}" "${BASE_URL}/${CHECKSUM_FILE}"

download_and_verify() {
local asset="$1"
local target="${TMP_DIR}/${asset}"
local checksum

echo "📥 正在下载 ${asset}..."
curl -fSL -o "$target" "${BASE_URL}/${asset}"
checksum=$(awk -v asset="$asset" '$2 == asset { print $1; exit }' "${TMP_DIR}/${CHECKSUM_FILE}")
if [ -z "$checksum" ]; then
echo "❌ 未找到 ${asset} 的 SHA-256 校验值。"
exit 1
fi
if ! printf '%s %s\n' "$checksum" "$target" | sha256sum --check --status; then
echo "❌ ${asset} 的 SHA-256 校验失败。"
exit 1
fi
echo "✅ SHA-256 校验通过: ${asset}"
}

if [ "$PKG_MGR" = "termux" ]; then
ANDROID_ASSET="winload-android-${ARCH_NAME}-${VERSION}"
echo "📥 正在从 Gitee 下载 ${ANDROID_ASSET}..."
curl -fSL -o "${TMP_DIR}/winload" "${BASE_URL}/${ANDROID_ASSET}"
download_and_verify "$ANDROID_ASSET"
echo "📦 安装到 ${PREFIX}/bin/ ..."
install -Dm755 "${TMP_DIR}/winload" "${PREFIX}/bin/winload"
install -Dm755 "${TMP_DIR}/${ANDROID_ASSET}" "${PREFIX}/bin/winload"
elif [ "$PKG_MGR" = "apt" ]; then
PLATFORM="linux-${ARCH_NAME}"
PKG_FILE="winload-${PLATFORM}-${VERSION}.deb"
echo "📥 正在从 Gitee 下载 ${PKG_FILE}..."
curl -fSL -o "${TMP_DIR}/${PKG_FILE}" "${BASE_URL}/${PKG_FILE}"
download_and_verify "$PKG_FILE"
echo "📦 通过 apt 安装中..."
sudo dpkg -i "${TMP_DIR}/${PKG_FILE}" || sudo apt-get install -f -y
elif [ "$PKG_MGR" = "dnf" ]; then
PLATFORM="linux-${ARCH_NAME}"
PKG_FILE="winload-${PLATFORM}-${VERSION}.rpm"
echo "📥 正在从 Gitee 下载 ${PKG_FILE}..."
curl -fSL -o "${TMP_DIR}/${PKG_FILE}" "${BASE_URL}/${PKG_FILE}"
download_and_verify "$PKG_FILE"
echo "📦 通过 dnf 安装中..."
sudo dnf install -y "${TMP_DIR}/${PKG_FILE}"
fi
Expand Down
16 changes: 15 additions & 1 deletion python/src/winload/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@
import sys
from typing import Optional, Sequence

from .config import BarStyle, MaxMode, RgbColor, RunConfig, TitleAlign, Unit
from .config import (
MAX_SAMPLE_CAPACITY,
BarStyle,
MaxMode,
RgbColor,
RunConfig,
TitleAlign,
Unit,
requested_sample_capacity,
)
from .diagnostics import format_build_info, get_help_system_info, get_system_info, get_version
from .emoji import decorate
from .i18n import set_lang, t
Expand Down Expand Up @@ -173,6 +182,11 @@ def parse_args(argv: Optional[Sequence[str]] = None) -> RunConfig:
parser.error("--interval must be greater than 0")
if args.average <= 0:
parser.error("--average must be greater than 0")
if requested_sample_capacity(args.interval, args.average) > MAX_SAMPLE_CAPACITY:
parser.error(
"--interval and --average retain more than "
f"{MAX_SAMPLE_CAPACITY} samples per interface"
)
if args.max_half_life <= 0:
parser.error("--max-half-life must be greater than 0")
has_half_life = any(
Expand Down
8 changes: 8 additions & 0 deletions python/src/winload/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ class MaxMode(StringEnum):

RgbColor = Tuple[int, int, int]

MIN_SAMPLE_CAPACITY = 600
MAX_SAMPLE_CAPACITY = 60_000


def requested_sample_capacity(interval_ms: int, average_window_sec: int) -> int:
"""Return the number of snapshots required by a requested average window."""
return max(1000 * average_window_sec // interval_ms, MIN_SAMPLE_CAPACITY)


@dataclass(frozen=True)
class RunConfig:
Expand Down
6 changes: 4 additions & 2 deletions python/src/winload/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import Optional

from .collector import Snapshot
from .config import MAX_SAMPLE_CAPACITY, requested_sample_capacity


@dataclass
Expand Down Expand Up @@ -37,8 +38,9 @@ def __init__(
self._avg_window_sec = average_window_sec

# 滑动窗口
max_samples = max(
int(1000 / refresh_interval_ms * average_window_sec), 600
max_samples = min(
requested_sample_capacity(refresh_interval_ms, average_window_sec),
MAX_SAMPLE_CAPACITY,
)
self._samples: deque[Snapshot] = deque(maxlen=max_samples)

Expand Down
6 changes: 6 additions & 0 deletions python/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ def test_invalid_zero_interval_exits(self):
parse_args(["--interval", "0"])
self.assertEqual(caught.exception.code, 2)

def test_rejects_an_oversized_sample_window(self):
with contextlib.redirect_stderr(io.StringIO()):
with self.assertRaises(SystemExit) as caught:
parse_args(["--interval", "1", "--average", "61"])
self.assertEqual(caught.exception.code, 2)

def test_fixed_requires_value(self):
with contextlib.redirect_stderr(io.StringIO()):
with self.assertRaises(SystemExit):
Expand Down
22 changes: 11 additions & 11 deletions readme.jp.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,16 +364,16 @@ python/
│ ├── __init__.py // 2 lines | 📦 Marks the Python package and exposes package-level metadata.
│ ├── __main__.py // 6 lines | ▶️ Runs the Python CLI entry point when invoked with python -m winload.
│ ├── app.py // 135 lines | Owns mutable application state, traffic updates, and device navigation.
│ ├── cli.py // 205 lines | Builds, localizes, parses, and validates the Python command-line interface.
│ ├── cli.py // 219 lines | Builds, localizes, parses, and validates the Python command-line interface.
│ ├── collector.py // 121 lines | 📡 Collects network interface counters with psutil or the optional Netlink backend.
│ ├── config.py // 65 lines | Defines immutable, strongly typed runtime configuration for the Python application.
│ ├── config.py // 73 lines | Defines immutable, strongly typed runtime configuration for the Python application.
│ ├── diagnostics.py // 162 lines | Reports version, build, system, and network-interface diagnostic information.
│ ├── emoji.py // 41 lines | ✨ Decorates CLI-facing labels with optional emoji icons.
│ ├── emoji.py // 48 lines | ✨ Decorates CLI-facing labels with optional emoji icons.
│ ├── graph.py // 108 lines | 📊 Renders incoming and outgoing traffic graphs for terminal display.
│ ├── main.py // 46 lines | Wires the Python CLI to diagnostics or the interactive terminal runtime.
│ ├── netlink.py // 120 lines | 🔗 Reads Linux and Android network counters directly through RTNETLINK.
│ ├── runtime.py // 62 lines | Manages the curses lifecycle, input mapping, refresh cadence, and UI rendering loop.
│ └── stats.py // 206 lines | 📈 Calculates rolling traffic rates, totals, and adaptive graph scale values.
│ └── stats.py // 208 lines | 📈 Calculates rolling traffic rates, totals, and adaptive graph scale values.
└── _build_info.py // 73 lines | 🧾 Resolves source or packaged Git metadata for Python version output.

rust/
Expand All @@ -387,18 +387,18 @@ rust/
│ │ ├── debug.rs // 105 lines | Draws the F3 runtime diagnostics overlay.
│ │ ├── mod.rs // 261 lines | Coordinates the ratatui layout, header, help bar, panels, and debug overlay.
│ │ └── panels.rs // 488 lines | Draws traffic histories in classic, line, scatter, and bar styles with optional axes.
│ ├── app.rs // 190 lines | Owns mutable application state, traffic collection, and device navigation.
│ ├── cli.rs // 375 lines | Parses localized command-line arguments and produces a validated RunConfig.
│ ├── app.rs // 192 lines | Owns mutable application state, traffic collection, and device navigation.
│ ├── cli.rs // 417 lines | Parses localized command-line arguments and produces a validated RunConfig.
│ ├── collector.rs // 238 lines | 📡 Collects network interface counters and prepares traffic snapshots for the TUI.
│ ├── config.rs // 183 lines | Defines validated, strongly typed runtime configuration shared by the Rust modules.
│ ├── config.rs // 194 lines | Defines validated, strongly typed runtime configuration shared by the Rust modules.
│ ├── diagnostics.rs // 45 lines | Prints build, platform, and network-interface diagnostics outside the TUI.
│ ├── emoji.rs // 40 lines | ✨ Decorates CLI-facing labels with optional emoji icons.
│ ├── emoji.rs // 50 lines | ✨ Decorates CLI-facing labels with optional emoji icons.
│ ├── graph.rs // 279 lines | 📊 Renders incoming and outgoing traffic graphs for terminal display.
│ ├── loopback.rs // 227 lines | 🪟 Captures and counts Windows loopback traffic through Npcap when enabled.
│ ├── loopback.rs // 265 lines | 🪟 Captures and counts Windows loopback traffic through Npcap when enabled.
│ ├── main.rs // 42 lines | Boots the Rust application and dispatches CLI actions to focused modules.
│ ├── netlink.rs // 175 lines | 🔗 Reads Linux and Android network counters directly through RTNETLINK.
│ ├── runtime.rs // 99 lines | Runs the terminal lifecycle, refresh loop, and semantic keyboard controls.
│ └── stats.rs // 231 lines | 📈 Calculates rolling traffic rates, totals, and adaptive graph scale values.
│ ├── runtime.rs // 100 lines | Runs the terminal lifecycle, refresh loop, and semantic keyboard controls.
│ └── stats.rs // 244 lines | 📈 Calculates rolling traffic rates, totals, and adaptive graph scale values.
└── _build_info.rs // 51 lines | 🧾 Injects Git metadata and configures platform-specific linker behavior.
```
<!-- winload-source-tree:end -->
Expand Down
22 changes: 11 additions & 11 deletions readme.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,16 +364,16 @@ python/
│ ├── __init__.py // 2 lines | 📦 Marks the Python package and exposes package-level metadata.
│ ├── __main__.py // 6 lines | ▶️ Runs the Python CLI entry point when invoked with python -m winload.
│ ├── app.py // 135 lines | Owns mutable application state, traffic updates, and device navigation.
│ ├── cli.py // 205 lines | Builds, localizes, parses, and validates the Python command-line interface.
│ ├── cli.py // 219 lines | Builds, localizes, parses, and validates the Python command-line interface.
│ ├── collector.py // 121 lines | 📡 Collects network interface counters with psutil or the optional Netlink backend.
│ ├── config.py // 65 lines | Defines immutable, strongly typed runtime configuration for the Python application.
│ ├── config.py // 73 lines | Defines immutable, strongly typed runtime configuration for the Python application.
│ ├── diagnostics.py // 162 lines | Reports version, build, system, and network-interface diagnostic information.
│ ├── emoji.py // 41 lines | ✨ Decorates CLI-facing labels with optional emoji icons.
│ ├── emoji.py // 48 lines | ✨ Decorates CLI-facing labels with optional emoji icons.
│ ├── graph.py // 108 lines | 📊 Renders incoming and outgoing traffic graphs for terminal display.
│ ├── main.py // 46 lines | Wires the Python CLI to diagnostics or the interactive terminal runtime.
│ ├── netlink.py // 120 lines | 🔗 Reads Linux and Android network counters directly through RTNETLINK.
│ ├── runtime.py // 62 lines | Manages the curses lifecycle, input mapping, refresh cadence, and UI rendering loop.
│ └── stats.py // 206 lines | 📈 Calculates rolling traffic rates, totals, and adaptive graph scale values.
│ └── stats.py // 208 lines | 📈 Calculates rolling traffic rates, totals, and adaptive graph scale values.
└── _build_info.py // 73 lines | 🧾 Resolves source or packaged Git metadata for Python version output.

rust/
Expand All @@ -387,18 +387,18 @@ rust/
│ │ ├── debug.rs // 105 lines | Draws the F3 runtime diagnostics overlay.
│ │ ├── mod.rs // 261 lines | Coordinates the ratatui layout, header, help bar, panels, and debug overlay.
│ │ └── panels.rs // 488 lines | Draws traffic histories in classic, line, scatter, and bar styles with optional axes.
│ ├── app.rs // 190 lines | Owns mutable application state, traffic collection, and device navigation.
│ ├── cli.rs // 375 lines | Parses localized command-line arguments and produces a validated RunConfig.
│ ├── app.rs // 192 lines | Owns mutable application state, traffic collection, and device navigation.
│ ├── cli.rs // 417 lines | Parses localized command-line arguments and produces a validated RunConfig.
│ ├── collector.rs // 238 lines | 📡 Collects network interface counters and prepares traffic snapshots for the TUI.
│ ├── config.rs // 183 lines | Defines validated, strongly typed runtime configuration shared by the Rust modules.
│ ├── config.rs // 194 lines | Defines validated, strongly typed runtime configuration shared by the Rust modules.
│ ├── diagnostics.rs // 45 lines | Prints build, platform, and network-interface diagnostics outside the TUI.
│ ├── emoji.rs // 40 lines | ✨ Decorates CLI-facing labels with optional emoji icons.
│ ├── emoji.rs // 50 lines | ✨ Decorates CLI-facing labels with optional emoji icons.
│ ├── graph.rs // 279 lines | 📊 Renders incoming and outgoing traffic graphs for terminal display.
│ ├── loopback.rs // 227 lines | 🪟 Captures and counts Windows loopback traffic through Npcap when enabled.
│ ├── loopback.rs // 265 lines | 🪟 Captures and counts Windows loopback traffic through Npcap when enabled.
│ ├── main.rs // 42 lines | Boots the Rust application and dispatches CLI actions to focused modules.
│ ├── netlink.rs // 175 lines | 🔗 Reads Linux and Android network counters directly through RTNETLINK.
│ ├── runtime.rs // 99 lines | Runs the terminal lifecycle, refresh loop, and semantic keyboard controls.
│ └── stats.rs // 231 lines | 📈 Calculates rolling traffic rates, totals, and adaptive graph scale values.
│ ├── runtime.rs // 100 lines | Runs the terminal lifecycle, refresh loop, and semantic keyboard controls.
│ └── stats.rs // 244 lines | 📈 Calculates rolling traffic rates, totals, and adaptive graph scale values.
└── _build_info.rs // 51 lines | 🧾 Injects Git metadata and configures platform-specific linker behavior.
```
<!-- winload-source-tree:end -->
Expand Down
Loading
Loading