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