Package org.apache.hadoop.hbase.regionserver.metrics

Source Code of org.apache.hadoop.hbase.regionserver.metrics.SchemaMetrics$SchemaAware

/*
* Copyright The Apache Software Foundation
*
* 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.hbase.regionserver.metrics;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.mutable.MutableDouble;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.io.hfile.BlockType.BlockCategory;
import org.apache.hadoop.hbase.regionserver.HRegion;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;

/**
* A collection of metric names in a given column family or a (table, column
* family) combination. The following "dimensions" are supported:
* <ul>
* <li>Table name (optional; enabled based on configuration)</li>
* <li>Per-column family vs. aggregated. The aggregated mode is only supported
* when table name is not included.</li>
* <li>Block category (data, index, bloom filter, etc.)</li>
* <li>Whether the request is part of a compaction</li>
* <li>Metric type (read time, block read count, cache hits/misses, etc.)</li>
* </ul>
* <p>
* An instance of this class does not store any metric values. It just allows
* to determine the correct metric name for each combination of the above
* dimensions.
* <p>
* <table>
* <tr>
* <th rowspan="2">Metric key</th>
* <th colspan="2">Per-table metrics conf setting</th>
* <th rowspan="2">Description</th>
* </tr>
* <tr>
* <th>On</th>
* <th>Off</th>
* </th>
* <tr>
*   <td> tbl.T.cf.CF.M </td> <td> Include </td> <td> Skip    </td>
*   <td> A specific column family of a specific table        </td>
* </tr>
* <tr>
*   <td> tbl.T.M       </td> <td> Skip    </td> <td> Skip    </td>
*   <td> All column families in the given table              </td>
* </tr>
* <tr>
*   <td> cf.CF.M       </td> <td> Skip    </td> <td> Include </td>
*   <td> A specific column family in all tables              </td>
* </tr>
* <tr>
*   <td> M             </td> <td> Include </td> <td> Include </td>
*   <td> All column families in all tables                   </td>
* </tr>
* </table>
*/
public class SchemaMetrics {

  public interface SchemaAware {
    public String getTableName();
    public String getColumnFamilyName();
    public SchemaMetrics getSchemaMetrics();
  }

  private static final Log LOG = LogFactory.getLog(SchemaMetrics.class);

  public static enum BlockMetricType {
    // Metric configuration: compactionAware, timeVarying
    READ_TIME("Read",                   true, true),
    READ_COUNT("BlockReadCnt",          true, false),
    CACHE_HIT("BlockReadCacheHitCnt",   true, false),
    CACHE_MISS("BlockReadCacheMissCnt", true, false),

    CACHE_SIZE("blockCacheSize",        false, false),
    CACHED("blockCacheNumCached",       false, false),
    EVICTED("blockCacheNumEvicted",     false, false);

    private final String metricStr;
    private final boolean compactionAware;
    private final boolean timeVarying;

    BlockMetricType(String metricStr, boolean compactionAware,
          boolean timeVarying) {
      this.metricStr = metricStr;
      this.compactionAware = compactionAware;
      this.timeVarying = timeVarying;
    }

    @Override
    public String toString() {
      return metricStr;
    }

    private static final String BLOCK_METRIC_TYPE_RE;
    static {
      StringBuilder sb = new StringBuilder();
      for (BlockMetricType bmt : values()) {
        if (sb.length() > 0)
          sb.append("|");
        sb.append(bmt);
      }
      BLOCK_METRIC_TYPE_RE = sb.toString();
    }
  };

  public static enum StoreMetricType {
    STORE_FILE_COUNT("storeFileCount"),
    STORE_FILE_INDEX_SIZE("storeFileIndexSizeMB"),
    STORE_FILE_SIZE_MB("storeFileSizeMB"),
    STATIC_BLOOM_SIZE_KB("staticBloomSizeKB"),
    MEMSTORE_SIZE_MB("memstoreSizeMB"),
    STATIC_INDEX_SIZE_KB("staticIndexSizeKB"),
    FLUSH_SIZE("flushSize");

    private final String metricStr;

    StoreMetricType(String metricStr) {
      this.metricStr = metricStr;
    }

    @Override
    public String toString() {
      return metricStr;
    }
  };

