// This file is part of OpenTSDB. // Copyright (C) 2011-2014 The OpenTSDB Authors. // // This program is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 2.1 of the License, or (at your // option) any later version. This program is distributed in the hope that it // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser // General Public License for more details. You should have received a copy // of the GNU Lesser General Public License along with this program. If not, // see <http://www.gnu.org/licenses/>. package net.opentsdb.core; import java.util.Arrays; import org.hbase.async.KeyValue; /** * Internal implementation detail for {@link net.opentsdb.core.CompactionQueue}. This * allows iterating over the datapoints in a column without creating objects for each * datapoint. * * @since 2.1 */ final class ColumnDatapointIterator implements Comparable<ColumnDatapointIterator> { /** * @return true if this column needs one or more fixups applied. */ public boolean needsFixup() { return needs_fixup; } private final long column_timestamp; // immutable once the constructor returns, but may need to be adjusted for fixups byte[] qualifier; // referenced by CompactionQueue private byte[] value; private boolean needs_fixup; // pointers into the qualifier/value buffers private int qualifier_offset; private int value_offset; // data from the current point private int current_timestamp_offset; private int current_qual_length; private int current_val_length; private boolean is_ms; /** * Create an entry for a column, which will be able to iterate over the individual values * contained in it. * * NOTE: This currently does not expect to be handed a column containing non-datapoint values. * * @param kv */ public ColumnDatapointIterator(final KeyValue kv) { this.column_timestamp = kv.timestamp(); this.qualifier = kv.qualifier(); this.value = kv.value(); qualifier_offset = 0; value_offset = 0; checkForFixup(); update(); } private void checkForFixup() { // fixups predate compaction and ms-resolution timestamps, so are all exactly 2 bytes if (qualifier.length == 2) { final byte qual1 = qualifier[1]; if (Internal.floatingPointValueToFix(qual1, value)) { value = Internal.fixFloatingPointValue(qual1, value); needs_fixup = true; } final byte lenByte = Internal.fixQualifierFlags(qual1, value.length); if (lenByte != qual1) { qualifier = new byte[] { qualifier[0], lenByte }; needs_fixup = true; } } } /** * @return true if there are datapoints in this column. */ public boolean hasMoreData() { return qualifier_offset < qualifier.length; } /** * @return the offset of the current datapoint from the column timestamp, in milliseconds * (regardless of the stored precision). */ public int getTimestampOffsetMs() { return current_timestamp_offset; } /** * @return true if the current datapoint's timestamp is in milliseconds. */ public boolean isMilliseconds() { return is_ms; } /** * Copy this value to the output and advance to the next one. * * @param compQualifier * @param compValue * @return true if there is more data left in this column */ public void writeToBuffers(ByteBufferList compQualifier, ByteBufferList compValue) { compQualifier.add(qualifier, qualifier_offset, current_qual_length); compValue.add(value, value_offset, current_val_length); } /** * @return the length of the qualifier for the current datapoint. */ public int getCurrentQualiferLength() { return current_qual_length; } /** * @return a copy of the value of the current datapoint, after any fixups. */ public byte[] getCopyOfCurrentValue() { if (needs_fixup) { assert value_offset == 0; // fixups should only be in single-value columns return Internal.fixFloatingPointValue(qualifier[qualifier_offset + 1], value); } else { return Arrays.copyOfRange(value, value_offset, value_offset + current_val_length); } } /** * Advance to the next datapoint. * * @return true if there is at least one more datapoint after advancing */ public boolean advance() { qualifier_offset += current_qual_length; value_offset += current_val_length; return update(); } private boolean update() { if (qualifier_offset >= qualifier.length || value_offset >= value.length) { return false; } if (Internal.inMilliseconds(qualifier[qualifier_offset])) { current_qual_length = 4; is_ms = true; } else { current_qual_length = 2; is_ms = false; } current_timestamp_offset = Internal.getOffsetFromQualifier(qualifier, qualifier_offset); current_val_length = Internal.getValueLengthFromQualifier(qualifier, qualifier_offset); return true; } // order in ascending order by timestamp, descending order by row timestamp (so we find the // entry we are going to keep first, and don't have to copy over it) @Override public int compareTo(ColumnDatapointIterator o) { int c = current_timestamp_offset - o.current_timestamp_offset; if (c == 0) { // note inverse order of comparison! c = Long.signum(o.column_timestamp - column_timestamp); } return c; } @Override public String toString() { return "q=" + Arrays.toString(qualifier) + " [ofs=" + qualifier_offset + "], v=" + Arrays.toString(value) + " [ofs=" + value_offset + "], ts=" + current_timestamp_offset; } }