org.apache.hadoop.yarn.server.applicationhistoryservice.metrics.timeline.PhoenixTransactSQL.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.yarn.server.applicationhistoryservice.metrics.timeline.PhoenixTransactSQL.java

Source

/**
 * 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;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Encapsulate all metrics related SQL queries.
 */
public class PhoenixTransactSQL {

    static final Log LOG = LogFactory.getLog(PhoenixTransactSQL.class);
    /**
     * Create table to store individual metric records.
     */
    public static final String CREATE_METRICS_TABLE_SQL = "CREATE TABLE IF NOT "
            + "EXISTS METRIC_RECORD (METRIC_NAME VARCHAR, " + "HOSTNAME VARCHAR, "
            + "SERVER_TIME UNSIGNED_LONG NOT NULL, " + "APP_ID VARCHAR, " + "INSTANCE_ID VARCHAR, "
            + "START_TIME UNSIGNED_LONG, " + "UNITS CHAR(20), " + "METRIC_SUM DOUBLE, "
            + "METRIC_COUNT UNSIGNED_INT, " + "METRIC_MAX DOUBLE, " + "METRIC_MIN DOUBLE, "
            + "METRICS VARCHAR CONSTRAINT pk " + "PRIMARY KEY (METRIC_NAME, HOSTNAME, SERVER_TIME, APP_ID, "
            + "INSTANCE_ID)) DATA_BLOCK_ENCODING='%s', IMMUTABLE_ROWS=true, " + "TTL=%s, COMPRESSION='%s'";

    public static final String CREATE_METRICS_AGGREGATE_HOURLY_TABLE_SQL = "CREATE TABLE IF NOT EXISTS METRIC_RECORD_HOURLY "
            + "(METRIC_NAME VARCHAR, " + "HOSTNAME VARCHAR, " + "APP_ID VARCHAR, " + "INSTANCE_ID VARCHAR, "
            + "SERVER_TIME UNSIGNED_LONG NOT NULL, " + "UNITS CHAR(20), " + "METRIC_SUM DOUBLE,"
            + "METRIC_COUNT UNSIGNED_INT, " + "METRIC_MAX DOUBLE," + "METRIC_MIN DOUBLE CONSTRAINT pk "
            + "PRIMARY KEY (METRIC_NAME, HOSTNAME, APP_ID, INSTANCE_ID, "
            + "SERVER_TIME)) DATA_BLOCK_ENCODING='%s', IMMUTABLE_ROWS=true, " + "TTL=%s, COMPRESSION='%s'";

    public static final String CREATE_METRICS_AGGREGATE_MINUTE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS METRIC_RECORD_MINUTE "
            + "(METRIC_NAME VARCHAR, " + "HOSTNAME VARCHAR, " + "APP_ID VARCHAR, " + "INSTANCE_ID VARCHAR, "
            + "SERVER_TIME UNSIGNED_LONG NOT NULL, " + "UNITS CHAR(20), " + "METRIC_SUM DOUBLE,"
            + "METRIC_COUNT UNSIGNED_INT, " + "METRIC_MAX DOUBLE," + "METRIC_MIN DOUBLE CONSTRAINT pk "
            + "PRIMARY KEY (METRIC_NAME, HOSTNAME, APP_ID, INSTANCE_ID, "
            + "SERVER_TIME)) DATA_BLOCK_ENCODING='%s', IMMUTABLE_ROWS=true, TTL=%s," + " COMPRESSION='%s'";

    public static final String CREATE_METRICS_CLUSTER_AGGREGATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS METRIC_AGGREGATE "
            + "(METRIC_NAME VARCHAR, " + "APP_ID VARCHAR, " + "INSTANCE_ID VARCHAR, "
            + "SERVER_TIME UNSIGNED_LONG NOT NULL, " + "UNITS CHAR(20), " + "METRIC_SUM DOUBLE, "
            + "HOSTS_COUNT UNSIGNED_INT, " + "METRIC_MAX DOUBLE, " + "METRIC_MIN DOUBLE "
            + "CONSTRAINT pk PRIMARY KEY (METRIC_NAME, APP_ID, INSTANCE_ID, "
            + "SERVER_TIME)) DATA_BLOCK_ENCODING='%s', IMMUTABLE_ROWS=true, " + "TTL=%s, COMPRESSION='%s'";

