// 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 java.util.ArrayList; import java.util.List; import net.opentsdb.core.DataPoint; import net.opentsdb.core.DataPoints; import net.opentsdb.core.MutableDataPoint; import net.opentsdb.core.SeekableView; import net.opentsdb.core.TSQuery; /** * Multiplies each data point in the series by the given factor. * @since 2.3 */ public class Scale implements Expression { @Override public DataPoints[] evaluate(final TSQuery data_query, final List<DataPoints[]> query_results, final List<String> params) { if (data_query == null) { throw new IllegalArgumentException("Missing time series query"); } if (query_results == null || query_results.isEmpty()) { return new DataPoints[]{}; } if (params == null || params.isEmpty()) { throw new IllegalArgumentException("Missing scaling factor"); } double scale_factor = 0; // zero is fine, if useless *shrug* final String factor = params.get(0); if (factor != null && factor.matches("^[-0-9\\.]+$")) { try { scale_factor = Double.parseDouble(factor); } catch (NumberFormatException nfe) { throw new IllegalArgumentException( "Invalid parameter, must be an integer or floating point", nfe); } } else { throw new IllegalArgumentException("Unparseable scale factor value: " + scale_factor); } int num_results = 0; for (DataPoints[] results: query_results) { num_results += results.length; } final DataPoints[] results = new DataPoints[num_results]; int ix = 0; // one or more sub queries (m=...&m=...&m=...) for (final DataPoints[] sub_query_result : query_results) { // group bys (m=sum:foo{host=*}) for (final DataPoints dps : sub_query_result) { results[ix++] = scale(dps, scale_factor); } } return results; } /** * Multiplies each data point in the series by the scale factor, maintaining * integers if both the data point and scale are integers. * @param points The data points to factor * @param scale_factor The factor to multiply by * @return The resulting data points */ private DataPoints scale(final DataPoints points, final double scale_factor) { // TODO(cl) - Using an array as the size function may not return the exact // results and we should figure a way to avoid copying data anyway. final List<DataPoint> dps = new ArrayList<DataPoint>(); final boolean scale_is_int = (scale_factor == Math.floor(scale_factor)) && !Double.isInfinite(scale_factor); final SeekableView view = points.iterator(); while (view.hasNext()) { DataPoint pt = view.next(); if (pt.isInteger() && scale_is_int) { dps.add(MutableDataPoint.ofLongValue(pt.timestamp(), (long)scale_factor * pt.longValue())); } else { // NaNs are fine here, they'll just be re-computed as NaN dps.add(MutableDataPoint.ofDoubleValue(pt.timestamp(), scale_factor * pt.toDouble())); } } final DataPoint[] results = new DataPoint[dps.size()]; dps.toArray(results); return new PostAggregatedDataPoints(points, results); } @Override public String writeStringField(final List<String> query_params, final String inner_expression) { return "scale(" + inner_expression + ")"; } }