// This file is part of OpenTSDB. // Copyright (C) 2013 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.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import net.opentsdb.query.filter.TagVFilter; import net.opentsdb.utils.ByteSet; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.google.common.base.Objects; import com.google.common.collect.ImmutableMap; /** * Represents the parameters for an individual sub query on a metric or specific * timeseries. When setting up a query, use the setter methods to store user * information such as the start time and list of queries. After setting the * proper values, add the sub query to a {@link TSQuery}. * <p> * When the query is processed by the TSD, if the {@code tsuids} list has one * or more timeseries, the {@code metric} and {@code tags} fields will be * ignored and only the tsuids processed. * <p> * <b>Note:</b> You do not need to call {@link #validateAndSetQuery} directly as * the {@link TSQuery} object will call this for you when the entire set of * queries has been compiled. * <b>Note:</b> If using POJO deserialization, make sure to avoid setting the * {@code agg} and {@code downsample_specifier} fields. * @since 2.0 */ @JsonIgnoreProperties(ignoreUnknown = true) public final class TSSubQuery { /** User given name of an aggregation function to use */ private String aggregator; /** User given name for a metric, e.g. "sys.cpu.0" */ private String metric; /** User provided list of timeseries UIDs */ private List<String> tsuids; /** User given downsampler */ private String downsample; /** Whether or not the user wants to perform a rate conversion */ private boolean rate; /** Rate options for counter rollover/reset */ private RateOptions rate_options; /** Parsed aggregation function */ private Aggregator agg; /** Parsed downsampling specification. */ private DownsamplingSpecification downsample_specifier; /** A list of filters for this query. For now these are pulled out of the * tags map. In the future we'll have special JSON objects for them. */ private List<TagVFilter> filters; /** Whether or not to match series with ONLY the given tags */ private boolean explicit_tags; /** Index of the sub query */ private int index; /** * Default constructor necessary for POJO de/serialization */ public TSSubQuery() { // Assume no downsampling until told otherwise. downsample_specifier = DownsamplingSpecification.NO_DOWNSAMPLER; } @Override public int hashCode() { // NOTE: Do not add any non-user submitted variables to the hash. We don't // want the hash to change after validation. return Objects.hashCode(aggregator, metric, tsuids, downsample, rate, rate_options, filters, explicit_tags); } @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (!(obj instanceof TSSubQuery)) { return false; } if (obj == this) { return true; } // NOTE: Do not add any non-user submitted variables to the comparator. We // don't want the value to change after validation. final TSSubQuery query = (TSSubQuery)obj; return Objects.equal(aggregator, query.aggregator) && Objects.equal(metric, query.metric) && Objects.equal(tsuids, query.tsuids) && Objects.equal(downsample, query.downsample) && Objects.equal(rate, query.rate) && Objects.equal(rate_options, query.rate_options) && Objects.equal(filters, query.filters) && Objects.equal(explicit_tags, query.explicit_tags); } public String toString() { final StringBuilder buf = new StringBuilder(); buf.append("TSSubQuery(metric=") .append(metric == null || metric.isEmpty() ? "" : metric); buf.append(", filters=["); if (filters != null && !filters.isEmpty()) { int counter = 0; for (final TagVFilter filter : filters) { if (counter > 0) { buf.append(", "); } buf.append(filter); ++counter; } } buf.append("], tsuids=["); if (tsuids != null && !tsuids.isEmpty()) { int counter = 0; for (String tsuid : tsuids) { if (counter > 0) { buf.append(", "); } buf.append(tsuid); counter++; } } buf.append("], agg=") .append(aggregator) .append(", downsample=") .append(downsample) .append(", ds_interval=") .append(downsample_specifier.getInterval()) .append(", rate=") .append(rate) .append(", rate_options=") .append(rate_options) .append(", explicit_tags=") .append("explicit_tags") .append(", index=") .append(index) .append(")"); return buf.toString(); } /** * Runs through query parameters to make sure it's a valid request. * This includes parsing the aggregator, downsampling info, metrics, tags or * timeseries and setting the local parsed fields needed by the TSD for proper * execution. If no exceptions are thrown, the query is considered valid. * <b>Note:</b> You do not need to call this directly as it will be executed * by the {@link TSQuery} object the sub query is assigned to. * @throws IllegalArgumentException if something is wrong with the query */ public void validateAndSetQuery() { if (aggregator == null || aggregator.isEmpty()) { throw new IllegalArgumentException("Missing the aggregation function"); } try { agg = Aggregators.get(aggregator); } catch (NoSuchElementException nse) { throw new IllegalArgumentException( "No such aggregation function: " + aggregator); } // we must have at least one TSUID OR a metric if ((tsuids == null || tsuids.isEmpty()) && (metric == null || metric.isEmpty())) { throw new IllegalArgumentException( "Missing the metric or tsuids, provide at least one"); } // Make sure we have a filter list if (filters == null) { filters = new ArrayList<TagVFilter>(); } // parse the downsampler if we have one if (downsample != null && !downsample.isEmpty()) { // downsampler given, so parse it downsample_specifier = new DownsamplingSpecification(downsample); } else { // no downsampler downsample_specifier = DownsamplingSpecification.NO_DOWNSAMPLER; } } /** @return the parsed aggregation function */ public Aggregator aggregator() { return this.agg; } /** @return the parsed downsampler aggregation function * @deprecated use {@link #downsamplingSpecification()} instead */ public Aggregator downsampler() { return downsample_specifier.getFunction(); } /** @return the parsed downsample interval in seconds * @deprecated use {@link #downsamplingSpecification()} instead */ public long downsampleInterval() { return downsample_specifier.getInterval(); } /** @return The downsampling specification for more options * @since 2.3 */ public DownsamplingSpecification downsamplingSpecification() { return downsample_specifier; } /** * @return the downsampling fill policy * @since 2.2 */ public FillPolicy fillPolicy() { return downsample_specifier.getFillPolicy(); } /** @return the user supplied aggregator */ public String getAggregator() { return aggregator; } /** @return the user supplied metric */ public String getMetric() { return metric; } /** @return the user supplied list of TSUIDs */ public List<String> getTsuids() { return tsuids; } /** @return the user supplied list of group by query tags, may be empty. * Note that as of version 2.2 this is an immutable list of tags built from * the filter list. * @deprecated */ public Map<String, String> getTags() { if (filters == null) { return Collections.emptyMap(); } final Map<String, String> tags = new HashMap<String, String>(filters.size()); for (final TagVFilter filter : filters) { if (filter.isGroupBy()) { tags.put(filter.getTagk(), filter.getType() + "(" + filter.getFilter() + ")"); } } return ImmutableMap.copyOf(tags); } /** @return the raw downsampling function request from the user, * e.g. "1h-avg" or "15m-sum-nan" */ public String getDownsample() { return downsample; } /** @return whether or not the user requested a rate conversion */ public boolean getRate() { return rate; } /** @return options to use for rate calculations */ public RateOptions getRateOptions() { return rate_options; } /** @return the filters pulled from the tags object * @since 2.2 */ public List<TagVFilter> getFilters() { if (filters == null) { filters = new ArrayList<TagVFilter>(); } // send a copy so ordering doesn't mess up the hash code return new ArrayList<TagVFilter>(filters); } /** @return the unique set of tagks from the filters. May be null if no filters * were set. Must make sure to resolve the string tag to UIDs in the filter first. * @since 2.3 */ public ByteSet getFilterTagKs() { if (filters == null || filters.isEmpty()) { return null; } final ByteSet tagks = new ByteSet(); for (final TagVFilter filter : filters) { if (filter != null && filter.getTagkBytes() != null) { tagks.add(filter.getTagkBytes()); } } return tagks; } /** @return whether or not to match series with ONLY the given tags * @since 2.3 */ public boolean getExplicitTags() { return explicit_tags; } /** @return the index of the sub query * @since 2.3 */ public int getIndex() { return index; } /** @param aggregator the name of an aggregation function */ public void setAggregator(String aggregator) { this.aggregator = aggregator; } /** @param metric the name of a metric to fetch */ public void setMetric(String metric) { this.metric = metric; } /** @param tsuids a list of timeseries UIDs as hex encoded strings to fetch */ public void setTsuids(List<String> tsuids) { this.tsuids = tsuids; } /** @param tags an optional list of tags for specificity or grouping * As of 2.2 this will convert the existing tags to filter * @deprecated */ public void setTags(Map<String, String> tags) { if (filters == null) { filters = new ArrayList<TagVFilter>(tags.size()); } else { filters.clear(); } TagVFilter.tagsToFilters(tags, filters); } /** @param downsample the downsampling function to use, e.g. "2h-avg" */ public void setDownsample(String downsample) { this.downsample = downsample; } /** @param rate whether or not the result should be rate converted */ public void setRate(boolean rate) { this.rate = rate; } /** @param options Options to set when calculating rates */ public void setRateOptions(RateOptions options) { this.rate_options = options; } /** @param filters A list of filters to use when querying * @since 2.2 */ public void setFilters(List<TagVFilter> filters) { this.filters = filters; } /** @param whether or not to match series with ONLY the given tags * @since 2.3 */ public void setExplicitTags(final boolean explicit_tags) { this.explicit_tags = explicit_tags; } /** @param index the index of the sub query * @since 2.3 */ public void setIndex(final int index) { this.index = index; } }