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}