View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
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   * HBase Canary Tool, that that can be used to do
100  * "canary monitoring" of a running HBase cluster.
101  *
102  * Here are three modes
103  * 1. region mode - Foreach region tries to get one row per column family
104  * and outputs some information about failure or latency.
105  *
106  * 2. regionserver mode - Foreach regionserver tries to get one row from one table
107  * selected randomly and outputs some information about failure or latency.
108  *
109  * 3. zookeeper mode - for each zookeeper instance, selects a zNode and
110  * outputs some information about failure or latency.
111  */
112 @InterfaceAudience.Private
113 public final class Canary implements Tool {
114   // Sink interface used by the canary to outputs information
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   // Simple implementation of canary sink that allows to plot on
127   // file or standard output timings or failures.
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    * For each column family of the region tries to get one row and outputs the latency, or the
297    * failure.
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         // Can't do a get on empty start row so do a Scan of first element if any instead.
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      * Check writes for the canary table
423      * @return
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    * Get one row from a region on the regionserver and outputs the latency, or the failure.
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       // monitor one region on every region server
495       stopWatch.reset();
496       try {
497         tableName = region.getTable();
498         table = connection.getTable(tableName);
499         startKey = region.getStartKey();
500         // Can't do a get on empty start row so do a Scan of first element if any instead.
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         // This is ignored because it doesn't imply that the regionserver is dead
531       } catch (TableNotEnabledException tnee) {
532         // This is considered a success since we got a response.
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) {/* DO NOTHING */
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; // 10 mins
566   private static final int MAX_THREADS_NUM = 16; // #threads to contact regions
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; // threads to retrieve data from regionservers
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     // Process command line args
616     for (int i = 0; i < args.length; i++) {
617       String cmd = args[i];
618 
619       if (cmd.startsWith("-")) {
620         if (index >= 0) {
621           // command line args must be in the form: [opts] [table 1 [table 2 ...]]
622           System.err.println("Invalid command line options");
623           printUsageAndExit();
624         }
625 
626         if (cmd.equals("-help")) {
627           // user asked for help, print the help and quit.
628           printUsageAndExit();
629         } else if (cmd.equals("-daemon") && interval == 0) {
630           // user asked for daemon mode, set a default interval between checks
631           interval = DEFAULT_INTERVAL;
632         } else if (cmd.equals("-interval")) {
633           // user has specified an interval for canary breaths (-interval N)
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           // no options match
743           System.err.println(cmd + " options is invalid.");
744           printUsageAndExit();
745         }
746       } else if (index < 0) {
747         // keep track of first table name specified by the user
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     // Launches chore for refreshing kerberos credentials if security is enabled.
779     // Please see http://hbase.apache.org/book.html#_running_canary_in_a_kerberos_enabled_cluster
780     // for more details.
781     final ScheduledChore authChore = AuthUtil.getAuthChore(conf);
782     if (authChore != null) {
783       choreService = new ChoreService("CANARY_TOOL");
784       choreService.scheduleChore(authChore);
785     }
786 
787     // Start to prepare the stuffs
788     Monitor monitor = null;
789     Thread monitorThread = null;
790     long startTime = 0;
791     long currentTimeLength = 0;
792     // Get a connection to use in below.
793     try (Connection connection = ConnectionFactory.createConnection(this.conf)) {
794       do {
795         // Do monitor !!
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             // wait for 1 sec
803             Thread.sleep(1000);
804             // exit if any error occurs
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     } // try-with-resources close
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    * A Factory method for {@link Monitor}.
889    * Can be overridden by user.
890    * @param index a start index for monitor target
891    * @param args args passed from user
892    * @return a Monitor instance
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   // a Monitor super-class can be extended by users
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   // a monitor for region mode
999   private static class RegionMonitor extends Monitor {
1000     // 10 minutes
1001     private static final int DEFAULT_WRITE_TABLE_CHECK_PERIOD = 10 * 60 * 1000;
1002     // 1 days
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             // Check to see that each table name passed in the -readTableTimeouts argument is also passed as a monitor target.
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             // sniff canary table with write operation
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             // Check that the writeTable write operation latency does not exceed the configured timeout.
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      * canary entry point to monitor all the tables.
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    * Canary entry point for specified table.
1251    * @throws Exception
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    * Loops over regions that owns this table, and output some information about the state.
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   //  monitor for zookeeper mode
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   // a monitor for regionserver mode
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           // random select a region if flag not set
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         //get any live regionservers not serving any regions
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     // loading the generic options to conf
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 }