  // Constants
  /**
   * A string used when column family or table name is unknown, and in some
   * unit tests. This should not normally show up in metric names but if it
   * does it is better than creating a silent discrepancy in total vs.
   * per-CF/table metrics.
   */
  public static final String UNKNOWN = "__unknown";

  public static final String TABLE_PREFIX = "tbl.";
  public static final String CF_PREFIX = "cf.";
  public static final String BLOCK_TYPE_PREFIX = "bt.";
  public static final String REGION_PREFIX = "region.";

  public static final String CF_UNKNOWN_PREFIX = CF_PREFIX + UNKNOWN + ".";
  public static final String CF_BAD_FAMILY_PREFIX = CF_PREFIX + "__badfamily.";

  /** Use for readability when obtaining non-compaction counters */
  public static final boolean NO_COMPACTION = false;

  /**
   * A special schema metric value that means "all tables aggregated" or
   * "all column families aggregated" when used as a table name or a column
   * family name.
   */
  public static final String TOTAL_KEY = "";

  /**
   * Special handling for meta-block-specific metrics for
   * backwards-compatibility.
   */
  private static final String META_BLOCK_CATEGORY_STR = "Meta";

  private static final int NUM_BLOCK_CATEGORIES =
      BlockCategory.values().length;

  private static final int NUM_METRIC_TYPES =
      BlockMetricType.values().length;

  static final boolean[] BOOL_VALUES = new boolean[] { false, true };

  private static final int NUM_BLOCK_METRICS =
      NUM_BLOCK_CATEGORIES *  // blockCategory
      BOOL_VALUES.length *    // isCompaction
      NUM_METRIC_TYPES;       // metricType

  private static final int NUM_STORE_METRIC_TYPES =
      StoreMetricType.values().length;

  /** Conf key controlling whether we include table name in metric names */
  private static final String SHOW_TABLE_NAME_CONF_KEY =
      "hbase.metrics.showTableName";

  /** We use this when too many column families are involved in a request. */
  private static final String MORE_CFS_OMITTED_STR = "__more";

  /**
   * Maximum length of a metric name prefix. Used when constructing metric
   * names from a set of column families participating in a request.
   */
  private static final int MAX_METRIC_PREFIX_LENGTH =
      256 - MORE_CFS_OMITTED_STR.length();

  // Global variables
  /**
   * Maps a string key consisting of table name and column family name, with
   * table name optionally replaced with {@link #TOTAL_KEY} if per-table
   * metrics are disabled, to an instance of this class.
   */
  private static final ConcurrentHashMap<String, SchemaMetrics>
      tableAndFamilyToMetrics = new ConcurrentHashMap<String, SchemaMetrics>();

  /** Metrics for all tables and column families. */
  // This has to be initialized after cfToMetrics.
  public static final SchemaMetrics ALL_SCHEMA_METRICS =
    getInstance(TOTAL_KEY, TOTAL_KEY);

  /**
   * Whether to include table name in metric names. If this is null, it has not
   * been initialized. This is a global instance, but we also have a copy of it
   * per a {@link SchemaMetrics} object to avoid synchronization overhead.
   */
  private static volatile Boolean useTableNameGlobally;

  /** Whether we logged a message about configuration inconsistency */
  private static volatile boolean loggedConfInconsistency;

  // Instance variables
  private final String[] blockMetricNames = new String[NUM_BLOCK_METRICS];
  private final boolean[] blockMetricTimeVarying =
      new boolean[NUM_BLOCK_METRICS];

  private final String[] bloomMetricNames = new String[2];
  private final String[] storeMetricNames = new String[NUM_STORE_METRIC_TYPES];
  private final String[] storeMetricNamesMax = new String[NUM_STORE_METRIC_TYPES];

