Add start-service.bat script for Resource Monitor Service v2.0
- Introduced a batch script to simplify the startup process for the Resource Monitor Service. - Included checks for .NET 9.0 Runtime installation. - Added build and run commands for the service with appropriate error handling. - Provided user instructions and API documentation links in the script output.
This commit is contained in:
@@ -1,166 +1,246 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Management;
|
||||
using ResourceMonitorService.Configuration;
|
||||
using ResourceMonitorService.Models;
|
||||
using ResourceMonitorService.Services;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace ResourceMonitorService
|
||||
{
|
||||
public class Worker : BackgroundService
|
||||
{
|
||||
private readonly ILogger<Worker> _logger;
|
||||
private readonly IHostApplicationLifetime _lifetime;
|
||||
private readonly ISystemInfoService _systemInfoService;
|
||||
private readonly IResourceMonitorService _resourceMonitorService;
|
||||
private readonly IGameDetectionService _gameDetectionService;
|
||||
private readonly IAlertService _alertService;
|
||||
private readonly ApiSettings _apiSettings;
|
||||
private readonly MonitoringSettings _monitoringSettings;
|
||||
|
||||
public Worker(IHostApplicationLifetime lifetime)
|
||||
public Worker(
|
||||
ILogger<Worker> logger,
|
||||
IHostApplicationLifetime lifetime,
|
||||
ISystemInfoService systemInfoService,
|
||||
IResourceMonitorService resourceMonitorService,
|
||||
IGameDetectionService gameDetectionService,
|
||||
IAlertService alertService,
|
||||
IOptions<ApiSettings> apiSettings,
|
||||
IOptions<MonitoringSettings> monitoringSettings)
|
||||
{
|
||||
_logger = logger;
|
||||
_lifetime = lifetime;
|
||||
_systemInfoService = systemInfoService;
|
||||
_resourceMonitorService = resourceMonitorService;
|
||||
_gameDetectionService = gameDetectionService;
|
||||
_alertService = alertService;
|
||||
_apiSettings = apiSettings.Value;
|
||||
_monitoringSettings = monitoringSettings.Value;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder();
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowAllOrigins",
|
||||
builder => builder
|
||||
.WithOrigins("http://localhost:4200","http://192.168.50.52:4200","http://vmwin11:4200")
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod());
|
||||
});
|
||||
builder.Services.AddControllers().AddNewtonsoftJson();
|
||||
_logger.LogInformation("Resource Monitor Service starting...");
|
||||
|
||||
// Read the API key from appsettings.json
|
||||
var configuration = builder.Configuration;
|
||||
var apiKey = configuration["ApiSettings:ApiKey"];
|
||||
var builder = WebApplication.CreateBuilder();
|
||||
|
||||
// Configure CORS
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowedOrigins", policy =>
|
||||
policy.WithOrigins(_apiSettings.AllowedOrigins.ToArray())
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod());
|
||||
});
|
||||
|
||||
builder.Services.AddControllers().AddNewtonsoftJson();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Middleware to validate API key
|
||||
// This middleware checks for the presence of the API key in the request headers
|
||||
// and compares it with the expected API key from appsettings.json.
|
||||
// If the API key is missing or invalid, it returns a 401 Unauthorized response.
|
||||
//
|
||||
/* app.Use(async (context, next) =>
|
||||
{
|
||||
if (!context.Request.Headers.TryGetValue("X-API-KEY", out var extractedApiKey) || extractedApiKey != apiKey)
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
await context.Response.WriteAsync("Unauthorized: Invalid API Key");
|
||||
return;
|
||||
}
|
||||
|
||||
await next();
|
||||
}); */
|
||||
// Apply CORS policy to allow all origins
|
||||
app.UseCors("AllowAllOrigins");
|
||||
|
||||
app.MapGet("/api/resource-usage", async context =>
|
||||
// API Key middleware (if enabled)
|
||||
if (_apiSettings.RequireApiKey)
|
||||
{
|
||||
var currentTime = GetCurrentTime();
|
||||
|
||||
var computerInfo = GetComputerInfo();
|
||||
var cpuUsage = GetCpuUsage();
|
||||
var ramUsage = GetRamUsage();
|
||||
var gpuUsage = GetGpuUsage();
|
||||
var runningGame = GetCurrentlyRunningGame();
|
||||
|
||||
var resourceUsage = new
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
CurrentTime = currentTime,
|
||||
ComputerInfo = computerInfo,
|
||||
CPU = cpuUsage,
|
||||
RAM = ramUsage,
|
||||
GPU = gpuUsage,
|
||||
CurrentlyRunningGame = runningGame
|
||||
};
|
||||
if (!context.Request.Headers.TryGetValue("X-API-KEY", out var extractedApiKey) ||
|
||||
extractedApiKey != _apiSettings.ApiKey)
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
await context.Response.WriteAsync("Unauthorized: Invalid API Key");
|
||||
return;
|
||||
}
|
||||
await next();
|
||||
});
|
||||
}
|
||||
|
||||
var json = JsonConvert.SerializeObject(resourceUsage);
|
||||
context.Response.ContentType = "application/json";
|
||||
await context.Response.WriteAsync(json);
|
||||
app.UseCors("AllowedOrigins");
|
||||
|
||||
// Enhanced API endpoints
|
||||
ConfigureApiEndpoints(app);
|
||||
|
||||
// Start background monitoring
|
||||
_ = Task.Run(async () => await BackgroundMonitoringLoop(stoppingToken), stoppingToken);
|
||||
|
||||
// Start the web application
|
||||
_ = app.RunAsync(stoppingToken);
|
||||
|
||||
await Task.Delay(Timeout.Infinite, stoppingToken);
|
||||
}
|
||||
|
||||
private void ConfigureApiEndpoints(WebApplication app)
|
||||
{
|
||||
var basePath = _apiSettings.BasePath;
|
||||
|
||||
// System information endpoints
|
||||
app.MapGet($"{basePath}/system-info", async () =>
|
||||
Results.Ok(await _systemInfoService.GetSystemInfoAsync()));
|
||||
|
||||
// Resource usage endpoints
|
||||
app.MapGet($"{basePath}/resource-usage", async () =>
|
||||
Results.Ok(await _resourceMonitorService.GetResourceUsageAsync()));
|
||||
|
||||
app.MapGet($"{basePath}/cpu-usage", async () =>
|
||||
Results.Ok(await _resourceMonitorService.GetCpuUsageAsync()));
|
||||
|
||||
app.MapGet($"{basePath}/memory-usage", async () =>
|
||||
Results.Ok(await _resourceMonitorService.GetMemoryUsageAsync()));
|
||||
|
||||
app.MapGet($"{basePath}/gpu-usage", async () =>
|
||||
Results.Ok(await _resourceMonitorService.GetGpuUsageAsync()));
|
||||
|
||||
app.MapGet($"{basePath}/disk-usage", async () =>
|
||||
Results.Ok(await _resourceMonitorService.GetDiskUsageAsync()));
|
||||
|
||||
app.MapGet($"{basePath}/network-usage", async () =>
|
||||
Results.Ok(await _resourceMonitorService.GetNetworkUsageAsync()));
|
||||
|
||||
app.MapGet($"{basePath}/top-processes", async (int count = 10) =>
|
||||
Results.Ok(await _resourceMonitorService.GetTopProcessesAsync(count)));
|
||||
|
||||
// Game detection endpoints
|
||||
app.MapGet($"{basePath}/current-game", async () =>
|
||||
Results.Ok(await _gameDetectionService.GetCurrentlyRunningGameAsync()));
|
||||
|
||||
app.MapGet($"{basePath}/all-games", async () =>
|
||||
Results.Ok(await _gameDetectionService.GetAllDetectedGamesAsync()));
|
||||
|
||||
app.MapGet($"{basePath}/fullscreen-status", async () =>
|
||||
Results.Ok(new { IsFullscreen = await _gameDetectionService.IsGameRunningFullscreenAsync() }));
|
||||
|
||||
// Alert endpoints
|
||||
app.MapGet($"{basePath}/alerts/active", async () =>
|
||||
Results.Ok(await _alertService.GetActiveAlertsAsync()));
|
||||
|
||||
app.MapGet($"{basePath}/alerts/history", async (int count = 100) =>
|
||||
Results.Ok(await _alertService.GetAlertHistoryAsync(count)));
|
||||
|
||||
app.MapPost($"{basePath}/alerts/{{alertId}}/resolve", async (string alertId) =>
|
||||
{
|
||||
await _alertService.ResolveAlertAsync(alertId);
|
||||
return Results.Ok(new { Message = "Alert resolved successfully" });
|
||||
});
|
||||
|
||||
app.MapPost("/api/kill-process", async context =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var idStr = await new StreamReader(context.Request.Body).ReadToEndAsync();
|
||||
int processId = Convert.ToInt32(idStr);
|
||||
app.MapGet($"{basePath}/alerts/enabled", async () =>
|
||||
Results.Ok(new { Enabled = await _alertService.IsAlertingEnabledAsync() }));
|
||||
|
||||
Process[] processes = Process.GetProcesses().Where(p => p.Id == processId).ToArray();
|
||||
|
||||
if (processes.Length > 0)
|
||||
{
|
||||
foreach (var process in processes)
|
||||
{
|
||||
try
|
||||
{
|
||||
process.Kill();
|
||||
await context.Response.WriteAsync($"Process with ID {processId} has been killed.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await context.Response.WriteAsync($"Error killing process with ID {processId}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await context.Response.WriteAsync($"No process found with ID {processId}.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await context.Response.WriteAsync($"An error occurred: {ex.Message}");
|
||||
}
|
||||
});
|
||||
|
||||
/* curl -X POST http://localhost:5000/api/force-shutdown -d "5000" */
|
||||
/* Invoke-WebRequest -Uri "http://localhost:5000/api/force-shutdown" -Method POST -Body "50000" -ContentType "text/plain" */
|
||||
app.MapPost("/api/force-shutdown", async context =>
|
||||
// Process management endpoints (enhanced)
|
||||
app.MapPost($"{basePath}/process/kill", async (HttpContext context) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var requestBody = await new StreamReader(context.Request.Body).ReadToEndAsync();
|
||||
var parameters = JsonConvert.DeserializeObject<dynamic>(requestBody);
|
||||
var body = await new StreamReader(context.Request.Body).ReadToEndAsync();
|
||||
var request = JsonConvert.DeserializeObject<dynamic>(body);
|
||||
|
||||
int processId = request?.ProcessId ?? 0;
|
||||
bool force = request?.Force ?? false;
|
||||
|
||||
string action = parameters?.Action?.ToString()?.ToLower(); // "shutdown" or "restart"
|
||||
int delaySeconds = parameters?.DelaySeconds ?? 0;
|
||||
|
||||
// Validate action input
|
||||
if (action != "shutdown" && action != "restart" && action != "cancel")
|
||||
if (processId <= 0)
|
||||
{
|
||||
await context.Response.WriteAsync("Invalid action. Use 'shutdown', 'restart', or 'cancel'.");
|
||||
return;
|
||||
return Results.BadRequest("Invalid process ID");
|
||||
}
|
||||
|
||||
var processes = Process.GetProcesses().Where(p => p.Id == processId).ToArray();
|
||||
|
||||
if (processes.Length == 0)
|
||||
{
|
||||
return Results.NotFound($"No process found with ID {processId}");
|
||||
}
|
||||
|
||||
var process = processes[0];
|
||||
var processName = process.ProcessName;
|
||||
|
||||
if (force)
|
||||
{
|
||||
process.Kill(true); // Force kill entire process tree
|
||||
}
|
||||
else
|
||||
{
|
||||
process.CloseMainWindow(); // Try graceful close first
|
||||
|
||||
// Wait a bit for graceful close
|
||||
await Task.Delay(3000);
|
||||
|
||||
if (!process.HasExited)
|
||||
{
|
||||
process.Kill();
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogWarning("Process {ProcessName} (ID: {ProcessId}) was terminated", processName, processId);
|
||||
return Results.Ok(new { Message = $"Process {processName} (ID: {processId}) has been terminated." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error terminating process");
|
||||
return Results.Problem(ex.Message);
|
||||
}
|
||||
});
|
||||
|
||||
// Enhanced shutdown/restart endpoints
|
||||
app.MapPost($"{basePath}/system/shutdown", async (HttpContext context) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var body = await new StreamReader(context.Request.Body).ReadToEndAsync();
|
||||
var request = JsonConvert.DeserializeObject<dynamic>(body);
|
||||
|
||||
string action = request?.Action?.ToString()?.ToLower() ?? "shutdown";
|
||||
int delaySeconds = request?.DelaySeconds ?? 0;
|
||||
string message = request?.Message?.ToString() ?? "System shutdown initiated by Resource Monitor";
|
||||
|
||||
if (action != "shutdown" && action != "restart" && action != "cancel")
|
||||
{
|
||||
return Results.BadRequest("Invalid action. Use 'shutdown', 'restart', or 'cancel'.");
|
||||
}
|
||||
|
||||
//if action is stop, then cancel the shutdown
|
||||
if (action == "cancel")
|
||||
{
|
||||
var processStartInfoCancel = new ProcessStartInfo
|
||||
var cancelProcess = new ProcessStartInfo
|
||||
{
|
||||
FileName = "shutdown",
|
||||
Arguments = "/a",
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false
|
||||
};
|
||||
|
||||
Process.Start(processStartInfoCancel);
|
||||
await context.Response.WriteAsync("Shutdown cancelled.");
|
||||
return;
|
||||
Process.Start(cancelProcess);
|
||||
|
||||
_logger.LogWarning("System shutdown cancelled");
|
||||
return Results.Ok(new { Message = "Shutdown cancelled." });
|
||||
}
|
||||
|
||||
// Validate delay input
|
||||
if (delaySeconds < 0)
|
||||
{
|
||||
await context.Response.WriteAsync("Delay must be a non-negative integer.");
|
||||
return;
|
||||
return Results.BadRequest("Delay must be a non-negative integer.");
|
||||
}
|
||||
|
||||
// Determine the shutdown command
|
||||
string shutdownCommand = action == "shutdown" ? $"/s /f /t {delaySeconds}" : $"/r /f /t {delaySeconds}";
|
||||
string shutdownCommand = action == "shutdown"
|
||||
? $"/s /f /t {delaySeconds} /c \"{message}\""
|
||||
: $"/r /f /t {delaySeconds} /c \"{message}\"";
|
||||
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
@@ -172,274 +252,233 @@ namespace ResourceMonitorService
|
||||
|
||||
Process.Start(processStartInfo);
|
||||
|
||||
await context.Response.WriteAsync($"{action.ToUpper()} command executed with a delay of {delaySeconds} seconds.");
|
||||
_logger.LogWarning("System {Action} initiated with {Delay} seconds delay", action, delaySeconds);
|
||||
return Results.Ok(new {
|
||||
Message = $"{action.ToUpper()} command executed with a delay of {delaySeconds} seconds.",
|
||||
Action = action,
|
||||
DelaySeconds = delaySeconds,
|
||||
Timestamp = DateTime.Now
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await context.Response.WriteAsync($"An error occurred: {ex.Message}");
|
||||
_logger.LogError(ex, "Error executing system command");
|
||||
return Results.Problem(ex.Message);
|
||||
}
|
||||
});
|
||||
|
||||
app.MapGet("/api/stop", async context =>
|
||||
// VM-specific endpoints for Unraid
|
||||
app.MapGet($"{basePath}/vm/info", async () =>
|
||||
{
|
||||
await context.Response.WriteAsync("Stopping the service...");
|
||||
_lifetime.StopApplication();
|
||||
var systemInfo = await _systemInfoService.GetSystemInfoAsync();
|
||||
return Results.Ok(new
|
||||
{
|
||||
IsVirtualMachine = systemInfo.IsVirtualMachine,
|
||||
HypervisorVendor = systemInfo.HypervisorVendor,
|
||||
Uptime = systemInfo.Uptime,
|
||||
BootTime = systemInfo.BootTime,
|
||||
MachineName = systemInfo.MachineName,
|
||||
Domain = systemInfo.Domain
|
||||
});
|
||||
});
|
||||
|
||||
app.MapGet("/", () => "Resource Monitor Service is running.");
|
||||
app.MapGet("/api/current-time", () => Results.Ok(GetCurrentTime()));
|
||||
app.MapGet("/api/computer-info", () => Results.Ok(GetComputerInfo()));
|
||||
app.MapGet("/api/cpu-usage", () => Results.Ok(GetCpuUsage()));
|
||||
app.MapGet("/api/ram-usage", () => Results.Ok(GetRamUsage()));
|
||||
app.MapGet("/api/gpu-usage", () => Results.Ok(GetGpuUsage()));
|
||||
app.MapGet("/api/running-game", () => Results.Ok(GetCurrentlyRunningGame()));
|
||||
app.MapGet("/api/total-physical-memory", () => Results.Ok(GetTotalPhysicalMemory()));
|
||||
app.MapGet("/api/total-available-memory", () => Results.Ok(new { TotalAvailableMemory = Environment.WorkingSet }));
|
||||
// Performance history endpoint (simple in-memory storage)
|
||||
app.MapGet($"{basePath}/performance/history", async (int minutes = 60) =>
|
||||
{
|
||||
// This would ideally be stored in a database or time-series database
|
||||
// For now, return current snapshot with timestamp
|
||||
var usage = await _resourceMonitorService.GetResourceUsageAsync();
|
||||
return Results.Ok(new {
|
||||
Current = usage,
|
||||
Message = "Historical data not implemented yet - showing current values"
|
||||
});
|
||||
});
|
||||
|
||||
app.MapGet("/health", () => Results.Ok("Service is healthy."));
|
||||
// Health check endpoint
|
||||
app.MapGet($"{basePath}/health", async () =>
|
||||
{
|
||||
var systemInfo = await _systemInfoService.GetSystemInfoAsync();
|
||||
var alerts = await _alertService.GetActiveAlertsAsync();
|
||||
|
||||
return Results.Ok(new
|
||||
{
|
||||
Status = "Healthy",
|
||||
Timestamp = DateTime.Now,
|
||||
Uptime = systemInfo.Uptime,
|
||||
ActiveAlerts = alerts.Count,
|
||||
MonitoringEnabled = new
|
||||
{
|
||||
GPU = _monitoringSettings.EnableGpuMonitoring,
|
||||
Disk = _monitoringSettings.EnableDiskMonitoring,
|
||||
Network = _monitoringSettings.EnableNetworkMonitoring,
|
||||
Temperature = _monitoringSettings.EnableTemperatureMonitoring,
|
||||
Processes = _monitoringSettings.EnableProcessMonitoring,
|
||||
Games = _monitoringSettings.EnableGameDetection,
|
||||
Alerts = _monitoringSettings.EnableAlerts
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Service control endpoints
|
||||
app.MapPost($"{basePath}/service/stop", () =>
|
||||
{
|
||||
_logger.LogWarning("Service stop requested via API");
|
||||
_lifetime.StopApplication();
|
||||
return Results.Ok(new { Message = "Stopping the service..." });
|
||||
});
|
||||
|
||||
_ = app.RunAsync(stoppingToken);
|
||||
// Configuration endpoint
|
||||
app.MapGet($"{basePath}/config", () => Results.Ok(new
|
||||
{
|
||||
MonitoringSettings = new
|
||||
{
|
||||
UpdateInterval = _monitoringSettings.UpdateIntervalMs,
|
||||
EnableGpuMonitoring = _monitoringSettings.EnableGpuMonitoring,
|
||||
EnableDiskMonitoring = _monitoringSettings.EnableDiskMonitoring,
|
||||
EnableNetworkMonitoring = _monitoringSettings.EnableNetworkMonitoring,
|
||||
EnableTemperatureMonitoring = _monitoringSettings.EnableTemperatureMonitoring,
|
||||
EnableProcessMonitoring = _monitoringSettings.EnableProcessMonitoring,
|
||||
EnableGameDetection = _monitoringSettings.EnableGameDetection,
|
||||
EnableAlerts = _monitoringSettings.EnableAlerts
|
||||
},
|
||||
ApiSettings = new
|
||||
{
|
||||
BasePath = _apiSettings.BasePath,
|
||||
RequireApiKey = _apiSettings.RequireApiKey,
|
||||
AllowedOrigins = _apiSettings.AllowedOrigins
|
||||
}
|
||||
}));
|
||||
|
||||
await Task.Delay(Timeout.Infinite, stoppingToken);
|
||||
// Service metrics endpoint
|
||||
app.MapGet($"{basePath}/metrics", async () =>
|
||||
{
|
||||
var usage = await _resourceMonitorService.GetResourceUsageAsync();
|
||||
var systemInfo = await _systemInfoService.GetSystemInfoAsync();
|
||||
return Results.Ok(new
|
||||
{
|
||||
Service = "Resource Monitor Service",
|
||||
Version = "2.0.0",
|
||||
Status = "Running",
|
||||
Timestamp = DateTime.Now,
|
||||
Uptime = systemInfo.Uptime,
|
||||
LastUpdate = usage.Timestamp,
|
||||
Performance = new
|
||||
{
|
||||
CPU = usage.CPU.Usage,
|
||||
Memory = usage.Memory.UsagePercentage,
|
||||
GPU = usage.GPU.Usage,
|
||||
ActiveProcesses = usage.TopProcesses.Count
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Root endpoint
|
||||
app.MapGet("/", () => Results.Ok(new
|
||||
{
|
||||
Service = "Resource Monitor Service for Unraid VM",
|
||||
Version = "2.0.0",
|
||||
Status = "Running",
|
||||
Timestamp = DateTime.Now,
|
||||
ApiBasePath = _apiSettings.BasePath,
|
||||
Endpoints = new[]
|
||||
{
|
||||
$"{_apiSettings.BasePath}/health",
|
||||
$"{_apiSettings.BasePath}/system-info",
|
||||
$"{_apiSettings.BasePath}/resource-usage",
|
||||
$"{_apiSettings.BasePath}/cpu-usage",
|
||||
$"{_apiSettings.BasePath}/memory-usage",
|
||||
$"{_apiSettings.BasePath}/gpu-usage",
|
||||
$"{_apiSettings.BasePath}/disk-usage",
|
||||
$"{_apiSettings.BasePath}/network-usage",
|
||||
$"{_apiSettings.BasePath}/top-processes",
|
||||
$"{_apiSettings.BasePath}/current-game",
|
||||
$"{_apiSettings.BasePath}/alerts/active",
|
||||
$"{_apiSettings.BasePath}/vm/info",
|
||||
$"{_apiSettings.BasePath}/config",
|
||||
$"{_apiSettings.BasePath}/metrics"
|
||||
}
|
||||
}));
|
||||
|
||||
_logger.LogInformation("API endpoints configured. Base path: {BasePath}", _apiSettings.BasePath);
|
||||
}
|
||||
|
||||
private object GetComputerInfo()
|
||||
private async Task BackgroundMonitoringLoop(CancellationToken cancellationToken)
|
||||
{
|
||||
return new
|
||||
{
|
||||
MachineName = Environment.MachineName,
|
||||
OSVersion = RuntimeInformation.OSDescription,
|
||||
OSArchitecture = RuntimeInformation.OSArchitecture.ToString(),
|
||||
ProcessorCount = Environment.ProcessorCount
|
||||
};
|
||||
}
|
||||
_logger.LogInformation("Background monitoring started");
|
||||
int errorCount = 0;
|
||||
int successfulCycles = 0;
|
||||
|
||||
private object GetCpuUsage()
|
||||
{
|
||||
#pragma warning disable CA1416 // Validate platform compatibility
|
||||
var cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
|
||||
#pragma warning restore CA1416 // Validate platform compatibility
|
||||
#pragma warning disable CA1416 // Validate platform compatibility
|
||||
cpuCounter.NextValue();
|
||||
#pragma warning restore CA1416 // Validate platform compatibility
|
||||
Thread.Sleep(1000); // Wait a second to get a valid reading
|
||||
|
||||
#pragma warning disable CA1416 // Validate platform compatibility
|
||||
var usage = cpuCounter.NextValue();
|
||||
#pragma warning restore CA1416 // Validate platform compatibility
|
||||
if (usage > 80)
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// Get the current processes and sort them by CPU usage in descending order
|
||||
var processes = Process.GetProcesses()
|
||||
.Select(p =>
|
||||
try
|
||||
{
|
||||
// Get current resource usage
|
||||
var resourceUsage = await _resourceMonitorService.GetResourceUsageAsync();
|
||||
|
||||
// Add current game info if game detection is enabled
|
||||
if (_monitoringSettings.EnableGameDetection)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new
|
||||
{
|
||||
Process = p,
|
||||
TotalProcessorTime = p.TotalProcessorTime
|
||||
};
|
||||
resourceUsage.RunningGame = await _gameDetectionService.GetCurrentlyRunningGameAsync();
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
return null; // Skip processes that throw exceptions
|
||||
}
|
||||
})
|
||||
.Where(p => p != null)
|
||||
.OrderByDescending(p => p.TotalProcessorTime)
|
||||
.Select(p => p.Process)
|
||||
.ToList();
|
||||
|
||||
// Create a new anonymous type containing the CPU usage, RAM usage, and the top 3 highest CPU-using processes
|
||||
return new
|
||||
{
|
||||
Usage = usage,
|
||||
Process1 = new
|
||||
{
|
||||
Name = processes.ElementAt(0).ProcessName,
|
||||
TotalProcessorTime = processes.ElementAt(0).TotalProcessorTime,
|
||||
WorkingSet64 = processes.ElementAt(0).WorkingSet64 / (1024 * 1024) // Convert to MB
|
||||
},
|
||||
Process2 = new
|
||||
{
|
||||
Name = processes.ElementAt(1).ProcessName,
|
||||
TotalProcessorTime = processes.ElementAt(1).TotalProcessorTime,
|
||||
WorkingSet64 = processes.ElementAt(1).WorkingSet64 / (1024 * 1024) // Convert to MB
|
||||
},
|
||||
Process3 = new
|
||||
{
|
||||
Name = processes.ElementAt(2).ProcessName,
|
||||
TotalProcessorTime = processes.ElementAt(2).TotalProcessorTime,
|
||||
WorkingSet64 = processes.ElementAt(2).WorkingSet64 / (1024 * 1024) // Convert to MB
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
Usage = usage
|
||||
};
|
||||
}
|
||||
|
||||
private float GetRamUsage()
|
||||
{
|
||||
#pragma warning disable CA1416 // Validate platform compatibility
|
||||
var ramCounter = new PerformanceCounter("Memory", "Available MBytes");
|
||||
#pragma warning restore CA1416 // Validate platform compatibility
|
||||
var totalMemory = GetTotalPhysicalMemory();
|
||||
#pragma warning disable CA1416 // Validate platform compatibility
|
||||
var availableMemory = ramCounter.NextValue() * 1024 * 1024;
|
||||
#pragma warning restore CA1416 // Validate platform compatibility
|
||||
return (float)(totalMemory - availableMemory) / totalMemory * 100;
|
||||
}
|
||||
|
||||
private ulong GetTotalPhysicalMemory()
|
||||
{
|
||||
ulong totalMemory = 0;
|
||||
#pragma warning disable CA1416 // Validate platform compatibility
|
||||
var searcher = new ManagementObjectSearcher("SELECT TotalPhysicalMemory FROM Win32_ComputerSystem");
|
||||
#pragma warning restore CA1416 // Validate platform compatibility
|
||||
#pragma warning disable CA1416 // Validate platform compatibility
|
||||
foreach (var obj in searcher.Get())
|
||||
{
|
||||
#pragma warning disable CA1416 // Validate platform compatibility
|
||||
totalMemory = (ulong)obj["TotalPhysicalMemory"];
|
||||
#pragma warning restore CA1416 // Validate platform compatibility
|
||||
}
|
||||
#pragma warning restore CA1416 // Validate platform compatibility
|
||||
return totalMemory;
|
||||
}
|
||||
|
||||
private object GetGpuUsage()
|
||||
{
|
||||
/* if (!IsNvidiaGpuPresent())
|
||||
{
|
||||
return new
|
||||
{
|
||||
Usage = 0,
|
||||
Temperature = 0,
|
||||
FanSpeed = 0,
|
||||
IsAvailable = false,
|
||||
Message = "No NVIDIA GPU detected"
|
||||
};
|
||||
} */
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
NvmlWrapper.NvmlInit();
|
||||
IntPtr device;
|
||||
NvmlWrapper.NvmlDeviceGetHandleByIndex(0, out device);
|
||||
NvmlWrapper.NvmlUtilization utilization;
|
||||
NvmlWrapper.NvmlDeviceGetUtilizationRates(device, out utilization);
|
||||
|
||||
uint temperature;
|
||||
NvmlWrapper.NvmlDeviceGetTemperature(device, 0, out temperature);
|
||||
|
||||
uint fanSpeed;
|
||||
NvmlWrapper.NvmlDeviceGetFanSpeed(device, out fanSpeed);
|
||||
|
||||
NvmlWrapper.NvmlShutdown();
|
||||
|
||||
return new
|
||||
{
|
||||
Usage = utilization.Gpu,
|
||||
Temperature = temperature,
|
||||
FanSpeed = fanSpeed,
|
||||
IsAvailable = false,
|
||||
Error = ""
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new
|
||||
{
|
||||
Usage = 0,
|
||||
Temperature = 0,
|
||||
FanSpeed = 0,
|
||||
IsAvailable = false,
|
||||
Error = ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/* private bool IsNvidiaGpuPresent()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Method 1: Try to initialize NVML
|
||||
NvmlWrapper.NvmlInit();
|
||||
uint deviceCount = 0;
|
||||
NvmlWrapper.NvmlDeviceGetCount(ref deviceCount);
|
||||
NvmlWrapper.NvmlShutdown();
|
||||
|
||||
return deviceCount > 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Method 2: Fallback to checking using WMI
|
||||
try
|
||||
{
|
||||
using (var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_VideoController WHERE Name LIKE '%NVIDIA%'"))
|
||||
{
|
||||
var collection = searcher.Get();
|
||||
return collection.Count > 0;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
private object GetCurrentlyRunningGame()
|
||||
{
|
||||
var processes = Process.GetProcesses();
|
||||
|
||||
foreach (var process in processes)
|
||||
{
|
||||
try
|
||||
{
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
var filePath = process.MainModule.FileName;
|
||||
#pragma warning restore CS8602 // Dereference of a possibly null reference.
|
||||
if (filePath.Contains(@"\steamapps\common\"))
|
||||
{
|
||||
// Extract the game directory name
|
||||
var parts = filePath.Split(new[] { @"\steamapps\common\" }, StringSplitOptions.None);
|
||||
if (parts.Length > 1)
|
||||
{
|
||||
var gamePath = parts[1];
|
||||
var gameName = gamePath.Split(Path.DirectorySeparatorChar)[0];
|
||||
return new
|
||||
// Only log game detection errors occasionally to avoid spam
|
||||
if (errorCount % 12 == 0) // Every minute if 5-second intervals
|
||||
{
|
||||
GameName = gameName,
|
||||
ExecutableName = Path.GetFileName(filePath),
|
||||
FullPath = filePath,
|
||||
ProcessId = process.Id,
|
||||
MemoryUsage = process.WorkingSet64 / (1024 * 1024) + " MB", // Memory usage in MB
|
||||
CpuTime = process.TotalProcessorTime.ToString(),
|
||||
StartTime = process.StartTime.ToString("G"), // General date/time pattern
|
||||
UserName = Environment.UserName // The user running the process
|
||||
};
|
||||
_logger.LogDebug("Game detection error (suppressed): {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for alerts
|
||||
if (_monitoringSettings.EnableAlerts)
|
||||
{
|
||||
await _alertService.CheckAndGenerateAlertsAsync(resourceUsage);
|
||||
}
|
||||
|
||||
successfulCycles++;
|
||||
|
||||
// Log performance metrics occasionally
|
||||
if (successfulCycles % 6 == 0) // Every 30 seconds with 5-second intervals
|
||||
{
|
||||
_logger.LogDebug("Performance: CPU: {CpuUsage:F1}%, Memory: {MemoryUsage:F1}%, GPU: {GpuUsage}%",
|
||||
resourceUsage.CPU.Usage,
|
||||
resourceUsage.Memory.UsagePercentage,
|
||||
resourceUsage.GPU.Usage);
|
||||
}
|
||||
|
||||
// Log successful monitoring occasionally for health verification
|
||||
if (successfulCycles % 120 == 0) // Every 10 minutes
|
||||
{
|
||||
_logger.LogInformation("Background monitoring healthy - completed {SuccessfulCycles} cycles", successfulCycles);
|
||||
}
|
||||
|
||||
errorCount = 0; // Reset error count on success
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle access exceptions or continue if not important
|
||||
errorCount++;
|
||||
|
||||
// Only log errors occasionally to avoid spam, but always log the first few
|
||||
if (errorCount <= 3 || errorCount % 12 == 0)
|
||||
{
|
||||
_logger.LogError(ex, "Error in background monitoring loop (occurrence #{ErrorCount})", errorCount);
|
||||
}
|
||||
|
||||
// If too many consecutive errors, increase delay
|
||||
if (errorCount > 10)
|
||||
{
|
||||
await Task.Delay(_monitoringSettings.UpdateIntervalMs * 2, cancellationToken);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(_monitoringSettings.UpdateIntervalMs, cancellationToken);
|
||||
}
|
||||
return "No Steam game is currently running.";
|
||||
}
|
||||
private string GetCurrentTime()
|
||||
{
|
||||
return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
|
||||
|
||||
_logger.LogInformation("Background monitoring stopped after {SuccessfulCycles} successful cycles", successfulCycles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user