928 lines
30 KiB
C#
928 lines
30 KiB
C#
using System.Diagnostics;
|
|
using System.Threading;
|
|
using QemuVmManager.Models;
|
|
|
|
namespace QemuVmManager.Core;
|
|
|
|
public class QemuProcessManager
|
|
{
|
|
private readonly Dictionary<string, Process> _runningVms = new();
|
|
private readonly Dictionary<string, VmStatus> _vmStatuses = new();
|
|
private readonly Dictionary<string, PerformanceMonitor> _performanceMonitors = new();
|
|
|
|
public event EventHandler<VmStatusChangedEventArgs>? VmStatusChanged;
|
|
|
|
public bool IsQemuInstalled()
|
|
{
|
|
try
|
|
{
|
|
var process = new Process
|
|
{
|
|
StartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = "qemu-system-x86_64",
|
|
Arguments = "--version",
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true
|
|
}
|
|
};
|
|
|
|
var started = process.Start();
|
|
if (!started)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
process.WaitForExit(5000);
|
|
return process.ExitCode == 0;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public string GetQemuVersion()
|
|
{
|
|
try
|
|
{
|
|
var process = new Process
|
|
{
|
|
StartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = "qemu-system-x86_64",
|
|
Arguments = "--version",
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true
|
|
}
|
|
};
|
|
|
|
var started = process.Start();
|
|
if (!started)
|
|
{
|
|
return "Unknown";
|
|
}
|
|
|
|
var output = process.StandardOutput.ReadToEnd();
|
|
process.WaitForExit(5000);
|
|
|
|
if (process.ExitCode == 0)
|
|
{
|
|
var lines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
|
return lines.FirstOrDefault() ?? "Unknown";
|
|
}
|
|
|
|
return "Unknown";
|
|
}
|
|
catch
|
|
{
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
public string GetQemuAccelerators()
|
|
{
|
|
try
|
|
{
|
|
var process = new Process
|
|
{
|
|
StartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = "qemu-system-x86_64",
|
|
Arguments = "-accel help",
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true
|
|
}
|
|
};
|
|
|
|
var started = process.Start();
|
|
if (!started)
|
|
{
|
|
return "Unknown";
|
|
}
|
|
|
|
var output = process.StandardOutput.ReadToEnd();
|
|
process.WaitForExit(5000);
|
|
|
|
if (process.ExitCode == 0)
|
|
{
|
|
return output.Trim();
|
|
}
|
|
|
|
return "Unknown";
|
|
}
|
|
catch
|
|
{
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
public VirtualizationType GetAvailableVirtualization()
|
|
{
|
|
try
|
|
{
|
|
// First check if virtualization is enabled in BIOS
|
|
if (!IsVirtualizationEnabled())
|
|
{
|
|
return VirtualizationType.TCG;
|
|
}
|
|
|
|
if (OperatingSystem.IsLinux())
|
|
{
|
|
// Check for KVM support on Linux
|
|
if (File.Exists("/dev/kvm"))
|
|
{
|
|
// Test if KVM is accessible
|
|
var process = new Process
|
|
{
|
|
StartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = "qemu-system-x86_64",
|
|
Arguments = "-accel help",
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true
|
|
}
|
|
};
|
|
|
|
if (process.Start())
|
|
{
|
|
process.WaitForExit(3000);
|
|
var output = process.StandardOutput.ReadToEnd();
|
|
if (output.Contains("kvm") && process.ExitCode == 0)
|
|
{
|
|
return VirtualizationType.KVM;
|
|
}
|
|
}
|
|
}
|
|
return VirtualizationType.TCG;
|
|
}
|
|
else if (OperatingSystem.IsWindows())
|
|
{
|
|
// Check QEMU's available accelerators first
|
|
var process = new Process
|
|
{
|
|
StartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = "qemu-system-x86_64",
|
|
Arguments = "-accel help",
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true
|
|
}
|
|
};
|
|
|
|
if (process.Start())
|
|
{
|
|
process.WaitForExit(3000);
|
|
var output = process.StandardOutput.ReadToEnd();
|
|
|
|
// Check for WHPX (Windows Hypervisor Platform) support
|
|
if (output.Contains("whpx") && process.ExitCode == 0)
|
|
{
|
|
return VirtualizationType.HyperV;
|
|
}
|
|
|
|
// Check for Hyper-V support (hvf)
|
|
if (output.Contains("hvf") && process.ExitCode == 0)
|
|
{
|
|
return VirtualizationType.HVF;
|
|
}
|
|
|
|
// Check for HAXM support
|
|
if (output.Contains("hax") && process.ExitCode == 0)
|
|
{
|
|
return VirtualizationType.HAXM;
|
|
}
|
|
}
|
|
|
|
// Fallback: Check for Hyper-V support using WMI
|
|
try
|
|
{
|
|
var wmiProcess = new Process
|
|
{
|
|
StartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = "powershell",
|
|
Arguments = "-Command \"Get-WmiObject -Class Msvm_VirtualSystemSettingData -Namespace root\\virtualization\\v2 -ErrorAction SilentlyContinue | Select-Object -First 1\"",
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true
|
|
}
|
|
};
|
|
|
|
if (wmiProcess.Start())
|
|
{
|
|
wmiProcess.WaitForExit(3000);
|
|
if (wmiProcess.ExitCode == 0 && !string.IsNullOrEmpty(wmiProcess.StandardOutput.ReadToEnd()))
|
|
{
|
|
return VirtualizationType.HyperV;
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Hyper-V check failed
|
|
}
|
|
|
|
return VirtualizationType.TCG;
|
|
}
|
|
else if (OperatingSystem.IsMacOS())
|
|
{
|
|
// Check for HVF (Hypervisor.framework) on macOS
|
|
var process = new Process
|
|
{
|
|
StartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = "qemu-system-x86_64",
|
|
Arguments = "-accel help",
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true
|
|
}
|
|
};
|
|
|
|
if (process.Start())
|
|
{
|
|
process.WaitForExit(3000);
|
|
var output = process.StandardOutput.ReadToEnd();
|
|
if (output.Contains("hvf") && process.ExitCode == 0)
|
|
{
|
|
return VirtualizationType.HVF;
|
|
}
|
|
}
|
|
|
|
return VirtualizationType.TCG;
|
|
}
|
|
|
|
return VirtualizationType.TCG;
|
|
}
|
|
catch
|
|
{
|
|
return VirtualizationType.TCG;
|
|
}
|
|
}
|
|
|
|
public bool IsVirtualizationEnabled()
|
|
{
|
|
try
|
|
{
|
|
if (OperatingSystem.IsLinux())
|
|
{
|
|
// Check if virtualization is enabled in BIOS
|
|
var process = new Process
|
|
{
|
|
StartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = "lscpu",
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true
|
|
}
|
|
};
|
|
|
|
if (process.Start())
|
|
{
|
|
process.WaitForExit(3000);
|
|
var output = process.StandardOutput.ReadToEnd();
|
|
return output.Contains("Virtualization:") &&
|
|
(output.Contains("VT-x") || output.Contains("AMD-V") || output.Contains("SVM"));
|
|
}
|
|
}
|
|
else if (OperatingSystem.IsWindows())
|
|
{
|
|
// Multiple methods to check virtualization on Windows
|
|
|
|
// Method 1: Check using systeminfo
|
|
try
|
|
{
|
|
var process = new Process
|
|
{
|
|
StartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = "systeminfo",
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true
|
|
}
|
|
};
|
|
|
|
if (process.Start())
|
|
{
|
|
process.WaitForExit(3000);
|
|
var output = process.StandardOutput.ReadToEnd();
|
|
|
|
// Check multiple possible virtualization indicators
|
|
if (output.Contains("Virtualization Enabled In Firmware: Yes") ||
|
|
output.Contains("Virtualization: Enabled") ||
|
|
output.Contains("Hyper-V Requirements:") && output.Contains("Yes"))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Continue to next method
|
|
}
|
|
|
|
// Method 2: Check using PowerShell Get-ComputerInfo
|
|
try
|
|
{
|
|
var process = new Process
|
|
{
|
|
StartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = "powershell",
|
|
Arguments = "-Command \"Get-ComputerInfo | Select-Object HyperVRequirementVirtualizationFirmwareEnabled\"",
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true
|
|
}
|
|
};
|
|
|
|
if (process.Start())
|
|
{
|
|
process.WaitForExit(3000);
|
|
var output = process.StandardOutput.ReadToEnd();
|
|
return output.Contains("True");
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Continue to next method
|
|
}
|
|
|
|
// Method 3: Check using wmic
|
|
try
|
|
{
|
|
var process = new Process
|
|
{
|
|
StartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = "wmic",
|
|
Arguments = "cpu get VirtualizationFirmwareEnabled",
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true
|
|
}
|
|
};
|
|
|
|
if (process.Start())
|
|
{
|
|
process.WaitForExit(3000);
|
|
var output = process.StandardOutput.ReadToEnd();
|
|
return output.Contains("TRUE");
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Continue to next method
|
|
}
|
|
|
|
// Method 4: Check using PowerShell Get-WmiObject
|
|
try
|
|
{
|
|
var process = new Process
|
|
{
|
|
StartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = "powershell",
|
|
Arguments = "-Command \"Get-WmiObject -Class Msvm_VirtualSystemSettingData -Namespace root\\virtualization\\v2 | Select-Object VirtualSystemIdentifiers\"",
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true
|
|
}
|
|
};
|
|
|
|
if (process.Start())
|
|
{
|
|
process.WaitForExit(3000);
|
|
var output = process.StandardOutput.ReadToEnd();
|
|
// If we can query Hyper-V WMI, virtualization is likely enabled
|
|
return !output.Contains("Get-WmiObject") && output.Length > 0;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// All methods failed
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public async Task<bool> StartVmAsync(VmConfiguration config)
|
|
{
|
|
try
|
|
{
|
|
if (_runningVms.ContainsKey(config.Name))
|
|
{
|
|
throw new InvalidOperationException($"VM '{config.Name}' is already running");
|
|
}
|
|
|
|
var virtualizationType = GetAvailableVirtualization();
|
|
var commandBuilder = new QemuCommandBuilder(config, virtualizationType);
|
|
var command = commandBuilder.BuildCommand();
|
|
|
|
Console.WriteLine($"[{config.Name}] Starting QEMU with command:");
|
|
Console.WriteLine($"[{config.Name}] {command}");
|
|
|
|
var process = new Process
|
|
{
|
|
StartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = "qemu-system-x86_64",
|
|
Arguments = command.Replace("qemu-system-x86_64 ", ""),
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = false
|
|
},
|
|
EnableRaisingEvents = true
|
|
};
|
|
|
|
var outputLines = new List<string>();
|
|
var errorLines = new List<string>();
|
|
|
|
// Set up event handlers for output capture
|
|
process.OutputDataReceived += (sender, e) =>
|
|
{
|
|
if (!string.IsNullOrEmpty(e.Data))
|
|
{
|
|
outputLines.Add(e.Data);
|
|
Console.WriteLine($"[{config.Name}] {e.Data}");
|
|
}
|
|
};
|
|
|
|
process.ErrorDataReceived += (sender, e) =>
|
|
{
|
|
if (!string.IsNullOrEmpty(e.Data))
|
|
{
|
|
errorLines.Add(e.Data);
|
|
Console.WriteLine($"[{config.Name}] ERROR: {e.Data}");
|
|
}
|
|
};
|
|
|
|
process.Exited += (sender, e) => OnVmExitedWithDetails(config.Name, process, outputLines, errorLines);
|
|
|
|
var started = process.Start();
|
|
if (!started)
|
|
{
|
|
throw new InvalidOperationException("Failed to start QEMU process");
|
|
}
|
|
|
|
// Start reading output
|
|
process.BeginOutputReadLine();
|
|
process.BeginErrorReadLine();
|
|
|
|
// Wait a moment to see if it starts successfully
|
|
await Task.Delay(3000);
|
|
|
|
if (process.HasExited)
|
|
{
|
|
var errorMessage = $"QEMU process exited immediately with code {process.ExitCode}";
|
|
if (errorLines.Any())
|
|
{
|
|
errorMessage += $". Errors: {string.Join("; ", errorLines)}";
|
|
}
|
|
if (outputLines.Any())
|
|
{
|
|
errorMessage += $". Output: {string.Join("; ", outputLines)}";
|
|
}
|
|
|
|
Console.WriteLine($"[{config.Name}] {errorMessage}");
|
|
UpdateVmStatus(config.Name, VmState.Error, -1, errorMessage);
|
|
throw new InvalidOperationException(errorMessage);
|
|
}
|
|
|
|
_runningVms[config.Name] = process;
|
|
UpdateVmStatus(config.Name, VmState.Running, process.Id);
|
|
|
|
// Start monitoring in background
|
|
_ = Task.Run(() => MonitorVmAsync(config.Name, process));
|
|
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
UpdateVmStatus(config.Name, VmState.Error, -1, ex.Message);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task<bool> StopVmAsync(string vmName, bool force = false)
|
|
{
|
|
if (!_runningVms.TryGetValue(vmName, out var process))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
UpdateVmStatus(vmName, VmState.Stopping, process.Id);
|
|
|
|
if (force)
|
|
{
|
|
process.Kill();
|
|
}
|
|
else
|
|
{
|
|
// Try graceful shutdown first
|
|
process.CloseMainWindow();
|
|
|
|
// Wait for graceful shutdown
|
|
try
|
|
{
|
|
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
|
|
await process.WaitForExitAsync(cts.Token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// Timeout occurred, kill the process
|
|
process.Kill();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
UpdateVmStatus(vmName, VmState.Error, process.Id, ex.Message);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task<bool> PauseVmAsync(string vmName)
|
|
{
|
|
if (!_runningVms.TryGetValue(vmName, out var process))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Use QEMU monitor to pause VM
|
|
await SendQemuCommandAsync(vmName, "stop");
|
|
UpdateVmStatus(vmName, VmState.Paused, process.Id);
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
UpdateVmStatus(vmName, VmState.Error, process.Id, ex.Message);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task<bool> ResumeVmAsync(string vmName)
|
|
{
|
|
if (!_runningVms.TryGetValue(vmName, out var process))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Use QEMU monitor to resume VM
|
|
await SendQemuCommandAsync(vmName, "cont");
|
|
UpdateVmStatus(vmName, VmState.Running, process.Id);
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
UpdateVmStatus(vmName, VmState.Error, process.Id, ex.Message);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public VmStatus? GetVmStatus(string vmName)
|
|
{
|
|
return _vmStatuses.TryGetValue(vmName, out var status) ? status : null;
|
|
}
|
|
|
|
public IEnumerable<VmStatus> GetAllVmStatuses()
|
|
{
|
|
return _vmStatuses.Values;
|
|
}
|
|
|
|
public bool IsVmRunning(string vmName)
|
|
{
|
|
return _runningVms.ContainsKey(vmName) && !_runningVms[vmName].HasExited;
|
|
}
|
|
|
|
private async Task MonitorVmAsync(string vmName, Process process)
|
|
{
|
|
try
|
|
{
|
|
while (!process.HasExited)
|
|
{
|
|
await Task.Delay(5000); // Check every 5 seconds
|
|
|
|
// Update resource usage if VM is running
|
|
if (process.HasExited == false)
|
|
{
|
|
var resourceUsage = await GetVmResourceUsageAsync(vmName);
|
|
UpdateVmResourceUsage(vmName, resourceUsage);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
UpdateVmStatus(vmName, VmState.Error, process.Id, ex.Message);
|
|
}
|
|
}
|
|
|
|
private Task<VmResourceUsage> GetVmResourceUsageAsync(string vmName)
|
|
{
|
|
try
|
|
{
|
|
// This is a simplified implementation
|
|
// In a real scenario, you would use QEMU monitor commands or libvirt
|
|
var usage = new VmResourceUsage();
|
|
|
|
// Get CPU usage from process
|
|
if (_runningVms.TryGetValue(vmName, out var process))
|
|
{
|
|
var startTime = process.StartTime;
|
|
var totalProcessorTime = process.TotalProcessorTime;
|
|
var realTime = DateTime.Now - startTime;
|
|
|
|
usage.CpuUsage = (totalProcessorTime.TotalMilliseconds / (Environment.ProcessorCount * realTime.TotalMilliseconds)) * 100;
|
|
|
|
// Get memory usage
|
|
usage.MemoryUsage = process.WorkingSet64 / (1024 * 1024); // Convert to MB
|
|
}
|
|
|
|
return Task.FromResult(usage);
|
|
}
|
|
catch
|
|
{
|
|
return Task.FromResult(new VmResourceUsage());
|
|
}
|
|
}
|
|
|
|
private async Task SendQemuCommandAsync(string vmName, string command)
|
|
{
|
|
// This would require QEMU monitor socket or similar mechanism
|
|
// For now, this is a placeholder
|
|
await Task.Delay(100);
|
|
}
|
|
|
|
private void OnVmExited(string vmName)
|
|
{
|
|
if (_runningVms.TryGetValue(vmName, out var process))
|
|
{
|
|
_runningVms.Remove(vmName);
|
|
UpdateVmStatus(vmName, VmState.Stopped, -1);
|
|
}
|
|
}
|
|
|
|
private void OnVmExitedWithDetails(string vmName, Process process, List<string> outputLines, List<string> errorLines)
|
|
{
|
|
if (_runningVms.TryGetValue(vmName, out var runningProcess))
|
|
{
|
|
_runningVms.Remove(vmName);
|
|
}
|
|
|
|
var errorMessage = $"Process exited with code {process.ExitCode}";
|
|
if (errorLines.Any())
|
|
{
|
|
errorMessage += $". Errors: {string.Join("; ", errorLines)}";
|
|
}
|
|
if (outputLines.Any())
|
|
{
|
|
errorMessage += $". Output: {string.Join("; ", outputLines)}";
|
|
}
|
|
|
|
Console.WriteLine($"[{vmName}] {errorMessage}");
|
|
UpdateVmStatus(vmName, VmState.Stopped, -1, errorMessage);
|
|
}
|
|
|
|
private void UpdateVmStatus(string vmName, VmState state, int processId, string? errorMessage = null)
|
|
{
|
|
if (!_vmStatuses.TryGetValue(vmName, out var status))
|
|
{
|
|
status = new VmStatus { Name = vmName };
|
|
_vmStatuses[vmName] = status;
|
|
}
|
|
|
|
var oldState = status.State;
|
|
|
|
status.State = state;
|
|
status.ProcessId = processId;
|
|
status.ErrorMessage = errorMessage;
|
|
|
|
switch (state)
|
|
{
|
|
case VmState.Running:
|
|
status.StartedAt = DateTime.UtcNow;
|
|
status.StoppedAt = null;
|
|
break;
|
|
case VmState.Stopped:
|
|
case VmState.Error:
|
|
status.StoppedAt = DateTime.UtcNow;
|
|
break;
|
|
}
|
|
|
|
VmStatusChanged?.Invoke(this, new VmStatusChangedEventArgs(vmName, oldState, state));
|
|
}
|
|
|
|
private void UpdateVmResourceUsage(string vmName, VmResourceUsage usage)
|
|
{
|
|
if (_vmStatuses.TryGetValue(vmName, out var status))
|
|
{
|
|
status.ResourceUsage = usage;
|
|
}
|
|
}
|
|
|
|
// Enhanced performance monitoring methods
|
|
public async Task<VmPerformanceMetrics> GetVmPerformanceMetricsAsync(string vmName)
|
|
{
|
|
if (!_performanceMonitors.TryGetValue(vmName, out var monitor))
|
|
{
|
|
return new VmPerformanceMetrics();
|
|
}
|
|
|
|
return await monitor.GetCurrentMetricsAsync();
|
|
}
|
|
|
|
public Task StartPerformanceMonitoringAsync(string vmName)
|
|
{
|
|
if (_runningVms.TryGetValue(vmName, out var process))
|
|
{
|
|
var monitor = new PerformanceMonitor(process);
|
|
_performanceMonitors[vmName] = monitor;
|
|
return monitor.StartMonitoringAsync();
|
|
}
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public void StopPerformanceMonitoring(string vmName)
|
|
{
|
|
if (_performanceMonitors.TryGetValue(vmName, out var monitor))
|
|
{
|
|
monitor.StopMonitoring();
|
|
_performanceMonitors.Remove(vmName);
|
|
}
|
|
}
|
|
|
|
public async Task<List<VmPerformanceMetrics>> GetPerformanceHistoryAsync(string vmName, int maxSamples = 100)
|
|
{
|
|
if (_performanceMonitors.TryGetValue(vmName, out var monitor))
|
|
{
|
|
return await monitor.GetPerformanceHistoryAsync(maxSamples);
|
|
}
|
|
return new List<VmPerformanceMetrics>();
|
|
}
|
|
}
|
|
|
|
public class PerformanceMonitor
|
|
{
|
|
private readonly Process _process;
|
|
private readonly List<VmPerformanceMetrics> _history = new();
|
|
private readonly object _lock = new();
|
|
private bool _isMonitoring = false;
|
|
private CancellationTokenSource? _cancellationTokenSource;
|
|
|
|
public PerformanceMonitor(Process process)
|
|
{
|
|
_process = process;
|
|
}
|
|
|
|
public async Task StartMonitoringAsync()
|
|
{
|
|
if (_isMonitoring) return;
|
|
|
|
_isMonitoring = true;
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
|
|
|
_ = Task.Run(async () =>
|
|
{
|
|
while (_isMonitoring && !_process.HasExited)
|
|
{
|
|
try
|
|
{
|
|
var metrics = await GetCurrentMetricsAsync();
|
|
|
|
lock (_lock)
|
|
{
|
|
_history.Add(metrics);
|
|
// Keep only last 1000 samples
|
|
if (_history.Count > 1000)
|
|
{
|
|
_history.RemoveAt(0);
|
|
}
|
|
}
|
|
|
|
await Task.Delay(2000, _cancellationTokenSource.Token); // Sample every 2 seconds
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
break;
|
|
}
|
|
catch
|
|
{
|
|
// Continue monitoring even if there's an error
|
|
}
|
|
}
|
|
}, _cancellationTokenSource.Token);
|
|
}
|
|
|
|
public void StopMonitoring()
|
|
{
|
|
_isMonitoring = false;
|
|
_cancellationTokenSource?.Cancel();
|
|
}
|
|
|
|
public Task<VmPerformanceMetrics> GetCurrentMetricsAsync()
|
|
{
|
|
try
|
|
{
|
|
var metrics = new VmPerformanceMetrics
|
|
{
|
|
Timestamp = DateTime.UtcNow,
|
|
ProcessId = _process.Id
|
|
};
|
|
|
|
if (!_process.HasExited)
|
|
{
|
|
_process.Refresh();
|
|
|
|
// CPU metrics
|
|
metrics.CpuUsagePercent = _process.TotalProcessorTime.TotalMilliseconds /
|
|
(Environment.ProcessorCount * (DateTime.Now - _process.StartTime).TotalMilliseconds) * 100;
|
|
|
|
// Memory metrics
|
|
metrics.MemoryUsageMB = _process.WorkingSet64 / (1024 * 1024);
|
|
metrics.PrivateMemoryMB = _process.PrivateMemorySize64 / (1024 * 1024);
|
|
metrics.VirtualMemoryMB = _process.VirtualMemorySize64 / (1024 * 1024);
|
|
|
|
// Process metrics
|
|
metrics.ThreadCount = _process.Threads.Count;
|
|
metrics.HandleCount = _process.HandleCount;
|
|
|
|
// Performance counters (if available)
|
|
try
|
|
{
|
|
using var cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
|
|
metrics.SystemCpuUsagePercent = cpuCounter.NextValue();
|
|
}
|
|
catch
|
|
{
|
|
metrics.SystemCpuUsagePercent = 0;
|
|
}
|
|
}
|
|
|
|
return Task.FromResult(metrics);
|
|
}
|
|
catch
|
|
{
|
|
return Task.FromResult(new VmPerformanceMetrics { Timestamp = DateTime.UtcNow });
|
|
}
|
|
}
|
|
|
|
public Task<List<VmPerformanceMetrics>> GetPerformanceHistoryAsync(int maxSamples = 100)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
return Task.FromResult(_history.TakeLast(maxSamples).ToList());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public class VmStatusChangedEventArgs : EventArgs
|
|
{
|
|
public string VmName { get; }
|
|
public VmState OldState { get; }
|
|
public VmState NewState { get; }
|
|
|
|
public VmStatusChangedEventArgs(string vmName, VmState oldState, VmState newState)
|
|
{
|
|
VmName = vmName;
|
|
OldState = oldState;
|
|
NewState = newState;
|
|
}
|
|
}
|