  private SchemaMetrics(final String tableName, final String cfName) {
    String metricPrefix = SchemaMetrics.generateSchemaMetricsPrefix(
        tableName, cfName);

    for (BlockCategory blockCategory : BlockCategory.values()) {
      for (boolean isCompaction : BOOL_VALUES) {
        for (BlockMetricType metricType : BlockMetricType.values()) {
          if (!metricType.compactionAware && isCompaction) {
            continue;
          }

          StringBuilder sb = new StringBuilder(metricPrefix);
          if (blockCategory != BlockCategory.ALL_CATEGORIES
              && blockCategory != BlockCategory.META) {
            String categoryStr = blockCategory.toString();
            categoryStr = categoryStr.charAt(0)
                + categoryStr.substring(1).toLowerCase();
            sb.append(BLOCK_TYPE_PREFIX + categoryStr + ".");
          }

          if (metricType.compactionAware) {
            sb.append(isCompaction ? "compaction" : "fs");
          }

          // A special-case for meta blocks for backwards-compatibility.
          if (blockCategory == BlockCategory.META) {
            sb.append(META_BLOCK_CATEGORY_STR);
          }

          sb.append(metricType);

          int i = getBlockMetricIndex(blockCategory, isCompaction, metricType);
          blockMetricNames[i] = sb.toString().intern();
          blockMetricTimeVarying[i] = metricType.timeVarying;
        }
      }
    }

    for (boolean isInBloom : BOOL_VALUES) {
      bloomMetricNames[isInBloom ? 1 : 0] = metricPrefix
          + (isInBloom ? "keyMaybeInBloomCnt" : "keyNotInBloomCnt");
    }

    for (StoreMetricType storeMetric : StoreMetricType.values()) {
      String coreName = metricPrefix + storeMetric.toString();
      storeMetricNames[storeMetric.ordinal()] = coreName;
      storeMetricNamesMax[storeMetric.ordinal()] = coreName + ".max";
    }
  }

  /**
   * Returns a {@link SchemaMetrics} object for the given table and column
   * family, instantiating it if necessary.
   *
   * @param tableName table name (null is interpreted as "unknown"). This is
   *          ignored
   * @param cfName column family name (null is interpreted as "unknown")
   */
  public static SchemaMetrics getInstance(String tableName, String cfName) {
    if (tableName == null) {
      tableName = UNKNOWN;
    }

    if (cfName == null) {
      cfName = UNKNOWN;
    }

    tableName = getEffectiveTableName(tableName);

    final String instanceKey = tableName + "\t" + cfName;
    SchemaMetrics schemaMetrics = tableAndFamilyToMetrics.get(instanceKey);
    if (schemaMetrics != null) {
      return schemaMetrics;
    }

    schemaMetrics = new SchemaMetrics(tableName, cfName);
    SchemaMetrics existingMetrics =
        tableAndFamilyToMetrics.putIfAbsent(instanceKey, schemaMetrics);
    return existingMetrics != null ? existingMetrics : schemaMetrics;
  }

  private static final int getBlockMetricIndex(BlockCategory blockCategory,
      boolean isCompaction, BlockMetricType metricType) {
    int i = 0;
    i = i * NUM_BLOCK_CATEGORIES + blockCategory.ordinal();
    i = i * BOOL_VALUES.length + (isCompaction ? 1 : 0);
    i = i * NUM_METRIC_TYPES + metricType.ordinal();
    return i;
  }

  public String getBlockMetricName(BlockCategory blockCategory,
      boolean isCompaction, BlockMetricType metricType) {
    if (isCompaction && !metricType.compactionAware) {
      throw new IllegalArgumentException("isCompaction cannot be true for "
          + metricType);
    }
    return blockMetricNames[getBlockMetricIndex(blockCategory, isCompaction,
        metricType)];
  }

  public String getBloomMetricName(boolean isInBloom) {
    return bloomMetricNames[isInBloom ? 1 : 0];
  }

  /**
   * Increments the given metric, both per-CF and aggregate, for both the given
   * category and all categories in aggregate (four counters total).
   */
  private void incrNumericMetric(BlockCategory blockCategory,
      boolean isCompaction, BlockMetricType metricType) {
    if (blockCategory == null) {
      blockCategory = BlockCategory.UNKNOWN;  // So that we see this in stats.
    }
    RegionMetricsStorage.incrNumericMetric(getBlockMetricName(blockCategory,
        isCompaction, metricType), 1);

    if (blockCategory != BlockCategory.ALL_CATEGORIES) {
      incrNumericMetric(BlockCategory.ALL_CATEGORIES, isCompaction,
          metricType);
    }
  }

