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.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
47  import org.apache.hadoop.util.StringUtils;
48  import org.apache.hadoop.util.Tool;
49  import org.apache.hadoop.util.ToolRunner;
50  
51  import org.apache.hadoop.conf.Configuration;
52  import org.apache.hadoop.hbase.HBaseConfiguration;
53  import org.apache.hadoop.hbase.io.HFileLink;
54  import org.apache.hadoop.hbase.io.WALLink;
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 | %-20s | %s%n", "SNAPSHOT", "CREATION TIME", "TTL IN SEC",
365               "TABLE NAME");
366       for (SnapshotDescription desc: getSnapshotList(conf)) {
367         System.out.printf("%-20s | %20s | %20s | %s%n", desc.getName(),
368                 df.format(new Date(desc.getCreationTime())), desc.getTtl(),
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("    Ttl: " + snapshotDesc.getTtl());
428     System.out.println("  Owner: " + snapshotDesc.getOwner());
429     System.out.println();
430   }
431 
432   /**
433    * Dump the {@link HTableDescriptor}
434    */
435   private void printSchema() {
436     System.out.println("Table Descriptor");
437     System.out.println("----------------------------------------");
438     System.out.println(snapshotManifest.getTableDescriptor().toString());
439     System.out.println();
440   }
441 
442   /**
443    * Collect the hfiles and logs statistics of the snapshot and
444    * dump the file list if requested and the collected information.
445    */
446   private void printFiles(final boolean showFiles, final boolean showStats) throws IOException {
447     if (showFiles) {
448       System.out.println("Snapshot Files");
449       System.out.println("----------------------------------------");
450     }
451 
452     // Collect information about hfiles and logs in the snapshot
453     final SnapshotDescription snapshotDesc = snapshotManifest.getSnapshotDescription();
454     final String table = snapshotDesc.getTable();
455     final SnapshotStats stats = new SnapshotStats(this.getConf(), this.fs, snapshotDesc);
456     SnapshotReferenceUtil.concurrentVisitReferencedFiles(getConf(), fs, snapshotManifest,
457         "SnapshotInfo", new SnapshotReferenceUtil.SnapshotVisitor() {
458           @Override public void storeFile(final HRegionInfo regionInfo, final String family,
459               final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
460             if (storeFile.hasReference()) return;
461 
462             SnapshotStats.FileInfo info = stats.addStoreFile(regionInfo, family, storeFile, null);
463             if (showFiles) {
464               String state = info.getStateToString();
465               System.out.printf("%8s %s/%s/%s/%s %s%n",
466                   (info.isMissing() ? "-" : fileSizeToString(info.getSize())), table,
467                   regionInfo.getEncodedName(), family, storeFile.getName(),
468                   state == null ? "" : "(" + state + ")");
469             }
470           }
471         });
472 
473     // Dump the stats
474     System.out.println();
475     if (stats.isSnapshotCorrupted()) {
476       System.out.println("**************************************************************");
477       System.out.printf("BAD SNAPSHOT: %d hfile(s) and %d log(s) missing.%n",
478         stats.getMissingStoreFilesCount(), stats.getMissingLogsCount());
479       System.out.printf("              %d hfile(s) corrupted.%n",
480         stats.getCorruptedStoreFilesCount());
481       System.out.println("**************************************************************");
482     }
483 
484     if (showStats) {
485       System.out.printf("%d HFiles (%d in archive), total size %s (%.2f%% %s shared with the source table)%n",
486         stats.getStoreFilesCount(), stats.getArchivedStoreFilesCount(),
487         fileSizeToString(stats.getStoreFilesSize()),
488         stats.getSharedStoreFilePercentage(),
489         fileSizeToString(stats.getSharedStoreFilesSize())
490       );
491       System.out.printf("%d Logs, total size %s%n",
492         stats.getLogsCount(), fileSizeToString(stats.getLogsSize()));
493       System.out.println();
494     }
495   }
496 
497   private String fileSizeToString(long size) {
498     return printSizeInBytes ? Long.toString(size) : StringUtils.humanReadableInt(size);
499   }
500 
501   private void printUsageAndExit() {
502     System.err.printf("Usage: bin/hbase snapshot info [options]%n");
503     System.err.println(" where [options] are:");
504     System.err.println("  -h|-help                Show this help and exit.");
505     System.err.println("  -remote-dir             Root directory that contains the snapshots.");
506     System.err.println("  -list-snapshots         List all the available snapshots and exit.");
507     System.err.println("  -size-in-bytes          Print the size of the files in bytes.");
508     System.err.println("  -snapshot NAME          Snapshot to examine.");
509     System.err.println("  -files                  Files and logs list.");
510     System.err.println("  -stats                  Files and logs stats.");
511     System.err.println("  -schema                 Describe the snapshotted table.");
512     System.err.println();
513     System.err.println("Examples:");
514     System.err.println("  hbase snapshot info \\");
515     System.err.println("    -snapshot MySnapshot -files");
516     System.exit(1);
517   }
518 
519   /**
520    * Returns the snapshot stats
521    * @param conf the {@link Configuration} to use
522    * @param snapshot {@link SnapshotDescription} to get stats from
523    * @return the snapshot stats
524    */
525   public static SnapshotStats getSnapshotStats(final Configuration conf,
526       final SnapshotDescription snapshot) throws IOException {
527     return getSnapshotStats(conf, snapshot, null);
528   }
529 
530   /**
531    * Returns the snapshot stats
532    * @param conf the {@link Configuration} to use
533    * @param snapshot  {@link SnapshotDescription} to get stats from
534    * @param filesMap {@link Map} store files map for all snapshots, it may be null
535    * @return the snapshot stats
536    */
537   public static SnapshotStats getSnapshotStats(final Configuration conf,
538       final SnapshotDescription snapshot,
539       final Map<Path, Integer> filesMap) throws IOException {
540     Path rootDir = FSUtils.getRootDir(conf);
541     FileSystem fs = FileSystem.get(rootDir.toUri(), conf);
542     Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshot, rootDir);
543     SnapshotManifest manifest = SnapshotManifest.open(conf, fs, snapshotDir, snapshot);
544     final SnapshotStats stats = new SnapshotStats(conf, fs, snapshot);
545     SnapshotReferenceUtil.concurrentVisitReferencedFiles(conf, fs, manifest,
546         "SnapshotsStatsAggregation", new SnapshotReferenceUtil.SnapshotVisitor() {
547           @Override
548           public void storeFile(final HRegionInfo regionInfo, final String family,
549               final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
550             if (!storeFile.hasReference()) {
551               stats.addStoreFile(regionInfo, family, storeFile, filesMap);
552             }
553           }});
554     return stats;
555   }
556 
557   /**
558    * Returns the list of available snapshots in the specified location
559    * @param conf the {@link Configuration} to use
560    * @return the list of snapshots
561    */
562   public static List<SnapshotDescription> getSnapshotList(final Configuration conf)
563       throws IOException {
564     Path rootDir = FSUtils.getRootDir(conf);
565     FileSystem fs = FileSystem.get(rootDir.toUri(), conf);
566     Path snapshotDir = SnapshotDescriptionUtils.getSnapshotsDir(rootDir);
567     FileStatus[] snapshots = fs.listStatus(snapshotDir,
568       new SnapshotDescriptionUtils.CompletedSnaphotDirectoriesFilter(fs));
569     List<SnapshotDescription> snapshotLists =
570       new ArrayList<SnapshotDescription>(snapshots.length);
571     for (FileStatus snapshotDirStat: snapshots) {
572       snapshotLists.add(SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDirStat.getPath()));
573     }
574     return snapshotLists;
575   }
576 
577   /**
578    * Gets the store files map for snapshot
579    * @param conf the {@link Configuration} to use
580    * @param snapshot {@link SnapshotDescription} to get stats from
581    * @param exec the {@link ExecutorService} to use
582    * @param filesMap {@link Map} the map to put the mapping entries
583    * @param uniqueHFilesArchiveSize {@link AtomicLong} the accumulated store file size in archive
584    * @param uniqueHFilesSize {@link AtomicLong} the accumulated store file size shared
585    */
586   private static void getSnapshotFilesMap(final Configuration conf,
587       final SnapshotDescription snapshot, final ExecutorService exec,
588       final ConcurrentHashMap<Path, Integer> filesMap,
589       final AtomicLong uniqueHFilesArchiveSize, final AtomicLong uniqueHFilesSize)
590       throws IOException {
591     Path rootDir = FSUtils.getRootDir(conf);
592     final FileSystem fs = FileSystem.get(rootDir.toUri(), conf);
593 
594     Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshot, rootDir);
595     SnapshotManifest manifest = SnapshotManifest.open(conf, fs, snapshotDir, snapshot);
596     SnapshotReferenceUtil.concurrentVisitReferencedFiles(conf, fs, manifest, exec,
597         new SnapshotReferenceUtil.SnapshotVisitor() {
598           @Override public void storeFile(final HRegionInfo regionInfo, final String family,
599               final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
600             if (!storeFile.hasReference()) {
601               HFileLink link = HFileLink
602                   .build(conf, TableName.valueOf(snapshot.getTable()), regionInfo.getEncodedName(),
603                       family, storeFile.getName());
604               long size;
605               Integer count;
606               Path p;
607               AtomicLong al;
608               int c = 0;
609 
610               if (fs.exists(link.getArchivePath())) {
611                 p = link.getArchivePath();
612                 al = uniqueHFilesArchiveSize;
613                 size = fs.getFileStatus(p).getLen();
614               } else {
615                 p = link.getOriginPath();
616                 al = uniqueHFilesSize;
617                 size = link.getFileStatus(fs).getLen();
618               }
619 
620               // If it has been counted, do not double count
621               count = filesMap.get(p);
622               if (count != null) {
623                 c = count.intValue();
624               } else {
625                 al.addAndGet(size);
626               }
627 
628               filesMap.put(p, ++c);
629             }
630           }
631         });
632   }
633 
634   /**
635    * Returns the map of store files based on path for all snapshots
636    * @param conf the {@link Configuration} to use
637    * @param uniqueHFilesArchiveSize pass out the size for store files in archive
638    * @param uniqueHFilesSize pass out the size for store files shared
639    * @return the map of store files
640    */
641   public static Map<Path, Integer> getSnapshotsFilesMap(final Configuration conf,
642       AtomicLong uniqueHFilesArchiveSize, AtomicLong uniqueHFilesSize) throws IOException {
643     List<SnapshotDescription> snapshotList = getSnapshotList(conf);
644 
645     if (snapshotList.size() == 0) {
646       return Collections.emptyMap();
647     }
648 
649     ConcurrentHashMap<Path, Integer> fileMap = new ConcurrentHashMap<>();
650 
651     ExecutorService exec = SnapshotManifest.createExecutor(conf, "SnapshotsFilesMapping");
652 
653     try {
654       for (final SnapshotDescription snapshot : snapshotList) {
655         getSnapshotFilesMap(conf, snapshot, exec, fileMap, uniqueHFilesArchiveSize,
656             uniqueHFilesSize);
657       }
658     } finally {
659       exec.shutdown();
660     }
661 
662     return fileMap;
663   }
664 
665   /**
666    * The guts of the {@link #main} method.
667    * Call this method to avoid the {@link #main(String[])} System.exit.
668    * @param args
669    * @return errCode
670    * @throws Exception
671    */
672   static int innerMain(final String [] args) throws Exception {
673     return ToolRunner.run(HBaseConfiguration.create(), new SnapshotInfo(), args);
674   }
675 
676   public static void main(String[] args) throws Exception {
677      System.exit(innerMain(args));
678   }
679 }