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