1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.hadoop.hbase.util;
18
19 import java.io.IOException;
20 import java.io.InterruptedIOException;
21 import java.lang.reflect.Constructor;
22 import java.net.InetAddress;
23 import java.security.SecureRandom;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.List;
27 import java.util.Properties;
28 import java.util.Random;
29 import java.util.concurrent.atomic.AtomicReference;
30
31 import javax.crypto.spec.SecretKeySpec;
32
33 import org.apache.commons.cli.CommandLine;
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36 import org.apache.hadoop.conf.Configuration;
37 import org.apache.hadoop.hbase.HBaseConfiguration;
38 import org.apache.hadoop.hbase.HBaseTestingUtility;
39 import org.apache.hadoop.hbase.HColumnDescriptor;
40 import org.apache.hadoop.hbase.HConstants;
41 import org.apache.hadoop.hbase.HTableDescriptor;
42 import org.apache.hadoop.hbase.TableName;
43 import org.apache.hadoop.hbase.client.Durability;
44 import org.apache.hadoop.hbase.client.HBaseAdmin;
45 import org.apache.hadoop.hbase.io.compress.Compression;
46 import org.apache.hadoop.hbase.io.crypto.Cipher;
47 import org.apache.hadoop.hbase.io.crypto.Encryption;
48 import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
49 import org.apache.hadoop.hbase.regionserver.BloomType;
50 import org.apache.hadoop.hbase.security.EncryptionUtil;
51 import org.apache.hadoop.hbase.security.User;
52 import org.apache.hadoop.hbase.security.access.AccessControlClient;
53 import org.apache.hadoop.hbase.security.access.Permission;
54 import org.apache.hadoop.hbase.util.test.LoadTestDataGenerator;
55 import org.apache.hadoop.hbase.util.test.LoadTestDataGeneratorWithACL;
56 import org.apache.hadoop.security.SecurityUtil;
57 import org.apache.hadoop.security.UserGroupInformation;
58 import org.apache.hadoop.util.ToolRunner;
59
60
61
62
63
64
65 public class LoadTestTool extends AbstractHBaseTool {
66
67 private static final Log LOG = LogFactory.getLog(LoadTestTool.class);
68 private static final String COLON = ":";
69
70
71 private TableName tableName;
72
73
74 protected static final String DEFAULT_TABLE_NAME = "cluster_test";
75
76
77 public static byte[] COLUMN_FAMILY = Bytes.toBytes("test_cf");
78
79
80 protected static final byte[][] COLUMN_FAMILIES = { COLUMN_FAMILY };
81
82
83 protected static final int DEFAULT_DATA_SIZE = 64;
84
85
86 protected static final int DEFAULT_NUM_THREADS = 20;
87
88
89 protected static final String OPT_USAGE_LOAD =
90 "<avg_cols_per_key>:<avg_data_size>" +
91 "[:<#threads=" + DEFAULT_NUM_THREADS + ">]";
92
93
94 protected static final String OPT_USAGE_READ =
95 "<verify_percent>[:<#threads=" + DEFAULT_NUM_THREADS + ">]";
96
97
98 protected static final String OPT_USAGE_UPDATE =
99 "<update_percent>[:<#threads=" + DEFAULT_NUM_THREADS
100 + ">][:<#whether to ignore nonce collisions=0>]";
101
102 protected static final String OPT_USAGE_BLOOM = "Bloom filter type, one of " +
103 Arrays.toString(BloomType.values());
104
105 protected static final String OPT_USAGE_COMPRESSION = "Compression type, " +
106 "one of " + Arrays.toString(Compression.Algorithm.values());
107
108 public static final String OPT_DATA_BLOCK_ENCODING_USAGE =
109 "Encoding algorithm (e.g. prefix "
110 + "compression) to use for data blocks in the test column family, "
111 + "one of " + Arrays.toString(DataBlockEncoding.values()) + ".";
112
113 public static final String OPT_BLOOM = "bloom";
114 public static final String OPT_COMPRESSION = "compression";
115 public static final String OPT_DEFERRED_LOG_FLUSH = "deferredlogflush";
116 public static final String OPT_DEFERRED_LOG_FLUSH_USAGE = "Enable deferred log flush.";
117
118 public static final String OPT_DATA_BLOCK_ENCODING =
119 HColumnDescriptor.DATA_BLOCK_ENCODING.toLowerCase();
120
121 public static final String OPT_INMEMORY = "in_memory";
122 public static final String OPT_USAGE_IN_MEMORY = "Tries to keep the HFiles of the CF " +
123 "inmemory as far as possible. Not guaranteed that reads are always served from inmemory";
124
125 public static final String OPT_GENERATOR = "generator";
126 public static final String OPT_GENERATOR_USAGE = "The class which generates load for the tool."
127 + " Any args for this class can be passed as colon separated after class name";
128
129 public static final String OPT_READER = "reader";
130 public static final String OPT_READER_USAGE = "The class for executing the read requests";
131
132 protected static final String OPT_KEY_WINDOW = "key_window";
133 protected static final String OPT_WRITE = "write";
134 protected static final String OPT_MAX_READ_ERRORS = "max_read_errors";
135 protected static final String OPT_MULTIPUT = "multiput";
136 protected static final String OPT_NUM_KEYS = "num_keys";
137 protected static final String OPT_READ = "read";
138 protected static final String OPT_START_KEY = "start_key";
139 public static final String OPT_TABLE_NAME = "tn";
140 protected static final String OPT_ZK_QUORUM = "zk";
141 protected static final String OPT_ZK_PARENT_NODE = "zk_root";
142 protected static final String OPT_SKIP_INIT = "skip_init";
143 protected static final String OPT_INIT_ONLY = "init_only";
144 protected static final String NUM_TABLES = "num_tables";
145 protected static final String OPT_REGIONS_PER_SERVER = "regions_per_server";
146 protected static final String OPT_BATCHUPDATE = "batchupdate";
147 protected static final String OPT_UPDATE = "update";
148
149 public static final String OPT_ENCRYPTION = "encryption";
150 protected static final String OPT_ENCRYPTION_USAGE =
151 "Enables transparent encryption on the test table, one of " +
152 Arrays.toString(Encryption.getSupportedCiphers());
153
154 public static final String OPT_NUM_REGIONS_PER_SERVER = "num_regions_per_server";
155 protected static final String OPT_NUM_REGIONS_PER_SERVER_USAGE
156 = "Desired number of regions per region server. Defaults to 5.";
157 protected static int DEFAULT_NUM_REGIONS_PER_SERVER = 5;
158
159 protected static final long DEFAULT_START_KEY = 0;
160
161
162 protected CommandLine cmd;
163
164 protected MultiThreadedWriter writerThreads = null;
165 protected MultiThreadedReader readerThreads = null;
166 protected MultiThreadedUpdater updaterThreads = null;
167
168 protected long startKey, endKey;
169
170 protected boolean isWrite, isRead, isUpdate;
171 protected boolean deferredLogFlush;
172
173
174 protected DataBlockEncoding dataBlockEncodingAlgo;
175 protected Compression.Algorithm compressAlgo;
176 protected BloomType bloomType;
177 private boolean inMemoryCF;
178
179 private User userOwner;
180
181 protected int numWriterThreads = DEFAULT_NUM_THREADS;
182 protected int minColsPerKey, maxColsPerKey;
183 protected int minColDataSize = DEFAULT_DATA_SIZE, maxColDataSize = DEFAULT_DATA_SIZE;
184 protected boolean isMultiPut;
185
186
187 protected int numUpdaterThreads = DEFAULT_NUM_THREADS;
188 protected int updatePercent;
189 protected boolean ignoreConflicts = false;
190 protected boolean isBatchUpdate;
191
192
193 private int numReaderThreads = DEFAULT_NUM_THREADS;
194 private int keyWindow = MultiThreadedReader.DEFAULT_KEY_WINDOW;
195 private int maxReadErrors = MultiThreadedReader.DEFAULT_MAX_ERRORS;
196 private int verifyPercent;
197
198 private int numTables = 1;
199 private int regionsPerServer = HBaseTestingUtility.DEFAULT_REGIONS_PER_SERVER;
200
201 private String superUser;
202
203 private String userNames;
204
205 private String authnFileName;
206
207
208
209 protected boolean isSkipInit = false;
210 protected boolean isInitOnly = false;
211
212 protected Cipher cipher = null;
213
214 protected String[] splitColonSeparated(String option,
215 int minNumCols, int maxNumCols) {
216 String optVal = cmd.getOptionValue(option);
217 String[] cols = optVal.split(COLON);
218 if (cols.length < minNumCols || cols.length > maxNumCols) {
219 throw new IllegalArgumentException("Expected at least "
220 + minNumCols + " columns but no more than " + maxNumCols +
221 " in the colon-separated value '" + optVal + "' of the " +
222 "-" + option + " option");
223 }
224 return cols;
225 }
226
227 protected int getNumThreads(String numThreadsStr) {
228 return parseInt(numThreadsStr, 1, Short.MAX_VALUE);
229 }
230
231
232
233
234
235 protected void applyColumnFamilyOptions(TableName tableName,
236 byte[][] columnFamilies) throws IOException {
237 HBaseAdmin admin = new HBaseAdmin(conf);
238 HTableDescriptor tableDesc = admin.getTableDescriptor(tableName);
239 LOG.info("Disabling table " + tableName);
240 admin.disableTable(tableName);
241 for (byte[] cf : columnFamilies) {
242 HColumnDescriptor columnDesc = tableDesc.getFamily(cf);
243 boolean isNewCf = columnDesc == null;
244 if (isNewCf) {
245 columnDesc = new HColumnDescriptor(cf);
246 }
247 if (bloomType != null) {
248 columnDesc.setBloomFilterType(bloomType);
249 }
250 if (compressAlgo != null) {
251 columnDesc.setCompressionType(compressAlgo);
252 }
253 if (dataBlockEncodingAlgo != null) {
254 columnDesc.setDataBlockEncoding(dataBlockEncodingAlgo);
255 }
256 if (inMemoryCF) {
257 columnDesc.setInMemory(inMemoryCF);
258 }
259 if (cipher != null) {
260 byte[] keyBytes = new byte[cipher.getKeyLength()];
261 new SecureRandom().nextBytes(keyBytes);
262 columnDesc.setEncryptionType(cipher.getName());
263 columnDesc.setEncryptionKey(EncryptionUtil.wrapKey(conf,
264 User.getCurrent().getShortName(),
265 new SecretKeySpec(keyBytes, cipher.getName())));
266 }
267 if (isNewCf) {
268 admin.addColumn(tableName, columnDesc);
269 } else {
270 admin.modifyColumn(tableName, columnDesc);
271 }
272 }
273 LOG.info("Enabling table " + tableName);
274 admin.enableTable(tableName);
275 admin.close();
276 }
277
278 @Override
279 protected void addOptions() {
280 addOptWithArg(OPT_ZK_QUORUM, "ZK quorum as comma-separated host names " +
281 "without port numbers");
282 addOptWithArg(OPT_ZK_PARENT_NODE, "name of parent znode in zookeeper");
283 addOptWithArg(OPT_TABLE_NAME, "The name of the table to read or write");
284 addOptWithArg(OPT_WRITE, OPT_USAGE_LOAD);
285 addOptWithArg(OPT_READ, OPT_USAGE_READ);
286 addOptWithArg(OPT_UPDATE, OPT_USAGE_UPDATE);
287 addOptNoArg(OPT_INIT_ONLY, "Initialize the test table only, don't do any loading");
288 addOptWithArg(OPT_BLOOM, OPT_USAGE_BLOOM);
289 addOptWithArg(OPT_COMPRESSION, OPT_USAGE_COMPRESSION);
290 addOptWithArg(OPT_DATA_BLOCK_ENCODING, OPT_DATA_BLOCK_ENCODING_USAGE);
291 addOptWithArg(OPT_MAX_READ_ERRORS, "The maximum number of read errors " +
292 "to tolerate before terminating all reader threads. The default is " +
293 MultiThreadedReader.DEFAULT_MAX_ERRORS + ".");
294 addOptWithArg(OPT_KEY_WINDOW, "The 'key window' to maintain between " +
295 "reads and writes for concurrent write/read workload. The default " +
296 "is " + MultiThreadedReader.DEFAULT_KEY_WINDOW + ".");
297
298 addOptNoArg(OPT_MULTIPUT, "Whether to use multi-puts as opposed to " +
299 "separate puts for every column in a row");
300 addOptNoArg(OPT_BATCHUPDATE, "Whether to use batch as opposed to " +
301 "separate updates for every column in a row");
302 addOptNoArg(OPT_INMEMORY, OPT_USAGE_IN_MEMORY);
303 addOptWithArg(OPT_GENERATOR, OPT_GENERATOR_USAGE);
304 addOptWithArg(OPT_READER, OPT_READER_USAGE);
305
306 addOptWithArg(OPT_NUM_KEYS, "The number of keys to read/write");
307 addOptWithArg(OPT_START_KEY, "The first key to read/write " +
308 "(a 0-based index). The default value is " +
309 DEFAULT_START_KEY + ".");
310 addOptNoArg(OPT_SKIP_INIT, "Skip the initialization; assume test table "
311 + "already exists");
312
313 addOptWithArg(NUM_TABLES,
314 "A positive integer number. When a number n is speicfied, load test "
315 + "tool will load n table parallely. -tn parameter value becomes "
316 + "table name prefix. Each table name is in format <tn>_1...<tn>_n");
317
318 addOptWithArg(OPT_REGIONS_PER_SERVER,
319 "A positive integer number. When a number n is specified, load test "
320 + "tool will create the test table with n regions per server");
321
322 addOptWithArg(OPT_ENCRYPTION, OPT_ENCRYPTION_USAGE);
323 addOptNoArg(OPT_DEFERRED_LOG_FLUSH, OPT_DEFERRED_LOG_FLUSH_USAGE);
324 addOptWithArg(OPT_NUM_REGIONS_PER_SERVER, OPT_NUM_REGIONS_PER_SERVER_USAGE);
325 }
326
327 @Override
328 protected void processOptions(CommandLine cmd) {
329 this.cmd = cmd;
330
331 tableName = TableName.valueOf(cmd.getOptionValue(OPT_TABLE_NAME,
332 DEFAULT_TABLE_NAME));
333
334 isWrite = cmd.hasOption(OPT_WRITE);
335 isRead = cmd.hasOption(OPT_READ);
336 isUpdate = cmd.hasOption(OPT_UPDATE);
337 isInitOnly = cmd.hasOption(OPT_INIT_ONLY);
338 deferredLogFlush = cmd.hasOption(OPT_DEFERRED_LOG_FLUSH);
339
340 if (!isWrite && !isRead && !isUpdate && !isInitOnly) {
341 throw new IllegalArgumentException("Either -" + OPT_WRITE + " or " +
342 "-" + OPT_UPDATE + "-" + OPT_READ + " has to be specified");
343 }
344
345 if (isInitOnly && (isRead || isWrite || isUpdate)) {
346 throw new IllegalArgumentException(OPT_INIT_ONLY + " cannot be specified with"
347 + " either -" + OPT_WRITE + " or -" + OPT_UPDATE + " or -" + OPT_READ);
348 }
349
350 if (!isInitOnly) {
351 if (!cmd.hasOption(OPT_NUM_KEYS)) {
352 throw new IllegalArgumentException(OPT_NUM_KEYS + " must be specified in "
353 + "read or write mode");
354 }
355 startKey = parseLong(cmd.getOptionValue(OPT_START_KEY,
356 String.valueOf(DEFAULT_START_KEY)), 0, Long.MAX_VALUE);
357 long numKeys = parseLong(cmd.getOptionValue(OPT_NUM_KEYS), 1,
358 Long.MAX_VALUE - startKey);
359 endKey = startKey + numKeys;
360 isSkipInit = cmd.hasOption(OPT_SKIP_INIT);
361 System.out.println("Key range: [" + startKey + ".." + (endKey - 1) + "]");
362 }
363
364 parseColumnFamilyOptions(cmd);
365
366 if (isWrite) {
367 String[] writeOpts = splitColonSeparated(OPT_WRITE, 2, 3);
368
369 int colIndex = 0;
370 minColsPerKey = 1;
371 maxColsPerKey = 2 * Integer.parseInt(writeOpts[colIndex++]);
372 int avgColDataSize =
373 parseInt(writeOpts[colIndex++], 1, Integer.MAX_VALUE);
374 minColDataSize = avgColDataSize / 2;
375 maxColDataSize = avgColDataSize * 3 / 2;
376
377 if (colIndex < writeOpts.length) {
378 numWriterThreads = getNumThreads(writeOpts[colIndex++]);
379 }
380
381 isMultiPut = cmd.hasOption(OPT_MULTIPUT);
382
383 System.out.println("Multi-puts: " + isMultiPut);
384 System.out.println("Columns per key: " + minColsPerKey + ".."
385 + maxColsPerKey);
386 System.out.println("Data size per column: " + minColDataSize + ".."
387 + maxColDataSize);
388 }
389
390 if (isUpdate) {
391 String[] mutateOpts = splitColonSeparated(OPT_UPDATE, 1, 3);
392 int colIndex = 0;
393 updatePercent = parseInt(mutateOpts[colIndex++], 0, 100);
394 if (colIndex < mutateOpts.length) {
395 numUpdaterThreads = getNumThreads(mutateOpts[colIndex++]);
396 }
397 if (colIndex < mutateOpts.length) {
398 ignoreConflicts = parseInt(mutateOpts[colIndex++], 0, 1) == 1;
399 }
400
401 isBatchUpdate = cmd.hasOption(OPT_BATCHUPDATE);
402
403 System.out.println("Batch updates: " + isBatchUpdate);
404 System.out.println("Percent of keys to update: " + updatePercent);
405 System.out.println("Updater threads: " + numUpdaterThreads);
406 System.out.println("Ignore nonce conflicts: " + ignoreConflicts);
407 }
408
409 if (isRead) {
410 String[] readOpts = splitColonSeparated(OPT_READ, 1, 2);
411 int colIndex = 0;
412 verifyPercent = parseInt(readOpts[colIndex++], 0, 100);
413 if (colIndex < readOpts.length) {
414 numReaderThreads = getNumThreads(readOpts[colIndex++]);
415 }
416
417 if (cmd.hasOption(OPT_MAX_READ_ERRORS)) {
418 maxReadErrors = parseInt(cmd.getOptionValue(OPT_MAX_READ_ERRORS),
419 0, Integer.MAX_VALUE);
420 }
421
422 if (cmd.hasOption(OPT_KEY_WINDOW)) {
423 keyWindow = parseInt(cmd.getOptionValue(OPT_KEY_WINDOW),
424 0, Integer.MAX_VALUE);
425 }
426
427 System.out.println("Percent of keys to verify: " + verifyPercent);
428 System.out.println("Reader threads: " + numReaderThreads);
429 }
430
431 numTables = 1;
432 if (cmd.hasOption(NUM_TABLES)) {
433 numTables = parseInt(cmd.getOptionValue(NUM_TABLES), 1, Short.MAX_VALUE);
434 }
435
436 regionsPerServer = DEFAULT_NUM_REGIONS_PER_SERVER;
437 if (cmd.hasOption(OPT_NUM_REGIONS_PER_SERVER)) {
438 regionsPerServer = Integer.parseInt(cmd.getOptionValue(OPT_NUM_REGIONS_PER_SERVER));
439 }
440 }
441
442 private void parseColumnFamilyOptions(CommandLine cmd) {
443 String dataBlockEncodingStr = cmd.getOptionValue(OPT_DATA_BLOCK_ENCODING);
444 dataBlockEncodingAlgo = dataBlockEncodingStr == null ? null :
445 DataBlockEncoding.valueOf(dataBlockEncodingStr);
446
447 String compressStr = cmd.getOptionValue(OPT_COMPRESSION);
448 compressAlgo = compressStr == null ? Compression.Algorithm.NONE :
449 Compression.Algorithm.valueOf(compressStr);
450
451 String bloomStr = cmd.getOptionValue(OPT_BLOOM);
452 bloomType = bloomStr == null ? BloomType.ROW :
453 BloomType.valueOf(bloomStr);
454
455 inMemoryCF = cmd.hasOption(OPT_INMEMORY);
456 if (cmd.hasOption(OPT_ENCRYPTION)) {
457 cipher = Encryption.getCipher(conf, cmd.getOptionValue(OPT_ENCRYPTION));
458 }
459
460 }
461
462 public void initTestTable() throws IOException {
463 Durability durability = Durability.USE_DEFAULT;
464 if (deferredLogFlush) {
465 durability = Durability.ASYNC_WAL;
466 }
467
468 HBaseTestingUtility.createPreSplitLoadTestTable(conf, tableName,
469 COLUMN_FAMILY, compressAlgo, dataBlockEncodingAlgo, regionsPerServer,
470 durability);
471 applyColumnFamilyOptions(tableName, COLUMN_FAMILIES);
472 }
473
474 @Override
475 protected int doWork() throws IOException {
476 if (numTables > 1) {
477 return parallelLoadTables();
478 } else {
479 return loadTable();
480 }
481 }
482
483 protected int loadTable() throws IOException {
484 if (cmd.hasOption(OPT_ZK_QUORUM)) {
485 conf.set(HConstants.ZOOKEEPER_QUORUM, cmd.getOptionValue(OPT_ZK_QUORUM));
486 }
487 if (cmd.hasOption(OPT_ZK_PARENT_NODE)) {
488 conf.set(HConstants.ZOOKEEPER_ZNODE_PARENT, cmd.getOptionValue(OPT_ZK_PARENT_NODE));
489 }
490
491 if (isInitOnly) {
492 LOG.info("Initializing only; no reads or writes");
493 initTestTable();
494 return 0;
495 }
496
497 if (!isSkipInit) {
498 initTestTable();
499 }
500 LoadTestDataGenerator dataGen = null;
501 if (cmd.hasOption(OPT_GENERATOR)) {
502 String[] clazzAndArgs = cmd.getOptionValue(OPT_GENERATOR).split(COLON);
503 dataGen = getLoadGeneratorInstance(clazzAndArgs[0]);
504 String[] args;
505 if (dataGen instanceof LoadTestDataGeneratorWithACL) {
506 LOG.info("Using LoadTestDataGeneratorWithACL");
507 if (User.isHBaseSecurityEnabled(conf)) {
508 LOG.info("Security is enabled");
509 authnFileName = clazzAndArgs[1];
510 superUser = clazzAndArgs[2];
511 userNames = clazzAndArgs[3];
512 args = Arrays.copyOfRange(clazzAndArgs, 2, clazzAndArgs.length);
513 Properties authConfig = new Properties();
514 authConfig.load(this.getClass().getClassLoader().getResourceAsStream(authnFileName));
515 try {
516 addAuthInfoToConf(authConfig, conf, superUser, userNames);
517 } catch (IOException exp) {
518 LOG.error(exp);
519 return EXIT_FAILURE;
520 }
521 userOwner = User.create(loginAndReturnUGI(conf, superUser));
522 } else {
523 superUser = clazzAndArgs[1];
524 userNames = clazzAndArgs[2];
525 args = Arrays.copyOfRange(clazzAndArgs, 1, clazzAndArgs.length);
526 userOwner = User.createUserForTesting(conf, superUser, new String[0]);
527 }
528 } else {
529 args = clazzAndArgs.length == 1 ? new String[0] : Arrays.copyOfRange(clazzAndArgs, 1,
530 clazzAndArgs.length);
531 }
532 dataGen.initialize(args);
533 } else {
534
535 dataGen = new MultiThreadedAction.DefaultDataGenerator(minColDataSize, maxColDataSize,
536 minColsPerKey, maxColsPerKey, COLUMN_FAMILY);
537 }
538
539 if (userOwner != null) {
540 LOG.info("Granting permissions for user " + userOwner.getShortName());
541 Permission.Action[] actions = {
542 Permission.Action.ADMIN, Permission.Action.CREATE,
543 Permission.Action.READ, Permission.Action.WRITE };
544 try {
545 AccessControlClient.grant(conf, tableName, userOwner.getShortName(), null, null, actions);
546 } catch (Throwable e) {
547 LOG.fatal("Error in granting permission for the user " + userOwner.getShortName(), e);
548 return EXIT_FAILURE;
549 }
550 }
551 if (userNames != null) {
552
553 String users[] = userNames.split(",");
554 User user = null;
555 for (String userStr : users) {
556 if (User.isHBaseSecurityEnabled(conf)) {
557 user = User.create(loginAndReturnUGI(conf, userStr));
558 } else {
559 user = User.createUserForTesting(conf, userStr, new String[0]);
560 }
561 }
562 }
563
564 if (isWrite) {
565 if (userOwner != null) {
566 writerThreads = new MultiThreadedWriterWithACL(dataGen, conf, tableName, userOwner);
567 } else {
568 writerThreads = new MultiThreadedWriter(dataGen, conf, tableName);
569 }
570 writerThreads.setMultiPut(isMultiPut);
571 }
572
573 if (isUpdate) {
574 if (userOwner != null) {
575 updaterThreads = new MultiThreadedUpdaterWithACL(dataGen, conf, tableName, updatePercent,
576 userOwner, userNames);
577 } else {
578 updaterThreads = new MultiThreadedUpdater(dataGen, conf, tableName, updatePercent);
579 }
580 updaterThreads.setBatchUpdate(isBatchUpdate);
581 updaterThreads.setIgnoreNonceConflicts(ignoreConflicts);
582 }
583
584 if (isRead) {
585 if (userOwner != null) {
586 readerThreads = new MultiThreadedReaderWithACL(dataGen, conf, tableName, verifyPercent,
587 userNames);
588 } else {
589 String readerClass = null;
590 if (cmd.hasOption(OPT_READER)) {
591 readerClass = cmd.getOptionValue(OPT_READER);
592 } else {
593 readerClass = MultiThreadedReader.class.getCanonicalName();
594 }
595 readerThreads = getMultiThreadedReaderInstance(readerClass, dataGen);
596 }
597 readerThreads.setMaxErrors(maxReadErrors);
598 readerThreads.setKeyWindow(keyWindow);
599 }
600
601 if (isUpdate && isWrite) {
602 LOG.info("Concurrent write/update workload: making updaters aware of the " +
603 "write point");
604 updaterThreads.linkToWriter(writerThreads);
605 }
606
607 if (isRead && (isUpdate || isWrite)) {
608 LOG.info("Concurrent write/read workload: making readers aware of the " +
609 "write point");
610 readerThreads.linkToWriter(isUpdate ? updaterThreads : writerThreads);
611 }
612
613 if (isWrite) {
614 System.out.println("Starting to write data...");
615 writerThreads.start(startKey, endKey, numWriterThreads);
616 }
617
618 if (isUpdate) {
619 LOG.info("Starting to mutate data...");
620 System.out.println("Starting to mutate data...");
621
622
623 updaterThreads.start(startKey, endKey, numUpdaterThreads);
624 }
625
626 if (isRead) {
627 System.out.println("Starting to read data...");
628 readerThreads.start(startKey, endKey, numReaderThreads);
629 }
630
631 if (isWrite) {
632 writerThreads.waitForFinish();
633 }
634
635 if (isUpdate) {
636 updaterThreads.waitForFinish();
637 }
638
639 if (isRead) {
640 readerThreads.waitForFinish();
641 }
642
643 boolean success = true;
644 if (isWrite) {
645 success = success && writerThreads.getNumWriteFailures() == 0;
646 }
647 if (isUpdate) {
648 success = success && updaterThreads.getNumWriteFailures() == 0;
649 }
650 if (isRead) {
651 success = success && readerThreads.getNumReadErrors() == 0
652 && readerThreads.getNumReadFailures() == 0;
653 }
654 return success ? EXIT_SUCCESS : EXIT_FAILURE;
655 }
656
657 private LoadTestDataGenerator getLoadGeneratorInstance(String clazzName) throws IOException {
658 try {
659 Class<?> clazz = Class.forName(clazzName);
660 Constructor<?> constructor = clazz.getConstructor(int.class, int.class, int.class, int.class,
661 byte[][].class);
662 return (LoadTestDataGenerator) constructor.newInstance(minColDataSize, maxColDataSize,
663 minColsPerKey, maxColsPerKey, COLUMN_FAMILIES);
664 } catch (Exception e) {
665 throw new IOException(e);
666 }
667 }
668
669 private MultiThreadedReader getMultiThreadedReaderInstance(String clazzName
670 , LoadTestDataGenerator dataGen) throws IOException {
671 try {
672 Class<?> clazz = Class.forName(clazzName);
673 Constructor<?> constructor = clazz.getConstructor(
674 LoadTestDataGenerator.class, Configuration.class, TableName.class, double.class);
675 return (MultiThreadedReader) constructor.newInstance(dataGen, conf, tableName, verifyPercent);
676 } catch (Exception e) {
677 throw new IOException(e);
678 }
679 }
680
681 public static byte[] generateData(final Random r, int length) {
682 byte [] b = new byte [length];
683 int i = 0;
684
685 for(i = 0; i < (length-8); i += 8) {
686 b[i] = (byte) (65 + r.nextInt(26));
687 b[i+1] = b[i];
688 b[i+2] = b[i];
689 b[i+3] = b[i];
690 b[i+4] = b[i];
691 b[i+5] = b[i];
692 b[i+6] = b[i];
693 b[i+7] = b[i];
694 }
695
696 byte a = (byte) (65 + r.nextInt(26));
697 for(; i < length; i++) {
698 b[i] = a;
699 }
700 return b;
701 }
702 public static void main(String[] args) {
703 new LoadTestTool().doStaticMain(args);
704 }
705
706
707
708
709
710
711
712
713
714 private int parallelLoadTables()
715 throws IOException {
716
717 String tableName = cmd.getOptionValue(OPT_TABLE_NAME, DEFAULT_TABLE_NAME);
718 String[] newArgs = null;
719 if (!cmd.hasOption(LoadTestTool.OPT_TABLE_NAME)) {
720 newArgs = new String[cmdLineArgs.length + 2];
721 newArgs[0] = "-" + LoadTestTool.OPT_TABLE_NAME;
722 newArgs[1] = LoadTestTool.DEFAULT_TABLE_NAME;
723 System.arraycopy(cmdLineArgs, 0, newArgs, 2, cmdLineArgs.length);
724 } else {
725 newArgs = cmdLineArgs;
726 }
727
728 int tableNameValueIndex = -1;
729 for (int j = 0; j < newArgs.length; j++) {
730 if (newArgs[j].endsWith(OPT_TABLE_NAME)) {
731 tableNameValueIndex = j + 1;
732 } else if (newArgs[j].endsWith(NUM_TABLES)) {
733
734 newArgs[j + 1] = "1";
735 }
736 }
737
738
739 List<WorkerThread> workers = new ArrayList<WorkerThread>();
740 for (int i = 0; i < numTables; i++) {
741 String[] workerArgs = newArgs.clone();
742 workerArgs[tableNameValueIndex] = tableName + "_" + (i+1);
743 WorkerThread worker = new WorkerThread(i, workerArgs);
744 workers.add(worker);
745 LOG.info(worker + " starting");
746 worker.start();
747 }
748
749
750 LOG.info("Waiting for worker threads to finish");
751 for (WorkerThread t : workers) {
752 try {
753 t.join();
754 } catch (InterruptedException ie) {
755 IOException iie = new InterruptedIOException();
756 iie.initCause(ie);
757 throw iie;
758 }
759 checkForErrors();
760 }
761
762 return EXIT_SUCCESS;
763 }
764
765
766
767 protected AtomicReference<Throwable> thrown = new AtomicReference<Throwable>();
768
769 private void workerThreadError(Throwable t) {
770 thrown.compareAndSet(null, t);
771 }
772
773
774
775
776 private void checkForErrors() throws IOException {
777 Throwable thrown = this.thrown.get();
778 if (thrown == null) return;
779 if (thrown instanceof IOException) {
780 throw (IOException) thrown;
781 } else {
782 throw new RuntimeException(thrown);
783 }
784 }
785
786 class WorkerThread extends Thread {
787 private String[] workerArgs;
788
789 WorkerThread(int i, String[] args) {
790 super("WorkerThread-" + i);
791 workerArgs = args;
792 }
793
794 @Override
795 public void run() {
796 try {
797 int ret = ToolRunner.run(HBaseConfiguration.create(), new LoadTestTool(), workerArgs);
798 if (ret != 0) {
799 throw new RuntimeException("LoadTestTool exit with non-zero return code.");
800 }
801 } catch (Exception ex) {
802 LOG.error("Error in worker thread", ex);
803 workerThreadError(ex);
804 }
805 }
806 }
807
808 private void addAuthInfoToConf(Properties authConfig, Configuration conf, String owner,
809 String userList) throws IOException {
810 List<String> users = Arrays.asList(userList.split(","));
811 users.add(owner);
812 for (String user : users) {
813 String keyTabFileConfKey = "hbase." + user + ".keytab.file";
814 String principalConfKey = "hbase." + user + ".kerberos.principal";
815 if (!authConfig.containsKey(keyTabFileConfKey) || !authConfig.containsKey(principalConfKey)) {
816 throw new IOException("Authentication configs missing for user : " + user);
817 }
818 }
819 for (String key : authConfig.stringPropertyNames()) {
820 conf.set(key, authConfig.getProperty(key));
821 }
822 LOG.debug("Added authentication properties to config successfully.");
823 }
824
825 public static UserGroupInformation loginAndReturnUGI(Configuration conf, String username)
826 throws IOException {
827 String hostname = InetAddress.getLocalHost().getHostName();
828 String keyTabFileConfKey = "hbase." + username + ".keytab.file";
829 String keyTabFileLocation = conf.get(keyTabFileConfKey);
830 String principalConfKey = "hbase." + username + ".kerberos.principal";
831 String principal = SecurityUtil.getServerPrincipal(conf.get(principalConfKey), hostname);
832 if (keyTabFileLocation == null || principal == null) {
833 LOG.warn("Principal or key tab file null for : " + principalConfKey + ", "
834 + keyTabFileConfKey);
835 }
836 UserGroupInformation ugi =
837 UserGroupInformation.loginUserFromKeytabAndReturnUGI(principal, keyTabFileLocation);
838 return ugi;
839 }
840 }