feat: use Alpine AOT images, static SQLite and Podman host metrics [CI]
This commit is contained in:
1 parent
6e945657c1
commit
103ef27ed3
37 files changed
+719
-309
No files matched your search
@@ -1,13 +0,0 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="5.6.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.12" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.12" />
|
||||
<PackageVersion Include="Microsoft.Extensions.ApiDescription.Server" Version="10.0.12" />
|
||||
<PackageVersion Include="Microsoft.Extensions.TimeProvider.Testing" Version="10.0.0" />
|
||||
<PackageVersion Include="xunit.v3" Version="4.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -8,6 +8,10 @@
|
||||
<EventSourceSupport>false</EventSourceSupport>
|
||||
<DebuggerSupport>false</DebuggerSupport>
|
||||
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
|
||||
<Http3Support>false</Http3Support>
|
||||
<MetricsSupport>false</MetricsSupport>
|
||||
<UseSizeOptimizedLinq>true</UseSizeOptimizedLinq>
|
||||
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
|
||||
<ServerGarbageCollection>false</ServerGarbageCollection>
|
||||
<ConcurrentGarbageCollection>false</ConcurrentGarbageCollection>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -6,23 +6,30 @@ namespace Monitor.Agent;
|
||||
/// <summary>
|
||||
/// 主机资源占用的只读快照:CPU 占用、内存、根分区、网卡累计流量与瞬时网速、系统运行时长。
|
||||
/// 只有汇总的数字:<b>不采集 CPU 型号、主机名、网卡名和任何 IP 地址</b>,所以面板和展示页想泄漏也泄漏不出去。
|
||||
/// Linux 读 <c>/proc</c>;读不到的项是 null(例如 Windows 上的 CPU)。不执行任何命令,不写文件。
|
||||
/// Linux 读 <c>/proc</c> 和 <c>/sys</c>;容器通过 HOST_PROC、HOST_SYS、HOST_ROOT 指定宿主路径。
|
||||
/// 读不到的项是 null(例如 Windows 上的 CPU)。不执行任何命令,不写文件。
|
||||
/// </summary>
|
||||
public sealed class HostSampler
|
||||
{
|
||||
private static readonly string[] s_virtualPrefixes = ["lo", "veth", "docker", "br-", "virbr", "cni", "flannel", "cali", "tun", "tap"];
|
||||
private static readonly string[] s_virtualPrefixes = ["lo", "veth", "docker", "podman", "br-", "virbr", "cni", "flannel", "cali", "tun", "tap"];
|
||||
|
||||
private readonly TimeProvider _time;
|
||||
private readonly Func<string, string?> _read;
|
||||
private readonly Func<(long Total, long Used)?> _disk;
|
||||
private readonly string _procRoot;
|
||||
private readonly string _sysRoot;
|
||||
private (long Total, long Idle)? _previousCpu;
|
||||
private (long At, long Rx, long Tx)? _previousNet;
|
||||
|
||||
public HostSampler(TimeProvider time, Func<string, string?>? read = null, Func<(long Total, long Used)?>? disk = null)
|
||||
public HostSampler(TimeProvider time, Func<string, string?>? read = null, Func<(long Total, long Used)?>? disk = null,
|
||||
string? procRoot = null, string? sysRoot = null, string? diskRoot = null)
|
||||
{
|
||||
_time = time;
|
||||
_read = read ?? ReadProc;
|
||||
_disk = disk ?? ReadRootDisk;
|
||||
_procRoot = Root(procRoot, "HOST_PROC", "/proc");
|
||||
_sysRoot = Root(sysRoot, "HOST_SYS", "/sys");
|
||||
string root = Root(diskRoot, "HOST_ROOT", OperatingSystem.IsWindows() ? Path.GetPathRoot(Environment.SystemDirectory) ?? "C:\\" : "/");
|
||||
_disk = disk ?? (() => ReadRootDisk(root));
|
||||
}
|
||||
|
||||
public HostSnapshot Sample()
|
||||
@@ -30,7 +37,7 @@ public sealed class HostSampler
|
||||
long now = _time.GetTimestamp();
|
||||
var snapshot = new HostSnapshot { AtUtcMs = _time.GetUtcNow().ToUnixTimeMilliseconds() };
|
||||
|
||||
if (_read("/proc/stat") is { } stat && ParseCpu(stat) is { } cpu)
|
||||
if (_read(Path.Combine(_procRoot, "stat")) is { } stat && ParseCpu(stat) is { } cpu)
|
||||
{
|
||||
if (_previousCpu is { } before && cpu.Total > before.Total)
|
||||
{
|
||||
@@ -41,7 +48,7 @@ public sealed class HostSampler
|
||||
_previousCpu = cpu;
|
||||
}
|
||||
|
||||
if (_read("/proc/meminfo") is { } meminfo && ParseMemory(meminfo) is { } memory)
|
||||
if (_read(Path.Combine(_procRoot, "meminfo")) is { } meminfo && ParseMemory(meminfo) is { } memory)
|
||||
{
|
||||
snapshot.MemTotal = memory.Total;
|
||||
snapshot.MemUsed = memory.Used;
|
||||
@@ -53,10 +60,11 @@ public sealed class HostSampler
|
||||
snapshot.DiskUsed = disk.Used;
|
||||
}
|
||||
|
||||
if (_read("/proc/net/dev") is { } dev && ParseNetwork(dev) is { } net)
|
||||
if (_read(Path.Combine(_procRoot, "net/dev")) is { } dev && ParseNetwork(dev) is { } net)
|
||||
{
|
||||
snapshot.NetRxTotal = net.Rx;
|
||||
snapshot.NetTxTotal = net.Tx;
|
||||
snapshot.NetLinkMbps = ReadLinkSpeed(dev);
|
||||
if (_previousNet is { } before && net.Rx >= before.Rx && net.Tx >= before.Tx)
|
||||
{
|
||||
double seconds = Math.Max(0.001, _time.GetElapsedTime(before.At, now).TotalSeconds);
|
||||
@@ -67,7 +75,11 @@ public sealed class HostSampler
|
||||
_previousNet = (now, net.Rx, net.Tx);
|
||||
}
|
||||
|
||||
snapshot.UptimeSeconds = _read("/proc/uptime") is { } uptime && ParseUptime(uptime) is { } up ? up : Environment.TickCount64 / 1000;
|
||||
snapshot.UptimeSeconds = _read(Path.Combine(_procRoot, "uptime")) is { } uptime && ParseUptime(uptime) is { } up ? up : null;
|
||||
if (!OperatingSystem.IsLinux() && snapshot.UptimeSeconds is null)
|
||||
{
|
||||
snapshot.UptimeSeconds = Environment.TickCount64 / 1000;
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
@@ -165,11 +177,54 @@ public sealed class HostSampler
|
||||
}
|
||||
}
|
||||
|
||||
private static (long Total, long Used)? ReadRootDisk()
|
||||
private static string Root(string? value, string variable, string fallback)
|
||||
{
|
||||
value ??= Environment.GetEnvironmentVariable(variable);
|
||||
return string.IsNullOrWhiteSpace(value) ? fallback : value;
|
||||
}
|
||||
|
||||
private long? ReadLinkSpeed(string dev)
|
||||
{
|
||||
long total = 0;
|
||||
bool any = false;
|
||||
foreach (string line in dev.Split('\n'))
|
||||
{
|
||||
int colon = line.IndexOf(':', StringComparison.Ordinal);
|
||||
if (colon < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string name = line[..colon].Trim();
|
||||
if (s_virtualPrefixes.Any(p => name.StartsWith(p, StringComparison.Ordinal)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string? speed = _read(Path.Combine(_sysRoot, "class/net", name, "speed"));
|
||||
if (long.TryParse(speed?.Trim(), CultureInfo.InvariantCulture, out long mbps) && mbps > 0)
|
||||
{
|
||||
total += mbps;
|
||||
any = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return any ? total : null;
|
||||
}
|
||||
|
||||
private static (long Total, long Used)? ReadRootDisk(string root)
|
||||
{
|
||||
try
|
||||
{
|
||||
string root = OperatingSystem.IsWindows() ? Path.GetPathRoot(Environment.SystemDirectory) ?? "C:\\" : "/";
|
||||
if (!Directory.Exists(root))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var drive = new DriveInfo(root);
|
||||
return drive.IsReady ? (drive.TotalSize, drive.TotalSize - drive.AvailableFreeSpace) : null;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
<AssemblyName>monitor-agent</AssemblyName>
|
||||
<RootNamespace>Monitor.Agent</RootNamespace>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
<PublishAot>true</PublishAot>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<TrimMode>full</TrimMode>
|
||||
<StripSymbols>true</StripSymbols>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Monitor.Backend.Tests" />
|
||||
|
||||
@@ -7,6 +7,25 @@
|
||||
<AssemblyName>fleet-enroll</AssemblyName>
|
||||
<RootNamespace>FleetEnroll</RootNamespace>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
<PublishAot>true</PublishAot>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<TrimMode>full</TrimMode>
|
||||
<StripSymbols>true</StripSymbols>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
|
||||
<OptimizationPreference>Size</OptimizationPreference>
|
||||
<UseSystemResourceKeys>true</UseSystemResourceKeys>
|
||||
<StackTraceSupport>false</StackTraceSupport>
|
||||
<EventSourceSupport>false</EventSourceSupport>
|
||||
<DebuggerSupport>false</DebuggerSupport>
|
||||
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
|
||||
<Http3Support>false</Http3Support>
|
||||
<MetricsSupport>false</MetricsSupport>
|
||||
<UseSizeOptimizedLinq>true</UseSizeOptimizedLinq>
|
||||
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
|
||||
<IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>
|
||||
<ServerGarbageCollection>false</ServerGarbageCollection>
|
||||
<ConcurrentGarbageCollection>false</ConcurrentGarbageCollection>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Fleet.Enrollment.Client\Fleet.Enrollment.Client.csproj" />
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "10.0.200",
|
||||
"rollForward": "latestFeature"
|
||||
},
|
||||
"msbuild-sdks": {
|
||||
"StuDev.AotAnywhere": "1.0.5"
|
||||
},
|
||||
"test": {
|
||||
"runner": "Microsoft.Testing.Platform"
|
||||
}
|
||||
}
|
||||
@@ -5172,6 +5172,7 @@
|
||||
"net_tx_total",
|
||||
"net_rx_bps",
|
||||
"net_tx_bps",
|
||||
"net_link_mbps",
|
||||
"uptime_seconds",
|
||||
"at"
|
||||
],
|
||||
@@ -5258,6 +5259,15 @@
|
||||
],
|
||||
"format": "int64"
|
||||
},
|
||||
"net_link_mbps": {
|
||||
"pattern": "^-?(?:0|[1-9]\\d*)$",
|
||||
"type": [
|
||||
"null",
|
||||
"integer",
|
||||
"string"
|
||||
],
|
||||
"format": "int64"
|
||||
},
|
||||
"uptime_seconds": {
|
||||
"pattern": "^-?(?:0|[1-9]\\d*)$",
|
||||
"type": [
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
<EventSourceSupport>false</EventSourceSupport>
|
||||
<DebuggerSupport>false</DebuggerSupport>
|
||||
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
|
||||
<Http3Support>false</Http3Support>
|
||||
<MetricsSupport>false</MetricsSupport>
|
||||
<UseSizeOptimizedLinq>true</UseSizeOptimizedLinq>
|
||||
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
|
||||
<ServerGarbageCollection>false</ServerGarbageCollection>
|
||||
<ConcurrentGarbageCollection>false</ConcurrentGarbageCollection>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
<AssemblyName>monitor-probe</AssemblyName>
|
||||
<RootNamespace>Monitor.Probe</RootNamespace>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
<PublishAot>true</PublishAot>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<TrimMode>full</TrimMode>
|
||||
<StripSymbols>true</StripSymbols>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Monitor.Backend.Tests" />
|
||||
|
||||
@@ -339,6 +339,10 @@ public sealed class HostSnapshot
|
||||
[JsonPropertyName("net_tx_bps")]
|
||||
public long? NetTxBps { get; set; }
|
||||
|
||||
/// <summary>已统计接口的内核报告链路速率之和(Mbit/s);驱动不支持或无法读取时为空,不代表互联网带宽。</summary>
|
||||
[JsonPropertyName("net_link_mbps")]
|
||||
public long? NetLinkMbps { get; set; }
|
||||
|
||||
[JsonPropertyName("uptime_s")]
|
||||
public long? UptimeSeconds { get; set; }
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ public static class Mappers
|
||||
line.Line.Agent.Online, line.Line.Connected, line.Line.Agent.EndpointChanges, ToDto(line.Summary), Summary(line.LastTest));
|
||||
|
||||
public static HostDto ToDto(HostSnapshot h) =>
|
||||
new(h.CpuPercent, h.MemUsed, h.MemTotal, h.DiskUsed, h.DiskTotal, h.NetRxTotal, h.NetTxTotal, h.NetRxBps, h.NetTxBps, h.UptimeSeconds, Time(h.AtUtcMs));
|
||||
new(h.CpuPercent, h.MemUsed, h.MemTotal, h.DiskUsed, h.DiskTotal, h.NetRxTotal, h.NetTxTotal, h.NetRxBps, h.NetTxBps, h.NetLinkMbps, h.UptimeSeconds, Time(h.AtUtcMs));
|
||||
|
||||
public static ConnectedProbeDto ToDto(ConnectedProbeInfo p) => new(p.Id, p.Version, p.Remote, Time(p.SinceMs));
|
||||
|
||||
|
||||
@@ -163,6 +163,7 @@ public sealed record HostDto(
|
||||
long? NetTxTotal,
|
||||
long? NetRxBps,
|
||||
long? NetTxBps,
|
||||
long? NetLinkMbps,
|
||||
long? UptimeSeconds,
|
||||
DateTimeOffset At);
|
||||
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project>
|
||||
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk.Web" />
|
||||
<Import Project="Sdk.props" Sdk="StuDev.AotAnywhere" Condition="'$(UseAotAnywhere)' == 'true'" />
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AssemblyName>monitor-backend</AssemblyName>
|
||||
<RootNamespace>Monitor.Backend</RootNamespace>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
<PublishAot>true</PublishAot>
|
||||
<OptimizationPreference>Size</OptimizationPreference>
|
||||
<StackTraceSupport>false</StackTraceSupport>
|
||||
<EventSourceSupport>false</EventSourceSupport>
|
||||
<DebuggerSupport>false</DebuggerSupport>
|
||||
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
|
||||
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
|
||||
<IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>
|
||||
<StripSymbols>true</StripSymbols>
|
||||
<!-- This service exposes APIs only; there are no static web assets to publish. -->
|
||||
<StaticWebAssetsEnabled>false</StaticWebAssetsEnabled>
|
||||
<SqliteStaticLink Condition="'$(SqliteStaticLink)' == '' and '$(PublishAot)' == 'true' and '$(RuntimeIdentifier)' != ''">true</SqliteStaticLink>
|
||||
<EnableRequestDelegateGenerator>true</EnableRequestDelegateGenerator>
|
||||
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
|
||||
<OpenApiGenerateDocuments>true</OpenApiGenerateDocuments>
|
||||
@@ -12,8 +26,13 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.ApiDescription.Server" PrivateAssets="all" />
|
||||
<PackageReference Include="SQLitePCLRaw.lib.e_sqlite3" Condition="'$(SqliteStaticLink)' != 'true'" />
|
||||
<PackageReference Include="SQLitePCLRaw.lib.e_sqlite3" ExcludeAssets="native" Condition="'$(SqliteStaticLink)' == 'true'" />
|
||||
<ProjectReference Include="..\Monitor.Backend.Api\Monitor.Backend.Api.csproj" />
|
||||
<ProjectReference Include="..\Monitor.Backend.Gateway\Monitor.Backend.Gateway.csproj" />
|
||||
<ProjectReference Include="..\Monitor.Backend.Storage.Sqlite\Monitor.Backend.Storage.Sqlite.csproj" />
|
||||
</ItemGroup>
|
||||
<Import Project="StaticSqlite.targets" />
|
||||
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk.Web" />
|
||||
<Import Project="Sdk.targets" Sdk="StuDev.AotAnywhere" Condition="'$(UseAotAnywhere)' == 'true'" />
|
||||
</Project>
|
||||
@@ -0,0 +1,71 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<_SqliteToolchainTargets>SetupOSSpecificProps</_SqliteToolchainTargets>
|
||||
<_SqliteToolchainTargets Condition="'$(UseAotAnywhere)' == 'true'">SetupOSSpecificProps;OverwriteTargetTriple;SetPathToZig</_SqliteToolchainTargets>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(SqliteStaticLink)' == 'true'">
|
||||
<DirectPInvoke Include="e_sqlite3" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PrepareStaticSqliteSource" Condition="'$(SqliteStaticLink)' == 'true'">
|
||||
<Error Condition="'$(RuntimeIdentifier)' != 'linux-x64' and '$(RuntimeIdentifier)' != 'linux-arm64' and
|
||||
'$(RuntimeIdentifier)' != 'linux-musl-x64' and '$(RuntimeIdentifier)' != 'linux-musl-arm64' and
|
||||
'$(RuntimeIdentifier)' != 'osx-x64' and '$(RuntimeIdentifier)' != 'osx-arm64' and
|
||||
'$(RuntimeIdentifier)' != 'win-x64' and '$(RuntimeIdentifier)' != 'win-arm64'"
|
||||
Text="Static SQLite is configured for the eight supported x64/arm64 Linux, musl, macOS and Windows targets. Unsupported RID: $(RuntimeIdentifier)." />
|
||||
<PropertyGroup>
|
||||
<_SqliteCache>$([System.IO.Path]::GetFullPath('$(BaseIntermediateOutputPath)sqlite/$(MonitorSqliteSourceVersion)/'))</_SqliteCache>
|
||||
<_SqliteArchive>$(_SqliteCache)sqlite-amalgamation-$(MonitorSqliteSourceVersion).zip</_SqliteArchive>
|
||||
<_SqliteSource>$(_SqliteCache)sqlite-amalgamation-$(MonitorSqliteSourceVersion)/sqlite3.c</_SqliteSource>
|
||||
<_SqliteObjectDirectory>$(_SqliteCache)$(RuntimeIdentifier)/$(UseAotAnywhere)/</_SqliteObjectDirectory>
|
||||
<_SqliteObject>$(_SqliteObjectDirectory)e_sqlite3.o</_SqliteObject>
|
||||
<_SqliteObject Condition="$(RuntimeIdentifier.StartsWith('win'))">$(_SqliteObjectDirectory)e_sqlite3.obj</_SqliteObject>
|
||||
</PropertyGroup>
|
||||
<MakeDir Directories="$(_SqliteObjectDirectory)" />
|
||||
<DownloadFile SourceUrl="https://www.sqlite.org/$(MonitorSqliteSourceYear)/sqlite-amalgamation-$(MonitorSqliteSourceVersion).zip"
|
||||
DestinationFolder="$(_SqliteCache)" Condition="!Exists('$(_SqliteArchive)')" />
|
||||
<GetFileHash Files="$(_SqliteArchive)" Algorithm="SHA256">
|
||||
<Output TaskParameter="Hash" PropertyName="_SqliteArchiveHash" />
|
||||
</GetFileHash>
|
||||
<Error Condition="'$(_SqliteArchiveHash.ToLowerInvariant())' != '$(MonitorSqliteSourceSha256)'"
|
||||
Text="SQLite source SHA256 mismatch: $(_SqliteArchiveHash)." />
|
||||
<Unzip SourceFiles="$(_SqliteArchive)" DestinationFolder="$(_SqliteCache)" Condition="!Exists('$(_SqliteSource)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CompileStaticSqliteObject" Condition="'$(SqliteStaticLink)' == 'true'"
|
||||
DependsOnTargets="PrepareStaticSqliteSource;$(_SqliteToolchainTargets)"
|
||||
Inputs="$(_SqliteSource);$(MSBuildThisFileFullPath)" Outputs="$(_SqliteObject)">
|
||||
<PropertyGroup>
|
||||
<_SqliteUseMsvc>false</_SqliteUseMsvc>
|
||||
<_SqliteUseMsvc Condition="$(RuntimeIdentifier.StartsWith('win')) and '$(_AotAnywhereWindowsCross)' != 'true'">true</_SqliteUseMsvc>
|
||||
<_SqliteCompiler>"$(CppCompilerAndLinker)"</_SqliteCompiler>
|
||||
<_SqliteCompiler Condition="'$(CppCompilerAndLinker)' == ''">cc</_SqliteCompiler>
|
||||
<_SqliteCompiler Condition="'$(TargetTriple)' != ''">$(_SqliteCompiler) --target=$(TargetTriple)</_SqliteCompiler>
|
||||
<_SqliteCompiler Condition="'$(SysRoot)' != ''">$(_SqliteCompiler) --sysroot="$(SysRoot)"</_SqliteCompiler>
|
||||
<_SqliteCompiler Condition="'$(UseAotAnywhere)' == 'true'">"$(_AotAnywhereZigExe)" cc -target $(TargetTriple)</_SqliteCompiler>
|
||||
<_SqliteCompiler Condition="'$(_SqliteUseMsvc)' == 'true'">cl</_SqliteCompiler>
|
||||
</PropertyGroup>
|
||||
<Message Importance="high" Text="Compiling SQLite $(MonitorSqliteSourceVersion) into the $(RuntimeIdentifier) NativeAOT executable." />
|
||||
<Exec Condition="'$(_SqliteUseMsvc)' != 'true'"
|
||||
Command="$(_SqliteCompiler) -Os -fPIC -c "$(_SqliteSource)" -o "$(_SqliteObject)" -DSQLITE_API= -DSQLITE_THREADSAFE=1 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_MATH_FUNCTIONS -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_SNAPSHOT -DSQLITE_ENABLE_UNLOCK_NOTIFY" />
|
||||
<Exec Condition="'$(_SqliteUseMsvc)' == 'true'"
|
||||
Command="cl /nologo /O1 /MT /c "$(_SqliteSource)" /Fo"$(_SqliteObject)" /DSQLITE_API= /DSQLITE_THREADSAFE=1 /DSQLITE_DEFAULT_MEMSTATUS=0 /DSQLITE_ENABLE_COLUMN_METADATA /DSQLITE_ENABLE_FTS4 /DSQLITE_ENABLE_FTS5 /DSQLITE_ENABLE_MATH_FUNCTIONS /DSQLITE_ENABLE_RTREE /DSQLITE_ENABLE_SNAPSHOT /DSQLITE_ENABLE_UNLOCK_NOTIFY" />
|
||||
</Target>
|
||||
|
||||
<!-- AotAnywhere runs before LinkNative; register the object before either linker starts. -->
|
||||
<Target Name="LinkStaticSqlite" Condition="'$(SqliteStaticLink)' == 'true'"
|
||||
BeforeTargets="AotAnywhereDirectLinkNative;AotAnywhereDirectLinkMacNative;AotAnywhereDirectLinkWindows;LinkNative"
|
||||
DependsOnTargets="CompileStaticSqliteObject">
|
||||
<ItemGroup>
|
||||
<NativeLibrary Include="$(_SqliteObject)" />
|
||||
<LinkerArg Include=""$(_SqliteObject)"" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="RemoveStaticSqliteRuntimeFiles" Condition="'$(SqliteStaticLink)' == 'true'" AfterTargets="ComputeFilesToPublish">
|
||||
<ItemGroup>
|
||||
<ResolvedFileToPublish Remove="@(ResolvedFileToPublish)"
|
||||
Condition="'%(ResolvedFileToPublish.Filename)' == 'libe_sqlite3' or '%(ResolvedFileToPublish.Filename)' == 'e_sqlite3'" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -35,11 +35,20 @@ public sealed class AotSmokeTests
|
||||
string token = JsonNode.Parse(await tokenResponse.Content.ReadAsStringAsync())!["plaintext"]!.GetValue<string>();
|
||||
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||
|
||||
using HttpResponseMessage settingsResponse = await http.PatchAsync("/api/admin/settings", BackendFixture.Json(new JsonObject { ["site.public"] = true }, "application/merge-patch+json"));
|
||||
settingsResponse.EnsureSuccessStatusCode();
|
||||
using HttpResponseMessage probeResponse = await http.PostAsync("/api/admin/probes", BackendFixture.Json(new JsonObject { ["name"] = "aot-probe-" + Guid.NewGuid().ToString("N")[..6] }));
|
||||
probeResponse.EnsureSuccessStatusCode();
|
||||
string probeId = JsonNode.Parse(await probeResponse.Content.ReadAsStringAsync())!["probe_id"]!.GetValue<string>();
|
||||
|
||||
// Agent:加入令牌 → 注册(ECDSA 持有证明)→ WebSocket 签名认证 → 期望状态
|
||||
using HttpResponseMessage joinResponse = await http.PostAsync("/api/admin/join-tokens", BackendFixture.Json(new JsonObject { ["max_uses"] = 1 }));
|
||||
joinResponse.EnsureSuccessStatusCode();
|
||||
string joinToken = JsonNode.Parse(await joinResponse.Content.ReadAsStringAsync())!["plaintext"]!.GetValue<string>();
|
||||
using var anonymous = new HttpClient { BaseAddress = baseAddress };
|
||||
await using AgentSimulator agent = await AgentSimulator.EnrollAsync(anonymous, joinToken, "aot-" + Guid.NewGuid().ToString("N")[..6]);
|
||||
agent.ProbeForChunks = new ProbeId(probeId);
|
||||
string lineId = $"{agent.NodeId.Value}~{probeId}";
|
||||
await agent.ConnectAsync(baseAddress);
|
||||
TaskSet tasks = await agent.WaitForTaskSetAsync(s => s.Tasks.Count >= 2);
|
||||
Assert.Contains(tasks.Tasks, t => t.Kind == "udp_echo");
|
||||
@@ -54,7 +63,7 @@ public sealed class AotSmokeTests
|
||||
|
||||
// 查询:热层的精确汇总,随后是持久层的分钟窗口
|
||||
string range = $"from={Iso(baseMs)}&to={Iso(baseMs + minute)}&metrics=loss.total,rtt.p95";
|
||||
JsonNode hot = await GetAsync(http, $"/api/public/lines/{agent.NodeId.Value}/series?{range}&step=1s");
|
||||
JsonNode hot = await GetAsync(http, $"/api/public/lines/{lineId}/series?{range}&step=1s");
|
||||
Assert.Equal("hot", hot["source"]!.GetValue<string>());
|
||||
Assert.Equal(600, hot["summary"]!["samples"]!["udp_echo"]!.GetValue<long>());
|
||||
Assert.Equal(10.0, hot["summary"]!["metrics"]!["loss.total"]!.GetValue<double>(), 6);
|
||||
@@ -62,7 +71,7 @@ public sealed class AotSmokeTests
|
||||
JsonNode persisted;
|
||||
do
|
||||
{
|
||||
persisted = await GetAsync(http, $"/api/public/lines/{agent.NodeId.Value}/series?{range}&step=1m");
|
||||
persisted = await GetAsync(http, $"/api/public/lines/{lineId}/series?{range}&step=1m");
|
||||
if (persisted["summary"]!["samples"]!["udp_echo"]?.GetValue<long>() == 600)
|
||||
{
|
||||
break;
|
||||
@@ -80,16 +89,16 @@ public sealed class AotSmokeTests
|
||||
Assert.True(RawChunkCodec.Decode(bytes).IsSuccess);
|
||||
|
||||
// SSE:快照 + 增量
|
||||
await using SseClient sse = await SseClient.ConnectAsync(http, $"/api/public/live?topics=nodes,series:{agent.NodeId.Value}:1s:udp");
|
||||
await using SseClient sse = await SseClient.ConnectAsync(http, $"/api/public/live?topics=nodes,series:{lineId}:1s:udp");
|
||||
await sse.WaitForAsync(e => e.Name == "series");
|
||||
|
||||
// 按需测试:Probe 事件 → 下发 → 状态
|
||||
await agent.SendEventAsync(AgentEventKinds.ProbeConnected);
|
||||
await agent.SendEventAsync(AgentEventKinds.ProbeConnected, probeId);
|
||||
JsonNode started = null!;
|
||||
long begun = TimeProvider.System.GetTimestamp();
|
||||
while (TimeProvider.System.GetElapsedTime(begun) < TimeSpan.FromSeconds(10))
|
||||
{
|
||||
using HttpResponseMessage response = await http.PostAsync($"/api/admin/lines/{agent.NodeId.Value}/tests", BackendFixture.Json(new JsonObject { ["plan"] = "reality_session" }));
|
||||
using HttpResponseMessage response = await http.PostAsync($"/api/admin/lines/{lineId}/tests", BackendFixture.Json(new JsonObject { ["plan"] = "reality_session" }));
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.Accepted)
|
||||
{
|
||||
started = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;
|
||||
|
||||
@@ -21,6 +21,7 @@ public sealed class HostSamplerTests
|
||||
Assert.Equal((1000L, 5000L), HostSampler.ParseNetwork(Dev1));
|
||||
Assert.Equal(3600L, HostSampler.ParseUptime("3600.55 7000.1\n"));
|
||||
Assert.Null(HostSampler.ParseNetwork("nothing useful"));
|
||||
Assert.Equal((1000L, 5000L), HostSampler.ParseNetwork(Dev1 + "podman0: 8888 1 0 0 0 0 0 0 9999 1 0 0 0 0 0 0\n"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -29,7 +30,7 @@ public sealed class HostSamplerTests
|
||||
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));
|
||||
var sampler = new HostSampler(time, path => path switch { "/proc/stat" => stat, "/proc/net/dev" => dev, "/proc/uptime" => "100.0 1.0", _ => null }, () => (1000, 250), procRoot: "/proc", sysRoot: "/sys");
|
||||
HostSnapshot first = sampler.Sample();
|
||||
Assert.Null(first.CpuPercent); // 第一次没有基线
|
||||
Assert.Null(first.NetRxBps);
|
||||
@@ -45,6 +46,54 @@ public sealed class HostSamplerTests
|
||||
Assert.Equal(2000, second.NetTxBps);
|
||||
Assert.Equal(3000, second.NetRxTotal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Sample_UsesMountedHostProcAndSys_InsteadOfContainerPaths()
|
||||
{
|
||||
var reads = new List<string>();
|
||||
var sampler = new HostSampler(TimeProvider.System, path =>
|
||||
{
|
||||
reads.Add(path);
|
||||
return path switch
|
||||
{
|
||||
"/host/proc/stat" => Stat1,
|
||||
"/host/proc/meminfo" => "MemTotal: 8192 kB\nMemAvailable: 6144 kB\n",
|
||||
"/host/proc/net/dev" => Dev1,
|
||||
"/host/proc/uptime" => "3600.0 7000.0",
|
||||
"/host/sys/class/net/eth0/speed" => "10000\n",
|
||||
_ => throw new InvalidOperationException("unexpected container path: " + path),
|
||||
};
|
||||
}, () => (1000, 250), procRoot: "/host/proc", sysRoot: "/host/sys");
|
||||
|
||||
HostSnapshot snapshot = sampler.Sample();
|
||||
Assert.Equal(8L * 1024 * 1024, snapshot.MemTotal);
|
||||
Assert.Equal(1000, snapshot.NetRxTotal);
|
||||
Assert.Equal(10000, snapshot.NetLinkMbps);
|
||||
Assert.Equal(3600, snapshot.UptimeSeconds);
|
||||
Assert.DoesNotContain(reads, path => path.StartsWith("/proc/", StringComparison.Ordinal));
|
||||
Assert.DoesNotContain(reads, path => path.Contains("docker0", StringComparison.Ordinal) || path.Contains("/lo/", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("-1")]
|
||||
[InlineData("0")]
|
||||
[InlineData("not supported")]
|
||||
public void Sample_DoesNotInventLinkSpeedOrContainerDisk_WhenHostDataIsUnavailable(string? speed)
|
||||
{
|
||||
var sampler = new HostSampler(TimeProvider.System,
|
||||
path => path.EndsWith("net/dev", StringComparison.Ordinal) ? Dev1 : path.EndsWith("/speed", StringComparison.Ordinal) ? speed : null,
|
||||
procRoot: "/host/proc", sysRoot: "/host/sys", diskRoot: Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")));
|
||||
|
||||
HostSnapshot snapshot = sampler.Sample();
|
||||
Assert.Null(snapshot.NetLinkMbps);
|
||||
Assert.Null(snapshot.DiskTotal);
|
||||
Assert.Null(snapshot.DiskUsed);
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
Assert.Null(snapshot.UptimeSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Collection("backend")]
|
||||
@@ -61,6 +110,7 @@ public sealed class HostStatusTests
|
||||
{
|
||||
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,
|
||||
NetLinkMbps = 1000,
|
||||
};
|
||||
|
||||
[Fact]
|
||||
@@ -81,6 +131,7 @@ public sealed class HostStatusTests
|
||||
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>());
|
||||
Assert.Equal(1000, item["host"]!["net_link_mbps"]!.GetValue<long>());
|
||||
|
||||
// 展示页能拿到的只有汇总数字:整份响应里没有型号、主机名、地址之类的字段
|
||||
string text = list.ToJsonString();
|
||||
|
||||
Reference in new issue
Block a user