From 32c431fd526eeae822d45ff2cdd21a598b4d1f80 Mon Sep 17 00:00:00 2001
From: Woody <2997336+MWGMorningwood@users.noreply.github.com>
Date: Sat, 12 Jul 2025 00:24:39 -0400
Subject: [PATCH 1/3] Enhance Agent Service with .NET 9 optimizations and
command processing
- Updated AgentService to utilize Channel for command processing, improving performance and responsiveness.
- Refactored command handling logic to support new command types and improved error handling.
- Introduced SystemInfoProvider enhancements for better system information retrieval with semaphore for concurrency control.
- Updated TelemetryCollector to streamline telemetry data collection and support specific metrics requests.
- Migrated project to .NET 9, updating package references and ensuring compatibility with new features.
- Removed obsolete DTOs and consolidated data transfer objects for system information and performance metrics.
- Improved logging and error handling throughout the service for better observability.
---
.../Signal9.Agent.Functions.csproj | 11 +-
src/Signal9.Agent/Program.cs | 32 +-
src/Signal9.Agent/Services/AgentService.cs | 241 +++++++------
src/Signal9.Agent/Services/IAgentServices.cs | 35 --
.../Services/SystemInfoProvider.cs | 85 +++--
.../Services/TelemetryCollector.cs | 340 ++++--------------
src/Signal9.Agent/Signal9.Agent.csproj | 18 +-
src/Signal9.Shared/DTOs/AgentDTOs.cs | 55 ++-
src/Signal9.Shared/Models/AgentCommand.cs | 115 ++++--
src/Signal9.Shared/Signal9.Shared.csproj | 4 +-
.../Signal9.Web.Functions.csproj | 11 +-
src/Signal9.Web/Signal9.Web.csproj | 3 +-
12 files changed, 442 insertions(+), 508 deletions(-)
diff --git a/src/Signal9.Agent.Functions/Signal9.Agent.Functions.csproj b/src/Signal9.Agent.Functions/Signal9.Agent.Functions.csproj
index cbad82b..deb883e 100644
--- a/src/Signal9.Agent.Functions/Signal9.Agent.Functions.csproj
+++ b/src/Signal9.Agent.Functions/Signal9.Agent.Functions.csproj
@@ -1,24 +1,29 @@
- net8.0
+ net9.0
v4
Exe
enable
enable
Signal9.Agent.Functions
Signal9.Agent.Functions
+ default
-
-
+
+
+<<<<<<< Updated upstream
+=======
+
+>>>>>>> Stashed changes
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
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
-
-
-
-
-
-
-