package org.yamcs.yarch.rocksdb;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.PriorityQueue;
import org.rocksdb.ColumnFamilyHandle;
import org.rocksdb.RocksDBException;
import org.rocksdb.RocksIterator;
import org.slf4j.Logger;
import org.yamcs.TimeInterval;
import org.yamcs.utils.LoggingUtils;
import org.yamcs.yarch.HistogramRecord;
import org.yamcs.yarch.HistogramSegment;
import org.yamcs.yarch.Partition;
import org.yamcs.yarch.PartitionManager;
import org.yamcs.yarch.TableDefinition;
import org.yamcs.yarch.YarchDatabase;
import static org.yamcs.yarch.rocksdb.CfTableWriter.zerobytes;
/**
*
* @author nm
*
*/
class RdbHistogramIterator implements Iterator<HistogramRecord> {
private Iterator<List<Partition>> partitionIterator;
private RocksIterator segmentIterator;
private PriorityQueue<HistogramRecord> records = new PriorityQueue<>();
private final TimeInterval interval;
private final long mergeTime;
YarchDatabase ydb;
TableDefinition tblDef;
YRDB rdb;
Logger log;
String colName;
boolean stopReached = false;
//FIXME: mergeTime does not merge records across partitions or segments
public RdbHistogramIterator(YarchDatabase ydb, TableDefinition tblDef, String colName, TimeInterval interval, long mergeTime) throws RocksDBException {
this.interval = interval;
this.mergeTime = mergeTime;
this.ydb = ydb;
this.tblDef = tblDef;
this.colName = colName;
PartitionManager partMgr = RdbStorageEngine.getInstance(ydb).getPartitionManager(tblDef);
partitionIterator = partMgr.iterator(interval.getStart(), null);
log = LoggingUtils.getLogger(this.getClass(), ydb.getName(), tblDef);
readNextPartition();
}
private void readNextPartition() throws RocksDBException {
try {
while(partitionIterator.hasNext()) {
RdbPartition part = (RdbPartition) partitionIterator.next().get(0);
if(interval.hasStop() && part.getStart()>interval.getStop()) {
break; //finished
}
RDBFactory rdbf = RDBFactory.getInstance(ydb.getName());
String dbDir = part.dir;
log.debug("opening database {}", dbDir);
if(rdb!=null) {
rdbf.dispose(rdb);
}
rdb = rdbf.getRdb(tblDef.getDataDir()+"/"+dbDir, false);
String histoCfName = InKeyTableWriter.getHistogramColumnFamilyName(colName);
ColumnFamilyHandle cfh = rdb.getColumnFamilyHandle(histoCfName);
if(cfh!=null) {
segmentIterator = rdb.newIterator(cfh);
if(!interval.hasStart()) {
segmentIterator.seek(HistogramSegment.key(0, zerobytes));
} else {
int sstart=(int)(interval.getStart()/HistogramSegment.GROUPING_FACTOR);
segmentIterator.seek(HistogramSegment.key(sstart, zerobytes));
}
if(segmentIterator.isValid()) {
readNextSegments();
break;
}
}
rdbf.dispose(rdb);
}
} catch (IOException e) {
log.error("Failed to open database", e);
}
}
//reads all the segments with the same sstart time
private void readNextSegments() throws RocksDBException {
ByteBuffer bb = ByteBuffer.wrap(segmentIterator.key());
long sstart = bb.getLong();
if(sstart==Long.MAX_VALUE) {
readNextPartition();
return;
}
while(true) {
boolean beyondStop = addRecords(segmentIterator.key(), segmentIterator.value());
if(beyondStop) {
stopReached = true;
}
segmentIterator.next();
if(!segmentIterator.isValid()) {
readNextPartition();
break;
}
bb = ByteBuffer.wrap(segmentIterator.key());
long g = bb.getLong();
if(g!=sstart) {
break;
}
}
}
public void close() {
if(rdb!=null) {
RDBFactory.getInstance(ydb.getName()).dispose(rdb);
rdb = null;
}
}
//add all records from this segment into the queue
// if the stop has been reached add only partially the records, return true
private boolean addRecords(byte[] key, byte[] val) {
ByteBuffer kbb = ByteBuffer.wrap(key);
long sstart = kbb.getLong();
byte[] columnv = new byte[kbb.remaining()];
kbb.get(columnv);
ByteBuffer vbb = ByteBuffer.wrap(val);
HistogramRecord r = null;
while(vbb.hasRemaining()) {
long start = sstart*HistogramSegment.GROUPING_FACTOR + vbb.getInt();
long stop = sstart*HistogramSegment.GROUPING_FACTOR + vbb.getInt();
int num = vbb.getShort();
if((interval.hasStart()) && (stop<interval.getStart())) {
continue;
}
if((interval.hasStop()) && (start>interval.getStop())) {
if(r!=null) {
records.add(r);
}
return true;
}
if(r==null) {
r = new HistogramRecord(columnv, start, stop, num);
} else {
if(start-r.getStop()<mergeTime) {
r = new HistogramRecord(r.getColumnv(), r.getStart(), stop, r.getNumTuples()+num);
} else {
records.add(r);
r = new HistogramRecord(columnv, start, stop, num);
}
}
}
if(r!=null) {
records.add(r);
}
return false;
}
@Override
public boolean hasNext() {
return !records.isEmpty();
}
@Override
public HistogramRecord next() {
if(records.isEmpty()) {
throw new NoSuchElementException();
}
HistogramRecord r = records.poll();
if(records.isEmpty() && !stopReached) {
try {
readNextSegments();
} catch (RocksDBException e) {
throw new RuntimeException(e);
}
}
return r;
}
}