    public static final String CREATE_METRICS_CLUSTER_AGGREGATE_HOURLY_TABLE_SQL = "CREATE TABLE IF NOT EXISTS METRIC_AGGREGATE_HOURLY "
            + "(METRIC_NAME VARCHAR, " + "APP_ID VARCHAR, " + "INSTANCE_ID VARCHAR, "
            + "SERVER_TIME UNSIGNED_LONG NOT NULL, " + "UNITS CHAR(20), " + "METRIC_SUM DOUBLE, "
            + "METRIC_COUNT UNSIGNED_INT, " + "METRIC_MAX DOUBLE, " + "METRIC_MIN DOUBLE "
            + "CONSTRAINT pk PRIMARY KEY (METRIC_NAME, APP_ID, INSTANCE_ID, "
            + "SERVER_TIME)) DATA_BLOCK_ENCODING='%s', IMMUTABLE_ROWS=true, " + "TTL=%s, COMPRESSION='%s'";

    /**
     * ALTER table to set new options
     */
    public static final String ALTER_SQL = "ALTER TABLE %s SET TTL=%s";

    /**
     * Insert into metric records table.
     */
    public static final String UPSERT_METRICS_SQL = "UPSERT INTO %s "
            + "(METRIC_NAME, HOSTNAME, APP_ID, INSTANCE_ID, SERVER_TIME, START_TIME, " + "UNITS, " + "METRIC_SUM, "
            + "METRIC_MAX, " + "METRIC_MIN, " + "METRIC_COUNT, " + "METRICS) VALUES "
            + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";

    public static final String UPSERT_CLUSTER_AGGREGATE_SQL = "UPSERT INTO "
            + "METRIC_AGGREGATE (METRIC_NAME, APP_ID, INSTANCE_ID, SERVER_TIME, " + "UNITS, " + "METRIC_SUM, "
            + "HOSTS_COUNT, " + "METRIC_MAX, " + "METRIC_MIN) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";

    public static final String UPSERT_CLUSTER_AGGREGATE_TIME_SQL = "UPSERT INTO"
            + " %s (METRIC_NAME, APP_ID, INSTANCE_ID, SERVER_TIME, " + "UNITS, " + "METRIC_SUM, " + "METRIC_COUNT, "
            + "METRIC_MAX, " + "METRIC_MIN) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";

    public static final String UPSERT_AGGREGATE_RECORD_SQL = "UPSERT INTO "
            + "%s (METRIC_NAME, HOSTNAME, APP_ID, INSTANCE_ID, " + "SERVER_TIME, " + "UNITS, " + "METRIC_SUM, "
            + "METRIC_MAX, " + "METRIC_MIN," + "METRIC_COUNT) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";

    /**
     * Retrieve a set of rows from metrics records table.
     */
    public static final String GET_METRIC_SQL = "SELECT %s METRIC_NAME, "
            + "HOSTNAME, APP_ID, INSTANCE_ID, SERVER_TIME, START_TIME, UNITS, " + "METRIC_SUM, " + "METRIC_MAX, "
            + "METRIC_MIN, " + "METRIC_COUNT, " + "METRICS " + "FROM %s";

    public static final String GET_METRIC_AGGREGATE_ONLY_SQL = "SELECT %s "
            + "METRIC_NAME, HOSTNAME, APP_ID, INSTANCE_ID, SERVER_TIME, " + "UNITS, " + "METRIC_SUM, "
            + "METRIC_MAX, " + "METRIC_MIN, " + "METRIC_COUNT " + "FROM %s";

