From fa06a6781048420aa32d9dda86c1b0321881a1c8 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Mon, 24 Aug 2026 23:08:53 +0200 Subject: [PATCH] feat(core): add mock metrics provider for offline development --- .../Metrics/MockMetricsProvider.cs | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 src/Vantage.Core/Metrics/MockMetricsProvider.cs diff --git a/src/Vantage.Core/Metrics/MockMetricsProvider.cs b/src/Vantage.Core/Metrics/MockMetricsProvider.cs new file mode 100644 index 0000000..0e0d92c --- /dev/null +++ b/src/Vantage.Core/Metrics/MockMetricsProvider.cs @@ -0,0 +1,93 @@ +using System.Collections.Concurrent; +using Vantage.Core.Models; + +namespace Vantage.Core.Metrics; + +public sealed class MockMetricsProvider : IMetricsProvider +{ + private sealed record HostProfile( + long MemoryTotalBytes, + long DiskTotalBytes, + double CpuPhase, + double MemoryPhase, + DateTimeOffset BootedAt, + Random Jitter); + + private readonly ConcurrentDictionary profiles = new(); + private readonly Func clock; + + public MockMetricsProvider(Func? clock = null) + { + this.clock = clock ?? (() => DateTimeOffset.UtcNow); + } + + public Task GetMetricsAsync(HostConfig host, CancellationToken cancellationToken) + { + var now = clock(); + var profile = profiles.GetOrAdd(host.Id, id => CreateProfile(id, now)); + var elapsed = (now - profile.BootedAt).TotalSeconds; + + var cpu = Oscillate(elapsed, period: 37, phase: profile.CpuPhase, min: 6, max: 82) + + profile.Jitter.NextDouble() * 6 - 3; + cpu = Math.Clamp(cpu, 0, 100); + + var memoryFraction = Oscillate(elapsed, period: 91, phase: profile.MemoryPhase, min: 0.28, max: 0.74); + var memoryUsed = (long)(profile.MemoryTotalBytes * memoryFraction); + + var diskFraction = Math.Clamp(0.41 + elapsed / 1_000_000.0, 0.41, 0.95); + var diskUsed = (long)(profile.DiskTotalBytes * diskFraction); + + var load1 = Math.Round(cpu / 100.0 * ProcessorCountFor(profile), 2); + + return Task.FromResult(new HostMetrics + { + CpuUsagePercent = Math.Round(cpu, 1), + MemoryUsedBytes = memoryUsed, + MemoryTotalBytes = profile.MemoryTotalBytes, + DiskUsedBytes = diskUsed, + DiskTotalBytes = profile.DiskTotalBytes, + Load1 = load1, + Load5 = Math.Round(load1 * 0.85, 2), + Load15 = Math.Round(load1 * 0.7, 2), + Uptime = now - profile.BootedAt, + CollectedAt = now + }); + } + + private static double ProcessorCountFor(HostProfile profile) => + 4 + profile.MemoryTotalBytes / (8L * 1024 * 1024 * 1024); + + private static HostProfile CreateProfile(string hostId, DateTimeOffset now) + { + var seed = StableSeed(hostId); + var random = new Random(seed); + var memGb = new[] { 4L, 8L, 16L, 32L }[random.Next(4)]; + var diskGb = new[] { 128L, 256L, 512L, 1024L }[random.Next(4)]; + var bootDaysAgo = 1 + random.Next(0, 60); + + return new HostProfile( + MemoryTotalBytes: memGb * 1024 * 1024 * 1024, + DiskTotalBytes: diskGb * 1024 * 1024 * 1024, + CpuPhase: random.NextDouble() * Math.PI * 2, + MemoryPhase: random.NextDouble() * Math.PI * 2, + BootedAt: now.AddDays(-bootDaysAgo).AddMinutes(-random.Next(0, 1440)), + Jitter: random); + } + + private static double Oscillate(double elapsedSeconds, double period, double phase, double min, double max) + { + var wave = (Math.Sin(elapsedSeconds / period * Math.PI * 2 + phase) + 1) / 2.0; + return min + wave * (max - min); + } + + private static int StableSeed(string value) + { + unchecked + { + var hash = 17; + foreach (var c in value) + hash = hash * 31 + c; + return hash; + } + } +}