Files
MoShiro/backend/src/Monitor.Backend.Application/Agents/AgentRuntime.cs
T
justme 14b0de3544 feat: node info, build info and acrylic web theme
Squash of recent dev work: return_session login, backend switcher, line
detail timeline, overview strips, node platform/country/ISP info with flags
and icons, build info (version, tag, time) behind site.show_build, and the
acrylic display theme.
2026-10-06 21:24:25 +08:00

103 lines
3.5 KiB
C#

using System.Collections.Concurrent;
using Fleet.Core;
using Monitor.Protocol;
namespace Monitor.Backend.Application;
/// <summary>Agent 眼中的一个已连接 Probe(Agent 在事件和心跳里上报)。</summary>
public sealed record ConnectedProbeInfo(string Id, string? Version, string? Remote, long SinceMs, PlatformInfo? Platform = null);
/// <summary>Agent 的运行时状态(只在内存中):连着哪些 Probe、最近的进程内计数、端点变化次数。</summary>
public sealed class AgentRuntimeState
{
private readonly object _gate = new();
private int _endpointChanges;
private IReadOnlyList<ConnectedProbeInfo> _probes = [];
/// <summary>当前连着这个 Agent 的 Probe(每个 Probe 一项)。</summary>
public IReadOnlyList<ConnectedProbeInfo> Probes => Volatile.Read(ref _probes);
public bool ProbeConnected => Probes.Count > 0;
public bool IsConnected(string probeId) => Probes.Any(p => p.Id == probeId);
/// <summary>事件 <c>probe_connected</c>:事件里带着 Probe 的版本和地址;心跳里的完整快照随后会再校正一次。</summary>
public void AddProbe(ConnectedProbeInfo info)
{
lock (_gate)
{
Volatile.Write(ref _probes, [.. _probes.Where(p => p.Id != info.Id), info]);
}
}
public void RemoveProbe(string id)
{
lock (_gate)
{
Volatile.Write(ref _probes, [.. _probes.Where(p => p.Id != id)]);
}
}
/// <summary>心跳里的完整快照:覆盖事件推算出的结果。</summary>
public void ReplaceProbes(IEnumerable<ConnectedProbeInfo> probes)
{
lock (_gate)
{
Volatile.Write(ref _probes, [.. probes.OrderBy(p => p.SinceMs).ThenBy(p => p.Id, StringComparer.Ordinal)]);
}
}
public void ClearProbes() => ReplaceProbes([]);
/// <summary>最新的主机资源占用快照,以及收到它的时间。</summary>
public HostSnapshot? Host { get; set; }
public long HostReceivedAtMs { get; set; }
public AgentHeartbeatExtra? LastHeartbeat { get; set; }
public long LastHeartbeatAtMs { get; set; }
public int EndpointChanges => Volatile.Read(ref _endpointChanges);
public void CountEndpointChange() => Interlocked.Increment(ref _endpointChanges);
}
public sealed class AgentRuntime
{
private readonly ConcurrentDictionary<NodeId, AgentRuntimeState> _states = new();
public AgentRuntimeState Get(NodeId agent) => _states.GetOrAdd(agent, _ => new AgentRuntimeState());
public bool TryGet(NodeId agent, out AgentRuntimeState state) => _states.TryGetValue(agent, out state!);
public void Remove(NodeId agent) => _states.TryRemove(agent, out _);
/// <summary>某个 Probe 现在连着哪些 Agent。</summary>
public IReadOnlyList<(NodeId Agent, ConnectedProbeInfo Probe)> ConnectionsOf(string probeId)
{
var result = new List<(NodeId, ConnectedProbeInfo)>();
foreach ((NodeId agent, AgentRuntimeState state) in _states)
{
foreach (ConnectedProbeInfo probe in state.Probes)
{
if (probe.Id == probeId)
{
result.Add((agent, probe));
}
}
}
return result;
}
/// <summary>Agent 断开时,它对 Probe 会话的认知也随之失效。</summary>
public void MarkOffline(NodeId agent)
{
if (_states.TryGetValue(agent, out AgentRuntimeState? state))
{
state.ClearProbes();
}
}
}