    public static final String GET_CLUSTER_AGGREGATE_SQL = "SELECT %s " + "METRIC_NAME, APP_ID, "
            + "INSTANCE_ID, SERVER_TIME, " + "UNITS, " + "METRIC_SUM, " + "HOSTS_COUNT, " + "METRIC_MAX, "
            + "METRIC_MIN " + "FROM %s";

    public static final String GET_CLUSTER_AGGREGATE_HOURLY_SQL = "SELECT %s " + "METRIC_NAME, APP_ID, "
            + "INSTANCE_ID, SERVER_TIME, " + "UNITS, " + "METRIC_SUM, " + "METRIC_COUNT, " + "METRIC_MAX, "
            + "METRIC_MIN " + "FROM %s";

    public static final String METRICS_RECORD_TABLE_NAME = "METRIC_RECORD";
    public static final String METRICS_AGGREGATE_MINUTE_TABLE_NAME = "METRIC_RECORD_MINUTE";
    public static final String METRICS_AGGREGATE_HOURLY_TABLE_NAME = "METRIC_RECORD_HOURLY";
    public static final String METRICS_CLUSTER_AGGREGATE_TABLE_NAME = "METRIC_AGGREGATE";
    public static final String METRICS_CLUSTER_AGGREGATE_HOURLY_TABLE_NAME = "METRIC_AGGREGATE_HOURLY";
    public static final String DEFAULT_TABLE_COMPRESSION = "SNAPPY";
    public static final String DEFAULT_ENCODING = "FAST_DIFF";
    public static final long NATIVE_TIME_RANGE_DELTA = 120000; // 2 minutes
    public static final long HOUR = 3600000; // 1 hour
    public static final long DAY = 86400000; // 1 day

    /**
     * Filter to optimize HBase scan by using file timestamps. This prevents
     * a full table scan of metric records.
     *
     * @return Phoenix Hint String
     */
    public static String getNaiveTimeRangeHint(Long startTime, Long delta) {
        return String.format("/*+ NATIVE_TIME_RANGE(%s) */", (startTime - delta));
    }

