/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
package org.apache.hadoop.yarn.server.applicationhistoryservice.metrics.timeline.query;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.metrics2.sink.timeline.Precision;
import org.apache.hadoop.yarn.server.applicationhistoryservice.metrics.timeline.aggregators.Function;
import static org.apache.hadoop.yarn.server.applicationhistoryservice.metrics.timeline.query.PhoenixTransactSQL.NATIVE_TIME_RANGE_DELTA;
import java.util.List;
public class TopNCondition extends DefaultCondition{
private Integer topN;
private boolean isBottomN;
private Function topNFunction;
private static final Log LOG = LogFactory.getLog(TopNCondition.class);
public TopNCondition(List<String> metricNames, List<String> hostnames, String appId,
String instanceId, Long startTime, Long endTime, Precision precision,
Integer limit, boolean grouped, Integer topN, Function topNFunction,
boolean isBottomN) {
super(metricNames, hostnames, appId, instanceId, startTime, endTime, precision, limit, grouped);
this.topN = topN;
this.isBottomN = isBottomN;
this.topNFunction = topNFunction;
}
@Override
public StringBuilder getConditionClause() {
StringBuilder sb = new StringBuilder();
boolean appendConjunction = false;
if (isTopNHostCondition(metricNames, hostnames)) {
appendConjunction = appendMetricNameClause(sb);
StringBuilder hostnamesCondition = new StringBuilder();
hostnamesCondition.append(" HOSTNAME IN (");
hostnamesCondition.append(getTopNInnerQuery());
hostnamesCondition.append(")");
appendConjunction = append(sb, appendConjunction, getHostnames(), hostnamesCondition.toString());
} else if (isTopNMetricCondition(metricNames, hostnames)) {
StringBuilder metricNamesCondition = new StringBuilder();
metricNamesCondition.append(" METRIC_NAME IN (");
metricNamesCondition.append(getTopNInnerQuery());
metricNamesCondition.append(")");
appendConjunction = append(sb, appendConjunction, getMetricNames(), metricNamesCondition.toString());
appendConjunction = appendHostnameClause(sb, appendConjunction);
} else {
LOG.error("Unsupported TopN Operation requested. Query can have either multiple hosts or multiple metric names " +
"but not both.");
return null;
}
appendConjunction = append(sb, appendConjunction, getAppId(), " APP_ID = ?");
appendConjunction = append(sb, appendConjunction, getInstanceId(), " INSTANCE_ID = ?");
appendConjunction = append(sb, appendConjunction, getStartTime(), " SERVER_TIME >= ?");
append(sb, appendConjunction, getEndTime(), " SERVER_TIME < ?");
return sb;
}
public String getTopNInnerQuery() {
String innerQuery = null;
if (isTopNHostCondition(metricNames, hostnames)) {
String groupByClause = "METRIC_NAME, HOSTNAME, APP_ID";
String orderByClause = getTopNOrderByClause();
innerQuery = String.format(PhoenixTransactSQL.TOP_N_INNER_SQL, PhoenixTransactSQL.getNaiveTimeRangeHint(getStartTime(), NATIVE_TIME_RANGE_DELTA),
"HOSTNAME", PhoenixTransactSQL.getTargetTableUsingPrecision(precision, true), super.getConditionClause().toString(),
groupByClause, orderByClause, topN);
} else if (isTopNMetricCondition(metricNames, hostnames)) {
String groupByClause = "METRIC_NAME, APP_ID";
String orderByClause = getTopNOrderByClause();
innerQuery = String.format(PhoenixTransactSQL.TOP_N_INNER_SQL, PhoenixTransactSQL.getNaiveTimeRangeHint(getStartTime(), NATIVE_TIME_RANGE_DELTA),
"METRIC_NAME", PhoenixTransactSQL.getTargetTableUsingPrecision(precision, (hostnames != null && hostnames.size() == 1)),
super.getConditionClause().toString(),
groupByClause, orderByClause, topN);
}
return innerQuery;
}
private String getTopNOrderByClause() {
String orderByClause = getColumnSelect(this.topNFunction);
orderByClause += (isBottomN ? " ASC" : " DESC");
return orderByClause;
}
public static String getColumnSelect(Function topNFunction) {
String columnSelect = null;
if (topNFunction != null) {
switch (topNFunction.getReadFunction()) {
case AVG:
columnSelect = "ROUND(AVG(METRIC_SUM),2)";
break;
case SUM:
columnSelect = "SUM(METRIC_SUM)";
break;
default:
columnSelect = "MAX(METRIC_MAX)";
break;
}
}
if (columnSelect == null) {
columnSelect = "MAX(METRIC_MAX)";
}
return columnSelect;
}
public boolean isTopNHostCondition() {
return isTopNHostCondition(metricNames, hostnames);
}
public boolean isTopNMetricCondition() {
return isTopNMetricCondition(metricNames, hostnames);
}
/**
* Check if this is a case of Top N hosts condition
* @param metricNames A list of Strings.
* @param hostnames A list of Strings.
* @return True if it is a Case of Top N Hosts (1 Metric and H hosts).
*/
public static boolean isTopNHostCondition(List<String> metricNames, List<String> hostnames) {
// Case 1 : 1 Metric, H hosts
// Select Top N or Bottom N host series based on 1 metric (max/avg/sum)
// Hostnames cannot be empty
// Only 1 metric allowed, without wildcards
return (CollectionUtils.isNotEmpty(hostnames) && metricNames.size() == 1 && !metricNamesHaveWildcard(metricNames));
}
/**
* Check if this is a case of Top N metrics condition
* @param metricNames A list of Strings.
* @param hostnames A list of Strings.
* @return True if it is a Case of Top N Metrics (M Metric and 1 or 0 host).
*/
public static boolean isTopNMetricCondition(List<String> metricNames, List<String> hostnames) {
// Case 2 : M Metric names or Regex, 1 or No host
// Select Top N or Bottom N metric series based on metric values(max/avg/sum)
// MetricNames cannot be empty
// No host (aggregate) or 1 host allowed, without wildcards
return (CollectionUtils.isNotEmpty(metricNames) && (hostnames == null || hostnames.size() <= 1) &&
!hostNamesHaveWildcard(hostnames));
}
public Integer getTopN() {
return topN;
}
public void setTopN(Integer topN) {
this.topN = topN;
}
public boolean isBottomN() {
return isBottomN;
}
public void setIsBottomN(boolean isBottomN) {
this.isBottomN = isBottomN;
}
public Function getTopNFunction() {
return topNFunction;
}
public void setTopNFunction(Function topNFunction) {
this.topNFunction = topNFunction;
}
}