  private void addToReadTime(BlockCategory blockCategory,
      boolean isCompaction, long timeMs) {
    RegionMetricsStorage.incrTimeVaryingMetric(getBlockMetricName(blockCategory,
        isCompaction, BlockMetricType.READ_TIME), timeMs);

    // Also update the read time aggregated across all block categories
    if (blockCategory != BlockCategory.ALL_CATEGORIES) {
      addToReadTime(BlockCategory.ALL_CATEGORIES, isCompaction, timeMs);
    }
  }

  /**
   * Used to accumulate store metrics across multiple regions in a region
   * server.  These metrics are not "persistent", i.e. we keep overriding them
   * on every update instead of incrementing, so we need to accumulate them in
   * a temporary map before pushing them to the global metric collection.
   * @param tmpMap a temporary map for accumulating store metrics
   * @param storeMetricType the store metric type to increment
   * @param val the value to add to the metric
   */
  public void accumulateStoreMetric(final Map<String, MutableDouble> tmpMap,
      StoreMetricType storeMetricType, double val) {
    final String key = getStoreMetricName(storeMetricType);
    if (tmpMap.get(key) == null) {
      tmpMap.put(key, new MutableDouble(val));
    } else {
      tmpMap.get(key).add(val);
    }

    if (this == ALL_SCHEMA_METRICS) {
      // also compute the max value across all Stores on this server
      final String maxKey = getStoreMetricNameMax(storeMetricType);
      MutableDouble cur = tmpMap.get(maxKey);
      if (cur == null) {
        tmpMap.put(maxKey, new MutableDouble(val));
      } else if (cur.doubleValue() < val) {
        cur.setValue(val);
      }
    } else {
      ALL_SCHEMA_METRICS.accumulateStoreMetric(tmpMap, storeMetricType, val);
    }
  }

  public String getStoreMetricName(StoreMetricType storeMetricType) {
    return storeMetricNames[storeMetricType.ordinal()];
  }

  public String getStoreMetricNameMax(StoreMetricType storeMetricType) {
    return storeMetricNamesMax[storeMetricType.ordinal()];
  }

  /**
   * Update a metric that does not get reset on every poll.
   * @param storeMetricType the store metric to update
   * @param value the value to update the metric to
   */
  public void updatePersistentStoreMetric(StoreMetricType storeMetricType,
      long value) {
    RegionMetricsStorage.incrNumericPersistentMetric(
        storeMetricNames[storeMetricType.ordinal()], value);
  }

  /**
   * Updates the number of hits and the total number of block reads on a block
   * cache hit.
   */
  public void updateOnCacheHit(BlockCategory blockCategory,
      boolean isCompaction) {
    blockCategory.expectSpecific();
    incrNumericMetric(blockCategory, isCompaction, BlockMetricType.CACHE_HIT);
    incrNumericMetric(blockCategory, isCompaction, BlockMetricType.READ_COUNT);
    if (this != ALL_SCHEMA_METRICS) {
      ALL_SCHEMA_METRICS.updateOnCacheHit(blockCategory, isCompaction);
    }
  }

  /**
   * Updates read time, the number of misses, and the total number of block
   * reads on a block cache miss.
   */
  public void updateOnCacheMiss(BlockCategory blockCategory,
      boolean isCompaction, long timeMs) {
    blockCategory.expectSpecific();
    addToReadTime(blockCategory, isCompaction, timeMs);
    incrNumericMetric(blockCategory, isCompaction, BlockMetricType.CACHE_MISS);
    incrNumericMetric(blockCategory, isCompaction, BlockMetricType.READ_COUNT);
    if (this != ALL_SCHEMA_METRICS) {
      ALL_SCHEMA_METRICS.updateOnCacheMiss(blockCategory, isCompaction,
          timeMs);
    }
  }

  /**
   * Adds the given delta to the cache size for the given block category and
   * the aggregate metric for all block categories. Updates both the per-CF
   * counter and the counter for all CFs (four metrics total). The cache size
   * metric is "persistent", i.e. it does not get reset when metrics are
   * collected.
   */
  public void addToCacheSize(BlockCategory category, long cacheSizeDelta) {
    if (category == null) {
      category = BlockCategory.ALL_CATEGORIES;
    }
    RegionMetricsStorage.incrNumericPersistentMetric(getBlockMetricName(category, false,
        BlockMetricType.CACHE_SIZE), cacheSizeDelta);

    if (category != BlockCategory.ALL_CATEGORIES) {
      addToCacheSize(BlockCategory.ALL_CATEGORIES, cacheSizeDelta);
    }
  }

