View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.snapshot;
20  
21  import java.io.IOException;
22  import java.io.FileNotFoundException;
23  import java.net.URI;
24  import java.text.SimpleDateFormat;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.Date;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.concurrent.ConcurrentHashMap;
31  import java.util.concurrent.ExecutorService;
32  import java.util.concurrent.atomic.AtomicInteger;
33  import java.util.concurrent.atomic.AtomicLong;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  
38  import org.apache.hadoop.fs.Path;
39  import org.apache.hadoop.fs.FileStatus;
40  import org.apache.hadoop.fs.FileSystem;
41  import org.apache.hadoop.hbase.classification.InterfaceAudience;
42  import org.apache.hadoop.hbase.classification.InterfaceStability;
43  import org.apache.hadoop.conf.Configured;
44  import org.apache.hadoop.hbase.HRegionInfo;
45  import org.apache.hadoop.hbase.TableName;
46  import org.apache.hadoop.util.StringUtils;
47  import org.apache.hadoop.util.Tool;
48  import org.apache.hadoop.util.ToolRunner;
49  
50  import org.apache.hadoop.conf.Configuration;
51  import org.apache.hadoop.hbase.HBaseConfiguration;
52  import org.apache.hadoop.hbase.io.HFileLink;
53  import org.apache.hadoop.hbase.io.WALLink;
54  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
55  import org.apache.hadoop.hbase.protobuf.generated.SnapshotProtos.SnapshotRegionManifest;
56  import org.apache.hadoop.hbase.util.FSUtils;
57  
58  /**
59   * Tool for dumping snapshot information.
60   * <ol>
61   * <li> Table Descriptor
62   * <li> Snapshot creation time, type, format version, ...
63   * <li> List of hfiles and wals
64   * <li> Stats about hfiles and logs sizes, percentage of shared with the source table, ...
65   * </ol>
66   */
67  @InterfaceAudience.Public
68  @InterfaceStability.Evolving
69  public final class SnapshotInfo extends Configured implements Tool {
70    private static final Log LOG = LogFactory.getLog(SnapshotInfo.class);
71  
72    /**
73     * Statistics about the snapshot
74     * <ol>
75     * <li> How many store files and logs are in the archive
76     * <li> How many store files and logs are shared with the table
77     * <li> Total store files and logs size and shared amount
78     * </ol>
79     */
80    public static class SnapshotStats {
81      /** Information about the file referenced by the snapshot */
82      static class FileInfo {
83        private final boolean corrupted;
84        private final boolean inArchive;
85        private final long size;
86  
87        FileInfo(final boolean inArchive, final long size, final boolean corrupted) {
88          this.corrupted = corrupted;
89          this.inArchive = inArchive;
90          this.size = size;
91        }
92  
93        /** @return true if the file is in the archive */
94        public boolean inArchive() {
95          return this.inArchive;
96        }
97  
98        /** @return true if the file is corrupted */
99        public boolean isCorrupted() {
100         return this.corrupted;
101       }
102 
103       /** @return true if the file is missing */
104       public boolean isMissing() {
105         return this.size < 0;
106       }
107 
108       /** @return the file size */
109       public long getSize() {
110         return this.size;
111       }
112 
113       String getStateToString() {
114         if (isCorrupted()) return "CORRUPTED";
115         if (isMissing()) return "NOT FOUND";
116         if (inArchive()) return "archive";
117         return null;
118       }
119     }
120 
121     private AtomicInteger hfileArchiveCount = new AtomicInteger();
122     private AtomicInteger hfilesCorrupted = new AtomicInteger();
123     private AtomicInteger hfilesMissing = new AtomicInteger();
124     private AtomicInteger hfilesCount = new AtomicInteger();
125     private AtomicInteger logsMissing = new AtomicInteger();
126     private AtomicInteger logsCount = new AtomicInteger();
127     private AtomicLong hfileArchiveSize = new AtomicLong();
128     private AtomicLong hfileSize = new AtomicLong();
129     private AtomicLong nonSharedHfilesArchiveSize = new AtomicLong();
130     private AtomicLong logSize = new AtomicLong();
131 
132     private final SnapshotDescription snapshot;
133     private final TableName snapshotTable;
134     private final Configuration conf;
135     private final FileSystem fs;
136 
137     SnapshotStats(final Configuration conf, final FileSystem fs, final SnapshotDescription snapshot)
138     {
139       this.snapshot = snapshot;
140       this.snapshotTable = TableName.valueOf(snapshot.getTable());
141       this.conf = conf;
142       this.fs = fs;
143     }
144 
145     /** @return the snapshot descriptor */
146     public SnapshotDescription getSnapshotDescription() {
147       return this.snapshot;
148     }
149 
150     /** @return true if the snapshot is corrupted */
151     public boolean isSnapshotCorrupted() {
152       return hfilesMissing.get() > 0 ||
153              logsMissing.get() > 0 ||
154              hfilesCorrupted.get() > 0;
155     }
156 
157     /** @return the number of available store files */
158     public int getStoreFilesCount() {
159       return hfilesCount.get() + hfileArchiveCount.get();
160     }
161 
162     /** @return the number of available store files in the archive */
163     public int getArchivedStoreFilesCount() {
164       return hfileArchiveCount.get();
165     }
166 
167     /** @return the number of available log files */
168     public int getLogsCount() {
169       return logsCount.get();
170     }
171 
172     /** @return the number of missing store files */
173     public int getMissingStoreFilesCount() {
174       return hfilesMissing.get();
175     }
176 
177     /** @return the number of corrupted store files */
178     public int getCorruptedStoreFilesCount() {
179       return hfilesCorrupted.get();
180     }
181 
182     /** @return the number of missing log files */
183     public int getMissingLogsCount() {
184       return logsMissing.get();
185     }
186 
187     /** @return the total size of the store files referenced by the snapshot */
188     public long getStoreFilesSize() {
189       return hfileSize.get() + hfileArchiveSize.get();
190     }
191 
192     /** @return the total size of the store files shared */
193     public long getSharedStoreFilesSize() {
194       return hfileSize.get();
195     }
196 
197     /** @return the total size of the store files in the archive */
198     public long getArchivedStoreFileSize() {
199       return hfileArchiveSize.get();
200     }
201 
202     /** @return the total size of the store files in the archive which is not shared
203      *    with other snapshots and tables.
204      *    This is only calculated when getSnapshotStats(Configuration, SnapshotDescription, Map)
205      *    is called with a non-null Map
206      */
207     public long getNonSharedArchivedStoreFilesSize() {
208       return nonSharedHfilesArchiveSize.get();
209     }
210 
211     /** @return the percentage of the shared store files */
212     public float getSharedStoreFilePercentage() {
213       return ((float)hfileSize.get() / (hfileSize.get() + hfileArchiveSize.get())) * 100;
214     }
215 
216     /** @return the total log size */
217     public long getLogsSize() {
218       return logSize.get();
219     }
220 
221     /** Check if for a give file in archive, if there are other snapshots/tables still
222      * reference it.
223      * @param filePath file path in archive
224      * @param snapshotFilesMap a map for store files in snapshots about how many snapshots refer
225      *                         to it.
226      * @return true or false
227      */
228     private boolean isArchivedFileStillReferenced(final Path filePath,
229         final Map<Path, Integer> snapshotFilesMap) {
230 
231       Integer c = snapshotFilesMap.get(filePath);
232 
233       // Check if there are other snapshots or table from clone_snapshot() (via back-reference)
234       // still reference to it.
235       if ((c != null) && (c == 1)) {
236         Path parentDir = filePath.getParent();
237         Path backRefDir = HFileLink.getBackReferencesDir(parentDir, filePath.getName());
238         try {
239           if (FSUtils.listStatus(fs, backRefDir) == null) {
240             return false;
241           }
242         } catch (IOException e) {
243           // For the purpose of this function, IOException is ignored and treated as
244           // the file is still being referenced.
245         }
246       }
247       return true;
248     }
249 
250     /**
251      * Add the specified store file to the stats
252      * @param region region encoded Name
253      * @param family family name
254      * @param storeFile store file name
255      * @param filesMap store files map for all snapshots, it may be null
256      * @return the store file information
257      */
258     FileInfo addStoreFile(final HRegionInfo region, final String family,
259         final SnapshotRegionManifest.StoreFile storeFile,
260         final Map<Path, Integer> filesMap) throws IOException {
261       HFileLink link = HFileLink.build(conf, snapshotTable, region.getEncodedName(),
262               family, storeFile.getName());
263       boolean isCorrupted = false;
264       boolean inArchive = false;
265       long size = -1;
266       try {
267         if ((inArchive = fs.exists(link.getArchivePath()))) {
268           size = fs.getFileStatus(link.getArchivePath()).getLen();
269           hfileArchiveSize.addAndGet(size);
270           hfileArchiveCount.incrementAndGet();
271 
272           // If store file is not shared with other snapshots and tables,
273           // increase nonSharedHfilesArchiveSize
274           if ((filesMap != null) &&
275               !isArchivedFileStillReferenced(link.getArchivePath(), filesMap)) {
276             nonSharedHfilesArchiveSize.addAndGet(size);
277           }
278         } else {
279           size = link.getFileStatus(fs).getLen();
280           hfileSize.addAndGet(size);
281           hfilesCount.incrementAndGet();
282         }
283         isCorrupted = (storeFile.hasFileSize() && storeFile.getFileSize() != size);
284         if (isCorrupted) hfilesCorrupted.incrementAndGet();
285       } catch (FileNotFoundException e) {
286         hfilesMissing.incrementAndGet();
287       }
288       return new FileInfo(inArchive, size, isCorrupted);
289     }
290 
291     /**
292      * Add the specified log file to the stats
293      * @param server server name
294      * @param logfile log file name
295      * @return the log information
296      */
297     FileInfo addLogFile(final String server, final String logfile) throws IOException {
298       WALLink logLink = new WALLink(conf, server, logfile);
299       long size = -1;
300       try {
301         size = logLink.getFileStatus(fs).getLen();
302         logSize.addAndGet(size);
303         logsCount.incrementAndGet();
304       } catch (FileNotFoundException e) {
305         logsMissing.incrementAndGet();
306       }
307       return new FileInfo(false, size, false);
308     }
309   }
310 
311   private boolean printSizeInBytes = false;
312   private FileSystem fs;
313   private Path rootDir;
314 
315   private SnapshotManifest snapshotManifest;
316 
317   @Override
318   @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="REC_CATCH_EXCEPTION",
319     justification="Intentional")
320   public int run(String[] args) throws IOException, InterruptedException {
321     final Configuration conf = getConf();
322     boolean listSnapshots = false;
323     String snapshotName = null;
324     boolean showSchema = false;
325     boolean showFiles = false;
326     boolean showStats = false;
327 
328     // Process command line args
329     for (int i = 0; i < args.length; i++) {
330       String cmd = args[i];
331       try {
332         if (cmd.equals("-snapshot")) {
333           snapshotName = args[++i];
334         } else if (cmd.equals("-files")) {
335           showFiles = true;
336           showStats = true;
337         } else if (cmd.equals("-stats")) {
338           showStats = true;
339         } else if (cmd.equals("-schema")) {
340           showSchema = true;
341         } else if (cmd.equals("-remote-dir")) {
342           Path sourceDir = new Path(args[++i]);
343           URI defaultFs = sourceDir.getFileSystem(conf).getUri();
344           FSUtils.setFsDefault(conf, new Path(defaultFs));
345           FSUtils.setRootDir(conf, sourceDir);
346         } else if (cmd.equals("-list-snapshots")) {
347           listSnapshots = true;
348         } else if (cmd.equals("-size-in-bytes")) {
349           printSizeInBytes = true;
350         } else if (cmd.equals("-h") || cmd.equals("--help")) {
351           printUsageAndExit();
352         } else {
353           System.err.println("UNEXPECTED: " + cmd);
354           printUsageAndExit();
355         }
356       } catch (Exception e) {
357         printUsageAndExit(); // FindBugs: REC_CATCH_EXCEPTION
358       }
359     }
360 
361     // List Available Snapshots
362     if (listSnapshots) {
363       SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
364       System.out.printf("%-20s | %-20s | %s%n", "SNAPSHOT", "CREATION TIME", "TABLE NAME");
365       for (SnapshotDescription desc: getSnapshotList(conf)) {
366         System.out.printf("%-20s | %20s | %s%n",
367                           desc.getName(),
368                           df.format(new Date(desc.getCreationTime())),
369                           desc.getTable());
370       }
371       return 0;
372     }
373 
374     if (snapshotName == null) {
375       System.err.println("Missing snapshot name!");
376       printUsageAndExit();
377       return 1;
378     }
379 
380     rootDir = FSUtils.getRootDir(conf);
381     fs = FileSystem.get(rootDir.toUri(), conf);
382     LOG.debug("fs=" + fs.getUri().toString() + " root=" + rootDir);
383 
384     // Load snapshot information
385     if (!loadSnapshotInfo(snapshotName)) {
386       System.err.println("Snapshot '" + snapshotName + "' not found!");
387       return 1;
388     }
389 
390     printInfo();
391     if (showSchema) printSchema();
392     printFiles(showFiles, showStats);
393 
394     return 0;
395   }
396 
397   /**
398    * Load snapshot info and table descriptor for the specified snapshot
399    * @param snapshotName name of the snapshot to load
400    * @return false if snapshot is not found
401    */
402   private boolean loadSnapshotInfo(final String snapshotName) throws IOException {
403     Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
404     if (!fs.exists(snapshotDir)) {
405       LOG.warn("Snapshot '" + snapshotName + "' not found in: " + snapshotDir);
406       return false;
407     }
408 
409     SnapshotDescription snapshotDesc = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir);
410     snapshotManifest = SnapshotManifest.open(getConf(), fs, snapshotDir, snapshotDesc);
411     return true;
412   }
413 
414   /**
415    * Dump the {@link SnapshotDescription}
416    */
417   private void printInfo() {
418     SnapshotDescription snapshotDesc = snapshotManifest.getSnapshotDescription();
419     SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
420     System.out.println("Snapshot Info");
421     System.out.println("----------------------------------------");
422     System.out.println("   Name: " + snapshotDesc.getName());
423     System.out.println("   Type: " + snapshotDesc.getType());
424     System.out.println("  Table: " + snapshotDesc.getTable());
425     System.out.println(" Format: " + snapshotDesc.getVersion());
426     System.out.println("Created: " + df.format(new Date(snapshotDesc.getCreationTime())));
427     System.out.println();
428   }
429 
430   /**
431    * Dump the {@link HTableDescriptor}
432    */
433   private void printSchema() {
434     System.out.println("Table Descriptor");
435     System.out.println("----------------------------------------");
436     System.out.println(snapshotManifest.getTableDescriptor().toString());
437     System.out.println();
438   }
439 
440   /**
441    * Collect the hfiles and logs statistics of the snapshot and
442    * dump the file list if requested and the collected information.
443    */
444   private void printFiles(final boolean showFiles, final boolean showStats) throws IOException {
445     if (showFiles) {
446       System.out.println("Snapshot Files");
447       System.out.println("----------------------------------------");
448     }
449 
450     // Collect information about hfiles and logs in the snapshot
451     final SnapshotDescription snapshotDesc = snapshotManifest.getSnapshotDescription();
452     final String table = snapshotDesc.getTable();
453     final SnapshotStats stats = new SnapshotStats(this.getConf(), this.fs, snapshotDesc);
454     SnapshotReferenceUtil.concurrentVisitReferencedFiles(getConf(), fs, snapshotManifest,
455         "SnapshotInfo", new SnapshotReferenceUtil.SnapshotVisitor() {
456           @Override public void storeFile(final HRegionInfo regionInfo, final String family,
457               final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
458             if (storeFile.hasReference()) return;
459 
460             SnapshotStats.FileInfo info = stats.addStoreFile(regionInfo, family, storeFile, null);
461             if (showFiles) {
462               String state = info.getStateToString();
463               System.out.printf("%8s %s/%s/%s/%s %s%n",
464                   (info.isMissing() ? "-" : fileSizeToString(info.getSize())), table,
465                   regionInfo.getEncodedName(), family, storeFile.getName(),
466                   state == null ? "" : "(" + state + ")");
467             }
468           }
469         });
470 
471     // Dump the stats
472     System.out.println();
473     if (stats.isSnapshotCorrupted()) {
474       System.out.println("**************************************************************");
475       System.out.printf("BAD SNAPSHOT: %d hfile(s) and %d log(s) missing.%n",
476         stats.getMissingStoreFilesCount(), stats.getMissingLogsCount());
477       System.out.printf("              %d hfile(s) corrupted.%n",
478         stats.getCorruptedStoreFilesCount());
479       System.out.println("**************************************************************");
480     }
481 
482     if (showStats) {
483       System.out.printf("%d HFiles (%d in archive), total size %s (%.2f%% %s shared with the source table)%n",
484         stats.getStoreFilesCount(), stats.getArchivedStoreFilesCount(),
485         fileSizeToString(stats.getStoreFilesSize()),
486         stats.getSharedStoreFilePercentage(),
487         fileSizeToString(stats.getSharedStoreFilesSize())
488       );
489       System.out.printf("%d Logs, total size %s%n",
490         stats.getLogsCount(), fileSizeToString(stats.getLogsSize()));
491       System.out.println();
492     }
493   }
494 
495   private String fileSizeToString(long size) {
496     return printSizeInBytes ? Long.toString(size) : StringUtils.humanReadableInt(size);
497   }
498 
499   private void printUsageAndExit() {
500     System.err.printf("Usage: bin/hbase snapshot info [options]%n");
501     System.err.println(" where [options] are:");
502     System.err.println("  -h|-help                Show this help and exit.");
503     System.err.println("  -remote-dir             Root directory that contains the snapshots.");
504     System.err.println("  -list-snapshots         List all the available snapshots and exit.");
505     System.err.println("  -size-in-bytes          Print the size of the files in bytes.");
506     System.err.println("  -snapshot NAME          Snapshot to examine.");
507     System.err.println("  -files                  Files and logs list.");
508     System.err.println("  -stats                  Files and logs stats.");
509     System.err.println("  -schema                 Describe the snapshotted table.");
510     System.err.println();
511     System.err.println("Examples:");
512     System.err.println("  hbase snapshot info \\");
513     System.err.println("    -snapshot MySnapshot -files");
514     System.exit(1);
515   }
516 
517   /**
518    * Returns the snapshot stats
519    * @param conf the {@link Configuration} to use
520    * @param snapshot {@link SnapshotDescription} to get stats from
521    * @return the snapshot stats
522    */
523   public static SnapshotStats getSnapshotStats(final Configuration conf,
524       final SnapshotDescription snapshot) throws IOException {
525     return getSnapshotStats(conf, snapshot, null);
526   }
527 
528   /**
529    * Returns the snapshot stats
530    * @param conf the {@link Configuration} to use
531    * @param snapshot  {@link SnapshotDescription} to get stats from
532    * @param filesMap {@link Map} store files map for all snapshots, it may be null
533    * @return the snapshot stats
534    */
535   public static SnapshotStats getSnapshotStats(final Configuration conf,
536       final SnapshotDescription snapshot,
537       final Map<Path, Integer> filesMap) throws IOException {
538     Path rootDir = FSUtils.getRootDir(conf);
539     FileSystem fs = FileSystem.get(rootDir.toUri(), conf);
540     Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshot, rootDir);
541     SnapshotManifest manifest = SnapshotManifest.open(conf, fs, snapshotDir, snapshot);
542     final SnapshotStats stats = new SnapshotStats(conf, fs, snapshot);
543     SnapshotReferenceUtil.concurrentVisitReferencedFiles(conf, fs, manifest,
544         "SnapshotsStatsAggregation", new SnapshotReferenceUtil.SnapshotVisitor() {
545           @Override
546           public void storeFile(final HRegionInfo regionInfo, final String family,
547               final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
548             if (!storeFile.hasReference()) {
549               stats.addStoreFile(regionInfo, family, storeFile, filesMap);
550             }
551           }});
552     return stats;
553   }
554 
555   /**
556    * Returns the list of available snapshots in the specified location
557    * @param conf the {@link Configuration} to use
558    * @return the list of snapshots
559    */
560   public static List<SnapshotDescription> getSnapshotList(final Configuration conf)
561       throws IOException {
562     Path rootDir = FSUtils.getRootDir(conf);
563     FileSystem fs = FileSystem.get(rootDir.toUri(), conf);
564     Path snapshotDir = SnapshotDescriptionUtils.getSnapshotsDir(rootDir);
565     FileStatus[] snapshots = fs.listStatus(snapshotDir,
566       new SnapshotDescriptionUtils.CompletedSnaphotDirectoriesFilter(fs));
567     List<SnapshotDescription> snapshotLists =
568       new ArrayList<SnapshotDescription>(snapshots.length);
569     for (FileStatus snapshotDirStat: snapshots) {
570       snapshotLists.add(SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDirStat.getPath()));
571     }
572     return snapshotLists;
573   }
574 
575   /**
576    * Gets the store files map for snapshot
577    * @param conf the {@link Configuration} to use
578    * @param snapshot {@link SnapshotDescription} to get stats from
579    * @param exec the {@link ExecutorService} to use
580    * @param filesMap {@link Map} the map to put the mapping entries
581    * @param uniqueHFilesArchiveSize {@link AtomicLong} the accumulated store file size in archive
582    * @param uniqueHFilesSize {@link AtomicLong} the accumulated store file size shared
583    * @return the snapshot stats
584    */
585   private static void getSnapshotFilesMap(final Configuration conf,
586       final SnapshotDescription snapshot, final ExecutorService exec,
587       final ConcurrentHashMap<Path, Integer> filesMap,
588       final AtomicLong uniqueHFilesArchiveSize, final AtomicLong uniqueHFilesSize)
589       throws IOException {
590     Path rootDir = FSUtils.getRootDir(conf);
591     final FileSystem fs = FileSystem.get(rootDir.toUri(), conf);
592 
593     Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshot, rootDir);
594     SnapshotManifest manifest = SnapshotManifest.open(conf, fs, snapshotDir, snapshot);
595     SnapshotReferenceUtil.concurrentVisitReferencedFiles(conf, fs, manifest, exec,
596         new SnapshotReferenceUtil.SnapshotVisitor() {
597           @Override public void storeFile(final HRegionInfo regionInfo, final String family,
598               final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
599             if (!storeFile.hasReference()) {
600               HFileLink link = HFileLink
601                   .build(conf, TableName.valueOf(snapshot.getTable()), regionInfo.getEncodedName(),
602                       family, storeFile.getName());
603               long size;
604               Integer count;
605               Path p;
606               AtomicLong al;
607               int c = 0;
608 
609               if (fs.exists(link.getArchivePath())) {
610                 p = link.getArchivePath();
611                 al = uniqueHFilesArchiveSize;
612                 size = fs.getFileStatus(p).getLen();
613               } else {
614                 p = link.getOriginPath();
615                 al = uniqueHFilesSize;
616                 size = link.getFileStatus(fs).getLen();
617               }
618 
619               // If it has been counted, do not double count
620               count = filesMap.get(p);
621               if (count != null) {
622                 c = count.intValue();
623               } else {
624                 al.addAndGet(size);
625               }
626 
627               filesMap.put(p, ++c);
628             }
629           }
630         });
631   }
632 
633   /**
634    * Returns the map of store files based on path for all snapshots
635    * @param conf the {@link Configuration} to use
636    * @param uniqueHFilesArchiveSize pass out the size for store files in archive
637    * @param uniqueHFilesSize pass out the size for store files shared
638    * @return the map of store files
639    */
640   public static Map<Path, Integer> getSnapshotsFilesMap(final Configuration conf,
641       AtomicLong uniqueHFilesArchiveSize, AtomicLong uniqueHFilesSize) throws IOException {
642     List<SnapshotDescription> snapshotList = getSnapshotList(conf);
643 
644     if (snapshotList.size() == 0) {
645       return Collections.emptyMap();
646     }
647 
648     ConcurrentHashMap<Path, Integer> fileMap = new ConcurrentHashMap<>();
649 
650     ExecutorService exec = SnapshotManifest.createExecutor(conf, "SnapshotsFilesMapping");
651 
652     try {
653       for (final SnapshotDescription snapshot : snapshotList) {
654         getSnapshotFilesMap(conf, snapshot, exec, fileMap, uniqueHFilesArchiveSize,
655             uniqueHFilesSize);
656       }
657     } finally {
658       exec.shutdown();
659     }
660 
661     return fileMap;
662   }
663 
664   /**
665    * The guts of the {@link #main} method.
666    * Call this method to avoid the {@link #main(String[])} System.exit.
667    * @param args
668    * @return errCode
669    * @throws Exception
670    */
671   static int innerMain(final String [] args) throws Exception {
672     return ToolRunner.run(HBaseConfiguration.create(), new SnapshotInfo(), args);
673   }
674 
675   public static void main(String[] args) throws Exception {
676      System.exit(innerMain(args));
677   }
678 }