// This file is part of OpenTSDB. // Copyright (C) 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.NoSuchElementException; /** * Iterator that generates rates from a sequence of adjacent data points. */ public class RateSpan implements SeekableView { // The Long.MAX_VALUE works fine as the invalid timestamp with open-ended // time ranges. /** Timestamp to indicate that the data point is invalid. */ private static long INVALID_TIMESTAMP = Long.MAX_VALUE; /** A sequence of data points to compute rates. */ private final SeekableView source; /** Options for calculating rates. */ private final RateOptions options; // TODO: use primitives for next_data, next_rate, and prev_rate instead // in order to reduce memory and CPU overhead. /** The latter of two raw data points used to calculate the next rate. */ private final MutableDataPoint next_data = new MutableDataPoint(); /** The rate that will be returned at the {@link #next} call. */ private final MutableDataPoint next_rate = new MutableDataPoint(); /** Users see this rate after they called next. */ private final MutableDataPoint prev_rate = new MutableDataPoint(); /** True if it is initialized for iterating rates of changes. */ private boolean initialized = false; /** * Constructs a {@link RateSpan} instance. * @param source The iterator to access the underlying data. * @param options Options for calculating rates. */ RateSpan(final SeekableView source, final RateOptions options) { this.source = source; this.options = options; } // ------------------ // // Iterator interface // // ------------------ // /** @return True if there is a valid next value. */ @Override public boolean hasNext() { initializeIfNotDone(); return next_rate.timestamp() != INVALID_TIMESTAMP; } /** * @return the next rate of changes. * @throws NoSuchElementException if there is no more data. */ @Override public DataPoint next() { initializeIfNotDone(); if (hasNext()) { // NOTE: Just copies currentRate to prevRate, and does not allocate // any new DataPoint object to reduce the memory allocation overhead. // So, users access data at prev_rate. prev_rate.reset(next_rate); populateNextRate(); return prev_rate; } else { throw new NoSuchElementException("no more values for " + toString()); } } @Override public void remove() { throw new UnsupportedOperationException(); } // ---------------------- // // SeekableView interface // // ---------------------- // @Override public void seek(long timestamp) { source.seek(timestamp); initialized = false; } // ---------------------- // // Private methods // // ---------------------- // /** Initializes to iterate rate of changes. */ private void initializeIfNotDone() { // NOTE: Delay initialization is needed to not access any data point // from source until a user requests it explicitly to avoid the // performance penalty by accessing the first data of a span. if (!initialized) { initialized = true; // NOTE: Calculates the first rate between the time zero and the first // data point for the backward compatibility. // TODO: Don't compute the first rate with the time zero. next_data.reset(0, 0); // Sets the first rate to be retrieved. populateNextRate(); } } /** * Populate the next rate. */ private void populateNextRate() { final MutableDataPoint prev_data = new MutableDataPoint(); if (source.hasNext()) { prev_data.reset(next_data); next_data.reset(source.next()); final long t0 = prev_data.timestamp(); final long t1 = next_data.timestamp(); if (t1 <= t0) { throw new IllegalStateException( "Next timestamp (" + t1 + ") is supposed to be " + " strictly greater than the previous one (" + t0 + "), but it's" + " not. this=" + this); } // TODO: for backwards compatibility we'll convert the ms to seconds // but in the future we should add a ratems flag that will calculate // the rate as is. final double time_delta_secs = ((double)(t1 - t0) / 1000.0); double difference; if (prev_data.isInteger() && next_data.isInteger()) { // NOTE: Calculates in the long type to avoid precision loss // while converting long values to double values if both values are long. // NOTE: Ignores the integer overflow. difference = next_data.longValue() - prev_data.longValue(); } else { difference = next_data.toDouble() - prev_data.toDouble(); } if (options.isCounter() && difference < 0) { if (options.getDropResets()) { populateNextRate(); return; } if (prev_data.isInteger() && next_data.isInteger()) { // NOTE: Calculates in the long type to avoid precision loss // while converting long values to double values if both values are long. difference = options.getCounterMax() - prev_data.longValue() + next_data.longValue(); } else { difference = options.getCounterMax() - prev_data.toDouble() + next_data.toDouble(); } // If the rate is greater than the reset value, return a 0 final double rate = difference / time_delta_secs; if (options.getResetValue() > RateOptions.DEFAULT_RESET_VALUE && rate > options.getResetValue()) { next_rate.reset(next_data.timestamp(), 0.0D); } else { next_rate.reset(next_data.timestamp(), rate); } } else { next_rate.reset(next_data.timestamp(), (difference / time_delta_secs)); } } else { // Invalidates the next rate with invalid timestamp. next_rate.reset(INVALID_TIMESTAMP, 0); } } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append("RateSpan: ") .append(", options=").append(options) .append(", next_data=[").append(next_data) .append("], next_rate=[").append(next_rate) .append("], prev_rate=[").append(prev_rate) .append("], source=[").append(source).append("]"); return buf.toString(); } }