feat: probe host stats, node cards with a separate lines panel grouped by carrier, Windows host sampling, manual carrier override [CI]
ci/woodpecker/push/main-amd64-build Pipeline was successful

This commit is contained in:
justme committed 2026-10-08 21:56:19 +08:00
1 parent bac1c27e72
commit 88b1a0a9bb
36 files changed
+750 -185

No files matched your search

+40 -1
View File
@@ -22,11 +22,15 @@ public sealed class ProbeApp : IDisposable
private readonly SemaphoreSlim _changed = new(0);
private readonly INodeLog _log;
private readonly FleetLink _link;
private readonly ReportingSection _reporting;
private readonly TimeProvider _time;
/// <param name="identity">注册时由节点自己生成:<c>Identity</c> 用来连面板,<c>PeerIdentity</c> 是另一把独立的密钥,只用来连 Agent。</param>
public ProbeApp(ProbeFile file, LoadedNodeIdentity identity, string configDirectory, TimeProvider time, INodeLog log, RestartController? restart = null)
{
_log = log;
_time = time;
_reporting = file.Reporting;
var trust = TrustAnchor.Load(file.Fleet.IdentityDir, log);
var directory = new AgentDirectory(new ConfigStore(configDirectory), configDirectory, log, trust);
_env = new ConnectionEnv(identity.PeerIdentity, identity.NodeId.Value, file.Limits, directory, new MonoClock(time), time, log);
@@ -105,6 +109,7 @@ public sealed class ProbeApp : IDisposable
}
Task link = _link.RunAsync(ct);
Task host = ReportHostAsync(ct);
try
{
while (!ct.IsCancellationRequested)
@@ -120,7 +125,41 @@ public sealed class ProbeApp : IDisposable
finally
{
await ReconcileAsync([], CancellationToken.None);
await Task.WhenAny(link, Task.Delay(TimeSpan.FromSeconds(5), CancellationToken.None));
await Task.WhenAny(Task.WhenAll(link, host), Task.Delay(TimeSpan.FromSeconds(5), CancellationToken.None));
}
}
/// <summary>每隔几秒把主机资源占用的汇总数字发给面板,和 Agent 一样(没有型号、主机名、网卡名和 IP);链路断开时丢弃,重连之后下一次就补上。</summary>
private async Task ReportHostAsync(CancellationToken ct)
{
if (!_reporting.HostStats)
{
return;
}
var sampler = new HostSampler(_time);
sampler.Sample(); // 第一次只用来建立 CPU 和网速的基线
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(Math.Clamp(_reporting.HostIntervalSeconds, 1, 60)), _time);
try
{
while (await timer.WaitForNextTickAsync(ct))
{
if (_link.CurrentSession is { } session)
{
try
{
await session.NotifyAsync(MoShiroMethods.Host, sampler.Sample(), MoShiroJsonContext.Default.HostSnapshot, CancellationToken.None);
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
_log.Warn($"could not report host status: {ex.Message}");
}
}
}
}
catch (OperationCanceledException)
{
// 停止
}
}
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using MoShiro.Measurement;
using MoShiro.Protocol;
namespace MoShiro.Probe;
@@ -18,6 +19,10 @@ public sealed class ProbeFile
[JsonPropertyName("limits")]
public ProbeLimits Limits { get; set; } = new();
/// <summary>上报主机资源占用,和 Agent 一样(只有汇总数字,没有型号和 IP);默认开,设 <c>host_stats: false</c> 就一个字也不上报。</summary>
[JsonPropertyName("reporting")]
public ReportingSection Reporting { get; set; } = new();
}
public sealed class ProbeFleetSection