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.io.util;
019
020import java.lang.management.ManagementFactory;
021import java.lang.management.MemoryType;
022import java.lang.management.MemoryUsage;
023
024import org.apache.hadoop.conf.Configuration;
025import org.apache.hadoop.hbase.HConstants;
026import org.apache.yetus.audience.InterfaceAudience;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029import org.apache.hadoop.hbase.regionserver.MemStoreLAB;
030import org.apache.hadoop.hbase.util.Pair;
031
032/**
033 * Util class to calculate memory size for memstore, block cache(L1, L2) of RS.
034 */
035@InterfaceAudience.Private
036public class MemorySizeUtil {
037
038  public static final String MEMSTORE_SIZE_KEY = "hbase.regionserver.global.memstore.size";
039  public static final String MEMSTORE_SIZE_OLD_KEY =
040      "hbase.regionserver.global.memstore.upperLimit";
041  public static final String MEMSTORE_SIZE_LOWER_LIMIT_KEY =
042      "hbase.regionserver.global.memstore.size.lower.limit";
043  public static final String MEMSTORE_SIZE_LOWER_LIMIT_OLD_KEY =
044      "hbase.regionserver.global.memstore.lowerLimit";
045  // Max global off heap memory that can be used for all memstores
046  // This should be an absolute value in MBs and not percent.
047  public static final String OFFHEAP_MEMSTORE_SIZE_KEY =
048      "hbase.regionserver.offheap.global.memstore.size";
049
050  public static final float DEFAULT_MEMSTORE_SIZE = 0.4f;
051  // Default lower water mark limit is 95% size of memstore size.
052  public static final float DEFAULT_MEMSTORE_SIZE_LOWER_LIMIT = 0.95f;
053
054  private static final Logger LOG = LoggerFactory.getLogger(MemorySizeUtil.class);
055  // a constant to convert a fraction to a percentage
056  private static final int CONVERT_TO_PERCENTAGE = 100;
057
058  private static final String JVM_HEAP_EXCEPTION = "Got an exception while attempting to read " +
059      "information about the JVM heap. Please submit this log information in a bug report and " +
060      "include your JVM settings, specifically the GC in use and any -XX options. Consider " +
061      "restarting the service.";
062
063  /**
064   * Return JVM memory statistics while properly handling runtime exceptions from the JVM.
065   * @return a memory usage object, null if there was a runtime exception. (n.b. you
066   *         could also get -1 values back from the JVM)
067   * @see MemoryUsage
068   */
069  public static MemoryUsage safeGetHeapMemoryUsage() {
070    MemoryUsage usage = null;
071    try {
072      usage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
073    } catch (RuntimeException exception) {
074      LOG.warn(JVM_HEAP_EXCEPTION, exception);
075    }
076    return usage;
077  }
078
079  /**
080   * Checks whether we have enough heap memory left out after portion for Memstore and Block cache.
081   * We need atleast 20% of heap left out for other RS functions.
082   * @param conf
083   */
084  public static void checkForClusterFreeHeapMemoryLimit(Configuration conf) {
085    if (conf.get(MEMSTORE_SIZE_OLD_KEY) != null) {
086      LOG.warn(MEMSTORE_SIZE_OLD_KEY + " is deprecated by " + MEMSTORE_SIZE_KEY);
087    }
088    float globalMemstoreSize = getGlobalMemStoreHeapPercent(conf, false);
089    int gml = (int)(globalMemstoreSize * CONVERT_TO_PERCENTAGE);
090    float blockCacheUpperLimit = getBlockCacheHeapPercent(conf);
091    int bcul = (int)(blockCacheUpperLimit * CONVERT_TO_PERCENTAGE);
092    if (CONVERT_TO_PERCENTAGE - (gml + bcul)
093            < (int)(CONVERT_TO_PERCENTAGE *
094                    HConstants.HBASE_CLUSTER_MINIMUM_MEMORY_THRESHOLD)) {
095      throw new RuntimeException("Current heap configuration for MemStore and BlockCache exceeds "
096          + "the threshold required for successful cluster operation. "
097          + "The combined value cannot exceed 0.8. Please check "
098          + "the settings for hbase.regionserver.global.memstore.size and "
099          + "hfile.block.cache.size in your configuration. "
100          + "hbase.regionserver.global.memstore.size is " + globalMemstoreSize
101          + " hfile.block.cache.size is " + blockCacheUpperLimit);
102    }
103  }
104
105  /**
106   * Retrieve global memstore configured size as percentage of total heap.
107   * @param c
108   * @param logInvalid
109   */
110  public static float getGlobalMemStoreHeapPercent(final Configuration c,
111      final boolean logInvalid) {
112    float limit = c.getFloat(MEMSTORE_SIZE_KEY,
113        c.getFloat(MEMSTORE_SIZE_OLD_KEY, DEFAULT_MEMSTORE_SIZE));
114    if (limit > 0.8f || limit <= 0.0f) {
115      if (logInvalid) {
116        LOG.warn("Setting global memstore limit to default of " + DEFAULT_MEMSTORE_SIZE
117            + " because supplied value outside allowed range of (0 -> 0.8]");
118      }
119      limit = DEFAULT_MEMSTORE_SIZE;
120    }
121    return limit;
122  }
123
124  /**
125   * Retrieve configured size for global memstore lower water mark as fraction of global memstore
126   * size.
127   */
128  public static float getGlobalMemStoreHeapLowerMark(final Configuration conf,
129      boolean honorOldConfig) {
130    String lowMarkPercentStr = conf.get(MEMSTORE_SIZE_LOWER_LIMIT_KEY);
131    if (lowMarkPercentStr != null) {
132      float lowMarkPercent = Float.parseFloat(lowMarkPercentStr);
133      if (lowMarkPercent > 1.0f) {
134        LOG.error("Bad configuration value for " + MEMSTORE_SIZE_LOWER_LIMIT_KEY + ": "
135            + lowMarkPercent + ". Using 1.0f instead.");
136        lowMarkPercent = 1.0f;
137      }
138      return lowMarkPercent;
139    }
140    if (!honorOldConfig) return DEFAULT_MEMSTORE_SIZE_LOWER_LIMIT;
141    String lowerWaterMarkOldValStr = conf.get(MEMSTORE_SIZE_LOWER_LIMIT_OLD_KEY);
142    if (lowerWaterMarkOldValStr != null) {
143      LOG.warn(MEMSTORE_SIZE_LOWER_LIMIT_OLD_KEY + " is deprecated. Instead use "
144          + MEMSTORE_SIZE_LOWER_LIMIT_KEY);
145      float lowerWaterMarkOldVal = Float.parseFloat(lowerWaterMarkOldValStr);
146      float upperMarkPercent = getGlobalMemStoreHeapPercent(conf, false);
147      if (lowerWaterMarkOldVal > upperMarkPercent) {
148        lowerWaterMarkOldVal = upperMarkPercent;
149        LOG.error("Value of " + MEMSTORE_SIZE_LOWER_LIMIT_OLD_KEY + " (" + lowerWaterMarkOldVal
150            + ") is greater than global memstore limit (" + upperMarkPercent + ") set by "
151            + MEMSTORE_SIZE_KEY + "/" + MEMSTORE_SIZE_OLD_KEY + ". Setting memstore lower limit "
152            + "to " + upperMarkPercent);
153      }
154      return lowerWaterMarkOldVal / upperMarkPercent;
155    }
156    return DEFAULT_MEMSTORE_SIZE_LOWER_LIMIT;
157  }
158
159  /**
160   * @return Pair of global memstore size and memory type(ie. on heap or off heap).
161   */
162  public static Pair<Long, MemoryType> getGlobalMemStoreSize(Configuration conf) {
163    long offheapMSGlobal = conf.getLong(OFFHEAP_MEMSTORE_SIZE_KEY, 0);// Size in MBs
164    if (offheapMSGlobal > 0) {
165      // Off heap memstore size has not relevance when MSLAB is turned OFF. We will go with making
166      // this entire size split into Chunks and pooling them in MemstoreLABPoool. We dont want to
167      // create so many on demand off heap chunks. In fact when this off heap size is configured, we
168      // will go with 100% of this size as the pool size
169      if (MemStoreLAB.isEnabled(conf)) {
170        // We are in offheap Memstore use
171        long globalMemStoreLimit = (long) (offheapMSGlobal * 1024 * 1024); // Size in bytes
172        return new Pair<>(globalMemStoreLimit, MemoryType.NON_HEAP);
173      } else {
174        // Off heap max memstore size is configured with turning off MSLAB. It makes no sense. Do a
175        // warn log and go with on heap memstore percentage. By default it will be 40% of Xmx
176        LOG.warn("There is no relevance of configuring '" + OFFHEAP_MEMSTORE_SIZE_KEY + "' when '"
177            + MemStoreLAB.USEMSLAB_KEY + "' is turned off."
178            + " Going with on heap global memstore size ('" + MEMSTORE_SIZE_KEY + "')");
179      }
180    }
181    return new Pair<>(getOnheapGlobalMemStoreSize(conf), MemoryType.HEAP);
182  }
183
184  /**
185   * Returns the onheap global memstore limit based on the config
186   * 'hbase.regionserver.global.memstore.size'.
187   * @param conf
188   * @return the onheap global memstore limt
189   */
190  public static long getOnheapGlobalMemStoreSize(Configuration conf) {
191    long max = -1L;
192    final MemoryUsage usage = safeGetHeapMemoryUsage();
193    if (usage != null) {
194      max = usage.getMax();
195    }
196    float globalMemStorePercent = getGlobalMemStoreHeapPercent(conf, true);
197    return ((long) (max * globalMemStorePercent));
198  }
199
200  /**
201   * Retrieve configured size for on heap block cache as percentage of total heap.
202   * @param conf
203   */
204  public static float getBlockCacheHeapPercent(final Configuration conf) {
205    // L1 block cache is always on heap
206    float l1CachePercent = conf.getFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY,
207        HConstants.HFILE_BLOCK_CACHE_SIZE_DEFAULT);
208    return l1CachePercent;
209  }
210
211  /**
212   * @param conf used to read cache configs
213   * @return the number of bytes to use for LRU, negative if disabled.
214   * @throws IllegalArgumentException if HFILE_BLOCK_CACHE_SIZE_KEY is > 1.0
215   */
216  public static long getOnHeapCacheSize(final Configuration conf) {
217    float cachePercentage = conf.getFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY,
218      HConstants.HFILE_BLOCK_CACHE_SIZE_DEFAULT);
219    if (cachePercentage <= 0.0001f) {
220      return -1;
221    }
222    if (cachePercentage > 1.0) {
223      throw new IllegalArgumentException(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY +
224        " must be between 0.0 and 1.0, and not > 1.0");
225    }
226    long max = -1L;
227    final MemoryUsage usage = safeGetHeapMemoryUsage();
228    if (usage != null) {
229      max = usage.getMax();
230    }
231
232    // Calculate the amount of heap to give the heap.
233    return (long) (max * cachePercentage);
234  }
235
236  /**
237   * @param conf used to read config for bucket cache size. (< 1 is treated as % and > is treated as MiB)
238   * @return the number of bytes to use for bucket cache, negative if disabled.
239   */
240  public static long getBucketCacheSize(final Configuration conf) {
241    // Size configured in MBs
242    float bucketCacheSize = conf.getFloat(HConstants.BUCKET_CACHE_SIZE_KEY, 0F);
243    if (bucketCacheSize < 1) {
244      throw new IllegalArgumentException("Bucket Cache should be minimum 1 MB in size."
245          + "Configure 'hbase.bucketcache.size' with > 1 value");
246    }
247    return (long) (bucketCacheSize * 1024 * 1024);
248  }
249
250}