package org.epics.archiverappliance.retrieval.postprocessors; import java.io.IOException; import java.sql.Timestamp; import java.util.concurrent.Callable; import javax.servlet.http.HttpServletRequest; import org.apache.log4j.Logger; import org.epics.archiverappliance.Event; import org.epics.archiverappliance.EventStream; import org.epics.archiverappliance.common.TimeUtils; import org.epics.archiverappliance.config.PVTypeInfo; import org.epics.archiverappliance.data.DBRTimeEvent; import org.epics.archiverappliance.engine.membuf.ArrayListEventStream; import org.epics.archiverappliance.retrieval.RemotableEventStreamDesc; import edu.stanford.slac.archiverappliance.PB.data.PBParseException; /** * The intent is to mimic ADEL; this is principally targeted at decimation * Like ADEL, it applies only to scalar numbers. * We have some notion of a "last" value that was written out. * The very first sample we get we write out and that becomes the "last" value. * <pre> * Diff = Math.abs(current value - last value) * If Diff ≥ ADEL * Write out value * Update last known value * </pre> * <ol> * <li>If either current value or previous value is NAN/INF, unconditionally write out current value and update last known value</li> * <li>Also need to to write out when alarm severity changes (MD)</li> * <li>Also need to to write out when connection state changes (connect/disconnect) (MD)</li> * </ol> * @author mshankar * */ public class DeadBand implements PostProcessor { private static Logger logger = Logger.getLogger(DeadBand.class.getName()); private static final double DEFAULT_ADEL=1.0; private double adelValue = DEFAULT_ADEL; private Event lastEventWrittenOut = null; @Override public void initialize(String userarg, String pvName) throws IOException { if(userarg != null && userarg.contains("_")) { String[] userparams = userarg.split("_"); String intervalStr = userparams[1]; adelValue = Double.parseDouble(intervalStr); logger.debug("DeadBand, using user supplied ADEL of " + adelValue); } else { logger.debug("DeadBand, using the default ADEL of " + adelValue); } } @Override public long estimateMemoryConsumption(String pvName, PVTypeInfo typeInfo, Timestamp start, Timestamp end, HttpServletRequest req) { float storageRate = typeInfo.getComputedStorageRate(); long numSeconds = TimeUtils.convertToEpochSeconds(end) - TimeUtils.convertToEpochSeconds(start); // Add a fudge factor of 2 for java long estimatedMemoryConsumption = (long) (storageRate*numSeconds*2); return estimatedMemoryConsumption; } @Override public Callable<EventStream> wrap(final Callable<EventStream> callable) { return new Callable<EventStream>() { @Override public EventStream call() throws Exception { try(EventStream strm = callable.call()) { ArrayListEventStream buf = new ArrayListEventStream(0, (RemotableEventStreamDesc) strm.getDescription()); for(Event e : strm) { try { // The very first sample we get we write out and that becomes the "last" value. if(lastEventWrittenOut == null) { Event clone = e.makeClone(); lastEventWrittenOut = clone; buf.add(clone); logger.debug("Writing the very first sample out at " + TimeUtils.convertToISO8601String(clone.getEventTimeStamp())); continue; } double currentVALue = e.getSampleValue().getValue().doubleValue(); double lastVALueWrittenOut = lastEventWrittenOut.getSampleValue().getValue().doubleValue(); if(Double.isInfinite(currentVALue) || Double.isNaN(currentVALue) || Double.isInfinite(lastVALueWrittenOut) || Double.isNaN(lastVALueWrittenOut) ) { // If either current value or previous value is NAN/INF, unconditionally write out current value and update last known value Event clone = e.makeClone(); lastEventWrittenOut = clone; buf.add(clone); logger.debug("Writing out from infinity/NAN at " + TimeUtils.convertToISO8601String(clone.getEventTimeStamp())); continue; } DBRTimeEvent currDBRTimeEvent = (DBRTimeEvent)e; int currentSeverity = currDBRTimeEvent.getSeverity(); DBRTimeEvent previousDBRTimeEvent = (DBRTimeEvent)lastEventWrittenOut; int previousSeverity = previousDBRTimeEvent.getSeverity(); if(currentSeverity != previousSeverity) { // If alarm severity changes, write out current value and update last known value Event clone = e.makeClone(); lastEventWrittenOut = clone; buf.add(clone); logger.debug("Writing out from severity change at " + TimeUtils.convertToISO8601String(clone.getEventTimeStamp())); continue; } boolean currentConnectionChangeStatus = currDBRTimeEvent.hasFieldValues() && currDBRTimeEvent.getFields().containsKey("cnxlostepsecs"); boolean previousConnectionChangeStatus = previousDBRTimeEvent.hasFieldValues() && previousDBRTimeEvent.getFields().containsKey("cnxlostepsecs"); if(currentConnectionChangeStatus != previousConnectionChangeStatus) { // If connection state changes changes, write out current value and update last known value Event clone = e.makeClone(); lastEventWrittenOut = clone; buf.add(clone); logger.debug("Writing out from connection state change at " + TimeUtils.convertToISO8601String(clone.getEventTimeStamp())); continue; } // If Diff >= ADEL // Write out value // Update last known value double diff = Math.abs(currentVALue - lastVALueWrittenOut); if (diff > adelValue) { Event clone = e.makeClone(); lastEventWrittenOut = clone; buf.add(clone); logger.debug("Writing out from magnitude change at " + TimeUtils.convertToISO8601String(clone.getEventTimeStamp())); continue; } } catch(PBParseException ex) { logger.error("Skipping possible corrupted event for pv " + strm.getDescription()); } } return buf; } } }; } @Override public String getIdentity() { return "deadBand"; } @Override public String getExtension() { if(adelValue == DEFAULT_ADEL) { return "deadBand"; } else { return "deadBand_" + Double.toString(adelValue); } } }