package org.yamcs.yarch; import java.nio.ByteBuffer; import java.util.ArrayList; /* * keeps all the records in a {@link groupFactor} millisec interval * * */ public class HistogramSegment { byte[] columnv; long sstart; //segment start ArrayList<HistogramSegment.SegRecord> pps; public final static long GROUPING_FACTOR = 3600*1000; //has to be less than 2^16 *1000 static final int REC_SIZE = 10; //4 bytes for start and stop, 2 bytes for num static final int MAX_INTERVAL = 120000; //make two records if the time between packets is more than 2 minutes (because the packets are not very related) private static long LOSS_TIME = 1000; //time in milliseconds above which we consider a packet loss /** * Constructs an empty segment * @param columnv - column value in binary * @param sstart */ public HistogramSegment(byte[] columnv, long sstart) { this.columnv = columnv; this.sstart = sstart; pps=new ArrayList<HistogramSegment.SegRecord>(); } public HistogramSegment(byte[] columnv, long sstart, byte[] val) { ByteBuffer v = ByteBuffer.wrap(val); this.columnv = columnv; this.sstart = sstart; pps = new ArrayList<HistogramSegment.SegRecord>(); while(v.hasRemaining()) { pps.add(new SegRecord(v.getInt(),v.getInt(),v.getShort())); } } public HistogramSegment(byte[] key, byte[] val) { ByteBuffer k = ByteBuffer.wrap(key); ByteBuffer v = ByteBuffer.wrap(val); this.sstart = k.getLong(0); columnv = new byte[k.remaining()]; k.get(columnv); pps = new ArrayList<HistogramSegment.SegRecord>(); while(v.hasRemaining()) { pps.add(new SegRecord(v.getInt(),v.getInt(),v.getShort())); } } public static long getSstart(byte[] key) { return ByteBuffer.wrap(key).getLong(0); } public byte[] key() { return key(sstart, columnv); } public static byte[] key(long sstart, byte[] columnv) { ByteBuffer bbk = ByteBuffer.allocate(8+columnv.length); bbk.putLong(sstart); bbk.put(columnv); return bbk.array(); } public byte[] val() { ByteBuffer bbv = ByteBuffer.allocate(REC_SIZE*pps.size()); for(HistogramSegment.SegRecord p:pps) { bbv.putInt(p.dstart); bbv.putInt(p.dstop); bbv.putShort(p.num); } return bbv.array(); } //used for merging - all these should be put into a PpMerger class //actions private boolean mergeLeft=false, mergeRight=false; //results boolean duplicate=false, leftUpdated=false, centerAdded=false, rightUpdated=false, rightDeleted=false; int leftIndex=-1, rightIndex=-1; HistogramSegment.SegRecord left, right; int dtime; /** * @param dtime1 delta time from segment start in milliseconds */ public void merge(int dtime1) { this.dtime=dtime1; for(int i=0;i<pps.size();i++) { HistogramSegment.SegRecord r=pps.get(i); if(dtime>=r.dstart) { if(dtime<=r.dstop) { //inside left duplicate=true; return; } left=r; leftIndex=i; continue; } if(dtime<r.dstart) { rightIndex=i; right=r; break; } } if(leftIndex!=-1) { checkMergeLeft(); } if(rightIndex!=-1){ checkMergeRight(); } if(mergeLeft && mergeRight) { selectBestMerge(); } //based on the information collected above, compute the new records if(mergeLeft & mergeRight) { pps.set(leftIndex, new SegRecord(left.dstart,right.dstop,(short)(left.num+right.num+1))); pps.remove(rightIndex); leftUpdated=true; rightDeleted=true; } else if(mergeLeft) { left.dstop=dtime; left.num++; leftUpdated=true; } else if (mergeRight) { right.dstart=dtime; right.num++; rightUpdated=true; } else { //add a new record HistogramSegment.SegRecord center=new SegRecord(dtime, dtime, (short)1); if(leftIndex!=-1) { pps.add(leftIndex+1,center); } else if(rightIndex!=-1) { pps.add(rightIndex,center); } else { pps.add(center); } centerAdded=true; } } int leftInterval=-1; int rightInterval=-1; private void checkMergeLeft() { //check if it can be merged to left if((dtime-left.dstop) < MAX_INTERVAL) { if(left.num==1) { mergeLeft=true; } else { leftInterval = (left.dstop-left.dstart)/(left.num-1); if((dtime-left.dstop) < leftInterval+LOSS_TIME) { mergeLeft=true; } } } } private void checkMergeRight() { //check if it can be merged to right if((right.dstart-dtime) < MAX_INTERVAL) { if(right.num==1) { mergeRight=true; } else { rightInterval=(right.dstop-right.dstart)/(right.num-1); if((right.dstart-dtime) < rightInterval+LOSS_TIME) { mergeRight=true; } } } } private void selectBestMerge() { int intervalToLeft=dtime-left.dstop; int intervalToRight=right.dstart-dtime; if(Math.abs(intervalToLeft-intervalToRight)>=LOSS_TIME) { if(intervalToLeft<intervalToRight) { mergeRight=false; } else { mergeLeft=false; } } } //add a new record to the segment (to be used for testing only void add(int dstart, int dstop, short num) { pps.add(new SegRecord(dstart, dstop, num)); } @Override public String toString() { return "start: "+sstart+", columnv: "+new String(columnv)+" recs:"+pps; } static class SegRecord { int dstart,dstop; //deltas from the segment start in milliseconds short num; public SegRecord(int dstart, int dstop, short num) { this.dstart=dstart; this.dstop=dstop; this.num=num; } @Override public String toString() { return String.format("time:(%d,%d), nump: %d",dstart,dstop,num); } } }