1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.hadoop.hbase.tool;
21
22 import java.io.Closeable;
23 import java.io.IOException;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.TreeSet;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33
34 import org.apache.commons.lang.time.StopWatch;
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
37 import org.apache.hadoop.conf.Configuration;
38 import org.apache.hadoop.hbase.AuthUtil;
39 import org.apache.hadoop.hbase.ChoreService;
40 import org.apache.hadoop.hbase.DoNotRetryIOException;
41 import org.apache.hadoop.hbase.HBaseConfiguration;
42 import org.apache.hadoop.hbase.HColumnDescriptor;
43 import org.apache.hadoop.hbase.HRegionInfo;
44 import org.apache.hadoop.hbase.HRegionLocation;
45 import org.apache.hadoop.hbase.HTableDescriptor;
46 import org.apache.hadoop.hbase.ScheduledChore;
47 import org.apache.hadoop.hbase.ServerName;
48 import org.apache.hadoop.hbase.TableName;
49 import org.apache.hadoop.hbase.TableNotEnabledException;
50 import org.apache.hadoop.hbase.TableNotFoundException;
51 import org.apache.hadoop.hbase.client.Admin;
52 import org.apache.hadoop.hbase.client.Connection;
53 import org.apache.hadoop.hbase.client.ConnectionFactory;
54 import org.apache.hadoop.hbase.client.Get;
55 import org.apache.hadoop.hbase.client.RegionLocator;
56 import org.apache.hadoop.hbase.client.ResultScanner;
57 import org.apache.hadoop.hbase.client.Scan;
58 import org.apache.hadoop.hbase.client.Table;
59 import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
60 import org.apache.hadoop.util.Tool;
61 import org.apache.hadoop.util.ToolRunner;
62
63
64
65
66
67
68
69
70
71
72
73
74 public final class Canary implements Tool {
75
76 public interface Sink {
77 public void publishReadFailure(HRegionInfo region, Exception e);
78 public void publishReadFailure(HRegionInfo region, HColumnDescriptor column, Exception e);
79 public void publishReadTiming(HRegionInfo region, HColumnDescriptor column, long msTime);
80 }
81
82
83 public interface ExtendedSink extends Sink {
84 public void publishReadFailure(String table, String server);
85 public void publishReadTiming(String table, String server, long msTime);
86 }
87
88
89
90 public static class StdOutSink implements Sink {
91 @Override
92 public void publishReadFailure(HRegionInfo region, Exception e) {
93 LOG.error(String.format("read from region %s failed", region.getRegionNameAsString()), e);
94 }
95
96 @Override
97 public void publishReadFailure(HRegionInfo region, HColumnDescriptor column, Exception e) {
98 LOG.error(String.format("read from region %s column family %s failed",
99 region.getRegionNameAsString(), column.getNameAsString()), e);
100 }
101
102 @Override
103 public void publishReadTiming(HRegionInfo region, HColumnDescriptor column, long msTime) {
104 LOG.info(String.format("read from region %s column family %s in %dms",
105 region.getRegionNameAsString(), column.getNameAsString(), msTime));
106 }
107 }
108
109 public static class RegionServerStdOutSink extends StdOutSink implements ExtendedSink {
110
111 @Override
112 public void publishReadFailure(String table, String server) {
113 LOG.error(String.format("Read from table:%s on region server:%s", table, server));
114 }
115
116 @Override
117 public void publishReadTiming(String table, String server, long msTime) {
118 LOG.info(String.format("Read from table:%s on region server:%s in %dms",
119 table, server, msTime));
120 }
121 }
122
123 private static final int USAGE_EXIT_CODE = 1;
124 private static final int INIT_ERROR_EXIT_CODE = 2;
125 private static final int TIMEOUT_ERROR_EXIT_CODE = 3;
126 private static final int ERROR_EXIT_CODE = 4;
127
128 private static final long DEFAULT_INTERVAL = 6000;
129
130 private static final long DEFAULT_TIMEOUT = 600000;
131
132 private static final Log LOG = LogFactory.getLog(Canary.class);
133
134 private Configuration conf = null;
135 private long interval = 0;
136 private Sink sink = null;
137
138 private boolean useRegExp;
139 private long timeout = DEFAULT_TIMEOUT;
140 private boolean failOnError = true;
141 private boolean regionServerMode = false;
142
143 public Canary() {
144 this(new RegionServerStdOutSink());
145 }
146
147 public Canary(Sink sink) {
148 this.sink = sink;
149 }
150
151 @Override
152 public Configuration getConf() {
153 return conf;
154 }
155
156 @Override
157 public void setConf(Configuration conf) {
158 this.conf = conf;
159 }
160
161 @Override
162 public int run(String[] args) throws Exception {
163 int index = -1;
164 ChoreService choreService = null;
165
166
167 for (int i = 0; i < args.length; i++) {
168 String cmd = args[i];
169
170 if (cmd.startsWith("-")) {
171 if (index >= 0) {
172
173 System.err.println("Invalid command line options");
174 printUsageAndExit();
175 }
176
177 if (cmd.equals("-help")) {
178
179 printUsageAndExit();
180 } else if (cmd.equals("-daemon") && interval == 0) {
181
182 interval = DEFAULT_INTERVAL;
183 } else if (cmd.equals("-interval")) {
184
185 i++;
186
187 if (i == args.length) {
188 System.err.println("-interval needs a numeric value argument.");
189 printUsageAndExit();
190 }
191
192 try {
193 interval = Long.parseLong(args[i]) * 1000;
194 } catch (NumberFormatException e) {
195 System.err.println("-interval needs a numeric value argument.");
196 printUsageAndExit();
197 }
198 } else if(cmd.equals("-regionserver")) {
199 this.regionServerMode = true;
200 } else if (cmd.equals("-e")) {
201 this.useRegExp = true;
202 } else if (cmd.equals("-t")) {
203 i++;
204
205 if (i == args.length) {
206 System.err.println("-t needs a numeric value argument.");
207 printUsageAndExit();
208 }
209
210 try {
211 this.timeout = Long.parseLong(args[i]);
212 } catch (NumberFormatException e) {
213 System.err.println("-t needs a numeric value argument.");
214 printUsageAndExit();
215 }
216
217 } else if (cmd.equals("-f")) {
218 i++;
219
220 if (i == args.length) {
221 System.err
222 .println("-f needs a boolean value argument (true|false).");
223 printUsageAndExit();
224 }
225
226 this.failOnError = Boolean.parseBoolean(args[i]);
227 } else {
228
229 System.err.println(cmd + " options is invalid.");
230 printUsageAndExit();
231 }
232 } else if (index < 0) {
233
234 index = i;
235 }
236 }
237
238
239
240
241 final ScheduledChore authChore = AuthUtil.getAuthChore(conf);
242 if (authChore != null) {
243 choreService = new ChoreService("CANARY_TOOL");
244 choreService.scheduleChore(authChore);
245 }
246
247
248 Monitor monitor = null;
249 Thread monitorThread = null;
250 long startTime = 0;
251 long currentTimeLength = 0;
252
253 try (Connection connection = ConnectionFactory.createConnection(this.conf)) {
254 do {
255
256 try {
257 monitor = this.newMonitor(connection, index, args);
258 monitorThread = new Thread(monitor);
259 startTime = System.currentTimeMillis();
260 monitorThread.start();
261 while (!monitor.isDone()) {
262
263 Thread.sleep(1000);
264
265 if (this.failOnError && monitor.hasError()) {
266 monitorThread.interrupt();
267 if (monitor.initialized) {
268 System.exit(monitor.errorCode);
269 } else {
270 System.exit(INIT_ERROR_EXIT_CODE);
271 }
272 }
273 currentTimeLength = System.currentTimeMillis() - startTime;
274 if (currentTimeLength > this.timeout) {
275 LOG.error("The monitor is running too long (" + currentTimeLength
276 + ") after timeout limit:" + this.timeout
277 + " will be killed itself !!");
278 if (monitor.initialized) {
279 System.exit(TIMEOUT_ERROR_EXIT_CODE);
280 } else {
281 System.exit(INIT_ERROR_EXIT_CODE);
282 }
283 break;
284 }
285 }
286
287 if (this.failOnError && monitor.hasError()) {
288 monitorThread.interrupt();
289 System.exit(monitor.errorCode);
290 }
291 } finally {
292 if (monitor != null) monitor.close();
293 }
294
295 Thread.sleep(interval);
296 } while (interval > 0);
297 }
298
299 if (choreService != null) {
300 choreService.shutdown();
301 }
302 return(monitor.errorCode);
303 }
304
305 private void printUsageAndExit() {
306 System.err.printf(
307 "Usage: bin/hbase %s [opts] [table1 [table2]...] | [regionserver1 [regionserver2]..]%n",
308 getClass().getName());
309 System.err.println(" where [opts] are:");
310 System.err.println(" -help Show this help and exit.");
311 System.err.println(" -regionserver replace the table argument to regionserver,");
312 System.err.println(" which means to enable regionserver mode");
313 System.err.println(" -daemon Continuous check at defined intervals.");
314 System.err.println(" -interval <N> Interval between checks (sec)");
315 System.err.println(" -e Use table/regionserver as regular expression");
316 System.err.println(" which means the table/regionserver is regular expression pattern");
317 System.err.println(" -f <B> stop whole program if first error occurs," +
318 " default is true");
319 System.err.println(" -t <N> timeout for a check, default is 600000 (milisecs)");
320 System.exit(USAGE_EXIT_CODE);
321 }
322
323
324
325
326
327
328
329
330 public Monitor newMonitor(final Connection connection, int index, String[] args) {
331 Monitor monitor = null;
332 String[] monitorTargets = null;
333
334 if(index >= 0) {
335 int length = args.length - index;
336 monitorTargets = new String[length];
337 System.arraycopy(args, index, monitorTargets, 0, length);
338 }
339
340 if(this.regionServerMode) {
341 monitor = new RegionServerMonitor(
342 connection,
343 monitorTargets,
344 this.useRegExp,
345 (ExtendedSink)this.sink);
346 } else {
347 monitor = new RegionMonitor(connection, monitorTargets, this.useRegExp, this.sink);
348 }
349 return monitor;
350 }
351
352
353 public static abstract class Monitor implements Runnable, Closeable {
354
355 protected Connection connection;
356 protected Admin admin;
357 protected String[] targets;
358 protected boolean useRegExp;
359 protected boolean initialized = false;
360
361 protected boolean done = false;
362 protected int errorCode = 0;
363 protected Sink sink;
364
365 public boolean isDone() {
366 return done;
367 }
368
369 public boolean hasError() {
370 return errorCode != 0;
371 }
372
373 @Override
374 public void close() throws IOException {
375 if (this.admin != null) this.admin.close();
376 }
377
378 protected Monitor(Connection connection, String[] monitorTargets,
379 boolean useRegExp, Sink sink) {
380 if (null == connection) throw new IllegalArgumentException("connection shall not be null");
381
382 this.connection = connection;
383 this.targets = monitorTargets;
384 this.useRegExp = useRegExp;
385 this.sink = sink;
386 }
387
388 @Override
389 public abstract void run();
390
391 protected boolean initAdmin() {
392 if (null == this.admin) {
393 try {
394 this.admin = this.connection.getAdmin();
395 } catch (Exception e) {
396 LOG.error("Initial HBaseAdmin failed...", e);
397 this.errorCode = INIT_ERROR_EXIT_CODE;
398 }
399 } else if (admin.isAborted()) {
400 LOG.error("HBaseAdmin aborted");
401 this.errorCode = INIT_ERROR_EXIT_CODE;
402 }
403 return !this.hasError();
404 }
405 }
406
407
408 private static class RegionMonitor extends Monitor {
409
410 public RegionMonitor(Connection connection, String[] monitorTargets,
411 boolean useRegExp, Sink sink) {
412 super(connection, monitorTargets, useRegExp, sink);
413 }
414
415 @Override
416 public void run() {
417 if(this.initAdmin()) {
418 try {
419 if (this.targets != null && this.targets.length > 0) {
420 String[] tables = generateMonitorTables(this.targets);
421 this.initialized = true;
422 for (String table : tables) {
423 Canary.sniff(admin, sink, table);
424 }
425 } else {
426 sniff();
427 }
428 } catch (Exception e) {
429 LOG.error("Run regionMonitor failed", e);
430 this.errorCode = ERROR_EXIT_CODE;
431 }
432 }
433 this.done = true;
434 }
435
436 private String[] generateMonitorTables(String[] monitorTargets) throws IOException {
437 String[] returnTables = null;
438
439 if(this.useRegExp) {
440 Pattern pattern = null;
441 HTableDescriptor[] tds = null;
442 Set<String> tmpTables = new TreeSet<String>();
443 try {
444 if (LOG.isDebugEnabled()) {
445 LOG.debug(String.format("reading list of tables"));
446 }
447 tds = this.admin.listTables(pattern);
448 if (tds == null) {
449 tds = new HTableDescriptor[0];
450 }
451 for (String monitorTarget : monitorTargets) {
452 pattern = Pattern.compile(monitorTarget);
453 for (HTableDescriptor td : tds) {
454 if (pattern.matcher(td.getNameAsString()).matches()) {
455 tmpTables.add(td.getNameAsString());
456 }
457 }
458 }
459 } catch(IOException e) {
460 LOG.error("Communicate with admin failed", e);
461 throw e;
462 }
463
464 if(tmpTables.size() > 0) {
465 returnTables = tmpTables.toArray(new String[tmpTables.size()]);
466 } else {
467 String msg = "No HTable found, tablePattern:"
468 + Arrays.toString(monitorTargets);
469 LOG.error(msg);
470 this.errorCode = INIT_ERROR_EXIT_CODE;
471 throw new TableNotFoundException(msg);
472 }
473 } else {
474 returnTables = monitorTargets;
475 }
476
477 return returnTables;
478 }
479
480
481
482
483 private void sniff() throws Exception {
484 if (LOG.isDebugEnabled()) {
485 LOG.debug(String.format("reading list of tables"));
486 }
487 for (HTableDescriptor table : admin.listTables()) {
488 Canary.sniff(admin, sink, table);
489 }
490 }
491 }
492
493
494
495
496
497 public static void sniff(final Admin admin, TableName tableName) throws Exception {
498 sniff(admin, new StdOutSink(), tableName.getNameAsString());
499 }
500
501
502
503
504
505 private static void sniff(final Admin admin, final Sink sink, String tableName)
506 throws Exception {
507 if (LOG.isDebugEnabled()) {
508 LOG.debug(String.format("checking table is enabled and getting table descriptor for table %s",
509 tableName));
510 }
511 if (admin.isTableAvailable(TableName.valueOf(tableName))) {
512 sniff(admin, sink, admin.getTableDescriptor(TableName.valueOf(tableName)));
513 } else {
514 LOG.warn(String.format("Table %s is not available", tableName));
515 }
516 }
517
518
519
520
521 private static void sniff(final Admin admin, final Sink sink, HTableDescriptor tableDesc)
522 throws Exception {
523 if (LOG.isDebugEnabled()) {
524 LOG.debug(String.format("reading list of regions for table %s", tableDesc.getTableName()));
525 }
526
527 Table table = null;
528
529 try {
530 table = admin.getConnection().getTable(tableDesc.getTableName());
531 } catch (TableNotFoundException e) {
532 return;
533 }
534
535 try {
536 for (HRegionInfo region : admin.getTableRegions(tableDesc.getTableName())) {
537 try {
538 sniffRegion(admin, sink, region, table);
539 } catch (Exception e) {
540 sink.publishReadFailure(region, e);
541 LOG.debug("sniffRegion failed", e);
542 }
543 }
544 } finally {
545 table.close();
546 }
547 }
548
549
550
551
552
553 private static void sniffRegion(
554 final Admin admin,
555 final Sink sink,
556 HRegionInfo region,
557 Table table) throws Exception {
558 HTableDescriptor tableDesc = table.getTableDescriptor();
559 byte[] startKey = null;
560 Get get = null;
561 Scan scan = null;
562 ResultScanner rs = null;
563 StopWatch stopWatch = new StopWatch();
564 for (HColumnDescriptor column : tableDesc.getColumnFamilies()) {
565 stopWatch.reset();
566 startKey = region.getStartKey();
567
568 if (startKey.length > 0) {
569 get = new Get(startKey);
570 get.setCacheBlocks(false);
571 get.setFilter(new FirstKeyOnlyFilter());
572 get.addFamily(column.getName());
573 } else {
574 scan = new Scan();
575 scan.setRaw(true);
576 scan.setCaching(1);
577 scan.setCacheBlocks(false);
578 scan.setFilter(new FirstKeyOnlyFilter());
579 scan.addFamily(column.getName());
580 scan.setMaxResultSize(1L);
581 }
582
583 try {
584 if (startKey.length > 0) {
585 stopWatch.start();
586 table.get(get);
587 stopWatch.stop();
588 sink.publishReadTiming(region, column, stopWatch.getTime());
589 } else {
590 stopWatch.start();
591 rs = table.getScanner(scan);
592 stopWatch.stop();
593 sink.publishReadTiming(region, column, stopWatch.getTime());
594 }
595 } catch (Exception e) {
596 sink.publishReadFailure(region, column, e);
597 } finally {
598 if (rs != null) {
599 rs.close();
600 }
601 scan = null;
602 get = null;
603 startKey = null;
604 }
605 }
606 }
607
608 private static class RegionServerMonitor extends Monitor {
609
610 public RegionServerMonitor(Connection connection, String[] monitorTargets,
611 boolean useRegExp, ExtendedSink sink) {
612 super(connection, monitorTargets, useRegExp, sink);
613 }
614
615 private ExtendedSink getSink() {
616 return (ExtendedSink) this.sink;
617 }
618
619 @Override
620 public void run() {
621 if (this.initAdmin() && this.checkNoTableNames()) {
622 Map<String, List<HRegionInfo>> rsAndRMap = this.filterRegionServerByName();
623 this.initialized = true;
624 this.monitorRegionServers(rsAndRMap);
625 }
626 this.done = true;
627 }
628
629 private boolean checkNoTableNames() {
630 List<String> foundTableNames = new ArrayList<String>();
631 TableName[] tableNames = null;
632
633 if (LOG.isDebugEnabled()) {
634 LOG.debug(String.format("reading list of tables"));
635 }
636 try {
637 tableNames = this.admin.listTableNames();
638 } catch (IOException e) {
639 LOG.error("Get listTableNames failed", e);
640 this.errorCode = INIT_ERROR_EXIT_CODE;
641 return false;
642 }
643
644 if (this.targets == null || this.targets.length == 0) return true;
645
646 for (String target : this.targets) {
647 for (TableName tableName : tableNames) {
648 if (target.equals(tableName.getNameAsString())) {
649 foundTableNames.add(target);
650 }
651 }
652 }
653
654 if (foundTableNames.size() > 0) {
655 System.err.println("Cannot pass a tablename when using the -regionserver " +
656 "option, tablenames:" + foundTableNames.toString());
657 this.errorCode = USAGE_EXIT_CODE;
658 }
659 return foundTableNames.size() == 0;
660 }
661
662 private void monitorRegionServers(Map<String, List<HRegionInfo>> rsAndRMap) {
663 String serverName = null;
664 TableName tableName = null;
665 HRegionInfo region = null;
666 Table table = null;
667 Get get = null;
668 byte[] startKey = null;
669 Scan scan = null;
670 StopWatch stopWatch = new StopWatch();
671
672 for (Map.Entry<String, List<HRegionInfo>> entry : rsAndRMap.entrySet()) {
673 stopWatch.reset();
674 serverName = entry.getKey();
675
676 region = entry.getValue().get(0);
677 try {
678 tableName = region.getTable();
679 table = admin.getConnection().getTable(tableName);
680 startKey = region.getStartKey();
681
682 if(startKey.length > 0) {
683 get = new Get(startKey);
684 stopWatch.start();
685 table.get(get);
686 stopWatch.stop();
687 } else {
688 scan = new Scan();
689 scan.setCaching(1);
690 scan.setMaxResultSize(1L);
691 stopWatch.start();
692 ResultScanner s = table.getScanner(scan);
693 s.close();
694 stopWatch.stop();
695 }
696 this.getSink().publishReadTiming(tableName.getNameAsString(),
697 serverName, stopWatch.getTime());
698 } catch (TableNotFoundException tnfe) {
699
700 } catch (TableNotEnabledException tnee) {
701
702 LOG.debug("The targeted table was disabled. Assuming success.");
703 } catch (DoNotRetryIOException dnrioe) {
704 this.getSink().publishReadFailure(tableName.getNameAsString(), serverName);
705 LOG.error(dnrioe);
706 } catch (IOException e) {
707 this.getSink().publishReadFailure(tableName.getNameAsString(), serverName);
708 LOG.error(e);
709 this.errorCode = ERROR_EXIT_CODE;
710 } finally {
711 if (table != null) {
712 try {
713 table.close();
714 } catch (IOException e) {
715 }
716 }
717 scan = null;
718 get = null;
719 startKey = null;
720 }
721 }
722 }
723
724 private Map<String, List<HRegionInfo>> filterRegionServerByName() {
725 Map<String, List<HRegionInfo>> regionServerAndRegionsMap = this.getAllRegionServerByName();
726 regionServerAndRegionsMap = this.doFilterRegionServerByName(regionServerAndRegionsMap);
727 return regionServerAndRegionsMap;
728 }
729
730 private Map<String, List<HRegionInfo>> getAllRegionServerByName() {
731 Map<String, List<HRegionInfo>> rsAndRMap = new HashMap<String, List<HRegionInfo>>();
732 Table table = null;
733 RegionLocator regionLocator = null;
734 try {
735 if (LOG.isDebugEnabled()) {
736 LOG.debug(String.format("reading list of tables and locations"));
737 }
738 HTableDescriptor[] tableDescs = this.admin.listTables();
739 List<HRegionInfo> regions = null;
740 for (HTableDescriptor tableDesc : tableDescs) {
741 table = this.admin.getConnection().getTable(tableDesc.getTableName());
742 regionLocator = this.admin.getConnection().getRegionLocator(tableDesc.getTableName());
743
744 for (HRegionLocation location: regionLocator.getAllRegionLocations()) {
745 ServerName rs = location.getServerName();
746 String rsName = rs.getHostname();
747 HRegionInfo r = location.getRegionInfo();
748
749 if (rsAndRMap.containsKey(rsName)) {
750 regions = rsAndRMap.get(rsName);
751 } else {
752 regions = new ArrayList<HRegionInfo>();
753 rsAndRMap.put(rsName, regions);
754 }
755 regions.add(r);
756 }
757 table.close();
758 }
759
760 } catch (IOException e) {
761 String msg = "Get HTables info failed";
762 LOG.error(msg, e);
763 this.errorCode = INIT_ERROR_EXIT_CODE;
764 } finally {
765 if (table != null) {
766 try {
767 table.close();
768 } catch (IOException e) {
769 LOG.warn("Close table failed", e);
770 }
771 }
772 }
773
774 return rsAndRMap;
775 }
776
777 private Map<String, List<HRegionInfo>> doFilterRegionServerByName(
778 Map<String, List<HRegionInfo>> fullRsAndRMap) {
779
780 Map<String, List<HRegionInfo>> filteredRsAndRMap = null;
781
782 if (this.targets != null && this.targets.length > 0) {
783 filteredRsAndRMap = new HashMap<String, List<HRegionInfo>>();
784 Pattern pattern = null;
785 Matcher matcher = null;
786 boolean regExpFound = false;
787 for (String rsName : this.targets) {
788 if (this.useRegExp) {
789 regExpFound = false;
790 pattern = Pattern.compile(rsName);
791 for (Map.Entry<String,List<HRegionInfo>> entry : fullRsAndRMap.entrySet()) {
792 matcher = pattern.matcher(entry.getKey());
793 if (matcher.matches()) {
794 filteredRsAndRMap.put(entry.getKey(), entry.getValue());
795 regExpFound = true;
796 }
797 }
798 if (!regExpFound) {
799 LOG.info("No RegionServerInfo found, regionServerPattern:" + rsName);
800 }
801 } else {
802 if (fullRsAndRMap.containsKey(rsName)) {
803 filteredRsAndRMap.put(rsName, fullRsAndRMap.get(rsName));
804 } else {
805 LOG.info("No RegionServerInfo found, regionServerName:" + rsName);
806 }
807 }
808 }
809 } else {
810 filteredRsAndRMap = fullRsAndRMap;
811 }
812 return filteredRsAndRMap;
813 }
814 }
815
816 public static void main(String[] args) throws Exception {
817 final Configuration conf = HBaseConfiguration.create();
818 int exitCode = ToolRunner.run(conf, new Canary(), args);
819 System.exit(exitCode);
820 }
821 }