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