package org.epics.archiverappliance.retrieval.postprocessors;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.LinkedList;
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.POJOEvent;
import org.epics.archiverappliance.common.TimeSpan;
import org.epics.archiverappliance.common.TimeUtils;
import org.epics.archiverappliance.config.PVTypeInfo;
import org.epics.archiverappliance.data.AlarmInfo;
import org.epics.archiverappliance.engine.membuf.ArrayListEventStream;
import org.epics.archiverappliance.retrieval.RemotableEventStreamDesc;
/**
*
* <code>Nth</code> is a post processor which returns every n-th value.
*
* @author <a href="mailto:jaka.bobnar@cosylab.com">Jaka Bobnar</a>
*
*/
public class Nth implements PostProcessor, PostProcessorWithConsolidatedEventStream {
private static final String IDENTITY = "nth";
public static final int MAX_COUNT = Integer.MAX_VALUE/1000;
private static Logger logger = Logger.getLogger(Nth.class.getName());
private int everyNth = PostProcessors.DEFAULT_SUMMARIZING_INTERVAL;
private int count;
private long startTime;
private long endTime;
private ArrayListEventStream data;
private Timestamp previousEventTimestamp = new Timestamp(1);
private int i = 0;
@Override
public String getIdentity() {
return IDENTITY;
}
@Override
public String getExtension() {
String identity = this.getIdentity();
if(everyNth == PostProcessors.DEFAULT_SUMMARIZING_INTERVAL) {
return identity;
} else {
return identity + "_" + Integer.toString(everyNth);
}
}
@Override
public void initialize(String userarg, String pvName) throws IOException {
if(userarg != null && userarg.contains("_")) {
String[] userparams = userarg.split("_");
String everyNthStr = userparams[1];
everyNth = Integer.parseInt(everyNthStr);
logger.debug("Using the supplied n " + everyNthStr);
} else {
logger.debug("Using the default n " + everyNth + " as the user has not specified the n argument.");
}
}
@Override
public long estimateMemoryConsumption(String pvName, PVTypeInfo typeInfo, Timestamp start, Timestamp end, HttpServletRequest req) {
this.startTime = start.getTime();
this.endTime = end.getTime();
long startTime = TimeUtils.convertToEpochSeconds(start);
long endTime = TimeUtils.convertToEpochSeconds(end);
count = (int)((endTime - startTime)/typeInfo.getSamplingPeriod())/everyNth;
//limit the count to ~2M (2.147.483) to avoid out of memory errors
if (count > MAX_COUNT)
logger.warn("Too many points expected (" + count + "). The returned data array will be cut off at " + MAX_COUNT +" points.");
if (count < 0) count = 1;
else count = Math.min(MAX_COUNT,count);
logger.debug(count + " number of points expected.");
return (long) (typeInfo.getComputedStorageRate()*(endTime - startTime)*2/everyNth);
}
@Override
public Callable<EventStream> wrap(final Callable<EventStream> callable) {
//This method might be called several times with different parameter, each containing a subset of points
//To handle the points correctly and to ensure that the points are ordered by the timestamps, hold
//the running integer (i) and the previous timestamp (previousEventTimestamp) globally.
return new Callable<EventStream>() {
@Override
public EventStream call() throws Exception {
try(EventStream strm = callable.call()) {
if(data == null) {
data = new ArrayListEventStream(count,(RemotableEventStreamDesc)strm.getDescription());
}
for(Event e : strm) {
if(e.getEventTimeStamp().after(previousEventTimestamp)) {
previousEventTimestamp = e.getEventTimeStamp();
} else {
if(logger.isDebugEnabled()) {
logger.debug("Skipping older event " + TimeUtils.convertToHumanReadableString(e.getEventTimeStamp()) + " previous " + TimeUtils.convertToHumanReadableString(previousEventTimestamp));
}
continue;
}
long s = e.getEventTimeStamp().getTime();
if (s < startTime || s > endTime) {
logger.debug("Skipping event that is out of selected boundaries. Time: " + TimeUtils.convertToHumanReadableString(s));
} else {
if (i++ % everyNth == 0) {
//Transform events to POJOEvent. Using the incomming events causes troubles when
//transferring data to the client (client only sees one sample).
if (e instanceof AlarmInfo) {
data.add(new POJOEvent(e.getDBRType(),e.getEventTimeStamp(),e.getSampleValue(),((AlarmInfo)e).getStatus(),((AlarmInfo)e).getSeverity()));
} else {
data.add(new POJOEvent(e.getDBRType(),e.getEventTimeStamp(),e.getSampleValue(),0,0));
}
}
}
if (data.size() == MAX_COUNT) {
logger.warn("Too many points. Truncating the data array at " + MAX_COUNT);
break;
}
}
return new ArrayListEventStream(0,(RemotableEventStreamDesc)strm.getDescription());
}
}
};
}
@Override
public LinkedList<TimeSpan> getBinTimestamps() {
LinkedList<TimeSpan> list = new LinkedList<>();
if (data == null) return list;
for (int i = 0; i < data.size() - 1; i++) {
list.add(new TimeSpan(data.get(i).getEventTimeStamp(),data.get(i+1).getEventTimeStamp()));
}
return list;
}
@Override
public EventStream getConsolidatedEventStream() {
return data;
}
@Override
public long getEndBinEpochSeconds() {
return TimeUtils.convertToEpochSeconds(new Timestamp(endTime));
}
@Override
public long getStartBinEpochSeconds() {
return TimeUtils.convertToEpochSeconds(new Timestamp(startTime));
}
}