001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.snapshot;
019
020import static org.apache.hadoop.hbase.HConstants.DEFAULT_SNAPSHOT_TTL;
021
022import java.io.FileNotFoundException;
023import java.io.IOException;
024import java.net.URI;
025import java.text.SimpleDateFormat;
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.Date;
029import java.util.List;
030import java.util.Map;
031import java.util.Properties;
032import java.util.concurrent.ConcurrentHashMap;
033import java.util.concurrent.ExecutorService;
034import java.util.concurrent.atomic.AtomicInteger;
035import java.util.concurrent.atomic.AtomicLong;
036import org.apache.hadoop.conf.Configuration;
037import org.apache.hadoop.fs.FileStatus;
038import org.apache.hadoop.fs.FileSystem;
039import org.apache.hadoop.fs.Path;
040import org.apache.hadoop.hbase.TableName;
041import org.apache.hadoop.hbase.client.RegionInfo;
042import org.apache.hadoop.hbase.client.SnapshotDescription;
043import org.apache.hadoop.hbase.io.HFileLink;
044import org.apache.hadoop.hbase.io.WALLink;
045import org.apache.hadoop.hbase.util.AbstractHBaseTool;
046import org.apache.hadoop.hbase.util.CommonFSUtils;
047import org.apache.hadoop.hbase.util.Strings;
048import org.apache.yetus.audience.InterfaceAudience;
049import org.slf4j.Logger;
050import org.slf4j.LoggerFactory;
051
052import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine;
053import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLineParser;
054import org.apache.hbase.thirdparty.org.apache.commons.cli.DefaultParser;
055import org.apache.hbase.thirdparty.org.apache.commons.cli.Option;
056import org.apache.hbase.thirdparty.org.apache.commons.cli.Options;
057import org.apache.hbase.thirdparty.org.apache.commons.cli.ParseException;
058
059import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
060import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos;
061import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotRegionManifest;
062
063/**
064 * Tool for dumping snapshot information.
065 * <ol>
066 * <li>Table Descriptor
067 * <li>Snapshot creation time, type, format version, ...
068 * <li>List of hfiles and wals
069 * <li>Stats about hfiles and logs sizes, percentage of shared with the source table, ...
070 * </ol>
071 */
072@InterfaceAudience.Public
073public final class SnapshotInfo extends AbstractHBaseTool {
074  private static final Logger LOG = LoggerFactory.getLogger(SnapshotInfo.class);
075
076  private static final String EXPIRED_LABEL = "Yes";
077  private static final String NOT_EXPIRED_LABEL = "No";
078  private static final String TTL_FOREVER = "FOREVER";
079
080  static final class Options {
081    static final Option SNAPSHOT =
082      new Option(null, "snapshot", true, "The name of the snapshot to be detailed.");
083    static final Option REMOTE_DIR =
084      new Option(null, "remote-dir", true, "A custom root directory where snapshots are stored. "
085        + "Use it together with the --snapshot option.");
086    static final Option LIST_SNAPSHOTS =
087      new Option(null, "list-snapshots", false, "List all the available snapshots and exit.");
088    static final Option FILES =
089      new Option(null, "files", false, "The list of files retained by the specified snapshot. "
090        + "Use it together with the --snapshot option.");
091    static final Option STATS =
092      new Option(null, "stats", false, "Additional information about the specified snapshot. "
093        + "Use it together with the --snapshot option.");
094    static final Option SCHEMA = new Option(null, "schema", false,
095      "Show the descriptor of the table for the specified snapshot. "
096        + "Use it together with the --snapshot option.");
097    static final Option SIZE_IN_BYTES =
098      new Option(null, "size-in-bytes", false, "Print the size of the files in bytes. "
099        + "Use it together with the --snapshot and --files options.");
100  }
101
102  /**
103   * Statistics about the snapshot
104   * <ol>
105   * <li>How many store files and logs are in the archive
106   * <li>How many store files and logs are shared with the table
107   * <li>Total store files and logs size and shared amount
108   * </ol>
109   */
110  public static class SnapshotStats {
111    /** Information about the file referenced by the snapshot */
112    static class FileInfo {
113      private final boolean corrupted;
114      private final boolean inArchive;
115      private final long size;
116
117      FileInfo(final boolean inArchive, final long size, final boolean corrupted) {
118        this.corrupted = corrupted;
119        this.inArchive = inArchive;
120        this.size = size;
121      }
122
123      /** Returns true if the file is in the archive */
124      public boolean inArchive() {
125        return this.inArchive;
126      }
127
128      /** Returns true if the file is corrupted */
129      public boolean isCorrupted() {
130        return this.corrupted;
131      }
132
133      /** Returns true if the file is missing */
134      public boolean isMissing() {
135        return this.size < 0;
136      }
137
138      /** Returns the file size */
139      public long getSize() {
140        return this.size;
141      }
142
143      String getStateToString() {
144        if (isCorrupted()) return "CORRUPTED";
145        if (isMissing()) return "NOT FOUND";
146        if (inArchive()) return "archive";
147        return null;
148      }
149    }
150
151    private AtomicInteger hfilesArchiveCount = new AtomicInteger();
152    private AtomicInteger hfilesCorrupted = new AtomicInteger();
153    private AtomicInteger hfilesMissing = new AtomicInteger();
154    private AtomicInteger hfilesCount = new AtomicInteger();
155    private AtomicInteger hfilesMobCount = new AtomicInteger();
156    private AtomicInteger logsMissing = new AtomicInteger();
157    private AtomicInteger logsCount = new AtomicInteger();
158    private AtomicLong hfilesArchiveSize = new AtomicLong();
159    private AtomicLong hfilesSize = new AtomicLong();
160    private AtomicLong hfilesMobSize = new AtomicLong();
161    private AtomicLong nonSharedHfilesArchiveSize = new AtomicLong();
162    private AtomicLong logSize = new AtomicLong();
163
164    private final SnapshotProtos.SnapshotDescription snapshot;
165    private final TableName snapshotTable;
166    private final Configuration conf;
167    private final FileSystem fs;
168
169    SnapshotStats(final Configuration conf, final FileSystem fs,
170      final SnapshotDescription snapshot) {
171      this.snapshot = ProtobufUtil.createHBaseProtosSnapshotDesc(snapshot);
172      this.snapshotTable = snapshot.getTableName();
173      this.conf = conf;
174      this.fs = fs;
175    }
176
177    SnapshotStats(final Configuration conf, final FileSystem fs,
178      final SnapshotProtos.SnapshotDescription snapshot) {
179      this.snapshot = snapshot;
180      this.snapshotTable = TableName.valueOf(snapshot.getTable());
181      this.conf = conf;
182      this.fs = fs;
183    }
184
185    /** Returns the snapshot descriptor */
186    public SnapshotDescription getSnapshotDescription() {
187      return ProtobufUtil.createSnapshotDesc(this.snapshot);
188    }
189
190    /** Returns true if the snapshot is corrupted */
191    public boolean isSnapshotCorrupted() {
192      return hfilesMissing.get() > 0 || logsMissing.get() > 0 || hfilesCorrupted.get() > 0;
193    }
194
195    /** Returns the number of available store files */
196    public int getStoreFilesCount() {
197      return hfilesCount.get() + hfilesArchiveCount.get() + hfilesMobCount.get();
198    }
199
200    /** Returns the number of available store files in the archive */
201    public int getArchivedStoreFilesCount() {
202      return hfilesArchiveCount.get();
203    }
204
205    /** Returns the number of available store files in the mob dir */
206    public int getMobStoreFilesCount() {
207      return hfilesMobCount.get();
208    }
209
210    /** Returns the number of available log files */
211    public int getLogsCount() {
212      return logsCount.get();
213    }
214
215    /** Returns the number of missing store files */
216    public int getMissingStoreFilesCount() {
217      return hfilesMissing.get();
218    }
219
220    /** Returns the number of corrupted store files */
221    public int getCorruptedStoreFilesCount() {
222      return hfilesCorrupted.get();
223    }
224
225    /** Returns the number of missing log files */
226    public int getMissingLogsCount() {
227      return logsMissing.get();
228    }
229
230    /** Returns the total size of the store files referenced by the snapshot */
231    public long getStoreFilesSize() {
232      return hfilesSize.get() + hfilesArchiveSize.get() + hfilesMobSize.get();
233    }
234
235    /** Returns the total size of the store files shared */
236    public long getSharedStoreFilesSize() {
237      return hfilesSize.get();
238    }
239
240    /** Returns the total size of the store files in the archive */
241    public long getArchivedStoreFileSize() {
242      return hfilesArchiveSize.get();
243    }
244
245    /** Returns the total size of the store files in the mob store */
246    public long getMobStoreFilesSize() {
247      return hfilesMobSize.get();
248    }
249
250    /**
251     * @return the total size of the store files in the archive which is not shared with other
252     *         snapshots and tables This is only calculated when
253     *         {@link #getSnapshotStats(Configuration, SnapshotProtos.SnapshotDescription, Map)} is
254     *         called with a non-null Map
255     */
256    public long getNonSharedArchivedStoreFilesSize() {
257      return nonSharedHfilesArchiveSize.get();
258    }
259
260    /** Returns the percentage of the shared store files */
261    public float getSharedStoreFilePercentage() {
262      return getStoreFilesSize() == 0
263        ? 0
264        : ((float) hfilesSize.get() / (getStoreFilesSize())) * 100;
265    }
266
267    /** Returns the percentage of the mob store files */
268    public float getMobStoreFilePercentage() {
269      return getStoreFilesSize() == 0
270        ? 0
271        : ((float) hfilesMobSize.get() / (getStoreFilesSize())) * 100;
272    }
273
274    /** Returns the total log size */
275    public long getLogsSize() {
276      return logSize.get();
277    }
278
279    /**
280     * Check if for a give file in archive, if there are other snapshots/tables still reference it.
281     * @param filePath         file path in archive
282     * @param snapshotFilesMap a map for store files in snapshots about how many snapshots refer to
283     *                         it.
284     * @return true or false
285     */
286    private boolean isArchivedFileStillReferenced(final Path filePath,
287      final Map<Path, Integer> snapshotFilesMap) {
288
289      Integer c = snapshotFilesMap.get(filePath);
290
291      // Check if there are other snapshots or table from clone_snapshot() (via back-reference)
292      // still reference to it.
293      if ((c != null) && (c == 1)) {
294        Path parentDir = filePath.getParent();
295        Path backRefDir = HFileLink.getBackReferencesDir(parentDir, filePath.getName());
296        try {
297          if (CommonFSUtils.listStatus(fs, backRefDir) == null) {
298            return false;
299          }
300        } catch (IOException e) {
301          // For the purpose of this function, IOException is ignored and treated as
302          // the file is still being referenced.
303        }
304      }
305      return true;
306    }
307
308    /**
309     * Add the specified store file to the stats
310     * @param region    region encoded Name
311     * @param family    family name
312     * @param storeFile store file name
313     * @param filesMap  store files map for all snapshots, it may be null
314     * @return the store file information
315     */
316    FileInfo addStoreFile(final RegionInfo region, final String family,
317      final SnapshotRegionManifest.StoreFile storeFile, final Map<Path, Integer> filesMap)
318      throws IOException {
319      HFileLink link =
320        HFileLink.build(conf, snapshotTable, region.getEncodedName(), family, storeFile.getName());
321      boolean isCorrupted = false;
322      boolean inArchive = false;
323      long size = -1;
324      try {
325        if (fs.exists(link.getArchivePath())) {
326          inArchive = true;
327          size = fs.getFileStatus(link.getArchivePath()).getLen();
328          hfilesArchiveSize.addAndGet(size);
329          hfilesArchiveCount.incrementAndGet();
330
331          // If store file is not shared with other snapshots and tables,
332          // increase nonSharedHfilesArchiveSize
333          if (
334            (filesMap != null) && !isArchivedFileStillReferenced(link.getArchivePath(), filesMap)
335          ) {
336            nonSharedHfilesArchiveSize.addAndGet(size);
337          }
338        } else if (fs.exists(link.getMobPath())) {
339          inArchive = true;
340          size = fs.getFileStatus(link.getMobPath()).getLen();
341          hfilesMobSize.addAndGet(size);
342          hfilesMobCount.incrementAndGet();
343        } else {
344          size = link.getFileStatus(fs).getLen();
345          hfilesSize.addAndGet(size);
346          hfilesCount.incrementAndGet();
347        }
348        isCorrupted = (storeFile.hasFileSize() && storeFile.getFileSize() != size);
349        if (isCorrupted) hfilesCorrupted.incrementAndGet();
350      } catch (FileNotFoundException e) {
351        hfilesMissing.incrementAndGet();
352      }
353      return new FileInfo(inArchive, size, isCorrupted);
354    }
355
356    /**
357     * Add the specified log file to the stats
358     * @param server  server name
359     * @param logfile log file name
360     * @return the log information
361     */
362    FileInfo addLogFile(final String server, final String logfile) throws IOException {
363      WALLink logLink = new WALLink(conf, server, logfile);
364      long size = -1;
365      try {
366        size = logLink.getFileStatus(fs).getLen();
367        logSize.addAndGet(size);
368        logsCount.incrementAndGet();
369      } catch (FileNotFoundException e) {
370        logsMissing.incrementAndGet();
371      }
372      return new FileInfo(false, size, false);
373    }
374  }
375
376  private FileSystem fs;
377  private Path rootDir;
378
379  private SnapshotManifest snapshotManifest;
380
381  private boolean listSnapshots = false;
382  private String snapshotName;
383  private Path remoteDir;
384  private boolean showSchema = false;
385  private boolean showFiles = false;
386  private boolean showStats = false;
387  private boolean printSizeInBytes = false;
388
389  @Override
390  public int doWork() throws IOException, InterruptedException {
391    if (remoteDir != null) {
392      URI defaultFs = remoteDir.getFileSystem(conf).getUri();
393      CommonFSUtils.setFsDefault(conf, new Path(defaultFs));
394      CommonFSUtils.setRootDir(conf, remoteDir);
395    }
396
397    // List Available Snapshots
398    if (listSnapshots) {
399      SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
400      List<SnapshotDescription> snapshotDescriptions = getSnapshotList(conf);
401      System.out.printf("%-20s | %-20s | %-20s | %-20s | %s%n", "SNAPSHOT", "CREATION TIME",
402        "TTL(Sec)", "EXPIRED", "TABLE NAME");
403      long currentTime = System.currentTimeMillis();
404      for (SnapshotDescription desc : snapshotDescriptions) {
405        String ttlInfo =
406          desc.getTtl() == DEFAULT_SNAPSHOT_TTL ? TTL_FOREVER : String.valueOf(desc.getTtl());
407        String expired = SnapshotDescriptionUtils.isExpiredSnapshot(desc.getTtl(),
408          desc.getCreationTime(), currentTime) ? EXPIRED_LABEL : NOT_EXPIRED_LABEL;
409        System.out.printf("%-20s | %20s | %20s | %20s | %s%n", desc.getName(),
410          df.format(new Date(desc.getCreationTime())), ttlInfo, expired,
411          desc.getTableNameAsString());
412      }
413      return 0;
414    }
415
416    rootDir = CommonFSUtils.getRootDir(conf);
417    fs = FileSystem.get(rootDir.toUri(), conf);
418    LOG.debug("fs=" + fs.getUri().toString() + " root=" + rootDir);
419
420    // Load snapshot information
421    if (!loadSnapshotInfo(snapshotName)) {
422      System.err.println("Snapshot '" + snapshotName + "' not found!");
423      return 1;
424    }
425
426    printInfo();
427    if (showSchema) {
428      printSchema();
429    }
430    printFiles(showFiles, showStats);
431
432    return 0;
433  }
434
435  /**
436   * Load snapshot info and table descriptor for the specified snapshot
437   * @param snapshotName name of the snapshot to load
438   * @return false if snapshot is not found
439   */
440  private boolean loadSnapshotInfo(final String snapshotName) throws IOException {
441    Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
442    if (!fs.exists(snapshotDir)) {
443      LOG.warn("Snapshot '" + snapshotName + "' not found in: " + snapshotDir);
444      return false;
445    }
446
447    SnapshotProtos.SnapshotDescription snapshotDesc =
448      SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir);
449    snapshotManifest = SnapshotManifest.open(getConf(), fs, snapshotDir, snapshotDesc);
450    return true;
451  }
452
453  /**
454   * Dump the {@link SnapshotDescription}
455   */
456  private void printInfo() {
457    SnapshotProtos.SnapshotDescription snapshotDesc = snapshotManifest.getSnapshotDescription();
458    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
459    String ttlInfo = snapshotDesc.getTtl() == DEFAULT_SNAPSHOT_TTL
460      ? TTL_FOREVER
461      : String.valueOf(snapshotDesc.getTtl());
462    String expired = SnapshotDescriptionUtils.isExpiredSnapshot(snapshotDesc.getTtl(),
463      snapshotDesc.getCreationTime(), System.currentTimeMillis())
464        ? EXPIRED_LABEL
465        : NOT_EXPIRED_LABEL;
466    System.out.println("Snapshot Info");
467    System.out.println("-----------------------------------------");
468    System.out.println("    Name: " + snapshotDesc.getName());
469    System.out.println("    Type: " + snapshotDesc.getType());
470    System.out.println("   Table: " + snapshotDesc.getTable());
471    System.out.println("  Format: " + snapshotDesc.getVersion());
472    System.out.println(" Created: " + df.format(new Date(snapshotDesc.getCreationTime())));
473    System.out.println("Ttl(Sec): " + ttlInfo);
474    System.out.println(" Expired: " + expired);
475    System.out.println("   Owner: " + snapshotDesc.getOwner());
476    System.out.println();
477  }
478
479  /**
480   * Dump the {@link org.apache.hadoop.hbase.client.TableDescriptor}
481   */
482  private void printSchema() {
483    System.out.println("Table Descriptor");
484    System.out.println("----------------------------------------");
485    System.out.println(snapshotManifest.getTableDescriptor().toString());
486    System.out.println();
487  }
488
489  /**
490   * Collect the hfiles and logs statistics of the snapshot and dump the file list if requested and
491   * the collected information.
492   */
493  private void printFiles(final boolean showFiles, final boolean showStats) throws IOException {
494    if (showFiles) {
495      System.out.println("Snapshot Files");
496      System.out.println("----------------------------------------");
497    }
498
499    // Collect information about hfiles and logs in the snapshot
500    final SnapshotProtos.SnapshotDescription snapshotDesc =
501      snapshotManifest.getSnapshotDescription();
502    final String table = snapshotDesc.getTable();
503    final SnapshotDescription desc = ProtobufUtil.createSnapshotDesc(snapshotDesc);
504    final SnapshotStats stats = new SnapshotStats(this.getConf(), this.fs, desc);
505    SnapshotReferenceUtil.concurrentVisitReferencedFiles(getConf(), fs, snapshotManifest,
506      "SnapshotInfo", new SnapshotReferenceUtil.SnapshotVisitor() {
507        @Override
508        public void storeFile(final RegionInfo regionInfo, final String family,
509          final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
510          if (storeFile.hasReference()) return;
511
512          SnapshotStats.FileInfo info = stats.addStoreFile(regionInfo, family, storeFile, null);
513          if (showFiles) {
514            String state = info.getStateToString();
515            System.out.printf("%8s %s/%s/%s/%s %s%n",
516              (info.isMissing() ? "-" : fileSizeToString(info.getSize())), table,
517              regionInfo.getEncodedName(), family, storeFile.getName(),
518              state == null ? "" : "(" + state + ")");
519          }
520        }
521      });
522
523    // Dump the stats
524    System.out.println();
525    if (stats.isSnapshotCorrupted()) {
526      System.out.println("**************************************************************");
527      System.out.printf("BAD SNAPSHOT: %d hfile(s) and %d log(s) missing.%n",
528        stats.getMissingStoreFilesCount(), stats.getMissingLogsCount());
529      System.out.printf("              %d hfile(s) corrupted.%n",
530        stats.getCorruptedStoreFilesCount());
531      System.out.println("**************************************************************");
532    }
533
534    if (showStats) {
535      System.out.printf(
536        "%d HFiles (%d in archive, %d in mob storage), total size %s "
537          + "(%.2f%% %s shared with the source table, %.2f%% %s in mob dir)%n",
538        stats.getStoreFilesCount(), stats.getArchivedStoreFilesCount(),
539        stats.getMobStoreFilesCount(), fileSizeToString(stats.getStoreFilesSize()),
540        stats.getSharedStoreFilePercentage(), fileSizeToString(stats.getSharedStoreFilesSize()),
541        stats.getMobStoreFilePercentage(), fileSizeToString(stats.getMobStoreFilesSize()));
542      System.out.printf("%d Logs, total size %s%n", stats.getLogsCount(),
543        fileSizeToString(stats.getLogsSize()));
544      System.out.println();
545    }
546  }
547
548  private String fileSizeToString(long size) {
549    return printSizeInBytes ? Long.toString(size) : Strings.humanReadableInt(size);
550  }
551
552  @Override
553  protected void addOptions() {
554    addOption(Options.SNAPSHOT);
555    addOption(Options.REMOTE_DIR);
556    addOption(Options.LIST_SNAPSHOTS);
557    addOption(Options.FILES);
558    addOption(Options.STATS);
559    addOption(Options.SCHEMA);
560    addOption(Options.SIZE_IN_BYTES);
561  }
562
563  @Override
564  protected CommandLineParser newParser() {
565    // Commons-CLI lacks the capability to handle combinations of options, so we do it ourselves
566    // Validate in parse() to get helpful error messages instead of exploding in processOptions()
567    return new DefaultParser() {
568      @Override
569      public CommandLine parse(org.apache.hbase.thirdparty.org.apache.commons.cli.Options opts,
570        String[] args, Properties props, boolean stop) throws ParseException {
571        CommandLine cl = super.parse(opts, args, props, stop);
572        if (!cmd.hasOption(Options.LIST_SNAPSHOTS) && !cmd.hasOption(Options.SNAPSHOT)) {
573          throw new ParseException("Missing required snapshot option!");
574        }
575        return cl;
576      }
577    };
578  }
579
580  @Override
581  protected void processOptions(CommandLine cmd) {
582    snapshotName = cmd.getOptionValue(Options.SNAPSHOT.getLongOpt());
583    showFiles = cmd.hasOption(Options.FILES.getLongOpt());
584    showStats =
585      cmd.hasOption(Options.FILES.getLongOpt()) || cmd.hasOption(Options.STATS.getLongOpt());
586    showSchema = cmd.hasOption(Options.SCHEMA.getLongOpt());
587    listSnapshots = cmd.hasOption(Options.LIST_SNAPSHOTS.getLongOpt());
588    printSizeInBytes = cmd.hasOption(Options.SIZE_IN_BYTES.getLongOpt());
589    if (cmd.hasOption(Options.REMOTE_DIR.getLongOpt())) {
590      remoteDir = new Path(cmd.getOptionValue(Options.REMOTE_DIR.getLongOpt()));
591    }
592  }
593
594  @Override
595  protected void printUsage() {
596    printUsage("hbase snapshot info [options]", "Options:", "");
597    System.err.println("Examples:");
598    System.err.println("  hbase snapshot info --snapshot MySnapshot --files");
599  }
600
601  /**
602   * Returns the snapshot stats
603   * @param conf     the {@link Configuration} to use
604   * @param snapshot {@link SnapshotDescription} to get stats from
605   * @return the snapshot stats
606   */
607  public static SnapshotStats getSnapshotStats(final Configuration conf,
608    final SnapshotDescription snapshot) throws IOException {
609    SnapshotProtos.SnapshotDescription snapshotDesc =
610      ProtobufUtil.createHBaseProtosSnapshotDesc(snapshot);
611    return getSnapshotStats(conf, snapshotDesc, null);
612  }
613
614  /**
615   * Returns the snapshot stats
616   * @param conf         the {@link Configuration} to use
617   * @param snapshotDesc HBaseProtos.SnapshotDescription to get stats from
618   * @param filesMap     {@link Map} store files map for all snapshots, it may be null
619   * @return the snapshot stats
620   */
621  public static SnapshotStats getSnapshotStats(final Configuration conf,
622    final SnapshotProtos.SnapshotDescription snapshotDesc, final Map<Path, Integer> filesMap)
623    throws IOException {
624    Path rootDir = CommonFSUtils.getRootDir(conf);
625    FileSystem fs = FileSystem.get(rootDir.toUri(), conf);
626    Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotDesc, rootDir);
627    SnapshotManifest manifest = SnapshotManifest.open(conf, fs, snapshotDir, snapshotDesc);
628    final SnapshotStats stats = new SnapshotStats(conf, fs, snapshotDesc);
629    SnapshotReferenceUtil.concurrentVisitReferencedFiles(conf, fs, manifest,
630      "SnapshotsStatsAggregation", new SnapshotReferenceUtil.SnapshotVisitor() {
631        @Override
632        public void storeFile(final RegionInfo regionInfo, final String family,
633          final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
634          if (!storeFile.hasReference()) {
635            stats.addStoreFile(regionInfo, family, storeFile, filesMap);
636          }
637        }
638      });
639    return stats;
640  }
641
642  /**
643   * Returns the list of available snapshots in the specified location
644   * @param conf the {@link Configuration} to use
645   * @return the list of snapshots
646   */
647  public static List<SnapshotDescription> getSnapshotList(final Configuration conf)
648    throws IOException {
649    Path rootDir = CommonFSUtils.getRootDir(conf);
650    FileSystem fs = FileSystem.get(rootDir.toUri(), conf);
651    Path snapshotDir = SnapshotDescriptionUtils.getSnapshotsDir(rootDir);
652    FileStatus[] snapshots = CommonFSUtils.listStatus(fs, snapshotDir,
653      new SnapshotDescriptionUtils.CompletedSnaphotDirectoriesFilter(fs));
654    if (snapshots == null) {
655      return Collections.emptyList();
656    }
657    List<SnapshotDescription> snapshotLists = new ArrayList<>(snapshots.length);
658    for (FileStatus snapshotDirStat : snapshots) {
659      SnapshotProtos.SnapshotDescription snapshotDesc =
660        SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDirStat.getPath());
661      snapshotLists.add(ProtobufUtil.createSnapshotDesc(snapshotDesc));
662    }
663    return snapshotLists;
664  }
665
666  /**
667   * Gets the store files map for snapshot
668   * @param conf                    the {@link Configuration} to use
669   * @param snapshot                {@link SnapshotDescription} to get stats from
670   * @param exec                    the {@link ExecutorService} to use
671   * @param filesMap                {@link Map} the map to put the mapping entries
672   * @param uniqueHFilesArchiveSize {@link AtomicLong} the accumulated store file size in archive
673   * @param uniqueHFilesSize        {@link AtomicLong} the accumulated store file size shared
674   * @param uniqueHFilesMobSize     {@link AtomicLong} the accumulated mob store file size shared
675   */
676  private static void getSnapshotFilesMap(final Configuration conf,
677    final SnapshotDescription snapshot, final ExecutorService exec,
678    final ConcurrentHashMap<Path, Integer> filesMap, final AtomicLong uniqueHFilesArchiveSize,
679    final AtomicLong uniqueHFilesSize, final AtomicLong uniqueHFilesMobSize) throws IOException {
680    SnapshotProtos.SnapshotDescription snapshotDesc =
681      ProtobufUtil.createHBaseProtosSnapshotDesc(snapshot);
682    Path rootDir = CommonFSUtils.getRootDir(conf);
683    final FileSystem fs = FileSystem.get(rootDir.toUri(), conf);
684
685    Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotDesc, rootDir);
686    SnapshotManifest manifest = SnapshotManifest.open(conf, fs, snapshotDir, snapshotDesc);
687    SnapshotReferenceUtil.concurrentVisitReferencedFiles(conf, fs, manifest, exec,
688      new SnapshotReferenceUtil.SnapshotVisitor() {
689        @Override
690        public void storeFile(final RegionInfo regionInfo, final String family,
691          final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
692          if (!storeFile.hasReference()) {
693            HFileLink link = HFileLink.build(conf, snapshot.getTableName(),
694              regionInfo.getEncodedName(), family, storeFile.getName());
695            long size;
696            Integer count;
697            Path p;
698            AtomicLong al;
699            int c = 0;
700
701            if (fs.exists(link.getArchivePath())) {
702              p = link.getArchivePath();
703              al = uniqueHFilesArchiveSize;
704              size = fs.getFileStatus(p).getLen();
705            } else if (fs.exists(link.getMobPath())) {
706              p = link.getMobPath();
707              al = uniqueHFilesMobSize;
708              size = fs.getFileStatus(p).getLen();
709            } else {
710              p = link.getOriginPath();
711              al = uniqueHFilesSize;
712              size = link.getFileStatus(fs).getLen();
713            }
714
715            // If it has been counted, do not double count
716            count = filesMap.get(p);
717            if (count != null) {
718              c = count.intValue();
719            } else {
720              al.addAndGet(size);
721            }
722
723            filesMap.put(p, ++c);
724          }
725        }
726      });
727  }
728
729  /**
730   * Returns the map of store files based on path for all snapshots
731   * @param conf                    the {@link Configuration} to use
732   * @param uniqueHFilesArchiveSize pass out the size for store files in archive
733   * @param uniqueHFilesSize        pass out the size for store files shared
734   * @param uniqueHFilesMobSize     pass out the size for mob store files shared
735   * @return the map of store files
736   */
737  public static Map<Path, Integer> getSnapshotsFilesMap(final Configuration conf,
738    AtomicLong uniqueHFilesArchiveSize, AtomicLong uniqueHFilesSize, AtomicLong uniqueHFilesMobSize)
739    throws IOException {
740    List<SnapshotDescription> snapshotList = getSnapshotList(conf);
741
742    if (snapshotList.isEmpty()) {
743      return Collections.emptyMap();
744    }
745
746    ConcurrentHashMap<Path, Integer> fileMap = new ConcurrentHashMap<>();
747
748    ExecutorService exec = SnapshotManifest.createExecutor(conf, "SnapshotsFilesMapping");
749
750    try {
751      for (final SnapshotDescription snapshot : snapshotList) {
752        getSnapshotFilesMap(conf, snapshot, exec, fileMap, uniqueHFilesArchiveSize,
753          uniqueHFilesSize, uniqueHFilesMobSize);
754      }
755    } finally {
756      exec.shutdown();
757    }
758
759    return fileMap;
760  }
761
762  public static void main(String[] args) {
763    new SnapshotInfo().doStaticMain(args);
764  }
765}