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