/*
* Copyright 2014, The Sporting Exchange Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.betfair.cougar.transport.nio;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import com.betfair.tornjak.monitor.MonitorRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.betfair.cougar.api.Service;
import com.betfair.cougar.core.api.ServiceAware;
import com.betfair.cougar.transport.nio.HealthMonitorStrategy.HealthMonitorStrategyListener;
import com.betfair.tornjak.monitor.Status;
import com.betfair.tornjak.monitor.StatusAggregator;
import com.betfair.tornjak.monitor.StatusChangeEvent;
import com.betfair.tornjak.monitor.StatusChangeListener;
/**
* Actively monitors the state of the host application to inform interested parties (via a configurable strategy) of the host's health
* Can be configured to either actively or passively monitor the application health. If passive then subscribes to updates from StatusAggregators, this assumes
* an external party (such as netscalers) are causing status updates. If active monitoring then a thread is started to periodically poll application health.
* </p>
* If any service hosted in the container is unhealthy, then the result is 'unhealthy'
* </p>
* Only status 'FAIL' is considered unhealthy (WARN is not)
* </p>
* passive monitoring only receives updates when the status changes, so it's important a suitable strategy is used. DebounceHealthMonitorStrategy suites
* passive monitoring, CountingHealthMonitorStrategy suites active monitoring.
*/
public class ApplicationHealthMonitor implements ServiceAware {
private static final Logger log = LoggerFactory.getLogger(ApplicationHealthMonitor.class);
private final long monitorInterval;
private final HealthMonitorStrategy strategy;
private final MonitorRegistry monitorRegistry;
public ApplicationHealthMonitor(final ExecutionVenueNioServer nioServer,
HealthMonitorStrategy strategy,
long monitorInterval,
MonitorRegistry monitorRegistry) {
this.monitorInterval = monitorInterval;
this.strategy = strategy;
this.strategy.registerListener(new HealthMonitorStrategyListener() {
@Override
public void onUpdate(boolean isHealthy) {
if (log.isDebugEnabled()) {
log.debug("updating health state to " + isHealthy);
}
nioServer.setHealthState(isHealthy);
}
});
this.monitorRegistry = monitorRegistry;
log.info("socket health monitor using strategy " + strategy.getClass().getName());
}
@Override
public void setServices(Set<Service> services) {
if (monitorInterval > 0) {
startActiveMonitoring(monitorRegistry.getStatusAggregator());
}
else {
startPassiveMonitoring(monitorRegistry.getStatusAggregator());
}
}
/**
* Add listeners to all status aggregators to be advised when status of the aggregator changes
* </p>
* Keep track of all current status. if, after update, all status are healthy then overall status = healthy, otherwise = unhealthy.
* </p>
* advise strategy only if the overall status has changed
*/
private void startPassiveMonitoring(final StatusAggregator aggregator) {
log.info("Starting application health monitoring in PASSIVE mode");
final AtomicBoolean health = new AtomicBoolean();
health.set(!Status.FAIL.equals(aggregator.getStatus()));
aggregator.addStatusChangeListener(new StatusChangeListener() {
@Override
public void statusChanged(StatusChangeEvent event) {
boolean healthStatusChanged ;
boolean isHealthy;
synchronized (health) {
boolean currentOverallHealth = health.get(); //true iff all services are healthy
isHealthy = !Status.FAIL.equals(event.getNewStatus()); //service is healthy if not fail status
health.set(isHealthy);
healthStatusChanged = currentOverallHealth != isHealthy;
}
if (healthStatusChanged) {
strategy.update(isHealthy);
}
}
});
strategy.update(health.get());
}
/**
* Start a new thread and periodically poll all status aggregators for their current status
* </p>
* Calculate a new status where newStatus = healthy if all aggregator's status = healthy
*/
private void startActiveMonitoring(final StatusAggregator aggregator) {
log.info("Starting application health monitoring in ACTIVE mode");
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r,"SocketTransport App Health Monitor");
t.setDaemon(true);
return t;
}
});
executor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
boolean healthy = !Status.FAIL.equals(aggregator.getStatus());
setStatus(healthy ? Status.OK : Status.FAIL);
}
catch (Exception e) {
log.warn("Error whilst setting health status",e);
}
}
}, monitorInterval, monitorInterval, TimeUnit.MILLISECONDS);
}
private void setStatus(Status status) {
boolean isUnhealthy = Status.FAIL.equals(status);
strategy.update(!isUnhealthy);
}
}