@ -1,11 +1,11 @@
using System ;
using System.Collections.Generic ;
using System.Linq ;
using NLog ;
using NzbDrone.Common.Cache ;
using NzbDrone.Common.EnvironmentInfo ;
using NzbDrone.Common.Messaging ;
using NzbDrone.Common.Reflection ;
using NzbDrone.Common.TPL ;
using NzbDrone.Core.Lifecycle ;
using NzbDrone.Core.Messaging.Commands ;
using NzbDrone.Core.Messaging.Events ;
@ -27,30 +27,27 @@ namespace NzbDrone.Core.HealthCheck
private readonly IProvideHealthCheck [ ] _startupHealthChecks ;
private readonly IProvideHealthCheck [ ] _scheduledHealthChecks ;
private readonly Dictionary < Type , IEventDrivenHealthCheck [ ] > _eventDrivenHealthChecks ;
private readonly IServerSideNotificationService _serverSideNotificationService ;
private readonly IEventAggregator _eventAggregator ;
private readonly ICacheManager _cacheManager ;
private readonly Logger _logger ;
private readonly ICached < HealthCheck > _healthCheckResults ;
private readonly HashSet < IProvideHealthCheck > _pendingHealthChecks ;
private readonly Debouncer _debounce ;
private bool _hasRunHealthChecksAfterGracePeriod ;
private bool _isRunningHealthChecksAfterGracePeriod ;
public HealthCheckService ( IEnumerable < IProvideHealthCheck > healthChecks ,
IServerSideNotificationService serverSideNotificationService ,
IEventAggregator eventAggregator ,
ICacheManager cacheManager ,
I RuntimeInfo runtimeInfo ,
Logger logger )
I DebounceManager debounceManager ,
IRuntimeInfo runtimeInfo )
{
_healthChecks = healthChecks . ToArray ( ) ;
_serverSideNotificationService = serverSideNotificationService ;
_eventAggregator = eventAggregator ;
_cacheManager = cacheManager ;
_logger = logger ;
_healthCheckResults = _cacheManager . GetCache < HealthCheck > ( GetType ( ) ) ;
_healthCheckResults = cacheManager . GetCache < HealthCheck > ( GetType ( ) ) ;
_pendingHealthChecks = new HashSet < IProvideHealthCheck > ( ) ;
_debounce = debounceManager . CreateDebouncer ( ProcessHealthChecks , TimeSpan . FromSeconds ( 5 ) ) ;
_startupHealthChecks = _healthChecks . Where ( v = > v . CheckOnStartup ) . ToArray ( ) ;
_scheduledHealthChecks = _healthChecks . Where ( v = > v . CheckOnSchedule ) . ToArray ( ) ;
@ -77,74 +74,86 @@ namespace NzbDrone.Core.HealthCheck
. ToDictionary ( g = > g . Key , g = > g . ToArray ( ) ) ;
}
private void P erformHealthCheck( IProvideHealthCheck [ ] healthChecks , IEvent message = null , bool performServerChecks = false )
private void P rocessHealthChecks( )
{
var results = new List < HealthCheck > ( ) ;
List < IProvideHealthCheck > healthChecks ;
foreach ( var healthCheck in h ealthChecks)
lock ( _pendingH ealthChecks)
{
if ( healthCheck is IProvideHealthCheckWithMessage & & message ! = null )
{
results . Add ( ( ( IProvideHealthCheckWithMessage ) healthCheck ) . Check ( message ) ) ;
}
else
{
results . Add ( healthCheck . Check ( ) ) ;
}
healthChecks = _pendingHealthChecks . ToList ( ) ;
_pendingHealthChecks . Clear ( ) ;
}
if ( performServerChecks )
{
results . AddRange ( _serverSideNotificationService . GetServerChecks ( ) ) ;
}
_debounce . Pause ( ) ;
foreach ( var result in results )
try
{
if ( result . Type = = HealthCheckResult . Ok )
{
var previous = _healthCheckResults . Find ( result . Source . Name ) ;
if ( previous ! = null )
{
_eventAggregator . PublishEvent ( new HealthCheckRestoredEvent ( previous , ! _hasRunHealthChecksAfterGracePeriod ) ) ;
}
var results = healthChecks . Select ( c = > c . Check ( ) )
. ToList ( ) ;
_healthCheckResults . Remove ( result . Source . Name ) ;
}
else
foreach ( var result in results )
{
if ( _healthCheckResults. Find ( result . Source . Name ) = = null )
if ( result . Type = = HealthCheckResult . Ok )
{
_eventAggregator . PublishEvent ( new HealthCheckFailedEvent ( result , ! _hasRunHealthChecksAfterGracePeriod ) ) ;
var previous = _healthCheckResults . Find ( result . Source . Name ) ;
if ( previous ! = null )
{
_eventAggregator . PublishEvent ( new HealthCheckRestoredEvent ( previous , ! _hasRunHealthChecksAfterGracePeriod ) ) ;
}
_healthCheckResults . Remove ( result . Source . Name ) ;
}
else
{
if ( _healthCheckResults . Find ( result . Source . Name ) = = null )
{
_eventAggregator . PublishEvent ( new HealthCheckFailedEvent ( result , ! _hasRunHealthChecksAfterGracePeriod ) ) ;
}
_healthCheckResults . Set ( result . Source . Name , result ) ;
_healthCheckResults . Set ( result . Source . Name , result ) ;
}
}
}
finally
{
_debounce . Resume ( ) ;
}
_eventAggregator . PublishEvent ( new HealthCheckCompleteEvent ( ) ) ;
}
public void Execute ( CheckHealthCommand message )
{
if ( message . Trigger = = CommandTrigger . Manual )
{
PerformHealthCheck ( _healthChecks , null , true ) ;
}
else
var healthChecks = message . Trigger = = CommandTrigger . Manual ? _healthChecks : _scheduledHealthChecks ;
lock ( _pendingHealthChecks )
{
PerformHealthCheck ( _scheduledHealthChecks , null , true ) ;
foreach ( var healthCheck in healthChecks )
{
_pendingHealthChecks . Add ( healthCheck ) ;
}
}
ProcessHealthChecks ( ) ;
}
public void HandleAsync ( ApplicationStartedEvent message )
{
PerformHealthCheck ( _startupHealthChecks , null , true ) ;
lock ( _pendingHealthChecks )
{
foreach ( var healthCheck in _startupHealthChecks )
{
_pendingHealthChecks . Add ( healthCheck ) ;
}
}
ProcessHealthChecks ( ) ;
}
public void HandleAsync ( IEvent message )
{
if ( message is HealthCheckCompleteEvent )
if ( message is HealthCheckCompleteEvent | | message is ApplicationStartedEvent )
{
return ;
}
@ -155,7 +164,16 @@ namespace NzbDrone.Core.HealthCheck
{
_isRunningHealthChecksAfterGracePeriod = true ;
PerformHealthCheck ( _startupHealthChecks ) ;
lock ( _pendingHealthChecks )
{
foreach ( var healthCheck in _startupHealthChecks )
{
_pendingHealthChecks . Add ( healthCheck ) ;
}
}
// Call it directly so it's not debounced and any alerts can be sent.
ProcessHealthChecks ( ) ;
// Update after running health checks so new failure notifications aren't sent 2x.
_hasRunHealthChecksAfterGracePeriod = true ;
@ -191,8 +209,12 @@ namespace NzbDrone.Core.HealthCheck
}
}
// TODO: Add debounce
PerformHealthCheck ( filteredChecks . ToArray ( ) , message ) ;
lock ( _pendingHealthChecks )
{
filteredChecks . ForEach ( h = > _pendingHealthChecks . Add ( h ) ) ;
}
_debounce . Execute ( ) ;
}
}
}