    public static PreparedStatement prepareGetMetricsSqlStmt(Connection connection, Condition condition)
            throws SQLException {

        validateConditionIsNotEmpty(condition);
        validateRowCountLimit(condition);

        String stmtStr;
        if (condition.getStatement() != null) {
            stmtStr = condition.getStatement();
        } else {

            String metricsTable;
            String query;
            if (condition.getPrecision() == null) {
                long endTime = condition.getEndTime() == null ? System.currentTimeMillis() : condition.getEndTime();
                long startTime = condition.getStartTime() == null ? 0 : condition.getStartTime();
                Long timeRange = endTime - startTime;
                if (timeRange > 5 * DAY) {
                    metricsTable = METRICS_AGGREGATE_HOURLY_TABLE_NAME;
                    query = GET_METRIC_AGGREGATE_ONLY_SQL;
                    condition.setPrecision(Precision.HOURS);
                } else if (timeRange > 10 * HOUR) {
                    metricsTable = METRICS_AGGREGATE_MINUTE_TABLE_NAME;
                    query = GET_METRIC_AGGREGATE_ONLY_SQL;
                    condition.setPrecision(Precision.MINUTES);
                } else {
                    metricsTable = METRICS_RECORD_TABLE_NAME;
                    query = GET_METRIC_SQL;
                    condition.setPrecision(Precision.SECONDS);
                }
            } else {
                switch (condition.getPrecision()) {
                case HOURS:
                    metricsTable = METRICS_AGGREGATE_HOURLY_TABLE_NAME;
                    query = GET_METRIC_AGGREGATE_ONLY_SQL;
                    break;
                case MINUTES:
                    metricsTable = METRICS_AGGREGATE_MINUTE_TABLE_NAME;
                    query = GET_METRIC_AGGREGATE_ONLY_SQL;
                    break;
                default:
                    metricsTable = METRICS_RECORD_TABLE_NAME;
                    query = GET_METRIC_SQL;
                }
            }

            stmtStr = String.format(query, getNaiveTimeRangeHint(condition.getStartTime(), NATIVE_TIME_RANGE_DELTA),
                    metricsTable);
        }

        StringBuilder sb = new StringBuilder(stmtStr);
        sb.append(" WHERE ");
        sb.append(condition.getConditionClause());
        String orderByClause = condition.getOrderByClause(true);

        if (orderByClause != null) {
            sb.append(orderByClause);
        } else {
            sb.append(" ORDER BY METRIC_NAME, SERVER_TIME ");
        }
        if (condition.getLimit() != null) {
            sb.append(" LIMIT ").append(condition.getLimit());
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("SQL: " + sb.toString() + ", condition: " + condition);
        }
        PreparedStatement stmt = connection.prepareStatement(sb.toString());
        int pos = 1;
        if (condition.getMetricNames() != null) {
            for (; pos <= condition.getMetricNames().size(); pos++) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Setting pos: " + pos + ", value = " + condition.getMetricNames().get(pos - 1));
                }
                stmt.setString(pos, condition.getMetricNames().get(pos - 1));
            }
        }
        if (condition.getHostname() != null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Setting pos: " + pos + ", value: " + condition.getHostname());
            }
            stmt.setString(pos++, condition.getHostname());
        }
        if (condition.getAppId() != null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Setting pos: " + pos + ", value: " + condition.getAppId());
            }
            stmt.setString(pos++, condition.getAppId());
        }
        if (condition.getInstanceId() != null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Setting pos: " + pos + ", value: " + condition.getInstanceId());
            }
            stmt.setString(pos++, condition.getInstanceId());
        }
        if (condition.getStartTime() != null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Setting pos: " + pos + ", value: " + condition.getStartTime());
            }
            stmt.setLong(pos++, condition.getStartTime());
        }
        if (condition.getEndTime() != null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Setting pos: " + pos + ", value: " + condition.getEndTime());
            }
            stmt.setLong(pos, condition.getEndTime());
        }
        if (condition.getFetchSize() != null) {
            stmt.setFetchSize(condition.getFetchSize());
        }

        return stmt;
    }

    private static void validateConditionIsNotEmpty(Condition condition) {
        if (condition.isEmpty()) {
            throw new IllegalArgumentException("Condition is empty.");
        }
    }

    private static void validateRowCountLimit(Condition condition) {
        if (condition.getMetricNames() == null || condition.getMetricNames().size() == 0) {
            //aggregator can use empty metrics query
            return;
        }

        long range = condition.getEndTime() - condition.getStartTime();
        long rowsPerMetric = TimeUnit.MILLISECONDS.toHours(range) + 1;

        Precision precision = condition.getPrecision();
        // for minutes and seconds we can use the rowsPerMetric computed based on
        // minutes
        if (precision != null && precision == Precision.HOURS) {
            rowsPerMetric = TimeUnit.MILLISECONDS.toHours(range) + 1;
        }

        long totalRowsRequested = rowsPerMetric * condition.getMetricNames().size();
        if (totalRowsRequested > PhoenixHBaseAccessor.RESULTSET_LIMIT) {
            throw new IllegalArgumentException("The time range query for "
                    + "precision table exceeds row count limit, please query aggregate " + "table instead.");
        }
    }

    public static PreparedStatement prepareGetLatestMetricSqlStmt(Connection connection, Condition condition)
            throws SQLException {

        validateConditionIsNotEmpty(condition);

        if (condition.getMetricNames() == null || condition.getMetricNames().size() == 0) {
            throw new IllegalArgumentException("Point in time query without " + "metric names not supported ");
        }

        String stmtStr;
        if (condition.getStatement() != null) {
            stmtStr = condition.getStatement();
        } else {
            stmtStr = String.format(GET_METRIC_SQL, "", METRICS_RECORD_TABLE_NAME);
        }

        StringBuilder sb = new StringBuilder(stmtStr);
        sb.append(" WHERE ");
        sb.append(condition.getConditionClause());
        String orderByClause = condition.getOrderByClause(false);
        if (orderByClause != null) {
            sb.append(orderByClause);
        } else {
            sb.append(" ORDER BY METRIC_NAME DESC, HOSTNAME DESC, SERVER_TIME DESC ");
        }

        sb.append(" LIMIT ").append(condition.getMetricNames().size());

        if (LOG.isDebugEnabled()) {
            LOG.debug("SQL: " + sb.toString() + ", condition: " + condition);
        }
        PreparedStatement stmt = connection.prepareStatement(sb.toString());
        int pos = 1;
        if (condition.getMetricNames() != null) {
            //IGNORE condition limit, set one based on number of metric names
            for (; pos <= condition.getMetricNames().size(); pos++) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Setting pos: " + pos + ", value = " + condition.getMetricNames().get(pos - 1));
                }
                stmt.setString(pos, condition.getMetricNames().get(pos - 1));
            }
        }
        if (condition.getHostname() != null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Setting pos: " + pos + ", value: " + condition.getHostname());
            }
            stmt.setString(pos++, condition.getHostname());
        }
        if (condition.getAppId() != null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Setting pos: " + pos + ", value: " + condition.getAppId());
            }
            stmt.setString(pos++, condition.getAppId());
        }
        if (condition.getInstanceId() != null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Setting pos: " + pos + ", value: " + condition.getInstanceId());
            }
            stmt.setString(pos++, condition.getInstanceId());
        }

        if (condition.getFetchSize() != null) {
            stmt.setFetchSize(condition.getFetchSize());
        }

        return stmt;
    }

    public static PreparedStatement prepareGetAggregateSqlStmt(Connection connection, Condition condition)
            throws SQLException {

        validateConditionIsNotEmpty(condition);

        String metricsAggregateTable;
        String queryStmt;
        if (condition.getPrecision() == null) {
            long endTime = condition.getEndTime() == null ? System.currentTimeMillis() : condition.getEndTime();
            long startTime = condition.getStartTime() == null ? 0 : condition.getStartTime();
            Long timeRange = endTime - startTime;
            if (timeRange > 5 * DAY) {
                metricsAggregateTable = METRICS_CLUSTER_AGGREGATE_HOURLY_TABLE_NAME;
                queryStmt = GET_CLUSTER_AGGREGATE_HOURLY_SQL;
                condition.setPrecision(Precision.HOURS);
            } else {
                metricsAggregateTable = METRICS_CLUSTER_AGGREGATE_TABLE_NAME;
                queryStmt = GET_CLUSTER_AGGREGATE_SQL;
                condition.setPrecision(Precision.SECONDS);
            }
        } else {
            switch (condition.getPrecision()) {
            case HOURS:
                metricsAggregateTable = METRICS_CLUSTER_AGGREGATE_HOURLY_TABLE_NAME;
                queryStmt = GET_CLUSTER_AGGREGATE_HOURLY_SQL;
                break;
            default:
                metricsAggregateTable = METRICS_CLUSTER_AGGREGATE_TABLE_NAME;
                queryStmt = GET_CLUSTER_AGGREGATE_SQL;
            }
        }

        StringBuilder sb = new StringBuilder(queryStmt);
        sb.append(" WHERE ");
        sb.append(condition.getConditionClause());
        sb.append(" ORDER BY METRIC_NAME, SERVER_TIME");
        if (condition.getLimit() != null) {
            sb.append(" LIMIT ").append(condition.getLimit());
        }

        String query = String.format(sb.toString(),
                PhoenixTransactSQL.getNaiveTimeRangeHint(condition.getStartTime(), NATIVE_TIME_RANGE_DELTA),
                metricsAggregateTable);
        if (LOG.isDebugEnabled()) {
            LOG.debug("SQL => " + query + ", condition => " + condition);
        }
        PreparedStatement stmt = connection.prepareStatement(query);
        int pos = 1;
        if (condition.getMetricNames() != null) {
            for (; pos <= condition.getMetricNames().size(); pos++) {
                stmt.setString(pos, condition.getMetricNames().get(pos - 1));
            }
        }
        // TODO: Upper case all strings on POST
        if (condition.getAppId() != null) {
            stmt.setString(pos++, condition.getAppId());
        }
        if (condition.getInstanceId() != null) {
            stmt.setString(pos++, condition.getInstanceId());
        }
        if (condition.getStartTime() != null) {
            stmt.setLong(pos++, condition.getStartTime());
        }
        if (condition.getEndTime() != null) {
            stmt.setLong(pos, condition.getEndTime());
        }

        return stmt;
    }

    public static PreparedStatement prepareGetLatestAggregateMetricSqlStmt(Connection connection,
            Condition condition) throws SQLException {

        validateConditionIsNotEmpty(condition);

        String stmtStr;
        if (condition.getStatement() != null) {
            stmtStr = condition.getStatement();
        } else {
            stmtStr = String.format(GET_CLUSTER_AGGREGATE_SQL, "", METRICS_CLUSTER_AGGREGATE_TABLE_NAME);
        }

        StringBuilder sb = new StringBuilder(stmtStr);
        sb.append(" WHERE ");
        sb.append(condition.getConditionClause());
        String orderByClause = condition.getOrderByClause(false);
        if (orderByClause != null) {
            sb.append(orderByClause);
        } else {
            sb.append(" ORDER BY METRIC_NAME DESC, SERVER_TIME DESC  ");
        }

        sb.append(" LIMIT ").append(condition.getMetricNames().size());

        String query = sb.toString();
        if (LOG.isDebugEnabled()) {
            LOG.debug("SQL: " + query + ", condition: " + condition);
        }

        PreparedStatement stmt = connection.prepareStatement(query);
        int pos = 1;
        if (condition.getMetricNames() != null) {
            for (; pos <= condition.getMetricNames().size(); pos++) {
                stmt.setString(pos, condition.getMetricNames().get(pos - 1));
            }
        }
        if (condition.getAppId() != null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Setting pos: " + pos + ", value: " + condition.getAppId());
            }
            stmt.setString(pos++, condition.getAppId());
        }
        if (condition.getInstanceId() != null) {
            stmt.setString(pos++, condition.getInstanceId());
        }

        return stmt;
    }

    static interface Condition {

        boolean isEmpty();

        List<String> getMetricNames();

        boolean isPointInTime();

        boolean isGrouped();

        void setStatement(String statement);

        String getHostname();

        Precision getPrecision();

        void setPrecision(Precision precision);

        String getAppId();

        String getInstanceId();

        StringBuilder getConditionClause();

        String getOrderByClause(boolean asc);

        String getStatement();

        Long getStartTime();

        Long getEndTime();

        Integer getLimit();

        Integer getFetchSize();

        void setFetchSize(Integer fetchSize);

        void addOrderByColumn(String column);

        void setNoLimit();
    }

    static class DefaultCondition implements Condition {
        List<String> metricNames;
        String hostname;
        String appId;
        String instanceId;
        Long startTime;
        Long endTime;
        Precision precision;
        Integer limit;
        boolean grouped;
        boolean noLimit = false;
        Integer fetchSize;
        String statement;
        Set<String> orderByColumns = new LinkedHashSet<String>();

        DefaultCondition(List<String> metricNames, String hostname, String appId, String instanceId, Long startTime,
                Long endTime, Precision precision, Integer limit, boolean grouped) {
            this.metricNames = metricNames;
            this.hostname = hostname;
            this.appId = appId;
            this.instanceId = instanceId;
            this.startTime = startTime;
            this.endTime = endTime;
            this.precision = precision;
            this.limit = limit;
            this.grouped = grouped;
        }

        public String getStatement() {
            return statement;
        }

        public void setStatement(String statement) {
            this.statement = statement;
        }

        public List<String> getMetricNames() {
            return metricNames == null || metricNames.isEmpty() ? null : metricNames;
        }

        public StringBuilder getConditionClause() {
            StringBuilder sb = new StringBuilder();
            boolean appendConjunction = false;
            StringBuilder metricsLike = new StringBuilder();
            StringBuilder metricsIn = new StringBuilder();

            if (getMetricNames() != null) {
                for (String name : getMetricNames()) {
                    if (name.contains("%")) {
                        if (metricsLike.length() > 1) {
                            metricsLike.append(" OR ");
                        }
                        metricsLike.append("METRIC_NAME LIKE ?");
                    } else {
                        if (metricsIn.length() > 0) {
                            metricsIn.append(", ");
                        }
                        metricsIn.append("?");
                    }
                }

                if (metricsIn.length() > 0) {
                    sb.append("(METRIC_NAME IN (");
                    sb.append(metricsIn);
                    sb.append(")");
                    appendConjunction = true;
                }

                if (metricsLike.length() > 0) {
                    if (appendConjunction) {
                        sb.append(" OR ");
                    } else {
                        sb.append("(");
                    }
                    sb.append(metricsLike);
                    appendConjunction = true;
                }

                if (appendConjunction) {
                    sb.append(")");
                }
            }

            appendConjunction = append(sb, appendConjunction, getHostname(), " HOSTNAME = ?");
            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;
        }

        protected static boolean append(StringBuilder sb, boolean appendConjunction, Object value, String str) {
            if (value != null) {
                if (appendConjunction) {
                    sb.append(" AND");
                }

                sb.append(str);
                appendConjunction = true;
            }
            return appendConjunction;
        }

        public String getHostname() {
            return hostname == null || hostname.isEmpty() ? null : hostname;
        }

        public Precision getPrecision() {
            return precision;
        }

        public void setPrecision(Precision precision) {
            this.precision = precision;
        }

        public String getAppId() {
            if (appId != null && !appId.isEmpty()) {
                if (!(appId.equals("HOST") || appId.equals("FLUME_HANDLER"))) {
                    return appId.toLowerCase();
                } else {
                    return appId;
                }
            }
            return null;
        }

        public String getInstanceId() {
            return instanceId == null || instanceId.isEmpty() ? null : instanceId;
        }

        /**
         * Convert to millis.
         */
        public Long getStartTime() {
            if (startTime == null) {
                return null;
            } else if (startTime < 9999999999l) {
                return startTime * 1000;
            } else {
                return startTime;
            }
        }

        public Long getEndTime() {
            if (endTime == null) {
                return null;
            }
            if (endTime < 9999999999l) {
                return endTime * 1000;
            } else {
                return endTime;
            }
        }

        public void setNoLimit() {
            this.noLimit = true;
        }

        public Integer getLimit() {
            if (noLimit) {
                return null;
            }
            return limit == null ? PhoenixHBaseAccessor.RESULTSET_LIMIT : limit;
        }

        public boolean isGrouped() {
            return grouped;
        }

        public boolean isPointInTime() {
            return getStartTime() == null && getEndTime() == null;
        }

        public boolean isEmpty() {
            return (metricNames == null || metricNames.isEmpty()) && (hostname == null || hostname.isEmpty())
                    && (appId == null || appId.isEmpty()) && (instanceId == null || instanceId.isEmpty())
                    && startTime == null && endTime == null;
        }

        public Integer getFetchSize() {
            return fetchSize;
        }

        public void setFetchSize(Integer fetchSize) {
            this.fetchSize = fetchSize;
        }

        public void addOrderByColumn(String column) {
            orderByColumns.add(column);
        }

        public String getOrderByClause(boolean asc) {
            String orderByStr = " ORDER BY ";
            if (!orderByColumns.isEmpty()) {
                StringBuilder sb = new StringBuilder(orderByStr);
                for (String orderByColumn : orderByColumns) {
                    if (sb.length() != orderByStr.length()) {
                        sb.append(", ");
                    }
                    sb.append(orderByColumn);
                    if (!asc) {
                        sb.append(" DESC");
                    }
                }
                sb.append(" ");
                return sb.toString();
            }
            return null;
        }

        @Override
        public String toString() {
            return "Condition{" + "metricNames=" + metricNames + ", hostname='" + hostname + '\'' + ", appId='"
                    + appId + '\'' + ", instanceId='" + instanceId + '\'' + ", startTime=" + startTime
                    + ", endTime=" + endTime + ", limit=" + limit + ", grouped=" + grouped + ", orderBy="
                    + orderByColumns + ", noLimit=" + noLimit + '}';
        }
    }

    static class SplitByMetricNamesCondition implements Condition {
        private final Condition adaptee;
        private String currentMetric;

        SplitByMetricNamesCondition(Condition condition) {
            this.adaptee = condition;
        }

        @Override
        public boolean isEmpty() {
            return adaptee.isEmpty();
        }

        @Override
        public List<String> getMetricNames() {
            return Collections.singletonList(currentMetric);
        }

        @Override
        public boolean isPointInTime() {
            return adaptee.isPointInTime();
        }

        @Override
        public boolean isGrouped() {
            return adaptee.isGrouped();
        }

        @Override
        public void setStatement(String statement) {
            adaptee.setStatement(statement);
        }

        @Override
        public String getHostname() {
            return adaptee.getHostname();
        }

        @Override
        public Precision getPrecision() {
            return adaptee.getPrecision();
        }

        @Override
        public void setPrecision(Precision precision) {
            adaptee.setPrecision(precision);
        }

        @Override
        public String getAppId() {
            return adaptee.getAppId();
        }

        @Override
        public String getInstanceId() {
            return adaptee.getInstanceId();
        }

        @Override
        public StringBuilder getConditionClause() {
            StringBuilder sb = new StringBuilder();
            boolean appendConjunction = false;

            if (getMetricNames() != null) {
                for (String name : getMetricNames()) {
                    if (sb.length() > 1) {
                        sb.append(" OR ");
                    }
                    sb.append("METRIC_NAME = ?");
                }

                appendConjunction = true;
            }

            appendConjunction = DefaultCondition.append(sb, appendConjunction, getHostname(), " HOSTNAME = ?");
            appendConjunction = DefaultCondition.append(sb, appendConjunction, getAppId(), " APP_ID = ?");
            appendConjunction = DefaultCondition.append(sb, appendConjunction, getInstanceId(), " INSTANCE_ID = ?");
            appendConjunction = DefaultCondition.append(sb, appendConjunction, getStartTime(), " SERVER_TIME >= ?");
            DefaultCondition.append(sb, appendConjunction, getEndTime(), " SERVER_TIME < ?");

            return sb;
        }

        @Override
        public String getOrderByClause(boolean asc) {
            return adaptee.getOrderByClause(asc);
        }

        @Override
        public String getStatement() {
            return adaptee.getStatement();
        }

        @Override
        public Long getStartTime() {
            return adaptee.getStartTime();
        }

        @Override
        public Long getEndTime() {
            return adaptee.getEndTime();
        }

        @Override
        public Integer getLimit() {
            return adaptee.getLimit();
        }

        @Override
        public Integer getFetchSize() {
            return adaptee.getFetchSize();
        }

        @Override
        public void setFetchSize(Integer fetchSize) {
            adaptee.setFetchSize(fetchSize);
        }

        @Override
        public void addOrderByColumn(String column) {
            adaptee.addOrderByColumn(column);
        }

        @Override
        public void setNoLimit() {
            adaptee.setNoLimit();
        }

        public List<String> getOriginalMetricNames() {
            return adaptee.getMetricNames();
        }

        public void setCurrentMetric(String currentMetric) {
            this.currentMetric = currentMetric;
        }
    }
}