package com.linkedin.thirdeye.client.comparison;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.joda.time.DateTime;
import com.google.common.collect.Range;
import com.linkedin.thirdeye.api.TimeGranularity;
import com.linkedin.thirdeye.client.MetricExpression;
import com.linkedin.thirdeye.client.MetricFunction;
import com.linkedin.thirdeye.client.ThirdEyeClient;
import com.linkedin.thirdeye.client.ThirdEyeRequest;
import com.linkedin.thirdeye.client.ThirdEyeRequest.ThirdEyeRequestBuilder;
import com.linkedin.thirdeye.client.ThirdEyeResponse;
import com.linkedin.thirdeye.client.TimeRangeUtils;
import com.linkedin.thirdeye.client.cache.QueryCache;
import com.linkedin.thirdeye.client.comparison.Row.Metric;
import com.linkedin.thirdeye.dashboard.Utils;
public class TimeOnTimeComparisonHandler {
private static final String CURRENT = "current";
private static final String BASELINE = "baseline";
private final QueryCache queryCache;
public TimeOnTimeComparisonHandler(QueryCache queryCache) {
this.queryCache = queryCache;
}
public TimeOnTimeComparisonResponse handle(TimeOnTimeComparisonRequest comparisonRequest)
throws Exception {
List<Range<DateTime>> baselineTimeranges = new ArrayList<>();
List<Range<DateTime>> currentTimeranges = new ArrayList<>();
TimeGranularity aggregationTimeGranularity = comparisonRequest.getAggregationTimeGranularity();
// baseline time ranges
DateTime baselineStart = comparisonRequest.getBaselineStart();
DateTime baselineEnd = comparisonRequest.getBaselineEnd();
// current time ranges
DateTime currentStart = comparisonRequest.getCurrentStart();
DateTime currentEnd = comparisonRequest.getCurrentEnd();
if (comparisonRequest.isEndDateInclusive()) {
// ThirdEyeRequest is exclusive endpoint, so increment end by one bucket
currentEnd = TimeRangeUtils.increment(currentEnd, aggregationTimeGranularity );
baselineEnd = TimeRangeUtils.increment(baselineEnd, aggregationTimeGranularity );
}
baselineTimeranges =
TimeRangeUtils.computeTimeRanges(aggregationTimeGranularity, baselineStart, baselineEnd);
currentTimeranges =
TimeRangeUtils.computeTimeRanges(aggregationTimeGranularity, currentStart, currentEnd);
// create baseline request
ThirdEyeRequest baselineRequest =
createThirdEyeRequest(BASELINE, comparisonRequest, baselineStart, baselineEnd);
// create current request
ThirdEyeRequest currentRequest =
createThirdEyeRequest(CURRENT, comparisonRequest, currentStart, currentEnd);
List<ThirdEyeRequest> requests = new ArrayList<>();
requests.add(baselineRequest);
requests.add(currentRequest);
Map<ThirdEyeRequest, Future<ThirdEyeResponse>> futureResponseMap;
futureResponseMap = queryCache.getQueryResultsAsync(requests);
ThirdEyeResponse baselineResponse = null;
ThirdEyeResponse currentResponse = null;
for (Entry<ThirdEyeRequest, Future<ThirdEyeResponse>> entry : futureResponseMap.entrySet()) {
ThirdEyeRequest request = entry.getKey();
Future<ThirdEyeResponse> responseFuture = entry.getValue();
ThirdEyeResponse response = responseFuture.get(60000, TimeUnit.SECONDS);
if (BASELINE.equals(request.getRequestReference())) {
baselineResponse = response;
} else if (CURRENT.equals(request.getRequestReference())) {
currentResponse = response;
}
}
TimeOnTimeResponseParser timeOnTimeResponseParser =
new TimeOnTimeResponseParser(baselineResponse, currentResponse, baselineTimeranges,
currentTimeranges, comparisonRequest.getAggregationTimeGranularity(),
comparisonRequest.getGroupByDimensions());
List<Row> rows = timeOnTimeResponseParser.parseResponse();
// compute the derived metrics
computeDerivedMetrics(comparisonRequest, rows);
return new TimeOnTimeComparisonResponse(rows);
}
private void computeDerivedMetrics(TimeOnTimeComparisonRequest comparisonRequest, List<Row> rows)
throws Exception {
// compute list of derived expressions
List<MetricFunction> metricFunctionsFromExpressions =
Utils.computeMetricFunctionsFromExpressions(comparisonRequest.getMetricExpressions());
Set<String> metricNameSet = new HashSet<>();
for (MetricFunction function : metricFunctionsFromExpressions) {
metricNameSet.add(function.getMetricName());
}
List<MetricExpression> derivedMetricExpressions = new ArrayList<>();
for (MetricExpression expression : comparisonRequest.getMetricExpressions()) {
if (!metricNameSet.contains(expression.getExpressionName())) {
derivedMetricExpressions.add(expression);
}
}
// add metric expressions
if (derivedMetricExpressions.size() > 0) {
Map<String, Double> baselineValueContext = new HashMap<>();
Map<String, Double> currentValueContext = new HashMap<>();
for (Row row : rows) {
baselineValueContext.clear();
currentValueContext.clear();
List<Metric> metrics = row.getMetrics();
// baseline value
for (Metric metric : metrics) {
baselineValueContext.put(metric.getMetricName(), metric.getBaselineValue());
currentValueContext.put(metric.getMetricName(), metric.getCurrentValue());
}
for (MetricExpression expression : derivedMetricExpressions) {
String derivedMetricExpression = expression.getExpression();
double derivedMetricBaselineValue =
MetricExpression.evaluateExpression(derivedMetricExpression, baselineValueContext);
if (Double.isInfinite(derivedMetricBaselineValue)
|| Double.isNaN(derivedMetricBaselineValue)) {
derivedMetricBaselineValue = 0;
}
double currentMetricBaselineValue =
MetricExpression.evaluateExpression(derivedMetricExpression, currentValueContext);
if (Double.isInfinite(currentMetricBaselineValue)
|| Double.isNaN(currentMetricBaselineValue)) {
currentMetricBaselineValue = 0;
}
row.getMetrics().add(
new Metric(expression.getExpressionName(), derivedMetricBaselineValue,
currentMetricBaselineValue));
}
}
}
}
private static ThirdEyeRequest createThirdEyeRequest(String requestReference,
TimeOnTimeComparisonRequest comparisonRequest, DateTime start, DateTime end) {
ThirdEyeRequestBuilder requestBuilder = ThirdEyeRequest.newBuilder();
requestBuilder.setStartTimeInclusive(start);
requestBuilder.setEndTimeExclusive(end);
requestBuilder.setFilterSet(comparisonRequest.getFilterSet());
requestBuilder.addGroupBy(comparisonRequest.getGroupByDimensions());
requestBuilder.setGroupByTimeGranularity(comparisonRequest.getAggregationTimeGranularity());
List<MetricExpression> metricExpressions = comparisonRequest.getMetricExpressions();
List<MetricFunction> metricFunctionsFromExpressions =
Utils.computeMetricFunctionsFromExpressions(metricExpressions);
requestBuilder.setMetricFunctions(metricFunctionsFromExpressions);
return requestBuilder.build(requestReference);
}
public ThirdEyeClient getClient() {
return queryCache.getClient();
}
}