refactor!: rename the project from Monitor to MoShiro

Namespaces, assemblies, projects and directories, the solution, binaries,
images, systemd units, config paths, environment variables, protocol method
names, the media type and the raw chunk magic all move to MoShiro/moshiro.
Existing data and deployments are not compatible and must be recreated.
Also fix the architecture tests after the solution layout change and make
the update tests independent of their order.
This commit is contained in:
justme committed 2026-10-07 01:49:04 +08:00
1 parent 17b35223aa
commit 83e5120377
309 files changed
+1505 -1499

No files matched your search

+1 -1
View File
@@ -23,4 +23,4 @@ steps:
environment:
NUGET_PACKAGES: /home/justme/.nuget/packages
commands:
- cd backend && dotnet test --solution Monitor.slnx -c Release
- cd backend && dotnet test --solution MoShiro.slnx -c Release
+8 -8
View File
@@ -2,7 +2,7 @@
## Project
Monitor is a self-hosted network path quality monitor. Backend coordinates Agents and Probes, stores measurements, and serves the REST/SSE API. `web/display` is the read-only overview; `web/admin` manages nodes, tests, and settings.
MoShiro is a self-hosted network path quality monitor. Backend coordinates Agents and Probes, stores measurements, and serves the REST/SSE API. `web/display` is the read-only overview; `web/admin` manages nodes, tests, and settings.
## Branch and workspace safety
@@ -10,12 +10,12 @@ Monitor is a self-hosted network path quality monitor. Backend coordinates Agent
- A push to `main` builds images only if its commit message contains `[CI]` (for a merged PR, put it in the PR or merge commit title). Without `[CI]` nothing is built; use `[CI SKIP]` to skip the pipeline entirely. When squashing or pushing to `main`, add `[CI]` unless a build is explicitly not wanted.
- Before editing, inspect `git status`. Preserve pre-existing user changes: do not reset, clean, stash, or include unrelated files in a commit.
- Do not commit credentials, API keys, registry passwords, or generated runtime data. CI credentials belong in Woodpecker secrets (`zot_secret`, `gitea_token`, `release_signing_key` for signing release manifests, `release_api_token` with the `release` scope). The release signing private key never goes into the repository or the panel; only the public key in `packaging/update/` does.
- Keep development OCI images in the `monitor-dev/` repository path and release images in `monitor/`. Do not let a development build overwrite release tags such as `latest`.
- Keep development OCI images in the `moshiro-dev/` repository path and release images in `moshiro/`. Do not let a development build overwrite release tags such as `latest`.
## Project layout
- `backend/src/Monitor.Backend/`: service entry point and composition.
- `backend/src/Monitor.Backend.Api/`: REST endpoints, contracts, and OpenAPI.
- `backend/src/MoShiro.Backend/`: service entry point and composition.
- `backend/src/MoShiro.Backend.Api/`: REST endpoints, contracts, and OpenAPI.
- `backend/agent/`, `backend/probe/`: node applications and their READMEs.
- `backend/fleet/`, `backend/shared/`: reusable node framework, measurement engine, protocols, and data formats.
- `web/display/`, `web/admin/`: Vite applications; `web/shared/`: API client, generated types, charts, and shared UI.
@@ -25,12 +25,12 @@ Monitor is a self-hosted network path quality monitor. Backend coordinates Agent
Requirements: .NET SDK 10 and Node.js/npm. The SDK selection is defined by the root `global.json`; NuGet package versions are centralized in the root `Directory.Packages.props`.
- All .NET projects: `dotnet build Monitor.slnx -c Release` from the repository root.
- Backend build: `cd backend && dotnet build Monitor.slnx -c Release`
- Backend tests: `cd backend && dotnet test --solution Monitor.slnx -c Release`
- All .NET projects: `dotnet build MoShiro.slnx -c Release` from the repository root.
- Backend build: `cd backend && dotnet build MoShiro.slnx -c Release`
- Backend tests: `cd backend && dotnet test --solution MoShiro.slnx -c Release`
- Frontend dependencies/build: `npm ci --prefix web && npm run build --prefix web`
- Frontend style check: `npm run lint:css --prefix web`
- Local backend: from `backend/`, run `dotnet run --project src/Monitor.Backend -- --urls http://localhost:5000`.
- Local backend: from `backend/`, run `dotnet run --project src/MoShiro.Backend -- --urls http://localhost:5000`.
- Local web apps: from the repository root, run `npm ci --prefix web` then `npm run dev --prefix web`. The current dev config uses display port 5173 and admin port 5175; `/admin/` is proxied through the display server. Check the Vite configs when working on another branch because ports can differ.
- After changing API contracts, regenerate/check `backend/openapi/v1.json` and run `npm run gen:api --prefix web` so frontend API types stay aligned.
+3 -3
View File
@@ -2,9 +2,9 @@
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<!-- Native SQLite source is pinned separately from the JIT/test binary package. -->
<MonitorSqliteSourceVersion>3530400</MonitorSqliteSourceVersion>
<MonitorSqliteSourceYear>2026</MonitorSqliteSourceYear>
<MonitorSqliteSourceSha256>1e71ddf93849c6a6ecf58b827c0692073d2dd7ee40196158068f7b29f422e87d</MonitorSqliteSourceSha256>
<MoShiroSqliteSourceVersion>3530400</MoShiroSqliteSourceVersion>
<MoShiroSqliteSourceYear>2026</MoShiroSqliteSourceYear>
<MoShiroSqliteSourceSha256>1e71ddf93849c6a6ecf58b827c0692073d2dd7ee40196158068f7b29f422e87d</MoShiroSqliteSourceSha256>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="5.6.0" />
+17 -17
View File
@@ -17,36 +17,36 @@
<Project Path="backend/fleet/tools/fleet-enroll/fleet-enroll.csproj" />
</Folder>
<Folder Name="/backend/shared/">
<Project Path="backend/shared/Monitor.Measurement/Monitor.Measurement.csproj" />
<Project Path="backend/shared/Monitor.Protocol/Monitor.Protocol.csproj" />
<Project Path="backend/shared/MoShiro.Measurement/MoShiro.Measurement.csproj" />
<Project Path="backend/shared/MoShiro.Protocol/MoShiro.Protocol.csproj" />
</Folder>
<Folder Name="/backend/shared/tests/">
<Project Path="backend/shared/tests/Monitor.Measurement.Tests/Monitor.Measurement.Tests.csproj" />
<Project Path="backend/shared/tests/Monitor.Protocol.Tests/Monitor.Protocol.Tests.csproj" />
<Project Path="backend/shared/tests/MoShiro.Measurement.Tests/MoShiro.Measurement.Tests.csproj" />
<Project Path="backend/shared/tests/MoShiro.Protocol.Tests/MoShiro.Protocol.Tests.csproj" />
</Folder>
<Folder Name="/backend/agent/src/">
<Project Path="backend/agent/src/Monitor.Agent/Monitor.Agent.csproj" />
<Project Path="backend/agent/src/MoShiro.Agent/MoShiro.Agent.csproj" />
</Folder>
<Folder Name="/backend/probe/src/">
<Project Path="backend/probe/src/Monitor.Probe/Monitor.Probe.csproj" />
<Project Path="backend/probe/src/MoShiro.Probe/MoShiro.Probe.csproj" />
</Folder>
<Folder Name="/backend/probe/tests/">
<Project Path="backend/probe/tests/Monitor.Probe.Tests/Monitor.Probe.Tests.csproj" />
<Project Path="backend/probe/tests/MoShiro.Probe.Tests/MoShiro.Probe.Tests.csproj" />
</Folder>
<Folder Name="/backend/src/">
<Project Path="backend/src/Monitor.Backend.Analysis/Monitor.Backend.Analysis.csproj" />
<Project Path="backend/src/Monitor.Backend.Api/Monitor.Backend.Api.csproj" />
<Project Path="backend/src/Monitor.Backend.Application/Monitor.Backend.Application.csproj" />
<Project Path="backend/src/Monitor.Backend.Gateway/Monitor.Backend.Gateway.csproj" />
<Project Path="backend/src/Monitor.Backend.Storage.Sqlite/Monitor.Backend.Storage.Sqlite.csproj" />
<Project Path="backend/src/Monitor.Backend/Monitor.Backend.csproj" />
<Project Path="backend/src/MoShiro.Backend.Analysis/MoShiro.Backend.Analysis.csproj" />
<Project Path="backend/src/MoShiro.Backend.Api/MoShiro.Backend.Api.csproj" />
<Project Path="backend/src/MoShiro.Backend.Application/MoShiro.Backend.Application.csproj" />
<Project Path="backend/src/MoShiro.Backend.Gateway/MoShiro.Backend.Gateway.csproj" />
<Project Path="backend/src/MoShiro.Backend.Storage.Sqlite/MoShiro.Backend.Storage.Sqlite.csproj" />
<Project Path="backend/src/MoShiro.Backend/MoShiro.Backend.csproj" />
</Folder>
<Folder Name="/backend/tests/">
<Project Path="backend/tests/Monitor.Backend.Analysis.Tests/Monitor.Backend.Analysis.Tests.csproj" />
<Project Path="backend/tests/Monitor.Backend.Tests/Monitor.Backend.Tests.csproj" />
<Project Path="backend/tests/Monitor.TestSupport/Monitor.TestSupport.csproj" />
<Project Path="backend/tests/MoShiro.Backend.Analysis.Tests/MoShiro.Backend.Analysis.Tests.csproj" />
<Project Path="backend/tests/MoShiro.Backend.Tests/MoShiro.Backend.Tests.csproj" />
<Project Path="backend/tests/MoShiro.TestSupport/MoShiro.TestSupport.csproj" />
</Folder>
<Folder Name="/ci/">
<Project Path="ci/Monitor.Tar/Monitor.Tar.csproj" />
<Project Path="ci/MoShiro.Tar/MoShiro.Tar.csproj" />
</Folder>
</Solution>
+7 -7
View File
@@ -1,6 +1,6 @@
# Monitor
# MoShiro
Monitor 是一个自托管的网络线路质量观测工具。它在 Probe 与多个 Agent 之间执行主动网络测量,记录逐包和逐事件结果;Backend 保存原始数据并生成可查询的线路指标,Web 界面用于查看、比较和管理节点。
MoShiro 是一个自托管的网络线路质量观测工具。它在 Probe 与多个 Agent 之间执行主动网络测量,记录逐包和逐事件结果;Backend 保存原始数据并生成可查询的线路指标,Web 界面用于查看、比较和管理节点。
它面向需要比较不同网络出口、评估线路稳定性,或观察特定网络服务体验的场景。Agent 与 Probe 专注于测量和上报;它们不提供远程终端、命令执行或文件管理能力。
@@ -48,10 +48,10 @@ Monitor 是一个自托管的网络线路质量观测工具。它在 Probe 与
从仓库根目录构建全部 .NET 项目(Backend、Agent、Probe、Fleet、测试和 CI 工具):
```sh
dotnet build Monitor.slnx -c Release
dotnet build MoShiro.slnx -c Release
```
NuGet 包版本统一在根目录 `Directory.Packages.props` 管理,项目中的 `PackageReference` 不写版本号。AotAnywhere 的 MSBuild SDK 版本在根目录 `global.json` 的 `msbuild-sdks` 中管理。`backend/Monitor.slnx` 保留用于只构建后端目录内的项目。
NuGet 包版本统一在根目录 `Directory.Packages.props` 管理,项目中的 `PackageReference` 不写版本号。AotAnywhere 的 MSBuild SDK 版本在根目录 `global.json` 的 `msbuild-sdks` 中管理。`backend/MoShiro.slnx` 保留用于只构建后端目录内的项目。
安装前端依赖并构建展示端和管理端(在仓库根目录运行):
@@ -72,7 +72,7 @@ Backend、Agent 和 Probe 均以 OCI 镜像发布,支持 Linux/amd64 与 Linux
```sh
cd backend
dotnet run --project src/Monitor.Backend -- --urls http://localhost:5000
dotnet run --project src/MoShiro.Backend -- --urls http://localhost:5000
```
2. **启动前端。** 保持 Backend 运行,在另一个终端的仓库根目录运行:
@@ -88,7 +88,7 @@ npm run dev --prefix web
- Pull request 和 `dev` 分支 push:先跑 Backend tests,通过后再构建前端。
- 在需要跳过 Backend tests 的提交信息后加 `[SKIP TESTS]`;Gitea pull request 需要把标记写进 PR 标题。前端构建仍会运行。
- `main` 上的提交信息包含 `[CI]` 时,并行构建 Backend、Agent、Probe 的 Linux/amd64 镜像,推送到 Zot 的 `monitor-dev/` 仓库;普通 main push 不构建。
- `main` 上的提交信息包含 `[CI]` 时,并行构建 Backend、Agent、Probe 的 Linux/amd64 镜像,推送到 Zot 的 `moshiro-dev/` 仓库;普通 main push 不构建。
- 推送 `vMAJOR.MINOR.PATCH` tag 会触发全平台正式构建和 Release 发布。
完整触发条件、开发镜像标签和发布路径见 [`docs/deployment.md`](docs/deployment.md)。
@@ -120,7 +120,7 @@ npm run dev --prefix web
## 测试
```sh
dotnet test --solution Monitor.slnx
dotnet test --solution MoShiro.slnx
```
```sh
+16 -16
View File
@@ -17,33 +17,33 @@
<Project Path="fleet/tools/fleet-enroll/fleet-enroll.csproj" />
</Folder>
<Folder Name="/shared/">
<Project Path="shared/Monitor.Measurement/Monitor.Measurement.csproj" />
<Project Path="shared/Monitor.Protocol/Monitor.Protocol.csproj" />
<Project Path="shared/MoShiro.Measurement/MoShiro.Measurement.csproj" />
<Project Path="shared/MoShiro.Protocol/MoShiro.Protocol.csproj" />
</Folder>
<Folder Name="/shared/tests/">
<Project Path="shared/tests/Monitor.Measurement.Tests/Monitor.Measurement.Tests.csproj" />
<Project Path="shared/tests/Monitor.Protocol.Tests/Monitor.Protocol.Tests.csproj" />
<Project Path="shared/tests/MoShiro.Measurement.Tests/MoShiro.Measurement.Tests.csproj" />
<Project Path="shared/tests/MoShiro.Protocol.Tests/MoShiro.Protocol.Tests.csproj" />
</Folder>
<Folder Name="/agent/src/">
<Project Path="agent/src/Monitor.Agent/Monitor.Agent.csproj" />
<Project Path="agent/src/MoShiro.Agent/MoShiro.Agent.csproj" />
</Folder>
<Folder Name="/probe/src/">
<Project Path="probe/src/Monitor.Probe/Monitor.Probe.csproj" />
<Project Path="probe/src/MoShiro.Probe/MoShiro.Probe.csproj" />
</Folder>
<Folder Name="/probe/tests/">
<Project Path="probe/tests/Monitor.Probe.Tests/Monitor.Probe.Tests.csproj" />
<Project Path="probe/tests/MoShiro.Probe.Tests/MoShiro.Probe.Tests.csproj" />
</Folder>
<Folder Name="/backend/src/">
<Project Path="src/Monitor.Backend.Analysis/Monitor.Backend.Analysis.csproj" />
<Project Path="src/Monitor.Backend.Api/Monitor.Backend.Api.csproj" />
<Project Path="src/Monitor.Backend.Application/Monitor.Backend.Application.csproj" />
<Project Path="src/Monitor.Backend.Gateway/Monitor.Backend.Gateway.csproj" />
<Project Path="src/Monitor.Backend.Storage.Sqlite/Monitor.Backend.Storage.Sqlite.csproj" />
<Project Path="src/Monitor.Backend/Monitor.Backend.csproj" />
<Project Path="src/MoShiro.Backend.Analysis/MoShiro.Backend.Analysis.csproj" />
<Project Path="src/MoShiro.Backend.Api/MoShiro.Backend.Api.csproj" />
<Project Path="src/MoShiro.Backend.Application/MoShiro.Backend.Application.csproj" />
<Project Path="src/MoShiro.Backend.Gateway/MoShiro.Backend.Gateway.csproj" />
<Project Path="src/MoShiro.Backend.Storage.Sqlite/MoShiro.Backend.Storage.Sqlite.csproj" />
<Project Path="src/MoShiro.Backend/MoShiro.Backend.csproj" />
</Folder>
<Folder Name="/backend/tests/">
<Project Path="tests/Monitor.Backend.Analysis.Tests/Monitor.Backend.Analysis.Tests.csproj" />
<Project Path="tests/Monitor.Backend.Tests/Monitor.Backend.Tests.csproj" />
<Project Path="tests/Monitor.TestSupport/Monitor.TestSupport.csproj" />
<Project Path="tests/MoShiro.Backend.Analysis.Tests/MoShiro.Backend.Analysis.Tests.csproj" />
<Project Path="tests/MoShiro.Backend.Tests/MoShiro.Backend.Tests.csproj" />
<Project Path="tests/MoShiro.TestSupport/MoShiro.TestSupport.csproj" />
</Folder>
</Solution>
+12 -12
View File
@@ -1,17 +1,17 @@
# Backend
Backend 是 Monitor 的控制面和数据服务,负责 Agent 注册与连接、任务编排、原始测量数据持久化、指标分析,以及供前端使用的 REST API 和 SSE 实时数据流。
Backend 是 MoShiro 的控制面和数据服务,负责 Agent 注册与连接、任务编排、原始测量数据持久化、指标分析,以及供前端使用的 REST API 和 SSE 实时数据流。
## 组件
| 路径 | 职责 |
| --- | --- |
| `src/Monitor.Backend/` | 应用入口与依赖组合 |
| `src/Monitor.Backend.Api/` | Public、Admin API 和 OpenAPI |
| `src/Monitor.Backend.Gateway/` | Fleet RPC、节点状态与测量数据接收 |
| `src/Monitor.Backend.Application/` | 业务用例、查询、任务和后台服务 |
| `src/Monitor.Backend.Analysis/` | 指标计算、累加器和服务模型 |
| `src/Monitor.Backend.Storage.Sqlite/` | 段文件与 SQLite 存储 |
| `src/MoShiro.Backend/` | 应用入口与依赖组合 |
| `src/MoShiro.Backend.Api/` | Public、Admin API 和 OpenAPI |
| `src/MoShiro.Backend.Gateway/` | Fleet RPC、节点状态与测量数据接收 |
| `src/MoShiro.Backend.Application/` | 业务用例、查询、任务和后台服务 |
| `src/MoShiro.Backend.Analysis/` | 指标计算、累加器和服务模型 |
| `src/MoShiro.Backend.Storage.Sqlite/` | 段文件与 SQLite 存储 |
| `fleet/` | 通用节点身份、注册和连接框架 |
| `shared/` | 测量引擎、协议和数据格式 |
| `tests/` | 集成测试和测试支持代码 |
@@ -26,14 +26,14 @@ Backend 是 Monitor 的控制面和数据服务,负责 Agent 注册与连接
```sh
cd backend
dotnet build Monitor.slnx -c Release
dotnet test --solution Monitor.slnx
dotnet build MoShiro.slnx -c Release
dotnet test --solution MoShiro.slnx
```
发布当前平台的 NativeAOT Backend:
```sh
dotnet publish src/Monitor.Backend -c Release -r <RID> -p:PublishAot=true
dotnet publish src/MoShiro.Backend -c Release -r <RID> -p:PublishAot=true
```
例如 `<RID>` 可为 `win-x64`、`linux-x64` 或 `linux-arm64`。NativeAOT 发布需要对应平台的 C/C++ 工具链。仓库根目录的 `scripts/publish.sh backend <RID>` 可构建单个平台的 Backend 可执行文件;容器发布由 Woodpecker tag 流水线完成,详见 [`../docs/deployment.md`](../docs/deployment.md)。
@@ -46,9 +46,9 @@ dotnet publish src/Monitor.Backend -c Release -r <RID> -p:PublishAot=true
```sh
cd backend
dotnet run --project src/Monitor.Backend -- --urls http://localhost:5000
dotnet run --project src/MoShiro.Backend -- --urls http://localhost:5000
```
首次启动时可用 `Monitor__InitialAdminUser` 和 `Monitor__InitialAdminPassword` 设置初始管理员。数据目录、公开地址、Cookie 和 CORS 等设置通过 `Monitor` 配置节传入;完整配置说明见 [`../docs/deployment.md`](../docs/deployment.md)。站点默认需要登录,可在管理端修改公开设置。
首次启动时可用 `MoShiro__InitialAdminUser` 和 `MoShiro__InitialAdminPassword` 设置初始管理员。数据目录、公开地址、Cookie 和 CORS 等设置通过 `MoShiro` 配置节传入;完整配置说明见 [`../docs/deployment.md`](../docs/deployment.md)。站点默认需要登录,可在管理端修改公开设置。
OpenAPI 文档由构建生成到 `openapi/v1.json`。API 设计和端点说明见 [`../docs/api.md`](../docs/api.md);项目总览见仓库根目录的 [`README.md`](../README.md)。
+7 -7
View File
@@ -1,4 +1,4 @@
# Monitor Agent
# MoShiro Agent
Agent 是部署在 VPS 或其他可达网络节点上的采集进程。它向 Backend 注册并保持连接,接受 Probe 的测量会话,执行线路任务中的 Agent 侧工作,再将两端数据合并后上报。
@@ -18,14 +18,14 @@ Probe ──TLS 控制 / UDP、TCP 测量──> Agent ──长连接与数据
```sh
cd backend
dotnet build agent/src/Monitor.Agent -c Release
dotnet build agent/src/MoShiro.Agent -c Release
dotnet build fleet/tools/fleet-enroll -c Release
```
发布 NativeAOT 程序需要对应平台的 C/C++ 工具链;`<RID>` 应与构建环境和目标平台匹配:
```sh
dotnet publish agent/src/Monitor.Agent -c Release -r <RID> -p:PublishAot=true
dotnet publish agent/src/MoShiro.Agent -c Release -r <RID> -p:PublishAot=true
dotnet publish fleet/tools/fleet-enroll -c Release -r <RID> -p:PublishAot=true
```
@@ -36,7 +36,7 @@ dotnet publish fleet/tools/fleet-enroll -c Release -r <RID> -p:PublishAot=true
先在管理端创建加入令牌,然后在 Agent 主机上运行一次性注册工具:
```sh
fleet-enroll --server https://<backend> --join <token> --name <agent-name> --identity-dir /etc/monitor
fleet-enroll --server https://<backend> --join <token> --name <agent-name> --identity-dir /etc/moshiro
```
注册工具会在身份目录写入节点身份和证书。准备本地 `agent.json`,其中 `fleet.server` 指向 Backend,`fleet.identity_dir` 指向上述目录;`listen`、`limits` 和 `reporting` 可按节点环境调整。配置文件不会由 Backend 修改。限制目标网段和测量预算时,应按实际网络与机器能力配置;`allowed_target_cidrs` 默认为空,不允许 Agent 执行目标探测任务。
@@ -44,12 +44,12 @@ fleet-enroll --server https://<backend> --join <token> --name <agent-name> --ide
启动 Agent:
```sh
monitor-agent --config /etc/monitor/agent.json
moshiro-agent --config /etc/moshiro/agent.json
```
容器节点的运行方式(podman + systemd)和宿主机更新器见 [`../../docs/deployment.md`](../../docs/deployment.md)。也可使用 OCI 镜像 `registry.oeo.one/monitor/monitor-agent:<version>`,自动选择 Linux amd64 或 arm64 镜像。镜像同时包含 `fleet-enroll`;可通过 `--entrypoint /usr/local/bin/fleet-enroll` 执行首次注册。容器默认读取命名 volume 中的 `/etc/monitor/agent.json` 与身份文件;建议使用宿主网络,并按 ICMP 测量需求添加 `NET_RAW` 能力。镜像以 UID/GID 1654 运行,新命名 volume 由引擎按镜像目录初始化权限。
容器节点的运行方式(podman + systemd)和宿主机更新器见 [`../../docs/deployment.md`](../../docs/deployment.md)。也可使用 OCI 镜像 `registry.oeo.one/moshiro/moshiro-agent:<version>`,自动选择 Linux amd64 或 arm64 镜像。镜像同时包含 `fleet-enroll`;可通过 `--entrypoint /usr/local/bin/fleet-enroll` 执行首次注册。容器默认读取命名 volume 中的 `/etc/moshiro/agent.json` 与身份文件;建议使用宿主网络,并按 ICMP 测量需求添加 `NET_RAW` 能力。镜像以 UID/GID 1654 运行,新命名 volume 由引擎按镜像目录初始化权限。
不传 `--config` 时默认读取 `/etc/monitor/agent.json`。Agent 默认监听控制端口 `7443/tcp`,数据端口 `7443/udp` 和 `7444/tcp`;防火墙和面板中的节点端口配置需与本地配置一致。`reporting.host_stats` 可关闭主机状态上报;启用时只上报资源使用等汇总数据,不包含主机名、进程或 IP。
不传 `--config` 时默认读取 `/etc/moshiro/agent.json`。Agent 默认监听控制端口 `7443/tcp`,数据端口 `7443/udp` 和 `7444/tcp`;防火墙和面板中的节点端口配置需与本地配置一致。`reporting.host_stats` 可关闭主机状态上报;启用时只上报资源使用等汇总数据,不包含主机名、进程或 IP。
## 设计说明
@@ -4,15 +4,15 @@ using Fleet.Core;
using Fleet.Hosting;
using Fleet.Link;
using Fleet.Rpc;
using Monitor.Measurement;
using Monitor.Protocol;
using MoShiro.Measurement;
using MoShiro.Protocol;
namespace Monitor.Agent;
namespace MoShiro.Agent;
/// <summary>Agent 的组装点:数据面、点对点监听、与 Backend 的链路都在这里接起来。没有全局状态,测试可以在同一进程里启动多个。</summary>
public sealed class AgentApp : IDisposable
{
public const string Version = Monitor.Protocol.BuildInfo.Version;
public const string Version = MoShiro.Protocol.BuildInfo.Version;
private readonly AgentConfig _config;
private readonly LoadedNodeIdentity _identity;
@@ -35,7 +35,7 @@ public sealed class AgentApp : IDisposable
private readonly TestRunner _tests;
private readonly FleetLink _link;
/// <param name="dataDirectory">配置目录(容器里的 <c>/etc/monitor</c>):宿主机更新器和节点在这里交换清单、请求和状态。</param>
/// <param name="dataDirectory">配置目录(容器里的 <c>/etc/moshiro</c>):宿主机更新器和节点在这里交换清单、请求和状态。</param>
public AgentApp(AgentConfig config, LoadedNodeIdentity identity, string dataDirectory, TimeProvider time, INodeLog log)
{
_update = new UpdateBridge(dataDirectory, new ConfigStore(dataDirectory).Write, log);
@@ -185,48 +185,48 @@ public sealed class AgentApp : IDisposable
HashSet<string>? allowed = _config.Policy.AllowMethods is { } list ? [.. list] : null;
bool Allow(string method) => allowed is null || allowed.Contains(method);
if (Allow(MonitorMethods.TestRun))
if (Allow(MoShiroMethods.TestRun))
{
router.On(
MonitorMethods.TestRun,
MonitorJsonContext.Default.TestRunParams,
MonitorJsonContext.Default.TestRunResult,
MoShiroMethods.TestRun,
MoShiroJsonContext.Default.TestRunParams,
MoShiroJsonContext.Default.TestRunResult,
(_, run, _) => ValueTask.FromResult(_tests.Start(run)));
}
if (Allow(MonitorMethods.TestCancel))
if (Allow(MoShiroMethods.TestCancel))
{
router.On(
MonitorMethods.TestCancel,
MonitorJsonContext.Default.TestCancelParams,
MonitorJsonContext.Default.TestRunResult,
MoShiroMethods.TestCancel,
MoShiroJsonContext.Default.TestCancelParams,
MoShiroJsonContext.Default.TestRunResult,
(_, cancel, _) => ValueTask.FromResult(Result<TestRunResult>.Ok(new TestRunResult { Accepted = _tests.Cancel(cancel.TestId) })));
}
if (Allow(MonitorMethods.UpdateOffer))
if (Allow(MoShiroMethods.UpdateOffer))
{
router.OnNotify(MonitorMethods.UpdateOffer, MonitorJsonContext.Default.UpdateOfferParams, (_, offer, _) =>
router.OnNotify(MoShiroMethods.UpdateOffer, MoShiroJsonContext.Default.UpdateOfferParams, (_, offer, _) =>
{
_update.WriteOffer(offer);
return ValueTask.CompletedTask;
});
}
if (Allow(MonitorMethods.UpdateApply))
if (Allow(MoShiroMethods.UpdateApply))
{
router.OnNotify(MonitorMethods.UpdateApply, MonitorJsonContext.Default.UpdateOfferParams, (_, _, _) =>
router.OnNotify(MoShiroMethods.UpdateApply, MoShiroJsonContext.Default.UpdateOfferParams, (_, _, _) =>
{
_update.RequestApply();
return ValueTask.CompletedTask;
});
}
if (Allow(MonitorMethods.ProbeDisconnect))
if (Allow(MoShiroMethods.ProbeDisconnect))
{
router.On(
MonitorMethods.ProbeDisconnect,
MonitorJsonContext.Default.ProbeDisconnectParams,
MonitorJsonContext.Default.TestRunResult,
MoShiroMethods.ProbeDisconnect,
MoShiroJsonContext.Default.ProbeDisconnectParams,
MoShiroJsonContext.Default.TestRunResult,
async (_, request, _) => Result<TestRunResult>.Ok(new TestRunResult { Accepted = await _hub.DisconnectAsync(request.ProbeId.Value) }));
}
@@ -236,11 +236,11 @@ public sealed class AgentApp : IDisposable
Identity = _identity.Identity,
NodeId = _identity.NodeId,
NodeVersion = Version,
Capabilities = [MonitorCapabilities.Base, MonitorCapabilities.KindsPrefix + string.Join(',', BlockCatalog.AgentKinds)],
Capabilities = [MoShiroCapabilities.Base, MoShiroCapabilities.KindsPrefix + string.Join(',', BlockCatalog.AgentKinds)],
HelloMeta = HelloMeta,
HeartbeatExtra = HeartbeatExtra,
};
var link = new FleetLink(options, router, [new StateHandler<TaskSet>(TaskSet.StateType, MonitorJsonContext.Default.TaskSet, _tasks.ApplyAsync)], _outbox, _time, _log);
var link = new FleetLink(options, router, [new StateHandler<TaskSet>(TaskSet.StateType, MoShiroJsonContext.Default.TaskSet, _tasks.ApplyAsync)], _outbox, _time, _log);
// 链路重新连上之后补报:在线的 Probe 都告诉 Backend(Backend 重启或链路抖动期间错过了 probe_connected)
link.OnConnected = async (_, _) =>
@@ -257,7 +257,7 @@ public sealed class AgentApp : IDisposable
{
var meta = new AgentHelloMeta
{
Platform = Monitor.Measurement.PlatformDetector.Detect(),
Platform = MoShiro.Measurement.PlatformDetector.Detect(),
TlsSpki = _identity.File.TlsSpki,
ProbeTcpPort = ControlPort,
ProbeUdpPorts = [.. UdpPorts],
@@ -269,7 +269,7 @@ public sealed class AgentApp : IDisposable
MaxConcurrentTests = _config.Limits.MaxConcurrentTests,
},
};
return JsonSerializer.SerializeToElement(meta, MonitorJsonContext.Default.AgentHelloMeta);
return JsonSerializer.SerializeToElement(meta, MoShiroJsonContext.Default.AgentHelloMeta);
}
private long _lastIn;
@@ -296,6 +296,6 @@ public sealed class AgentApp : IDisposable
_lastIn = packetsIn;
_lastOut = packetsOut;
_lastAtMs = nowMs;
return JsonSerializer.SerializeToElement(extra, MonitorJsonContext.Default.AgentHeartbeatExtra);
return JsonSerializer.SerializeToElement(extra, MoShiroJsonContext.Default.AgentHeartbeatExtra);
}
}
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Monitor.Agent;
namespace MoShiro.Agent;
/// <summary>
/// <c>agent.json</c>:节点本地配置,面板无法修改。其中 <c>limits</c> 是本地硬上限:期望任务集超出这些范围时,
@@ -40,7 +40,7 @@ public sealed class FleetSection
public string Server { get; set; } = "";
[JsonPropertyName("identity_dir")]
public string IdentityDir { get; set; } = "/etc/monitor";
public string IdentityDir { get; set; } = "/etc/moshiro";
}
public sealed class ListenSection
@@ -1,9 +1,9 @@
using Fleet.Core;
using Fleet.Link;
using Monitor.Measurement;
using Monitor.Protocol;
using MoShiro.Measurement;
using MoShiro.Protocol;
namespace Monitor.Agent;
namespace MoShiro.Agent;
/// <summary>
/// Probe 的列块到达之后:找到对应的流 → 与 Agent 在数据面记录的列合并 → 盖章(块头、时钟锚点)→ 编码 → 放进发件箱(通道 16)。
@@ -70,6 +70,6 @@ public sealed class ChunkAssembler
/// <summary>Agent 自己采集的块(目标任务)直接发出。</summary>
public void Emit(RawChunk chunk)
{
_outbox.Enqueue(MonitorChannels.RawChunk, chunk.Header.ChunkId.Value, RawChunkCodec.Encode(chunk));
_outbox.Enqueue(MoShiroChannels.RawChunk, chunk.Header.ChunkId.Value, RawChunkCodec.Encode(chunk));
}
}
@@ -4,10 +4,10 @@ using System.Net.Sockets;
using Fleet.Core;
using Fleet.Link;
using Fleet.Rpc;
using Monitor.Measurement;
using Monitor.Protocol;
using MoShiro.Measurement;
using MoShiro.Protocol;
namespace Monitor.Agent;
namespace MoShiro.Agent;
/// <summary>进程内计数(只有计数,没有主机信息):用于心跳和面板上的节点状态。数据面的包计数由共享的 <see cref="DataPlaneCounters"/> 提供。</summary>
public sealed class AgentCounters
@@ -1,7 +1,7 @@
using System.Globalization;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Agent;
namespace MoShiro.Agent;
/// <summary>
/// 主机资源占用的只读快照:CPU 占用、内存、根分区、网卡累计流量与瞬时网速、系统运行时长。
@@ -4,8 +4,8 @@
Condition="'$(UseAotAnywhere)' == 'true'" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<AssemblyName>monitor-probe</AssemblyName>
<RootNamespace>Monitor.Probe</RootNamespace>
<AssemblyName>moshiro-agent</AssemblyName>
<RootNamespace>MoShiro.Agent</RootNamespace>
<IsAotCompatible>true</IsAotCompatible>
<PublishAot>true</PublishAot>
<PublishTrimmed>true</PublishTrimmed>
@@ -15,10 +15,10 @@
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="Monitor.Backend.Tests" />
<InternalsVisibleTo Include="MoShiro.Backend.Tests" />
<ProjectReference Include="..\..\..\fleet\src\Fleet.Link\Fleet.Link.csproj" />
<ProjectReference Include="..\..\..\fleet\src\Fleet.Hosting\Fleet.Hosting.csproj" />
<ProjectReference Include="..\..\..\shared\Monitor.Measurement\Monitor.Measurement.csproj" />
<ProjectReference Include="..\..\..\shared\MoShiro.Measurement\MoShiro.Measurement.csproj" />
</ItemGroup>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
<Import Project="Sdk.targets" Sdk="StuDev.AotAnywhere"
@@ -2,10 +2,10 @@ using System.Text.Json;
using Fleet.Core;
using Fleet.Link;
using Fleet.Rpc;
using Monitor.Measurement;
using Monitor.Protocol;
using MoShiro.Measurement;
using MoShiro.Protocol;
namespace Monitor.Agent;
namespace MoShiro.Agent;
/// <summary>Agent → Backend 的上行:事件和测试状态是尽力而为的通知(链路断开时丢弃;列块走发件箱,不会丢)。</summary>
public sealed class BackendUplink
@@ -31,7 +31,7 @@ public sealed class BackendUplink
}
var parameters = new AgentEventParams { Kind = kind, AtUtcMs = _time.GetUtcNow().ToUnixTimeMilliseconds(), Detail = detail, Probe = probe };
await SafeAsync(() => session.NotifyAsync(MonitorMethods.Event, parameters, MonitorJsonContext.Default.AgentEventParams, CancellationToken.None));
await SafeAsync(() => session.NotifyAsync(MoShiroMethods.Event, parameters, MoShiroJsonContext.Default.AgentEventParams, CancellationToken.None));
}
public async ValueTask SendHostAsync(HostSnapshot snapshot)
@@ -42,7 +42,7 @@ public sealed class BackendUplink
return;
}
await SafeAsync(() => session.NotifyAsync(MonitorMethods.Host, snapshot, MonitorJsonContext.Default.HostSnapshot, CancellationToken.None));
await SafeAsync(() => session.NotifyAsync(MoShiroMethods.Host, snapshot, MoShiroJsonContext.Default.HostSnapshot, CancellationToken.None));
}
public async ValueTask SendTestStatusAsync(TestId test, string status, int? phase = null, string? errorCode = null)
@@ -54,7 +54,7 @@ public sealed class BackendUplink
}
var parameters = new TestStatusParams { TestId = test, Status = status, Phase = phase, ErrorCode = errorCode };
await SafeAsync(() => session.NotifyAsync(MonitorMethods.TestStatus, parameters, MonitorJsonContext.Default.TestStatusParams, CancellationToken.None));
await SafeAsync(() => session.NotifyAsync(MoShiroMethods.TestStatus, parameters, MoShiroJsonContext.Default.TestStatusParams, CancellationToken.None));
}
private static async ValueTask SafeAsync(Func<ValueTask> send)
@@ -115,7 +115,7 @@ public sealed class PeerHub
var order = new Queue<Guid>();
rpc.OnBinary = async (frame, token) =>
{
if (!OutboxFrame.TryParse(frame, out byte channel, out Guid id, out ReadOnlyMemory<byte> payload) || channel != MonitorChannels.ColumnBlock)
if (!OutboxFrame.TryParse(frame, out byte channel, out Guid id, out ReadOnlyMemory<byte> payload) || channel != MoShiroChannels.ColumnBlock)
{
return;
}
@@ -251,7 +251,7 @@ public sealed class PeerHub
{
Type = LineTaskSet.StateType,
Version = set.Version,
Doc = JsonSerializer.SerializeToElement(set, MonitorJsonContext.Default.LineTaskSet),
Doc = JsonSerializer.SerializeToElement(set, MoShiroJsonContext.Default.LineTaskSet),
};
try
{
@@ -270,8 +270,8 @@ public sealed class PeerHub
private static RpcRouter<PeerContext> BuildRouter() =>
new RpcRouter<PeerContext>()
.On(
MonitorMethods.Clock,
MonitorJsonContext.Default.ClockParams,
MonitorJsonContext.Default.ClockResult,
MoShiroMethods.Clock,
MoShiroJsonContext.Default.ClockParams,
MoShiroJsonContext.Default.ClockResult,
(context, _, _) => ValueTask.FromResult(Result<ClockResult>.Ok(new ClockResult { AgentMonoUs = context.Clock.NowUs() })));
}
@@ -1,6 +1,6 @@
using Fleet.Core;
using Fleet.Hosting;
using Monitor.Agent;
using MoShiro.Agent;
TimeProvider time = TimeProvider.System;
INodeLog log = new ConsoleNodeLog(time);
@@ -11,7 +11,7 @@ if (PlatformCheck.VerifyCrypto() is { } problem)
return 1;
}
string configPath = "/etc/monitor/agent.json";
string configPath = "/etc/moshiro/agent.json";
for (int i = 0; i < args.Length - 1; i++)
{
if (args[i] == "--config")
@@ -1,10 +1,10 @@
using System.Net;
using System.Text.Json;
using Fleet.Core;
using Monitor.Measurement;
using Monitor.Protocol;
using MoShiro.Measurement;
using MoShiro.Protocol;
namespace Monitor.Agent;
namespace MoShiro.Agent;
/// <summary>
/// 目标任务(Agent 自己探测一个地址,没有对端):用和线路任务同一套积木(<see cref="BlockCatalog"/> 里的拨号方,例如 icmp_echo、connect),
@@ -1,10 +1,10 @@
using System.Text.Json;
using Fleet.Core;
using Fleet.Rpc;
using Monitor.Measurement;
using Monitor.Protocol;
using MoShiro.Measurement;
using MoShiro.Protocol;
namespace Monitor.Agent;
namespace MoShiro.Agent;
/// <summary>
/// 期望任务集(<c>task_set</c>)的调和器:每个任务先经过 <see cref="BudgetGuard"/>(参数校验 + 本地硬上限)。
@@ -1,15 +1,15 @@
using System.Collections.Concurrent;
using Fleet.Core;
using Fleet.Rpc;
using Monitor.Measurement;
using Monitor.Protocol;
using MoShiro.Measurement;
using MoShiro.Protocol;
namespace Monitor.Agent;
namespace MoShiro.Agent;
/// <summary>
/// 按需测试的编排(<c>monitor.test.run</c>):Agent 是时间的主人。每个阶段:
/// 登记数据面的流 → <c>monitor.test.phase</c>(Probe 准备好自己一侧的角色)→ 运行(单向数据流由 Agent 发包,回显类等待 Probe 发包)
/// → <c>monitor.test.phase_end</c>(Probe 收尾,回复它交出的列块个数)→ 等所有列块合并上传 → <c>phase_done</c>。
/// 按需测试的编排(<c>moshiro.test.run</c>):Agent 是时间的主人。每个阶段:
/// 登记数据面的流 → <c>moshiro.test.phase</c>(Probe 准备好自己一侧的角色)→ 运行(单向数据流由 Agent 发包,回显类等待 Probe 发包)
/// → <c>moshiro.test.phase_end</c>(Probe 收尾,回复它交出的列块个数)→ 等所有列块合并上传 → <c>phase_done</c>。
/// 同一时间只能运行 <c>max_concurrent_tests</c> 个测试;Probe 掉线或被取消时测试中止,已经上传的阶段数据保留。
/// </summary>
public sealed class TestRunner
@@ -43,17 +43,17 @@ public sealed class TestRunner
public int Running => _running.Count;
/// <summary>校验并接受一个测试(立即返回),实际运行在后台进行,状态通过 <c>monitor.test.status</c> 回报。</summary>
/// <summary>校验并接受一个测试(立即返回),实际运行在后台进行,状态通过 <c>moshiro.test.status</c> 回报。</summary>
public Result<TestRunResult> Start(TestRunParams run)
{
if (_sessions.Get(run.ProbeId.Value) is not { } probe)
{
return Result<TestRunResult>.Fail(MonitorErrors.ProbeOffline, $"probe {run.ProbeId.Value} is not connected to this agent");
return Result<TestRunResult>.Fail(MoShiroErrors.ProbeOffline, $"probe {run.ProbeId.Value} is not connected to this agent");
}
if (run.Phases.Count == 0)
{
return Result<TestRunResult>.Fail(MonitorErrors.ValidationFailed, "a test needs at least one phase");
return Result<TestRunResult>.Fail(MoShiroErrors.ValidationFailed, "a test needs at least one phase");
}
var resolved = new List<(TestPhase Phase, TaskDecision Decision)>();
@@ -64,7 +64,7 @@ public sealed class TestRunner
if (!decision.Accepted)
{
return Result<TestRunResult>.Fail(
decision.Reason == RejectReasons.Unsupported ? FleetErrors.Unsupported : MonitorErrors.BudgetExceeded,
decision.Reason == RejectReasons.Unsupported ? FleetErrors.Unsupported : MoShiroErrors.BudgetExceeded,
$"phase {phase.Kind} refused: {decision.Reason}");
}
@@ -84,7 +84,7 @@ public sealed class TestRunner
if (_running.Count >= _maxConcurrent || !_running.TryAdd(run.TestId.Value, running))
{
running.Dispose();
return Result<TestRunResult>.Fail(MonitorErrors.TestBusy, "this agent is already running a test");
return Result<TestRunResult>.Fail(MoShiroErrors.TestBusy, "this agent is already running a test");
}
_ = Task.Run(() => RunAsync(run.TestId, resolved, running), CancellationToken.None);
@@ -154,7 +154,7 @@ public sealed class TestRunner
catch (OperationCanceledException)
{
finalStatus = running.Lost ? TestStatuses.Failed : TestStatuses.Aborted;
errorCode = running.Lost ? MonitorErrors.ProbeOffline.Value : null;
errorCode = running.Lost ? MoShiroErrors.ProbeOffline.Value : null;
await TryAbortProbeAsync(running.Probe, test);
}
catch (TestFailure failure)
@@ -183,7 +183,7 @@ public sealed class TestRunner
_streams.Add(stream);
try
{
RpcSession<PeerContext> rpc = probe.Rpc ?? throw new TestFailure(MonitorErrors.ProbeOffline.Value);
RpcSession<PeerContext> rpc = probe.Rpc ?? throw new TestFailure(MoShiroErrors.ProbeOffline.Value);
var start = new TestPhaseStartParams
{
TestId = test,
@@ -194,7 +194,7 @@ public sealed class TestRunner
DurationSeconds = durationSeconds,
Port = stream.Port,
};
Result<TestPhaseStartResult> accepted = await rpc.RequestAsync(MonitorMethods.TestPhase, start, MonitorJsonContext.Default.TestPhaseStartParams, MonitorJsonContext.Default.TestPhaseStartResult, ct);
Result<TestPhaseStartResult> accepted = await rpc.RequestAsync(MoShiroMethods.TestPhase, start, MoShiroJsonContext.Default.TestPhaseStartParams, MoShiroJsonContext.Default.TestPhaseStartResult, ct);
if (!accepted.TryGet(out TestPhaseStartResult? started, out FleetError? error) || !started.Accepted)
{
throw new TestFailure(error?.Code.Value ?? FleetErrors.Unsupported.Value);
@@ -209,7 +209,7 @@ public sealed class TestRunner
}
catch (BlockFaultException)
{
throw new TestFailure(MonitorErrors.ProbeOffline.Value);
throw new TestFailure(MoShiroErrors.ProbeOffline.Value);
}
}
else
@@ -219,7 +219,7 @@ public sealed class TestRunner
long sent = stream.Running.Block.Produced;
var end = new TestPhaseEndParams { TestId = test, Phase = index, StreamId = stream.Id, SentCount = sent };
Result<TestPhaseEndResult> ended = await rpc.RequestAsync(MonitorMethods.TestPhaseEnd, end, MonitorJsonContext.Default.TestPhaseEndParams, MonitorJsonContext.Default.TestPhaseEndResult, ct, s_phaseSlack);
Result<TestPhaseEndResult> ended = await rpc.RequestAsync(MoShiroMethods.TestPhaseEnd, end, MoShiroJsonContext.Default.TestPhaseEndParams, MoShiroJsonContext.Default.TestPhaseEndResult, ct, s_phaseSlack);
if (!ended.TryGet(out TestPhaseEndResult? finished, out FleetError? endError))
{
throw new TestFailure(endError.Code.Value);
@@ -254,7 +254,7 @@ public sealed class TestRunner
try
{
await rpc.NotifyAsync(MonitorMethods.TestAbort, new TestAbortParams { TestId = test }, MonitorJsonContext.Default.TestAbortParams, CancellationToken.None);
await rpc.NotifyAsync(MoShiroMethods.TestAbort, new TestAbortParams { TestId = test }, MoShiroJsonContext.Default.TestAbortParams, CancellationToken.None);
}
catch (Exception ex) when (ex is ObjectDisposedException or InvalidOperationException or IOException)
{
+1 -1
View File
@@ -3,7 +3,7 @@ using System.Text.Json.Serialization;
namespace Fleet.Core;
/// <summary>节点 ID(带类型前缀的 ULID)。Monitor 中的 AgentId 就是 NodeId。</summary>
/// <summary>节点 ID(带类型前缀的 ULID)。MoShiro 中的 AgentId 就是 NodeId。</summary>
[JsonConverter(typeof(NodeIdJsonConverter))]
public readonly record struct NodeId(string Value)
{
+1 -1
View File
@@ -49,7 +49,7 @@ public sealed class FleetLinkOptions
public string NodeVersion { get; init; } = "0.0.0";
/// <summary>框架之外的能力,例如 <c>monitor.v1</c> 与 <c>monitor.kinds:udp_echo,tcp_echo</c>。</summary>
/// <summary>框架之外的能力,例如 <c>moshiro.v1</c> 与 <c>moshiro.kinds:udp_echo,tcp_echo</c>。</summary>
public IReadOnlyList<string> Capabilities { get; init; } = [];
/// <summary>附加到 <c>fleet.hello</c> 的产品元数据。</summary>
+1 -1
View File
@@ -58,7 +58,7 @@ public sealed class HelloParams
[JsonPropertyName("now_utc_ms")]
public long NowUtcMs { get; set; }
/// <summary>产品自定义的元数据(例如 Monitor 的 tls_spki、probe_ports、local_limits 摘要)。</summary>
/// <summary>产品自定义的元数据(例如 MoShiro 的 tls_spki、probe_ports、local_limits 摘要)。</summary>
[JsonPropertyName("meta")]
public JsonElement? Meta { get; set; }
}
@@ -2,7 +2,7 @@ namespace Fleet.Server;
public sealed class FleetServerOptions
{
/// <summary>节点 ID 的类型前缀,Monitor 使用 <c>agt</c>。</summary>
/// <summary>节点 ID 的类型前缀,MoShiro 使用 <c>agt</c>。</summary>
public string NodeIdPrefix { get; set; } = "node";
public int MaxFrameBytes { get; set; } = 16 << 20;
@@ -2,7 +2,7 @@ using Fleet.Core;
namespace Fleet.Server;
/// <summary>Fleet 服务端需要的存储。产品可以自己实现(Monitor 把它放进 monitor.db)。</summary>
/// <summary>Fleet 服务端需要的存储。产品可以自己实现(MoShiro 把它放进 moshiro.db)。</summary>
public interface IFleetStore
{
ValueTask<JoinTokenRecord?> FindJoinTokenAsync(ReadOnlyMemory<byte> tokenSha256, CancellationToken ct);
@@ -50,7 +50,7 @@ public sealed class FleetServerBuilder
return this;
}
/// <summary>注册产品的 RPC 方法(<c>monitor.*</c> 等)。处理函数通过 <see cref="IServiceProvider"/> 在调用时解析服务,避免构造期的循环依赖。</summary>
/// <summary>注册产品的 RPC 方法(<c>moshiro.*</c> 等)。处理函数通过 <see cref="IServiceProvider"/> 在调用时解析服务,避免构造期的循环依赖。</summary>
public FleetServerBuilder MapMethods(Action<RpcRouter<NodeCallContext>, IServiceProvider> configure)
{
_routeConfigurators.Add(configure);
+1 -1
View File
@@ -2,7 +2,7 @@ using System.Text.Json.Nodes;
using Fleet.Core;
using Fleet.Enrollment.Client;
// fleet-enroll --server https://panel --join jt_xxx --name jp-1 --identity-dir /etc/monitor [--meta '{"probe_tcp_port":7443,...}'] [--version 1.0]
// fleet-enroll --server https://panel --join jt_xxx --name jp-1 --identity-dir /etc/moshiro [--meta '{"probe_tcp_port":7443,...}'] [--version 1.0]
// 一次性运行:生成身份、注册、写入身份目录,然后退出。
string? Arg(string name) => args.SkipWhile(a => a != name).Skip(1).FirstOrDefault();
+2 -2
View File
@@ -1,7 +1,7 @@
{
"openapi": "3.1.1",
"info": {
"title": "monitor-backend | v1",
"title": "moshiro-backend | v1",
"version": "1.0.0"
},
"paths": {
@@ -3168,7 +3168,7 @@
"tags": [
"admin-data"
],
"summary": "Download a raw chunk (application/vnd.monitor.rawchunk)",
"summary": "Download a raw chunk (application/vnd.moshiro.rawchunk)",
"operationId": "raw_chunks_get",
"parameters": [
{
+9 -9
View File
@@ -1,4 +1,4 @@
# Monitor Probe
# MoShiro Probe
Probe 是网络测量的发起端,通常运行在家宽或其他需要观测的网络出口。它连接 Backend(身份、Agent 列表、状态)和 Agent(测量),执行线路任务,并把本端采集的原始数据交给 Agent 汇总上报。Probe 不提供远程命令或文件管理功能。
@@ -13,7 +13,7 @@ Backend ──任务──> Agent <──测量会话──> Probe
Probe 使用面板生成的 `probe.json` 读取自身身份、面板地址(`server`)和本地测试限制。连上面板之后,面板下发要连接的 Agent 列表;Probe 将列表缓存到同目录的 `agents.json`,面板暂时连不上时靠它继续测量。`probe.json` 含私钥,请限制文件访问权限,不要提交到版本控制。
容器节点的运行方式(podman + systemd)和宿主机更新器见 [`../../docs/deployment.md`](../../docs/deployment.md);Probe 也可以用 OCI 镜像 `registry.oeo.one/monitor/monitor-probe:<version>`,容器默认读取 `/etc/monitor/probe.json`,命名 volume 保存身份配置和缓存,镜像以 UID/GID 1654 运行。
容器节点的运行方式(podman + systemd)和宿主机更新器见 [`../../docs/deployment.md`](../../docs/deployment.md);Probe 也可以用 OCI 镜像 `registry.oeo.one/moshiro/moshiro-probe:<version>`,容器默认读取 `/etc/moshiro/probe.json`,命名 volume 保存身份配置和缓存,镜像以 UID/GID 1654 运行。
## 构建
@@ -21,33 +21,33 @@ Probe 使用面板生成的 `probe.json` 读取自身身份、面板地址(`se
```sh
cd backend
dotnet build probe/src/Monitor.Probe -c Release
dotnet build probe/src/MoShiro.Probe -c Release
```
发布 NativeAOT 程序需要对应平台的 C/C++ 工具链;`<RID>` 应与构建环境和目标平台匹配:
```sh
dotnet publish probe/src/Monitor.Probe -c Release -r <RID> -p:PublishAot=true
dotnet publish probe/src/MoShiro.Probe -c Release -r <RID> -p:PublishAot=true
```
也可在仓库根目录运行 `bash scripts/publish.sh probe <RID>`,再使用 `scripts/package.sh probe <RID>` 生成部署包。支持平台和安装流程见 [`../../docs/deployment.md`](../../docs/deployment.md)。
## 配置与运行
在管理端创建 Probe 并下载一次性身份配置,将文件保存为目标机上的 `/etc/monitor/probe.json`。随后运行:
在管理端创建 Probe 并下载一次性身份配置,将文件保存为目标机上的 `/etc/moshiro/probe.json`。随后运行:
```sh
monitor-probe --config /etc/monitor/probe.json
moshiro-probe --config /etc/moshiro/probe.json
```
不传 `--config` 时默认读取 `/etc/monitor/probe.json`。配置中的 `limits` 是本地硬上限,Backend 无法远程放宽;路由器等低性能设备可调低测试码率和包速率,避免 Probe 自身成为测量瓶颈。部署时可使用仓库的 `packaging/` 文件和安装脚本。
不传 `--config` 时默认读取 `/etc/moshiro/probe.json`。配置中的 `limits` 是本地硬上限,Backend 无法远程放宽;路由器等低性能设备可调低测试码率和包速率,避免 Probe 自身成为测量瓶颈。部署时可使用仓库的 `packaging/` 文件和安装脚本。
也可使用 OCI 镜像 `registry.oeo.one/monitor/monitor-probe:<version>`,自动选择 Linux amd64 或 arm64 镜像。容器默认读取 `/etc/monitor/probe.json`,并在同一目录维护 `agents.json` 缓存,因此挂载目录必须允许 UID/GID 1654 读写。建议使用宿主网络,并按 ICMP 测量需求添加 `NET_RAW` 能力。
也可使用 OCI 镜像 `registry.oeo.one/moshiro/moshiro-probe:<version>`,自动选择 Linux amd64 或 arm64 镜像。容器默认读取 `/etc/moshiro/probe.json`,并在同一目录维护 `agents.json` 缓存,因此挂载目录必须允许 UID/GID 1654 读写。建议使用宿主网络,并按 ICMP 测量需求添加 `NET_RAW` 能力。
## 设计说明
- Probe 只执行编译进程序的测量模块。它不落盘测量数据,也不能执行命令或自行更新(更新由宿主机上的更新器完成,Probe 只写清单和请求文件)。
- 测量模块由 `backend/shared/Monitor.Measurement/` 与 Agent 共用;Probe 负责发起连接、调度任务和记录本端数据。
- 测量模块由 `backend/shared/MoShiro.Measurement/` 与 Agent 共用;Probe 负责发起连接、调度任务和记录本端数据。
- 测量模型和协议细节见 [`../../docs/measurement.md`](../../docs/measurement.md) 与 [`../../docs/protocol.md`](../../docs/protocol.md);整体架构见 [`../../docs/architecture.md`](../../docs/architecture.md)。
Docker 与 rootless/rootful Podman 共用 `packaging/compose/probe/compose.yaml`,配置、身份和缓存放在命名 volume 中,无需宿主配置目录、keep-id 或手动 chown。首次导入和自动更新见部署文档。
@@ -6,10 +6,10 @@ using Fleet.Core;
using Fleet.Identity;
using Fleet.Link;
using Fleet.Rpc;
using Monitor.Measurement;
using Monitor.Protocol;
using MoShiro.Measurement;
using MoShiro.Protocol;
namespace Monitor.Probe;
namespace MoShiro.Probe;
/// <summary>所有连接共用的东西。</summary>
public sealed record ConnectionEnv(NodeIdentity Identity, string ProbeId, ProbeLimits Limits, AgentDirectory Directory, MonoClock Clock, TimeProvider Time, INodeLog Log);
@@ -154,7 +154,7 @@ public sealed class AgentConnection
for (int i = 0; i < rounds; i++)
{
long sent = _env.Clock.NowUs();
Result<ClockResult> reply = await rpc.RequestAsync(MonitorMethods.Clock, new ClockParams { ProbeMonoUs = sent }, MonitorJsonContext.Default.ClockParams, MonitorJsonContext.Default.ClockResult, ct);
Result<ClockResult> reply = await rpc.RequestAsync(MoShiroMethods.Clock, new ClockParams { ProbeMonoUs = sent }, MoShiroJsonContext.Default.ClockParams, MoShiroJsonContext.Default.ClockResult, ct);
if (reply.TryGet(out ClockResult? value, out _))
{
clock.Add(sent, value.AgentMonoUs, _env.Clock.NowUs());
@@ -189,7 +189,7 @@ public sealed class AgentConnection
var result = new StateSyncResult { AppliedVersion = sync.Version };
if (sync.Type == LineTaskSet.StateType)
{
LineTaskSet? set = sync.Doc.Deserialize(MonitorJsonContext.Default.LineTaskSet);
LineTaskSet? set = sync.Doc.Deserialize(MoShiroJsonContext.Default.LineTaskSet);
if (set is null)
{
return Result<StateSyncResult>.Fail(FleetErrors.InvalidParams, "line_task_set could not be parsed");
@@ -201,18 +201,18 @@ public sealed class AgentConnection
return Result<StateSyncResult>.Ok(result);
})
.On(
MonitorMethods.TestPhase,
MonitorJsonContext.Default.TestPhaseStartParams,
MonitorJsonContext.Default.TestPhaseStartResult,
MoShiroMethods.TestPhase,
MoShiroJsonContext.Default.TestPhaseStartParams,
MoShiroJsonContext.Default.TestPhaseStartResult,
(session, start, _) => ValueTask.FromResult(Result<TestPhaseStartResult>.Ok(new TestPhaseStartResult { Accepted = session.StartPhase(start) })))
.On(
MonitorMethods.TestPhaseEnd,
MonitorJsonContext.Default.TestPhaseEndParams,
MonitorJsonContext.Default.TestPhaseEndResult,
MoShiroMethods.TestPhaseEnd,
MoShiroJsonContext.Default.TestPhaseEndParams,
MoShiroJsonContext.Default.TestPhaseEndResult,
async (session, end, ct) => Result<TestPhaseEndResult>.Ok(new TestPhaseEndResult { Fragments = await session.EndPhaseAsync(end, ct) }))
.OnNotify(
MonitorMethods.TestAbort,
MonitorJsonContext.Default.TestAbortParams,
MoShiroMethods.TestAbort,
MoShiroJsonContext.Default.TestAbortParams,
async (session, abort, _) => await session.AbortAsync(abort.TestId))
.OnNotify(
FleetMethods.OutboxAck,
@@ -1,9 +1,9 @@
using System.Text.Json;
using Fleet.Core;
using Fleet.Hosting;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Probe;
namespace MoShiro.Probe;
/// <summary>
/// Probe 要连接的 Agent 列表:由面板作为期望状态(<c>probe_agents</c>)直接下发;面板连不上时用上一次保存的 <c>agents.json</c>。
@@ -1,13 +1,13 @@
using System.Text.Json;
using Fleet.Core;
using Fleet.Link;
using Monitor.Measurement;
using Monitor.Protocol;
using MoShiro.Measurement;
using MoShiro.Protocol;
namespace Monitor.Probe;
namespace MoShiro.Probe;
/// <summary>
/// 估计 Agent 单调时钟 − Probe 单调时钟:每次 <c>monitor.clock</c> 往返取中点,在最近 8 次里取往返最短的一次
/// 估计 Agent 单调时钟 − Probe 单调时钟:每次 <c>moshiro.clock</c> 往返取中点,在最近 8 次里取往返最短的一次
/// (往返越短,对称性假设的误差越小)。Probe 的列块头里带着这个偏移,Backend 据此对齐两端的时间戳。
/// </summary>
public sealed class ClockEstimator
@@ -82,6 +82,6 @@ public sealed class FragmentShipper : IFragmentSink
}
}
_outbox.Enqueue(MonitorChannels.ColumnBlock, header.ChunkId.Value, RawChunkCodec.Encode(new RawChunk(header, streams)));
_outbox.Enqueue(MoShiroChannels.ColumnBlock, header.ChunkId.Value, RawChunkCodec.Encode(new RawChunk(header, streams)));
}
}
@@ -4,8 +4,8 @@
Condition="'$(UseAotAnywhere)' == 'true'" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<AssemblyName>monitor-agent</AssemblyName>
<RootNamespace>Monitor.Agent</RootNamespace>
<AssemblyName>moshiro-probe</AssemblyName>
<RootNamespace>MoShiro.Probe</RootNamespace>
<IsAotCompatible>true</IsAotCompatible>
<PublishAot>true</PublishAot>
<PublishTrimmed>true</PublishTrimmed>
@@ -15,10 +15,10 @@
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="Monitor.Backend.Tests" />
<InternalsVisibleTo Include="MoShiro.Backend.Tests" />
<ProjectReference Include="..\..\..\fleet\src\Fleet.Link\Fleet.Link.csproj" />
<ProjectReference Include="..\..\..\fleet\src\Fleet.Hosting\Fleet.Hosting.csproj" />
<ProjectReference Include="..\..\..\shared\Monitor.Measurement\Monitor.Measurement.csproj" />
<ProjectReference Include="..\..\..\shared\MoShiro.Measurement\MoShiro.Measurement.csproj" />
</ItemGroup>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
<Import Project="Sdk.targets" Sdk="StuDev.AotAnywhere"
@@ -4,10 +4,10 @@ using System.Text.Json;
using Fleet.Identity;
using Fleet.Link;
using Fleet.Rpc;
using Monitor.Measurement;
using Monitor.Protocol;
using MoShiro.Measurement;
using MoShiro.Protocol;
namespace Monitor.Probe;
namespace MoShiro.Probe;
/// <summary>
/// Probe 的组装点:到面板的长连接(身份、Agent 列表、状态)+ 每个 Agent 一个测量连接(认证、重连、角色)。没有全局状态,测试可以在同一进程里启动多个。
@@ -15,7 +15,7 @@ namespace Monitor.Probe;
/// </summary>
public sealed class ProbeApp : IDisposable
{
public const string Version = Monitor.Protocol.BuildInfo.Version;
public const string Version = MoShiro.Protocol.BuildInfo.Version;
private readonly ConnectionEnv _env;
private readonly Dictionary<string, Running> _connections = [];
@@ -38,25 +38,25 @@ public sealed class ProbeApp : IDisposable
Identity = identity,
NodeId = new NodeId(file.ProbeId),
NodeVersion = Version,
Capabilities = [MonitorCapabilities.Base],
HeartbeatExtra = () => JsonSerializer.SerializeToElement(new ProbeHeartbeatExtra { Update = update.ReadStatus() }, MonitorJsonContext.Default.ProbeHeartbeatExtra),
HelloMeta = () => JsonSerializer.SerializeToElement(new ProbeHelloMeta { Platform = PlatformDetector.Detect() }, MonitorJsonContext.Default.ProbeHelloMeta),
Capabilities = [MoShiroCapabilities.Base],
HeartbeatExtra = () => JsonSerializer.SerializeToElement(new ProbeHeartbeatExtra { Update = update.ReadStatus() }, MoShiroJsonContext.Default.ProbeHeartbeatExtra),
HelloMeta = () => JsonSerializer.SerializeToElement(new ProbeHelloMeta { Platform = PlatformDetector.Detect() }, MoShiroJsonContext.Default.ProbeHelloMeta),
};
var list = new StateHandler<ProbeAgentList>(
ProbeAgentList.StateType,
MonitorJsonContext.Default.ProbeAgentList,
MoShiroJsonContext.Default.ProbeAgentList,
(version, doc, _) =>
{
directory.Apply(doc);
return ValueTask.FromResult(new StateSyncResult { AppliedVersion = version });
});
var router = new RpcRouter<LinkContext>()
.OnNotify(MonitorMethods.UpdateOffer, MonitorJsonContext.Default.UpdateOfferParams, (_, offer, _) =>
.OnNotify(MoShiroMethods.UpdateOffer, MoShiroJsonContext.Default.UpdateOfferParams, (_, offer, _) =>
{
update.WriteOffer(offer);
return ValueTask.CompletedTask;
})
.OnNotify(MonitorMethods.UpdateApply, MonitorJsonContext.Default.UpdateOfferParams, (_, _, _) =>
.OnNotify(MoShiroMethods.UpdateApply, MoShiroJsonContext.Default.UpdateOfferParams, (_, _, _) =>
{
update.RequestApply();
return ValueTask.CompletedTask;
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
namespace Monitor.Probe;
namespace MoShiro.Probe;
/// <summary>
/// <c>probe.json</c>:面板 <c>POST /api/admin/probe/identity</c> 生成的文件(只能下载一次)。
@@ -1,6 +1,6 @@
using Fleet.Core;
using Fleet.Hosting;
using Monitor.Probe;
using MoShiro.Probe;
TimeProvider time = TimeProvider.System;
INodeLog log = new ConsoleNodeLog(time);
@@ -11,7 +11,7 @@ if (PlatformCheck.VerifyCrypto() is { } problem)
return 1;
}
string configPath = "/etc/monitor/probe.json";
string configPath = "/etc/moshiro/probe.json";
for (int i = 0; i < args.Length - 1; i++)
{
if (args[i] == "--config")
@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit.v3" />
<ProjectReference Include="..\..\src\Monitor.Probe\Monitor.Probe.csproj" />
<ProjectReference Include="..\..\src\MoShiro.Probe\MoShiro.Probe.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
@@ -1,8 +1,8 @@
using Fleet.Core;
using Fleet.Hosting;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Probe.Tests;
namespace MoShiro.Probe.Tests;
public sealed class ProbeTests
{
@@ -1,7 +1,7 @@
using System.Net;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Measurement;
namespace MoShiro.Measurement;
/// <summary>积木的公共部分:环境、参数、把行切成列块交出去。</summary>
public abstract class BlockBase : IBlock
@@ -1,6 +1,6 @@
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Measurement;
namespace MoShiro.Measurement;
/// <summary>
/// 一种采集方法(Kind)的两个角色:拨号方(Probe)和监听方(Agent),以及它能不能作为目标任务(Agent 自己探测一个地址,没有对端)运行。
@@ -1,7 +1,7 @@
using System.Net.Sockets;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Measurement;
namespace MoShiro.Measurement;
/// <summary>批量传输的收发循环(两端共用)。</summary>
internal static class BulkIo
@@ -1,8 +1,8 @@
using System.Net;
using System.Net.Sockets;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Measurement;
namespace MoShiro.Measurement;
/// <summary>
/// 回显积木的行表(拨号方,行的主导方):发出时记 size、t1,回复到达时按序号回填 t4,到点把已经稳定的行切成列块。
@@ -1,9 +1,9 @@
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Security.Authentication;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Measurement;
namespace MoShiro.Measurement;
/// <summary>什么也不做的监听方:connect 的另一端就是 TCP 监听器本身(接受连接、认证失败就关闭),不需要积木,但需要占一个流编号。</summary>
public sealed class IdleListener : BlockBase
@@ -1,8 +1,8 @@
using System.Diagnostics;
using System.Net;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Measurement;
namespace MoShiro.Measurement;
/// <summary>
/// 单向数据流(UDP train),监听方(Agent,行的主导方):按声明的码率均匀发包,发出的每个包 (seq, size, t_send) 记在按序号的记录里,
@@ -1,9 +1,9 @@
using System.Net;
using System.Text.Json;
using Fleet.Core;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Measurement;
namespace MoShiro.Measurement;
/// <summary>
/// 数据面会话:<c>session_id</c> 和密钥(由点对点握手里的 ECDH 派生)。两端的数据面包都用它认证。
@@ -1,4 +1,4 @@
namespace Monitor.Measurement;
namespace MoShiro.Measurement;
/// <summary>节点的单调时钟(微秒):t1…t4、t_send、t_recv 都用它;Probe 和 Agent 各自一个,块头里的时钟锚点把它对应到 UTC。</summary>
public sealed class MonoClock
@@ -1,6 +1,6 @@
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Measurement;
namespace MoShiro.Measurement;
/// <summary>
/// 一侧记录的行:只含这一侧拥有的列(见 <see cref="StreamSpec.ColumnsOf"/>),按连接键(通常是 <c>seq</c>)索引的环形缓冲,
@@ -1,4 +1,4 @@
namespace Monitor.Measurement;
namespace MoShiro.Measurement;
/// <summary>一个运行中的积木:自己的取消令牌和后台任务。Probe 和 Agent 共用。</summary>
public sealed class RunningBlock : IDisposable
@@ -1,8 +1,8 @@
using System.Net;
using System.Text.Json;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Measurement;
namespace MoShiro.Measurement;
/// <summary>节点的本地硬上限(写在本机配置里,Backend 无法修改)。</summary>
public sealed class NodeLimits
@@ -1,8 +1,8 @@
using System.Text.Json;
using Fleet.Core;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Measurement;
namespace MoShiro.Measurement;
/// <summary>Agent 单调时钟(微秒)与 UTC(毫秒)的对应关系:块头里的时钟锚点。</summary>
public readonly record struct ClockAnchor(long AgentMonoUs, long UtcMs);
@@ -1,8 +1,8 @@
using System.Text.Json;
using Fleet.Core;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Measurement;
namespace MoShiro.Measurement;
/// <summary>Agent 一侧为合并保留的按键记录(由正在运行的积木提供)。</summary>
public interface IRecordSource
@@ -29,7 +29,7 @@ public static class FragmentMerger
StreamSpec? streamSpec = spec.FindStream(stream.Name);
if (streamSpec is null)
{
return Result<IReadOnlyList<RawStream>>.Fail(MonitorErrors.ValidationFailed, $"stream {stream.Name} is not declared by {spec.Id}");
return Result<IReadOnlyList<RawStream>>.Fail(MoShiroErrors.ValidationFailed, $"stream {stream.Name} is not declared by {spec.Id}");
}
Result<RawStream> merged = Join(streamSpec, stream, fragment.Header, records.Records(streamId, stream.Name));
@@ -71,7 +71,7 @@ public static class FragmentMerger
RawColumn? key = probe.Find(spec.Key);
if (key is null || probeColumns.Any(c => probe.Find(c.Name) is null))
{
return Result<RawStream>.Fail(MonitorErrors.ValidationFailed, $"a fragment of stream {spec.Name} needs the columns {string.Join(", ", new[] { spec.Key }.Concat(probeColumns.Select(c => c.Name)))}");
return Result<RawStream>.Fail(MoShiroErrors.ValidationFailed, $"a fragment of stream {spec.Name} needs the columns {string.Join(", ", new[] { spec.Key }.Concat(probeColumns.Select(c => c.Name)))}");
}
return spec.Driver == Side.Probe ? JoinOnProbeRows(spec, probe, key, agent) : JoinOnAgentRows(spec, probe, key, header, agent);
@@ -118,7 +118,7 @@ public static class FragmentMerger
{
if (!TryGetRange(header, out uint from, out uint to))
{
return Result<RawStream>.Fail(MonitorErrors.ValidationFailed, $"a fragment of stream {spec.Name} must declare its sequence range");
return Result<RawStream>.Fail(MoShiroErrors.ValidationFailed, $"a fragment of stream {spec.Name} must declare its sequence range");
}
// 行取自 Agent 的记录:范围内发出过的每一个包都有一行,Probe 没有报告到达的就是丢失
@@ -3,8 +3,8 @@
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="Monitor.Measurement.Tests" />
<InternalsVisibleTo Include="MoShiro.Measurement.Tests" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)../../BannedSymbols.Node.txt" />
<ProjectReference Include="..\Monitor.Protocol\Monitor.Protocol.csproj" />
<ProjectReference Include="..\MoShiro.Protocol\MoShiro.Protocol.csproj" />
</ItemGroup>
</Project>
@@ -5,9 +5,9 @@ using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Measurement;
namespace MoShiro.Measurement;
/// <summary>拨号方的一条数据面 TCP 连接(已经完成 TLS 和首帧认证)。</summary>
public sealed class TcpLink : IDisposable
@@ -1,8 +1,8 @@
using System.Buffers.Binary;
using System.Net.Sockets;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Measurement;
namespace MoShiro.Measurement;
/// <summary>内核对一条 TCP 连接的采样(Linux <c>TCP_INFO</c>)。</summary>
public readonly record struct TcpInfoSample(long SrttUs, long RttVarUs, long Cwnd, long TotalRetrans, long SegsOut, long BytesAcked);
@@ -2,9 +2,9 @@ using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using Fleet.Core;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Measurement;
namespace MoShiro.Measurement;
/// <summary>一个通过校验的 UDP 包,交给按 <c>stream_id</c> 登记的处理函数。处理函数在接收循环里同步执行,不能阻塞。</summary>
public readonly ref struct UdpDelivery
@@ -1,7 +1,7 @@
using System.Runtime.InteropServices;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Measurement;
namespace MoShiro.Measurement;
/// <summary>
/// 读出本机的系统名和架构,只用于在面板上展示。故意只取粗粒度的信息:
@@ -1,12 +1,12 @@
using System.Text;
using System.Text.Json;
using Fleet.Core;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Measurement;
namespace MoShiro.Measurement;
/// <summary>
/// 节点进程和宿主机更新器之间的文件交换(容器里 <c>/etc/monitor</c>,也就是数据目录):
/// 节点进程和宿主机更新器之间的文件交换(容器里 <c>/etc/moshiro</c>,也就是数据目录):
/// 节点把面板发来的清单和签名写进 <c>update/</c>,把「更新」请求写成 <c>update/apply.request</c>;更新器验签并更新,状态由宿主更新器写在只读挂载的 <c>update-status/status.json</c>。
/// 节点进程不执行任何命令,也不信任清单的内容(验签在宿主机上)。没有安装更新器时目录写不了或没人读,调用只是返回 false。
/// </summary>
@@ -88,7 +88,7 @@ public sealed class UpdateBridge
return null;
}
return JsonSerializer.Deserialize(File.ReadAllBytes(_statusFile), MonitorJsonContext.Default.UpdateStatus);
return JsonSerializer.Deserialize(File.ReadAllBytes(_statusFile), MoShiroJsonContext.Default.UpdateStatus);
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or JsonException)
{
@@ -1,4 +1,4 @@
namespace Monitor.Protocol;
namespace MoShiro.Protocol;
[Flags]
public enum Scope
@@ -1,4 +1,4 @@
namespace Monitor.Protocol;
namespace MoShiro.Protocol;
/// <summary>
/// Kind 目录。新增一种采集方法 = 在这里加一个静态字段并放进 <see cref="All"/>;
@@ -1,7 +1,7 @@
using System.Globalization;
using System.Text.Json;
namespace Monitor.Protocol;
namespace MoShiro.Protocol;
public enum ParamType
{
@@ -1,7 +1,7 @@
using System.Text.Json.Serialization;
using Fleet.Core;
namespace Monitor.Protocol;
namespace MoShiro.Protocol;
[JsonConverter(typeof(ProbeIdJsonConverter))]
public readonly record struct ProbeId(string Value)
@@ -7,7 +7,7 @@
<ProjectReference Include="..\..\fleet\src\Fleet.Rpc\Fleet.Rpc.csproj" />
</ItemGroup>
<!--
构建信息:版本来自 $(Version),tag、提交、构建时间来自环境变量 MONITOR_TAG / MONITOR_COMMIT / MONITOR_BUILT_AT
构建信息:版本来自 $(Version),tag、提交、构建时间来自环境变量 MOSHIRO_TAG / MOSHIRO_COMMIT / MOSHIRO_BUILT_AT
(scripts/publish.sh 和 CI 会设置;本地开发不设置就是空,这样本地构建是可重复的、不会每次都重新编译)。
生成一个带常量的源文件,NativeAOT 下不用反射。
-->
@@ -16,14 +16,14 @@
<BuildInfoFile>$(IntermediateOutputPath)BuildInfo.g.cs</BuildInfoFile>
</PropertyGroup>
<ItemGroup>
<BuildInfoLine Include="namespace Monitor.Protocol%3B" />
<BuildInfoLine Include="namespace MoShiro.Protocol%3B" />
<BuildInfoLine Include="/// &lt;summary&gt;这个程序是什么时候、从哪个版本构建出来的(构建时生成)。&lt;/summary&gt;" />
<BuildInfoLine Include="public static class BuildInfo" />
<BuildInfoLine Include="{" />
<BuildInfoLine Include=" public const string Version = &quot;$(Version)&quot;%3B" />
<BuildInfoLine Include=" public const string Tag = &quot;$(MONITOR_TAG)&quot;%3B" />
<BuildInfoLine Include=" public const string Commit = &quot;$(MONITOR_COMMIT)&quot;%3B" />
<BuildInfoLine Include=" public const string BuiltAt = &quot;$(MONITOR_BUILT_AT)&quot;%3B" />
<BuildInfoLine Include=" public const string Tag = &quot;$(MOSHIRO_TAG)&quot;%3B" />
<BuildInfoLine Include=" public const string Commit = &quot;$(MOSHIRO_COMMIT)&quot;%3B" />
<BuildInfoLine Include=" public const string BuiltAt = &quot;$(MOSHIRO_BUILT_AT)&quot;%3B" />
<BuildInfoLine Include="}" />
</ItemGroup>
<WriteLinesToFile File="$(BuildInfoFile)" Lines="@(BuildInfoLine)" Overwrite="true" WriteOnlyWhenDifferent="true" />
@@ -1,9 +1,9 @@
using Fleet.Core;
namespace Monitor.Protocol;
namespace MoShiro.Protocol;
/// <summary>Monitor 的错误码(在 Fleet 错误码之上扩展)。REST、JSON-RPC、OpenAPI 共用同一份。</summary>
public static class MonitorErrors
/// <summary>MoShiro 的错误码(在 Fleet 错误码之上扩展)。REST、JSON-RPC、OpenAPI 共用同一份。</summary>
public static class MoShiroErrors
{
public static readonly ErrorCode ValidationFailed = new("validation_failed");
public static readonly ErrorCode BudgetExceeded = new("budget_exceeded");
@@ -1,4 +1,4 @@
namespace Monitor.Protocol;
namespace MoShiro.Protocol;
public static class ProtocolVersion
{
@@ -25,8 +25,8 @@ public static class ProtocolLimits
public const long MaxLineBudgetBitsPerSecond = 100_000;
}
/// <summary>二进制通道号(0–15 属于 Fleet,Monitor 从 16 开始)。</summary>
public static class MonitorChannels
/// <summary>二进制通道号(0–15 属于 Fleet,MoShiro 从 16 开始)。</summary>
public static class MoShiroChannels
{
/// <summary>Agent → Backend:RawChunk(发件箱)。</summary>
public const byte RawChunk = 16;
@@ -35,9 +35,9 @@ public static class MonitorChannels
public const byte ColumnBlock = 17;
}
public static class MonitorCapabilities
public static class MoShiroCapabilities
{
public const string Base = "monitor.v1";
public const string KindsPrefix = "monitor.kinds:";
public const string BlocksPrefix = "monitor.blocks:";
public const string Base = "moshiro.v1";
public const string KindsPrefix = "moshiro.kinds:";
public const string BlocksPrefix = "moshiro.blocks:";
}
@@ -1,4 +1,4 @@
namespace Monitor.Protocol;
namespace MoShiro.Protocol;
/// <summary>CRC-32C(Castagnoli),RawChunk 的完整性校验。</summary>
public static class Crc32c
@@ -2,7 +2,7 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using Fleet.Core;
namespace Monitor.Protocol;
namespace MoShiro.Protocol;
/// <summary>Agent 单调时钟与 UTC 的锚点,以及 Probe 时钟相对 Agent 时钟的偏移估计。</summary>
public sealed class ChunkClock
@@ -3,18 +3,18 @@ using System.IO.Compression;
using System.Text.Json;
using Fleet.Core;
namespace Monitor.Protocol;
namespace MoShiro.Protocol;
/// <summary>
/// RawChunk 的编解码(Probe、Agent、Backend、离线工具共用同一份代码)。
/// 布局:<c>"MNRC" | u8 container_version | u32 crc32c(压缩数据) | Brotli( u32 header_len | header JSON | 各列数据 )</c>。
/// 布局:<c>"MSRC" | u8 container_version | u32 crc32c(压缩数据) | Brotli( u32 header_len | header JSON | 各列数据 )</c>。
/// 列数据:可空列先写有效位图,再写有效值;plain 为无符号 varint(有符号类型用 zigzag),delta 为与上一个有效值之差的 zigzag varint。
/// </summary>
public static class RawChunkCodec
{
private const int PrefixBytes = 4 + 1 + 4;
private static ReadOnlySpan<byte> Magic => "MNRC"u8;
private static ReadOnlySpan<byte> Magic => "MSRC"u8;
/// <summary>压缩之前的正文:<c>u32 header_len | header JSON | 各列数据</c>。布局变化时黄金字节测试会失败,提醒升级版本。</summary>
public static byte[] EncodeBody(RawChunk chunk)
@@ -40,7 +40,7 @@ public static class RawChunkCodec
header.Streams.Add(streamHeader);
}
byte[] headerJson = JsonSerializer.SerializeToUtf8Bytes(header, MonitorJsonContext.Default.RawChunkHeader);
byte[] headerJson = JsonSerializer.SerializeToUtf8Bytes(header, MoShiroJsonContext.Default.RawChunkHeader);
using var body = new MemoryStream();
Span<byte> length = stackalloc byte[4];
@@ -114,7 +114,7 @@ public static class RawChunkCodec
RawChunkHeader? header;
try
{
header = JsonSerializer.Deserialize(plain.AsSpan(4, (int)headerLength), MonitorJsonContext.Default.RawChunkHeader);
header = JsonSerializer.Deserialize(plain.AsSpan(4, (int)headerLength), MoShiroJsonContext.Default.RawChunkHeader);
}
catch (JsonException)
{
@@ -159,7 +159,7 @@ public static class RawChunkCodec
}
private static Result<RawChunk> Corrupt(string message) =>
Result<RawChunk>.Fail(MonitorErrors.RawCorrupt, message);
Result<RawChunk>.Fail(MoShiroErrors.RawCorrupt, message);
private static byte[]? Inflate(ReadOnlySpan<byte> payload)
{
@@ -1,4 +1,4 @@
namespace Monitor.Protocol;
namespace MoShiro.Protocol;
/// <summary>按 <see cref="StreamSpec"/> 逐行构造一个 <see cref="RawStream"/>(用于测试、模拟器和离线工具,不用于节点热路径)。</summary>
public sealed class StreamBuilder
@@ -2,25 +2,25 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using Fleet.Core;
namespace Monitor.Protocol;
namespace MoShiro.Protocol;
/// <summary>monitor.* 方法名。新增方法 = 在这里定义名称和 params/result,再在组装处加一行 <c>.On(...)</c>。</summary>
public static class MonitorMethods
/// <summary>moshiro.* 方法名。新增方法 = 在这里定义名称和 params/result,再在组装处加一行 <c>.On(...)</c>。</summary>
public static class MoShiroMethods
{
public const string TestRun = "monitor.test.run";
public const string TestCancel = "monitor.test.cancel";
public const string ProbeDisconnect = "monitor.probe.disconnect";
public const string TestStatus = "monitor.test.status";
public const string Event = "monitor.event";
public const string Host = "monitor.host";
public const string UpdateOffer = "monitor.update.offer";
public const string UpdateApply = "monitor.update.apply";
public const string TestRun = "moshiro.test.run";
public const string TestCancel = "moshiro.test.cancel";
public const string ProbeDisconnect = "moshiro.probe.disconnect";
public const string TestStatus = "moshiro.test.status";
public const string Event = "moshiro.event";
public const string Host = "moshiro.host";
public const string UpdateOffer = "moshiro.update.offer";
public const string UpdateApply = "moshiro.update.apply";
// Probe ↔ Agent(点对点会话)
public const string Clock = "monitor.clock";
public const string TestPhase = "monitor.test.phase";
public const string TestPhaseEnd = "monitor.test.phase_end";
public const string TestAbort = "monitor.test.abort";
public const string Clock = "moshiro.clock";
public const string TestPhase = "moshiro.test.phase";
public const string TestPhaseEnd = "moshiro.test.phase_end";
public const string TestAbort = "moshiro.test.abort";
}
public static class AgentEventKinds
@@ -159,7 +159,7 @@ public sealed class ProbeAgentList
public List<AgentEndpoint> Agents { get; set; } = [];
}
/// <summary>注册元数据(Monitor 定义,经 Fleet 的 <c>meta</c> 字段带上来)。</summary>
/// <summary>注册元数据(MoShiro 定义,经 Fleet 的 <c>meta</c> 字段带上来)。</summary>
public sealed class AgentEnrollMeta
{
[JsonPropertyName("probe_tcp_port")]
@@ -220,7 +220,7 @@ public sealed class PlatformInfo
}
/// <summary>
/// 面板给节点的更新清单(<c>monitor.update.offer</c>):<c>manifest</c> 是发布清单的原始文本,<c>signature</c> 是 base64 的签名。
/// 面板给节点的更新清单(<c>moshiro.update.offer</c>):<c>manifest</c> 是发布清单的原始文本,<c>signature</c> 是 base64 的签名。
/// 节点只是把它们原样放进数据目录;验签、拉取、重启都由宿主机上的更新器完成(节点进程不执行任何命令,也不信任清单的内容)。
/// </summary>
public sealed class UpdateOfferParams
@@ -436,7 +436,7 @@ public sealed class LineTaskSet
public List<int> TcpPorts { get; set; } = [];
}
/// <summary><c>monitor.clock</c>:Probe 用它估计与 Agent 单调时钟的偏移(对称往返假设)。</summary>
/// <summary><c>moshiro.clock</c>:Probe 用它估计与 Agent 单调时钟的偏移(对称往返假设)。</summary>
public sealed class ClockParams
{
[JsonPropertyName("probe_mono_us")]
@@ -543,4 +543,4 @@ public static class FragmentKeys
[JsonSerializable(typeof(AgentHelloMeta))]
[JsonSerializable(typeof(AgentHeartbeatExtra))]
[JsonSerializable(typeof(RawChunkHeader))]
public sealed partial class MonitorJsonContext : JsonSerializerContext;
public sealed partial class MoShiroJsonContext : JsonSerializerContext;
@@ -3,7 +3,7 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using Fleet.Core;
namespace Monitor.Protocol;
namespace MoShiro.Protocol;
/// <summary>Task = { id, scope, kind, target, params, schedule, enabled }。参数已经按 ParamSpec 校验并补全默认值。</summary>
public sealed class TaskSpec
@@ -79,9 +79,9 @@ public static class TaskSchedule
{
return minutes >= 60
? Result<int?>.Ok(minutes)
: Result<int?>.Fail(MonitorErrors.ValidationFailed, "every:N requires N >= 60 minutes");
: Result<int?>.Fail(MoShiroErrors.ValidationFailed, "every:N requires N >= 60 minutes");
}
return Result<int?>.Fail(MonitorErrors.ValidationFailed, "schedule must be continuous, once or every:<minutes>");
return Result<int?>.Fail(MoShiroErrors.ValidationFailed, "schedule must be continuous, once or every:<minutes>");
}
}
@@ -2,7 +2,7 @@ using System.Buffers.Binary;
using System.Security.Cryptography;
using Fleet.Core;
namespace Monitor.Protocol;
namespace MoShiro.Protocol;
/// <summary>
/// 数据面的 TCP 连接(Fleet 之外的线格式)。连接都由 Probe 发起;TLS 是可选的(ClientHello 以 0x16 开头,
@@ -1,7 +1,7 @@
using System.Buffers.Binary;
using System.Security.Cryptography;
namespace Monitor.Protocol;
namespace MoShiro.Protocol;
public enum UdpPacketType : byte
{
@@ -1,8 +1,8 @@
using System.Text.Json;
using Fleet.Core;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Measurement.Tests;
namespace MoShiro.Measurement.Tests;
public sealed class MeasurementTests
{
@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit.v3" />
<ProjectReference Include="..\..\Monitor.Measurement\Monitor.Measurement.csproj" />
<ProjectReference Include="..\..\MoShiro.Measurement\MoShiro.Measurement.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
@@ -1,4 +1,4 @@
namespace Monitor.Measurement.Tests;
namespace MoShiro.Measurement.Tests;
public sealed class PlatformDetectorTests
{
@@ -1,8 +1,8 @@
using System.Text.Json;
using Fleet.Core;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Protocol.Tests;
namespace MoShiro.Protocol.Tests;
public sealed class CatalogTests
{
@@ -101,8 +101,8 @@ public sealed class CatalogTests
Tasks = [new TaskSpec { Id = new TaskId("tsk_1"), Kind = "udp_echo", Params = new Dictionary<string, JsonElement> { ["rate_hz"] = Json("10") } }],
Peers = [new PeerSpec { ProbeId = new ProbeId("prb_1"), PublicKey = "AAAA" }],
};
string text = JsonSerializer.Serialize(set, MonitorJsonContext.Default.TaskSet);
TaskSet back = JsonSerializer.Deserialize(text, MonitorJsonContext.Default.TaskSet)!;
string text = JsonSerializer.Serialize(set, MoShiroJsonContext.Default.TaskSet);
TaskSet back = JsonSerializer.Deserialize(text, MoShiroJsonContext.Default.TaskSet)!;
Assert.Contains("\"id\":\"tsk_1\"", text);
Assert.Equal(7, back.Version);
@@ -114,7 +114,7 @@ public sealed class CatalogTests
[Fact]
public void AgentEnrollMeta_ToleratesUnknownFields_AndKeepsDefaults()
{
AgentEnrollMeta meta = JsonSerializer.Deserialize("{\"kinds\":[\"udp_echo\"],\"future\":1}", MonitorJsonContext.Default.AgentEnrollMeta)!;
AgentEnrollMeta meta = JsonSerializer.Deserialize("{\"kinds\":[\"udp_echo\"],\"future\":1}", MoShiroJsonContext.Default.AgentEnrollMeta)!;
Assert.Equal(7443, meta.ProbeTcpPort);
Assert.Equal(["udp_echo"], meta.Kinds);
}
@@ -122,7 +122,7 @@ public sealed class CatalogTests
[Fact]
public void ErrorCodes_AreSnakeCase()
{
foreach (System.Reflection.FieldInfo field in typeof(MonitorErrors).GetFields())
foreach (System.Reflection.FieldInfo field in typeof(MoShiroErrors).GetFields())
{
string value = ((ErrorCode)field.GetValue(null)!).Value;
Assert.Matches("^[a-z]+(_[a-z]+)*$", value);
@@ -5,8 +5,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit.v3" />
<ProjectReference Include="..\..\Monitor.Protocol\Monitor.Protocol.csproj" />
<ProjectReference Include="..\..\..\tests\Monitor.TestSupport\Monitor.TestSupport.csproj" />
<ProjectReference Include="..\..\MoShiro.Protocol\MoShiro.Protocol.csproj" />
<ProjectReference Include="..\..\..\tests\MoShiro.TestSupport\MoShiro.TestSupport.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
@@ -1,10 +1,10 @@
using System.Buffers.Binary;
using System.IO.Compression;
using Fleet.Core;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Protocol.Tests;
namespace MoShiro.Protocol.Tests;
public sealed class RawChunkCodecTests
{
@@ -103,7 +103,7 @@ public sealed class RawChunkCodecTests
byte[] badCrc = (byte[])good.Clone();
badCrc[^1] ^= 0xFF;
Assert.Equal(MonitorErrors.RawCorrupt, RawChunkCodec.Decode(badMagic).Error.Code);
Assert.Equal(MoShiroErrors.RawCorrupt, RawChunkCodec.Decode(badMagic).Error.Code);
Assert.Contains("version", RawChunkCodec.Decode(badVersion).Error.Message);
Assert.Contains("crc", RawChunkCodec.Decode(badCrc).Error.Message);
Assert.False(RawChunkCodec.Decode(good.AsSpan(0, good.Length / 2)).IsSuccess);
@@ -190,7 +190,7 @@ public sealed class RawChunkCodecTests
private static byte[] Wrap(byte[] payload)
{
byte[] result = new byte[9 + payload.Length];
"MNRC"u8.CopyTo(result);
"MSRC"u8.CopyTo(result);
result[4] = ProtocolVersion.Container;
BinaryPrimitives.WriteUInt32LittleEndian(result.AsSpan(5), Crc32c.Compute(payload));
payload.CopyTo(result, 9);
@@ -1,4 +1,4 @@
namespace Monitor.Backend.Analysis;
namespace MoShiro.Backend.Analysis;
public enum AccKind : byte
{
@@ -2,7 +2,7 @@ using System.Buffers.Binary;
using System.Text;
using Fleet.Core;
namespace Monitor.Backend.Analysis;
namespace MoShiro.Backend.Analysis;
/// <summary>一个时间桶里所有指标的累加器:<c>指标 id → 累加器</c>。</summary>
public sealed class MetricSet
@@ -1,4 +1,4 @@
namespace Monitor.Backend.Analysis;
namespace MoShiro.Backend.Analysis;
/// <summary>
/// 固定对数桶直方图:100 µs ~ 30 s,每 2 倍 8 个桶(相对误差 ≤ 4.5%)。
@@ -1,4 +1,4 @@
namespace Monitor.Backend.Analysis;
namespace MoShiro.Backend.Analysis;
/// <summary>
/// 估计两端的时钟偏移和线性漂移:单向时延 x(t) = 偏移 + 漂移·t + 真实的时延变化。
@@ -1,4 +1,4 @@
namespace Monitor.Backend.Analysis;
namespace MoShiro.Backend.Analysis;
/// <summary>变点检测(均值漂移):找到使前后两段均值差最显著的位置,用来判断 QoS 从什么时候开始限速。</summary>
public static class ChangePoint
@@ -1,7 +1,7 @@
using System.Text.Json;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Backend.Analysis;
namespace MoShiro.Backend.Analysis;
/// <summary>算子把观测值交给这里;窗口评估器负责按时间分桶。</summary>
public interface IObservationSink
@@ -1,8 +1,8 @@
using System.Security.Cryptography;
using System.Text;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Backend.Analysis;
namespace MoShiro.Backend.Analysis;
public enum Unit
{
@@ -1,7 +1,7 @@
using System.Text.Json;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Backend.Analysis;
namespace MoShiro.Backend.Analysis;
public sealed class TestStepResult
{
@@ -1,6 +1,6 @@
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Backend.Analysis;
namespace MoShiro.Backend.Analysis;
/// <summary>
/// 把原始块按时间分桶算成累加器。流式路径(1s 桶)和持久路径(分钟窗口)调用同一份代码,
@@ -1,4 +1,4 @@
namespace Monitor.Backend.Analysis;
namespace MoShiro.Backend.Analysis;
/// <summary>线路基础指标(udp_echo / icmp_echo 的回显流)。新增指标 = 加一行声明。</summary>
public static class LinkMetrics
@@ -1,4 +1,4 @@
namespace Monitor.Backend.Analysis;
namespace MoShiro.Backend.Analysis;
public sealed record PresetDef(string Id, string Label, IReadOnlyList<string> MetricIds);
@@ -1,4 +1,4 @@
namespace Monitor.Backend.Analysis;
namespace MoShiro.Backend.Analysis;
/// <summary>连接建立、长连接、UDP 压测(udp_train)的指标。</summary>
public static class SessionMetrics
@@ -1,4 +1,4 @@
namespace Monitor.Backend.Analysis;
namespace MoShiro.Backend.Analysis;
/// <summary>TCP 指标:回显的 RTT 与卡顿、内核采样的重传、批量传输的吞吐。</summary>
public static class TcpMetrics
@@ -3,6 +3,6 @@
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\shared\Monitor.Protocol\Monitor.Protocol.csproj" />
<ProjectReference Include="..\..\shared\MoShiro.Protocol\MoShiro.Protocol.csproj" />
</ItemGroup>
</Project>
@@ -1,4 +1,4 @@
namespace Monitor.Backend.Analysis;
namespace MoShiro.Backend.Analysis;
/// <summary>线路基础质量:RTT、丢包、抖动、连续丢包。</summary>
public sealed class BaseModel : ServiceModelBase
@@ -1,4 +1,4 @@
namespace Monitor.Backend.Analysis;
namespace MoShiro.Backend.Analysis;
/// <summary>模型的输入:一个时间段内的导出值(例如 <c>rtt.p95</c>)。没有数据的指标不存在。</summary>
public sealed class MetricView
@@ -1,6 +1,6 @@
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Backend.Analysis;
namespace MoShiro.Backend.Analysis;
public enum Dir
{
@@ -1,4 +1,4 @@
namespace Monitor.Backend.Analysis;
namespace MoShiro.Backend.Analysis;
/// <summary>算子工厂:<c>Ops.Rtt</c>、<c>Ops.Loss(Dir.Up)</c> 等,供 <c>MetricDef</c> 声明使用。</summary>
public static class Ops
@@ -1,6 +1,6 @@
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.Backend.Analysis;
namespace MoShiro.Backend.Analysis;
/// <summary>tcp_info 流的辅助:<c>side</c> 为 0 表示 Probe 一侧记录(Probe 时钟),否则为 Agent 一侧。</summary>
internal static class TcpInfoTime
@@ -2,10 +2,10 @@ using Fleet.Core;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Monitor.Backend.Application;
using Monitor.Protocol;
using MoShiro.Backend.Application;
using MoShiro.Protocol;
namespace Monitor.Backend.Api;
namespace MoShiro.Backend.Api;
/// <summary>Agent 管理:列表、修改、审批、启停、接管、删除;加入令牌;Probe 身份。</summary>
public static class AgentEndpoints
@@ -77,7 +77,7 @@ public static class AgentEndpoints
AgentView? view = await agents.GetAsync(new NodeId(id), ct);
if (view is null)
{
return Problems.Create(http, MonitorErrors.AgentNotFound, "agent not found");
return Problems.Create(http, MoShiroErrors.AgentNotFound, "agent not found");
}
Concurrency.SetETag(http, view.Agent.RowVersion);
@@ -166,7 +166,7 @@ public static class AgentEndpoints
private static async Task<IResult> GetProbeAsync(HttpContext http, ProbeService probes, string id, CancellationToken ct) =>
await probes.GetAsync(id, ct) is { } view
? TypedResults.Json(Mappers.ToDto(view), ApiJsonContext.Default.ProbeDto)
: Problems.Create(http, MonitorErrors.ProbeNotFound, "probe not found");
: Problems.Create(http, MoShiroErrors.ProbeNotFound, "probe not found");
private static async Task<IResult> PatchProbeAsync(HttpContext http, ProbeService probes, PatchProbeRequest request, string id, CancellationToken ct)
{
@@ -211,7 +211,7 @@ public static class AgentEndpoints
return Problems.From(http, error);
}
return dropped ? TypedResults.NoContent() : Problems.Create(http, MonitorErrors.ProbeOffline, "this probe has no session with this agent");
return dropped ? TypedResults.NoContent() : Problems.Create(http, MoShiroErrors.ProbeOffline, "this probe has no session with this agent");
}
/// <summary>Probe 要连的面板地址:配置的公开地址,没有就用这次请求的地址。</summary>
@@ -2,10 +2,10 @@ using Fleet.Core;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Monitor.Backend.Application;
using Monitor.Protocol;
using MoShiro.Backend.Application;
using MoShiro.Protocol;
namespace Monitor.Backend.Api;
namespace MoShiro.Backend.Api;
/// <summary>登录、退出、当前身份。登录接口限流,失败一律返回 401,不区分“用户不存在”和“密码错误”。</summary>
public static class AuthEndpoints
@@ -35,7 +35,7 @@ public static class AuthEndpoints
string source = http.Connection.RemoteIpAddress?.ToString() ?? "unknown";
if (!limiter.TryAcquire(source, out int retryAfter))
{
return Problems.RetryAfter(http, new FleetError(MonitorErrors.TooManyRequests, "too many sign-in attempts"), retryAfter);
return Problems.RetryAfter(http, new FleetError(MoShiroErrors.TooManyRequests, "too many sign-in attempts"), retryAfter);
}
Result<LoginResult> result = await auth.LoginAsync(request.Username ?? "", request.Password ?? "", http.RequestAborted);
@@ -2,10 +2,10 @@ using Fleet.Core;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Monitor.Backend.Application;
using Monitor.Protocol;
using MoShiro.Backend.Application;
using MoShiro.Protocol;
namespace Monitor.Backend.Api;
namespace MoShiro.Backend.Api;
/// <summary>构建信息与更新:后端和每个节点的版本、tag、构建时间,CI 签名的发布清单,以及「更新这个节点」的请求。更新本身由节点宿主机上的更新器验签并执行。</summary>
public static class BuildEndpoints
@@ -54,7 +54,7 @@ public static class BuildEndpoints
}
private static async Task<IResult> RequestUpdateAsync(HttpContext http, ReleaseService releases, string id, CancellationToken ct) =>
await releases.RequestUpdateAsync(new NodeId(id), ct) ? TypedResults.NoContent() : Problems.Create(http, MonitorErrors.AgentOffline, "the node is not connected");
await releases.RequestUpdateAsync(new NodeId(id), ct) ? TypedResults.NoContent() : Problems.Create(http, MoShiroErrors.AgentOffline, "the node is not connected");
private static ReleaseDto ToDto(ReleaseOffer offer) => new(offer.Component, offer.Seq, offer.Tag, offer.Version, offer.Digest);
@@ -3,10 +3,10 @@ using Fleet.Core;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Monitor.Backend.Application;
using Monitor.Protocol;
using MoShiro.Backend.Application;
using MoShiro.Protocol;
namespace Monitor.Backend.Api;
namespace MoShiro.Backend.Api;
/// <summary>原始数据检索与下载、事件、指标重算进度。</summary>
public static class DataEndpoints
@@ -18,8 +18,8 @@ public static class DataEndpoints
group.MapGet("/raw/chunks", ListChunksAsync)
.Api("raw_chunks_list", "Raw chunk metadata", Tag).Returns<PageDto<RawChunkMetaDto>>().ProducesProblems(400, 401, 403);
group.MapGet("/raw/chunks/{chunk_id}", DownloadChunkAsync)
.Api("raw_chunks_get", "Download a raw chunk (application/vnd.monitor.rawchunk)", Tag)
.Produces(StatusCodes.Status200OK, contentType: "application/vnd.monitor.rawchunk").ProducesProblems(401, 403, 404, 410);
.Api("raw_chunks_get", "Download a raw chunk (application/vnd.moshiro.rawchunk)", Tag)
.Produces(StatusCodes.Status200OK, contentType: "application/vnd.moshiro.rawchunk").ProducesProblems(401, 403, 404, 410);
group.MapGet("/events", ListEventsAsync)
.Api("events_list", "Agent events", Tag).Returns<PageDto<EventDto>>().ProducesProblems(400, 401, 403);
group.MapGet("/analysis/recompute", RecomputeAsync)
@@ -57,7 +57,7 @@ public static class DataEndpoints
{
if (!Guid.TryParse(chunk_id, out Guid id))
{
return Problems.Create(http, MonitorErrors.ChunkNotFound, "chunk not found");
return Problems.Create(http, MoShiroErrors.ChunkNotFound, "chunk not found");
}
Result<(RawChunkMeta Meta, byte[] Bytes)> result = await raw.GetAsync(new ChunkId(id), http.RequestAborted);
@@ -67,7 +67,7 @@ public static class DataEndpoints
}
http.Response.Headers.CacheControl = "private, max-age=3600";
return TypedResults.File(found.Bytes, "application/vnd.monitor.rawchunk", chunk_id + ".mnrc");
return TypedResults.File(found.Bytes, "application/vnd.moshiro.rawchunk", chunk_id + ".msrc");
}
private static async Task<IResult> ListEventsAsync(
Loaded 100 of 309 files, more files were not shown because too many files have changed in this diff. Show more