package net.opentsdb.query.expression; /** * Copyright 2015 The opentsdb Authors * <p/> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import net.opentsdb.core.*; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; public class TimeShift implements Expression { /** * in place modify of TsdbResult array to increase timestamps by timeshift * @param data_query * @param results * @param params * @return */ @Override public DataPoints[] evaluate(TSQuery data_query, List<DataPoints[]> results, List<String> params) { //not 100% sure what to do here -> do I need to think of the case where I have no data points if(results == null || results.isEmpty()) { return new DataPoints[]{}; } if(params == null || results.isEmpty()) { throw new IllegalArgumentException("Need amount of timeshift to perform timeshift"); } String param = params.get(0); if (param == null || param.length() == 0) { throw new IllegalArgumentException("Invalid timeshift='" + param + "'"); } param = param.trim(); long timeshift = -1; if (param.startsWith("'") && param.endsWith("'")) { timeshift = parseParam(param) / 1000; } else { throw new RuntimeException("Invalid timeshift parameter: eg '10min'"); } if (timeshift <= 0) { throw new RuntimeException("timeshift <= 0"); } DataPoints[] inputPoints = results.get(0); DataPoints[] outputPoints = new DataPoints[inputPoints.length]; for(int n = 0; n < inputPoints.length; n++) { outputPoints[n] = shift(inputPoints[n], timeshift); } return outputPoints; } public static long parseParam(String param) { char[] chars = param.toCharArray(); int tuIndex = 0; for (int c = 1; c < chars.length; c++) { if (Character.isDigit(chars[c])) { tuIndex++; } else { break; } } if (tuIndex == 0) { throw new RuntimeException("Invalid Parameter: " + param); } int time = Integer.parseInt(param.substring(1, tuIndex + 1)); String unit = param.substring(tuIndex + 1, param.length() - 1); if ("sec".equals(unit)) { return TimeUnit.MILLISECONDS.convert(time, TimeUnit.SECONDS); } else if ("min".equals(unit)) { return TimeUnit.MILLISECONDS.convert(time, TimeUnit.MINUTES); } else if ("hr".equals(unit)) { return TimeUnit.MILLISECONDS.convert(time, TimeUnit.HOURS); } else if ("day".equals(unit) || "days".equals(unit)) { return TimeUnit.MILLISECONDS.convert(time, TimeUnit.DAYS); } else if ("week".equals(unit) || "weeks".equals(unit)) { //didn't have week so small cheat here return TimeUnit.MILLISECONDS.convert(time*7, TimeUnit.DAYS); } else { throw new RuntimeException("unknown time unit=" + unit); } } /** * Adjusts the timestamp of each datapoint by timeshift * @param points The data points to factor * @param timeshift The factor to multiply by * @return The resulting data points */ private DataPoints shift(final DataPoints points, final long timeshift) { // 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 shift_is_int = (timeshift == Math.floor(timeshift)) && !Double.isInfinite(timeshift); final SeekableView view = points.iterator(); while (view.hasNext()) { DataPoint pt = view.next(); if (shift_is_int) { dps.add(MutableDataPoint.ofLongValue(pt.timestamp() + timeshift, pt.longValue())); } else { // NaNs are fine here, they'll just be re-computed as NaN dps.add(MutableDataPoint.ofDoubleValue(pt.timestamp() + timeshift, timeshift * pt.toDouble())); } } final DataPoint[] results = new DataPoint[dps.size()]; dps.toArray(results); return new PostAggregatedDataPoints(points, results); } @Override public String writeStringField(List<String> params, String inner_expression) { return "timeshift(" + inner_expression + ")"; } }