using ErsatzTV.Core.Health; using ErsatzTV.Core.Health.Checks; using MediatR; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; namespace ErsatzTV.Infrastructure.Health; public class HealthCheckService : IHealthCheckService { private const string CacheKey = "healthcheck.summary"; private readonly List _checks; // ReSharper disable SuggestBaseTypeForParameterInConstructor private readonly IMemoryCache _memoryCache; private readonly IMediator _mediator; private readonly ILogger _logger; public HealthCheckService( IMacOsConfigFolderHealthCheck macOsConfigFolderHealthCheck, IFFmpegVersionHealthCheck ffmpegVersionHealthCheck, IFFmpegReportsHealthCheck ffmpegReportsHealthCheck, IHardwareAccelerationHealthCheck hardwareAccelerationHealthCheck, IMovieMetadataHealthCheck movieMetadataHealthCheck, IEpisodeMetadataHealthCheck episodeMetadataHealthCheck, IZeroDurationHealthCheck zeroDurationHealthCheck, IFileNotFoundHealthCheck fileNotFoundHealthCheck, IUnavailableHealthCheck unavailableHealthCheck, IVaapiDriverHealthCheck vaapiDriverHealthCheck, IErrorReportsHealthCheck errorReportsHealthCheck, IUnifiedDockerHealthCheck unifiedDockerHealthCheck, IMemoryCache memoryCache, IMediator mediator, ILogger logger) { _memoryCache = memoryCache; _mediator = mediator; _logger = logger; _checks = [ macOsConfigFolderHealthCheck, unifiedDockerHealthCheck, ffmpegVersionHealthCheck, ffmpegReportsHealthCheck, hardwareAccelerationHealthCheck, movieMetadataHealthCheck, episodeMetadataHealthCheck, zeroDurationHealthCheck, fileNotFoundHealthCheck, unavailableHealthCheck, vaapiDriverHealthCheck, errorReportsHealthCheck ]; } public async Task> PerformHealthChecks(CancellationToken cancellationToken) { List result = await _checks.Map(c => { var failedResult = new HealthCheckResult( c.Title, HealthCheckStatus.Fail, "Health check failure; see logs", "Health check failure", None); return TryAsync(() => c.Check(cancellationToken)).IfFail(ex => LogAndReturn(ex, failedResult)); }) .SequenceParallel() .Map(results => results.ToList()); var summary = new HealthCheckSummary( result.Count(x => x.Status is HealthCheckStatus.Warning), result.Count(x => x.Status is HealthCheckStatus.Fail)); _memoryCache.Set(CacheKey, summary); await _mediator.Publish(summary, cancellationToken); return result; } public HealthCheckSummary GetHealthCheckSummary() => _memoryCache.Get(CacheKey) ?? new HealthCheckSummary(0, 0); private HealthCheckResult LogAndReturn(Exception ex, HealthCheckResult failedResult) { if (ex is not OperationCanceledException) { _logger.LogWarning(ex, "Failed to run health check {Title}", failedResult.Title); } return failedResult; } }