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 static org.apache.hadoop.hbase.HConstants.DEFAULT_ZOOKEEPER_ZNODE_PARENT;
23 import static org.apache.hadoop.hbase.HConstants.ZOOKEEPER_ZNODE_PARENT;
24
25 import com.google.common.collect.Lists;
26
27 import java.io.Closeable;
28 import java.io.IOException;
29 import java.net.InetSocketAddress;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.LinkedList;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Map.Entry;
39 import java.util.Random;
40 import java.util.Set;
41 import java.util.TreeSet;
42 import java.util.concurrent.Callable;
43 import java.util.concurrent.ConcurrentHashMap;
44 import java.util.concurrent.ExecutionException;
45 import java.util.concurrent.ExecutorService;
46 import java.util.concurrent.Future;
47 import java.util.concurrent.ScheduledThreadPoolExecutor;
48 import java.util.concurrent.atomic.AtomicLong;
49 import java.util.regex.Matcher;
50 import java.util.regex.Pattern;
51
52 import org.apache.commons.lang.time.StopWatch;
53 import org.apache.commons.logging.Log;
54 import org.apache.commons.logging.LogFactory;
55 import org.apache.hadoop.conf.Configuration;
56 import org.apache.hadoop.hbase.AuthUtil;
57 import org.apache.hadoop.hbase.ChoreService;
58 import org.apache.hadoop.hbase.ClusterStatus;
59 import org.apache.hadoop.hbase.DoNotRetryIOException;
60 import org.apache.hadoop.hbase.HBaseConfiguration;
61 import org.apache.hadoop.hbase.HColumnDescriptor;
62 import org.apache.hadoop.hbase.HConstants;
63 import org.apache.hadoop.hbase.HRegionInfo;
64 import org.apache.hadoop.hbase.HRegionLocation;
65 import org.apache.hadoop.hbase.HTableDescriptor;
66 import org.apache.hadoop.hbase.NamespaceDescriptor;
67 import org.apache.hadoop.hbase.ScheduledChore;
68 import org.apache.hadoop.hbase.ServerName;
69 import org.apache.hadoop.hbase.TableName;
70 import org.apache.hadoop.hbase.TableNotEnabledException;
71 import org.apache.hadoop.hbase.TableNotFoundException;
72 import org.apache.hadoop.hbase.classification.InterfaceAudience;
73 import org.apache.hadoop.hbase.client.Admin;
74 import org.apache.hadoop.hbase.client.Connection;
75 import org.apache.hadoop.hbase.client.ConnectionFactory;
76 import org.apache.hadoop.hbase.client.Get;
77 import org.apache.hadoop.hbase.client.Put;
78 import org.apache.hadoop.hbase.client.RegionLocator;
79 import org.apache.hadoop.hbase.client.ResultScanner;
80 import org.apache.hadoop.hbase.client.Scan;
81 import org.apache.hadoop.hbase.client.Table;
82 import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
83 import org.apache.hadoop.hbase.tool.Canary.RegionTask.TaskType;
84 import org.apache.hadoop.hbase.util.Bytes;
85 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
86 import org.apache.hadoop.hbase.util.ReflectionUtils;
87 import org.apache.hadoop.hbase.util.RegionSplitter;
88 import org.apache.hadoop.hbase.zookeeper.EmptyWatcher;
89 import org.apache.hadoop.hbase.zookeeper.ZKConfig;
90 import org.apache.hadoop.util.GenericOptionsParser;
91 import org.apache.hadoop.util.Tool;
92 import org.apache.hadoop.util.ToolRunner;
93 import org.apache.zookeeper.KeeperException;
94 import org.apache.zookeeper.ZooKeeper;
95 import org.apache.zookeeper.client.ConnectStringParser;
96 import org.apache.zookeeper.data.Stat;
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112 @InterfaceAudience.Private
113 public final class Canary implements Tool {
114
115 public interface Sink {
116 public long getReadFailureCount();
117 public long incReadFailureCount();
118 public Map<String,String> getReadFailures();
119 public void updateReadFailures(String regionName, String serverName);
120 public long getWriteFailureCount();
121 public long incWriteFailureCount();
122 public Map<String,String> getWriteFailures();
123 public void updateWriteFailures(String regionName, String serverName);
124 }
125
126
127
128 public static class StdOutSink implements Sink {
129 private AtomicLong readFailureCount = new AtomicLong(0),
130 writeFailureCount = new AtomicLong(0);
131
132 private Map<String, String> readFailures = new ConcurrentHashMap<String, String>();
133 private Map<String, String> writeFailures = new ConcurrentHashMap<String, String>();
134
135 @Override
136 public long getReadFailureCount() {
137 return readFailureCount.get();
138 }
139
140 @Override
141 public long incReadFailureCount() {
142 return readFailureCount.incrementAndGet();
143 }
144
145 @Override
146 public Map<String, String> getReadFailures() {
147 return readFailures;
148 }
149
150 @Override
151 public void updateReadFailures(String regionName, String serverName) {
152 readFailures.put(regionName, serverName);
153 }
154
155 @Override
156 public long getWriteFailureCount() {
157 return writeFailureCount.get();
158 }
159
160 @Override
161 public long incWriteFailureCount() {
162 return writeFailureCount.incrementAndGet();
163 }
164
165 @Override
166 public Map<String, String> getWriteFailures() {
167 return writeFailures;
168 }
169
170 @Override
171 public void updateWriteFailures(String regionName, String serverName) {
172 writeFailures.put(regionName, serverName);
173 }
174 }
175
176 public static class RegionServerStdOutSink extends StdOutSink {
177
178 public void publishReadFailure(String table, String server) {
179 incReadFailureCount();
180 LOG.error(String.format("Read from table:%s on region server:%s", table, server));
181 }
182
183 public void publishReadTiming(String table, String server, long msTime) {
184 LOG.info(String.format("Read from table:%s on region server:%s in %dms",
185 table, server, msTime));
186 }
187 }
188
189 public static class ZookeeperStdOutSink extends StdOutSink {
190
191 public void publishReadFailure(String zNode, String server) {
192 incReadFailureCount();
193 LOG.error(String.format("Read from zNode:%s on zookeeper instance:%s", zNode, server));
194 }
195
196 public void publishReadTiming(String znode, String server, long msTime) {
197 LOG.info(String.format("Read from zNode:%s on zookeeper instance:%s in %dms",
198 znode, server, msTime));
199 }
200 }
201
202 public static class RegionStdOutSink extends StdOutSink {
203
204 private Map<String, AtomicLong> perTableReadLatency = new HashMap<>();
205 private AtomicLong writeLatency = new AtomicLong();
206
207 public void publishReadFailure(ServerName serverName, HRegionInfo region, Exception e) {
208 incReadFailureCount();
209 LOG.error(String.format("read from region %s on regionserver %s failed", region.getRegionNameAsString(), serverName), e);
210 }
211
212 public void publishReadFailure(ServerName serverName, HRegionInfo region, HColumnDescriptor column, Exception e) {
213 incReadFailureCount();
214 LOG.error(String.format("read from region %s on regionserver %s column family %s failed",
215 region.getRegionNameAsString(), serverName, column.getNameAsString()), e);
216 }
217
218 public void publishReadTiming(ServerName serverName, HRegionInfo region, HColumnDescriptor column, long msTime) {
219 LOG.info(String.format("read from region %s on regionserver %s column family %s in %dms",
220 region.getRegionNameAsString(), serverName, column.getNameAsString(), msTime));
221 }
222
223 public void publishWriteFailure(ServerName serverName, HRegionInfo region, Exception e) {
224 incWriteFailureCount();
225 LOG.error(String.format("write to region %s on regionserver %s failed", region.getRegionNameAsString(), serverName), e);
226 }
227
228 public void publishWriteFailure(ServerName serverName, HRegionInfo region, HColumnDescriptor column, Exception e) {
229 incWriteFailureCount();
230 LOG.error(String.format("write to region %s on regionserver %s column family %s failed",
231 region.getRegionNameAsString(), serverName, column.getNameAsString()), e);
232 }
233
234 public void publishWriteTiming(ServerName serverName, HRegionInfo region, HColumnDescriptor column, long msTime) {
235 LOG.info(String.format("write to region %s on regionserver %s column family %s in %dms",
236 region.getRegionNameAsString(), serverName, column.getNameAsString(), msTime));
237 }
238
239 public Map<String, AtomicLong> getReadLatencyMap() {
240 return this.perTableReadLatency;
241 }
242
243 public AtomicLong initializeAndGetReadLatencyForTable(String tableName) {
244 AtomicLong initLatency = new AtomicLong(0L);
245 this.perTableReadLatency.put(tableName, initLatency);
246 return initLatency;
247 }
248
249 public void initializeWriteLatency() {
250 this.writeLatency.set(0L);
251 }
252
253 public AtomicLong getWriteLatency() {
254 return this.writeLatency;
255 }
256 }
257
258 static class ZookeeperTask implements Callable<Void> {
259 private final Connection connection;
260 private final String host;
261 private String znode;
262 private final int timeout;
263 private ZookeeperStdOutSink sink;
264
265 public ZookeeperTask(Connection connection, String host, String znode, int timeout,
266 ZookeeperStdOutSink sink) {
267 this.connection = connection;
268 this.host = host;
269 this.znode = znode;
270 this.timeout = timeout;
271 this.sink = sink;
272 }
273
274 @Override public Void call() throws Exception {
275 ZooKeeper zooKeeper = null;
276 try {
277 zooKeeper = new ZooKeeper(host, timeout, EmptyWatcher.instance);
278 Stat exists = zooKeeper.exists(znode, false);
279 StopWatch stopwatch = new StopWatch();
280 stopwatch.start();
281 zooKeeper.getData(znode, false, exists);
282 stopwatch.stop();
283 sink.publishReadTiming(znode, host, stopwatch.getTime());
284 } catch (KeeperException | InterruptedException e) {
285 sink.publishReadFailure(znode, host);
286 } finally {
287 if (zooKeeper != null) {
288 zooKeeper.close();
289 }
290 }
291 return null;
292 }
293 }
294
295
296
297
298
299 static class RegionTask implements Callable<Void> {
300 public enum TaskType{
301 READ, WRITE
302 }
303 private Connection connection;
304 private HRegionInfo region;
305 private RegionStdOutSink sink;
306 private TaskType taskType;
307 private boolean rawScanEnabled;
308 private ServerName serverName;
309 private AtomicLong readWriteLatency;
310
311 RegionTask(Connection connection, HRegionInfo region, ServerName serverName, RegionStdOutSink sink,
312 TaskType taskType, boolean rawScanEnabled, AtomicLong rwLatency) {
313 this.connection = connection;
314 this.region = region;
315 this.serverName = serverName;
316 this.sink = sink;
317 this.taskType = taskType;
318 this.rawScanEnabled = rawScanEnabled;
319 this.readWriteLatency = rwLatency;
320 }
321
322 @Override
323 public Void call() {
324 switch (taskType) {
325 case READ:
326 return read();
327 case WRITE:
328 return write();
329 default:
330 return read();
331 }
332 }
333
334 public Void read() {
335 Table table = null;
336 HTableDescriptor tableDesc = null;
337 try {
338 if (LOG.isDebugEnabled()) {
339 LOG.debug(String.format("reading table descriptor for table %s",
340 region.getTable()));
341 }
342 table = connection.getTable(region.getTable());
343 tableDesc = table.getTableDescriptor();
344 } catch (IOException e) {
345 LOG.debug("sniffRegion failed", e);
346 sink.publishReadFailure(serverName, region, e);
347 if (table != null) {
348 try {
349 table.close();
350 } catch (IOException ioe) {
351 LOG.error("Close table failed", e);
352 }
353 }
354 return null;
355 }
356
357 byte[] startKey = null;
358 Get get = null;
359 Scan scan = null;
360 ResultScanner rs = null;
361 StopWatch stopWatch = new StopWatch();
362 for (HColumnDescriptor column : tableDesc.getColumnFamilies()) {
363 stopWatch.reset();
364 startKey = region.getStartKey();
365
366 if (startKey.length > 0) {
367 get = new Get(startKey);
368 get.setCacheBlocks(false);
369 get.setFilter(new FirstKeyOnlyFilter());
370 get.addFamily(column.getName());
371 } else {
372 scan = new Scan();
373 if (LOG.isDebugEnabled()) {
374 LOG.debug(String.format("rawScan : %s for table: %s", rawScanEnabled,
375 tableDesc.getTableName()));
376 }
377 scan.setRaw(rawScanEnabled);
378 scan.setCaching(1);
379 scan.setCacheBlocks(false);
380 scan.setFilter(new FirstKeyOnlyFilter());
381 scan.addFamily(column.getName());
382 scan.setMaxResultSize(1L);
383 scan.setSmall(true);
384 }
385
386 if (LOG.isDebugEnabled()) {
387 LOG.debug(String.format("reading from table %s region %s column family %s and key %s",
388 tableDesc.getTableName(), region.getRegionNameAsString(), column.getNameAsString(),
389 Bytes.toStringBinary(startKey)));
390 }
391 try {
392 stopWatch.start();
393 if (startKey.length > 0) {
394 table.get(get);
395 } else {
396 rs = table.getScanner(scan);
397 rs.next();
398 }
399 stopWatch.stop();
400 this.readWriteLatency.addAndGet(stopWatch.getTime());
401 sink.publishReadTiming(serverName, region, column, stopWatch.getTime());
402 } catch (Exception e) {
403 sink.publishReadFailure(serverName, region, column, e);
404 sink.updateReadFailures(region.getRegionNameAsString(), serverName.getHostname());
405 } finally {
406 if (rs != null) {
407 rs.close();
408 }
409 scan = null;
410 get = null;
411 }
412 }
413 try {
414 table.close();
415 } catch (IOException e) {
416 LOG.error("Close table failed", e);
417 }
418 return null;
419 }
420
421
422
423
424
425 private Void write() {
426 Table table = null;
427 HTableDescriptor tableDesc = null;
428 try {
429 table = connection.getTable(region.getTable());
430 tableDesc = table.getTableDescriptor();
431 byte[] rowToCheck = region.getStartKey();
432 if (rowToCheck.length == 0) {
433 rowToCheck = new byte[]{0x0};
434 }
435 int writeValueSize =
436 connection.getConfiguration().getInt(HConstants.HBASE_CANARY_WRITE_VALUE_SIZE_KEY, 10);
437 for (HColumnDescriptor column : tableDesc.getColumnFamilies()) {
438 Put put = new Put(rowToCheck);
439 byte[] value = new byte[writeValueSize];
440 Bytes.random(value);
441 put.addColumn(column.getName(), HConstants.EMPTY_BYTE_ARRAY, value);
442
443 if (LOG.isDebugEnabled()) {
444 LOG.debug(String.format("writing to table %s region %s column family %s and key %s",
445 tableDesc.getTableName(), region.getRegionNameAsString(), column.getNameAsString(),
446 Bytes.toStringBinary(rowToCheck)));
447 }
448 try {
449 long startTime = System.currentTimeMillis();
450 table.put(put);
451 long time = System.currentTimeMillis() - startTime;
452 this.readWriteLatency.addAndGet(time);
453 sink.publishWriteTiming(serverName, region, column, time);
454 } catch (Exception e) {
455 sink.publishWriteFailure(serverName, region, column, e);
456 }
457 }
458 table.close();
459 } catch (IOException e) {
460 sink.publishWriteFailure(serverName, region, e);
461 sink.updateWriteFailures(region.getRegionNameAsString(), serverName.getHostname() );
462 }
463 return null;
464 }
465 }
466
467
468
469
470 static class RegionServerTask implements Callable<Void> {
471 private Connection connection;
472 private String serverName;
473 private HRegionInfo region;
474 private RegionServerStdOutSink sink;
475 private AtomicLong successes;
476
477 RegionServerTask(Connection connection, String serverName, HRegionInfo region,
478 RegionServerStdOutSink sink, AtomicLong successes) {
479 this.connection = connection;
480 this.serverName = serverName;
481 this.region = region;
482 this.sink = sink;
483 this.successes = successes;
484 }
485
486 @Override
487 public Void call() {
488 TableName tableName = null;
489 Table table = null;
490 Get get = null;
491 byte[] startKey = null;
492 Scan scan = null;
493 StopWatch stopWatch = new StopWatch();
494
495 stopWatch.reset();
496 try {
497 tableName = region.getTable();
498 table = connection.getTable(tableName);
499 startKey = region.getStartKey();
500
501 if (LOG.isDebugEnabled()) {
502 LOG.debug(String.format("reading from region server %s table %s region %s and key %s",
503 serverName, region.getTable(), region.getRegionNameAsString(),
504 Bytes.toStringBinary(startKey)));
505 }
506 if (startKey.length > 0) {
507 get = new Get(startKey);
508 get.setCacheBlocks(false);
509 get.setFilter(new FirstKeyOnlyFilter());
510 stopWatch.start();
511 table.get(get);
512 stopWatch.stop();
513 } else {
514 scan = new Scan();
515 scan.setCacheBlocks(false);
516 scan.setFilter(new FirstKeyOnlyFilter());
517 scan.setCaching(1);
518 scan.setMaxResultSize(1L);
519 scan.setSmall(true);
520 stopWatch.start();
521 ResultScanner s = table.getScanner(scan);
522 s.next();
523 s.close();
524 stopWatch.stop();
525 }
526 successes.incrementAndGet();
527 sink.publishReadTiming(tableName.getNameAsString(), serverName, stopWatch.getTime());
528 } catch (TableNotFoundException tnfe) {
529 LOG.error("Table may be deleted", tnfe);
530
531 } catch (TableNotEnabledException tnee) {
532
533 successes.incrementAndGet();
534 LOG.debug("The targeted table was disabled. Assuming success.");
535 } catch (DoNotRetryIOException dnrioe) {
536 sink.publishReadFailure(tableName.getNameAsString(), serverName);
537 LOG.error(dnrioe);
538 } catch (IOException e) {
539 sink.publishReadFailure(tableName.getNameAsString(), serverName);
540 LOG.error(e);
541 } finally {
542 if (table != null) {
543 try {
544 table.close();
545 } catch (IOException e) {
546 LOG.error("Close table failed", e);
547 }
548 }
549 scan = null;
550 get = null;
551 startKey = null;
552 }
553 return null;
554 }
555 }
556
557 private static final int USAGE_EXIT_CODE = 1;
558 private static final int INIT_ERROR_EXIT_CODE = 2;
559 private static final int TIMEOUT_ERROR_EXIT_CODE = 3;
560 private static final int ERROR_EXIT_CODE = 4;
561 private static final int FAILURE_EXIT_CODE = 5;
562
563 private static final long DEFAULT_INTERVAL = 60000;
564
565 private static final long DEFAULT_TIMEOUT = 600000;
566 private static final int MAX_THREADS_NUM = 16;
567
568 private static final Log LOG = LogFactory.getLog(Canary.class);
569
570 public static final TableName DEFAULT_WRITE_TABLE_NAME = TableName.valueOf(
571 NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "canary");
572
573 private static final String CANARY_TABLE_FAMILY_NAME = "Test";
574
575 private Configuration conf = null;
576 private long interval = 0;
577 private Sink sink = null;
578
579 private boolean useRegExp;
580 private long timeout = DEFAULT_TIMEOUT;
581 private boolean failOnError = true;
582 private boolean regionServerMode = false;
583 private boolean zookeeperMode = false;
584 private long permittedFailures = 0;
585 private boolean regionServerAllRegions = false;
586 private boolean writeSniffing = false;
587 private long configuredWriteTableTimeout = DEFAULT_TIMEOUT;
588 private boolean treatFailureAsError = false;
589 private TableName writeTableName = DEFAULT_WRITE_TABLE_NAME;
590 private HashMap<String, Long> configuredReadTableTimeouts = new HashMap<>();
591
592 private ExecutorService executor;
593
594 public Canary() {
595 this(new ScheduledThreadPoolExecutor(1), new RegionServerStdOutSink());
596 }
597
598 public Canary(ExecutorService executor, Sink sink) {
599 this.executor = executor;
600 this.sink = sink;
601 }
602
603 @Override
604 public Configuration getConf() {
605 return conf;
606 }
607
608 @Override
609 public void setConf(Configuration conf) {
610 this.conf = conf;
611 }
612
613 private int parseArgs(String[] args) {
614 int index = -1;
615
616 for (int i = 0; i < args.length; i++) {
617 String cmd = args[i];
618
619 if (cmd.startsWith("-")) {
620 if (index >= 0) {
621
622 System.err.println("Invalid command line options");
623 printUsageAndExit();
624 }
625
626 if (cmd.equals("-help")) {
627
628 printUsageAndExit();
629 } else if (cmd.equals("-daemon") && interval == 0) {
630
631 interval = DEFAULT_INTERVAL;
632 } else if (cmd.equals("-interval")) {
633
634 i++;
635
636 if (i == args.length) {
637 System.err.println("-interval needs a numeric value argument.");
638 printUsageAndExit();
639 }
640
641 try {
642 interval = Long.parseLong(args[i]) * 1000;
643 } catch (NumberFormatException e) {
644 System.err.println("-interval needs a numeric value argument.");
645 printUsageAndExit();
646 }
647 } else if (cmd.equals("-zookeeper")) {
648 this.zookeeperMode = true;
649 } else if(cmd.equals("-regionserver")) {
650 this.regionServerMode = true;
651 } else if(cmd.equals("-allRegions")) {
652 this.regionServerAllRegions = true;
653 } else if(cmd.equals("-writeSniffing")) {
654 this.writeSniffing = true;
655 } else if(cmd.equals("-treatFailureAsError")) {
656 this.treatFailureAsError = true;
657 } else if (cmd.equals("-e")) {
658 this.useRegExp = true;
659 } else if (cmd.equals("-t")) {
660 i++;
661
662 if (i == args.length) {
663 System.err.println("-t needs a numeric value argument.");
664 printUsageAndExit();
665 }
666
667 try {
668 this.timeout = Long.parseLong(args[i]);
669 } catch (NumberFormatException e) {
670 System.err.println("-t needs a numeric value argument.");
671 printUsageAndExit();
672 }
673 } else if(cmd.equals("-writeTableTimeout")) {
674 i++;
675
676 if (i == args.length) {
677 System.err.println("-writeTableTimeout needs a numeric value argument.");
678 printUsageAndExit();
679 }
680
681 try {
682 this.configuredWriteTableTimeout = Long.parseLong(args[i]);
683 } catch (NumberFormatException e) {
684 System.err.println("-writeTableTimeout needs a numeric value argument.");
685 printUsageAndExit();
686 }
687 } else if (cmd.equals("-writeTable")) {
688 i++;
689
690 if (i == args.length) {
691 System.err.println("-writeTable needs a string value argument.");
692 printUsageAndExit();
693 }
694 this.writeTableName = TableName.valueOf(args[i]);
695 } else if (cmd.equals("-f")) {
696 i++;
697
698 if (i == args.length) {
699 System.err
700 .println("-f needs a boolean value argument (true|false).");
701 printUsageAndExit();
702 }
703
704 this.failOnError = Boolean.parseBoolean(args[i]);
705 } else if (cmd.equals("-readTableTimeouts")) {
706 i++;
707
708 if (i == args.length) {
709 System.err.println("-readTableTimeouts needs a comma-separated list of read timeouts per table (without spaces).");
710 printUsageAndExit();
711 }
712 String [] tableTimeouts = args[i].split(",");
713 for (String tT: tableTimeouts) {
714 String [] nameTimeout = tT.split("=");
715 if (nameTimeout.length < 2) {
716 System.err.println("Each -readTableTimeouts argument must be of the form <tableName>=<read timeout>.");
717 printUsageAndExit();
718 }
719 long timeoutVal = 0L;
720 try {
721 timeoutVal = Long.parseLong(nameTimeout[1]);
722 } catch (NumberFormatException e) {
723 System.err.println("-readTableTimeouts read timeout for each table must be a numeric value argument.");
724 printUsageAndExit();
725 }
726 this.configuredReadTableTimeouts.put(nameTimeout[0], timeoutVal);
727 }
728 } else if (cmd.equals("-permittedZookeeperFailures")) {
729 i++;
730
731 if (i == args.length) {
732 System.err.println("-permittedZookeeperFailures needs a numeric value argument.");
733 printUsageAndExit();
734 }
735 try {
736 this.permittedFailures = Long.parseLong(args[i]);
737 } catch (NumberFormatException e) {
738 System.err.println("-permittedZookeeperFailures needs a numeric value argument.");
739 printUsageAndExit();
740 }
741 } else {
742
743 System.err.println(cmd + " options is invalid.");
744 printUsageAndExit();
745 }
746 } else if (index < 0) {
747
748 index = i;
749 }
750 }
751 if (this.regionServerAllRegions && !this.regionServerMode) {
752 System.err.println("-allRegions can only be specified in regionserver mode.");
753 printUsageAndExit();
754 }
755 if (this.zookeeperMode) {
756 if (this.regionServerMode || this.regionServerAllRegions || this.writeSniffing) {
757 System.err.println("-zookeeper is exclusive and cannot be combined with "
758 + "other modes.");
759 printUsageAndExit();
760 }
761 }
762 if (this.permittedFailures != 0 && !this.zookeeperMode) {
763 System.err.println("-permittedZookeeperFailures requires -zookeeper mode.");
764 printUsageAndExit();
765 }
766 if (!this.configuredReadTableTimeouts.isEmpty() && (this.regionServerMode || this.zookeeperMode)) {
767 System.err.println("-readTableTimeouts can only be configured in region mode.");
768 printUsageAndExit();
769 }
770 return index;
771 }
772
773 @Override
774 public int run(String[] args) throws Exception {
775 int index = parseArgs(args);
776 ChoreService choreService = null;
777
778
779
780
781 final ScheduledChore authChore = AuthUtil.getAuthChore(conf);
782 if (authChore != null) {
783 choreService = new ChoreService("CANARY_TOOL");
784 choreService.scheduleChore(authChore);
785 }
786
787
788 Monitor monitor = null;
789 Thread monitorThread = null;
790 long startTime = 0;
791 long currentTimeLength = 0;
792
793 try (Connection connection = ConnectionFactory.createConnection(this.conf)) {
794 do {
795
796 try {
797 monitor = this.newMonitor(connection, index, args);
798 monitorThread = new Thread(monitor, "CanaryMonitor-" + System.currentTimeMillis());
799 startTime = System.currentTimeMillis();
800 monitorThread.start();
801 while (!monitor.isDone()) {
802
803 Thread.sleep(1000);
804
805 if (this.failOnError && monitor.hasError()) {
806 monitorThread.interrupt();
807 if (monitor.initialized) {
808 return monitor.errorCode;
809 } else {
810 return INIT_ERROR_EXIT_CODE;
811 }
812 }
813 currentTimeLength = System.currentTimeMillis() - startTime;
814 if (currentTimeLength > this.timeout) {
815 LOG.error("The monitor is running too long (" + currentTimeLength
816 + ") after timeout limit:" + this.timeout
817 + " will be killed itself !!");
818 if (monitor.initialized) {
819 return TIMEOUT_ERROR_EXIT_CODE;
820 } else {
821 return INIT_ERROR_EXIT_CODE;
822 }
823 }
824 }
825
826 if (this.failOnError && monitor.finalCheckForErrors()) {
827 monitorThread.interrupt();
828 return monitor.errorCode;
829 }
830 } finally {
831 if (monitor != null) monitor.close();
832 }
833
834 Thread.sleep(interval);
835 } while (interval > 0);
836 }
837
838 if (choreService != null) {
839 choreService.shutdown();
840 }
841 return monitor.errorCode;
842 }
843
844 public Map<String, String> getReadFailures() {
845 return sink.getReadFailures();
846 }
847
848 public Map<String, String> getWriteFailures() {
849 return sink.getWriteFailures();
850 }
851
852 private void printUsageAndExit() {
853 System.err.printf(
854 "Usage: bin/hbase %s [opts] [table1 [table2]...] | [regionserver1 [regionserver2]..]%n",
855 getClass().getName());
856 System.err.println(" where [opts] are:");
857 System.err.println(" -help Show this help and exit.");
858 System.err.println(" -regionserver replace the table argument to regionserver,");
859 System.err.println(" which means to enable regionserver mode");
860 System.err.println(" -allRegions Tries all regions on a regionserver,");
861 System.err.println(" only works in regionserver mode.");
862 System.err.println(" -zookeeper Tries to grab zookeeper.znode.parent ");
863 System.err.println(" on each zookeeper instance");
864 System.err.println(" -permittedZookeeperFailures <N> Ignore first N failures when attempting to ");
865 System.err.println(" connect to individual zookeeper nodes in the ensemble");
866 System.err.println(" -daemon Continuous check at defined intervals.");
867 System.err.println(" -interval <N> Interval between checks (sec)");
868 System.err.println(" -e Use table/regionserver as regular expression");
869 System.err.println(" which means the table/regionserver is regular expression pattern");
870 System.err.println(" -f <B> stop whole program if first error occurs," +
871 " default is true");
872 System.err.println(" -t <N> timeout for a check, default is 600000 (millisecs)");
873 System.err.println(" -writeTableTimeout <N> write timeout for the writeTable, default is 600000 (millisecs)");
874 System.err.println(" -readTableTimeouts <tableName>=<read timeout>,<tableName>=<read timeout>, ... "
875 + "comma-separated list of read timeouts per table (no spaces), default is 600000 (millisecs)");
876 System.err.println(" -writeSniffing enable the write sniffing in canary");
877 System.err.println(" -treatFailureAsError treats read / write failure as error");
878 System.err.println(" -writeTable The table used for write sniffing."
879 + " Default is hbase:canary");
880 System.err.println(" -Dhbase.canary.read.raw.enabled=<true/false> Use this flag to enable or disable raw scan during read canary test"
881 + " Default is false and raw is not enabled during scan");
882 System.err
883 .println(" -D<configProperty>=<value> assigning or override the configuration params");
884 System.exit(USAGE_EXIT_CODE);
885 }
886
887
888
889
890
891
892
893
894 public Monitor newMonitor(final Connection connection, int index, String[] args) {
895 Monitor monitor = null;
896 String[] monitorTargets = null;
897
898 if(index >= 0) {
899 int length = args.length - index;
900 monitorTargets = new String[length];
901 System.arraycopy(args, index, monitorTargets, 0, length);
902 }
903
904 if (this.sink instanceof RegionServerStdOutSink || this.regionServerMode) {
905 monitor =
906 new RegionServerMonitor(connection, monitorTargets, this.useRegExp,
907 (StdOutSink) this.sink, this.executor, this.regionServerAllRegions,
908 this.treatFailureAsError, this.permittedFailures);
909 } else if (this.sink instanceof ZookeeperStdOutSink || this.zookeeperMode) {
910 monitor =
911 new ZookeeperMonitor(connection, monitorTargets, this.useRegExp,
912 (StdOutSink) this.sink, this.executor, this.treatFailureAsError,
913 this.permittedFailures);
914 } else {
915 monitor =
916 new RegionMonitor(connection, monitorTargets, this.useRegExp,
917 (StdOutSink) this.sink, this.executor, this.writeSniffing,
918 this.writeTableName, this.treatFailureAsError, this.configuredReadTableTimeouts,
919 this.configuredWriteTableTimeout, this.permittedFailures);
920 }
921 return monitor;
922 }
923
924
925 public static abstract class Monitor implements Runnable, Closeable {
926
927 protected Connection connection;
928 protected Admin admin;
929 protected String[] targets;
930 protected boolean useRegExp;
931 protected boolean treatFailureAsError;
932 protected boolean initialized = false;
933
934 protected boolean done = false;
935 protected int errorCode = 0;
936 protected long allowedFailures = 0;
937 protected Sink sink;
938 protected ExecutorService executor;
939
940 public boolean isDone() {
941 return done;
942 }
943
944 public boolean hasError() {
945 return errorCode != 0;
946 }
947
948 public boolean finalCheckForErrors() {
949 if (errorCode != 0) {
950 return true;
951 }
952 if (treatFailureAsError &&
953 (sink.getReadFailureCount() > allowedFailures || sink.getWriteFailureCount() > allowedFailures)) {
954 LOG.error("Too many failures detected, treating failure as error, failing the Canary.");
955 errorCode = FAILURE_EXIT_CODE;
956 return true;
957 }
958 return false;
959 }
960
961 @Override
962 public void close() throws IOException {
963 if (this.admin != null) this.admin.close();
964 }
965
966 protected Monitor(Connection connection, String[] monitorTargets, boolean useRegExp, Sink sink,
967 ExecutorService executor, boolean treatFailureAsError, long allowedFailures) {
968 if (null == connection) throw new IllegalArgumentException("connection shall not be null");
969
970 this.connection = connection;
971 this.targets = monitorTargets;
972 this.useRegExp = useRegExp;
973 this.treatFailureAsError = treatFailureAsError;
974 this.sink = sink;
975 this.executor = executor;
976 this.allowedFailures = allowedFailures;
977 }
978
979 @Override
980 public abstract void run();
981
982 protected boolean initAdmin() {
983 if (null == this.admin) {
984 try {
985 this.admin = this.connection.getAdmin();
986 } catch (Exception e) {
987 LOG.error("Initial HBaseAdmin failed...", e);
988 this.errorCode = INIT_ERROR_EXIT_CODE;
989 }
990 } else if (admin.isAborted()) {
991 LOG.error("HBaseAdmin aborted");
992 this.errorCode = INIT_ERROR_EXIT_CODE;
993 }
994 return !this.hasError();
995 }
996 }
997
998
999 private static class RegionMonitor extends Monitor {
1000
1001 private static final int DEFAULT_WRITE_TABLE_CHECK_PERIOD = 10 * 60 * 1000;
1002
1003 private static final int DEFAULT_WRITE_DATA_TTL = 24 * 60 * 60;
1004
1005 private long lastCheckTime = -1;
1006 private boolean writeSniffing;
1007 private TableName writeTableName;
1008 private int writeDataTTL;
1009 private float regionsLowerLimit;
1010 private float regionsUpperLimit;
1011 private int checkPeriod;
1012 private boolean rawScanEnabled;
1013 private HashMap<String, Long> configuredReadTableTimeouts;
1014 private long configuredWriteTableTimeout;
1015
1016 public RegionMonitor(Connection connection, String[] monitorTargets, boolean useRegExp,
1017 StdOutSink sink, ExecutorService executor, boolean writeSniffing, TableName writeTableName,
1018 boolean treatFailureAsError, HashMap<String, Long> configuredReadTableTimeouts,
1019 long configuredWriteTableTimeout, long allowedFailures) {
1020 super(connection, monitorTargets, useRegExp, sink, executor, treatFailureAsError, allowedFailures);
1021 Configuration conf = connection.getConfiguration();
1022 this.writeSniffing = writeSniffing;
1023 this.writeTableName = writeTableName;
1024 this.writeDataTTL =
1025 conf.getInt(HConstants.HBASE_CANARY_WRITE_DATA_TTL_KEY, DEFAULT_WRITE_DATA_TTL);
1026 this.regionsLowerLimit =
1027 conf.getFloat(HConstants.HBASE_CANARY_WRITE_PERSERVER_REGIONS_LOWERLIMIT_KEY, 1.0f);
1028 this.regionsUpperLimit =
1029 conf.getFloat(HConstants.HBASE_CANARY_WRITE_PERSERVER_REGIONS_UPPERLIMIT_KEY, 1.5f);
1030 this.checkPeriod =
1031 conf.getInt(HConstants.HBASE_CANARY_WRITE_TABLE_CHECK_PERIOD_KEY,
1032 DEFAULT_WRITE_TABLE_CHECK_PERIOD);
1033 this.rawScanEnabled = conf.getBoolean(HConstants.HBASE_CANARY_READ_RAW_SCAN_KEY, false);
1034 this.configuredReadTableTimeouts = new HashMap<>(configuredReadTableTimeouts);
1035 this.configuredWriteTableTimeout = configuredWriteTableTimeout;
1036 }
1037
1038 private RegionStdOutSink getSink() {
1039 if (!(sink instanceof RegionStdOutSink)) {
1040 throw new RuntimeException("Can only write to Region sink");
1041 }
1042 return ((RegionStdOutSink) sink);
1043 }
1044
1045 @Override
1046 public void run() {
1047 if (this.initAdmin()) {
1048 try {
1049 List<Future<Void>> taskFutures = new LinkedList<>();
1050 RegionStdOutSink regionSink = this.getSink();
1051 if (this.targets != null && this.targets.length > 0) {
1052 String[] tables = generateMonitorTables(this.targets);
1053
1054 if (! new HashSet<>(Arrays.asList(tables)).containsAll(this.configuredReadTableTimeouts.keySet())) {
1055 LOG.error("-readTableTimeouts can only specify read timeouts for monitor targets passed via command line.");
1056 this.errorCode = USAGE_EXIT_CODE;
1057 return;
1058 }
1059 this.initialized = true;
1060 for (String table : tables) {
1061 AtomicLong readLatency = regionSink.initializeAndGetReadLatencyForTable(table);
1062 taskFutures.addAll(Canary.sniff(admin, regionSink, table, executor, TaskType.READ,
1063 this.rawScanEnabled, readLatency));
1064 }
1065 } else {
1066 taskFutures.addAll(sniff(TaskType.READ, regionSink));
1067 }
1068
1069 if (writeSniffing) {
1070 if (EnvironmentEdgeManager.currentTime() - lastCheckTime > checkPeriod) {
1071 try {
1072 checkWriteTableDistribution();
1073 } catch (IOException e) {
1074 LOG.error("Check canary table distribution failed!", e);
1075 }
1076 lastCheckTime = EnvironmentEdgeManager.currentTime();
1077 }
1078
1079 regionSink.initializeWriteLatency();
1080 AtomicLong writeTableLatency = regionSink.getWriteLatency();
1081 taskFutures.addAll(Canary.sniff(admin, regionSink, admin.getTableDescriptor(writeTableName),
1082 executor, TaskType.WRITE, this.rawScanEnabled, writeTableLatency));
1083 }
1084
1085 for (Future<Void> future : taskFutures) {
1086 try {
1087 future.get();
1088 } catch (ExecutionException e) {
1089 LOG.error("Sniff region failed!", e);
1090 }
1091 }
1092 Map<String, AtomicLong> actualReadTableLatency = regionSink.getReadLatencyMap();
1093 for (Entry<String, Long> entry : this.configuredReadTableTimeouts.entrySet()) {
1094 String tableName = entry.getKey();
1095 if (actualReadTableLatency.containsKey(tableName)) {
1096 Long actual = actualReadTableLatency.get(tableName).longValue();
1097 Long configured = this.configuredReadTableTimeouts.get(tableName);
1098 LOG.info("Read operation for " + tableName + " took " + actual +
1099 " ms. The configured read timeout was " + configured + " ms.");
1100 if (actual > configured) {
1101 LOG.error("Read operation for " + tableName + " exceeded the configured read timeout.");
1102 }
1103 } else {
1104 LOG.error("Read operation for " + tableName + " failed!");
1105 }
1106 }
1107 if (this.writeSniffing) {
1108 String writeTableStringName = this.writeTableName.getNameAsString();
1109 long actualWriteLatency = regionSink.getWriteLatency().longValue();
1110 LOG.info("Write operation for " + writeTableStringName + " took " + actualWriteLatency + " ms. The configured write timeout was " +
1111 this.configuredWriteTableTimeout + " ms.");
1112
1113 if (actualWriteLatency > this.configuredWriteTableTimeout) {
1114 LOG.error("Write operation for " + writeTableStringName + " exceeded the configured write timeout.");
1115 }
1116 }
1117 } catch (Exception e) {
1118 LOG.error("Run regionMonitor failed", e);
1119 this.errorCode = ERROR_EXIT_CODE;
1120 } finally {
1121 this.done = true;
1122 }
1123 }
1124 this.done = true;
1125 }
1126
1127 private String[] generateMonitorTables(String[] monitorTargets) throws IOException {
1128 String[] returnTables = null;
1129
1130 if (this.useRegExp) {
1131 Pattern pattern = null;
1132 HTableDescriptor[] tds = null;
1133 Set<String> tmpTables = new TreeSet<String>();
1134 try {
1135 if (LOG.isDebugEnabled()) {
1136 LOG.debug(String.format("reading list of tables"));
1137 }
1138 tds = this.admin.listTables(pattern);
1139 if (tds == null) {
1140 tds = new HTableDescriptor[0];
1141 }
1142 for (String monitorTarget : monitorTargets) {
1143 pattern = Pattern.compile(monitorTarget);
1144 for (HTableDescriptor td : tds) {
1145 if (pattern.matcher(td.getNameAsString()).matches()) {
1146 tmpTables.add(td.getNameAsString());
1147 }
1148 }
1149 }
1150 } catch (IOException e) {
1151 LOG.error("Communicate with admin failed", e);
1152 throw e;
1153 }
1154
1155 if (tmpTables.size() > 0) {
1156 returnTables = tmpTables.toArray(new String[tmpTables.size()]);
1157 } else {
1158 String msg = "No HTable found, tablePattern:" + Arrays.toString(monitorTargets);
1159 LOG.error(msg);
1160 this.errorCode = INIT_ERROR_EXIT_CODE;
1161 throw new TableNotFoundException(msg);
1162 }
1163 } else {
1164 returnTables = monitorTargets;
1165 }
1166
1167 return returnTables;
1168 }
1169
1170
1171
1172
1173 private List<Future<Void>> sniff(TaskType taskType, RegionStdOutSink regionSink) throws Exception {
1174 if (LOG.isDebugEnabled()) {
1175 LOG.debug(String.format("reading list of tables"));
1176 }
1177 List<Future<Void>> taskFutures = new LinkedList<>();
1178 for (HTableDescriptor table : admin.listTables()) {
1179 if (admin.tableExists(table.getTableName()) && admin.isTableEnabled(table.getTableName())
1180 && (!table.getTableName().equals(writeTableName))) {
1181 AtomicLong readLatency = regionSink.initializeAndGetReadLatencyForTable(table.getNameAsString());
1182 taskFutures.addAll(Canary.sniff(admin, sink, table, executor, taskType, this.rawScanEnabled, readLatency));
1183 }
1184 }
1185 return taskFutures;
1186 }
1187
1188 private void checkWriteTableDistribution() throws IOException {
1189 if (!admin.tableExists(writeTableName)) {
1190 int numberOfServers = admin.getClusterStatus().getServers().size();
1191 if (numberOfServers == 0) {
1192 throw new IllegalStateException("No live regionservers");
1193 }
1194 createWriteTable(numberOfServers);
1195 }
1196
1197 if (!admin.isTableEnabled(writeTableName)) {
1198 admin.enableTable(writeTableName);
1199 }
1200
1201 ClusterStatus status = admin.getClusterStatus();
1202 int numberOfServers = status.getServersSize();
1203 if (status.getServers().contains(status.getMaster())) {
1204 numberOfServers -= 1;
1205 }
1206
1207 List<HRegionLocation> locations;
1208 RegionLocator locator = connection.getRegionLocator(writeTableName);
1209 try {
1210 locations = locator.getAllRegionLocations();
1211 } finally {
1212 locator.close();
1213 }
1214 int numberOfRegions = locations.size();
1215 if (numberOfRegions < numberOfServers * regionsLowerLimit
1216 || numberOfRegions > numberOfServers * regionsUpperLimit) {
1217 admin.disableTable(writeTableName);
1218 admin.deleteTable(writeTableName);
1219 createWriteTable(numberOfServers);
1220 }
1221 HashSet<ServerName> serverSet = new HashSet<ServerName>();
1222 for (HRegionLocation location: locations) {
1223 serverSet.add(location.getServerName());
1224 }
1225 int numberOfCoveredServers = serverSet.size();
1226 if (numberOfCoveredServers < numberOfServers) {
1227 admin.balancer();
1228 }
1229 }
1230
1231 private void createWriteTable(int numberOfServers) throws IOException {
1232 int numberOfRegions = (int)(numberOfServers * regionsLowerLimit);
1233 LOG.info("Number of live regionservers: " + numberOfServers + ", "
1234 + "pre-splitting the canary table into " + numberOfRegions + " regions "
1235 + "(current lower limit of regions per server is " + regionsLowerLimit
1236 + " and you can change it by config: "
1237 + HConstants.HBASE_CANARY_WRITE_PERSERVER_REGIONS_LOWERLIMIT_KEY + " )");
1238 HTableDescriptor desc = new HTableDescriptor(writeTableName);
1239 HColumnDescriptor family = new HColumnDescriptor(CANARY_TABLE_FAMILY_NAME);
1240 family.setMaxVersions(1);
1241 family.setTimeToLive(writeDataTTL);
1242
1243 desc.addFamily(family);
1244 byte[][] splits = new RegionSplitter.HexStringSplit().split(numberOfRegions);
1245 admin.createTable(desc, splits);
1246 }
1247 }
1248
1249
1250
1251
1252
1253 private static List<Future<Void>> sniff(final Admin admin, final Sink sink, String tableName,
1254 ExecutorService executor, TaskType taskType, boolean rawScanEnabled, AtomicLong readLatency) throws Exception {
1255 if (LOG.isDebugEnabled()) {
1256 LOG.debug(String.format("checking table is enabled and getting table descriptor for table %s",
1257 tableName));
1258 }
1259 if (admin.isTableEnabled(TableName.valueOf(tableName))) {
1260 return Canary.sniff(admin, sink, admin.getTableDescriptor(TableName.valueOf(tableName)),
1261 executor, taskType, rawScanEnabled, readLatency);
1262 } else {
1263 LOG.warn(String.format("Table %s is not enabled", tableName));
1264 }
1265 return new LinkedList<Future<Void>>();
1266 }
1267
1268
1269
1270
1271 private static List<Future<Void>> sniff(final Admin admin, final Sink sink,
1272 HTableDescriptor tableDesc, ExecutorService executor, TaskType taskType,
1273 boolean rawScanEnabled, AtomicLong rwLatency) throws Exception {
1274
1275 if (LOG.isDebugEnabled()) {
1276 LOG.debug(String.format("reading list of regions for table %s", tableDesc.getTableName()));
1277 }
1278
1279 Table table = null;
1280 try {
1281 table = admin.getConnection().getTable(tableDesc.getTableName());
1282 } catch (TableNotFoundException e) {
1283 return new ArrayList<Future<Void>>();
1284 } finally {
1285 if (table != null) {
1286 table.close();
1287 }
1288 }
1289
1290 List<RegionTask> tasks = new ArrayList<RegionTask>();
1291 RegionLocator regionLocator = null;
1292 try {
1293 regionLocator = admin.getConnection().getRegionLocator(tableDesc.getTableName());
1294 for (HRegionLocation location : regionLocator.getAllRegionLocations()) {
1295 ServerName rs = location.getServerName();
1296 HRegionInfo region = location.getRegionInfo();
1297 tasks.add(new RegionTask(admin.getConnection(), region, rs, (RegionStdOutSink) sink, taskType, rawScanEnabled,
1298 rwLatency));
1299 }
1300 } finally {
1301 if (regionLocator != null) {
1302 regionLocator.close();
1303 }
1304 }
1305 return executor.invokeAll(tasks);
1306 }
1307
1308
1309 private static class ZookeeperMonitor extends Monitor {
1310 private List<String> hosts;
1311 private final String znode;
1312 private final int timeout;
1313
1314 protected ZookeeperMonitor(Connection connection, String[] monitorTargets, boolean useRegExp,
1315 StdOutSink sink, ExecutorService executor, boolean treatFailureAsError, long allowedFailures) {
1316 super(connection, monitorTargets, useRegExp, sink, executor, treatFailureAsError, allowedFailures);
1317 Configuration configuration = connection.getConfiguration();
1318 znode =
1319 configuration.get(ZOOKEEPER_ZNODE_PARENT,
1320 DEFAULT_ZOOKEEPER_ZNODE_PARENT);
1321 timeout = configuration
1322 .getInt(HConstants.ZK_SESSION_TIMEOUT, HConstants.DEFAULT_ZK_SESSION_TIMEOUT);
1323 ConnectStringParser parser =
1324 new ConnectStringParser(ZKConfig.getZKQuorumServersString(configuration));
1325 hosts = Lists.newArrayList();
1326 for (InetSocketAddress server : parser.getServerAddresses()) {
1327 hosts.add(server.toString());
1328 }
1329 if (allowedFailures > (hosts.size() - 1) / 2) {
1330 LOG.warn(String.format("Confirm allowable number of failed ZooKeeper nodes, as quorum will " +
1331 "already be lost. Setting of %d failures is unexpected for %d ensemble size.",
1332 allowedFailures, hosts.size()));
1333 }
1334 }
1335
1336 @Override public void run() {
1337 List<ZookeeperTask> tasks = Lists.newArrayList();
1338 ZookeeperStdOutSink zkSink = null;
1339 try {
1340 zkSink = this.getSink();
1341 } catch (RuntimeException e) {
1342 LOG.error("Run ZooKeeperMonitor failed!", e);
1343 this.errorCode = ERROR_EXIT_CODE;
1344 }
1345 this.initialized = true;
1346 for (final String host : hosts) {
1347 tasks.add(new ZookeeperTask(connection, host, znode, timeout, zkSink));
1348 }
1349 try {
1350 for (Future<Void> future : this.executor.invokeAll(tasks)) {
1351 try {
1352 future.get();
1353 } catch (ExecutionException e) {
1354 LOG.error("Sniff zookeeper failed!", e);
1355 this.errorCode = ERROR_EXIT_CODE;
1356 }
1357 }
1358 } catch (InterruptedException e) {
1359 this.errorCode = ERROR_EXIT_CODE;
1360 Thread.currentThread().interrupt();
1361 LOG.error("Sniff zookeeper interrupted!", e);
1362 }
1363 this.done = true;
1364 }
1365
1366 private ZookeeperStdOutSink getSink() {
1367 if (!(sink instanceof ZookeeperStdOutSink)) {
1368 throw new RuntimeException("Can only write to zookeeper sink");
1369 }
1370 return ((ZookeeperStdOutSink) sink);
1371 }
1372 }
1373
1374
1375
1376 private static class RegionServerMonitor extends Monitor {
1377
1378 private boolean allRegions;
1379
1380 public RegionServerMonitor(Connection connection, String[] monitorTargets, boolean useRegExp,
1381 StdOutSink sink, ExecutorService executor, boolean allRegions,
1382 boolean treatFailureAsError, long allowedFailures) {
1383 super(connection, monitorTargets, useRegExp, sink, executor, treatFailureAsError, allowedFailures);
1384 this.allRegions = allRegions;
1385 }
1386
1387 private RegionServerStdOutSink getSink() {
1388 if (!(sink instanceof RegionServerStdOutSink)) {
1389 throw new RuntimeException("Can only write to regionserver sink");
1390 }
1391 return ((RegionServerStdOutSink) sink);
1392 }
1393
1394 @Override
1395 public void run() {
1396 if (this.initAdmin() && this.checkNoTableNames()) {
1397 RegionServerStdOutSink regionServerSink = null;
1398 try {
1399 regionServerSink = this.getSink();
1400 } catch (RuntimeException e) {
1401 LOG.error("Run RegionServerMonitor failed!", e);
1402 this.errorCode = ERROR_EXIT_CODE;
1403 }
1404 Map<String, List<HRegionInfo>> rsAndRMap = this.filterRegionServerByName();
1405 this.initialized = true;
1406 this.monitorRegionServers(rsAndRMap, regionServerSink);
1407 }
1408 this.done = true;
1409 }
1410
1411 private boolean checkNoTableNames() {
1412 List<String> foundTableNames = new ArrayList<String>();
1413 TableName[] tableNames = null;
1414
1415 if (LOG.isDebugEnabled()) {
1416 LOG.debug(String.format("reading list of tables"));
1417 }
1418 try {
1419 tableNames = this.admin.listTableNames();
1420 } catch (IOException e) {
1421 LOG.error("Get listTableNames failed", e);
1422 this.errorCode = INIT_ERROR_EXIT_CODE;
1423 return false;
1424 }
1425
1426 if (this.targets == null || this.targets.length == 0) return true;
1427
1428 for (String target : this.targets) {
1429 for (TableName tableName : tableNames) {
1430 if (target.equals(tableName.getNameAsString())) {
1431 foundTableNames.add(target);
1432 }
1433 }
1434 }
1435
1436 if (foundTableNames.size() > 0) {
1437 System.err.println("Cannot pass a tablename when using the -regionserver " +
1438 "option, tablenames:" + foundTableNames.toString());
1439 this.errorCode = USAGE_EXIT_CODE;
1440 }
1441 return foundTableNames.size() == 0;
1442 }
1443
1444 private void monitorRegionServers(Map<String, List<HRegionInfo>> rsAndRMap,
1445 RegionServerStdOutSink regionServerSink) {
1446 List<RegionServerTask> tasks = new ArrayList<>();
1447 Map<String, AtomicLong> successMap = new HashMap<>();
1448 Random rand = new Random();
1449 for (Map.Entry<String, List<HRegionInfo>> entry : rsAndRMap.entrySet()) {
1450 String serverName = entry.getKey();
1451 AtomicLong successes = new AtomicLong(0);
1452 successMap.put(serverName, successes);
1453 if (entry.getValue().isEmpty()) {
1454 LOG.error(String.format("Regionserver not serving any regions - %s", serverName));
1455 } else if (this.allRegions) {
1456 for (HRegionInfo region : entry.getValue()) {
1457 tasks.add(new RegionServerTask(this.connection,
1458 serverName,
1459 region,
1460 regionServerSink,
1461 successes));
1462 }
1463 } else {
1464
1465 HRegionInfo region = entry.getValue().get(rand.nextInt(entry.getValue().size()));
1466 tasks.add(new RegionServerTask(this.connection,
1467 serverName,
1468 region,
1469 regionServerSink,
1470 successes));
1471 }
1472 }
1473 try {
1474 for (Future<Void> future : this.executor.invokeAll(tasks)) {
1475 try {
1476 future.get();
1477 } catch (ExecutionException e) {
1478 LOG.error("Sniff regionserver failed!", e);
1479 this.errorCode = ERROR_EXIT_CODE;
1480 }
1481 }
1482 if (this.allRegions) {
1483 for (Map.Entry<String, List<HRegionInfo>> entry : rsAndRMap.entrySet()) {
1484 String serverName = entry.getKey();
1485 LOG.info("Successfully read " + successMap.get(serverName) + " regions out of "
1486 + entry.getValue().size() + " on regionserver:" + serverName);
1487 }
1488 }
1489 } catch (InterruptedException e) {
1490 this.errorCode = ERROR_EXIT_CODE;
1491 LOG.error("Sniff regionserver interrupted!", e);
1492 }
1493 }
1494
1495 private Map<String, List<HRegionInfo>> filterRegionServerByName() {
1496 Map<String, List<HRegionInfo>> regionServerAndRegionsMap = this.getAllRegionServerByName();
1497 regionServerAndRegionsMap = this.doFilterRegionServerByName(regionServerAndRegionsMap);
1498 return regionServerAndRegionsMap;
1499 }
1500
1501 private Map<String, List<HRegionInfo>> getAllRegionServerByName() {
1502 Map<String, List<HRegionInfo>> rsAndRMap = new HashMap<String, List<HRegionInfo>>();
1503 Table table = null;
1504 RegionLocator regionLocator = null;
1505 try {
1506 if (LOG.isDebugEnabled()) {
1507 LOG.debug(String.format("reading list of tables and locations"));
1508 }
1509 HTableDescriptor[] tableDescs = this.admin.listTables();
1510 List<HRegionInfo> regions = null;
1511 for (HTableDescriptor tableDesc : tableDescs) {
1512 table = this.admin.getConnection().getTable(tableDesc.getTableName());
1513 regionLocator = this.admin.getConnection().getRegionLocator(tableDesc.getTableName());
1514
1515 for (HRegionLocation location : regionLocator.getAllRegionLocations()) {
1516 ServerName rs = location.getServerName();
1517 String rsName = rs.getHostname();
1518 HRegionInfo r = location.getRegionInfo();
1519
1520 if (rsAndRMap.containsKey(rsName)) {
1521 regions = rsAndRMap.get(rsName);
1522 } else {
1523 regions = new ArrayList<HRegionInfo>();
1524 rsAndRMap.put(rsName, regions);
1525 }
1526 regions.add(r);
1527 }
1528 table.close();
1529 }
1530
1531
1532 for (ServerName rs : this.admin.getClusterStatus().getServers()) {
1533 String rsName = rs.getHostname();
1534 if (!rsAndRMap.containsKey(rsName)) {
1535 rsAndRMap.put(rsName, Collections.<HRegionInfo>emptyList());
1536 }
1537 }
1538 } catch (IOException e) {
1539 String msg = "Get HTables info failed";
1540 LOG.error(msg, e);
1541 this.errorCode = INIT_ERROR_EXIT_CODE;
1542 } finally {
1543 if (table != null) {
1544 try {
1545 table.close();
1546 } catch (IOException e) {
1547 LOG.warn("Close table failed", e);
1548 }
1549 }
1550 }
1551
1552 return rsAndRMap;
1553 }
1554
1555 private Map<String, List<HRegionInfo>> doFilterRegionServerByName(
1556 Map<String, List<HRegionInfo>> fullRsAndRMap) {
1557
1558 Map<String, List<HRegionInfo>> filteredRsAndRMap = null;
1559
1560 if (this.targets != null && this.targets.length > 0) {
1561 filteredRsAndRMap = new HashMap<String, List<HRegionInfo>>();
1562 Pattern pattern = null;
1563 Matcher matcher = null;
1564 boolean regExpFound = false;
1565 for (String rsName : this.targets) {
1566 if (this.useRegExp) {
1567 regExpFound = false;
1568 pattern = Pattern.compile(rsName);
1569 for (Map.Entry<String, List<HRegionInfo>> entry : fullRsAndRMap.entrySet()) {
1570 matcher = pattern.matcher(entry.getKey());
1571 if (matcher.matches()) {
1572 filteredRsAndRMap.put(entry.getKey(), entry.getValue());
1573 regExpFound = true;
1574 }
1575 }
1576 if (!regExpFound) {
1577 LOG.info("No RegionServerInfo found, regionServerPattern:" + rsName);
1578 }
1579 } else {
1580 if (fullRsAndRMap.containsKey(rsName)) {
1581 filteredRsAndRMap.put(rsName, fullRsAndRMap.get(rsName));
1582 } else {
1583 LOG.info("No RegionServerInfo found, regionServerName:" + rsName);
1584 }
1585 }
1586 }
1587 } else {
1588 filteredRsAndRMap = fullRsAndRMap;
1589 }
1590 return filteredRsAndRMap;
1591 }
1592 }
1593
1594 public static void main(String[] args) throws Exception {
1595 final Configuration conf = HBaseConfiguration.create();
1596
1597
1598 new GenericOptionsParser(conf, args);
1599
1600 int numThreads = conf.getInt("hbase.canary.threads.num", MAX_THREADS_NUM);
1601 LOG.info("Number of execution threads " + numThreads);
1602
1603 ExecutorService executor = new ScheduledThreadPoolExecutor(numThreads);
1604
1605 Class<? extends Sink> sinkClass =
1606 conf.getClass("hbase.canary.sink.class", RegionServerStdOutSink.class, Sink.class);
1607 Sink sink = ReflectionUtils.newInstance(sinkClass);
1608
1609 int exitCode = ToolRunner.run(conf, new Canary(executor, sink), args);
1610 executor.shutdown();
1611 System.exit(exitCode);
1612 }
1613 }