/******************************************************************************* * 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 edu.stanford.slac.archiverappliance.PB.search; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import org.apache.log4j.Logger; import org.epics.archiverappliance.config.ArchDBRTypes; import edu.stanford.slac.archiverappliance.PB.utils.LineByteStream; import edu.stanford.slac.archiverappliance.PlainPB.ComparePBEvent; /** * This is a variant of binary search that searches for an event in a event stream file. * We assume a time-sorted PB file separated by unix newlines as outlined in the archiver appliance design doc. * We pass in file channel and a function that compares an event line (most likely a PB) to the desired time t1. * The fileChannels position is moved to a spot where this constraint is satisfied s1 ≤ t1 < s2 * That is * <ol> * <li>Search the file for a time t1</li> * <li>Do a LineByteStream.seekToFirstNewLine</li> * <li>LineByteStream.readLine's after this should give events that satisfy getData's requirements.</li> * <li>Remember to terminate appropriately</li> * </ol> * * @author mshankar * */ public class FileEventStreamSearch { private static final Logger logger = Logger.getLogger(FileEventStreamSearch.class); /** * Constant use to bound length of searches before giving up. */ private static final int MAXITERATIONS = 1000; /** * Internally used to indicate the left/lower window for the search */ private long min = 0; /** * Internally used to indicate the right/upper window for the search */ private long max = 0; /** * Internally used to the current search point */ private long mid = 0; /** * Remember the last left/lower bound; this is the requirement for getData when the time t1 is not present exactly in the file. */ private long lastgoright = 0; private Path path; /** * Position where we found the value */ private long foundPosition = 0; /** * PB files often have a header; so we'd want to start after the header. */ private long startPosition = 0; /** * @param path Path * @param startPosn a starting position of search PB files */ public FileEventStreamSearch(Path path, long startPosn) { this.path = path; this.startPosition = startPosn; } public long getFoundPosition() { return foundPosition; } /** * Set the fileChannels position to a point that best satisfies the requirements for getData(t1,...). * If found (return value is true), the file's position is set such that * <code> * LineByteStream lis = new LineByteStream(fchannel); * lis.seekToFirstNewLine(); * byte[] line = lis.readLine(); * </code> * starts returning events that satisfy getData's requirements * @return <code>true</code> or <code>false</code> * @param dbrtype ArchDBRType the enumeration type * @param secondsIntoYear Search seconds into year * @throws IOException   * @see edu.stanford.slac.archiverappliance.PlainPB.ComparePBEvent */ public boolean seekToTime(ArchDBRTypes dbrtype, int secondsIntoYear) throws IOException { ComparePBEvent comparefunction = new ComparePBEvent(dbrtype, secondsIntoYear); return seekToTime(comparefunction); } /** * This should only be used by the unit tests. * @param comparefunction CompareEventLine * @return <code>true</code> or <code>false</code> * @throws IOException when parsing the absolute path */ public boolean seekToTime(CompareEventLine comparefunction) throws IOException { boolean found = binarysearch(comparefunction); if(found) { // We found an exact match. return true; } else { // We did not find an exact match. // However, check to see if the location satisfies s1 <= t1 < s2 if(lastgoright == 0 && startPosition != 0) { // Skip the header if any specified. lastgoright = startPosition; } try(LineByteStream lis = new LineByteStream(path, lastgoright)) { long currPosn = lis.getCurrentPosition(); try { lis.seekToFirstNewLine(); byte[] line1 = lis.readLine(); byte[] line2 = lis.readLine(); if(line1 == null || line2 == null || line1.length == 0 || line2.length == 0) { // Nope, we did not find anything. return false; } else { CompareEventLine.NextStep test = comparefunction.compare(line1, line2); // For s1 < t1, the compare function should tell us to go right. // For s1 == t1, it should tell us to stay where we are. // for t1 < s2, the compare function should tell us to go left. if(test == CompareEventLine.NextStep.STAY_WHERE_YOU_ARE) { // We found a location that satisfies s1 <= t1 < s2 foundPosition = lastgoright; return true; } else { // In this case, we really did not find the event as it is out of range. return false; } } } catch(IOException ex) { logger.error("Exception when parsing " + lis.getAbsolutePath() + " near posn " + currPosn, ex); throw ex; } } } } private boolean binarysearch(CompareEventLine comparefunction) throws IOException { // We bound the binary search to avoid infinite loops. int maxIterations = MAXITERATIONS; try { // Set up binary search. min = this.startPosition; max = Files.size(path)-1; do { mid = min + ((max - min)/2); // System.out.println("Min: " + min + " Mid: " + mid + " Max: " + max); try(LineByteStream lis = new LineByteStream(path, mid)) { lis.seekToFirstNewLine(); byte[] line1 = lis.readLine(); if(line1 == null || line1.length <= 0) { // Empty line in the PB file - Returning false from search. return false; } byte[] line2 = lis.readLine(); CompareEventLine.NextStep nextStep = comparefunction.compare(line1, line2); switch(nextStep) { case GO_LEFT: max = mid - 1; break; case GO_RIGHT: lastgoright = mid; min = mid + 1; break; case STAY_WHERE_YOU_ARE: foundPosition = mid; return true; default: logger.error("Compare function returned something unexpeected " + nextStep); } maxIterations--; } } while((max > min) && maxIterations > 0); } catch(Exception ex) { throw new IOException("Exception searching in input stream; min: " + min + " mid: " + mid + " max:" + max, ex); } if(maxIterations <= 0) { throw new IOException("Max iterations reached"); } return false; } }