  public void updateOnCachePutOrEvict(BlockCategory blockCategory,
      long cacheSizeDelta, boolean isEviction) {
    addToCacheSize(blockCategory, cacheSizeDelta);
    incrNumericMetric(blockCategory, false,
        isEviction ? BlockMetricType.EVICTED : BlockMetricType.CACHED);
    if (this != ALL_SCHEMA_METRICS) {
      ALL_SCHEMA_METRICS.updateOnCachePutOrEvict(blockCategory, cacheSizeDelta,
          isEviction);
    }
  }

  /**
   * Increments both the per-CF and the aggregate counter of bloom
   * positives/negatives as specified by the argument.
   */
  public void updateBloomMetrics(boolean isInBloom) {
    RegionMetricsStorage.incrNumericMetric(getBloomMetricName(isInBloom), 1);
    if (this != ALL_SCHEMA_METRICS) {
      ALL_SCHEMA_METRICS.updateBloomMetrics(isInBloom);
    }
  }

  /**
   * Sets the flag whether to use table name in metric names according to the
   * given configuration. This must be called at least once before
   * instantiating HFile readers/writers.
   */
  public static void configureGlobally(Configuration conf) {
    final boolean useTableNameNew =
        conf.getBoolean(SHOW_TABLE_NAME_CONF_KEY, false);
    setUseTableName(useTableNameNew);
  }

  /**
   * Determine the table name to be included in metric keys. If the global
   * configuration says that we should not use table names in metrics,
   * we always return {@link #TOTAL_KEY} even if nontrivial table name is
   * provided.
   *
   * @param tableName a table name or {@link #TOTAL_KEY} when aggregating
   * across all tables
   * @return the table name to use in metric keys
   */
  private static String getEffectiveTableName(String tableName) {
    if (!tableName.equals(TOTAL_KEY)) {
      // We are provided with a non-trivial table name (including "unknown").
      // We need to know whether table name should be included into metrics.
      if (useTableNameGlobally == null) {
        throw new IllegalStateException("The value of the "
            + SHOW_TABLE_NAME_CONF_KEY + " conf option has not been specified "
            + "in SchemaMetrics");
      }
      final boolean useTableName = useTableNameGlobally;
      if (!useTableName) {
        // Don't include table name in metric keys.
        tableName = TOTAL_KEY;
      }
    }
    return tableName;
  }

  /**
   * Method to transform a combination of a table name and a column family name
   * into a metric key prefix. Tables/column family names equal to
   * {@link #TOTAL_KEY} are omitted from the prefix.
   *
   * @param tableName the table name or {@link #TOTAL_KEY} for all tables
   * @param cfName the column family name or {@link #TOTAL_KEY} for all CFs
   * @return the metric name prefix, ending with a dot.
   */
  public static String generateSchemaMetricsPrefix(String tableName,
      final String cfName) {
    tableName = getEffectiveTableName(tableName);
    String schemaMetricPrefix =
        tableName.equals(TOTAL_KEY) ? "" : TABLE_PREFIX + tableName + ".";
    schemaMetricPrefix +=
        cfName.equals(TOTAL_KEY) ? "" : CF_PREFIX + cfName + ".";
    return schemaMetricPrefix;
  }

  public static String generateSchemaMetricsPrefix(byte[] tableName,
      byte[] cfName) {
    return generateSchemaMetricsPrefix(Bytes.toString(tableName),
        Bytes.toString(cfName));
  }

