feat(core): add process list collector

This commit is contained in:
dualfroz committed 2026-08-23 19:27:39 +02:00
1 parent 90b7d72e68
commit 5c8c6d565f
5 files changed
+125

No files matched your search

@@ -0,0 +1,8 @@
using Vantage.Core.Models;
namespace Vantage.Core.Collectors.Processes;
public interface IProcessProvider
{
Task<IReadOnlyList<ProcessInfo>> GetTopProcessesAsync(HostConfig host, int count, CancellationToken cancellationToken);
}
@@ -0,0 +1,44 @@
using Vantage.Core.Models;
namespace Vantage.Core.Collectors.Processes;
public sealed class MockProcessProvider : IProcessProvider
{
private static readonly (string User, string Command, double Cpu, double Mem)[] Catalog =
{
("root", "/usr/lib/systemd/systemd", 0.3, 0.4),
("postgres", "postgres: writer", 3.1, 8.7),
("root", "dockerd", 5.4, 2.1),
("www-data", "nginx: worker", 1.2, 0.9),
("root", "containerd", 2.8, 1.5),
("node", "node server.js", 12.6, 6.3),
("root", "sshd: foksiu", 0.1, 0.2),
("prometheus", "prometheus", 4.7, 3.9),
("grafana", "grafana-server", 1.9, 4.2),
("root", "journald", 0.2, 0.3)
};
private readonly Random random;
public MockProcessProvider(int seed = 1337)
{
random = new Random(seed);
}
public Task<IReadOnlyList<ProcessInfo>> GetTopProcessesAsync(HostConfig host, int count, CancellationToken cancellationToken)
{
var pid = 100;
var processes = Catalog
.Select(entry => new ProcessInfo(
pid += random.Next(1, 900),
entry.User,
Math.Round(entry.Cpu + random.NextDouble() * 4, 1),
Math.Round(entry.Mem + random.NextDouble() * 2, 1),
entry.Command))
.OrderByDescending(p => p.CpuPercent)
.Take(count)
.ToArray();
return Task.FromResult<IReadOnlyList<ProcessInfo>>(processes);
}
}
@@ -0,0 +1,8 @@
namespace Vantage.Core.Collectors.Processes;
public readonly record struct ProcessInfo(
int Pid,
string User,
double CpuPercent,
double MemoryPercent,
string Command);
@@ -0,0 +1,39 @@
using System.Globalization;
namespace Vantage.Core.Collectors.Processes;
public static class ProcessListParser
{
public static IReadOnlyList<ProcessInfo> Parse(string psOutput)
{
var processes = new List<ProcessInfo>();
foreach (var rawLine in psOutput.Replace("\r\n", "\n").Split('\n'))
{
var line = rawLine.Trim();
if (line.Length == 0)
continue;
var fields = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (fields.Length < 5)
continue;
if (!int.TryParse(fields[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var pid))
continue;
if (!TryParsePercent(fields[2], out var cpu) || !TryParsePercent(fields[3], out var mem))
continue;
var command = string.Join(' ', fields[4..]);
processes.Add(new ProcessInfo(pid, fields[1], cpu, mem, command));
}
return processes;
}
public static IReadOnlyList<ProcessInfo> TopByCpu(string psOutput, int count) =>
Parse(psOutput).OrderByDescending(p => p.CpuPercent).Take(count).ToArray();
private static bool TryParsePercent(string value, out double parsed) =>
double.TryParse(value.Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out parsed);
}
@@ -0,0 +1,26 @@
using Vantage.Core.Models;
using Vantage.Core.Ssh;
namespace Vantage.Core.Collectors.Processes;
public sealed class SshProcessProvider : IProcessProvider
{
private readonly ISshCommandRunner runner;
public SshProcessProvider(ISshCommandRunner runner)
{
this.runner = runner;
}
public static string BuildCommand(int count)
{
var limit = Math.Max(1, count);
return $"ps -eo pid,user:20,pcpu,pmem,comm --sort=-pcpu --no-headers | head -n {limit}";
}
public async Task<IReadOnlyList<ProcessInfo>> GetTopProcessesAsync(HostConfig host, int count, CancellationToken cancellationToken)
{
var output = await runner.RunAsync(host, BuildCommand(count), cancellationToken).ConfigureAwait(false);
return ProcessListParser.TopByCpu(output, count);
}
}