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.
103 lines
3.5 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|