/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.mapred.gridmix.emulators.resourceusage; import java.io.IOException; import java.util.ArrayList; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.mapred.gridmix.Progressive; import org.apache.hadoop.tools.rumen.ResourceUsageMetrics; import org.apache.hadoop.yarn.util.ResourceCalculatorPlugin; /** * <p>A {@link ResourceUsageEmulatorPlugin} that emulates the total heap * usage by loading the JVM heap memory. Adding smaller chunks of data to the * heap will essentially use up some heap space thus forcing the JVM to expand * its heap and thus resulting into increase in the heap usage.</p> * * <p>{@link TotalHeapUsageEmulatorPlugin} emulates the heap usage in steps. * The frequency of emulation can be configured via * {@link #HEAP_EMULATION_PROGRESS_INTERVAL}. * Heap usage values are matched via emulation only at specific interval * boundaries. * </p> * * {@link TotalHeapUsageEmulatorPlugin} is a wrapper program for managing * the heap usage emulation feature. It internally uses an emulation algorithm * (called as core and described using {@link HeapUsageEmulatorCore}) for * performing the actual emulation. Multiple calls to this core engine should * use up some amount of heap. */ public class TotalHeapUsageEmulatorPlugin implements ResourceUsageEmulatorPlugin { // Configuration parameters // the core engine to emulate heap usage protected HeapUsageEmulatorCore emulatorCore; // the progress bar private Progressive progress; // decides if this plugin can emulate heap usage or not private boolean enabled = true; // the progress boundaries/interval where emulation should be done private float emulationInterval; // target heap usage to emulate private long targetHeapUsageInMB = 0; /** * The frequency (based on task progress) with which memory-emulation code is * run. If the value is set to 0.1 then the emulation will happen at 10% of * the task's progress. The default value of this parameter is * {@link #DEFAULT_EMULATION_PROGRESS_INTERVAL}. */ public static final String HEAP_EMULATION_PROGRESS_INTERVAL = "gridmix.emulators.resource-usage.heap.emulation-interval"; // Default value for emulation interval private static final float DEFAULT_EMULATION_PROGRESS_INTERVAL = 0.1F; // 10 % private float prevEmulationProgress = 0F; /** * The minimum buffer reserved for other non-emulation activities. */ public static final String MIN_HEAP_FREE_RATIO = "gridmix.emulators.resource-usage.heap.min-free-ratio"; private float minFreeHeapRatio; private static final float DEFAULT_MIN_FREE_HEAP_RATIO = 0.3F; /** * Determines the unit increase per call to the core engine's load API. This * is expressed as a percentage of the difference between the expected total * heap usage and the current usage. */ public static final String HEAP_LOAD_RATIO = "gridmix.emulators.resource-usage.heap.load-ratio"; private float heapLoadRatio; private static final float DEFAULT_HEAP_LOAD_RATIO = 0.1F; public static final int ONE_MB = 1024 * 1024; /** * Defines the core heap usage emulation algorithm. This engine is expected * to perform certain memory intensive operations to consume some * amount of heap. {@link #load(long)} should load the current heap and * increase the heap usage by the specified value. This core engine can be * initialized using the {@link #initialize(ResourceCalculatorPlugin, long)} * API to suit the underlying hardware better. */ public interface HeapUsageEmulatorCore { /** * Performs some memory intensive operations to use up some heap. */ public void load(long sizeInMB); /** * Initialize the core. */ public void initialize(ResourceCalculatorPlugin monitor, long totalHeapUsageInMB); /** * Reset the resource usage */ public void reset(); } /** * This is the core engine to emulate the heap usage. The only responsibility * of this class is to perform certain memory intensive operations to make * sure that some desired value of heap is used. */ public static class DefaultHeapUsageEmulator implements HeapUsageEmulatorCore { // store the unit loads in a list protected static final ArrayList<Object> heapSpace = new ArrayList<Object>(); /** * Increase heap usage by current process by the given amount. * This is done by creating objects each of size 1MB. */ public void load(long sizeInMB) { for (long i = 0; i < sizeInMB; ++i) { // Create another String object of size 1MB heapSpace.add((Object)new byte[ONE_MB]); } } /** * This will initialize the core and check if the core can emulate the * desired target on the underlying hardware. */ public void initialize(ResourceCalculatorPlugin monitor, long totalHeapUsageInMB) { long maxPhysicalMemoryInMB = monitor.getPhysicalMemorySize() / ONE_MB ; if(maxPhysicalMemoryInMB < totalHeapUsageInMB) { throw new RuntimeException("Total heap the can be used is " + maxPhysicalMemoryInMB + " bytes while the emulator is configured to emulate a total of " + totalHeapUsageInMB + " bytes"); } } /** * Clear references to all the GridMix-allocated special objects so that * heap usage is reduced. */ @Override public void reset() { heapSpace.clear(); } } public TotalHeapUsageEmulatorPlugin() { this(new DefaultHeapUsageEmulator()); } /** * For testing. */ public TotalHeapUsageEmulatorPlugin(HeapUsageEmulatorCore core) { emulatorCore = core; } protected long getTotalHeapUsageInMB() { return Runtime.getRuntime().totalMemory() / ONE_MB; } protected long getMaxHeapUsageInMB() { return Runtime.getRuntime().maxMemory() / ONE_MB; } @Override public float getProgress() { return enabled ? Math.min(1f, ((float)getTotalHeapUsageInMB())/targetHeapUsageInMB) : 1.0f; } @Override public void emulate() throws IOException, InterruptedException { if (enabled) { float currentProgress = progress.getProgress(); if (prevEmulationProgress < currentProgress && ((currentProgress - prevEmulationProgress) >= emulationInterval || currentProgress == 1)) { long maxHeapSizeInMB = getMaxHeapUsageInMB(); long committedHeapSizeInMB = getTotalHeapUsageInMB(); // Increase committed heap usage, if needed // Using a linear weighing function for computing the expected usage long expectedHeapUsageInMB = Math.min(maxHeapSizeInMB, (long) (targetHeapUsageInMB * currentProgress)); if (expectedHeapUsageInMB < maxHeapSizeInMB && committedHeapSizeInMB < expectedHeapUsageInMB) { long bufferInMB = (long)(minFreeHeapRatio * expectedHeapUsageInMB); long currentDifferenceInMB = expectedHeapUsageInMB - committedHeapSizeInMB; long currentIncrementLoadSizeInMB = (long)(currentDifferenceInMB * heapLoadRatio); // Make sure that at least 1 MB is incremented. currentIncrementLoadSizeInMB = Math.max(1, currentIncrementLoadSizeInMB); while (committedHeapSizeInMB + bufferInMB < expectedHeapUsageInMB) { // add blocks in order of X% of the difference, X = 10% by default emulatorCore.load(currentIncrementLoadSizeInMB); committedHeapSizeInMB = getTotalHeapUsageInMB(); } } // store the emulation progress boundary prevEmulationProgress = currentProgress; } // reset the core so that the garbage is reclaimed emulatorCore.reset(); } } @Override public void initialize(Configuration conf, ResourceUsageMetrics metrics, ResourceCalculatorPlugin monitor, Progressive progress) { this.progress = progress; // get the target heap usage targetHeapUsageInMB = metrics.getHeapUsage() / ONE_MB; if (targetHeapUsageInMB <= 0 ) { enabled = false; return; } else { // calibrate the core heap-usage utility emulatorCore.initialize(monitor, targetHeapUsageInMB); enabled = true; } emulationInterval = conf.getFloat(HEAP_EMULATION_PROGRESS_INTERVAL, DEFAULT_EMULATION_PROGRESS_INTERVAL); minFreeHeapRatio = conf.getFloat(MIN_HEAP_FREE_RATIO, DEFAULT_MIN_FREE_HEAP_RATIO); heapLoadRatio = conf.getFloat(HEAP_LOAD_RATIO, DEFAULT_HEAP_LOAD_RATIO); prevEmulationProgress = 0; } }