  /**
   * Method to transform a set of column families in byte[] format with table
   * name into a metric key prefix.
   *
   * @param tableName the table name or {@link #TOTAL_KEY} for all tables
   * @param families the ordered set of column families
   * @return the metric name prefix, ending with a dot, or an empty string in
   *         case of invalid arguments. This is OK since we always expect
   *         some CFs to be included.
   */
  public static String generateSchemaMetricsPrefix(String tableName,
      Set<byte[]> families) {
    if (families == null || families.isEmpty() ||
        tableName == null || tableName.isEmpty()) {
      return "";
    }

    if (families.size() == 1) {
      return generateSchemaMetricsPrefix(tableName,
          Bytes.toString(families.iterator().next()));
    }

    tableName = getEffectiveTableName(tableName);
    List<byte[]> sortedFamilies = new ArrayList<byte[]>(families);
    Collections.sort(sortedFamilies, Bytes.BYTES_COMPARATOR);

    StringBuilder sb = new StringBuilder();

    int numCFsLeft = families.size();
    for (byte[] family : sortedFamilies) {
      if (sb.length() > MAX_METRIC_PREFIX_LENGTH) {
        sb.append(MORE_CFS_OMITTED_STR);
        break;
      }
      --numCFsLeft;
      sb.append(Bytes.toString(family));
      if (numCFsLeft > 0) {
        sb.append("~");
      }
    }

    return SchemaMetrics.generateSchemaMetricsPrefix(tableName, sb.toString());
  }

  /**
   * Get the prefix for metrics generated about a single region.
   *
   * @param tableName
   *          the table name or {@link #TOTAL_KEY} for all tables
   * @param regionName
   *          regionName
   * @return the prefix for this table/region combination.
   */
  static String generateRegionMetricsPrefix(String tableName, String regionName) {
    tableName = getEffectiveTableName(tableName);
    String schemaMetricPrefix = tableName.equals(TOTAL_KEY) ? "" : TABLE_PREFIX + tableName + ".";
    schemaMetricPrefix += regionName.equals(TOTAL_KEY) ? "" : REGION_PREFIX + regionName + ".";

    return schemaMetricPrefix;
  }
 
  /**
   * Sets the flag of whether to use table name in metric names. This flag
   * is specified in configuration and is not expected to change at runtime,
   * so we log an error message when it does change.
   */
  private static void setUseTableName(final boolean useTableNameNew) {
    if (useTableNameGlobally == null) {
      // This configuration option has not yet been set.
      useTableNameGlobally = useTableNameNew;
    } else if (useTableNameGlobally != useTableNameNew
        && !loggedConfInconsistency) {
      // The configuration is inconsistent and we have not reported it
      // previously. Once we report it, just keep ignoring the new setting.
      LOG.error("Inconsistent configuration. Previous configuration "
          + "for using table name in metrics: " + useTableNameGlobally + ", "
          + "new configuration: " + useTableNameNew);
      loggedConfInconsistency = true;
    }
  }

  // Methods used in testing

  private static final String regexEscape(String s) {
    return s.replace(".", "\\.");
  }

  /**
   * Assume that table names used in tests don't contain dots, except for the
   * META table.
   */
  private static final String WORD_AND_DOT_RE_STR = "([^.]+|" +
      regexEscape(Bytes.toString(HConstants.META_TABLE_NAME)) +
      ")\\.";

  /** "tbl.<table_name>." */
  private static final String TABLE_NAME_RE_STR =
      "\\b" + regexEscape(TABLE_PREFIX) + WORD_AND_DOT_RE_STR;

  /** "cf.<cf_name>." */
  private static final String CF_NAME_RE_STR =
      "\\b" + regexEscape(CF_PREFIX) + WORD_AND_DOT_RE_STR;
  private static final Pattern CF_NAME_RE = Pattern.compile(CF_NAME_RE_STR);

  /** "tbl.<table_name>.cf.<cf_name>." */
  private static final Pattern TABLE_AND_CF_NAME_RE = Pattern.compile(
      TABLE_NAME_RE_STR + CF_NAME_RE_STR);

  private static final Pattern BLOCK_CATEGORY_RE = Pattern.compile(
      "\\b" + regexEscape(BLOCK_TYPE_PREFIX) + "[^.]+\\." +
      // Also remove the special-case block type marker for meta blocks
      "|" + META_BLOCK_CATEGORY_STR + "(?=" +
      BlockMetricType.BLOCK_METRIC_TYPE_RE + ")");

  /**
   * A suffix for the "number of operations" part of "time-varying metrics". We
   * only use this for metric verification in unit testing. Time-varying
   * metrics are handled by a different code path in production.
   */
  private static String NUM_OPS_SUFFIX = "numops";

