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.Collections;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026import java.util.concurrent.TimeUnit;
027import org.apache.hadoop.hbase.HBaseCommonTestingUtil;
028import org.apache.hadoop.hbase.HBaseInterfaceAudience;
029import org.apache.hadoop.hbase.HConstants;
030import org.apache.hadoop.hbase.ServerName;
031import org.apache.hadoop.hbase.TableName;
032import org.apache.hadoop.hbase.client.RegionInfo;
033import org.apache.hadoop.hbase.client.RegionInfoBuilder;
034import org.apache.hadoop.hbase.master.LoadBalancer;
035import org.apache.hadoop.hbase.util.AbstractHBaseTool;
036import org.apache.hadoop.hbase.util.Bytes;
037import org.apache.yetus.audience.InterfaceAudience;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
042import org.apache.hbase.thirdparty.com.google.common.base.Stopwatch;
043import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine;
044import org.apache.hbase.thirdparty.org.apache.commons.cli.Option;
045
046/**
047 * Tool to test performance of different {@link org.apache.hadoop.hbase.master.LoadBalancer}
048 * implementations. Example command: $ bin/hbase
049 * org.apache.hadoop.hbase.master.balancer.LoadBalancerPerformanceEvaluation -regions 1000 -servers
050 * 100 -load_balancer org.apache.hadoop.hbase.master.balancer.SimpleLoadBalancer
051 */
052@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.TOOLS)
053public class LoadBalancerPerformanceEvaluation extends AbstractHBaseTool {
054  private static final Logger LOG =
055    LoggerFactory.getLogger(LoadBalancerPerformanceEvaluation.class.getName());
056
057  protected static final HBaseCommonTestingUtil UTIL = new HBaseCommonTestingUtil();
058
059  private static final int DEFAULT_NUM_REGIONS = 1000000;
060  private static Option NUM_REGIONS_OPT = new Option("regions", true,
061    "Number of regions to consider by load balancer. Default: " + DEFAULT_NUM_REGIONS);
062
063  private static final int DEFAULT_NUM_SERVERS = 1000;
064  private static Option NUM_SERVERS_OPT = new Option("servers", true,
065    "Number of servers to consider by load balancer. Default: " + DEFAULT_NUM_SERVERS);
066
067  private static final String DEFAULT_LOAD_BALANCER =
068    "org.apache.hadoop.hbase.master.balancer.StochasticLoadBalancer";
069  private static Option LOAD_BALANCER_OPT = new Option("load_balancer", true,
070    "Type of Load Balancer to use. Default: " + DEFAULT_LOAD_BALANCER);
071
072  private int numRegions;
073  private int numServers;
074  private String loadBalancerType;
075  private Class<?> loadBalancerClazz;
076
077  private LoadBalancer loadBalancer;
078
079  // data
080  private List<ServerName> servers;
081  private List<RegionInfo> regions;
082  private Map<RegionInfo, ServerName> regionServerMap;
083  private Map<TableName, Map<ServerName, List<RegionInfo>>> tableServerRegionMap;
084
085  // Non-default configurations.
086  private void setupConf() {
087    conf.setClass(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, loadBalancerClazz,
088      LoadBalancer.class);
089    loadBalancer = LoadBalancerFactory.getLoadBalancer(conf);
090  }
091
092  private void generateRegionsAndServers() {
093    TableName tableName = TableName.valueOf("LoadBalancerPerfTable");
094    // regions
095    regions = new ArrayList<>(numRegions);
096    regionServerMap = new HashMap<>(numRegions);
097    for (int i = 0; i < numRegions; ++i) {
098      byte[] start = new byte[16];
099      byte[] end = new byte[16];
100
101      Bytes.putInt(start, 0, i);
102      Bytes.putInt(end, 0, i + 1);
103      RegionInfo hri = RegionInfoBuilder.newBuilder(tableName).setStartKey(start).setEndKey(end)
104        .setSplit(false).setRegionId(i).build();
105      regions.add(hri);
106      regionServerMap.put(hri, null);
107    }
108
109    // servers
110    servers = new ArrayList<>(numServers);
111    Map<ServerName, List<RegionInfo>> serverRegionMap = new HashMap<>(numServers);
112    for (int i = 0; i < numServers; ++i) {
113      ServerName sn = ServerName.valueOf("srv" + i, HConstants.DEFAULT_REGIONSERVER_PORT, i);
114      servers.add(sn);
115      serverRegionMap.put(sn, i == 0 ? regions : Collections.emptyList());
116    }
117    tableServerRegionMap = Collections.singletonMap(tableName, serverRegionMap);
118  }
119
120  @Override
121  protected void addOptions() {
122    addOption(NUM_REGIONS_OPT);
123    addOption(NUM_SERVERS_OPT);
124    addOption(LOAD_BALANCER_OPT);
125  }
126
127  @Override
128  protected void processOptions(CommandLine cmd) {
129    numRegions = getOptionAsInt(cmd, NUM_REGIONS_OPT.getOpt(), DEFAULT_NUM_REGIONS);
130    Preconditions.checkArgument(numRegions > 0, "Invalid number of regions!");
131
132    numServers = getOptionAsInt(cmd, NUM_SERVERS_OPT.getOpt(), DEFAULT_NUM_SERVERS);
133    Preconditions.checkArgument(numServers > 0, "Invalid number of servers!");
134
135    loadBalancerType = cmd.getOptionValue(LOAD_BALANCER_OPT.getOpt(), DEFAULT_LOAD_BALANCER);
136    Preconditions.checkArgument(!loadBalancerType.isEmpty(), "Invalid load balancer type!");
137
138    try {
139      loadBalancerClazz = Class.forName(loadBalancerType);
140    } catch (ClassNotFoundException e) {
141      System.err.println("Class '" + loadBalancerType + "' not found!");
142      System.exit(1);
143    }
144
145    setupConf();
146  }
147
148  private String formatResults(final String methodName, final long timeMillis) {
149    return String.format("Time for %-25s: %dms%n", methodName, timeMillis);
150  }
151
152  @Override
153  protected int doWork() throws Exception {
154    generateRegionsAndServers();
155
156    String methodName = "roundRobinAssignment";
157    LOG.info("Calling " + methodName);
158    Stopwatch watch = Stopwatch.createStarted();
159    loadBalancer.roundRobinAssignment(regions, servers);
160    System.out.print(formatResults(methodName, watch.elapsed(TimeUnit.MILLISECONDS)));
161
162    methodName = "retainAssignment";
163    LOG.info("Calling " + methodName);
164    watch.reset().start();
165    loadBalancer.retainAssignment(regionServerMap, servers);
166    System.out.print(formatResults(methodName, watch.elapsed(TimeUnit.MILLISECONDS)));
167
168    methodName = "balanceCluster";
169    LOG.info("Calling " + methodName);
170    watch.reset().start();
171
172    loadBalancer.balanceCluster(tableServerRegionMap);
173    System.out.print(formatResults(methodName, watch.elapsed(TimeUnit.MILLISECONDS)));
174
175    return EXIT_SUCCESS;
176  }
177
178  public static void main(String[] args) throws IOException {
179    LoadBalancerPerformanceEvaluation tool = new LoadBalancerPerformanceEvaluation();
180    tool.setConf(UTIL.getConfiguration());
181    tool.run(args);
182  }
183}