package io.pcp.parfait;
import static tec.uom.se.AbstractUnit.ONE;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.measure.Unit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
/**
* Monitors the value returned by calls at the provided interval to the provided
* {@link Supplier}.
*/
public class PollingMonitoredValue<T> extends SettableValue<T> {
private static final Logger LOG = LoggerFactory.getLogger("parfait.polling");
/**
* The minimum time in ms that may be specified as an updateInterval.
*/
private static final int MIN_UPDATE_INTERVAL = 250;
private static final Timer POLLING_TIMER = new Timer(
"PollingMonitoredValue-poller", true);
private static Scheduler SHARED_TIMER_SCHEDULER = new TimerScheduler(
POLLING_TIMER);
private final Supplier<T> poller;
/**
* All timer tasks that have been scheduled in PollingMonitoredValues;
* useful only for testing.
*/
private static final List<TimerTask> SCHEDULED_TASKS = new CopyOnWriteArrayList<TimerTask>();
/**
* Creates a new {@link PollingMonitoredValue} with the specified polling
* interval.
*
* @param updateInterval
* how frequently the Poller should be checked for updates (may
* not be less than {@link #MIN_UPDATE_INTERVAL}
*/
public PollingMonitoredValue(String name, String description,
MonitorableRegistry registry, int updateInterval, Supplier<T> poller, ValueSemantics semantics) {
this(name, description, registry, updateInterval, poller, semantics, ONE);
}
/**
* Creates a new {@link PollingMonitoredValue} with the specified polling
* interval.
*
* @param updateInterval
* how frequently the Poller should be checked for updates (may
* not be less than {@link #MIN_UPDATE_INTERVAL}
*/
public PollingMonitoredValue(String name, String description, MonitorableRegistry registry, int updateInterval,
Supplier<T> poller, ValueSemantics semantics, Unit<?> unit) {
this(name, description, registry, updateInterval, poller, semantics, unit, SHARED_TIMER_SCHEDULER);
}
/**
* Creates a new {@link PollingMonitoredValue} with the specified polling
* interval.
*
* @param updateInterval
* how frequently the Poller should be checked for updates (may
* not be less than {@link #MIN_UPDATE_INTERVAL}
*/
public PollingMonitoredValue(String name, String description,
MonitorableRegistry registry, int updateInterval, Supplier<T> poller,
ValueSemantics semantics, Unit<?> unit, Scheduler scheduler) {
super(name, description, registry, poller.get(), unit, semantics);
this.poller = poller;
Preconditions.checkState(updateInterval >= MIN_UPDATE_INTERVAL,
"updateInterval is too short.");
TimerTask task = new PollerTask();
SCHEDULED_TASKS.add(task);
scheduler.schedule(new PollerTask(), updateInterval);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("name", getName()).add("description", getDescription()).add("poller", poller).toString();
}
private class PollerTask extends TimerTask {
@Override
public void run() {
try {
set(poller.get());
} catch (Throwable t) {
LOG.error("Error running poller " + this + "; will rerun next cycle", t);
}
}
}
@VisibleForTesting
static void runAllTasks() {
for (TimerTask task : SCHEDULED_TASKS) {
task.run();
}
}
/**
* Convenient factory method to create pollers you don't care about keeping
* – that is, pollers which should be registered and start updating their
* value, but which you don't need to hold a reference to (because you will
* presumably just be modifying the polled source).
*/
public static <T> void poll(String name, String description,
MonitorableRegistry registry, int updateInterval, Supplier<T> poller,
ValueSemantics semantics, Unit<?> unit) {
new PollingMonitoredValue<T>(name, description, registry,
updateInterval, poller, semantics, unit);
}
}