package storm.applications.bolt;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Tuple;
import backtype.storm.tuple.Values;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import storm.applications.constants.SmartGridConstants.Component;
import storm.applications.constants.SmartGridConstants.Field;
import storm.applications.util.collections.FixedMap;
import storm.applications.util.math.OutlierTracker;
/**
* Author: Thilina
* Date: 12/6/14
*/
public class OutlierDetectionBolt extends AbstractBolt {
private FixedMap<Long, Double> globalMedianBacklog;
private Map<String, OutlierTracker> outliers;
private PriorityQueue<ComparableTuple> unprocessedMessages;
@Override
public void initialize() {
globalMedianBacklog = new FixedMap<>(300, 300);
outliers = new HashMap<>();
unprocessedMessages = new PriorityQueue<>();
}
@Override
public void execute(Tuple tuple) {
String component = tuple.getSourceComponent();
if (component.equals(Component.GLOBAL_MEDIAN)) {
long timestamp = tuple.getLongByField(Field.TIMESTAMP);
double globalMedianLoad = tuple.getDoubleByField(Field.GLOBAL_MEDIAN_LOAD);
globalMedianBacklog.put(timestamp, globalMedianLoad);
// ordered based on the timestamps
while (!unprocessedMessages.isEmpty() &&
unprocessedMessages.peek().tuple.getLongByField(Field.TIMESTAMP).equals(timestamp)) {
Tuple perPlugMedianTuple = unprocessedMessages.poll().tuple;
processPerPlugMedianTuple(perPlugMedianTuple);
}
} else {
processPerPlugMedianTuple(tuple);
}
}
private void processPerPlugMedianTuple(Tuple tuple) {
String key = tuple.getStringByField(Field.PLUG_SPECIFIC_KEY);
String houseId = key.split(":")[0];
long timestamp = tuple.getLongByField(Field.TIMESTAMP);
double value = tuple.getDoubleByField(Field.PER_PLUG_MEDIAN);
if (globalMedianBacklog.containsKey(timestamp)) {
OutlierTracker tracker;
if (outliers.containsKey(houseId)) {
tracker = outliers.get(houseId);
} else {
tracker = new OutlierTracker();
outliers.put(houseId, tracker);
}
if (!tracker.isMember(key)) {
tracker.addMember(key);
}
double globalMedian = globalMedianBacklog.get(timestamp);
if (globalMedian < value) { // outlier
if (!tracker.isOutlier(key)) {
tracker.addOutlier(key);
collector.emit(new Values(timestamp - 24 * 60 * 60, timestamp,
houseId, tracker.getCurrentPercentage()));
}
} else {
if (tracker.isOutlier(key)) {
tracker.removeOutlier(key);
//emit
collector.emit(new Values(timestamp - 24 * 60 * 60, timestamp,
houseId, tracker.getCurrentPercentage()));
}
}
} else { // global median has not arrived
unprocessedMessages.add(new ComparableTuple(tuple));
}
}
@Override
public Fields getDefaultFields() {
return new Fields(Field.SLIDING_WINDOW_START, Field.SLIDING_WINDOW_END,
Field.HOUSE_ID, Field.OUTLIER_PERCENTAGE);
}
private class ComparableTuple implements Serializable, Comparable<ComparableTuple> {
private final Tuple tuple;
private ComparableTuple(Tuple tuple) {
this.tuple = tuple;
}
@Override
public int compareTo(ComparableTuple o) {
return this.tuple.getLongByField(Field.TIMESTAMP).compareTo(
o.tuple.getLongByField(Field.TIMESTAMP));
}
}
}