using System.Diagnostics; using System.Runtime.InteropServices; using System.Text.RegularExpressions; namespace JMonAgent; public interface IMetricCollector { SystemMetrics Collect(); } public class LinuxMetricCollector : IMetricCollector { private long _prevJiffy; private long _prevIdle; private string? _cachedIpAddress; private string? _cachedHwId; public SystemMetrics Collect() { var memInfo = ParseMemInfo(); var swapInfo = ParseSwapInfo(); var diskInfo = ParseDiskInfo(); // Cache IP and HWID on first call _cachedIpAddress ??= GetIpAddress(); _cachedHwId ??= GetHwId(); return new SystemMetrics( Environment.MachineName, RuntimeInformation.OSDescription, GetCpuUsage(), memInfo.UsedBytes, memInfo.TotalBytes, swapInfo.UsedBytes, swapInfo.TotalBytes, diskInfo.UsedBytes, diskInfo.TotalBytes, diskInfo.MountPoint, DateTime.UtcNow, _cachedIpAddress ?? "", _cachedHwId ?? "", Environment.ProcessId ); } private string GetIpAddress() { try { var psi = new ProcessStartInfo("hostname", "-I") { RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true }; using var process = Process.Start(psi); if (process == null) return ""; var output = process.StandardOutput.ReadToEnd().Trim(); var addresses = output.Split(' ', StringSplitOptions.RemoveEmptyEntries); return addresses.FirstOrDefault() ?? ""; } catch { return ""; } } private string GetHwId() { try { // Try to read machine-id from Linux if (File.Exists("/etc/machine-id")) { return File.ReadAllText("/etc/machine-id").Trim(); } // Fallback: use MAC address from first interface var interfaces = Directory.GetFiles("/sys/class/net/"); if (interfaces.Length > 0) { var firstInterface = Path.GetFileName(interfaces[0]); var macPath = $"/sys/class/net/{firstInterface}/address"; if (File.Exists(macPath)) { return File.ReadAllText(macPath).Trim(); } } return ""; } catch { return ""; } } private (long UsedBytes, long TotalBytes) ParseMemInfo() { try { var lines = File.ReadAllLines("/proc/meminfo"); var dict = new Dictionary(); foreach (var line in lines) { var parts = line.Split(':', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 2 && long.TryParse(Regex.Replace(parts[1], @"\D+", ""), out var value)) { dict[parts[0]] = value * 1024; // Convert from KB to bytes } } long total = dict.TryGetValue("MemTotal", out var t) ? t : 0; long available = dict.TryGetValue("MemAvailable", out var a) ? a : 0; long used = total - available; return (used, total); } catch { return (0, 0); } } private (long UsedBytes, long TotalBytes) ParseSwapInfo() { try { var lines = File.ReadAllLines("/proc/meminfo"); var dict = new Dictionary(); foreach (var line in lines) { var parts = line.Split(':', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 2 && long.TryParse(Regex.Replace(parts[1], @"\D+", ""), out var value)) { dict[parts[0]] = value * 1024; // Convert from KB to bytes } } long total = dict.TryGetValue("SwapTotal", out var st) ? st : 0; long free = dict.TryGetValue("SwapFree", out var sf) ? sf : 0; long used = total - free; return (used, total); } catch { return (0, 0); } } private (long UsedBytes, long TotalBytes, string MountPoint) ParseDiskInfo() { try { // Use 'df' command to get root filesystem info var psi = new ProcessStartInfo("df", "-B1 /") { RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true }; using var process = Process.Start(psi); if (process == null) return (0, 0, "/"); var output = process.StandardOutput.ReadToEnd(); var lines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries); // Skip header, parse second line if (lines.Length >= 2) { var parts = lines[1].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (parts.Length >= 4 && long.TryParse(parts[1], out var total) && long.TryParse(parts[2], out var used)) { return (used, total, parts[parts.Length - 1]); } } return (0, 0, "/"); } catch { return (0, 0, "/"); } } private double GetCpuUsage() { try { var firstLine = File.ReadLines("/proc/stat").First(); var parts = firstLine.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (parts.Length < 5) return 0.0; // /proc/stat format: cpu user nice system idle iowait irq softirq steal guest guest_nice long user = long.Parse(parts[1]); long nice = long.Parse(parts[2]); long system = long.Parse(parts[3]); long idle = long.Parse(parts[4]); long totalJiffy = user + nice + system + idle; long activeJiffy = user + nice + system; // First call - initialize baseline if (_prevJiffy == 0) { _prevJiffy = totalJiffy; _prevIdle = idle; return 0.0; } // Calculate deltas long diffTotal = totalJiffy - _prevJiffy; long diffIdle = idle - _prevIdle; _prevJiffy = totalJiffy; _prevIdle = idle; if (diffTotal == 0) return 0.0; double usage = 100.0 * (1.0 - ((double)diffIdle / diffTotal)); return Math.Max(0, Math.Min(100, usage)); } catch { return 0.0; } } } public class WindowsMetricCollector : IMetricCollector { public SystemMetrics Collect() { // TODO: Use PerformanceCounters or WMI for Windows return new SystemMetrics( Environment.MachineName, RuntimeInformation.OSDescription, 0.0, 0, 0, 0, 0, 0, 0, "C:\\", DateTime.UtcNow, "", "", Environment.ProcessId ); } } public class MacMetricCollector : IMetricCollector { public SystemMetrics Collect() { // TODO: Use sysctl for macOS metrics return new SystemMetrics( Environment.MachineName, RuntimeInformation.OSDescription, 0.0, 0, 0, 0, 0, 0, 0, "/", DateTime.UtcNow, "", "", Environment.ProcessId ); } }