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