Files
Finlytic/FinlyticBackend/Services/SystemHealthBackgroundService.cs
T

132 lines
5.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FinlyticBackend.Controllers;
using FinlyticBackend.Hubs;
using FinlyticBackend.Util;
using FinlyticCore.Dtos;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace FinlyticBackend.Services;
/// <summary>
/// Background service that continuously executes live MQTT RPC health pings across all microservices every 5 seconds
/// and broadcasts real-time health diagnostic updates to all connected SignalR clients on SystemHealthHub (AOT-compliant).
/// </summary>
public class SystemHealthBackgroundService : BackgroundService
{
private readonly IHubContext<SystemHealthHub> _hubContext;
private readonly WebMqttClient _mqttClient;
private readonly ILogger<SystemHealthBackgroundService> _logger;
public SystemHealthBackgroundService(
IHubContext<SystemHealthHub> hubContext,
WebMqttClient mqttClient,
ILogger<SystemHealthBackgroundService> logger)
{
_hubContext = hubContext;
_mqttClient = mqttClient;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("[SystemHealthBackgroundService] Started periodic MQTT RPC health pings (Interval: 5s).");
// Initial delay to allow MQTT client to establish connection
await Task.Delay(3000, stoppingToken);
while (!stoppingToken.IsCancellationRequested)
{
try
{
var healthData = await PerformHealthCheckAsync();
await _hubContext.Clients.All.SendAsync("ReceiveSystemHealth", healthData, cancellationToken: stoppingToken);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "[SystemHealthBackgroundService] Error performing system health check broadcast.");
}
await Task.Delay(5000, stoppingToken);
}
}
/// <summary>
/// Executes health check pings and returns AOT-compliant DTOs.
/// </summary>
public async Task<List<ServiceHealthStatusDto>> PerformHealthCheckAsync()
{
var servicesToCheck = new (string Name, string Channel, string Type, string Db)[]
{
("FinlyticAssets", "health_Ping/FinlyticAssets", "Asset Catalog & Scraper", "PostgreSQL assets"),
("FinlyticNews", "health_Ping/FinlyticNews", "News RSS Scraper & AI", "PostgreSQL news"),
("FinlyticTechnicalAnalysis", "health_Ping/FinlyticTechnicalAnalysis", "Technical Indicators (EMA/RSI)", "PostgreSQL ta"),
("FinlyticSentiment", "health_Ping/FinlyticSentiment", "NLP Sentiment Engine", "PostgreSQL sentiment"),
("FinlyticAnalyzer", "health_Ping/FinlyticAnalyzer", "Multi-Layer Signal Engine", "PostgreSQL analyzer"),
("FinlyticTrades", "health_Ping/FinlyticTrades", "Trade Lifecycle Manager", "PostgreSQL trades"),
("FinlyticFundamentals", "health_Ping/FinlyticFundamentals", "Financial Statements & Estimates", "PostgreSQL fundamentals"),
};
var results = new List<ServiceHealthStatusDto>
{
new(
Name: "FinlyticBackend",
Type: "REST API & SignalR Gateway",
Status: "Online",
Port: "5000",
Communication: "Kestrel HTTP / WebSocket",
Db: "PostgreSQL backend",
LastPing: DateTime.UtcNow
)
};
var tasks = servicesToCheck.Select(async s =>
{
try
{
if (_mqttClient.IsConnected)
{
var resp = await _mqttClient.SendRpcRequestAsync<ServiceHealthResponse, EmptyRequest>(
s.Channel,
new EmptyRequest(),
TimeSpan.FromMilliseconds(1200)
);
if (resp != null)
{
return new ServiceHealthStatusDto(
Name: s.Name,
Type: s.Type,
Status: "Online",
Port: "MQTT Only (No HTTP Port)",
Communication: "MQTT RPC & Pub/Sub",
Db: s.Db,
LastPing: resp.Timestamp
);
}
}
}
catch { }
return new ServiceHealthStatusDto(
Name: s.Name,
Type: s.Type,
Status: "Offline",
Port: "MQTT Only (No HTTP Port)",
Communication: "MQTT (No Response / Timeout)",
Db: s.Db,
LastPing: DateTime.UtcNow
);
});
var pingResults = await Task.WhenAll(tasks);
results.AddRange(pingResults);
return results;
}
}