package uk.ac.imperial.lsds.seepworker.core; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.util.Arrays; import java.util.Iterator; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import uk.ac.imperial.lsds.seep.api.DataReference; import uk.ac.imperial.lsds.seep.api.RuntimeEventRegister; import uk.ac.imperial.lsds.seep.api.data.ITuple; import uk.ac.imperial.lsds.seep.api.data.OTuple; import uk.ac.imperial.lsds.seep.api.data.Schema; import uk.ac.imperial.lsds.seep.api.data.TupleInfo; import uk.ac.imperial.lsds.seep.api.data.ZCITuple; import uk.ac.imperial.lsds.seep.core.IBuffer; import uk.ac.imperial.lsds.seep.core.OBuffer; // No thread safe. In particular no simultaneous write and read is allowed right now public class Dataset implements IBuffer, OBuffer { private int id; private DataReferenceManager drm; private DataReference dataReference; private BufferPool bufferPool; private Queue<ByteBuffer> buffers; private Iterator<ByteBuffer> readerIterator; private ByteBuffer wPtrToBuffer; private ByteBuffer rPtrToBuffer; // Variables to estimate cost of creating the dataset final private long creationTime; private long lastAccessForWriteTime; private long totalDataWrittenToThisDataset; // FIXME: this guy should not have this info. Instead, put this along with the // dataset in a helper class, and do the management outside this. Open issue for this. private String cacheFileName = ""; private long cacheFilePosition = 0; private int diskAccess; private int memAccess; private byte[] readInt = new byte[Integer.BYTES]; public static Dataset newDatasetOnDisk(DataReference dataRef, BufferPool bufferPool, DataReferenceManager drm) { return new Dataset(dataRef, bufferPool, drm, true); } public Dataset(DataReference dataReference, BufferPool bufferPool, DataReferenceManager drm, boolean onDisk) { this.drm = drm; this.dataReference = dataReference; this.id = dataReference.getId(); this.bufferPool = bufferPool; // Get cache buffer, always one available this.wPtrToBuffer = this.obtainInitialNewWPtrBuffer(); // If dataset is to be created on disk: // if(onDisk) { // String name = drm.createDatasetOnDisk(id); // this.setCachedLocation(name); // } this.buffers = new ConcurrentLinkedQueue<>(); this.creationTime = System.nanoTime(); } public Dataset(DataReference dataReference, BufferPool bufferPool, DataReferenceManager drm) { this.drm = drm; this.dataReference = dataReference; this.id = dataReference.getId(); this.bufferPool = bufferPool; // Get cache buffer, always one available this.wPtrToBuffer = this.obtainInitialNewWPtrBuffer(); this.buffers = new ConcurrentLinkedQueue<>(); this.creationTime = System.nanoTime(); } public Dataset(int id, DataReference dataReference, BufferPool bufferPool, DataReferenceManager drm) { this.id = id; this.drm = drm; this.dataReference = dataReference; this.bufferPool = bufferPool; // Get cache buffer, always one available this.wPtrToBuffer = this.obtainInitialNewWPtrBuffer(); this.buffers = new ConcurrentLinkedQueue<>(); this.creationTime = System.nanoTime(); } public Dataset(int id, byte[] syntheticData, DataReference dr, BufferPool bufferPool) { // This method does not need the DataReferenceManager as it's only used for producing data // one cannot write to it this.dataReference = dr; this.id = id; this.bufferPool = bufferPool; // Get cache buffer, always one available this.wPtrToBuffer = this.obtainInitialNewWPtrBuffer(); // This data is ready to be simply copied over wPtrToBuffer.put(syntheticData); this.buffers = new ConcurrentLinkedQueue<>(); this.creationTime = System.nanoTime(); } /** * Call this method before moving a dataset to disk * @return */ public Iterator<ByteBuffer> prepareForTransferToDisk() { readerIterator = this.buffers.iterator(); return readerIterator; } public ByteBuffer prepareForTransferToMemory() { ByteBuffer bb = this.wPtrToBuffer; this.buffers = new ConcurrentLinkedQueue<>(); return bb; } /** * Call this method after moving a dataset buffers to disk * @return */ public long completeTransferToDisk() { readerIterator = this.buffers.iterator(); long freedMemory = 0; while(readerIterator != null && readerIterator.hasNext()) { ByteBuffer bb = readerIterator.next(); freedMemory += bufferPool.returnBuffer(bb); readerIterator.remove(); } if (wPtrToBuffer != null) { if (wPtrToBuffer.position() > 0) { freedMemory += wPtrToBuffer.position(); transferBBToDisk(); } else { wPtrToBuffer.clear(); } } return freedMemory; } public void transferToMemory(BufferedInputStream bis, int bbSize) { boolean goOn = true; while(goOn) { int limit = 0; ByteBuffer bb = null; try { limit = bis.read(); if(limit == -1) { goOn = false; bis.close(); continue; } bb = bufferPool.borrowBuffer(); int read = 0; if(bb == null) { // Run out of memory, we can try with the cached buffer read = bis.read(wPtrToBuffer.array()); } else { read = bis.read(bb.array()); } if(read == -1) { goOn = false; bufferPool.returnBuffer(bb); bis.close(); continue; } } catch (IOException e) { e.printStackTrace(); } // Add read buffer to memory if(bb == null) { wPtrToBuffer.limit(limit); wPtrToBuffer.flip(); this.addBufferToBuffers(wPtrToBuffer); } else { bb.limit(limit); this.addBufferToBuffers(bb); } } } public void completeTransferToMemory(ByteBuffer currentPointer) { // Reset dataset so that it can be read again this.readerIterator = null; this.rPtrToBuffer = null; this.cacheFilePosition = 0; } public long size() { return totalDataWrittenToThisDataset; } public long creationCost() { if(this.lastAccessForWriteTime > 0) { return (this.lastAccessForWriteTime - this.creationTime); } else { return 0; // because we don't want negative cost } } @Deprecated public void markAcess() { if (!cacheFileName.equals("")) { diskAccess++; } else { memAccess++; } } public int getDiskAccess() { return diskAccess; } public int getMemAccess() { return memAccess; } public int freeDataset() { int totalFreedMemory = 0; for(ByteBuffer bb : buffers) { totalFreedMemory = totalFreedMemory + bufferPool.returnBuffer(bb); } if(this.wPtrToBuffer != null) { totalFreedMemory = totalFreedMemory + bufferPool.returnBuffer(wPtrToBuffer); this.wPtrToBuffer = null; } /*if(this.rPtrToBuffer != null) { totalFreedMemory = totalFreedMemory + bufferPool.returnBuffer(rPtrToBuffer); }*/ return totalFreedMemory; } public void addBufferToBuffers(ByteBuffer buf) { if(buf != null) { buffers.add(buf); } else { System.out.println("ERROR ADDING EMPTY BUFFER TO MEMORY"); System.exit(-1); } } private ByteBuffer obtainInitialNewWPtrBuffer() { ByteBuffer bb = bufferPool.borrowBuffer(); if(bb == null) { if (drm.spillDatasetsToDisk(null) == 0) { String name = drm.createDatasetOnDisk(id); this.setCachedLocation(name); bb = bufferPool.getCacheBuffer(); } else { bb = obtainInitialNewWPtrBuffer(); } } return bb; } private ByteBuffer obtainNewWPtrBuffer() { ByteBuffer bb = bufferPool.borrowBuffer(); if(bb == null) { try { if (drm.spillDatasetsToDisk(id) == 0) { drm.sendDatasetToDisk(id); bb = bufferPool.getCacheBuffer(); } else { bb = obtainNewWPtrBuffer(); } } catch (IOException e) { e.printStackTrace(); } } return bb; } public void prepareDatasetForFutureRead() { // For memory read only this is enough wPtrToBuffer = rPtrToBuffer; // Set wPtrToBuffer to that one rPtrToBuffer = null; readerIterator = this.buffers.iterator(); // Flip all memory buffers while(readerIterator.hasNext()) { readerIterator.next().flip(); } readerIterator = null; // Reset and let consumer create this again as needed // For file operations, reset cacheFilePosition = 0; } public void prepareSyntheticDatasetForRead() { rPtrToBuffer = null; readerIterator = null; // Reset and let consumer create this again as needed // For file operations, reset cacheFilePosition = 0; } public ITuple consumeData_zerocopy(ZCITuple t) { // Try to read from rPtrToBuffer if(rPtrToBuffer == null || rPtrToBuffer.remaining() == 0) { // MEMORY if (cacheFileName.equals("")) { memAccess++; if(readerIterator == null) { readerIterator = this.buffers.iterator(); } if(readerIterator.hasNext()) { rPtrToBuffer = readerIterator.next(); if(rPtrToBuffer.position() == rPtrToBuffer.limit()) { rPtrToBuffer.flip(); } } else { // No more buffers available, read the write buffer if(wPtrToBuffer != null) { if(wPtrToBuffer.position() != 0) { wPtrToBuffer.flip(); } rPtrToBuffer = wPtrToBuffer; if(rPtrToBuffer.limit() == 0) { System.out.println("B"); } wPtrToBuffer = null; } else { prepareDatasetForFutureRead(); return null; } } } // DISK else { diskAccess++; FileInputStream is = null; try { is = new FileInputStream(cacheFileName); is.getChannel().position(cacheFilePosition); int limit = is.read(readInt); int size = ByteBuffer.wrap(readInt).getInt(); if(limit == -1 || size == 0) { // if the write buffer still contains data if(wPtrToBuffer != null) { if(wPtrToBuffer.position() != 0) { wPtrToBuffer.flip(); } rPtrToBuffer = wPtrToBuffer; if(rPtrToBuffer.limit() == 0) { System.out.println("D"); } is.close(); wPtrToBuffer = null; } else { is.close(); prepareDatasetForFutureRead(); return null; } } else { byte[] d = new byte[size]; int read = is.read(d); if(read == -1) { // if the write buffer still contains data if(wPtrToBuffer != null) { if(wPtrToBuffer.position() != 0) { wPtrToBuffer.flip(); } rPtrToBuffer = wPtrToBuffer; if(rPtrToBuffer.limit() == 0) { System.out.println("F"); } is.close(); wPtrToBuffer = null; } else { is.close(); prepareDatasetForFutureRead(); return null; } } else if (read != d.length) { System.out.println("Problem reading smaller buffer chunk (Dataset.consumeData)"); System.exit(-1); } else { rPtrToBuffer = ByteBuffer.wrap(d); if(rPtrToBuffer.limit() == 0) { System.out.println("H"); } cacheFilePosition = is.getChannel().position(); // 4 limit size is.close(); } } // else } catch (FileNotFoundException fnfe) { if(wPtrToBuffer != null) { if(wPtrToBuffer.position() != 0) { wPtrToBuffer.flip(); } rPtrToBuffer = wPtrToBuffer; if(rPtrToBuffer.limit() == 0) { System.out.println("I"); } // no need to close stream as it does not exist wPtrToBuffer = null; } else { // no need to close stream as it does not exist prepareDatasetForFutureRead(); return null; } } catch (IOException e) { e.printStackTrace(); } } t.assignBuffer(rPtrToBuffer); } if (rPtrToBuffer.hasRemaining()) { int size = rPtrToBuffer.getInt(); int currentPosition = rPtrToBuffer.position(); t.setBufferPtr(currentPosition); } return t; } public byte[] consumeData() { // Try to read from rPtrToBuffer if(rPtrToBuffer == null || rPtrToBuffer.remaining() == 0) { // MEMORY if (cacheFileName.equals("")) { memAccess++; if(readerIterator == null) { readerIterator = this.buffers.iterator(); } if(readerIterator.hasNext()) { rPtrToBuffer = readerIterator.next(); if(rPtrToBuffer.position() == rPtrToBuffer.limit()) { rPtrToBuffer.flip(); } } else { // No more buffers available, read the write buffer if(wPtrToBuffer != null) { if(wPtrToBuffer.position() != 0) { wPtrToBuffer.flip(); } rPtrToBuffer = wPtrToBuffer; if(rPtrToBuffer.limit() == 0) { System.out.println("B"); } wPtrToBuffer = null; } else { prepareDatasetForFutureRead(); return null; } } } // DISK else { diskAccess++; FileInputStream is = null; try { is = new FileInputStream(cacheFileName); is.getChannel().position(cacheFilePosition); int limit = is.read(readInt); int size = ByteBuffer.wrap(readInt).getInt(); if(limit == -1 || size == 0) { // if the write buffer still contains data if(wPtrToBuffer != null) { if(wPtrToBuffer.position() != 0) { wPtrToBuffer.flip(); } rPtrToBuffer = wPtrToBuffer; if(rPtrToBuffer.limit() == 0) { System.out.println("D"); } is.close(); // if(wPtrToBuffer.limit() == 0) return null; wPtrToBuffer = null; } else { is.close(); prepareDatasetForFutureRead(); return null; } } else { byte[] d = new byte[size]; int read = is.read(d); if(read == -1) { // if the write buffer still contains data if(wPtrToBuffer != null) { if(wPtrToBuffer.position() != 0) { wPtrToBuffer.flip(); } rPtrToBuffer = wPtrToBuffer; if(rPtrToBuffer.limit() == 0) { System.out.println("F"); } is.close(); // if(wPtrToBuffer.limit() == 0) return null; wPtrToBuffer = null; } else { is.close(); prepareDatasetForFutureRead(); return null; } } else if (read != d.length) { System.out.println("Problem reading smaller buffer chunk (Dataset.consumeData)"); System.exit(-1); } else { rPtrToBuffer = ByteBuffer.wrap(d); if(rPtrToBuffer.limit() == 0) { System.out.println("H"); } cacheFilePosition = is.getChannel().position(); // 4 limit size is.close(); } } // else } catch (FileNotFoundException fnfe) { if(wPtrToBuffer != null) { if(wPtrToBuffer.position() != 0) { wPtrToBuffer.flip(); } rPtrToBuffer = wPtrToBuffer; if(rPtrToBuffer.limit() == 0) { System.out.println("I"); } // no need to close stream as it does not exist // if(wPtrToBuffer.limit() == 0) return null; wPtrToBuffer = null; } else { // no need to close stream as it does not exist prepareDatasetForFutureRead(); return null; } } catch (IOException e) { e.printStackTrace(); } } } // At this point we have rPtrToBuffer int size = rPtrToBuffer.getInt(); if(size == 0) { System.out.println(); } byte[] data = new byte[size]; rPtrToBuffer.get(data); return data; } public Schema getSchemaForDataset() { return this.dataReference.getDataStore().getSchema(); } /** * OBuffer */ @Override public int id() { return id; } @Override public DataReference getDataReference() { return dataReference; } @Override public boolean drainTo(WritableByteChannel channel) { // TODO Auto-generated method stub return false; } @Override public boolean write(byte[] data, RuntimeEventRegister reg) { int dataSize = data.length; totalDataWrittenToThisDataset = totalDataWrittenToThisDataset + dataSize + TupleInfo.TUPLE_SIZE_OVERHEAD; this.lastAccessForWriteTime = System.nanoTime(); // Try to write to cache buffer first if(wPtrToBuffer.remaining() < dataSize + TupleInfo.TUPLE_SIZE_OVERHEAD) { // When buffer is full, then we check whether this dataset is in memory or not if (!cacheFileName.equals("")) { // disk transferBBToDisk(); } else { // memory wPtrToBuffer.flip(); this.addBufferToBuffers(wPtrToBuffer); // add full buffer this.wPtrToBuffer = this.obtainNewWPtrBuffer(); // try to get a new one } } // Write size and data to cache buffer. Here it is guaranteed to exist wPtrToBuffer.putInt(dataSize); wPtrToBuffer.put(data); return true; } @Override public boolean write(OTuple o, RuntimeEventRegister reg) { int dataSize = o.getTupleSize(); totalDataWrittenToThisDataset = totalDataWrittenToThisDataset + dataSize + TupleInfo.TUPLE_SIZE_OVERHEAD; this.lastAccessForWriteTime = System.nanoTime(); // Try to write to cache buffer first if(wPtrToBuffer.remaining() < dataSize + TupleInfo.TUPLE_SIZE_OVERHEAD) { // When buffer is full, then we check whether this dataset is in memory or not if (!cacheFileName.equals("")) { // disk transferBBToDisk(); } else { // memory wPtrToBuffer.flip(); this.addBufferToBuffers(wPtrToBuffer); // add full buffer this.wPtrToBuffer = this.obtainNewWPtrBuffer(); // try to get a new one } } // Write size and data to cache buffer. Here it is guaranteed to exist o.writeValues(wPtrToBuffer); return true; } private void _transferBBToDisk() { WritableByteChannel bc = null; try { // Open file to append buffer wPtrToBuffer.flip(); bc = Channels.newChannel(new FileOutputStream(cacheFileName, true)); int limit = wPtrToBuffer.limit(); ByteBuffer limitInt = ByteBuffer.allocate(Integer.BYTES).putInt(limit); bc.write(limitInt); bc.write(wPtrToBuffer); bc.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private void transferBBToDisk() { /*if (wPtrToBuffer.position() == 0) { return; }*/ BufferedOutputStream bos = null; try { // Open file to append buffer wPtrToBuffer.flip(); FileOutputStream fos = new FileOutputStream(cacheFileName, true); bos = new BufferedOutputStream(fos, bufferPool.getMinimumBufferSize()); byte[] payload = Arrays.copyOfRange(wPtrToBuffer.array(), wPtrToBuffer.arrayOffset(), wPtrToBuffer.limit()); bos.write(ByteBuffer.wrap(readInt).putInt(payload.length).array()); bos.write(payload); bos.flush(); fos.getFD().sync(); bos.close(); fos.close(); wPtrToBuffer.clear(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public void setCachedLocation(String filename) { cacheFileName = filename; cacheFilePosition = 0; } public void unsetCachedLocation() { cacheFileName = ""; } public long cacheFileLocation() { return cacheFilePosition; } /** * IBuffer interface */ @Override public boolean readyToWrite() { // TODO Auto-generated method stub return false; } @Override public int readFrom(ReadableByteChannel channel) { // TODO Auto-generated method stub return -1; } @Override public byte[] read(int timeout) { // TODO Auto-generated method stub return null; } @Override public void pushData(byte[] data) { // TODO Auto-generated method stub } @Override public void flush() { // TODO Auto-generated method stub } }