001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.master.balancer;
019
020import java.util.Map;
021import org.apache.yetus.audience.InterfaceAudience;
022
023/**
024 * Base class of StochasticLoadBalancer's Cost Functions.
025 */
026@InterfaceAudience.Private
027abstract class CostFunction {
028
029  public static double getCostEpsilon(double cost) {
030    return Math.ulp(cost);
031  }
032
033  private float multiplier = 0;
034
035  protected BalancerClusterState cluster;
036
037  boolean isNeeded() {
038    return true;
039  }
040
041  float getMultiplier() {
042    return multiplier;
043  }
044
045  void setMultiplier(float m) {
046    this.multiplier = m;
047  }
048
049  /**
050   * Called once per LB invocation to give the cost function to initialize it's state, and perform
051   * any costly calculation.
052   */
053  void prepare(BalancerClusterState cluster) {
054    this.cluster = cluster;
055  }
056
057  /**
058   * Called once per cluster Action to give the cost function an opportunity to update it's state.
059   * postAction() is always called at least once before cost() is called with the cluster that this
060   * action is performed on.
061   */
062  void postAction(BalanceAction action) {
063    switch (action.getType()) {
064      case NULL:
065        break;
066      case ASSIGN_REGION:
067        AssignRegionAction ar = (AssignRegionAction) action;
068        regionMoved(ar.getRegion(), -1, ar.getServer());
069        break;
070      case MOVE_REGION:
071        MoveRegionAction mra = (MoveRegionAction) action;
072        regionMoved(mra.getRegion(), mra.getFromServer(), mra.getToServer());
073        break;
074      case SWAP_REGIONS:
075        SwapRegionsAction a = (SwapRegionsAction) action;
076        regionMoved(a.getFromRegion(), a.getFromServer(), a.getToServer());
077        regionMoved(a.getToRegion(), a.getToServer(), a.getFromServer());
078        break;
079      default:
080        throw new RuntimeException("Uknown action:" + action.getType());
081    }
082  }
083
084  protected void regionMoved(int region, int oldServer, int newServer) {
085  }
086
087  protected abstract double cost();
088
089  /**
090   * Add the cost of this cost function to the weight of the candidate generator that is optimized
091   * for this cost function. By default it is the RandomCandiateGenerator for a cost function.
092   * Called once per init or after postAction.
093   * @param weights the weights for every generator.
094   */
095  public void updateWeight(Map<Class<? extends CandidateGenerator>, Double> weights) {
096    weights.merge(RandomCandidateGenerator.class, cost(), Double::sum);
097  }
098
099  /**
100   * Scale the value between 0 and 1.
101   * @param min   Min value
102   * @param max   The Max value
103   * @param value The value to be scaled.
104   * @return The scaled value.
105   */
106  protected static double scale(double min, double max, double value) {
107    double costEpsilon = getCostEpsilon(max);
108    if (
109      max <= min || value <= min || Math.abs(max - min) <= costEpsilon
110        || Math.abs(value - min) <= costEpsilon
111    ) {
112      return 0;
113    }
114    if (max <= min || Math.abs(max - min) <= costEpsilon) {
115      return 0;
116    }
117
118    return Math.max(0d, Math.min(1d, (value - min) / (max - min)));
119  }
120}