// 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.core;
import java.util.NoSuchElementException;
import java.util.TimeZone;
import com.google.common.base.MoreObjects;
import net.opentsdb.utils.DateTime;
/**
* Representation of a downsampling specification in a TSDB query.
* @since 2.2
*/
public final class DownsamplingSpecification {
/** Instance of a specification indicating no downsampling requested. */
public static final DownsamplingSpecification NO_DOWNSAMPLER =
new DownsamplingSpecification();
/** Special value representing no downsampling interval given. */
public static final long NO_INTERVAL = 0L;
/** Special value representing no downsampling function given. */
public static final Aggregator NO_FUNCTION = null;
/** The default fill policy. */
public static final FillPolicy DEFAULT_FILL_POLICY = FillPolicy.NONE;
// Parsed downsample interval.
private final long interval;
//The string interval, e.g. 1h, 30d, etc
private final String string_interval;
// Parsed downsampler function.
private final Aggregator function;
// Parsed fill policy: whether to interpolate or to fill.
private final FillPolicy fill_policy;
// Whether or not to use the calendar for intervals
private boolean use_calendar;
// The user provided timezone for calendar alignment (defaults to UTC)
private TimeZone timezone;
/**
* A specification indicating no downsampling is requested.
*/
private DownsamplingSpecification() {
interval = NO_INTERVAL;
function = NO_FUNCTION;
fill_policy = DEFAULT_FILL_POLICY;
string_interval = null;
use_calendar = false;
timezone = DateTime.timezones.get(DateTime.UTC_ID);
}
/**
* Non-stringified, piecewise c-tor.
* @param interval The downsampling interval, in milliseconds.
* @param function The downsampling function.
* @param fill_policy The policy specifying how to deal with missing data.
* @throws IllegalArgumentException if any argument is invalid.
* @deprecated since 2.3
*/
public DownsamplingSpecification(final long interval,
final Aggregator function, final FillPolicy fill_policy) {
if (null == function) {
throw new IllegalArgumentException("downsampling function cannot be null");
}
if (interval <= 0L) {
throw new IllegalArgumentException("interval not > 0: " + interval);
}
if (null == fill_policy) {
throw new IllegalArgumentException("fill policy cannot be null");
}
if (function == Aggregators.NONE) {
throw new IllegalArgumentException("cannot use the NONE "
+ "aggregator for downsampling");
}
this.interval = interval;
this.function = function;
this.fill_policy = fill_policy;
string_interval = null;
use_calendar = false;
timezone = DateTime.timezones.get(DateTime.UTC_ID);
}
/**
* C-tor for string representations.
* The argument to this c-tor should have the following format:
* {@code interval-function[-fill_policy]}.
* This ctor supports the "all" flag to downsample to a single value as well
* as units suffixed with 'c' to use the calendar for downsample alignment.
* @param specification String representation of a downsample specifier.
* @throws IllegalArgumentException if the specification is null or invalid.
*/
public DownsamplingSpecification(final String specification) {
if (null == specification) {
throw new IllegalArgumentException("Downsampling specifier cannot be " +
"null");
}
final String[] parts = specification.split("-");
if (parts.length < 2) {
// Too few items.
throw new IllegalArgumentException("Invalid downsampling specifier '" +
specification + "': must provide at least interval and function");
} else if (parts.length > 3) {
// Too many items.
throw new IllegalArgumentException("Invalid downsampling specifier '" +
specification + "': must consist of interval, function, and optional " +
"fill policy");
}
// This porridge is just right.
// INTERVAL.
// This will throw if interval is invalid.
if (parts[0].contains("all")) {
interval = NO_INTERVAL;
use_calendar = false;
string_interval = parts[0];
} else if (parts[0].charAt(parts[0].length() - 1) == 'c') {
final String duration = parts[0].substring(0, parts[0].length() - 1);
interval = DateTime.parseDuration(duration);
string_interval = duration;
use_calendar = true;
} else {
interval = DateTime.parseDuration(parts[0]);
use_calendar = false;
string_interval = parts[0];
}
// FUNCTION.
try {
function = Aggregators.get(parts[1]);
} catch (final NoSuchElementException e) {
throw new IllegalArgumentException("No such downsampling function: " +
parts[1]);
}
if (function == Aggregators.NONE) {
throw new IllegalArgumentException("cannot use the NONE "
+ "aggregator for downsampling");
}
// FILL POLICY.
if (3 == parts.length) {
// If the user gave us three parts, then the third must be a fill
// policy.
fill_policy = FillPolicy.fromString(parts[2]);
if (null == fill_policy) {
final StringBuilder oss = new StringBuilder();
oss.append("No such fill policy: '").append(parts[2])
.append("': must be one of:");
for (final FillPolicy policy : FillPolicy.values()) {
oss.append(" ").append(policy.getName());
}
throw new IllegalArgumentException(oss.toString());
}
} else {
// Default to linear interpolation.
fill_policy = FillPolicy.NONE;
}
timezone = DateTime.timezones.get(DateTime.UTC_ID);
}
/** @param use_calendar Whether or not to use the calendar when downsampling
* @since 2.3 */
public void setUseCalendar(final boolean use_calendar) {
this.use_calendar = use_calendar;
}
/** @param timezone The timezone to use when downsampling on calendar
* boundaries.
* @since 2.3 */
public void setTimezone(final TimeZone timezone) {
if (timezone == null) {
throw new IllegalArgumentException("Timezone cannot be null");
}
this.timezone = timezone;
}
/**
* Get the downsampling interval, in milliseconds.
* @return the downsampling interval, in milliseconds.
*/
public long getInterval() {
return interval;
}
/** @return The string interval from the user (without the 'c' if given)
* @since 2.3 */
public String getStringInterval() {
return string_interval;
}
/**
* Get the downsampling function.
* @return the downsampling function.
*/
public Aggregator getFunction() {
return function;
}
/**
* Get the policy specifying how to deal with missing data.
* @return the policy specifying how to deal with missing data.
*/
public FillPolicy getFillPolicy() {
return fill_policy;
}
/** @return Whether or not to use the calendar when downsampling
* @since 2.3 */
public boolean useCalendar() {
return use_calendar;
}
/** @return The timezone to use when downsampling on calendar boundaries.
* @since 2.3 */
public TimeZone getTimezone() {
return timezone;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("interval", getInterval())
.add("function", getFunction())
.add("fillPolicy", getFillPolicy())
.add("stringInterval", string_interval)
.add("useCalendar", useCalendar())
.add("timeZone", getTimezone() != null ? getTimezone().getID() : null)
.toString();
}
}