package org.yamcs.yarch.rocksdb; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicInteger; import org.rocksdb.RocksDB; import org.rocksdb.RocksDBException; import org.rocksdb.RocksIterator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yamcs.TimeInterval; import org.yamcs.YamcsException; import org.yamcs.archive.TagDb; import org.yamcs.archive.TagReceiver; import org.yamcs.protobuf.Yamcs.ArchiveTag; import org.yamcs.yarch.YarchDatabase; public class RdbTagDb implements TagDb { static Logger log=LoggerFactory.getLogger(RdbTagDb.class); private RocksDB db; final static int __key_size=12; AtomicInteger idgenerator=new AtomicInteger(0); final byte[] firstkey=new byte[12]; RdbTagDb(YarchDatabase ydb) throws RocksDBException { this(ydb.getRoot()+"/tags"); } RdbTagDb(String path) throws RocksDBException { db = RocksDB.open(path); log.debug("opened {}: {} ", path, db.getProperty("rocksdb.stats")); String num = db.getProperty("rocksdb.estimate-num-keys"); if("0".equals(num)) { writeHeader(); } else { ByteBuffer bb=ByteBuffer.wrap(db.get(firstkey)); idgenerator.set(bb.getInt()); } } private void writeHeader() throws RocksDBException { //put a record at the beginning containing the current id and the max distance int id=idgenerator.get(); ByteBuffer bb=ByteBuffer.allocate(12); bb.putInt(id); db.put(firstkey, bb.array()); } private byte[] key(ArchiveTag tag) { ByteBuffer bbk=ByteBuffer.allocate(__key_size); bbk.putLong(tag.hasStart()?tag.getStart():0); bbk.putInt(tag.getId()); return bbk.array(); } private byte[] key(long start, int id) { ByteBuffer bbk=ByteBuffer.allocate(__key_size); bbk.putLong(start); bbk.putInt(id); return bbk.array(); } /** * Synchonously gets tags, passing every separate one to the provided * {@link TagReceiver}. */ @Override public void getTags(TimeInterval intv, TagReceiver callback) throws IOException { log.debug("processing request: {}", intv); RocksIterator it = db.newIterator(); boolean hasNext; it.seek(firstkey); it.next(); hasNext = it.isValid(); //first we read all the records without start, then we jump to reqStart-maxDistance while(hasNext) { ArchiveTag tag = ArchiveTag.parseFrom(it.value()); if(intv.hasStop() && tag.hasStart() && intv.getStop()<tag.getStart()) break; if(intv.hasStart() && tag.hasStop() && tag.getStop()<intv.getStart()) { it.next(); hasNext = it.isValid(); continue; } callback.onTag(tag); it.next(); hasNext = it.isValid(); } callback.finished(); } /** * Returns a specific tag, or null if the requested tag does not exist */ @Override public ArchiveTag getTag(long tagTime, int tagId) throws IOException { try { byte[] k = key(tagTime, tagId); byte[] v = db.get(k); return (v != null) ? ArchiveTag.parseFrom(v) : null; } catch (RocksDBException e) { throw new IOException(e); } } /** * Inserts a new Tag. No id should be specified. If it is, it will * silently be overwritten, and the new tag will be returned. */ @Override public ArchiveTag insertTag(ArchiveTag tag) throws IOException { ArchiveTag newTag=ArchiveTag.newBuilder(tag).setId(getNewId()).build(); try { db.put(key(newTag), newTag.toByteArray()); } catch (RocksDBException e) { throw new IOException(e); } return newTag; } /** * Updates an existing tag. The tag is fetched by the specified id * throws YamcsException if the tag could not be found. * <p> * Note that both tagId and oldTagStart need to be specified so that * a direct lookup in the internal data structure can be made. */ @Override public ArchiveTag updateTag(long tagTime, int tagId, ArchiveTag tag) throws YamcsException, IOException { try { if(tagId<1) throw new YamcsException("Invalid or unexisting id"); db.remove(key(tagTime, tagId)); ArchiveTag newTag=ArchiveTag.newBuilder(tag).setId(tagId).build(); db.put(key(newTag), newTag.toByteArray()); return newTag; } catch (RocksDBException e) { throw new IOException(e); } } /** * Deletes the specified tag * @throws YamcsException if the id was invalid, or if the tag could not be found */ @Override public ArchiveTag deleteTag(long tagTime, int tagId) throws IOException, YamcsException { if(tagId<1) throw new YamcsException("Invalid or unexisting id"); try { byte[] k=key(tagTime, tagId); byte[]v=db.get(k); if(v==null) throw new YamcsException("No tag with the given time,id"); db.remove(k); return ArchiveTag.parseFrom(v); } catch (RocksDBException e) { throw new IOException(e); } } private int getNewId() throws IOException { int id=idgenerator.incrementAndGet(); try { writeHeader(); return id; } catch (RocksDBException e) { throw new IOException(e); } } @Override public void close() { db.close(); } }