115 lines
5.9 KiB
C#
115 lines
5.9 KiB
C#
using System.Text.Json.Nodes;
|
|
using Monitor.Agent;
|
|
using Monitor.Protocol;
|
|
using Monitor.TestSupport;
|
|
|
|
namespace Monitor.Backend.Tests;
|
|
|
|
public sealed class HostSamplerTests
|
|
{
|
|
private const string Stat1 = "cpu 100 0 100 700 100 0 0 0 0 0\ncpu0 1 1 1 1 1 0 0 0 0 0\n";
|
|
private const string Stat2 = "cpu 200 0 200 800 100 0 0 0 0 0\n"; // 比上一次:总时间 +300,其中空闲 +100
|
|
|
|
private const string Dev1 = "Inter-| Receive | Transmit\n face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed\n lo: 999 1 0 0 0 0 0 0 999 1 0 0 0 0 0 0\n eth0: 1000 10 0 0 0 0 0 0 5000 10 0 0 0 0 0 0\ndocker0: 777 1 0 0 0 0 0 0 777 1 0 0 0 0 0 0\n";
|
|
private const string Dev2 = "Inter-| Receive\n face |bytes\n eth0: 3000 10 0 0 0 0 0 0 9000 10 0 0 0 0 0 0\n";
|
|
|
|
[Fact]
|
|
public void Parsers_ReadTheProcFormats_AndSkipLoopbackAndContainerInterfaces()
|
|
{
|
|
Assert.Equal((1000L, 800L), HostSampler.ParseCpu(Stat1));
|
|
Assert.Equal((8L * 1024 * 1024, 2L * 1024 * 1024), HostSampler.ParseMemory("MemTotal: 8192 kB\nMemFree: 100 kB\nMemAvailable: 6144 kB\n"));
|
|
Assert.Equal((1000L, 5000L), HostSampler.ParseNetwork(Dev1));
|
|
Assert.Equal(3600L, HostSampler.ParseUptime("3600.55 7000.1\n"));
|
|
Assert.Null(HostSampler.ParseNetwork("nothing useful"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Sample_DerivesCpuPercentAndNetworkSpeedFromTheDifferenceBetweenTwoReads()
|
|
{
|
|
var time = new Microsoft.Extensions.Time.Testing.FakeTimeProvider();
|
|
string stat = Stat1;
|
|
string dev = Dev1;
|
|
var sampler = new HostSampler(time, path => path switch { "/proc/stat" => stat, "/proc/net/dev" => dev, "/proc/uptime" => "100.0 1.0", _ => null }, () => (1000, 250));
|
|
HostSnapshot first = sampler.Sample();
|
|
Assert.Null(first.CpuPercent); // 第一次没有基线
|
|
Assert.Null(first.NetRxBps);
|
|
Assert.Equal(250, first.DiskUsed);
|
|
Assert.Equal(100, first.UptimeSeconds);
|
|
|
|
time.Advance(TimeSpan.FromSeconds(2));
|
|
stat = Stat2;
|
|
dev = Dev2;
|
|
HostSnapshot second = sampler.Sample();
|
|
Assert.Equal(66.7, second.CpuPercent); // 总时间 +300、空闲 +100:忙碌 200/300
|
|
Assert.Equal(1000, second.NetRxBps); // (3000 - 1000) / 2 s
|
|
Assert.Equal(2000, second.NetTxBps);
|
|
Assert.Equal(3000, second.NetRxTotal);
|
|
}
|
|
}
|
|
|
|
[Collection("backend")]
|
|
public sealed class HostStatusTests
|
|
{
|
|
private readonly BackendFixture _backend;
|
|
|
|
public HostStatusTests(BackendFixture backend)
|
|
{
|
|
_backend = backend;
|
|
}
|
|
|
|
private static HostSnapshot Snapshot(double cpu) => new()
|
|
{
|
|
CpuPercent = cpu, MemUsed = 2L << 30, MemTotal = 8L << 30, DiskUsed = 10L << 30, DiskTotal = 40L << 30,
|
|
NetRxTotal = 600L << 30, NetTxTotal = 180L << 30, NetRxBps = 2500, NetTxBps = 4800, UptimeSeconds = 3_000_000,
|
|
};
|
|
|
|
[Fact]
|
|
public async Task AgentHostSnapshots_AreVisibleToTheDisplayInRealTime_StoredAsHistory_AndNeverCarryModelsOrAddresses()
|
|
{
|
|
await _backend.SiteMustBePublicAsync();
|
|
await using AgentSimulator agent = await _backend.ConnectAgentAsync();
|
|
string id = agent.NodeId.Value;
|
|
|
|
await using SseClient sse = await SseClient.ConnectAsync(_backend.Anonymous, "/api/public/live?topics=hosts");
|
|
await agent.SendHostAsync(Snapshot(18.5));
|
|
SseEvent live = await sse.WaitForAsync(e => e.Name == "host" && e.Data!["agent_id"]!.GetValue<string>() == id);
|
|
Assert.Equal(18.5, live.Data!["host"]!["cpu_percent"]!.GetValue<double>());
|
|
Assert.True(live.Data["online"]!.GetValue<bool>());
|
|
|
|
// REST:最新值,没有过期
|
|
JsonNode list = await _backend.GetJsonAsync(_backend.Anonymous, "/api/public/hosts");
|
|
JsonNode item = list["items"]!.AsArray().Single(i => i!["agent_id"]!.GetValue<string>() == id)!;
|
|
Assert.False(item["stale"]!.GetValue<bool>());
|
|
Assert.Equal(40L << 30, item["host"]!["disk_total"]!.GetValue<long>());
|
|
|
|
// 展示页能拿到的只有汇总数字:整份响应里没有型号、主机名、地址之类的字段
|
|
string text = list.ToJsonString();
|
|
foreach (string forbidden in new[] { "model", "hostname", "\"ip\"", "address", "remote", "mac", "iface", "public_host" })
|
|
{
|
|
Assert.DoesNotContain(forbidden, text, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
// 线路详情也不泄漏 Probe 的地址和版本
|
|
string line = (await _backend.GetJsonAsync(_backend.Anonymous, $"/api/public/lines/{_backend.Line(id)}")).ToJsonString();
|
|
Assert.DoesNotContain("remote", line, StringComparison.OrdinalIgnoreCase);
|
|
Assert.DoesNotContain("probe_version", line, StringComparison.OrdinalIgnoreCase);
|
|
|
|
// 历史:每分钟一个样本
|
|
JsonNode history = null!;
|
|
await BackendFixture.EventuallyAsync(async () =>
|
|
{
|
|
history = await _backend.GetJsonAsync(_backend.Anonymous, $"/api/public/agents/{id}/host?range=1h");
|
|
return history["values"]!["cpu"]!.AsArray().Any(v => v is not null);
|
|
});
|
|
Assert.Equal(25.0, history["values"]!["mem"]!.AsArray().Last(v => v is not null)!.GetValue<double>());
|
|
Assert.Equal(2500, history["values"]!["rx_bps"]!.AsArray().Last(v => v is not null)!.GetValue<double>());
|
|
|
|
// 隐藏的 Agent 对匿名访客不可见
|
|
await _backend.Admin.PatchAsync($"/api/admin/agents/{id}", BackendFixture.Json(new JsonObject { ["public_visible"] = false }));
|
|
JsonNode hidden = await _backend.GetJsonAsync(_backend.Anonymous, "/api/public/hosts");
|
|
Assert.DoesNotContain(hidden["items"]!.AsArray(), i => i!["agent_id"]!.GetValue<string>() == id);
|
|
using HttpResponseMessage denied = await _backend.Anonymous.GetAsync($"/api/public/agents/{id}/host");
|
|
Assert.Equal(System.Net.HttpStatusCode.NotFound, denied.StatusCode);
|
|
}
|
|
}
|