package org.yamcs.parameterarchive; import java.nio.ByteBuffer; import java.util.PrimitiveIterator; import me.lemire.integercompression.FastPFOR128; import me.lemire.integercompression.IntWrapper; import org.yamcs.parameter.Value; import org.yamcs.utils.DecodingException; import org.yamcs.utils.SortedIntArray; import org.yamcs.utils.ValueUtility; import org.yamcs.utils.VarIntUtil; /** * TimeSegment stores timestamps relative to a t0. * The timestamps are stored in a sorted int array. * * @author nm * */ public class SortedTimeSegment extends BaseSegment implements ValueSegment { public static final int NUMBITS_MASK = 22; //2^22 millisecons =~ 70 minutes per segment public static final int TIMESTAMP_MASK = (0xFFFFFFFF>>>(32-NUMBITS_MASK)); public static final long SEGMENT_MASK = ~TIMESTAMP_MASK; final static byte SUBFORMAT_ID_DELTAZG_FPF128_VB = 1; //compressed with DeltaZigzag and then FastPFOR128 plus VarInt32 for remaining final static byte SUBFORMAT_ID_DELTAZG_VB = 2; //compressed with DeltaZigzag plus VarInt32 public static final int VERSION = 0; final private long segmentStart; private SortedIntArray tsarray; public SortedTimeSegment(long segmentStart) { super(FORMAT_ID_SortedTimeValueSegment); if((segmentStart & TIMESTAMP_MASK) !=0) throw new IllegalArgumentException("t0 must be 0 in last "+NUMBITS_MASK+" bits"); tsarray = new SortedIntArray(); this.segmentStart = segmentStart; } /** * Insert instant into the array and return the position at which it has been inserted. * * @param instant */ public int add(long instant) { if((instant&SEGMENT_MASK) != segmentStart) { throw new IllegalArgumentException("This timestamp does not fit into this segment"); } return tsarray.insert((int)(instant & TIMESTAMP_MASK)); } /** * get timestamp at position idx * @param idx * @return */ public long getTime(int idx) { return tsarray.get(idx) | segmentStart; } /** * Constructs an ascending iterator starting from a specified value (inclusive) * * @param startFrom * @return */ public PrimitiveIterator.OfLong getAscendingIterator(long startFrom) { return new PrimitiveIterator.OfLong() { PrimitiveIterator.OfInt it = tsarray.getAscendingIterator((int)(startFrom&TIMESTAMP_MASK)); @Override public boolean hasNext() { return it.hasNext(); } @Override public long nextLong() { return segmentStart+ it.nextInt(); } }; } /** * Constructs an descending iterator starting from a specified value (exclusive) * * @param startFrom * @return */ public PrimitiveIterator.OfLong getDescendingIterator(long startFrom) { return new PrimitiveIterator.OfLong() { PrimitiveIterator.OfInt it = tsarray.getDescendingIterator((int)(startFrom&TIMESTAMP_MASK)); @Override public boolean hasNext() { return it.hasNext(); } @Override public long nextLong() { return segmentStart + it.nextInt(); } }; } public long getT0() { return segmentStart; } /** * returns the start of the segment where instant fits * @param instant * @return */ public static long getSegmentStart(long instant) { return instant & SEGMENT_MASK; } /** * returns the ID of the segment where the instant fits - this is the same with segment start * @param instant * @return */ public static long getSegmentId(long instant) { return instant & SEGMENT_MASK; } /** * returns the end of the segment where the instant fits * @param instant * @return */ public static long getSegmentEnd(long instant) { return instant | TIMESTAMP_MASK; } public static long getNextSegmentStart(long instant) { return (instant | TIMESTAMP_MASK) +1; } /** * returns true if the segment overlaps the [start,stop) interval * @param segmentId * @param start * @param stop * @return */ public static boolean overlap(long segmentId, long start, long stop) { long segmentStart = segmentId; long segmentStop = getSegmentEnd(segmentId); return start<segmentStop && stop>segmentStart; } /** * performs a binary search in the time segment and returns the position of t or where t would fit in. * * @see java.util.Arrays#binarySearch(int[], int) * @param instant * @return */ public int search(long instant) { if((instant&SEGMENT_MASK) != segmentStart) { throw new IllegalArgumentException("This timestamp does not fit into this segment"); } return tsarray.search((int)(instant&TIMESTAMP_MASK)); } public int size() { return tsarray.size(); } public long getSegmentStart() { return segmentStart; } public String toString() { return "[TimeSegment: t0:"+segmentStart+", relative times: "+ tsarray.toString()+"]"; } /** * Encode the time array */ @Override public void writeTo(ByteBuffer bb) { if(tsarray.size()==0) throw new IllegalStateException(" the time segment has no data"); int[] ddz = VarIntUtil.encodeDeltaDeltaZigZag(tsarray); int position = bb.position(); bb.put(SUBFORMAT_ID_DELTAZG_FPF128_VB); int size = ddz.length; VarIntUtil.writeVarInt32(bb,size); FastPFOR128 fastpfor = FastPFORFactory.get(); IntWrapper inputoffset = new IntWrapper(0); IntWrapper outputoffset = new IntWrapper(0); int[] out = new int[size]; fastpfor.compress(ddz, inputoffset, size, out, outputoffset); if (outputoffset.get() == 0) { //fastpfor didn't compress anything, probably there were too few datapoints bb.put(position, SUBFORMAT_ID_DELTAZG_VB); } else { //write the fastpfor output for(int i=0; i<outputoffset.get(); i++) { bb.putInt(out[i]); } } //write the remaining bytes varint compressed for(int i = inputoffset.get(); i<size; i++) { VarIntUtil.writeVarInt32(bb, ddz[i]); } } /** * Creates a TimeSegment by decoding the buffer * this is the reverse of the {@link #encode()} operation * * @param buf * @return * @throws DecodingException */ private void parse(ByteBuffer bb) throws DecodingException { byte subFormatId = bb.get(); int n = VarIntUtil.readVarInt32(bb); int position = bb.position(); IntWrapper inputoffset = new IntWrapper(0); IntWrapper outputoffset = new IntWrapper(0); int[] ddz = new int[n]; if(subFormatId==SUBFORMAT_ID_DELTAZG_FPF128_VB) { int[] x = new int[(bb.limit()-bb.position())/4]; for(int i=0; i<x.length;i++) { x[i]=bb.getInt(); } FastPFOR128 fastpfor = FastPFORFactory.get(); fastpfor.uncompress(x, inputoffset, x.length, ddz, outputoffset); bb.position(position+inputoffset.get()*4); } for(int i = outputoffset.get(); i<n; i++) { ddz[i] = VarIntUtil.readVarInt32(bb); } tsarray = new SortedIntArray(VarIntUtil.decodeDeltaDeltaZigZag(ddz)); } public static SortedTimeSegment parseFrom(ByteBuffer bb, long segmentStart) throws DecodingException { SortedTimeSegment r = new SortedTimeSegment(segmentStart); r.parse(bb); return r; } @Override public int getMaxSerializedSize() { return 4*(tsarray.size())+3; } @Override public Value getValue(int index) { return ValueUtility.getTimestampValue(getTime(index)); } public long getSegmentEnd() { return getSegmentEnd(segmentStart); } public long[] getRange(int posStart, int posStop, boolean ascending) { long[] r = new long[posStop-posStart]; if(ascending) { for(int i = posStart; i<posStop; i++) { r[i-posStart] = tsarray.get(i)|segmentStart; } } else { for(int i = posStop; i>posStart; i--) { r[posStop-i] = tsarray.get(i)|segmentStart; } } return r; } @Override public void add(int pos, Value engValue) { throw new UnsupportedOperationException("add not supported"); } @Override public BaseSegment consolidate() { throw new UnsupportedOperationException("consolidate not supported"); } /** * duration in milliseconds of one segment * @return */ public static long getSegmentDuration() { return TIMESTAMP_MASK+1; } }