From 877e4cf41da0020845e23536f3579b051ce97015 Mon Sep 17 00:00:00 2001 From: codex Date: Tue, 19 May 2026 02:30:49 +0000 Subject: [PATCH] =?UTF-8?q?docs(agent):=20=E7=BB=9F=E4=B8=80=E4=B8=AD?= =?UTF-8?q?=E6=96=87=E6=B3=A8=E9=87=8A=E5=B9=B6=E8=A1=A5=E5=85=85=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将代码与测试注释改为中文以符合仓库规范;新增中文部署指南、 示例配置,并完善构建脚本对 VERSION 注入的说明。 Co-authored-by: Cursor --- README.md | 4 +- cmd/agent/main.go | 16 +- configs/example.json | 21 ++ docs/DEPLOYMENT.md | 336 +++++++++++++++++++ internal/collector/collector.go | 8 +- internal/collector/collector_linux.go | 58 ++-- internal/collector/collector_linux_test.go | 16 +- internal/collector/collector_other.go | 10 +- internal/collector/collector_windows.go | 84 ++--- internal/collector/collector_windows_test.go | 10 +- internal/collector/network.go | 2 +- internal/collector/network_test.go | 2 +- internal/config/config.go | 24 +- internal/config/config_test.go | 6 +- internal/httpapi/server.go | 12 +- internal/httpapi/server_test.go | 12 +- internal/reporter/reporter.go | 16 +- internal/reporter/reporter_test.go | 12 +- internal/service/service.go | 16 +- internal/service/service_linux.go | 14 +- internal/service/service_linux_test.go | 4 +- internal/service/service_other.go | 12 +- internal/service/service_windows.go | 12 +- internal/snapshot/snapshot.go | 24 +- internal/snapshot/snapshot_test.go | 8 +- internal/status/types.go | 72 ++-- scripts/build-linux.sh | 13 + scripts/build-windows.ps1 | 10 + 28 files changed, 608 insertions(+), 226 deletions(-) create mode 100644 configs/example.json create mode 100644 docs/DEPLOYMENT.md diff --git a/README.md b/README.md index 41772a7..c20c20f 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ Single-binary host status agent implemented in Go. - Linux collectors read host, network, CPU, memory, disk, process, and TCP listener data from the OS. - Windows and Linux service lifecycle commands are exposed through the CLI. +**完整部署说明(中文):** [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) + ## Commands ```bash @@ -46,7 +48,7 @@ Build outputs are written to `dist/`, which is intentionally ignored by Git. ## Configuration -All settings have safe defaults. The HTTP API listens on `127.0.0.1:8080` by default. +Example config: [configs/example.json](configs/example.json). See [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) for defaults, environment variables, and production deployment steps. ```json { diff --git a/cmd/agent/main.go b/cmd/agent/main.go index 55f8b96..c24a0a6 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -20,9 +20,9 @@ import ( "mfd-monitor-agent/internal/snapshot" ) -const version = "0.1.0" +var version = "0.1.0" -// main runs the CLI and exits non-zero when a command fails. +// main 运行 CLI,命令失败时以非零状态退出。 func main() { if err := runCLI(os.Args[1:]); err != nil { _, _ = fmt.Fprintln(os.Stderr, err) @@ -30,7 +30,7 @@ func main() { } } -// runCLI dispatches agent subcommands and loads config before invoking command handlers. +// runCLI 分发 agent 子命令,并在调用处理函数前加载配置。 func runCLI(args []string) error { if len(args) == 0 { args = []string{"run"} @@ -38,7 +38,7 @@ func runCLI(args []string) error { switch args[0] { case "run": fs := flag.NewFlagSet("run", flag.ExitOnError) - configPath := fs.String("config", "", "path to JSON config file") + configPath := fs.String("config", "", "JSON 配置文件路径") if err := fs.Parse(args[1:]); err != nil { return err } @@ -79,11 +79,11 @@ func runCLI(args []string) error { } } -// commandConfig parses service command flags and resolves the executable path. +// commandConfig 解析服务相关子命令的标志并解析可执行文件路径。 func commandConfig(args []string) (config.Config, string, string, error) { fs := flag.NewFlagSet("service", flag.ExitOnError) - configPath := fs.String("config", "", "path to JSON config file") - executableFlag := fs.String("exe", "", "path to agent executable") + configPath := fs.String("config", "", "JSON 配置文件路径") + executableFlag := fs.String("exe", "", "agent 可执行文件路径(默认当前二进制)") if err := fs.Parse(args); err != nil { return config.Config{}, "", "", err } @@ -101,7 +101,7 @@ func commandConfig(args []string) (config.Config, string, string, error) { return cfg, executable, *configPath, nil } -// runAgent starts collection, optional reporting, and the local HTTP API until signaled. +// runAgent 启动采集、可选上报与本地 HTTP API,直至收到退出信号。 func runAgent(cfg config.Config) error { logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) diff --git a/configs/example.json b/configs/example.json new file mode 100644 index 0000000..a57af12 --- /dev/null +++ b/configs/example.json @@ -0,0 +1,21 @@ +{ + "host_id": "host-example", + "http": { + "listen_address": "127.0.0.1:8080" + }, + "collect": { + "interval": "30s", + "timeout": "10s" + }, + "report": { + "enabled": false, + "endpoint": "http://center.example.internal/agent/status", + "interval": "60s", + "timeout": "5s" + }, + "service": { + "name": "mfd-monitor-agent", + "display_name": "MFD Monitor Agent", + "description": "采集主机状态并通过 HTTP 提供查询" + } +} diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 0000000..8353ba7 --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,336 @@ +# MFD Monitor Agent 部署指南 + +本文档说明如何在 Linux 与 Windows 上编译、配置、安装并验收 MFD Monitor Agent。 + +## 概述 + +MFD Monitor Agent 是单二进制主机状态采集程序,具备以下能力: + +- 后台周期采集主机状态并写入内存缓存 +- 本地 HTTP API:`GET /health`、`GET /status`(默认仅监听 `127.0.0.1:8080`) +- 可选周期性向中心端 POST 与 `/status` 相同结构的 `HostStatus` JSON +- CLI 管理服务生命周期:`run`、`install`、`uninstall`、`start`、`stop`、`version` + +HTTP 接口与上报均读取同一份缓存,**不会在每次请求时实时扫描系统**。 + +## 环境要求 + +| 项目 | 要求 | +|------|------| +| Go | 1.22 及以上(见 `go.mod`) | +| Linux 服务化 | systemd | +| 安装权限 | Linux `install` 需 **root**;Windows `install` 需**管理员** | +| 运行账户 | 首版建议以高权限服务账户运行,以便采集完整进程列表与 TCP 监听→进程映射 | + +> 非 Linux/Windows 平台可前台 `run`,但不支持 `install`/`start`/`stop` 服务命令。 + +## 编译与产物 + +### Linux / 交叉编译(bash) + +```bash +cd /path/to/mfd-monitor-agent +./scripts/build-linux.sh +``` + +默认输出:`dist/agent-linux-amd64`。 + +常用环境变量: + +| 变量 | 含义 | 默认值 | +|------|------|--------| +| `OUT_DIR` | 输出目录 | `dist/` | +| `VERSION` | 写入二进制的版本号 | `0.1.0` | +| `GOOS_TARGET` | 目标操作系统 | `linux` | +| `GOARCH_TARGET` | 目标架构 | `amd64` | +| `OUTPUT_NAME` | 输出文件名 | `agent--` | +| `CGO_ENABLED` | 是否启用 CGO | `0` | + +交叉编译 Windows 示例: + +```bash +GOOS_TARGET=windows GOARCH_TARGET=amd64 OUTPUT_NAME=agent-windows-amd64.exe ./scripts/build-linux.sh +``` + +指定发布版本: + +```bash +VERSION=1.0.0 ./scripts/build-linux.sh +./dist/agent-linux-amd64 version +``` + +### Windows(PowerShell) + +```powershell +cd C:\path\to\mfd-monitor-agent +.\scripts\build-windows.ps1 +``` + +默认输出:`dist\agent-windows-amd64.exe`。 + +构建产物目录 `dist/` 已在 `.gitignore` 中忽略,不会提交到仓库。 + +## 推荐目录布局 + +### Linux + +```text +/opt/mfd-monitor-agent/agent # 可执行文件 +/etc/mfd-monitor-agent/config.json # 配置文件 +``` + +### Windows + +```text +C:\Program Files\MFD\agent\agent.exe +C:\ProgramData\MFD\config.json +``` + +仓库提供示例配置:[configs/example.json](../configs/example.json)。 + +## 配置说明 + +### JSON 配置文件 + +所有字段均有安全默认值;未提供配置文件时也可仅依赖环境变量启动。 + +```json +{ + "host_id": "host-a", + "http": { + "listen_address": "127.0.0.1:8080" + }, + "collect": { + "interval": "30s", + "timeout": "10s" + }, + "report": { + "enabled": true, + "endpoint": "http://center.example.internal/agent/status", + "interval": "60s", + "timeout": "5s" + }, + "service": { + "name": "mfd-monitor-agent", + "display_name": "MFD Monitor Agent", + "description": "采集主机状态并通过 HTTP 提供查询" + } +} +``` + +| 字段 | 说明 | 默认值 | +|------|------|--------| +| `host_id` | 机器标识;为空时采集器可回退为主机名 | `""` | +| `http.listen_address` | 本地 HTTP 监听地址 | `127.0.0.1:8080` | +| `collect.interval` | 采集周期 | `30s` | +| `collect.timeout` | 单次采集总超时 | `10s` | +| `report.enabled` | 是否启用主动上报 | `false` | +| `report.endpoint` | 中心端 POST URL | `""` | +| `report.interval` | 上报周期 | `60s` | +| `report.timeout` | 单次上报 HTTP 超时 | `5s` | +| `service.name` | 服务名(systemd unit / Windows 服务名) | `mfd-monitor-agent` | +| `service.display_name` | Windows 显示名称 | `MFD Monitor Agent` | +| `service.description` | systemd unit 描述 | 见默认配置 | + +**校验规则:** `report.enabled=true` 时必须设置 `report.endpoint`,否则 `config.Load` 失败。 + +时长字段使用 Go duration 字符串,例如 `"30s"`、`"1m"`。 + +### 环境变量覆盖 + +在 JSON 加载后,以下环境变量可覆盖对应配置(`Load("")` 时同样生效): + +| 环境变量 | 配置字段 | +|----------|----------| +| `MFD_AGENT_HOST_ID` | `host_id` | +| `MFD_AGENT_LISTEN` | `http.listen_address` | +| `MFD_AGENT_COLLECT_INTERVAL` | `collect.interval` | +| `MFD_AGENT_COLLECT_TIMEOUT` | `collect.timeout` | +| `MFD_AGENT_REPORT_ENABLED` | `report.enabled` | +| `MFD_AGENT_REPORT_ENDPOINT` | `report.endpoint` | +| `MFD_AGENT_REPORT_INTERVAL` | `report.interval` | +| `MFD_AGENT_REPORT_TIMEOUT` | `report.timeout` | + +## CLI 命令 + +```bash +agent run [-config /path/to/config.json] +agent install [-config /path/to/config.json] [-exe /path/to/agent] +agent uninstall [-config /path/to/config.json] +agent start [-config /path/to/config.json] +agent stop [-config /path/to/config.json] +agent version +``` + +无参数时默认执行 `run`。 + +## Linux systemd 部署 + +### 1. 安装文件 + +```bash +sudo install -m 0755 dist/agent-linux-amd64 /opt/mfd-monitor-agent/agent +sudo install -m 0644 configs/example.json /etc/mfd-monitor-agent/config.json +# 按需编辑 host_id、report 等字段 +sudo ${EDITOR:-vi} /etc/mfd-monitor-agent/config.json +``` + +### 2. 注册并启用服务 + +```bash +sudo /opt/mfd-monitor-agent/agent install \ + -config /etc/mfd-monitor-agent/config.json \ + -exe /opt/mfd-monitor-agent/agent +``` + +`install` 会: + +1. 写入 `/etc/systemd/system/.service` +2. 执行 `systemctl daemon-reload` +3. 执行 `systemctl enable ` + +生成的 unit 中 `ExecStart` 形如: + +```ini +ExecStart=/opt/mfd-monitor-agent/agent run -config /etc/mfd-monitor-agent/config.json +``` + +### 3. 启动与查看状态 + +```bash +sudo /opt/mfd-monitor-agent/agent start -config /etc/mfd-monitor-agent/config.json +# 或 +sudo systemctl start mfd-monitor-agent + +sudo systemctl status mfd-monitor-agent +sudo journalctl -u mfd-monitor-agent -f +``` + +### 4. 停止与卸载 + +```bash +sudo /opt/mfd-monitor-agent/agent stop -config /etc/mfd-monitor-agent/config.json +sudo /opt/mfd-monitor-agent/agent uninstall -config /etc/mfd-monitor-agent/config.json +``` + +`uninstall` 会 `disable --now` 服务、删除 unit 文件并 `daemon-reload`。 + +## Windows 服务部署 + +> 当前通过 `sc.exe` 注册 Windows 服务;`run` 为前台模式,**非** Go SCM 内嵌服务包装。服务启动后由 SCM 按 `binPath` 拉起 `agent run`。 + +### 1. 复制文件 + +将 `agent-windows-amd64.exe` 与 `config.json` 放到固定路径,例如: + +- `C:\Program Files\MFD\agent\agent.exe` +- `C:\ProgramData\MFD\config.json` + +### 2. 注册服务(管理员 PowerShell) + +```powershell +cd "C:\Program Files\MFD\agent" +.\agent.exe install -config "C:\ProgramData\MFD\config.json" -exe "C:\Program Files\MFD\agent\agent.exe" +``` + +### 3. 启动与查询 + +```powershell +.\agent.exe start -config "C:\ProgramData\MFD\config.json" +sc.exe query mfd-monitor-agent +``` + +也可在「服务」管理器中启停。 + +### 4. 卸载 + +```powershell +.\agent.exe uninstall -config "C:\ProgramData\MFD\config.json" +``` + +内部会先 `sc stop` 再 `sc delete`。 + +## 前台运行与验收 + +开发或临时排查时可前台运行: + +```bash +./agent run -config /etc/mfd-monitor-agent/config.json +``` + +### 健康检查 + +```bash +curl -s http://127.0.0.1:8080/health | jq . +``` + +首次采集完成前可能返回 `503` 且 `{"status":"starting"}`。就绪后示例: + +```json +{ + "status": "ok", + "version": "0.1.0", + "snapshot_at": "2026-05-19T12:00:00Z", + "snapshot_status": "ok" +} +``` + +`snapshot_status` 为 `partial` 表示部分子采集器失败,但接口仍返回 200。 + +### 完整状态 + +```bash +curl -s http://127.0.0.1:8080/status | jq '.agent, .host, .processes.status, .tcp_listeners.status' +``` + +### 主动上报验收 + +1. 在配置中设置 `report.enabled=true` 与可达的 `report.endpoint` +2. 在中心端确认收到 POST,Body 为完整 `HostStatus` JSON +3. 上报失败仅记录日志并重试一次,**不会**导致 agent 进程退出或影响本地 HTTP + +## 升级与回滚 + +### Linux + +```bash +sudo systemctl stop mfd-monitor-agent +sudo install -m 0755 dist/agent-linux-amd64 /opt/mfd-monitor-agent/agent +sudo systemctl start mfd-monitor-agent +``` + +若 unit 中可执行路径未变,无需重新 `install`。 + +### Windows + +```powershell +sc.exe stop mfd-monitor-agent +# 替换 agent.exe +sc.exe start mfd-monitor-agent +``` + +## 安全注意事项 + +- **默认仅本机监听**(`127.0.0.1:8080`),避免将进程列表、监听端口等敏感信息默认暴露到网络。 +- 若需远程拉取,应通过反向代理、mTLS 或防火墙限制来源,而非直接绑定 `0.0.0.0`(除非明确接受风险)。 +- 服务账户权限越高,采集越完整;生产环境应在「采集完整性」与「最小权限」之间权衡。 +- `/status` 响应体可能较大(进程多、监听多时),注意中心端与本地内存占用。 + +## 故障排查 + +| 现象 | 可能原因 | 处理建议 | +|------|----------|----------| +| 启动报错 `report endpoint is required` | 开启上报但未配置 endpoint | 设置 `report.endpoint` 或关闭 `report.enabled` | +| `install requires root` | Linux 非 root 执行 install | 使用 `sudo` | +| `service install is not implemented` | 非 Linux/Windows | 仅使用 `run` 前台模式 | +| `/health` 长期 `starting` | 首次采集未完成或采集卡住 | 检查 `collect.timeout`、系统负载与权限 | +| `snapshot_status: partial` | 部分采集器失败 | 查看 `/status` 中 `errors` 数组 | +| `processes.status: partial/error` | 权限不足或 /proc 部分不可读 | 以更高权限运行服务 | +| `tcp_listeners.status: partial` | 端口→进程映射不完整 | Linux 检查 `/proc//fd` 可读性;Windows 检查权限 | +| 上报失败日志 | 网络、中心端 5xx、超时 | 检查 `report.endpoint` 与 `report.timeout`;失败会自动重试一次 | + +## 相关文档 + +- 项目简介:[README.md](../README.md) +- 架构与规范:[AGENTS.md](../AGENTS.md) +- 示例配置:[configs/example.json](../configs/example.json) diff --git a/internal/collector/collector.go b/internal/collector/collector.go index 3f58fd4..d700e5b 100644 --- a/internal/collector/collector.go +++ b/internal/collector/collector.go @@ -7,23 +7,23 @@ import ( "mfd-monitor-agent/internal/status" ) -// namedCollector binds a stable collector name to a collection function. +// namedCollector 将稳定的采集器名称与采集函数绑定。 type namedCollector struct { name string fn func(context.Context, *status.HostStatus) error } -// Name returns the collector name used in snapshot error records. +// Name 返回用于快照错误记录的采集器名称。 func (c namedCollector) Name() string { return c.name } -// Collect runs the wrapped collection function. +// Collect 执行绑定的采集函数。 func (c namedCollector) Collect(ctx context.Context, out *status.HostStatus) error { return c.fn(ctx, out) } -// New returns the default collector set for the current platform. +// New 返回当前平台的默认采集器集合。 func New(hostID string) []snapshot.Collector { return []snapshot.Collector{ namedCollector{name: "system", fn: func(ctx context.Context, out *status.HostStatus) error { diff --git a/internal/collector/collector_linux.go b/internal/collector/collector_linux.go index de3b8f1..a84fbdd 100644 --- a/internal/collector/collector_linux.go +++ b/internal/collector/collector_linux.go @@ -26,7 +26,7 @@ import ( var socketInodesFunc = socketInodes var readTCPListenersFunc = readTCPListeners -// collectSystem fills Linux host identity, OS, architecture, and uptime fields. +// collectSystem 从 Linux 填充主机标识、操作系统、架构与运行时长字段。 func collectSystem(_ context.Context, hostID string, out *status.HostStatus) error { hostname, err := os.Hostname() if err != nil { @@ -44,7 +44,7 @@ func collectSystem(_ context.Context, hostID string, out *status.HostStatus) err return nil } -// collectNetwork fills Linux network interface, address, and MAC data. +// collectNetwork 填充 Linux 网卡、地址与 MAC 数据。 func collectNetwork(_ context.Context, out *status.HostStatus) error { ifaces, err := net.Interfaces() if err != nil { @@ -75,7 +75,7 @@ func collectNetwork(_ context.Context, out *status.HostStatus) error { return nil } -// collectResource fills CPU, memory, and disk resource data from Linux proc files. +// collectResource 从 Linux /proc 文件填充 CPU、内存与磁盘资源数据。 func collectResource(ctx context.Context, out *status.HostStatus) error { out.CPU.LogicalCores = runtime.NumCPU() out.CPU.PhysicalCores, out.CPU.ModelName = readCPUInfo() @@ -90,7 +90,7 @@ func collectResource(ctx context.Context, out *status.HostStatus) error { return nil } -// collectProcesses reads /proc process directories and degrades to partial when some entries fail. +// collectProcesses 读取 /proc 进程目录;部分条目失败时降级为 partial。 func collectProcesses(_ context.Context, out *status.HostStatus) error { entries, err := os.ReadDir("/proc") if err != nil { @@ -124,7 +124,7 @@ func collectProcesses(_ context.Context, out *status.HostStatus) error { return nil } -// collectSockets reads Linux TCP listener tables and maps socket inodes to owning processes. +// collectSockets 读取 Linux TCP 监听表,并将 socket inode 映射到所属进程。 func collectSockets(_ context.Context, out *status.HostStatus) error { inodes, inodeErr := socketInodesFunc() var listeners []status.TCPListenerInfo @@ -161,7 +161,7 @@ func collectSockets(_ context.Context, out *status.HostStatus) error { return nil } -// readOSRelease returns the Linux PRETTY_NAME value when /etc/os-release is readable. +// readOSRelease 在可读时从 /etc/os-release 返回 PRETTY_NAME。 func readOSRelease() string { file, err := os.Open("/etc/os-release") if err != nil { @@ -178,7 +178,7 @@ func readOSRelease() string { return "" } -// readUptime returns host uptime in seconds from /proc/uptime. +// readUptime 从 /proc/uptime 返回主机运行时长(秒)。 func readUptime() uint64 { data, err := os.ReadFile("/proc/uptime") if err != nil { @@ -192,7 +192,7 @@ func readUptime() uint64 { return uint64(value) } -// readCPUInfo returns physical CPU package count and the first model name from /proc/cpuinfo. +// readCPUInfo 从 /proc/cpuinfo 返回物理 CPU 封装数与首个型号名称。 func readCPUInfo() (int, string) { file, err := os.Open("/proc/cpuinfo") if err != nil { @@ -222,7 +222,7 @@ func readCPUInfo() (int, string) { return len(physical), model } -// readLoadAvg returns the 1, 5, and 15 minute Linux load averages when available. +// readLoadAvg 在可用时返回 Linux 1/5/15 分钟负载均值。 func readLoadAvg() []float64 { data, err := os.ReadFile("/proc/loadavg") if err != nil { @@ -239,7 +239,7 @@ func readLoadAvg() []float64 { return values } -// readMemory returns Linux memory counters in bytes from /proc/meminfo. +// readMemory 从 /proc/meminfo 返回以字节为单位的 Linux 内存计数。 func readMemory() status.MemoryInfo { values := map[string]uint64{} file, err := os.Open("/proc/meminfo") @@ -254,8 +254,8 @@ func readMemory() status.MemoryInfo { continue } value, _ := strconv.ParseUint(fields[1], 10, 64) - // /proc/meminfo reports memory in KiB; the API exposes bytes and - // includes memory.unit so clients can render the values explicitly. + // /proc/meminfo 以 KiB 报告内存;API 对外暴露字节, + // 并通过 memory.unit 让客户端可明确展示单位。 values[strings.TrimSuffix(fields[0], ":")] = value * 1024 } total := values["MemTotal"] @@ -269,7 +269,7 @@ func readMemory() status.MemoryInfo { return status.MemoryInfo{Unit: "byte", Total: total, Used: used, Free: free, Available: available, UsedPercent: percent} } -// readDisks returns physical and network mounted filesystem usage from /proc/mounts and statfs. +// readDisks 从 /proc/mounts 与 statfs 返回物理盘与网络挂载文件系统用量。 func readDisks() ([]status.DiskInfo, error) { file, err := os.Open("/proc/mounts") if err != nil { @@ -305,7 +305,7 @@ func readDisks() ([]status.DiskInfo, error) { return disks, scanner.Err() } -// shouldIncludeDiskMount reports whether a mount represents a user-relevant disk. +// shouldIncludeDiskMount 判断挂载点是否代表用户关心的磁盘。 func shouldIncludeDiskMount(device string, fsType string, total uint64) bool { if total == 0 { return false @@ -319,7 +319,7 @@ func shouldIncludeDiskMount(device string, fsType string, total uint64) bool { return strings.HasPrefix(device, "/dev/") } -// readProcess returns one Linux process entry from /proc/. +// readProcess 从 /proc/ 返回一条 Linux 进程记录。 func readProcess(pid int) (status.ProcessInfo, error) { base := filepath.Join("/proc", strconv.Itoa(pid)) statusValues, err := readProcessStatus(filepath.Join(base, "status")) @@ -348,12 +348,12 @@ func readProcess(pid int) (status.ProcessInfo, error) { }, nil } -// processStat contains process counters parsed from /proc//stat. +// processStat 包含从 /proc//stat 解析的进程计数。 type processStat struct { StartTime int64 } -// readProcessStat parses Linux process stat counters from path. +// readProcessStat 从路径解析 Linux 进程 stat 计数。 func readProcessStat(path string) (processStat, error) { data, err := os.ReadFile(path) if err != nil { @@ -366,7 +366,7 @@ func readProcessStat(path string) (processStat, error) { return processStat{StartTime: startTime}, nil } -// readProcessStatus parses key-value fields from /proc//status. +// readProcessStatus 从 /proc//status 解析键值字段。 func readProcessStatus(path string) (map[string]string, error) { file, err := os.Open(path) if err != nil { @@ -384,7 +384,7 @@ func readProcessStatus(path string) (map[string]string, error) { return values, scanner.Err() } -// socketInodes maps Linux socket inode numbers to best-effort owning process data. +// socketInodes 将 Linux socket inode 映射到尽力获取的所属进程数据。 func socketInodes() (map[string]status.ProcessInfo, error) { out := map[string]status.ProcessInfo{} var readErrors int @@ -419,7 +419,7 @@ func socketInodes() (map[string]status.ProcessInfo, error) { return out, nil } -// readTCPListeners parses a Linux TCP table and returns sockets in LISTEN state. +// readTCPListeners 解析 Linux TCP 表并返回处于 LISTEN 状态的套接字。 func readTCPListeners(path string, family string, inodes map[string]status.ProcessInfo) ([]status.TCPListenerInfo, error) { file, err := os.Open(path) if err != nil { @@ -429,7 +429,7 @@ func readTCPListeners(path string, family string, inodes map[string]status.Proce var items []status.TCPListenerInfo scanner := bufio.NewScanner(file) if scanner.Scan() { - // Skip header. + // 跳过表头行。 } for scanner.Scan() { fields := strings.Fields(scanner.Text()) @@ -449,7 +449,7 @@ func readTCPListeners(path string, family string, inodes map[string]status.Proce return items, scanner.Err() } -// readCPUUsagePercent samples /proc/stat over a short interval and returns best-effort usage. +// readCPUUsagePercent 在短间隔内采样 /proc/stat 并返回尽力估算的 CPU 使用率。 func readCPUUsagePercent(ctx context.Context) float64 { prevData, err := os.ReadFile("/proc/stat") if err != nil { @@ -475,7 +475,7 @@ func readCPUUsagePercent(ctx context.Context) float64 { return cpuUsagePercent(prevIdle, prevTotal, nextIdle, nextTotal) } -// parseAddress decodes a Linux /proc/net/tcp address into IP and port values. +// parseAddress 将 Linux /proc/net/tcp 地址解码为 IP 与端口。 func parseAddress(raw string, family string) (string, int, error) { parts := strings.Split(raw, ":") if len(parts) != 2 { @@ -502,7 +502,7 @@ func parseAddress(raw string, family string) (string, int, error) { return "", 0, fmt.Errorf("invalid %s address", family) } -// parseProcessStatStartTime extracts field 22 from Linux /proc//stat content. +// parseProcessStatStartTime 从 Linux /proc//stat 内容提取第 22 字段。 func parseProcessStatStartTime(raw string) (int64, error) { end := strings.LastIndex(raw, ")") if end < 0 || end+2 >= len(raw) { @@ -515,7 +515,7 @@ func parseProcessStatStartTime(raw string) (int64, error) { return strconv.ParseInt(fields[19], 10, 64) } -// parseCPUTimes extracts idle and total jiffy counters from the first cpu line in /proc/stat. +// parseCPUTimes 从 /proc/stat 首行 cpu 提取空闲与总 jiffies 计数。 func parseCPUTimes(raw string) (idle uint64, total uint64, err error) { scanner := bufio.NewScanner(strings.NewReader(raw)) for scanner.Scan() { @@ -544,7 +544,7 @@ func parseCPUTimes(raw string) (idle uint64, total uint64, err error) { return 0, 0, fmt.Errorf("cpu line not found") } -// cpuUsagePercent calculates non-idle CPU percentage between two /proc/stat samples. +// cpuUsagePercent 根据两次 /proc/stat 采样计算非空闲 CPU 百分比。 func cpuUsagePercent(prevIdle, prevTotal, nextIdle, nextTotal uint64) float64 { totalDelta := nextTotal - prevTotal if totalDelta == 0 { @@ -557,13 +557,13 @@ func cpuUsagePercent(prevIdle, prevTotal, nextIdle, nextTotal uint64) float64 { return float64(totalDelta-idleDelta) / float64(totalDelta) * 100 } -// parseKB converts the first numeric kilobyte value in raw to bytes. +// parseKB 将 raw 中首个以 KiB 表示的数值转换为字节。 func parseKB(raw string) uint64 { value, _ := strconv.ParseUint(firstField(raw), 10, 64) return value * 1024 } -// firstField returns the first whitespace-separated token in raw. +// firstField 返回 raw 中首个以空白分隔的字段。 func firstField(raw string) string { fields := strings.Fields(raw) if len(fields) == 0 { @@ -572,7 +572,7 @@ func firstField(raw string) string { return fields[0] } -// sortedKeys returns deterministic sorted keys from a string set. +// sortedKeys 从字符串集合返回确定顺序的排序键列表。 func sortedKeys(values map[string]struct{}) []string { keys := make([]string, 0, len(values)) for key := range values { diff --git a/internal/collector/collector_linux_test.go b/internal/collector/collector_linux_test.go index 9dc853e..5ebbfd2 100644 --- a/internal/collector/collector_linux_test.go +++ b/internal/collector/collector_linux_test.go @@ -14,7 +14,7 @@ import ( "mfd-monitor-agent/internal/status" ) -// TestParseAddressIPv4 verifies Linux IPv4 socket addresses are decoded. +// TestParseAddressIPv4 验证 Linux IPv4 套接字地址解码。 func TestParseAddressIPv4(t *testing.T) { ip, port, err := parseAddress("0100007F:1F90", "ipv4") if err != nil { @@ -25,7 +25,7 @@ func TestParseAddressIPv4(t *testing.T) { } } -// TestParseAddressIPv6 verifies Linux IPv6 socket addresses are decoded. +// TestParseAddressIPv6 验证 Linux IPv6 套接字地址解码。 func TestParseAddressIPv6(t *testing.T) { ip, port, err := parseAddress("00000000000000000000000001000000:1F90", "ipv6") if err != nil { @@ -36,7 +36,7 @@ func TestParseAddressIPv6(t *testing.T) { } } -// TestParseProcessStatStartTime verifies field 22 parsing from /proc//stat. +// TestParseProcessStatStartTime 验证从 /proc//stat 解析第 22 字段。 func TestParseProcessStatStartTime(t *testing.T) { raw := "1234 (agent worker) S 1 2 3 4 5 0 0 0 0 0 11 22 0 0 20 0 1 0 987654 0 0" @@ -49,7 +49,7 @@ func TestParseProcessStatStartTime(t *testing.T) { } } -// TestCPUUsageFromProcStatSamples verifies CPU usage from two /proc/stat samples. +// TestCPUUsageFromProcStatSamples 验证由两次 /proc/stat 采样计算 CPU 使用率。 func TestCPUUsageFromProcStatSamples(t *testing.T) { prevIdle, prevTotal, err := parseCPUTimes("cpu 100 0 100 800 0 0 0 0 0 0\n") if err != nil { @@ -66,7 +66,7 @@ func TestCPUUsageFromProcStatSamples(t *testing.T) { } } -// TestReadProcessStatReturnsStartTime verifies process start time is exposed. +// TestReadProcessStatReturnsStartTime 验证进程启动时间被暴露。 func TestReadProcessStatReturnsStartTime(t *testing.T) { path := filepath.Join(t.TempDir(), "stat") raw := "1234 (agent worker) S 1 2 3 4 5 0 0 0 0 0 11 22 0 0 20 0 1 0 987654 0 0" @@ -83,7 +83,7 @@ func TestReadProcessStatReturnsStartTime(t *testing.T) { } } -// TestReadMemoryReturnsBytesWithUnit verifies memory capacity fields are exposed in bytes. +// TestReadMemoryReturnsBytesWithUnit 验证内存容量字段以字节暴露。 func TestReadMemoryReturnsBytesWithUnit(t *testing.T) { meminfo, err := os.ReadFile("/proc/meminfo") if err != nil { @@ -114,7 +114,7 @@ func TestReadMemoryReturnsBytesWithUnit(t *testing.T) { } } -// TestShouldIncludeDiskMount keeps physical and network disks while dropping pseudo mounts. +// TestShouldIncludeDiskMount 保留物理盘与网络盘并排除伪挂载。 func TestShouldIncludeDiskMount(t *testing.T) { tests := []struct { name string @@ -140,7 +140,7 @@ func TestShouldIncludeDiskMount(t *testing.T) { } } -// TestCollectSocketsMarksPartialWhenProcessFDsUnreadable verifies socket mapping degradation. +// TestCollectSocketsMarksPartialWhenProcessFDsUnreadable 验证进程 fd 不可读时套接字映射降级。 func TestCollectSocketsMarksPartialWhenProcessFDsUnreadable(t *testing.T) { oldSocketInodes := socketInodesFunc oldReadTCPListeners := readTCPListenersFunc diff --git a/internal/collector/collector_other.go b/internal/collector/collector_other.go index a9aa5eb..f6dac44 100644 --- a/internal/collector/collector_other.go +++ b/internal/collector/collector_other.go @@ -13,7 +13,7 @@ import ( "mfd-monitor-agent/internal/status" ) -// collectSystem fills portable host identity fields for non-Linux platforms. +// collectSystem 为非 Linux/Windows 平台填充可移植的主机标识字段。 func collectSystem(_ context.Context, hostID string, out *status.HostStatus) error { hostname, err := os.Hostname() if err != nil { @@ -29,7 +29,7 @@ func collectSystem(_ context.Context, hostID string, out *status.HostStatus) err return nil } -// collectNetwork fills interface, address, and MAC data using the portable net package. +// collectNetwork 使用可移植的 net 包填充网卡、地址与 MAC 数据。 func collectNetwork(_ context.Context, out *status.HostStatus) error { ifaces, err := net.Interfaces() if err != nil { @@ -56,19 +56,19 @@ func collectNetwork(_ context.Context, out *status.HostStatus) error { return nil } -// collectResource fills portable resource fields available without platform-specific APIs. +// collectResource 填充无需平台专用 API 的可移植资源字段。 func collectResource(_ context.Context, out *status.HostStatus) error { out.CPU.LogicalCores = runtime.NumCPU() return nil } -// collectProcesses reports partial process collection until platform-specific support is added. +// collectProcesses 在尚未实现平台专用支持时,将进程采集标记为 partial。 func collectProcesses(_ context.Context, out *status.HostStatus) error { out.Processes.Status = "partial" return fmt.Errorf("process collection is not implemented for %s without platform-specific support", runtime.GOOS) } -// collectSockets reports partial TCP listener collection until platform-specific support is added. +// collectSockets 在尚未实现平台专用支持时,将 TCP 监听采集标记为 partial。 func collectSockets(_ context.Context, out *status.HostStatus) error { out.TCPListeners.Status = "partial" return fmt.Errorf("tcp listener process mapping is not implemented for %s without platform-specific support", runtime.GOOS) diff --git a/internal/collector/collector_windows.go b/internal/collector/collector_windows.go index 8f6e093..cd937da 100644 --- a/internal/collector/collector_windows.go +++ b/internal/collector/collector_windows.go @@ -62,7 +62,7 @@ var ( windowsLookupAccountSidWProc = windowsAdvapi32.NewProc("LookupAccountSidW") ) -// windowsMemoryStatus mirrors the Windows MEMORYSTATUSEX structure used by GlobalMemoryStatusEx. +// windowsMemoryStatus 镜像 GlobalMemoryStatusEx 使用的 Windows MEMORYSTATUSEX 结构。 type windowsMemoryStatus struct { Length uint32 MemoryLoad uint32 @@ -75,7 +75,7 @@ type windowsMemoryStatus struct { AvailExtendedVirtual uint64 } -// windowsMIBTCPRowOwnerPID mirrors a MIB_TCPROW_OWNER_PID row returned by GetExtendedTcpTable. +// windowsMIBTCPRowOwnerPID 镜像 GetExtendedTcpTable 返回的 MIB_TCPROW_OWNER_PID 行。 type windowsMIBTCPRowOwnerPID struct { State uint32 LocalAddr uint32 @@ -85,7 +85,7 @@ type windowsMIBTCPRowOwnerPID struct { OwningPID uint32 } -// windowsMIBTCP6RowOwnerPID mirrors a MIB_TCP6ROW_OWNER_PID row returned by GetExtendedTcpTable. +// windowsMIBTCP6RowOwnerPID 镜像 GetExtendedTcpTable 返回的 MIB_TCP6ROW_OWNER_PID 行。 type windowsMIBTCP6RowOwnerPID struct { State uint32 LocalAddr [16]byte @@ -97,7 +97,7 @@ type windowsMIBTCP6RowOwnerPID struct { OwningPID uint32 } -// windowsSystemInfo mirrors the subset of SYSTEM_INFO needed for CPU package sizing. +// windowsSystemInfo 镜像 CPU 封装规模计算所需的 SYSTEM_INFO 子集。 type windowsSystemInfo struct { ProcessorArchitecture uint16 Reserved uint16 @@ -112,21 +112,21 @@ type windowsSystemInfo struct { ProcessorRevision uint16 } -// windowsSystemTimes stores system CPU time counters as 100ns units. +// windowsSystemTimes 以 100 纳秒为单位存储系统 CPU 时间计数。 type windowsSystemTimes struct { Idle uint64 Kernel uint64 User uint64 } -// windowsLogicalProcessorInformation mirrors SYSTEM_LOGICAL_PROCESSOR_INFORMATION for core counting. +// windowsLogicalProcessorInformation 镜像用于统计核心的 SYSTEM_LOGICAL_PROCESSOR_INFORMATION。 type windowsLogicalProcessorInformation struct { ProcessorMask uintptr Relationship uint32 Data [16]byte } -// windowsProcessMemoryCounters mirrors PROCESS_MEMORY_COUNTERS. +// windowsProcessMemoryCounters 镜像 PROCESS_MEMORY_COUNTERS。 type windowsProcessMemoryCounters struct { CB uint32 PageFaultCount uint32 @@ -140,7 +140,7 @@ type windowsProcessMemoryCounters struct { PeakPagefileUsage uintptr } -// windowsProcessDetails contains best-effort per-process fields requiring extra handles or commands. +// windowsProcessDetails 包含需额外句柄或命令才能获取的尽力进程字段。 type windowsProcessDetails struct { Exe string Cmdline string @@ -152,7 +152,7 @@ type windowsProcessDetails struct { MemoryVMS uint64 } -// collectSystem fills Windows host identity, OS, and architecture fields. +// collectSystem 填充 Windows 主机标识、操作系统与架构字段。 func collectSystem(_ context.Context, hostID string, out *status.HostStatus) error { hostname, err := os.Hostname() if err != nil { @@ -170,7 +170,7 @@ func collectSystem(_ context.Context, hostID string, out *status.HostStatus) err return nil } -// collectNetwork fills Windows interface, address, and MAC data using the portable net package. +// collectNetwork 使用可移植 net 包填充 Windows 网卡、地址与 MAC 数据。 func collectNetwork(_ context.Context, out *status.HostStatus) error { ifaces, err := net.Interfaces() if err != nil { @@ -195,7 +195,7 @@ func collectNetwork(_ context.Context, out *status.HostStatus) error { return nil } -// collectResource fills Windows CPU, memory, and disk resource data. +// collectResource 填充 Windows CPU、内存与磁盘资源数据。 func collectResource(ctx context.Context, out *status.HostStatus) error { var errs []error out.CPU.LogicalCores = runtime.NumCPU() @@ -217,7 +217,7 @@ func collectResource(ctx context.Context, out *status.HostStatus) error { return errors.Join(errs...) } -// collectProcesses enumerates Windows processes with Toolhelp and returns best-effort names. +// collectProcesses 通过 Toolhelp 枚举 Windows 进程并返回尽力获取的名称等信息。 func collectProcesses(_ context.Context, out *status.HostStatus) error { processes, err := windowsProcessList() out.Processes.Items = processes @@ -229,7 +229,7 @@ func collectProcesses(_ context.Context, out *status.HostStatus) error { return nil } -// collectSockets reads Windows TCP listener tables and maps listener PIDs to process names. +// collectSockets 读取 Windows TCP 监听表并将监听 PID 映射到进程名。 func collectSockets(_ context.Context, out *status.HostStatus) error { processes, processErr := windowsProcessMap() var errs []error @@ -259,7 +259,7 @@ func collectSockets(_ context.Context, out *status.HostStatus) error { return nil } -// windowsReadMemory calls GlobalMemoryStatusEx and converts physical memory counters to API fields. +// windowsReadMemory 调用 GlobalMemoryStatusEx 并将物理内存计数转换为 API 字段。 func windowsReadMemory() (status.MemoryInfo, error) { memory := windowsMemoryStatus{Length: uint32(unsafe.Sizeof(windowsMemoryStatus{}))} ok, _, err := windowsGlobalMemoryStatusExProc.Call(uintptr(unsafe.Pointer(&memory))) @@ -269,7 +269,7 @@ func windowsReadMemory() (status.MemoryInfo, error) { return windowsMemoryInfoFromStatus(memory), nil } -// windowsVersion returns the legacy Windows major.minor.build tuple exposed by GetVersion. +// windowsVersion 返回 GetVersion 暴露的传统 major.minor.build 版本元组。 func windowsVersion() string { version, err := syscall.GetVersion() if err != nil { @@ -281,13 +281,13 @@ func windowsVersion() string { return fmt.Sprintf("%d.%d.%d", major, minor, build) } -// windowsUptimeSeconds returns host uptime in seconds from GetTickCount64. +// windowsUptimeSeconds 从 GetTickCount64 返回主机运行时长(秒)。 func windowsUptimeSeconds() uint64 { millis, _, _ := windowsGetTickCount64Proc.Call() return uint64(millis) / 1000 } -// windowsPhysicalCores returns physical CPU core count from Windows processor topology. +// windowsPhysicalCores 从 Windows 处理器拓扑返回物理 CPU 核心数。 func windowsPhysicalCores() int { var size uint32 result, _, _ := windowsGetLogicalProcessorInfoProc.Call(0, uintptr(unsafe.Pointer(&size))) @@ -319,7 +319,7 @@ func windowsPhysicalCores() int { return runtime.NumCPU() } -// windowsCPUModelName reads the first processor name from the registry. +// windowsCPUModelName 从注册表读取首个处理器名称。 func windowsCPUModelName() string { keyPath, _ := syscall.UTF16PtrFromString(`HARDWARE\DESCRIPTION\System\CentralProcessor\0`) var key syscall.Handle @@ -344,7 +344,7 @@ func windowsCPUModelName() string { return strings.TrimSpace(syscall.UTF16ToString(utf16s)) } -// windowsReadCPUUsagePercent samples GetSystemTimes and returns best-effort CPU usage. +// windowsReadCPUUsagePercent 采样 GetSystemTimes 并返回尽力估算的 CPU 使用率。 func windowsReadCPUUsagePercent(ctx context.Context) float64 { prev, err := windowsReadSystemTimes() if err != nil { @@ -362,7 +362,7 @@ func windowsReadCPUUsagePercent(ctx context.Context) float64 { return windowsCPUUsagePercent(prev, next) } -// windowsReadSystemTimes returns idle, kernel, and user system counters from GetSystemTimes. +// windowsReadSystemTimes 从 GetSystemTimes 返回空闲、内核与用户系统计数。 func windowsReadSystemTimes() (windowsSystemTimes, error) { var idle, kernel, user syscall.Filetime ok, _, err := windowsGetSystemTimesProc.Call( @@ -376,7 +376,7 @@ func windowsReadSystemTimes() (windowsSystemTimes, error) { return windowsSystemTimes{Idle: windowsFiletimeUint64(idle), Kernel: windowsFiletimeUint64(kernel), User: windowsFiletimeUint64(user)}, nil } -// windowsCPUUsagePercent calculates non-idle CPU percentage between two Windows CPU samples. +// windowsCPUUsagePercent 根据两次 Windows CPU 采样计算非空闲 CPU 百分比。 func windowsCPUUsagePercent(prev windowsSystemTimes, next windowsSystemTimes) float64 { prevTotal := prev.Kernel + prev.User nextTotal := next.Kernel + next.User @@ -391,12 +391,12 @@ func windowsCPUUsagePercent(prev windowsSystemTimes, next windowsSystemTimes) fl return float64(totalDelta-idleDelta) / float64(totalDelta) * 100 } -// windowsFiletimeUint64 converts a FILETIME to a monotonically comparable counter. +// windowsFiletimeUint64 将 FILETIME 转换为可单调比较的计数。 func windowsFiletimeUint64(value syscall.Filetime) uint64 { return uint64(value.HighDateTime)<<32 | uint64(value.LowDateTime) } -// windowsReadDisks enumerates fixed and remote Windows drives and returns capacity data. +// windowsReadDisks 枚举 Windows 本地固定盘与远程盘并返回容量数据。 func windowsReadDisks() ([]status.DiskInfo, error) { mask, _, _ := windowsGetLogicalDrivesProc.Call() if mask == 0 { @@ -430,7 +430,7 @@ func windowsReadDisks() ([]status.DiskInfo, error) { return disks, errors.Join(errs...) } -// windowsDiskInfoFromSpace converts Windows disk capacity counters into API disk fields. +// windowsDiskInfoFromSpace 将 Windows 磁盘容量计数转换为 API 磁盘字段。 func windowsDiskInfoFromSpace(root string, driveType uint32, total uint64, free uint64) status.DiskInfo { used := uint64(0) if total >= free { @@ -446,7 +446,7 @@ func windowsDiskInfoFromSpace(root string, driveType uint32, total uint64, free } } -// windowsDriveTypeName maps Windows drive type constants to stable API labels. +// windowsDriveTypeName 将 Windows 驱动器类型常量映射为稳定的 API 标签。 func windowsDriveTypeName(driveType uint32) string { switch driveType { case windowsDriveFixed: @@ -458,7 +458,7 @@ func windowsDriveTypeName(driveType uint32) string { } } -// windowsMemoryInfoFromStatus converts Windows physical memory counters into HostStatus memory data. +// windowsMemoryInfoFromStatus 将 Windows 物理内存计数转换为 HostStatus 内存数据。 func windowsMemoryInfoFromStatus(memory windowsMemoryStatus) status.MemoryInfo { used := uint64(0) if memory.TotalPhys >= memory.AvailPhys { @@ -474,7 +474,7 @@ func windowsMemoryInfoFromStatus(memory windowsMemoryStatus) status.MemoryInfo { } } -// windowsTCPListeners returns IPv4 or IPv6 TCP listeners from GetExtendedTcpTable. +// windowsTCPListeners 从 GetExtendedTcpTable 返回 IPv4 或 IPv6 TCP 监听项。 func windowsTCPListeners(family int, familyName string, processes map[int]status.ProcessInfo) ([]status.TCPListenerInfo, error) { table, err := windowsExtendedTCPTable(family) if err != nil { @@ -490,7 +490,7 @@ func windowsTCPListeners(family int, familyName string, processes map[int]status return windowsParseTCP6Rows(table[4:], count, processes), nil } -// windowsExtendedTCPTable calls GetExtendedTcpTable and returns the raw table bytes. +// windowsExtendedTCPTable 调用 GetExtendedTcpTable 并返回原始表字节。 func windowsExtendedTCPTable(family int) ([]byte, error) { var size uint32 result, _, _ := windowsGetExtendedTCPTableProc.Call( @@ -522,7 +522,7 @@ func windowsExtendedTCPTable(family int) ([]byte, error) { return table, nil } -// windowsParseTCP4Rows converts raw MIB_TCPROW_OWNER_PID rows into listener records. +// windowsParseTCP4Rows 将原始 MIB_TCPROW_OWNER_PID 行转换为监听记录。 func windowsParseTCP4Rows(rows []byte, count uint32, processes map[int]status.ProcessInfo) []status.TCPListenerInfo { rowSize := int(unsafe.Sizeof(windowsMIBTCPRowOwnerPID{})) items := make([]status.TCPListenerInfo, 0, count) @@ -546,7 +546,7 @@ func windowsParseTCP4Rows(rows []byte, count uint32, processes map[int]status.Pr return items } -// windowsParseTCP6Rows converts raw MIB_TCP6ROW_OWNER_PID rows into listener records. +// windowsParseTCP6Rows 将原始 MIB_TCP6ROW_OWNER_PID 行转换为监听记录。 func windowsParseTCP6Rows(rows []byte, count uint32, processes map[int]status.ProcessInfo) []status.TCPListenerInfo { rowSize := int(unsafe.Sizeof(windowsMIBTCP6RowOwnerPID{})) items := make([]status.TCPListenerInfo, 0, count) @@ -568,13 +568,13 @@ func windowsParseTCP6Rows(rows []byte, count uint32, processes map[int]status.Pr return items } -// windowsTCPPortFromNetworkOrder decodes the low 16 bits Windows stores in network byte order. +// windowsTCPPortFromNetworkOrder 解码 Windows 以网络字节序存储的低 16 位端口。 func windowsTCPPortFromNetworkOrder(raw uint32) int { port := uint16(raw) return int(port>>8) | int(port&0xff)<<8 } -// windowsProcessList enumerates processes with the Windows Toolhelp snapshot API. +// windowsProcessList 使用 Windows Toolhelp 快照 API 枚举进程。 func windowsProcessList() ([]status.ProcessInfo, error) { cmdlines := windowsProcessCommandLines() snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0) @@ -604,7 +604,7 @@ func windowsProcessList() ([]status.ProcessInfo, error) { } } -// windowsProcessInfoFromEntry combines Toolhelp identity data with optional process details. +// windowsProcessInfoFromEntry 合并 Toolhelp 身份数据与可选进程详情。 func windowsProcessInfoFromEntry(entry syscall.ProcessEntry32, details windowsProcessDetails) status.ProcessInfo { name := syscall.UTF16ToString(entry.ExeFile[:]) exe := details.Exe @@ -619,7 +619,7 @@ func windowsProcessInfoFromEntry(entry syscall.ProcessEntry32, details windowsPr } } -// windowsReadProcessDetails opens a process and reads fields that may require elevated privileges. +// windowsReadProcessDetails 打开进程并读取可能需要提升权限的字段。 func windowsReadProcessDetails(pid uint32) windowsProcessDetails { handle, err := syscall.OpenProcess(windowsProcessQueryLimitedInfo|windowsProcessQueryInformation|windowsProcessVMRead, false, pid) if err != nil { @@ -634,7 +634,7 @@ func windowsReadProcessDetails(pid uint32) windowsProcessDetails { return details } -// windowsQueryProcessImageName returns the full process image path when access allows it. +// windowsQueryProcessImageName 在访问允许时返回完整进程映像路径。 func windowsQueryProcessImageName(handle syscall.Handle) string { buf := make([]uint16, windowsMaxPath) size := uint32(len(buf)) @@ -650,7 +650,7 @@ func windowsQueryProcessImageName(handle syscall.Handle) string { return syscall.UTF16ToString(buf[:size]) } -// windowsQueryProcessTimes returns start time and lifetime-average CPU percentage. +// windowsQueryProcessTimes 返回启动时间与生命周期平均 CPU 百分比。 func windowsQueryProcessTimes(handle syscall.Handle) (int64, float64) { var creation, exit, kernel, user syscall.Filetime if err := syscall.GetProcessTimes(handle, &creation, &exit, &kernel, &user); err != nil { @@ -666,7 +666,7 @@ func windowsQueryProcessTimes(handle syscall.Handle) (int64, float64) { return startNanos / int64(time.Second), percent } -// windowsQueryProcessMemory returns working set and pagefile usage when access allows it. +// windowsQueryProcessMemory 在访问允许时返回工作集与页面文件占用。 func windowsQueryProcessMemory(handle syscall.Handle) (uint64, uint64) { counters := windowsProcessMemoryCounters{CB: uint32(unsafe.Sizeof(windowsProcessMemoryCounters{}))} ok, _, _ := windowsGetProcessMemoryInfoProc.Call( @@ -680,7 +680,7 @@ func windowsQueryProcessMemory(handle syscall.Handle) (uint64, uint64) { return uint64(counters.WorkingSetSize), uint64(counters.PagefileUsage) } -// windowsQueryProcessUsername returns DOMAIN\user for a process token when access allows it. +// windowsQueryProcessUsername 在访问允许时返回进程令牌的 DOMAIN\user。 func windowsQueryProcessUsername(handle syscall.Handle) string { var token syscall.Handle ok, _, _ := windowsOpenProcessTokenProc.Call(uintptr(handle), uintptr(windowsTokenQuery), uintptr(unsafe.Pointer(&token))) @@ -702,7 +702,7 @@ func windowsQueryProcessUsername(handle syscall.Handle) string { return windowsLookupAccountName(sid) } -// windowsLookupAccountName resolves a SID pointer to DOMAIN\name. +// windowsLookupAccountName 将 SID 指针解析为 DOMAIN\name。 func windowsLookupAccountName(sid uintptr) string { var nameSize, domainSize uint32 var sidType uint32 @@ -732,7 +732,7 @@ func windowsLookupAccountName(sid uintptr) string { return domainName + `\` + account } -// windowsProcessCommandLines reads command lines in one best-effort WMIC call to avoid per-process shelling. +// windowsProcessCommandLines 通过一次尽力 WMIC 调用读取命令行,避免逐进程 shell。 func windowsProcessCommandLines() map[int]string { out := map[int]string{} ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) @@ -760,7 +760,7 @@ func windowsProcessCommandLines() map[int]string { return out } -// windowsProcessMap returns process metadata keyed by PID for TCP owner mapping. +// windowsProcessMap 返回以 PID 为键的进程元数据,供 TCP 属主映射使用。 func windowsProcessMap() (map[int]status.ProcessInfo, error) { processes, err := windowsProcessList() out := make(map[int]status.ProcessInfo, len(processes)) @@ -770,7 +770,7 @@ func windowsProcessMap() (map[int]status.ProcessInfo, error) { return out, err } -// windowsSortedKeys returns deterministic sorted keys from a string set. +// windowsSortedKeys 从字符串集合返回确定顺序的排序键列表。 func windowsSortedKeys(values map[string]struct{}) []string { keys := make([]string, 0, len(values)) for key := range values { diff --git a/internal/collector/collector_windows_test.go b/internal/collector/collector_windows_test.go index 1974361..9bcad23 100644 --- a/internal/collector/collector_windows_test.go +++ b/internal/collector/collector_windows_test.go @@ -7,7 +7,7 @@ import ( "testing" ) -// TestWindowsMemoryInfoFromStatus verifies Windows memory counters are exposed in bytes. +// TestWindowsMemoryInfoFromStatus 验证 Windows 内存计数以字节对外暴露。 func TestWindowsMemoryInfoFromStatus(t *testing.T) { got := windowsMemoryInfoFromStatus(windowsMemoryStatus{ TotalPhys: 10, @@ -25,14 +25,14 @@ func TestWindowsMemoryInfoFromStatus(t *testing.T) { } } -// TestWindowsTCPPortFromNetworkOrder verifies Windows TCP port values are decoded. +// TestWindowsTCPPortFromNetworkOrder 验证 Windows TCP 端口值被正确解码。 func TestWindowsTCPPortFromNetworkOrder(t *testing.T) { if got := windowsTCPPortFromNetworkOrder(0x0000901f); got != 8080 { t.Fatalf("port = %d, want 8080", got) } } -// TestWindowsDiskInfoFromSpace verifies Windows disk counters are mapped to API disk fields. +// TestWindowsDiskInfoFromSpace 验证 Windows 磁盘计数映射到 API 磁盘字段。 func TestWindowsDiskInfoFromSpace(t *testing.T) { got := windowsDiskInfoFromSpace(`C:\`, windowsDriveFixed, 100, 40) @@ -50,7 +50,7 @@ func TestWindowsDiskInfoFromSpace(t *testing.T) { } } -// TestWindowsCPUUsagePercent verifies idle and kernel counters are converted to busy percentage. +// TestWindowsCPUUsagePercent 验证空闲与内核计数转换为繁忙百分比。 func TestWindowsCPUUsagePercent(t *testing.T) { prev := windowsSystemTimes{Idle: 100, Kernel: 500, User: 500} next := windowsSystemTimes{Idle: 150, Kernel: 650, User: 700} @@ -62,7 +62,7 @@ func TestWindowsCPUUsagePercent(t *testing.T) { } } -// TestWindowsProcessInfoFromEntry verifies Toolhelp process data is enriched with memory and command line data. +// TestWindowsProcessInfoFromEntry 验证 Toolhelp 进程数据补充内存与命令行信息。 func TestWindowsProcessInfoFromEntry(t *testing.T) { entry := syscall.ProcessEntry32{ProcessID: 123, ParentProcessID: 12} copy(entry.ExeFile[:], syscall.StringToUTF16("agent.exe")) diff --git a/internal/collector/network.go b/internal/collector/network.go index 76e034c..43a6484 100644 --- a/internal/collector/network.go +++ b/internal/collector/network.go @@ -5,7 +5,7 @@ import ( "sort" ) -// networkAddressSets separates interface addresses into deterministic IPv4 and IPv6 lists. +// networkAddressSets 将接口地址拆分为确定顺序的 IPv4 与 IPv6 列表。 func networkAddressSets(values map[string]struct{}) (ipv4s []string, ipv6s []string) { for value := range values { host, _, err := net.ParseCIDR(value) diff --git a/internal/collector/network_test.go b/internal/collector/network_test.go index bd21f85..6efa258 100644 --- a/internal/collector/network_test.go +++ b/internal/collector/network_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -// TestNetworkAddressSetsSeparatesIPv4AndIPv6 verifies flattened IP lists are split by address family. +// TestNetworkAddressSetsSeparatesIPv4AndIPv6 验证展平 IP 列表按地址族拆分。 func TestNetworkAddressSetsSeparatesIPv4AndIPv6(t *testing.T) { ipv4s, ipv6s := networkAddressSets(map[string]struct{}{ "fe80::1/64": {}, diff --git a/internal/config/config.go b/internal/config/config.go index 98fca36..b6dad72 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -8,7 +8,7 @@ import ( "time" ) -// Config contains all runtime, reporting, collection, and service settings. +// Config 包含运行时、上报、采集与服务注册相关的全部配置项。 type Config struct { HostID string `json:"host_id"` HTTP HTTPConfig `json:"http"` @@ -17,18 +17,18 @@ type Config struct { Service ServiceConfig `json:"service"` } -// HTTPConfig controls the local HTTP API listener. +// HTTPConfig 控制本地 HTTP API 的监听地址。 type HTTPConfig struct { ListenAddress string `json:"listen_address"` } -// CollectConfig controls periodic host status collection. +// CollectConfig 控制周期性主机状态采集。 type CollectConfig struct { Interval Duration `json:"interval"` Timeout Duration `json:"timeout"` } -// ReportConfig controls optional periodic status POST reporting. +// ReportConfig 控制可选的周期性状态 POST 上报。 type ReportConfig struct { Enabled bool `json:"enabled"` Endpoint string `json:"endpoint"` @@ -36,27 +36,27 @@ type ReportConfig struct { Timeout Duration `json:"timeout"` } -// ServiceConfig controls operating system service registration metadata. +// ServiceConfig 控制操作系统服务注册时使用的元数据。 type ServiceConfig struct { Name string `json:"name"` DisplayName string `json:"display_name"` Description string `json:"description"` } -// Duration wraps time.Duration so JSON config can use Go duration strings. +// Duration 封装 time.Duration,使 JSON 配置可使用 Go 时长字符串(如 "30s")。 type Duration time.Duration -// Duration returns the standard library duration value. +// Duration 返回标准库的 time.Duration 值。 func (d Duration) Duration() time.Duration { return time.Duration(d) } -// MarshalJSON writes the duration as a Go duration string such as "30s". +// MarshalJSON 将时长序列化为 Go 时长字符串,例如 "30s"。 func (d Duration) MarshalJSON() ([]byte, error) { return json.Marshal(time.Duration(d).String()) } -// UnmarshalJSON parses a Go duration string from JSON. +// UnmarshalJSON 从 JSON 解析 Go 时长字符串。 func (d *Duration) UnmarshalJSON(data []byte) error { var raw string if err := json.Unmarshal(data, &raw); err != nil { @@ -70,7 +70,7 @@ func (d *Duration) UnmarshalJSON(data []byte) error { return nil } -// Default returns safe local defaults for running the agent. +// Default 返回适合本地运行的安全默认配置。 func Default() Config { return Config{ HTTP: HTTPConfig{ListenAddress: "127.0.0.1:8080"}, @@ -91,7 +91,7 @@ func Default() Config { } } -// Load reads optional JSON config, applies environment overrides, and validates required settings. +// Load 读取可选的 JSON 配置文件,应用环境变量覆盖,并校验必填项。 func Load(path string) (Config, error) { cfg := Default() if path != "" { @@ -110,7 +110,7 @@ func Load(path string) (Config, error) { return cfg, nil } -// applyEnv overlays supported MFD_AGENT_* environment variables onto cfg. +// applyEnv 将支持的 MFD_AGENT_* 环境变量覆盖到 cfg 上。 func applyEnv(cfg *Config) { if value := os.Getenv("MFD_AGENT_HOST_ID"); value != "" { cfg.HostID = value diff --git a/internal/config/config_test.go b/internal/config/config_test.go index a1cb864..af9018d 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -6,7 +6,7 @@ import ( "time" ) -// TestDefaultConfigUsesLocalhostAndSaneIntervals verifies safe local defaults. +// TestDefaultConfigUsesLocalhostAndSaneIntervals 验证默认配置使用本地监听地址与合理的采集间隔。 func TestDefaultConfigUsesLocalhostAndSaneIntervals(t *testing.T) { cfg := Default() @@ -21,7 +21,7 @@ func TestDefaultConfigUsesLocalhostAndSaneIntervals(t *testing.T) { } } -// TestLoadAppliesEnvironmentOverrides verifies environment variables override defaults. +// TestLoadAppliesEnvironmentOverrides 验证环境变量可覆盖默认配置。 func TestLoadAppliesEnvironmentOverrides(t *testing.T) { t.Setenv("MFD_AGENT_LISTEN", "127.0.0.1:9090") t.Setenv("MFD_AGENT_REPORT_ENABLED", "true") @@ -48,7 +48,7 @@ func TestLoadAppliesEnvironmentOverrides(t *testing.T) { } } -// TestLoadJSONConfigFile verifies JSON config files are parsed into Config. +// TestLoadJSONConfigFile 验证可从 JSON 配置文件解析 Config。 func TestLoadJSONConfigFile(t *testing.T) { file, err := os.CreateTemp(t.TempDir(), "config-*.json") if err != nil { diff --git a/internal/httpapi/server.go b/internal/httpapi/server.go index 0985c78..894c489 100644 --- a/internal/httpapi/server.go +++ b/internal/httpapi/server.go @@ -7,24 +7,24 @@ import ( "mfd-monitor-agent/internal/status" ) -// Provider supplies the latest cached HostStatus to HTTP handlers. +// Provider 向 HTTP 处理器提供最新缓存的 HostStatus。 type Provider interface { - // Get returns the latest snapshot and whether a snapshot is ready. + // Get 返回最新快照及快照是否已就绪。 Get() (status.HostStatus, bool) } -// StaticProvider is a test provider with a fixed snapshot response. +// StaticProvider 是用于测试的固定快照提供者。 type StaticProvider struct { Snapshot status.HostStatus OK bool } -// Get returns the configured static snapshot and readiness flag. +// Get 返回配置的静态快照与就绪标志。 func (p StaticProvider) Get() (status.HostStatus, bool) { return p.Snapshot, p.OK } -// New creates the HTTP API handler for health and status endpoints. +// New 创建提供 health 与 status 端点的 HTTP API 处理器。 func New(provider Provider) http.Handler { mux := http.NewServeMux() mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { @@ -61,7 +61,7 @@ func New(provider Provider) http.Handler { return mux } -// writeJSON serializes value as JSON with a response status code. +// writeJSON 以给定状态码将 value 序列化为 JSON 响应。 func writeJSON(w http.ResponseWriter, code int, value any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) diff --git a/internal/httpapi/server_test.go b/internal/httpapi/server_test.go index 8866e60..11c569e 100644 --- a/internal/httpapi/server_test.go +++ b/internal/httpapi/server_test.go @@ -10,7 +10,7 @@ import ( "mfd-monitor-agent/internal/status" ) -// TestStatusReturnsCachedSnapshot verifies /status reads the provider cache. +// TestStatusReturnsCachedSnapshot 验证 /status 读取提供者缓存。 func TestStatusReturnsCachedSnapshot(t *testing.T) { provider := StaticProvider{Snapshot: status.HostStatus{ Agent: status.AgentInfo{Version: "test", SnapshotAt: time.Now(), SnapshotStatus: "ok"}, @@ -34,7 +34,7 @@ func TestStatusReturnsCachedSnapshot(t *testing.T) { } } -// TestHealthReportsSnapshotMetadata verifies /health returns snapshot metadata. +// TestHealthReportsSnapshotMetadata 验证 /health 返回快照元数据。 func TestHealthReportsSnapshotMetadata(t *testing.T) { provider := StaticProvider{Snapshot: status.HostStatus{ Agent: status.AgentInfo{Version: "test", SnapshotAt: time.Unix(10, 0), SnapshotStatus: "partial"}, @@ -57,7 +57,7 @@ func TestHealthReportsSnapshotMetadata(t *testing.T) { } } -// TestStatusReturnsServiceUnavailableBeforeSnapshot verifies /status does not live collect. +// TestStatusReturnsServiceUnavailableBeforeSnapshot 验证 /status 不会在请求时实时采集。 func TestStatusReturnsServiceUnavailableBeforeSnapshot(t *testing.T) { server := New(StaticProvider{OK: false}) @@ -70,7 +70,7 @@ func TestStatusReturnsServiceUnavailableBeforeSnapshot(t *testing.T) { } } -// TestHealthReturnsStartingBeforeSnapshot verifies /health exposes startup state. +// TestHealthReturnsStartingBeforeSnapshot 验证 /health 在启动阶段暴露 starting 状态。 func TestHealthReturnsStartingBeforeSnapshot(t *testing.T) { server := New(StaticProvider{OK: false}) @@ -90,7 +90,7 @@ func TestHealthReturnsStartingBeforeSnapshot(t *testing.T) { } } -// TestStatusRejectsNonGET verifies /status only accepts GET requests. +// TestStatusRejectsNonGET 验证 /status 仅接受 GET 请求。 func TestStatusRejectsNonGET(t *testing.T) { server := New(StaticProvider{OK: true}) @@ -103,7 +103,7 @@ func TestStatusRejectsNonGET(t *testing.T) { } } -// TestHealthRejectsNonGET verifies /health only accepts GET requests. +// TestHealthRejectsNonGET 验证 /health 仅接受 GET 请求。 func TestHealthRejectsNonGET(t *testing.T) { server := New(StaticProvider{OK: true}) diff --git a/internal/reporter/reporter.go b/internal/reporter/reporter.go index 47f3297..634c4da 100644 --- a/internal/reporter/reporter.go +++ b/internal/reporter/reporter.go @@ -14,19 +14,19 @@ import ( const defaultRetryDelay = 2 * time.Second -// Provider supplies the cached HostStatus used for outbound reports. +// Provider 提供用于外发上报的缓存 HostStatus。 type Provider interface { - // Get returns the latest snapshot and whether a snapshot is ready. + // Get 返回最新快照及快照是否已就绪。 Get() (status.HostStatus, bool) } -// Client posts HostStatus snapshots to a configured center endpoint. +// Client 将 HostStatus 快照 POST 到配置的中心端地址。 type Client struct { endpoint string client *http.Client } -// NewClient creates a reporting client with the provided endpoint and request timeout. +// NewClient 使用给定端点与请求超时创建上报客户端。 func NewClient(endpoint string, timeout time.Duration) *Client { return &Client{ endpoint: endpoint, @@ -34,7 +34,7 @@ func NewClient(endpoint string, timeout time.Duration) *Client { } } -// Report posts one HostStatus snapshot as JSON and returns non-2xx responses as errors. +// Report 将一份 HostStatus 以 JSON POST 上报;非 2xx 响应作为错误返回。 func (c *Client) Report(snapshot status.HostStatus) error { body, err := json.Marshal(snapshot) if err != nil { @@ -56,12 +56,12 @@ func (c *Client) Report(snapshot status.HostStatus) error { return nil } -// Run periodically posts the latest cached snapshot until ctx is canceled. +// Run 在 ctx 取消前按间隔 POST 最新缓存快照。 func Run(ctx context.Context, provider Provider, client *Client, interval time.Duration, logger *slog.Logger) { runWithRetryDelay(ctx, provider, client, interval, defaultRetryDelay, logger) } -// runWithRetryDelay runs the reporting loop and retries one failed report after retryDelay. +// runWithRetryDelay 运行上报循环;单次失败后按 retryDelay 重试一次。 func runWithRetryDelay(ctx context.Context, provider Provider, client *Client, interval time.Duration, retryDelay time.Duration, logger *slog.Logger) { ticker := time.NewTicker(interval) defer ticker.Stop() @@ -81,7 +81,7 @@ func runWithRetryDelay(ctx context.Context, provider Provider, client *Client, i } } -// reportWithRetry posts a snapshot and performs one cancellation-aware retry on failure. +// reportWithRetry 上报快照;失败时在可取消的等待后重试一次。 func reportWithRetry(ctx context.Context, client *Client, snapshot status.HostStatus, retryDelay time.Duration) error { err := client.Report(snapshot) if err == nil { diff --git a/internal/reporter/reporter_test.go b/internal/reporter/reporter_test.go index ccd50c7..384c179 100644 --- a/internal/reporter/reporter_test.go +++ b/internal/reporter/reporter_test.go @@ -14,18 +14,18 @@ import ( "mfd-monitor-agent/internal/status" ) -// StaticProvider is a test provider with a fixed cached snapshot. +// StaticProvider 是用于测试的固定缓存快照提供者。 type StaticProvider struct { Snapshot status.HostStatus OK bool } -// Get returns the configured static snapshot and readiness flag. +// Get 返回配置的静态快照与就绪标志。 func (p StaticProvider) Get() (status.HostStatus, bool) { return p.Snapshot, p.OK } -// TestReportPostsHostStatusJSON verifies reports reuse the HostStatus JSON payload. +// TestReportPostsHostStatusJSON 验证上报复用 HostStatus JSON 载荷。 func TestReportPostsHostStatusJSON(t *testing.T) { var got status.HostStatus server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -52,7 +52,7 @@ func TestReportPostsHostStatusJSON(t *testing.T) { } } -// TestReportReturnsErrorForHTTPFailure verifies non-2xx responses fail one report. +// TestReportReturnsErrorForHTTPFailure 验证非 2xx 响应导致单次上报失败。 func TestReportReturnsErrorForHTTPFailure(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusInternalServerError) @@ -65,7 +65,7 @@ func TestReportReturnsErrorForHTTPFailure(t *testing.T) { } } -// TestRunRetriesFailedReportOnce verifies the loop retries one failed report. +// TestRunRetriesFailedReportOnce 验证循环对失败上报重试一次。 func TestRunRetriesFailedReportOnce(t *testing.T) { var requests atomic.Int32 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { @@ -99,7 +99,7 @@ func TestRunRetriesFailedReportOnce(t *testing.T) { <-done } -// TestRunStopsDuringRetryDelayWhenContextCanceled verifies retry waits honor cancellation. +// TestRunStopsDuringRetryDelayWhenContextCanceled 验证重试等待可被 ctx 取消。 func TestRunStopsDuringRetryDelayWhenContextCanceled(t *testing.T) { var requests atomic.Int32 firstRequest := make(chan struct{}) diff --git a/internal/service/service.go b/internal/service/service.go index bdfb947..c77b7e2 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -2,25 +2,25 @@ package service import "mfd-monitor-agent/internal/config" -// InstallOptions carries platform-specific service installation inputs. +// InstallOptions 携带平台相关的服务安装参数。 type InstallOptions struct { - // ConfigPath is preserved in the service command line when a config file was supplied. + // ConfigPath 在提供了配置文件时,会写入服务命令行中的 -config 参数。 ConfigPath string } -// Manager controls operating system service installation and lifecycle commands. +// Manager 控制操作系统服务的安装与生命周期命令。 type Manager interface { - // Install registers the agent executable as an operating system service. + // Install 将 agent 可执行文件注册为操作系统服务。 Install(executable string, cfg config.Config, opts InstallOptions) error - // Uninstall removes the operating system service registration. + // Uninstall 移除操作系统服务注册。 Uninstall(cfg config.Config) error - // Start asks the operating system service manager to start the agent service. + // Start 请求操作系统服务管理器启动 agent 服务。 Start(cfg config.Config) error - // Stop asks the operating system service manager to stop the agent service. + // Stop 请求操作系统服务管理器停止 agent 服务。 Stop(cfg config.Config) error } -// NewManager returns the platform-specific service manager. +// NewManager 返回平台相关的服务管理器。 func NewManager() Manager { return newManager() } diff --git a/internal/service/service_linux.go b/internal/service/service_linux.go index 9b9c3bf..9010c7e 100644 --- a/internal/service/service_linux.go +++ b/internal/service/service_linux.go @@ -13,15 +13,15 @@ import ( var currentUID = os.Geteuid -// linuxManager manages a systemd service for the agent. +// linuxManager 通过 systemd 管理 agent 服务。 type linuxManager struct{} -// newManager returns the Linux systemd-backed service manager. +// newManager 返回基于 Linux systemd 的服务管理器。 func newManager() Manager { return linuxManager{} } -// Install writes a systemd unit, reloads systemd, and enables the configured service. +// Install 写入 systemd unit、重载 systemd 并 enable 配置的服务。 func (linuxManager) Install(executable string, cfg config.Config, opts InstallOptions) error { if currentUID() != 0 { return fmt.Errorf("install requires root") @@ -40,7 +40,7 @@ func (linuxManager) Install(executable string, cfg config.Config, opts InstallOp return nil } -// renderSystemdUnit builds the Linux systemd unit used by Install. +// renderSystemdUnit 生成 Install 使用的 Linux systemd unit 内容。 func renderSystemdUnit(executable string, configPath string, cfg config.Config) string { args := []string{executable, "run"} if configPath != "" { @@ -62,7 +62,7 @@ WantedBy=multi-user.target `, cfg.Service.Description, strings.Join(args, " ")) } -// Uninstall disables the service, removes its unit file, and reloads systemd. +// Uninstall 禁用服务、删除 unit 文件并重载 systemd。 func (linuxManager) Uninstall(cfg config.Config) error { _ = exec.Command("systemctl", "disable", "--now", cfg.Service.Name).Run() if err := os.Remove("/etc/systemd/system/" + cfg.Service.Name + ".service"); err != nil && !os.IsNotExist(err) { @@ -71,12 +71,12 @@ func (linuxManager) Uninstall(cfg config.Config) error { return exec.Command("systemctl", "daemon-reload").Run() } -// Start asks systemd to start the configured service. +// Start 请求 systemd 启动配置的服务。 func (linuxManager) Start(cfg config.Config) error { return exec.Command("systemctl", "start", cfg.Service.Name).Run() } -// Stop asks systemd to stop the configured service. +// Stop 请求 systemd 停止配置的服务。 func (linuxManager) Stop(cfg config.Config) error { return exec.Command("systemctl", "stop", cfg.Service.Name).Run() } diff --git a/internal/service/service_linux_test.go b/internal/service/service_linux_test.go index 1d7ac95..c8659f4 100644 --- a/internal/service/service_linux_test.go +++ b/internal/service/service_linux_test.go @@ -9,7 +9,7 @@ import ( "mfd-monitor-agent/internal/config" ) -// TestRenderSystemdUnitIncludesConfigPath verifies install preserves the config flag. +// TestRenderSystemdUnitIncludesConfigPath 验证安装时保留 -config 参数。 func TestRenderSystemdUnitIncludesConfigPath(t *testing.T) { cfg := config.Default() cfg.Service.Description = "MFD Agent" @@ -21,7 +21,7 @@ func TestRenderSystemdUnitIncludesConfigPath(t *testing.T) { } } -// TestLinuxInstallRequiresRoot verifies Linux install rejects non-root callers. +// TestLinuxInstallRequiresRoot 验证 Linux 安装拒绝非 root 调用。 func TestLinuxInstallRequiresRoot(t *testing.T) { oldUID := currentUID t.Cleanup(func() { currentUID = oldUID }) diff --git a/internal/service/service_other.go b/internal/service/service_other.go index 4e65f16..3335385 100644 --- a/internal/service/service_other.go +++ b/internal/service/service_other.go @@ -9,30 +9,30 @@ import ( "mfd-monitor-agent/internal/config" ) -// unsupportedManager reports service lifecycle operations as unsupported on this platform. +// unsupportedManager 在本平台将服务生命周期操作报告为不支持。 type unsupportedManager struct{} -// newManager returns a manager that fails all service operations on unsupported platforms. +// newManager 返回在不支持平台上对所有服务操作均失败的管理器。 func newManager() Manager { return unsupportedManager{} } -// Install reports that service installation is unsupported on this platform. +// Install 报告本平台不支持服务安装。 func (unsupportedManager) Install(string, config.Config, InstallOptions) error { return fmt.Errorf("service install is not implemented for %s", runtime.GOOS) } -// Uninstall reports that service removal is unsupported on this platform. +// Uninstall 报告本平台不支持服务卸载。 func (unsupportedManager) Uninstall(config.Config) error { return fmt.Errorf("service uninstall is not implemented for %s", runtime.GOOS) } -// Start reports that service start is unsupported on this platform. +// Start 报告本平台不支持服务启动。 func (unsupportedManager) Start(config.Config) error { return fmt.Errorf("service start is not implemented for %s", runtime.GOOS) } -// Stop reports that service stop is unsupported on this platform. +// Stop 报告本平台不支持服务停止。 func (unsupportedManager) Stop(config.Config) error { return fmt.Errorf("service stop is not implemented for %s", runtime.GOOS) } diff --git a/internal/service/service_windows.go b/internal/service/service_windows.go index e6e52b6..b633a78 100644 --- a/internal/service/service_windows.go +++ b/internal/service/service_windows.go @@ -9,15 +9,15 @@ import ( "mfd-monitor-agent/internal/config" ) -// windowsManager manages the agent through the Windows Service Control Manager. +// windowsManager 通过 Windows 服务控制管理器(SCM)管理 agent。 type windowsManager struct{} -// newManager returns the Windows service manager. +// newManager 返回 Windows 服务管理器。 func newManager() Manager { return windowsManager{} } -// Install registers the agent executable as an auto-start Windows Service. +// Install 将 agent 可执行文件注册为自动启动的 Windows 服务。 func (windowsManager) Install(executable string, cfg config.Config, opts InstallOptions) error { args := []string{`"` + executable + `"`, "run"} if opts.ConfigPath != "" { @@ -26,18 +26,18 @@ func (windowsManager) Install(executable string, cfg config.Config, opts Install return exec.Command("sc.exe", "create", cfg.Service.Name, "binPath=", strings.Join(args, " "), "DisplayName=", cfg.Service.DisplayName, "start=", "auto").Run() } -// Uninstall stops and removes the configured Windows Service. +// Uninstall 停止并删除已配置的 Windows 服务。 func (windowsManager) Uninstall(cfg config.Config) error { _ = exec.Command("sc.exe", "stop", cfg.Service.Name).Run() return exec.Command("sc.exe", "delete", cfg.Service.Name).Run() } -// Start asks the Windows Service Control Manager to start the service. +// Start 请求 SCM 启动服务。 func (windowsManager) Start(cfg config.Config) error { return exec.Command("sc.exe", "start", cfg.Service.Name).Run() } -// Stop asks the Windows Service Control Manager to stop the service. +// Stop 请求 SCM 停止服务。 func (windowsManager) Stop(cfg config.Config) error { return exec.Command("sc.exe", "stop", cfg.Service.Name).Run() } diff --git a/internal/snapshot/snapshot.go b/internal/snapshot/snapshot.go index a9b47b4..393bb25 100644 --- a/internal/snapshot/snapshot.go +++ b/internal/snapshot/snapshot.go @@ -8,28 +8,28 @@ import ( "mfd-monitor-agent/internal/status" ) -// Collector gathers one subsection of HostStatus into the provided snapshot. +// Collector 将 HostStatus 的一个子区块采集并写入提供的快照。 type Collector interface { - // Name returns the stable collector name used in partial snapshot errors. + // Name 返回用于 partial 快照错误记录的稳定采集器名称。 Name() string - // Collect fills its subsection of out and returns an error when that subsection is incomplete. + // Collect 填充 out 中对应子区块;该子区块不完整时返回错误。 Collect(context.Context, *status.HostStatus) error } -// CollectorFunc adapts a function into a Collector for tests and small collectors. +// CollectorFunc 将函数适配为 Collector,供测试与小型采集器使用。 type CollectorFunc func(context.Context, *status.HostStatus) error -// Name returns a generic collector name for function-backed collectors. +// Name 为基于函数的采集器返回通用名称。 func (fn CollectorFunc) Name() string { return "collector" } -// Collect calls the wrapped function. +// Collect 调用包装的采集函数。 func (fn CollectorFunc) Collect(ctx context.Context, out *status.HostStatus) error { return fn(ctx, out) } -// Cache stores the most recent HostStatus snapshot behind a concurrency-safe interface. +// Cache 在并发安全接口后保存最近一次 HostStatus 快照。 type Cache struct { collectors []Collector version string @@ -39,12 +39,12 @@ type Cache struct { hasLast bool } -// NewCache creates an empty snapshot cache using the provided collectors and agent metadata. +// NewCache 使用给定采集器与 agent 元数据创建空的快照缓存。 func NewCache(collectors []Collector, version string, startedAt time.Time) *Cache { return &Cache{collectors: collectors, version: version, startedAt: startedAt} } -// Collect runs all collectors once and stores the resulting snapshot even when some collectors fail. +// Collect 运行全部采集器一次并存储结果;部分采集器失败时仍保存快照。 func (c *Cache) Collect(ctx context.Context) status.HostStatus { out := status.HostStatus{ Agent: status.AgentInfo{ @@ -74,14 +74,14 @@ func (c *Cache) Collect(ctx context.Context) status.HostStatus { return out } -// Get returns the latest cached snapshot and whether collection has completed at least once. +// Get 返回最新缓存快照,以及是否至少完成过一次采集。 func (c *Cache) Get() (status.HostStatus, bool) { c.mu.RLock() defer c.mu.RUnlock() return c.last, c.hasLast } -// Run collects immediately and then refreshes the cache until ctx is canceled. +// Run 立即采集一次,随后在 ctx 取消前按间隔刷新缓存。 func (c *Cache) Run(ctx context.Context, interval time.Duration, timeout time.Duration) { c.collectWithTimeout(ctx, timeout) ticker := time.NewTicker(interval) @@ -96,7 +96,7 @@ func (c *Cache) Run(ctx context.Context, interval time.Duration, timeout time.Du } } -// collectWithTimeout runs one collection cycle with an overall deadline. +// collectWithTimeout 在整体截止时间下执行一轮采集。 func (c *Cache) collectWithTimeout(ctx context.Context, timeout time.Duration) { collectCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() diff --git a/internal/snapshot/snapshot_test.go b/internal/snapshot/snapshot_test.go index 87222f8..3286e29 100644 --- a/internal/snapshot/snapshot_test.go +++ b/internal/snapshot/snapshot_test.go @@ -9,7 +9,7 @@ import ( "mfd-monitor-agent/internal/status" ) -// TestCollectorFailuresReturnPartialSnapshot verifies failed collectors do not fail snapshots. +// TestCollectorFailuresReturnPartialSnapshot 验证采集器失败不会导致整份快照失败。 func TestCollectorFailuresReturnPartialSnapshot(t *testing.T) { cache := NewCache([]Collector{ CollectorFunc(func(context.Context, *status.HostStatus) error { @@ -30,7 +30,7 @@ func TestCollectorFailuresReturnPartialSnapshot(t *testing.T) { } } -// TestCacheStoresLastSnapshot verifies successful collection is cached. +// TestCacheStoresLastSnapshot 验证成功采集结果被缓存。 func TestCacheStoresLastSnapshot(t *testing.T) { cache := NewCache([]Collector{ CollectorFunc(func(_ context.Context, out *status.HostStatus) error { @@ -50,7 +50,7 @@ func TestCacheStoresLastSnapshot(t *testing.T) { } } -// TestRunCollectsImmediately verifies Run collects before the first interval tick. +// TestRunCollectsImmediately 验证 Run 在首个间隔 tick 之前立即采集。 func TestRunCollectsImmediately(t *testing.T) { collected := make(chan struct{}) cache := NewCache([]Collector{ @@ -79,7 +79,7 @@ func TestRunCollectsImmediately(t *testing.T) { } } -// TestCollectWithTimeoutMarksCanceledCollectorPartial verifies timeout errors are partial. +// TestCollectWithTimeoutMarksCanceledCollectorPartial 验证超时被记为 partial。 func TestCollectWithTimeoutMarksCanceledCollectorPartial(t *testing.T) { cache := NewCache([]Collector{ CollectorFunc(func(ctx context.Context, _ *status.HostStatus) error { diff --git a/internal/status/types.go b/internal/status/types.go index d7f91c9..d5276ef 100644 --- a/internal/status/types.go +++ b/internal/status/types.go @@ -2,9 +2,9 @@ package status import "time" -// HostStatus is the full cached host snapshot returned by /status and posted by the reporter. +// HostStatus 是 /status 返回的完整缓存主机快照,也是上报器 POST 的载荷结构。 type HostStatus struct { - // Agent describes the agent process and most recent snapshot metadata. + // Agent 描述 agent 进程及最近一次快照的元数据。 Agent AgentInfo `json:"agent"` Host HostInfo `json:"host"` Network NetworkInfo `json:"network"` @@ -13,31 +13,31 @@ type HostStatus struct { Disks []DiskInfo `json:"disks"` Processes ProcessResult `json:"processes"` TCPListeners TCPResult `json:"tcp_listeners"` - // Errors records collector-level failures that caused a partial snapshot. + // Errors 记录导致快照变为 partial 的子采集器失败信息。 Errors []StatusError `json:"errors"` } -// AgentInfo contains agent process metadata and the latest collection result. +// AgentInfo 包含 agent 进程元数据及最近一次采集结果。 type AgentInfo struct { Version string `json:"version"` StartedAt time.Time `json:"started_at"` SnapshotAt time.Time `json:"snapshot_at"` - // SnapshotStatus is ok when all collectors succeed and partial when one or more fail. + // SnapshotStatus 在所有采集器成功时为 ok,任一失败时为 partial。 SnapshotStatus string `json:"snapshot_status"` } -// HostInfo describes the monitored host identity and operating system. +// HostInfo 描述被监控主机的标识与操作系统信息。 type HostInfo struct { HostID string `json:"host_id"` Hostname string `json:"hostname"` Platform string `json:"platform"` PlatformVersion string `json:"platform_version"` Arch string `json:"arch"` - // UptimeSeconds is the host uptime in seconds when available. + // UptimeSeconds 为主机运行时长(秒),在可用时填充。 UptimeSeconds uint64 `json:"uptime_seconds"` } -// NetworkInfo contains all network interfaces plus flattened IPv4, IPv6, and MAC lists. +// NetworkInfo 包含全部网卡及展平后的 IPv4、IPv6、MAC 列表。 type NetworkInfo struct { Interfaces []NetworkInterface `json:"interfaces"` IPv4s []string `json:"ipv4s"` @@ -45,67 +45,67 @@ type NetworkInfo struct { MACs []string `json:"macs"` } -// NetworkInterface describes one host network interface and its configured addresses. +// NetworkInterface 描述一块网卡及其配置的地址。 type NetworkInterface struct { Name string `json:"name"` MAC string `json:"mac"` - // MTU is the interface maximum transmission unit in bytes. + // MTU 为接口最大传输单元(字节)。 MTU int `json:"mtu"` Flags []string `json:"flags"` Addresses []string `json:"addresses"` } -// CPUInfo describes host CPU capacity and current utilization. +// CPUInfo 描述主机 CPU 容量与当前利用率。 type CPUInfo struct { LogicalCores int `json:"logical_cores"` PhysicalCores int `json:"physical_cores"` - // UsagePercent is the best-effort host CPU usage percentage for the latest collection. + // UsagePercent 为最近一次采集得到的尽力主机 CPU 使用率(百分比)。 UsagePercent float64 `json:"usage_percent"` LoadAvg []float64 `json:"load_avg"` ModelName string `json:"model_name"` } -// MemoryInfo describes host memory usage capacity fields in the declared unit. +// MemoryInfo 描述主机内存容量与使用情况,数值单位由 Unit 字段声明。 type MemoryInfo struct { - // Unit declares the unit used by total, used, free, and available. + // Unit 声明 total、used、free、available 使用的单位。 Unit string `json:"unit"` - // Total is total physical memory in the declared unit. + // Total 为物理内存总量(与 Unit 一致)。 Total uint64 `json:"total"` - // Used is memory currently used in the declared unit. + // Used 为当前已用内存(与 Unit 一致)。 Used uint64 `json:"used"` - // Free is memory currently free in the declared unit. + // Free 为当前空闲内存(与 Unit 一致)。 Free uint64 `json:"free"` - // Available is memory available to new workloads in the declared unit. + // Available 为可供新负载使用的内存(与 Unit 一致)。 Available uint64 `json:"available"` - // UsedPercent is the percentage of total memory currently used. + // UsedPercent 为已用内存占总量的百分比。 UsedPercent float64 `json:"used_percent"` } -// DiskInfo describes one physical or network mounted filesystem and its capacity. +// DiskInfo 描述一块已挂载文件系统的容量与使用情况。 type DiskInfo struct { Device string `json:"device"` MountPoint string `json:"mount_point"` FSType string `json:"fs_type"` - // Unit declares the unit used by total, used, and free. + // Unit 声明 total、used、free 使用的单位。 Unit string `json:"unit"` - // Total is filesystem capacity in the declared unit. + // Total 为文件系统总容量(与 Unit 一致)。 Total uint64 `json:"total"` - // Used is consumed filesystem capacity in the declared unit. + // Used 为已占用容量(与 Unit 一致)。 Used uint64 `json:"used"` - // Free is available filesystem capacity in the declared unit. + // Free 为可用容量(与 Unit 一致)。 Free uint64 `json:"free"` - // UsedPercent is the percentage of total filesystem capacity currently used. + // UsedPercent 为已用容量占总量的百分比。 UsedPercent float64 `json:"used_percent"` } -// ProcessResult contains process collection status and the collected process list. +// ProcessResult 包含进程采集状态及进程列表。 type ProcessResult struct { - // Status is ok, partial, or error depending on process collection completeness. + // Status 根据进程采集完整程度为 ok、partial 或 error。 Status string `json:"status"` Items []ProcessInfo `json:"items"` } -// ProcessInfo describes one operating system process. +// ProcessInfo 描述一个操作系统进程。 type ProcessInfo struct { PID int `json:"pid"` PPID int `json:"ppid"` @@ -114,24 +114,24 @@ type ProcessInfo struct { Cmdline string `json:"cmdline"` Username string `json:"username"` Status string `json:"status"` - // StartTime is the platform-specific process start timestamp when available. + // StartTime 为平台相关的进程启动时间戳,在可用时填充。 StartTime int64 `json:"start_time"` - // CPUPercent is the best-effort process CPU usage percentage. + // CPUPercent 为尽力估算的进程 CPU 使用率(百分比)。 CPUPercent float64 `json:"cpu_percent"` - // MemoryRSS is resident memory size in bytes. + // MemoryRSS 为常驻内存大小(字节)。 MemoryRSS uint64 `json:"memory_rss"` - // MemoryVMS is virtual memory size in bytes. + // MemoryVMS 为虚拟内存大小(字节)。 MemoryVMS uint64 `json:"memory_vms"` } -// TCPResult contains TCP listener collection status and listener entries. +// TCPResult 包含 TCP 监听采集状态及监听项列表。 type TCPResult struct { - // Status is ok, partial, or error depending on listener mapping completeness. + // Status 根据监听端口到进程映射的完整程度为 ok、partial 或 error。 Status string `json:"status"` Items []TCPListenerInfo `json:"items"` } -// TCPListenerInfo describes one listening TCP socket and its owning process when known. +// TCPListenerInfo 描述一个处于监听状态的 TCP 套接字及所属进程(若已知)。 type TCPListenerInfo struct { IP string `json:"ip"` Port int `json:"port"` @@ -141,7 +141,7 @@ type TCPListenerInfo struct { Family string `json:"family"` } -// StatusError records a collector failure without failing the whole snapshot. +// StatusError 记录单个子采集器失败,不导致整份快照请求失败。 type StatusError struct { Collector string `json:"collector"` Message string `json:"message"` diff --git a/scripts/build-linux.sh b/scripts/build-linux.sh index 066da61..d51ff30 100755 --- a/scripts/build-linux.sh +++ b/scripts/build-linux.sh @@ -1,4 +1,17 @@ #!/usr/bin/env bash +# 构建 mfd-monitor-agent 单二进制产物。 +# +# 环境变量: +# OUT_DIR 输出目录,默认 <仓库根>/dist +# VERSION 写入二进制的版本号,默认 0.1.0(通过 -ldflags -X main.version) +# GOOS_TARGET 目标 GOOS,默认 linux +# GOARCH_TARGET 目标 GOARCH,默认 amd64 +# OUTPUT_NAME 输出文件名,默认 agent-- +# CGO_ENABLED 是否启用 CGO,默认 0(静态链接友好) +# +# 交叉编译 Windows 示例: +# GOOS_TARGET=windows GOARCH_TARGET=amd64 OUTPUT_NAME=agent-windows-amd64.exe ./scripts/build-linux.sh +# set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" diff --git a/scripts/build-windows.ps1 b/scripts/build-windows.ps1 index b928ab3..db71e06 100644 --- a/scripts/build-windows.ps1 +++ b/scripts/build-windows.ps1 @@ -1,3 +1,13 @@ +# 构建 mfd-monitor-agent 单二进制产物(Windows 目标)。 +# +# 环境变量: +# OUT_DIR 输出目录,默认 <仓库根>\dist +# VERSION 写入二进制的版本号,默认 0.1.0(通过 -ldflags -X main.version) +# GOOS_TARGET 目标 GOOS,默认 windows +# GOARCH_TARGET 目标 GOARCH,默认 amd64 +# OUTPUT_NAME 输出文件名,默认 agent--.exe +# CGO_ENABLED 是否启用 CGO,默认 0 +# $ErrorActionPreference = "Stop" $RootDir = Resolve-Path (Join-Path $PSScriptRoot "..")