@RestrictedApi(explanation = "Should only be called in tests", link = "",
      allowedOnPath = ".*(/src/test/.*|StochasticLoadBalancer).java")
  boolean needsBalance(TableName tableName, BalancerClusterState cluster) {
    ClusterLoadState cs = new ClusterLoadState(cluster.clusterState);
    if (cs.getNumServers() < MIN_SERVER_BALANCE) {
      LOG.info(
        "Not running balancer because only " + cs.getNumServers() + " active regionserver(s)");
      sendRejectionReasonToRingBuffer(() -> "The number of RegionServers " + cs.getNumServers()
        + " < MIN_SERVER_BALANCE(" + MIN_SERVER_BALANCE + ")", null);
      return false;
    }

    if (areSomeRegionReplicasColocatedOnHost(cluster)) {
      LOG.info("Running balancer because at least one server hosts replicas of the same region."
        + " function cost={}", functionCost());
      return true;
    }

    if (areSomeRegionReplicasColocatedOnRack(cluster)) {
      LOG.info("Running balancer because at least one rack hosts replicas of the same region."
        + " function cost={}", functionCost());
      return true;
    }

    if (idleRegionServerExist(cluster)) {
      LOG.info("Running balancer because cluster has idle server(s)." + " function cost={}",
        functionCost());
      return true;
    }

    if (
      // table isolation is inherently incompatible with naive "sloppy server" checks
      !balancerConditionals.isTableIsolationEnabled() && sloppyRegionServerExist(cs)
    ) {
      LOG.info("Running balancer because cluster has sloppy server(s)." + " function cost={}",
        functionCost());
      return true;
    }

    if (balancerConditionals.shouldRunBalancer(cluster)) {
      LOG.info("Running balancer because conditional candidate generators have important moves");
      return true;
    }

    double total = 0.0;
    float localSumMultiplier = 0; // in case this.sumMultiplier is not initialized
    for (CostFunction c : costFunctions) {
      if (!c.isNeeded()) {
        LOG.trace("{} not needed", c.getClass().getSimpleName());
        continue;
      }
      total += c.cost() * c.getMultiplier();
      localSumMultiplier += c.getMultiplier();
    }
    sumMultiplier = localSumMultiplier;
    boolean balanced = (total / sumMultiplier < minCostNeedBalance);

    if (balanced) {
      final double calculatedTotal = total;
      sendRejectionReasonToRingBuffer(() -> getBalanceReason(calculatedTotal, sumMultiplier),
        costFunctions);
      LOG.info(
        "{} - skipping load balancing because weighted average imbalance={} <= "
          + "threshold({}) and conditionals do not have opinionated move candidates. "
          + "If you want more aggressive balancing, either lower "
          + "hbase.master.balancer.stochastic.minCostNeedBalance from {} or increase the relative "
          + "multiplier(s) of the specific cost function(s). functionCost={}",
        isByTable ? "Table specific (" + tableName + ")" : "Cluster wide", total / sumMultiplier,
        minCostNeedBalance, minCostNeedBalance, functionCost());
    } else {
      LOG.info(
        "{} - Calculating plan. may take up to {}ms to complete. currentCost={}, targetCost={}",
        isByTable ? "Table specific (" + tableName + ")" : "Cluster wide", maxRunningTime, total,
        minCostNeedBalance);
    }
    return !balanced;
  }
Update the number of metrics that are reported to JMX
hbase-balancer/src/main/java/org/apache/hadoop/hbase/master/balancer/StochasticLoadBalancer.java