  /**
   * A custom suffix that we use for verifying the second component of
   * a "time-varying metric".
   */
  private static String TOTAL_SUFFIX = "_total";
  private static final Pattern TIME_VARYING_SUFFIX_RE = Pattern.compile(
      "(" + NUM_OPS_SUFFIX + "|" + TOTAL_SUFFIX + ")$");

  void printMetricNames() {
    for (BlockCategory blockCategory : BlockCategory.values()) {
      for (boolean isCompaction : BOOL_VALUES) {
        for (BlockMetricType metricType : BlockMetricType.values()) {
          int i = getBlockMetricIndex(blockCategory, isCompaction, metricType);
          LOG.debug("blockCategory=" + blockCategory + ", "
              + "metricType=" + metricType + ", isCompaction=" + isCompaction
              + ", metricName=" + blockMetricNames[i]);
        }
      }
    }
  }

  private Collection<String> getAllMetricNames() {
    List<String> allMetricNames = new ArrayList<String>();
    for (int i = 0; i < blockMetricNames.length; ++i) {
      final String blockMetricName = blockMetricNames[i];
      final boolean timeVarying = blockMetricTimeVarying[i];
      if (blockMetricName != null) {
        if (timeVarying) {
          allMetricNames.add(blockMetricName + NUM_OPS_SUFFIX);
          allMetricNames.add(blockMetricName + TOTAL_SUFFIX);
        } else {
          allMetricNames.add(blockMetricName);
        }
      }
    }
    allMetricNames.addAll(Arrays.asList(bloomMetricNames));
    return allMetricNames;
  }

  private static final boolean isTimeVaryingKey(String metricKey) {
    return metricKey.endsWith(NUM_OPS_SUFFIX)
        || metricKey.endsWith(TOTAL_SUFFIX);
  }

  private static final String stripTimeVaryingSuffix(String metricKey) {
    return TIME_VARYING_SUFFIX_RE.matcher(metricKey).replaceAll("");
  }

  public static Map<String, Long> getMetricsSnapshot() {
    Map<String, Long> metricsSnapshot = new TreeMap<String, Long>();
    for (SchemaMetrics cfm : tableAndFamilyToMetrics.values()) {
      for (String metricName : cfm.getAllMetricNames()) {
        long metricValue;
        if (isTimeVaryingKey(metricName)) {
          Pair<Long, Integer> totalAndCount =
              RegionMetricsStorage.getTimeVaryingMetric(stripTimeVaryingSuffix(metricName));
          metricValue = metricName.endsWith(TOTAL_SUFFIX) ?
              totalAndCount.getFirst() : totalAndCount.getSecond();
        } else {
          metricValue = RegionMetricsStorage.getNumericMetric(metricName);
        }

        metricsSnapshot.put(metricName, metricValue);
      }
    }
    return metricsSnapshot;
  }

  public static long getLong(Map<String, Long> m, String k) {
    Long l = m.get(k);
    return l != null ? l : 0;
  }

  private static void putLong(Map<String, Long> m, String k, long v) {
    if (v != 0) {
      m.put(k, v);
    } else {
      m.remove(k);
    }
  }

  /**
   * @return the difference between two sets of metrics (second minus first).
   *         Only includes keys that have nonzero difference.
   */
  public static Map<String, Long> diffMetrics(Map<String, Long> a,
      Map<String, Long> b) {
    Set<String> allKeys = new TreeSet<String>(a.keySet());
    allKeys.addAll(b.keySet());
    Map<String, Long> diff = new TreeMap<String, Long>();
    for (String k : allKeys) {
      long aVal = getLong(a, k);
      long bVal = getLong(b, k);
      if (aVal != bVal) {
        diff.put(k, bVal - aVal);
      }
    }
    return diff;
  }

