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.quotas;
019
020import java.util.HashSet;
021import java.util.Iterator;
022import java.util.Set;
023import java.util.concurrent.TimeUnit;
024import org.apache.hadoop.conf.Configuration;
025import org.apache.hadoop.hbase.ScheduledChore;
026import org.apache.hadoop.hbase.client.RegionInfo;
027import org.apache.hadoop.hbase.regionserver.HRegion;
028import org.apache.hadoop.hbase.regionserver.HRegionServer;
029import org.apache.hadoop.hbase.regionserver.Region;
030import org.apache.hadoop.hbase.regionserver.Store;
031import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
032import org.apache.yetus.audience.InterfaceAudience;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036/**
037 * A chore which computes the size of each {@link HRegion} on the FileSystem hosted by the given
038 * {@link HRegionServer}. The results of this computation are stored in the
039 * {@link RegionServerSpaceQuotaManager}'s {@link RegionSizeStore} object.
040 */
041@InterfaceAudience.Private
042public class FileSystemUtilizationChore extends ScheduledChore {
043  private static final Logger LOG = LoggerFactory.getLogger(FileSystemUtilizationChore.class);
044  static final String FS_UTILIZATION_CHORE_PERIOD_KEY =
045    "hbase.regionserver.quotas.fs.utilization.chore.period";
046  static final int FS_UTILIZATION_CHORE_PERIOD_DEFAULT = 1000 * 60 * 5; // 5 minutes in millis
047
048  static final String FS_UTILIZATION_CHORE_DELAY_KEY =
049    "hbase.regionserver.quotas.fs.utilization.chore.delay";
050  static final long FS_UTILIZATION_CHORE_DELAY_DEFAULT = 1000L * 60L; // 1 minute
051
052  static final String FS_UTILIZATION_CHORE_TIMEUNIT_KEY =
053    "hbase.regionserver.quotas.fs.utilization.chore.timeunit";
054  static final String FS_UTILIZATION_CHORE_TIMEUNIT_DEFAULT = TimeUnit.MILLISECONDS.name();
055
056  static final String FS_UTILIZATION_MAX_ITERATION_DURATION_KEY =
057    "hbase.regionserver.quotas.fs.utilization.chore.max.iteration.millis";
058  static final long FS_UTILIZATION_MAX_ITERATION_DURATION_DEFAULT = 5000L;
059
060  private final HRegionServer rs;
061  private final long maxIterationMillis;
062  private Iterator<Region> leftoverRegions;
063
064  public FileSystemUtilizationChore(HRegionServer rs) {
065    super(FileSystemUtilizationChore.class.getSimpleName(), rs, getPeriod(rs.getConfiguration()),
066      getInitialDelay(rs.getConfiguration()), getTimeUnit(rs.getConfiguration()));
067    this.rs = rs;
068    this.maxIterationMillis = rs.getConfiguration().getLong(
069      FS_UTILIZATION_MAX_ITERATION_DURATION_KEY, FS_UTILIZATION_MAX_ITERATION_DURATION_DEFAULT);
070  }
071
072  @Override
073  protected void chore() {
074    final RegionSizeStore regionSizeStore = getRegionSizeStore();
075    final Set<Region> onlineRegions = new HashSet<>(rs.getRegions());
076    // Process the regions from the last run if we have any. If we are somehow having difficulty
077    // processing the Regions, we want to avoid creating a backlog in memory of Region objs.
078    Iterator<Region> oldRegionsToProcess = getLeftoverRegions();
079    final Iterator<Region> iterator;
080    final boolean processingLeftovers;
081    if (oldRegionsToProcess == null) {
082      iterator = onlineRegions.iterator();
083      processingLeftovers = false;
084    } else {
085      iterator = oldRegionsToProcess;
086      processingLeftovers = true;
087    }
088    // Reset the leftoverRegions and let the loop re-assign if necessary.
089    setLeftoverRegions(null);
090    long regionSizesCalculated = 0L;
091    long offlineRegionsSkipped = 0L;
092    long skippedSplitParents = 0L;
093    long skippedRegionReplicas = 0L;
094    final long start = EnvironmentEdgeManager.currentTime();
095    while (iterator.hasNext()) {
096      // Make sure this chore doesn't hog the thread.
097      long timeRunning = EnvironmentEdgeManager.currentTime() - start;
098      if (timeRunning > maxIterationMillis) {
099        LOG.debug("Preempting execution of FileSystemUtilizationChore because it exceeds the"
100          + " maximum iteration configuration value. Will process remaining Regions"
101          + " on a subsequent invocation.");
102        setLeftoverRegions(iterator);
103        break;
104      }
105
106      final Region region = iterator.next();
107      // If we're processing leftover regions, the region may no-longer be online.
108      // If so, we can skip it.
109      if (processingLeftovers && !onlineRegions.contains(region)) {
110        offlineRegionsSkipped++;
111        continue;
112      }
113      // Avoid computing the size of regions which are the parent of split.
114      if (region.getRegionInfo().isSplitParent()) {
115        skippedSplitParents++;
116        continue;
117      }
118      // Avoid computing the size of region replicas.
119      if (RegionInfo.DEFAULT_REPLICA_ID != region.getRegionInfo().getReplicaId()) {
120        skippedRegionReplicas++;
121        continue;
122      }
123      final long sizeInBytes = computeSize(region);
124      regionSizeStore.put(region.getRegionInfo(), sizeInBytes);
125      regionSizesCalculated++;
126    }
127    if (LOG.isTraceEnabled()) {
128      LOG.trace("Computed the size of " + regionSizesCalculated + " Regions. Skipped computation"
129        + " of " + offlineRegionsSkipped + " regions due to not being online on this RS, "
130        + skippedSplitParents + " regions due to being the parent of a split, and"
131        + skippedRegionReplicas + " regions due to being region replicas.");
132    }
133  }
134
135  /**
136   * Returns an {@link Iterator} over the Regions which were skipped last invocation of the chore.
137   * @return Regions from the previous invocation to process, or null.
138   */
139  Iterator<Region> getLeftoverRegions() {
140    return leftoverRegions;
141  }
142
143  /**
144   * Sets a new collection of Regions as leftovers.
145   */
146  void setLeftoverRegions(Iterator<Region> newLeftovers) {
147    this.leftoverRegions = newLeftovers;
148  }
149
150  /**
151   * Computes total FileSystem size for the given {@link Region}.
152   * @param r The region
153   * @return The size, in bytes, of the Region.
154   */
155  long computeSize(Region r) {
156    long regionSize = 0L;
157    for (Store store : r.getStores()) {
158      regionSize += store.getHFilesSize();
159    }
160    if (LOG.isTraceEnabled()) {
161      LOG.trace("Size of " + r + " is " + regionSize);
162    }
163    return regionSize;
164  }
165
166  // visible for testing
167  RegionSizeStore getRegionSizeStore() {
168    return rs.getRegionServerSpaceQuotaManager().getRegionSizeStore();
169  }
170
171  /**
172   * Extracts the period for the chore from the configuration.
173   * @param conf The configuration object.
174   * @return The configured chore period or the default value.
175   */
176  static int getPeriod(Configuration conf) {
177    return conf.getInt(FS_UTILIZATION_CHORE_PERIOD_KEY, FS_UTILIZATION_CHORE_PERIOD_DEFAULT);
178  }
179
180  /**
181   * Extracts the initial delay for the chore from the configuration.
182   * @param conf The configuration object.
183   * @return The configured chore initial delay or the default value.
184   */
185  static long getInitialDelay(Configuration conf) {
186    return conf.getLong(FS_UTILIZATION_CHORE_DELAY_KEY, FS_UTILIZATION_CHORE_DELAY_DEFAULT);
187  }
188
189  /**
190   * Extracts the time unit for the chore period and initial delay from the configuration. The
191   * configuration value for {@link #FS_UTILIZATION_CHORE_TIMEUNIT_KEY} must correspond to a
192   * {@link TimeUnit} value.
193   * @param conf The configuration object.
194   * @return The configured time unit for the chore period and initial delay or the default value.
195   */
196  static TimeUnit getTimeUnit(Configuration conf) {
197    return TimeUnit
198      .valueOf(conf.get(FS_UTILIZATION_CHORE_TIMEUNIT_KEY, FS_UTILIZATION_CHORE_TIMEUNIT_DEFAULT));
199  }
200}