private StepDirection getTuneDirection(TunerContext context) {
    StepDirection newTuneDirection = StepDirection.NEUTRAL;
    long blockedFlushCount = context.getBlockedFlushCount();
    long unblockedFlushCount = context.getUnblockedFlushCount();
    long evictCount = context.getEvictCount();
    long cacheMissCount = context.getCacheMissCount();
    long totalFlushCount = blockedFlushCount + unblockedFlushCount;
    float curMemstoreSize = context.getCurMemStoreSize();
    float curBlockCacheSize = context.getCurBlockCacheSize();
    StringBuilder tunerLog = new StringBuilder();
    // We can consider memstore or block cache to be sufficient if
    // we are using only a minor fraction of what have been already provided to it.
    boolean earlyMemstoreSufficientCheck = totalFlushCount == 0
      || context.getCurMemStoreUsed() < curMemstoreSize * sufficientMemoryLevel;
    boolean earlyBlockCacheSufficientCheck =
      evictCount == 0 || context.getCurBlockCacheUsed() < curBlockCacheSize * sufficientMemoryLevel;
    if (earlyMemstoreSufficientCheck && earlyBlockCacheSufficientCheck) {
      // Both memstore and block cache memory seems to be sufficient. No operation required.
      newTuneDirection = StepDirection.NEUTRAL;
      tunerLog.append("Going to do nothing because no changes are needed.");
    } else if (earlyMemstoreSufficientCheck) {
      // Increase the block cache size and corresponding decrease in memstore size.
      newTuneDirection = StepDirection.INCREASE_BLOCK_CACHE_SIZE;
      tunerLog.append("Going to increase the block cache size.");
    } else if (earlyBlockCacheSufficientCheck) {
      // Increase the memstore size and corresponding decrease in block cache size.
      newTuneDirection = StepDirection.INCREASE_MEMSTORE_SIZE;
      tunerLog.append("Going to increase the memstore size.");
    } else {
      // Early checks for sufficient memory failed. Tuning memory based on past statistics.
      // Boolean indicator to show if we need to revert previous step or not.
      boolean isReverting = false;
      switch (prevTuneDirection) {
        // Here we are using number of evictions rather than cache misses because it is more
        // strong indicator for deficient cache size. Improving caching is what we
        // would like to optimize for in steady state.
        case INCREASE_BLOCK_CACHE_SIZE:
          if (
            (double) evictCount > rollingStatsForEvictions.getMean() || (double) totalFlushCount
                > rollingStatsForFlushes.getMean() + rollingStatsForFlushes.getDeviation() / 2.00
          ) {
            // Reverting previous step as it was not useful.
            // Tuning failed to decrease evictions or tuning resulted in large number of flushes.
            newTuneDirection = StepDirection.INCREASE_MEMSTORE_SIZE;
            tunerLog.append("We will revert previous tuning");
            if ((double) evictCount > rollingStatsForEvictions.getMean()) {
              tunerLog.append(" because we could not decrease evictions sufficiently.");
            } else {
              tunerLog.append(" because the number of flushes rose significantly.");
            }
            isReverting = true;
          }
          break;
        case INCREASE_MEMSTORE_SIZE:
          if (
            (double) totalFlushCount > rollingStatsForFlushes.getMean()
              || (double) evictCount > rollingStatsForEvictions.getMean()
                + rollingStatsForEvictions.getDeviation() / 2.00
          ) {
            // Reverting previous step as it was not useful.
            // Tuning failed to decrease flushes or tuning resulted in large number of evictions.
            newTuneDirection = StepDirection.INCREASE_BLOCK_CACHE_SIZE;
            tunerLog.append("We will revert previous tuning");
            if ((double) totalFlushCount > rollingStatsForFlushes.getMean()) {
              tunerLog.append(" because we could not decrease flushes sufficiently.");
            } else {
              tunerLog.append(" because number of evictions rose significantly.");
            }
            isReverting = true;
          }
          break;
        default:
          // Last step was neutral, revert doesn't not apply here.
          break;
      }
      // If we are not reverting. We try to tune memory sizes by looking at cache misses / flushes.
      if (!isReverting) {
        // mean +- deviation*0.8 is considered to be normal
        // below it its consider low and above it is considered high.
        // We can safely assume that the number cache misses, flushes are normally distributed over
        // past periods and hence on all the above mentioned classes (normal, high and low)
        // are likely to occur with probability 56%, 22%, 22% respectively. Hence there is at
        // least ~10% probability that we will not fall in NEUTRAL step.
        // This optimization solution is feedback based and we revert when we
        // dont find our steps helpful. Hence we want to do tuning only when we have clear
        // indications because too many unnecessary tuning may affect the performance of cluster.
        if (
          (double) cacheMissCount
              < rollingStatsForCacheMisses.getMean()
                - rollingStatsForCacheMisses.getDeviation() * 0.80
            && (double) totalFlushCount
                < rollingStatsForFlushes.getMean() - rollingStatsForFlushes.getDeviation() * 0.80
        ) {
          // Everything is fine no tuning required
          newTuneDirection = StepDirection.NEUTRAL;
        } else if (
          (double) cacheMissCount
              > rollingStatsForCacheMisses.getMean()
                + rollingStatsForCacheMisses.getDeviation() * 0.80
            && (double) totalFlushCount
                < rollingStatsForFlushes.getMean() - rollingStatsForFlushes.getDeviation() * 0.80
        ) {
          // more misses , increasing cache size
          newTuneDirection = StepDirection.INCREASE_BLOCK_CACHE_SIZE;
          tunerLog.append(
            "Going to increase block cache size due to increase in number of cache misses.");
        } else if (
          (double) cacheMissCount
              < rollingStatsForCacheMisses.getMean()
                - rollingStatsForCacheMisses.getDeviation() * 0.80
            && (double) totalFlushCount
                > rollingStatsForFlushes.getMean() + rollingStatsForFlushes.getDeviation() * 0.80
        ) {
          // more flushes , increasing memstore size
          newTuneDirection = StepDirection.INCREASE_MEMSTORE_SIZE;
          tunerLog.append("Going to increase memstore size due to increase in number of flushes.");
        } else if (blockedFlushCount > 0 && prevTuneDirection == StepDirection.NEUTRAL) {
          // we do not want blocked flushes
          newTuneDirection = StepDirection.INCREASE_MEMSTORE_SIZE;
          tunerLog.append(
            "Going to increase memstore size due to" + blockedFlushCount + " blocked flushes.");
        } else {
          // Default. Not enough facts to do tuning.
          tunerLog.append(
            "Going to do nothing because we " + "could not determine best tuning direction");
          newTuneDirection = StepDirection.NEUTRAL;
        }
      }
    }
    // Log NEUTRAL decisions at DEBUG, because they are the most frequent and not that interesting.
    // Log other decisions at INFO because they are making meaningful operational changes.
    switch (newTuneDirection) {
      case NEUTRAL:
        if (LOG.isDebugEnabled()) {
          LOG.debug(tunerLog.toString());
        }
        break;
      default:
        LOG.info(tunerLog.toString());
        break;
    }
    return newTuneDirection;
  }
Determine best direction of tuning base on given context.
@param context The tuner context.
@return tuning direction.
hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/DefaultHeapMemoryTuner.java