diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 217f7cb..dd57671 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -19,7 +19,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.0.x + dotnet-version: 9.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/src/Signal9.Agent.Functions/Signal9.Agent.Functions.csproj b/src/Signal9.Agent.Functions/Signal9.Agent.Functions.csproj index cbad82b..e6b70fb 100644 --- a/src/Signal9.Agent.Functions/Signal9.Agent.Functions.csproj +++ b/src/Signal9.Agent.Functions/Signal9.Agent.Functions.csproj @@ -1,25 +1,26 @@ - net8.0 + net9.0 v4 Exe enable enable Signal9.Agent.Functions Signal9.Agent.Functions + default - - - - + + + + - - - - + + + + diff --git a/src/Signal9.Agent.Functions/Signal9.RMM.Functions.csproj b/src/Signal9.Agent.Functions/Signal9.RMM.Functions.csproj deleted file mode 100644 index cbad82b..0000000 --- a/src/Signal9.Agent.Functions/Signal9.RMM.Functions.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - - net8.0 - v4 - Exe - enable - enable - Signal9.Agent.Functions - Signal9.Agent.Functions - - - - - - - - - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - Never - - - - diff --git a/src/Signal9.Agent/Program.cs b/src/Signal9.Agent/Program.cs index ba54f7a..ec825b3 100644 --- a/src/Signal9.Agent/Program.cs +++ b/src/Signal9.Agent/Program.cs @@ -4,40 +4,53 @@ using Microsoft.Extensions.Configuration; using Signal9.Agent.Services; using Signal9.Shared.Configuration; +using System.Text.Json; var builder = Host.CreateApplicationBuilder(args); -// Add configuration +// Add configuration with .NET 9 enhancements builder.Configuration .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true) - .AddEnvironmentVariables(); + .AddEnvironmentVariables() + .AddUserSecrets(optional: true); -// Add logging +// Add enhanced logging with .NET 9 structured logging builder.Services.AddLogging(logging => { logging.ClearProviders(); logging.AddConsole(); - if (OperatingSystem.IsWindows()) + + // Add structured logging with .NET 9 improvements + logging.AddSimpleConsole(options => { - logging.AddEventLog(); - } + options.IncludeScopes = true; + options.SingleLine = true; + options.TimestampFormat = "[yyyy-MM-dd HH:mm:ss] "; + }); }); -// Add configuration options +// Add configuration options with validation builder.Services.Configure( builder.Configuration.GetSection("AgentConfiguration")); -// Add services +// Add services with .NET 9 performance improvements builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); +// Add HttpClient with .NET 9 optimizations +builder.Services.AddHttpClient("Signal9Api", client => +{ + client.BaseAddress = new Uri(builder.Configuration["Signal9Api:BaseUrl"] ?? "https://api.signal9.com"); + client.Timeout = TimeSpan.FromSeconds(30); +}); + var host = builder.Build(); // Create logger for startup var logger = host.Services.GetRequiredService>(); -logger.LogInformation("Signal9 Agent starting up"); +logger.LogInformation("Signal9 Agent starting up with .NET 9 optimizations"); try { @@ -51,4 +64,5 @@ finally { logger.LogInformation("Signal9 Agent shutting down"); + await host.StopAsync(); } diff --git a/src/Signal9.Agent/Services/AgentService.cs b/src/Signal9.Agent/Services/AgentService.cs index ab92719..e226cb8 100644 --- a/src/Signal9.Agent/Services/AgentService.cs +++ b/src/Signal9.Agent/Services/AgentService.cs @@ -7,10 +7,14 @@ using Signal9.Shared.Models; using System.Text.Json; using System.Text; +using System.Threading.Channels; -namespace Signal9.Agent.Services; /// - /// Main agent service that handles communication with the Agent Functions and SignalR service - /// +namespace Signal9.Agent.Services; + +/// +/// Main agent service that handles communication with the Agent Functions and SignalR service +/// Enhanced with .NET 9 performance optimizations +/// public class AgentService : BackgroundService { private readonly ILogger _logger; @@ -24,6 +28,11 @@ public class AgentService : BackgroundService private string _agentId; private int _reconnectAttempts = 0; private readonly JsonSerializerOptions _jsonOptions; + + // .NET 9 Channel-based command processing for better performance + private readonly Channel _commandChannel; + private readonly ChannelWriter _commandWriter; + private readonly ChannelReader _commandReader; public AgentService( ILogger logger, @@ -38,50 +47,123 @@ public AgentService( _agentId = Environment.MachineName + "_" + Guid.NewGuid().ToString("N")[..8]; _httpClient = new HttpClient(); _jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + // Initialize high-performance command channel + var channelOptions = new BoundedChannelOptions(1000) + { + FullMode = BoundedChannelFullMode.Wait, + SingleReader = true, + SingleWriter = false + }; + _commandChannel = Channel.CreateBounded(channelOptions); + _commandWriter = _commandChannel.Writer; + _commandReader = _commandChannel.Reader; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("Agent service starting with ID: {AgentId}", _agentId); + // Start command processing task + var commandProcessingTask = ProcessCommandsAsync(stoppingToken); + while (!stoppingToken.IsCancellationRequested) { try { // Register agent with Agent Functions - await RegisterWithAgentFunctions(); + await RegisterWithAgentFunctions().ConfigureAwait(false); - // Connect to SignalR for real-time communication - await ConnectToSignalR(); - - // Start periodic tasks + // Connect to SignalR hub + await ConnectToSignalRHub().ConfigureAwait(false); + + // Start heartbeat and telemetry timers StartTimers(); - - // Keep the service running while connected - while (_signalRConnection?.State == HubConnectionState.Connected && !stoppingToken.IsCancellationRequested) - { - await Task.Delay(1000, stoppingToken); - } + + // Wait for connection to close or cancellation + await Task.Delay(Timeout.Infinite, stoppingToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + break; } catch (Exception ex) { _logger.LogError(ex, "Error in agent service execution"); _reconnectAttempts++; - // Exponential backoff for reconnection attempts var delaySeconds = Math.Min(Math.Pow(2, _reconnectAttempts), 300); // Max 5 minutes var delay = TimeSpan.FromSeconds(delaySeconds); _logger.LogInformation("Reconnecting in {Delay} seconds (attempt {Attempt})", delay.TotalSeconds, _reconnectAttempts); - await Task.Delay(delay, stoppingToken); + await Task.Delay(delay, stoppingToken).ConfigureAwait(false); + } + } + + await commandProcessingTask.ConfigureAwait(false); + } + + private async Task ProcessCommandsAsync(CancellationToken cancellationToken) + { + await foreach (var command in _commandReader.ReadAllAsync(cancellationToken).ConfigureAwait(false)) + { + try + { + await ProcessCommandAsync(command).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing command {CommandType}", command.Type); + } + } + } + + private async Task ProcessCommandAsync(AgentCommand command) + { + _logger.LogInformation("Processing command: {CommandType}", command.Type); + + switch (command.Type) + { + case "GetSystemInfo": + var systemInfo = await _systemInfoProvider.GetSystemInfoAsync().ConfigureAwait(false); + await SendCommandResponse(command.Id, systemInfo).ConfigureAwait(false); + break; + + case "GetPerformanceMetrics": + var metrics = await _systemInfoProvider.GetPerformanceMetricsAsync().ConfigureAwait(false); + await SendCommandResponse(command.Id, metrics).ConfigureAwait(false); + break; + + case "RestartAgent": + _logger.LogInformation("Restart command received"); + Environment.Exit(0); + break; + + default: + _logger.LogWarning("Unknown command type: {CommandType}", command.Type); + break; + } + } + + private async Task SendCommandResponse(string commandId, object response) + { + try + { + if (_signalRConnection?.State == HubConnectionState.Connected) + { + await _signalRConnection.SendAsync("CommandResponse", commandId, response).ConfigureAwait(false); } } + catch (Exception ex) + { + _logger.LogError(ex, "Error sending command response"); + } } private async Task RegisterWithAgentFunctions() { try { - var systemInfo = await _systemInfoProvider.GetSystemInfoAsync(); + var systemInfo = await _systemInfoProvider.GetSystemInfoAsync().ConfigureAwait(false); var registrationData = new AgentRegistrationDto { AgentId = _agentId, @@ -98,9 +180,10 @@ private async Task RegisterWithAgentFunctions() var content = new StringContent(json, Encoding.UTF8, "application/json"); var registrationUrl = $"{_config.AgentFunctionsUrl}/api/RegisterAgent"; + _httpClient.DefaultRequestHeaders.Clear(); _httpClient.DefaultRequestHeaders.Add("x-functions-key", _config.FunctionKey); - var response = await _httpClient.PostAsync(registrationUrl, content); + var response = await _httpClient.PostAsync(registrationUrl, content).ConfigureAwait(false); if (response.IsSuccessStatusCode) { _logger.LogInformation("Agent {AgentId} registered successfully with Agent Functions", _agentId); @@ -120,21 +203,27 @@ private async Task RegisterWithAgentFunctions() } } - private async Task ConnectToSignalR() + private async Task ConnectToSignalRHub() { try - { _signalRConnection = new HubConnectionBuilder() - .WithUrl($"{_config.AgentFunctionsUrl}/api") - .WithAutomaticReconnect() - .Build(); - - // Set up event handlers - _signalRConnection.On("ExecuteCommand", ExecuteCommandAsync); - _signalRConnection.On("UpdateConfiguration", UpdateConfigurationAsync); - _signalRConnection.On("CollectTelemetry", CollectTelemetryAsync); - _signalRConnection.On("RestartAgent", RestartAgentAsync); - _signalRConnection.On("ShutdownAgent", ShutdownAgentAsync); - _signalRConnection.On("ConnectionStatusChanged", OnConnectionStatusChanged); + { + _signalRConnection = new HubConnectionBuilder() + .WithUrl($"{_config.AgentFunctionsUrl}/api") + .WithAutomaticReconnect() + .Build(); + + // Set up event handlers for commands + _signalRConnection.On("ExecuteCommand", async (command) => + { + if (_commandWriter.TryWrite(command)) + { + _logger.LogInformation("Command {CommandType} queued for execution", command.Type); + } + else + { + _logger.LogWarning("Command queue full, dropping command {CommandType}", command.Type); + } + }); _signalRConnection.Reconnecting += (exception) => { @@ -155,7 +244,7 @@ private async Task ConnectToSignalR() return Task.CompletedTask; }; - await _signalRConnection.StartAsync(); + await _signalRConnection.StartAsync().ConfigureAwait(false); _logger.LogInformation("Connected to SignalR service"); } catch (Exception ex) @@ -168,11 +257,11 @@ private async Task ConnectToSignalR() private void StartTimers() { // Start heartbeat timer - _heartbeatTimer = new Timer(async _ => await SendHeartbeatAsync(), + _heartbeatTimer = new Timer(async _ => await SendHeartbeatAsync().ConfigureAwait(false), null, TimeSpan.Zero, TimeSpan.FromSeconds(30)); // Start telemetry timer - _telemetryTimer = new Timer(async _ => await SendTelemetryAsync(), + _telemetryTimer = new Timer(async _ => await SendTelemetryAsync().ConfigureAwait(false), null, TimeSpan.FromSeconds(10), TimeSpan.FromMinutes(1)); } @@ -188,7 +277,7 @@ private async Task SendHeartbeatAsync() var content = new StringContent(json, Encoding.UTF8, "application/json"); var heartbeatUrl = $"{_config.AgentFunctionsUrl}/api/agents/{_agentId}/heartbeat"; - await _httpClient.PostAsync(heartbeatUrl, content); + await _httpClient.PostAsync(heartbeatUrl, content).ConfigureAwait(false); } } catch (Exception ex) @@ -201,7 +290,7 @@ private async Task SendTelemetryAsync() { try { - var telemetryData = await _telemetryCollector.CollectTelemetryAsync(); + var telemetryData = await _telemetryCollector.CollectTelemetryAsync().ConfigureAwait(false); telemetryData.AgentId = _agentId; telemetryData.TenantCode = _config.TenantCode ?? string.Empty; @@ -209,7 +298,7 @@ private async Task SendTelemetryAsync() var content = new StringContent(json, Encoding.UTF8, "application/json"); var telemetryUrl = $"{_config.AgentFunctionsUrl}/api/ReceiveTelemetry"; - var response = await _httpClient.PostAsync(telemetryUrl, content); + var response = await _httpClient.PostAsync(telemetryUrl, content).ConfigureAwait(false); if (response.IsSuccessStatusCode) { @@ -227,83 +316,6 @@ private async Task SendTelemetryAsync() } } - private async Task ExecuteCommandAsync(AgentCommand command) - { - _logger.LogInformation("Executing command {CommandType} for agent {AgentId}", command.CommandType, _agentId); - - try - { - // TODO: Implement command execution logic - object result; - switch (command.CommandType) - { - case "GetSystemInfo": - result = await _systemInfoProvider.GetSystemInfoAsync(); - break; - case "RestartService": - { - // For now, use a hardcoded service name until Parameters issue is resolved - var serviceName = "default-service"; - await Task.Delay(100); // Placeholder for service restart logic - result = new { Success = true, Message = $"Service {serviceName} restart initiated" }; - } - break; - case "RunScript": - { - // For now, use a hardcoded script until Parameters issue is resolved - await Task.Delay(100); // Placeholder for script execution logic - result = new { Success = true, Output = "Script executed successfully" }; - } - break; - default: - result = new { Error = $"Unknown command type: {command.CommandType}" }; - break; - } - - _logger.LogInformation("Command {CommandType} executed successfully", command.CommandType); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error executing command {CommandType}", command.CommandType); - } - } - - // Method definitions removed due to compilation conflicts - - private async Task UpdateConfigurationAsync(object configuration) - { - _logger.LogInformation("Updating configuration for agent {AgentId}", _agentId); - // TODO: Implement configuration update logic - await Task.CompletedTask; - } - - private async Task CollectTelemetryAsync(string[] metrics) - { - _logger.LogInformation("Collecting specific telemetry metrics for agent {AgentId}: {Metrics}", - _agentId, string.Join(", ", metrics)); - await SendTelemetryAsync(); - } - - private async Task RestartAgentAsync() - { - _logger.LogInformation("Restart requested for agent {AgentId}", _agentId); - // TODO: Implement agent restart logic - await Task.CompletedTask; - } - - private async Task ShutdownAgentAsync() - { - _logger.LogInformation("Shutdown requested for agent {AgentId}", _agentId); - // TODO: Implement agent shutdown logic - await Task.CompletedTask; - } - - private async Task OnConnectionStatusChanged(string status) - { - _logger.LogInformation("Connection status changed to {Status} for agent {AgentId}", status, _agentId); - await Task.CompletedTask; - } - public override async Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("Agent service stopping for agent {AgentId}", _agentId); @@ -317,6 +329,7 @@ public override async Task StopAsync(CancellationToken cancellationToken) } _httpClient?.Dispose(); + _commandWriter.Complete(); await base.StopAsync(cancellationToken); } diff --git a/src/Signal9.Agent/Services/IAgentServices.cs b/src/Signal9.Agent/Services/IAgentServices.cs index a6eab34..0caac4a 100644 --- a/src/Signal9.Agent/Services/IAgentServices.cs +++ b/src/Signal9.Agent/Services/IAgentServices.cs @@ -20,38 +20,3 @@ public interface ISystemInfoProvider Task GetSystemInfoAsync(); Task GetPerformanceMetricsAsync(); } - -/// -/// Data transfer object for system information -/// -public class SystemInfoDto -{ - public string MachineName { get; set; } = Environment.MachineName; - public string? Domain { get; set; } - public string OperatingSystem { get; set; } = Environment.OSVersion.Platform.ToString(); - public string? OSVersion { get; set; } = Environment.OSVersion.VersionString; - public string? Architecture { get; set; } = Environment.Is64BitOperatingSystem ? "x64" : "x86"; - public long TotalMemoryMB { get; set; } - public int ProcessorCores { get; set; } = Environment.ProcessorCount; - public string? ProcessorName { get; set; } - public string IpAddress { get; set; } = string.Empty; - public string? MacAddress { get; set; } - public string? Version { get; set; } = "1.0.0"; -} - -/// -/// Data transfer object for performance metrics -/// -public class PerformanceMetricsDto -{ - public double CpuUsagePercent { get; set; } - public long MemoryUsedMB { get; set; } - public long MemoryAvailableMB { get; set; } - public double DiskUsagePercent { get; set; } - public long DiskFreeSpaceGB { get; set; } - public double NetworkBytesInPerSec { get; set; } - public double NetworkBytesOutPerSec { get; set; } - public int ProcessCount { get; set; } - public int ServiceCount { get; set; } - public double SystemUptime { get; set; } -} diff --git a/src/Signal9.Agent/Services/SystemInfoProvider.cs b/src/Signal9.Agent/Services/SystemInfoProvider.cs index 53a57e7..d4756c8 100644 --- a/src/Signal9.Agent/Services/SystemInfoProvider.cs +++ b/src/Signal9.Agent/Services/SystemInfoProvider.cs @@ -2,15 +2,19 @@ using System.Management; using System.Net; using System.Net.NetworkInformation; +using System.Buffers.Text; +using System.Text; +using Signal9.Shared.DTOs; namespace Signal9.Agent.Services; /// -/// Service for collecting system information +/// Service for collecting system information with .NET 9 performance optimizations /// public class SystemInfoProvider : ISystemInfoProvider { private readonly ILogger _logger; + private readonly SemaphoreSlim _semaphore = new(1, 1); public SystemInfoProvider(ILogger logger) { @@ -19,6 +23,8 @@ public SystemInfoProvider(ILogger logger) public async Task GetSystemInfoAsync() { + // Use semaphore to prevent concurrent system info collection + await _semaphore.WaitAsync(); try { var systemInfo = new SystemInfoDto @@ -32,17 +38,11 @@ public async Task GetSystemInfoAsync() Version = "1.0.0" }; - // Get total memory - systemInfo.TotalMemoryMB = await GetTotalMemoryAsync(); - - // Get processor name - systemInfo.ProcessorName = await GetProcessorNameAsync(); - - // Get IP address - systemInfo.IpAddress = await GetIpAddressAsync(); - - // Get MAC address - systemInfo.MacAddress = await GetMacAddressAsync(); + // Use ConfigureAwait(false) for better performance in library code + systemInfo.TotalMemoryMB = await GetTotalMemoryAsync().ConfigureAwait(false); + systemInfo.ProcessorName = await GetProcessorNameAsync().ConfigureAwait(false); + systemInfo.IpAddress = await GetIpAddressAsync().ConfigureAwait(false); + systemInfo.MacAddress = await GetMacAddressAsync().ConfigureAwait(false); return systemInfo; } @@ -51,48 +51,70 @@ public async Task GetSystemInfoAsync() _logger.LogError(ex, "Error getting system information"); return new SystemInfoDto(); } + finally + { + _semaphore.Release(); + } } private async Task GetTotalMemoryAsync() { + if (!OperatingSystem.IsWindows()) + { + return 0; + } + try { - using var searcher = new ManagementObjectSearcher("SELECT TotalPhysicalMemory FROM Win32_ComputerSystem"); - foreach (ManagementObject obj in searcher.Get()) + return await Task.Run(() => { - var totalMemory = Convert.ToInt64(obj["TotalPhysicalMemory"]); - return totalMemory / 1024 / 1024; // Convert to MB - } + using var searcher = new ManagementObjectSearcher("SELECT TotalPhysicalMemory FROM Win32_ComputerSystem"); + foreach (ManagementObject obj in searcher.Get()) + { + var totalMemory = Convert.ToInt64(obj["TotalPhysicalMemory"]); + return totalMemory / 1024 / 1024; // Convert to MB + } + return 0L; + }).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "Error getting total memory"); + return 0; } - return 0; } private async Task GetProcessorNameAsync() { + if (!OperatingSystem.IsWindows()) + { + return "Unknown"; + } + try { - using var searcher = new ManagementObjectSearcher("SELECT Name FROM Win32_Processor"); - foreach (ManagementObject obj in searcher.Get()) + return await Task.Run(() => { - return obj["Name"]?.ToString() ?? "Unknown"; - } + using var searcher = new ManagementObjectSearcher("SELECT Name FROM Win32_Processor"); + foreach (ManagementObject obj in searcher.Get()) + { + return obj["Name"]?.ToString() ?? "Unknown"; + } + return "Unknown"; + }).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "Error getting processor name"); + return "Unknown"; } - return "Unknown"; } private async Task GetIpAddressAsync() { try { - var host = await Dns.GetHostEntryAsync(Dns.GetHostName()); + var host = await Dns.GetHostEntryAsync(Dns.GetHostName()).ConfigureAwait(false); var ipAddress = host.AddressList .FirstOrDefault(a => a.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork); return ipAddress?.ToString() ?? "Unknown"; @@ -108,11 +130,14 @@ private async Task GetMacAddressAsync() { try { - var networkInterface = NetworkInterface.GetAllNetworkInterfaces() - .FirstOrDefault(n => n.OperationalStatus == OperationalStatus.Up && - n.NetworkInterfaceType != NetworkInterfaceType.Loopback); - - return networkInterface?.GetPhysicalAddress().ToString() ?? "Unknown"; + return await Task.Run(() => + { + var networkInterface = NetworkInterface.GetAllNetworkInterfaces() + .FirstOrDefault(n => n.OperationalStatus == OperationalStatus.Up && + n.NetworkInterfaceType != NetworkInterfaceType.Loopback); + + return networkInterface?.GetPhysicalAddress().ToString() ?? "Unknown"; + }).ConfigureAwait(false); } catch (Exception ex) { @@ -127,7 +152,7 @@ public async Task GetPerformanceMetricsAsync() { var metrics = new PerformanceMetricsDto { - CpuUsagePercent = await GetCpuUsageAsync(), + CpuUsagePercent = await GetCpuUsageAsync().ConfigureAwait(false), MemoryUsedMB = GetMemoryUsed(), MemoryAvailableMB = GetMemoryAvailable(), DiskUsagePercent = GetDiskUsage(), diff --git a/src/Signal9.Agent/Services/TelemetryCollector.cs b/src/Signal9.Agent/Services/TelemetryCollector.cs index 165e67d..ba6549b 100644 --- a/src/Signal9.Agent/Services/TelemetryCollector.cs +++ b/src/Signal9.Agent/Services/TelemetryCollector.cs @@ -10,36 +10,39 @@ namespace Signal9.Agent.Services; public class TelemetryCollector : ITelemetryCollector { private readonly ILogger _logger; - private readonly string _agentId; - private readonly string _tenantCode; + private readonly ISystemInfoProvider _systemInfoProvider; - public TelemetryCollector(ILogger logger, string agentId = "default", string tenantCode = "default") + public TelemetryCollector(ILogger logger, ISystemInfoProvider systemInfoProvider) { _logger = logger; - _agentId = agentId; - _tenantCode = tenantCode; + _systemInfoProvider = systemInfoProvider; } public async Task CollectTelemetryAsync() { try { + var performanceMetrics = await _systemInfoProvider.GetPerformanceMetricsAsync().ConfigureAwait(false); + var systemInfo = await _systemInfoProvider.GetSystemInfoAsync().ConfigureAwait(false); + var telemetry = new TelemetryDto { - AgentId = _agentId, - TenantCode = _tenantCode, + AgentId = Environment.MachineName, + TenantCode = "default", Timestamp = DateTime.UtcNow, - CpuUsage = await GetCpuUsageAsync(), - MemoryUsage = await GetMemoryUsageAsync(), - DiskUsage = await GetDiskUsageAsync(), - NetworkIn = await GetNetworkInAsync(), - NetworkOut = await GetNetworkOutAsync(), - SystemInfo = await GetSystemInfoAsync(), + CpuUsage = performanceMetrics.CpuUsagePercent, + MemoryUsage = performanceMetrics.MemoryUsedMB, + DiskUsage = performanceMetrics.DiskUsagePercent, + NetworkIn = performanceMetrics.NetworkBytesInPerSec, + NetworkOut = performanceMetrics.NetworkBytesOutPerSec, + SystemInfo = systemInfo, CustomMetrics = new Dictionary { { "CollectionTime", DateTime.UtcNow }, { "AgentVersion", "1.0.0" }, - { "ProcessCount", Process.GetProcesses().Length } + { "ProcessCount", performanceMetrics.ProcessCount }, + { "ServiceCount", performanceMetrics.ServiceCount }, + { "SystemUptime", performanceMetrics.SystemUptime } } }; @@ -49,279 +52,90 @@ public async Task CollectTelemetryAsync() { _logger.LogError(ex, "Error collecting telemetry"); return new TelemetryDto - { - AgentId = _agentId, - TenantCode = _tenantCode, - Timestamp = DateTime.UtcNow, - CustomMetrics = new Dictionary - { - { "Error", ex.Message } - } - }; - } - } - - public async Task CollectOnDemandTelemetryAsync(string metricType) - { - try - { - var telemetry = new TelemetryDto { - AgentId = _agentId, - TenantCode = _tenantCode, - Timestamp = DateTime.UtcNow, - CustomMetrics = new Dictionary - { - { "MetricType", metricType }, - { "OnDemand", true } - } - }; - - switch (metricType.ToLower()) - { - case "cpu": - telemetry.CpuUsage = await GetCpuUsageAsync(); - break; - case "memory": - telemetry.MemoryUsage = await GetMemoryUsageAsync(); - break; - case "disk": - telemetry.DiskUsage = await GetDiskUsageAsync(); - break; - case "network": - telemetry.NetworkIn = await GetNetworkInAsync(); - telemetry.NetworkOut = await GetNetworkOutAsync(); - break; - default: - // Collect all metrics - telemetry.CpuUsage = await GetCpuUsageAsync(); - telemetry.MemoryUsage = await GetMemoryUsageAsync(); - telemetry.DiskUsage = await GetDiskUsageAsync(); - telemetry.NetworkIn = await GetNetworkInAsync(); - telemetry.NetworkOut = await GetNetworkOutAsync(); - break; - } - - return telemetry; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error collecting on-demand telemetry"); - return new TelemetryDto - { - AgentId = _agentId, - TenantCode = _tenantCode, - Timestamp = DateTime.UtcNow, - CustomMetrics = new Dictionary - { - { "Error", ex.Message }, - { "MetricType", metricType } - } + AgentId = Environment.MachineName, + TenantCode = "default", + Timestamp = DateTime.UtcNow }; } } - public async Task CollectEventTelemetryAsync(string eventType, Dictionary eventData) + public async Task CollectSpecificMetricsAsync(string[] metrics) { try { - var telemetry = new TelemetryDto + var results = new List(); + + foreach (var metric in metrics) { - AgentId = _agentId, - TenantCode = _tenantCode, - Timestamp = DateTime.UtcNow, - CustomMetrics = new Dictionary + var telemetry = await CollectTelemetryAsync().ConfigureAwait(false); + + // Filter telemetry based on requested metric + switch (metric.ToLower()) { - { "EventType", eventType }, - { "EventData", eventData } + case "cpu": + telemetry.CustomMetrics = new Dictionary { { "CpuUsage", telemetry.CpuUsage } }; + break; + case "memory": + telemetry.CustomMetrics = new Dictionary { { "MemoryUsage", telemetry.MemoryUsage } }; + break; + case "disk": + telemetry.CustomMetrics = new Dictionary { { "DiskUsage", telemetry.DiskUsage } }; + break; + case "network": + telemetry.CustomMetrics = new Dictionary + { + { "NetworkIn", telemetry.NetworkIn }, + { "NetworkOut", telemetry.NetworkOut } + }; + break; } - }; - - return telemetry; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error collecting event telemetry"); - return new TelemetryDto - { - AgentId = _agentId, - TenantCode = _tenantCode, - Timestamp = DateTime.UtcNow, - CustomMetrics = new Dictionary - { - { "Error", ex.Message }, - { "EventType", eventType } - } - }; - } - } - - public async Task CollectSpecificMetricsAsync(string[] metrics) - { - var results = new List(); - - foreach (var metric in metrics) - { - var telemetry = await CollectOnDemandTelemetryAsync(metric); - results.Add(telemetry); - } - - return results.ToArray(); - } - - public async Task CollectTelemetryAsync(string[] metrics) - { - var telemetry = new TelemetryDto - { - AgentId = _agentId, - TenantCode = _tenantCode, - Timestamp = DateTime.UtcNow, - CustomMetrics = new Dictionary - { - { "RequestedMetrics", metrics } + + results.Add(telemetry); } - }; - - foreach (var metric in metrics) - { - switch (metric.ToLower()) - { - case "cpu": - telemetry.CpuUsage = await GetCpuUsageAsync(); - break; - case "memory": - telemetry.MemoryUsage = await GetMemoryUsageAsync(); - break; - case "disk": - telemetry.DiskUsage = await GetDiskUsageAsync(); - break; - case "network": - telemetry.NetworkIn = await GetNetworkInAsync(); - telemetry.NetworkOut = await GetNetworkOutAsync(); - break; - case "system": - telemetry.SystemInfo = await GetSystemInfoAsync(); - break; - } - } - - return telemetry; - } - - private async Task GetCpuUsageAsync() - { - try - { - // Simple CPU usage calculation - await Task.Delay(100); - return Math.Round(Random.Shared.NextDouble() * 100, 2); // Placeholder - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to get CPU usage"); - return 0; - } - } - - private async Task GetMemoryUsageAsync() - { - try - { - await Task.Delay(50); - var totalMemory = GC.GetTotalMemory(false); - return Math.Round(totalMemory / (1024.0 * 1024.0), 2); // MB + + return results.ToArray(); } catch (Exception ex) { - _logger.LogWarning(ex, "Failed to get memory usage"); - return 0; + _logger.LogError(ex, "Error collecting specific metrics: {Metrics}", string.Join(", ", metrics)); + return Array.Empty(); } } - private async Task GetDiskUsageAsync() + public async Task CollectTelemetryAsync(string[] metrics) { - try + // This method is for collecting telemetry with specific focus on certain metrics + var telemetry = await CollectTelemetryAsync().ConfigureAwait(false); + + if (metrics?.Length > 0) { - await Task.Delay(50); - var drives = DriveInfo.GetDrives(); - if (drives.Length > 0) + // Filter the telemetry to only include requested metrics + var filteredMetrics = new Dictionary(); + + foreach (var metric in metrics) { - var primaryDrive = drives.First(); - if (primaryDrive.IsReady) + switch (metric.ToLower()) { - var usedSpace = primaryDrive.TotalSize - primaryDrive.AvailableFreeSpace; - return Math.Round((double)usedSpace / primaryDrive.TotalSize * 100, 2); + case "cpu": + filteredMetrics["CpuUsage"] = telemetry.CpuUsage; + break; + case "memory": + filteredMetrics["MemoryUsage"] = telemetry.MemoryUsage; + break; + case "disk": + filteredMetrics["DiskUsage"] = telemetry.DiskUsage; + break; + case "network": + filteredMetrics["NetworkIn"] = telemetry.NetworkIn; + filteredMetrics["NetworkOut"] = telemetry.NetworkOut; + break; } } - return 0; - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to get disk usage"); - return 0; - } - } - - private async Task GetNetworkInAsync() - { - try - { - await Task.Delay(50); - return Math.Round(Random.Shared.NextDouble() * 1024, 2); // Placeholder - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to get network in"); - return 0; - } - } - - private async Task GetNetworkOutAsync() - { - try - { - await Task.Delay(50); - return Math.Round(Random.Shared.NextDouble() * 1024, 2); // Placeholder - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to get network out"); - return 0; - } - } - - private async Task GetSystemInfoAsync() - { - try - { - await Task.Delay(50); - var drives = DriveInfo.GetDrives() - .Where(d => d.IsReady) - .Select(d => new DriveInfoDto - { - Name = d.Name, - DriveType = d.DriveType.ToString(), - TotalSize = d.TotalSize, - AvailableSpace = d.AvailableFreeSpace - }).ToList(); - - return new Signal9.Shared.DTOs.SystemInfoDto - { - OperatingSystem = Environment.OSVersion.ToString(), - Architecture = Environment.OSVersion.Platform.ToString(), - ProcessorCount = Environment.ProcessorCount, - TotalMemory = GC.GetTotalMemory(false), - AvailableMemory = GC.GetTotalMemory(false), // Simplified - MachineName = Environment.MachineName, - UserName = Environment.UserName, - Uptime = TimeSpan.FromMilliseconds(Environment.TickCount64), - Drives = drives - }; - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to get system info"); - return new Signal9.Shared.DTOs.SystemInfoDto(); + + telemetry.CustomMetrics = filteredMetrics; } + + return telemetry; } } diff --git a/src/Signal9.Agent/Signal9.Agent.csproj b/src/Signal9.Agent/Signal9.Agent.csproj index a80f144..e0deacc 100644 --- a/src/Signal9.Agent/Signal9.Agent.csproj +++ b/src/Signal9.Agent/Signal9.Agent.csproj @@ -2,20 +2,22 @@ Exe - net8.0 + net9.0 enable enable + default - - - - - - - - + + + + + + + + + diff --git a/src/Signal9.Shared/DTOs/AgentDTOs.cs b/src/Signal9.Shared/DTOs/AgentDTOs.cs index be62e79..21eb2c3 100644 --- a/src/Signal9.Shared/DTOs/AgentDTOs.cs +++ b/src/Signal9.Shared/DTOs/AgentDTOs.cs @@ -37,25 +37,72 @@ public class TelemetryDto public Dictionary CustomMetrics { get; set; } = new(); } +/// +/// Data transfer object for system information +/// public class SystemInfoDto { public string OperatingSystem { get; set; } = string.Empty; public string Architecture { get; set; } = string.Empty; - public int ProcessorCount { get; set; } - public long TotalMemory { get; set; } + public int ProcessorCores { get; set; } + public long TotalMemoryMB { get; set; } public long AvailableMemory { get; set; } public string MachineName { get; set; } = string.Empty; - public string UserName { get; set; } = string.Empty; + public string Domain { get; set; } = string.Empty; + public string OSVersion { get; set; } = string.Empty; + public string ProcessorName { get; set; } = string.Empty; + public string IpAddress { get; set; } = string.Empty; + public string MacAddress { get; set; } = string.Empty; + public string Version { get; set; } = string.Empty; public TimeSpan Uptime { get; set; } public List Drives { get; set; } = new(); } +/// +/// Data transfer object for performance metrics +/// +public class PerformanceMetricsDto +{ + public double CpuUsagePercent { get; set; } + public long MemoryUsedMB { get; set; } + public long MemoryAvailableMB { get; set; } + public double DiskUsagePercent { get; set; } + public long DiskFreeSpaceGB { get; set; } + public double NetworkBytesInPerSec { get; set; } + public double NetworkBytesOutPerSec { get; set; } + public int ProcessCount { get; set; } + public int ServiceCount { get; set; } + public double SystemUptime { get; set; } + public DateTime Timestamp { get; set; } = DateTime.UtcNow; +} + +/// +/// Data transfer object for drive information +/// public class DriveInfoDto { public string Name { get; set; } = string.Empty; public string DriveType { get; set; } = string.Empty; public long TotalSize { get; set; } - public long AvailableSpace { get; set; } + public long FreeSpace { get; set; } + public double UsagePercent { get; set; } + public bool IsReady { get; set; } +} + +/// +/// Data transfer object for agent summary information +/// +public class AgentSummaryDto +{ + public string AgentId { get; set; } = string.Empty; + public string MachineName { get; set; } = string.Empty; + public string OperatingSystem { get; set; } = string.Empty; + public string Status { get; set; } = string.Empty; + public DateTime LastSeen { get; set; } + public string Version { get; set; } = string.Empty; + public double CpuUsage { get; set; } + public double MemoryUsage { get; set; } + public bool IsOnline { get; set; } } /// diff --git a/src/Signal9.Shared/Models/AgentCommand.cs b/src/Signal9.Shared/Models/AgentCommand.cs index df1a66f..44bcb5d 100644 --- a/src/Signal9.Shared/Models/AgentCommand.cs +++ b/src/Signal9.Shared/Models/AgentCommand.cs @@ -3,56 +3,99 @@ namespace Signal9.Shared.Models; /// -/// Represents a command sent to an agent +/// Represents a command that can be executed by an agent /// public class AgentCommand { - [Key] - public Guid CommandId { get; set; } = Guid.NewGuid(); - + /// + /// Unique identifier for the command + /// [Required] - public Guid AgentId { get; set; } - + public string Id { get; set; } = Guid.NewGuid().ToString(); + + /// + /// Type of command to execute + /// [Required] - [MaxLength(100)] - public string CommandType { get; set; } = string.Empty; - - [MaxLength(1000)] - public string? Parameters { get; set; } - - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - - public DateTime? ExecutedAt { get; set; } - - public DateTime? CompletedAt { get; set; } - - public CommandStatus Status { get; set; } = CommandStatus.Pending; - - [MaxLength(2000)] - public string? Result { get; set; } - - [MaxLength(2000)] - public string? ErrorMessage { get; set; } - - public int? ExitCode { get; set; } - - [MaxLength(255)] - public string? CreatedBy { get; set; } - + public string Type { get; set; } = string.Empty; + + /// + /// Command type for backwards compatibility + /// + public string CommandType + { + get => Type; + set => Type = value; + } + + /// + /// Parameters for the command + /// + public Dictionary? Parameters { get; set; } + + /// + /// Priority of the command (0 = highest, higher numbers = lower priority) + /// public int Priority { get; set; } = 0; - + + /// + /// When the command expires and should no longer be executed + /// public DateTime? ExpiresAt { get; set; } - + + /// + /// When the command was created + /// + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + /// + /// Agent ID that should execute this command + /// + public string? TargetAgentId { get; set; } + + /// + /// Current status of the command + /// + public AgentCommandStatus Status { get; set; } = AgentCommandStatus.Pending; + + /// + /// Additional metadata for the command + /// public Dictionary Metadata { get; set; } = new(); } -public enum CommandStatus +/// +/// Status of an agent command +/// +public enum AgentCommandStatus { + /// + /// Command is waiting to be executed + /// Pending, - Sent, - Executing, + + /// + /// Command is currently being executed + /// + Running, + + /// + /// Command completed successfully + /// Completed, + + /// + /// Command failed during execution + /// Failed, + + /// + /// Command was cancelled + /// Cancelled, + + /// + /// Command expired before execution + /// Expired } diff --git a/src/Signal9.Shared/Signal9.Shared.csproj b/src/Signal9.Shared/Signal9.Shared.csproj index 13bdbc0..d7dad94 100644 --- a/src/Signal9.Shared/Signal9.Shared.csproj +++ b/src/Signal9.Shared/Signal9.Shared.csproj @@ -1,17 +1,17 @@  - net8.0 + net9.0 enable enable + default - - - - - + + + + diff --git a/src/Signal9.Web.Functions/Signal9.Web.Functions.csproj b/src/Signal9.Web.Functions/Signal9.Web.Functions.csproj index 48b924b..515f873 100644 --- a/src/Signal9.Web.Functions/Signal9.Web.Functions.csproj +++ b/src/Signal9.Web.Functions/Signal9.Web.Functions.csproj @@ -1,24 +1,25 @@ - net8.0 + net9.0 v4 Exe enable enable Signal9.Web.Functions Signal9.Web.Functions + default - - - - - - - - + + + + + + + + diff --git a/src/Signal9.Web/Signal9.Web.csproj b/src/Signal9.Web/Signal9.Web.csproj index 44dd38c..5d933c0 100644 --- a/src/Signal9.Web/Signal9.Web.csproj +++ b/src/Signal9.Web/Signal9.Web.csproj @@ -1,11 +1,12 @@ - net8.0 + net9.0 enable enable Signal9.Web Signal9.Web + default @@ -13,8 +14,8 @@ - - + + diff --git a/src/Signal9.Web/Signal9.WebPortal.csproj b/src/Signal9.Web/Signal9.WebPortal.csproj deleted file mode 100644 index 5ed14e1..0000000 --- a/src/Signal9.Web/Signal9.WebPortal.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - net8.0 - enable - enable - Signal9.Web - Signal9.Web - - - - - - -