  public static void validateMetricChanges(Map<String, Long> oldMetrics) {
    final Map<String, Long> newMetrics = getMetricsSnapshot();
    final Map<String, Long> allCfDeltas = new TreeMap<String, Long>();
    final Map<String, Long> allBlockCategoryDeltas =
        new TreeMap<String, Long>();
    final Map<String, Long> deltas = diffMetrics(oldMetrics, newMetrics);
    final Pattern cfTableMetricRE =
        useTableNameGlobally ? TABLE_AND_CF_NAME_RE : CF_NAME_RE;
    final Set<String> allKeys = new TreeSet<String>(oldMetrics.keySet());
    allKeys.addAll(newMetrics.keySet());

    for (SchemaMetrics cfm : tableAndFamilyToMetrics.values()) {
      for (String metricName : cfm.getAllMetricNames()) {
        if (metricName.startsWith(CF_PREFIX + CF_PREFIX)) {
          throw new AssertionError("Column family prefix used twice: " +
              metricName);
        }

        final long oldValue = getLong(oldMetrics, metricName);
        final long newValue = getLong(newMetrics, metricName);
        final long delta = newValue - oldValue;

        // Re-calculate values of metrics with no column family (or CF/table)
        // specified based on all metrics with CF (or CF/table) specified.
        if (delta != 0) {
          if (cfm != ALL_SCHEMA_METRICS) {
            final String aggregateMetricName =
                cfTableMetricRE.matcher(metricName).replaceAll("");
            if (!aggregateMetricName.equals(metricName)) {
              LOG.debug("Counting " + delta + " units of " + metricName
                  + " towards " + aggregateMetricName);

              putLong(allCfDeltas, aggregateMetricName,
                  getLong(allCfDeltas, aggregateMetricName) + delta);
            }
          } else {
            LOG.debug("Metric=" + metricName + ", delta=" + delta);
          }
        }

        Matcher matcher = BLOCK_CATEGORY_RE.matcher(metricName);
        if (matcher.find()) {
           // Only process per-block-category metrics
          String metricNoBlockCategory = matcher.replaceAll("");

          putLong(allBlockCategoryDeltas, metricNoBlockCategory,
              getLong(allBlockCategoryDeltas, metricNoBlockCategory) + delta);
        }
      }
    }

    StringBuilder errors = new StringBuilder();
    for (String key : ALL_SCHEMA_METRICS.getAllMetricNames()) {
      long actual = getLong(deltas, key);
      long expected = getLong(allCfDeltas, key);
      if (actual != expected) {
        if (errors.length() > 0)
          errors.append("\n");
        errors.append("The all-CF metric " + key + " changed by "
            + actual + " but the aggregation of per-CF/table metrics "
            + "yields " + expected);
      }
    }

    // Verify metrics computed for all block types based on the aggregation
    // of per-block-type metrics.
    for (String key : allKeys) {
      if (BLOCK_CATEGORY_RE.matcher(key).find() ||
          key.contains(ALL_SCHEMA_METRICS.getBloomMetricName(false)) ||
          key.contains(ALL_SCHEMA_METRICS.getBloomMetricName(true))){
        // Skip per-block-category metrics. Also skip bloom filters, because
        // they are not aggregated per block type.
        continue;
      }
      long actual = getLong(deltas, key);
      long expected = getLong(allBlockCategoryDeltas, key);
      if (actual != expected) {
        if (errors.length() > 0)
          errors.append("\n");
        errors.append("The all-block-category metric " + key
            + " changed by " + actual + " but the aggregation of "
            + "per-block-category metrics yields " + expected);
      }
    }

    if (errors.length() > 0) {
      throw new AssertionError(errors.toString());
    }
  }

  /**
   * Creates an instance pretending both the table and column family are
   * unknown. Used in unit tests.
   */
  public static SchemaMetrics getUnknownInstanceForTest() {
    return getInstance(UNKNOWN, UNKNOWN);
  }

  /**
   * Set the flag to use or not use table name in metric names. Used in unit
   * tests, so the flag can be set arbitrarily.
   */
  public static void setUseTableNameInTest(final boolean useTableNameNew) {
    useTableNameGlobally = useTableNameNew;
  }

  /** Formats the given map of metrics in a human-readable way. */
  public static String formatMetrics(Map<String, Long> metrics) {
    StringBuilder sb = new StringBuilder();
    for (Map.Entry<String, Long> entry : metrics.entrySet()) {
      if (sb.length() > 0) {
        sb.append('\n');
      }
      sb.append(entry.getKey() + " : " + entry.getValue());
    }
    return sb.toString();
  }

}
TOP

Related Classes of org.apache.hadoop.hbase.regionserver.metrics.SchemaMetrics$SchemaAware

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.