/******************************************************************************* * Copyright (c) 2011 The Board of Trustees of the Leland Stanford Junior University * as Operator of the SLAC National Accelerator Laboratory. * Copyright (c) 2011 Brookhaven National Laboratory. * EPICS archiver appliance is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. *******************************************************************************/ package org.epics.archiverappliance.retrieval; import java.io.OutputStream; import java.sql.Timestamp; import org.apache.log4j.Logger; import org.epics.archiverappliance.Event; import org.epics.archiverappliance.EventStream; import org.epics.archiverappliance.EventStreamDesc; import org.epics.archiverappliance.common.TimeUtils; import org.epics.archiverappliance.retrieval.mimeresponses.ExceptionCommunicator; import org.epics.archiverappliance.retrieval.mimeresponses.MimeResponse; import com.google.protobuf.InvalidProtocolBufferException; import edu.stanford.slac.archiverappliance.PB.data.PBParseException; /** * Implementation of the Merge/Dedup algorithm for combining EventStreams into one EventStream. * @author mshankar * */ class MergeDedupConsumer implements EventStreamConsumer, AutoCloseable { private static Logger logger = Logger.getLogger(MergeDedupConsumer.class.getName()); private Timestamp startTimeStamp; int totalEvents = 0; int skippedEvents = 0; int comparedEvents = 0; OutputStream os = null; private Timestamp timestampOfLastEvent; boolean amIDeduping = false; boolean haveIpushedTheFirstEvent = false; Event firstEvent = null; MimeResponse mimeresponse = null; String pvName = null; int totalEventsForAllPVs = 0; int skippedEventsForAllPVs = 0; int comparedEventsForAllPVs = 0; MergeDedupConsumer(MimeResponse mimeresponse, OutputStream os) { this.os = os; this.mimeresponse = mimeresponse; this.mimeresponse.setOutputStream(os); } @Override public void consumeEventStream(EventStream strm) throws Exception { // This while true looks scary but 99.99% of the time we should hit the return and get out with one execution of the loop // In cases where the data spans year boundaries, we continue with the same stream. while(true) { try { mimeresponse.swicthingToStream(strm); consumeEventStreamAndOutputToMimeResponse(strm); return; } catch(ChangeInYearsException ex) { if(logger.isDebugEnabled()) logger.debug("Got a change in years exception. Inserting a fake swicthingToStream with the same stream. Previous=" + ex.getPreviousYear() + " and current=" + ex.getCurrentYear()); } } } @Override public void close() { if(!haveIpushedTheFirstEvent && firstEvent != null) { try { logger.debug("Pushing the first event as part of the close as it has not been sent yet."); mimeresponse.consumeEvent(firstEvent); totalEvents++; haveIpushedTheFirstEvent = true; } catch(Exception ex) { logger.error("Unable to even push the first event thru for pv " + pvName, ex); } } logNumbersAndCollectTotal(); mimeresponse.close(); try { os.flush(); } catch(Throwable t) { logger.debug("Exception flushing response", t); } try { os.close(); } catch(Throwable t) { logger.debug("Exception closing response", t); } } public void processingPV(String PV, Timestamp start, Timestamp end, EventStreamDesc streamDesc) { logNumbersAndCollectTotal(); this.startTimeStamp = start; mimeresponse.processingPV(PV, start, end, streamDesc); pvName = PV; resetForNextPV(); } private void consumeEventStreamAndOutputToMimeResponse(EventStream strm) throws Exception { try { int eventsInCurrentStream = 0; for(Event e : strm) { try { eventsInCurrentStream++; if(!haveIpushedTheFirstEvent && firstEvent == null) { logger.debug("Making a copy of the first event " + TimeUtils.convertToHumanReadableString(e.getEventTimeStamp())); firstEvent = e.makeClone(); continue; } if(!haveIpushedTheFirstEvent) { if(e.getEventTimeStamp().before(this.startTimeStamp)) { logger.debug("Making a copy of another event " + TimeUtils.convertToHumanReadableString(e.getEventTimeStamp())); firstEvent = e.makeClone(); continue; } else { haveIpushedTheFirstEvent = true; logger.debug("Consuming first and current events " + TimeUtils.convertToHumanReadableString(e.getEventTimeStamp())); mimeresponse.consumeEvent(firstEvent); timestampOfLastEvent = firstEvent.getEventTimeStamp(); totalEvents++; if(!e.getEventTimeStamp().after(timestampOfLastEvent)) { logger.debug("After sending first event, current event is not after the first event. Skipping " + TimeUtils.convertToHumanReadableString(e.getEventTimeStamp())); skippedEvents++; continue; } else { mimeresponse.consumeEvent(e); totalEvents++; timestampOfLastEvent = e.getEventTimeStamp(); continue; } } } if(amIDeduping) { comparedEvents++; if(!e.getEventTimeStamp().after(timestampOfLastEvent)) { skippedEvents++; continue; } else { amIDeduping = false; mimeresponse.consumeEvent(e); timestampOfLastEvent = e.getEventTimeStamp(); totalEvents++; } } else { mimeresponse.consumeEvent(e); timestampOfLastEvent = e.getEventTimeStamp(); totalEvents++; } } catch(InvalidProtocolBufferException|PBParseException ex) { logger.warn(ex.getMessage(), ex); if(this.mimeresponse instanceof ExceptionCommunicator) { ((ExceptionCommunicator)this.mimeresponse).comminucateException(ex); } skippedEvents++; } } if(eventsInCurrentStream == 0) { logger.info("The stream from " + ((strm.getDescription() != null ) ? strm.getDescription().getSource() : "Unknown") + " was an empty stream."); } // We start deduping at the boundaries of event streams. // This does not apply until we have pushed the first event out.. if(haveIpushedTheFirstEvent) startDeduping(); } catch(InvalidProtocolBufferException|PBParseException ex) { logger.warn(ex.getMessage(), ex); if(this.mimeresponse instanceof ExceptionCommunicator) { ((ExceptionCommunicator)this.mimeresponse).comminucateException(ex); } skippedEvents++; } } private void startDeduping() { amIDeduping = true; } public void resetForNextPV() { totalEvents = 0; skippedEvents = 0; comparedEvents = 0; timestampOfLastEvent = new Timestamp(Long.MIN_VALUE); amIDeduping = false; firstEvent = null; haveIpushedTheFirstEvent = false; } public void logNumbersAndCollectTotal() { if(pvName != null) { logger.info("Found a total of " + totalEvents + " skipping " + skippedEvents + " events" + " deduping involved " + comparedEvents + " compares for PV " + pvName); } totalEventsForAllPVs += totalEvents; skippedEventsForAllPVs += skippedEvents; comparedEventsForAllPVs += comparedEvents; } }