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; /// /// 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). /// public class SystemHealthBackgroundService : BackgroundService { private readonly IHubContext _hubContext; private readonly WebMqttClient _mqttClient; private readonly ILogger _logger; public SystemHealthBackgroundService( IHubContext hubContext, WebMqttClient mqttClient, ILogger 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); } } /// /// Executes health check pings and returns AOT-compliant DTOs. /// public async Task> 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"), ("FinlyticTechnicals", "health_Ping/FinlyticTechnicals", "Technical Indicators & SMC Patterns", "PostgreSQL ta"), ("FinlyticSentiment", "health_Ping/FinlyticSentiment", "NLP Sentiment Engine", "PostgreSQL sentiment"), ("FinlyticFundamentals", "health_Ping/FinlyticFundamentals", "Financial Statements & Estimates", "PostgreSQL fundamentals"), ("FinlyticEngine", "health_Ping/FinlyticEngine", "Strategy Screener & Signals", "PostgreSQL engine"), ("FinlyticBot", "health_Ping/FinlyticBot", "Automated Trading Execution", "PostgreSQL bot"), ("FinlyticNotify", "health_Ping/FinlyticNotify", "ntfy Push Notifications", "PostgreSQL / Mosquitto"), }; var results = new List { 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( 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; } }