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.io.IOException;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.concurrent.TimeUnit;
028import java.util.function.Predicate;
029import java.util.function.Supplier;
030import org.apache.hadoop.conf.Configuration;
031import org.apache.hadoop.hbase.HBaseConfiguration;
032import org.apache.hadoop.hbase.HBaseInterfaceAudience;
033import org.apache.hadoop.hbase.HConstants;
034import org.apache.hadoop.hbase.HDFSBlocksDistribution;
035import org.apache.hadoop.hbase.ServerMetrics;
036import org.apache.hadoop.hbase.ServerName;
037import org.apache.hadoop.hbase.TableName;
038import org.apache.hadoop.hbase.client.BalancerDecision;
039import org.apache.hadoop.hbase.client.BalancerRejection;
040import org.apache.hadoop.hbase.client.Connection;
041import org.apache.hadoop.hbase.client.RegionInfo;
042import org.apache.hadoop.hbase.client.RegionInfoBuilder;
043import org.apache.hadoop.hbase.client.TableDescriptor;
044import org.apache.hadoop.hbase.master.LoadBalancer;
045import org.apache.hadoop.hbase.util.AbstractHBaseTool;
046import org.apache.hadoop.hbase.util.Bytes;
047import org.apache.yetus.audience.InterfaceAudience;
048import org.slf4j.Logger;
049import org.slf4j.LoggerFactory;
050
051import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
052import org.apache.hbase.thirdparty.com.google.common.base.Stopwatch;
053import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine;
054import org.apache.hbase.thirdparty.org.apache.commons.cli.Option;
055
056/**
057 * Tool to test performance of different {@link org.apache.hadoop.hbase.master.LoadBalancer}
058 * implementations. Example command: $ bin/hbase
059 * org.apache.hadoop.hbase.master.balancer.LoadBalancerPerformanceEvaluation -regions 1000 -servers
060 * 100 -load_balancer org.apache.hadoop.hbase.master.balancer.SimpleLoadBalancer
061 */
062@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.TOOLS)
063public class LoadBalancerPerformanceEvaluation extends AbstractHBaseTool {
064  private static final Logger LOG =
065    LoggerFactory.getLogger(LoadBalancerPerformanceEvaluation.class.getName());
066
067  private static final int DEFAULT_NUM_REGIONS = 1000000;
068  private static final Option NUM_REGIONS_OPT = new Option("regions", true,
069    "Number of regions to consider by load balancer. Default: " + DEFAULT_NUM_REGIONS);
070
071  private static final int DEFAULT_NUM_SERVERS = 1000;
072  private static final Option NUM_SERVERS_OPT = new Option("servers", true,
073    "Number of servers to consider by load balancer. Default: " + DEFAULT_NUM_SERVERS);
074
075  private static final String DEFAULT_LOAD_BALANCER =
076    "org.apache.hadoop.hbase.master.balancer.StochasticLoadBalancer";
077  private static final Option LOAD_BALANCER_OPT = new Option("load_balancer", true,
078    "Type of Load Balancer to use. Default: " + DEFAULT_LOAD_BALANCER);
079
080  private int numRegions;
081  private int numServers;
082  private String loadBalancerType;
083  private Class<?> loadBalancerClazz;
084
085  private LoadBalancer loadBalancer;
086
087  // data
088  private List<ServerName> servers;
089  private List<RegionInfo> regions;
090  private Map<RegionInfo, ServerName> regionServerMap;
091  private Map<TableName, Map<ServerName, List<RegionInfo>>> tableServerRegionMap;
092
093  // Non-default configurations.
094  private void setupConf() {
095    conf.setClass(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, loadBalancerClazz,
096      LoadBalancer.class);
097    loadBalancer = LoadBalancerFactory.getLoadBalancer(conf);
098    loadBalancer.setClusterInfoProvider(new DummyClusterInfoProvider(conf));
099    try {
100      loadBalancer.initialize();
101    } catch (IOException e) {
102      LOG.error("Failed to initialize load balancer", e);
103      throw new RuntimeException("Failed to initialize load balancer", e);
104    }
105  }
106
107  private void generateRegionsAndServers() {
108    TableName tableName = TableName.valueOf("LoadBalancerPerfTable");
109    // regions
110    regions = new ArrayList<>(numRegions);
111    regionServerMap = new HashMap<>(numRegions);
112    for (int i = 0; i < numRegions; ++i) {
113      byte[] start = new byte[16];
114      byte[] end = new byte[16];
115
116      Bytes.putInt(start, 0, i);
117      Bytes.putInt(end, 0, i + 1);
118      RegionInfo hri = RegionInfoBuilder.newBuilder(tableName).setStartKey(start).setEndKey(end)
119        .setSplit(false).setRegionId(i).build();
120      regions.add(hri);
121      regionServerMap.put(hri, null);
122    }
123
124    // servers
125    servers = new ArrayList<>(numServers);
126    Map<ServerName, List<RegionInfo>> serverRegionMap = new HashMap<>(numServers);
127    for (int i = 0; i < numServers; ++i) {
128      ServerName sn = ServerName.valueOf("srv" + i, HConstants.DEFAULT_REGIONSERVER_PORT, i);
129      servers.add(sn);
130      serverRegionMap.put(sn, i == 0 ? regions : Collections.emptyList());
131    }
132    tableServerRegionMap = Collections.singletonMap(tableName, serverRegionMap);
133  }
134
135  @Override
136  protected void addOptions() {
137    addOption(NUM_REGIONS_OPT);
138    addOption(NUM_SERVERS_OPT);
139    addOption(LOAD_BALANCER_OPT);
140  }
141
142  @Override
143  protected void processOptions(CommandLine cmd) {
144    numRegions = getOptionAsInt(cmd, NUM_REGIONS_OPT.getOpt(), DEFAULT_NUM_REGIONS);
145    Preconditions.checkArgument(numRegions > 0, "Invalid number of regions!");
146
147    numServers = getOptionAsInt(cmd, NUM_SERVERS_OPT.getOpt(), DEFAULT_NUM_SERVERS);
148    Preconditions.checkArgument(numServers > 0, "Invalid number of servers!");
149
150    loadBalancerType = cmd.getOptionValue(LOAD_BALANCER_OPT.getOpt(), DEFAULT_LOAD_BALANCER);
151    Preconditions.checkArgument(!loadBalancerType.isEmpty(), "Invalid load balancer type!");
152
153    try {
154      loadBalancerClazz = Class.forName(loadBalancerType);
155    } catch (ClassNotFoundException e) {
156      System.err.println("Class '" + loadBalancerType + "' not found!");
157      System.exit(1);
158    }
159
160    setupConf();
161  }
162
163  private String formatResults(final String methodName, final long timeMillis) {
164    return String.format("Time for %-25s: %dms%n", methodName, timeMillis);
165  }
166
167  @Override
168  protected int doWork() throws Exception {
169    generateRegionsAndServers();
170
171    String methodName = "roundRobinAssignment";
172    LOG.info("Calling {}", methodName);
173    Stopwatch watch = Stopwatch.createStarted();
174    loadBalancer.roundRobinAssignment(regions, servers);
175    System.out.print(formatResults(methodName, watch.elapsed(TimeUnit.MILLISECONDS)));
176
177    methodName = "retainAssignment";
178    LOG.info("Calling {}", methodName);
179    watch.reset().start();
180    loadBalancer.retainAssignment(regionServerMap, servers);
181    System.out.print(formatResults(methodName, watch.elapsed(TimeUnit.MILLISECONDS)));
182
183    methodName = "balanceCluster";
184    LOG.info("Calling {}", methodName);
185    watch.reset().start();
186
187    loadBalancer.balanceCluster(tableServerRegionMap);
188    System.out.print(formatResults(methodName, watch.elapsed(TimeUnit.MILLISECONDS)));
189
190    return EXIT_SUCCESS;
191  }
192
193  public static void main(String[] args) throws IOException {
194    LoadBalancerPerformanceEvaluation tool = new LoadBalancerPerformanceEvaluation();
195    tool.setConf(HBaseConfiguration.create());
196    tool.run(args);
197  }
198
199  private static class DummyClusterInfoProvider implements ClusterInfoProvider {
200
201    private volatile Configuration conf;
202
203    public DummyClusterInfoProvider(Configuration conf) {
204      this.conf = conf;
205    }
206
207    @Override
208    public Configuration getConfiguration() {
209      return conf;
210    }
211
212    @Override
213    public Connection getConnection() {
214      return null;
215    }
216
217    @Override
218    public List<RegionInfo> getAssignedRegions() {
219      return Collections.emptyList();
220    }
221
222    @Override
223    public void unassign(RegionInfo regionInfo) throws IOException {
224
225    }
226
227    @Override
228    public TableDescriptor getTableDescriptor(TableName tableName) throws IOException {
229      return null;
230    }
231
232    @Override
233    public int getNumberOfTables() throws IOException {
234      return 0;
235    }
236
237    @Override
238    public HDFSBlocksDistribution computeHDFSBlocksDistribution(Configuration conf,
239      TableDescriptor tableDescriptor, RegionInfo regionInfo) throws IOException {
240      return new HDFSBlocksDistribution();
241    }
242
243    @Override
244    public boolean hasRegionReplica(Collection<RegionInfo> regions) throws IOException {
245      return false;
246    }
247
248    @Override
249    public List<ServerName> getOnlineServersList() {
250      return Collections.emptyList();
251    }
252
253    @Override
254    public List<ServerName> getOnlineServersListWithPredicator(List<ServerName> servers,
255      Predicate<ServerMetrics> filter) {
256      return Collections.emptyList();
257    }
258
259    @Override
260    public Map<ServerName, List<RegionInfo>>
261      getSnapShotOfAssignment(Collection<RegionInfo> regions) {
262      return Collections.emptyMap();
263    }
264
265    @Override
266    public boolean isOffPeakHour() {
267      return false;
268    }
269
270    @Override
271    public void recordBalancerDecision(Supplier<BalancerDecision> decision) {
272
273    }
274
275    @Override
276    public void recordBalancerRejection(Supplier<BalancerRejection> rejection) {
277
278    }
279
280    @Override
281    public ServerMetrics getLoad(ServerName serverName) {
282      return null;
283    }
284
285    @Override
286    public void onConfigurationChange(Configuration conf) {
287      this.conf = conf;
288    }
289  }
290}