// This file is part of OpenTSDB. // Copyright (C) 2015 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.query.expression; import net.opentsdb.core.DataPoint; import net.opentsdb.core.DataPoints; import net.opentsdb.core.FillPolicy; import net.opentsdb.core.SeekableView; import net.opentsdb.utils.ByteSet; /** * Holds the results of a sub query (a single metric) and iterates over each * resultant series in lock-step for expression evaluation. * @since 2.3 */ public class TimeSyncedIterator implements ITimeSyncedIterator { /** The name of this sub query given by the user */ private final String id; /** The set of tag keys issued with the query */ private final ByteSet query_tagks; /** The data point interfaces fetched from storage */ private final DataPoints[] dps; /** The current value used for iterating */ private final DataPoint[] current_values; /** References to the MutableDataObjects the ExpressionIterator will read */ private final ExpressionDataPoint[] emitter_values; /** A list of the iterators used for fetching the next value */ private final SeekableView[] iterators; /** Set by the ExpressionIterator when it computes the intersection */ private int index; /** A policy to use for emitting values when a timestamp is missing data */ private NumericFillPolicy fill_policy; /** * Instantiates an iterator based on the results of a TSSubQuery. * This will setup the emitters so it's safe to call {@link #values()} * @param id The name of the query. * @param query_tagks The set of tags used in filters on the query. * @param dps The data points fetched from storage. * @throws IllegalArgumentException if one of the parameters is null or the ID * is empty */ public TimeSyncedIterator(final String id, final ByteSet query_tagks, final DataPoints[] dps) { if (id == null || id.isEmpty()) { throw new IllegalArgumentException("Missing ID string"); } if (dps == null) { // it's ok for these to be empty, but they canna be null ya ken? throw new IllegalArgumentException("Missing data points"); } this.id = id; this.query_tagks = query_tagks; this.dps = dps; // TODO - load from a default or something fill_policy = new NumericFillPolicy(FillPolicy.ZERO); current_values = new DataPoint[dps.length]; emitter_values = new ExpressionDataPoint[dps.length]; iterators = new SeekableView[dps.length]; setupEmitters(); } /** * A copy constructor that loads from an existing iterator. * @param iterator The iterator to load from */ private TimeSyncedIterator(final TimeSyncedIterator iterator) { id = iterator.id; query_tagks = iterator.query_tagks; // sharing is ok here dps = iterator.dps; // TODO ?? OK? fill_policy = iterator.fill_policy; current_values = new DataPoint[dps.length]; emitter_values = new ExpressionDataPoint[dps.length]; iterators = new SeekableView[dps.length]; setupEmitters(); } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append("TimeSyncedIterator(id=") .append(id) .append(", index=") .append(index) .append(", dpsSize=") .append(dps.length) .append(")"); return buf.toString(); } @Override public int size() { return dps.length; } @Override public boolean hasNext() { for (final DataPoint dp : current_values) { if (dp != null) { return true; } } return false; } @Override public ExpressionDataPoint[] next(final long timestamp) { for (int i = 0; i < current_values.length; i++) { if (current_values[i] == null) { emitter_values[i].reset(timestamp, fill_policy.getValue()); continue; } if (current_values[i].timestamp() > timestamp) { emitter_values[i].reset(timestamp, fill_policy.getValue()); } else { emitter_values[i].reset(current_values[i]); if (!iterators[i].hasNext()) { current_values[i] = null; } else { current_values[i] = iterators[i].next(); } } } return emitter_values; } @Override public long nextTimestamp() { long ts = Long.MAX_VALUE; for (final DataPoint dp : current_values) { if (dp != null) { long t = dp.timestamp(); if (t < ts) { ts = t; } } } return ts; } @Override public void next(final int i) { if (current_values[i] == null) { throw new RuntimeException("No more elements"); } emitter_values[i].reset(current_values[i]); if (iterators[i].hasNext()) { current_values[i] = iterators[i].next(); } else { current_values[i] = null; } } @Override public boolean hasNext(final int i) { return current_values[i] != null; } @Override public int getIndex() { return index; } @Override public void setIndex(final int index) { this.index = index; } @Override public String getId() { return id; } /** @return the set of data points */ public DataPoints[] getDataPoints() { return dps; } @Override public void nullIterator(final int index) { if (index < 0 || index > current_values.length) { throw new IllegalArgumentException("Index out of range: " + index); } current_values[index] = null; } @Override public ExpressionDataPoint[] values() { return emitter_values; } @Override public ByteSet getQueryTagKs() { return query_tagks; } @Override public void setFillPolicy(final NumericFillPolicy policy) { fill_policy = policy; } @Override public NumericFillPolicy getFillPolicy() { return fill_policy; } @Override public ITimeSyncedIterator getCopy() { return new TimeSyncedIterator(this); } /** * Iterates over the values and sets up the current and emitter values */ private void setupEmitters() { // set the iterators for (int i = 0; i < dps.length; i++) { iterators[i] = dps[i].iterator(); if (!iterators[i].hasNext()) { current_values[i] = null; emitter_values[i] = null; } else { current_values[i] = iterators[i].next(); emitter_values[i] = new ExpressionDataPoint(dps[i]); emitter_values[i].setIndex(i); } } } }