1 /** 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 package org.apache.hadoop.mapred.gridmix.emulators.resourceusage; 19 20 import java.io.IOException; 21 import java.util.Random; 22 23 import org.apache.hadoop.conf.Configuration; 24 import org.apache.hadoop.mapred.gridmix.Progressive; 25 import org.apache.hadoop.tools.rumen.ResourceUsageMetrics; 26 import org.apache.hadoop.yarn.util.ResourceCalculatorPlugin; 27 28 /** 29 * <p>A {@link ResourceUsageEmulatorPlugin} that emulates the cumulative CPU 30 * usage by performing certain CPU intensive operations. Performing such CPU 31 * intensive operations essentially uses up some CPU. Every 32 * {@link ResourceUsageEmulatorPlugin} is configured with a feedback module i.e 33 * a {@link ResourceCalculatorPlugin}, to monitor the resource usage.</p> 34 * 35 * <p>{@link CumulativeCpuUsageEmulatorPlugin} emulates the CPU usage in steps. 36 * The frequency of emulation can be configured via 37 * {@link #CPU_EMULATION_PROGRESS_INTERVAL}. 38 * CPU usage values are matched via emulation only on the interval boundaries. 39 * </p> 40 * 41 * {@link CumulativeCpuUsageEmulatorPlugin} is a wrapper program for managing 42 * the CPU usage emulation feature. It internally uses an emulation algorithm 43 * (called as core and described using {@link CpuUsageEmulatorCore}) for 44 * performing the actual emulation. Multiple calls to this core engine should 45 * use up some amount of CPU.<br> 46 * 47 * <p>{@link CumulativeCpuUsageEmulatorPlugin} provides a calibration feature 48 * via {@link #initialize(Configuration, ResourceUsageMetrics, 49 * ResourceCalculatorPlugin, Progressive)} to calibrate 50 * the plugin and its core for the underlying hardware. As a result of 51 * calibration, every call to the emulation engine's core should roughly use up 52 * 1% of the total usage value to be emulated. This makes sure that the 53 * underlying hardware is profiled before use and that the plugin doesn't 54 * accidently overuse the CPU. With 1% as the unit emulation target value for 55 * the core engine, there will be roughly 100 calls to the engine resulting in 56 * roughly 100 calls to the feedback (resource usage monitor) module. 57 * Excessive usage of the feedback module is discouraged as 58 * it might result into excess CPU usage resulting into no real CPU emulation. 59 * </p> 60 */ 61 public class CumulativeCpuUsageEmulatorPlugin 62 implements ResourceUsageEmulatorPlugin { 63 protected CpuUsageEmulatorCore emulatorCore; 64 private ResourceCalculatorPlugin monitor; 65 private Progressive progress; 66 private boolean enabled = true; 67 private float emulationInterval; // emulation interval 68 private long targetCpuUsage = 0; 69 private float lastSeenProgress = 0; 70 private long lastSeenCpuUsage = 0; 71 72 // Configuration parameters 73 public static final String CPU_EMULATION_PROGRESS_INTERVAL = 74 "gridmix.emulators.resource-usage.cpu.emulation-interval"; 75 private static final float DEFAULT_EMULATION_FREQUENCY = 0.1F; // 10 times 76 77 /** 78 * This is the core CPU usage emulation algorithm. This is the core engine 79 * which actually performs some CPU intensive operations to consume some 80 * amount of CPU. Multiple calls of {@link #compute()} should help the 81 * plugin emulate the desired level of CPU usage. This core engine can be 82 * calibrated using the {@link #calibrate(ResourceCalculatorPlugin, long)} 83 * API to suit the underlying hardware better. It also can be used to optimize 84 * the emulation cycle. 85 */ 86 public interface CpuUsageEmulatorCore { 87 /** 88 * Performs some computation to use up some CPU. 89 */ compute()90 public void compute(); 91 92 /** 93 * Allows the core to calibrate itself. 94 */ calibrate(ResourceCalculatorPlugin monitor, long totalCpuUsage)95 public void calibrate(ResourceCalculatorPlugin monitor, 96 long totalCpuUsage); 97 } 98 99 /** 100 * This is the core engine to emulate the CPU usage. The only responsibility 101 * of this class is to perform certain math intensive operations to make sure 102 * that some desired value of CPU is used. 103 */ 104 public static class DefaultCpuUsageEmulator implements CpuUsageEmulatorCore { 105 // number of times to loop for performing the basic unit computation 106 private int numIterations; 107 private final Random random; 108 109 /** 110 * This is to fool the JVM and make it think that we need the value 111 * stored in the unit computation i.e {@link #compute()}. This will prevent 112 * the JVM from optimizing the code. 113 */ 114 protected double returnValue; 115 116 /** 117 * Initialized the {@link DefaultCpuUsageEmulator} with default values. 118 * Note that the {@link DefaultCpuUsageEmulator} should be calibrated 119 * (see {@link #calibrate(ResourceCalculatorPlugin, long)}) when initialized 120 * using this constructor. 121 */ DefaultCpuUsageEmulator()122 public DefaultCpuUsageEmulator() { 123 this(-1); 124 } 125 DefaultCpuUsageEmulator(int numIterations)126 DefaultCpuUsageEmulator(int numIterations) { 127 this.numIterations = numIterations; 128 random = new Random(); 129 } 130 131 /** 132 * This will consume some desired level of CPU. This API will try to use up 133 * 'X' percent of the target cumulative CPU usage. Currently X is set to 134 * 10%. 135 */ compute()136 public void compute() { 137 for (int i = 0; i < numIterations; ++i) { 138 performUnitComputation(); 139 } 140 } 141 142 // Perform unit computation. The complete CPU emulation will be based on 143 // multiple invocations to this unit computation module. performUnitComputation()144 protected void performUnitComputation() { 145 //TODO can this be configurable too. Users/emulators should be able to 146 // pick and choose what MATH operations to run. 147 // Example : 148 // BASIC : ADD, SUB, MUL, DIV 149 // ADV : SQRT, SIN, COSIN.. 150 // COMPO : (BASIC/ADV)* 151 // Also define input generator. For now we can use the random number 152 // generator. Later this can be changed to accept multiple sources. 153 154 int randomData = random.nextInt(); 155 int randomDataCube = randomData * randomData * randomData; 156 double randomDataCubeRoot = Math.cbrt(randomData); 157 returnValue = Math.log(Math.tan(randomDataCubeRoot 158 * Math.exp(randomDataCube)) 159 * Math.sqrt(randomData)); 160 } 161 162 /** 163 * This will calibrate the algorithm such that a single invocation of 164 * {@link #compute()} emulates roughly 1% of the total desired resource 165 * usage value. 166 */ calibrate(ResourceCalculatorPlugin monitor, long totalCpuUsage)167 public void calibrate(ResourceCalculatorPlugin monitor, 168 long totalCpuUsage) { 169 long initTime = monitor.getCumulativeCpuTime(); 170 171 long defaultLoopSize = 0; 172 long finalTime = initTime; 173 174 //TODO Make this configurable 175 while (finalTime - initTime < 100) { // 100 ms 176 ++defaultLoopSize; 177 performUnitComputation(); //perform unit computation 178 finalTime = monitor.getCumulativeCpuTime(); 179 } 180 181 long referenceRuntime = finalTime - initTime; 182 183 // time for one loop = (final-time - init-time) / total-loops 184 float timePerLoop = ((float)referenceRuntime) / defaultLoopSize; 185 186 // compute the 1% of the total CPU usage desired 187 //TODO Make this configurable 188 long onePercent = totalCpuUsage / 100; 189 190 // num-iterations for 1% = (total-desired-usage / 100) / time-for-one-loop 191 numIterations = Math.max(1, (int)((float)onePercent/timePerLoop)); 192 193 System.out.println("Calibration done. Basic computation runtime : " 194 + timePerLoop + " milliseconds. Optimal number of iterations (1%): " 195 + numIterations); 196 } 197 } 198 CumulativeCpuUsageEmulatorPlugin()199 public CumulativeCpuUsageEmulatorPlugin() { 200 this(new DefaultCpuUsageEmulator()); 201 } 202 203 /** 204 * For testing. 205 */ CumulativeCpuUsageEmulatorPlugin(CpuUsageEmulatorCore core)206 public CumulativeCpuUsageEmulatorPlugin(CpuUsageEmulatorCore core) { 207 emulatorCore = core; 208 } 209 210 // Note that this weighing function uses only the current progress. In future, 211 // this might depend on progress, emulation-interval and expected target. getWeightForProgressInterval(float progress)212 private float getWeightForProgressInterval(float progress) { 213 // we want some kind of exponential growth function that gives less weight 214 // on lower progress boundaries but high (exact emulation) near progress 215 // value of 1. 216 // so here is how the current growth function looks like 217 // progress weight 218 // 0.1 0.0001 219 // 0.2 0.0016 220 // 0.3 0.0081 221 // 0.4 0.0256 222 // 0.5 0.0625 223 // 0.6 0.1296 224 // 0.7 0.2401 225 // 0.8 0.4096 226 // 0.9 0.6561 227 // 1.0 1.000 228 229 return progress * progress * progress * progress; 230 } 231 getCurrentCPUUsage()232 private synchronized long getCurrentCPUUsage() { 233 return monitor.getCumulativeCpuTime(); 234 235 } 236 237 @Override getProgress()238 public float getProgress() { 239 return enabled 240 ? Math.min(1f, ((float)getCurrentCPUUsage())/targetCpuUsage) 241 : 1.0f; 242 } 243 244 @Override 245 //TODO Multi-threading for speedup? emulate()246 public void emulate() throws IOException, InterruptedException { 247 if (enabled) { 248 float currentProgress = progress.getProgress(); 249 if (lastSeenProgress < currentProgress 250 && ((currentProgress - lastSeenProgress) >= emulationInterval 251 || currentProgress == 1)) { 252 // Estimate the final cpu usage 253 // 254 // Consider the following 255 // Cl/Cc/Cp : Last/Current/Projected Cpu usage 256 // Pl/Pc/Pp : Last/Current/Projected progress 257 // Then 258 // (Cp-Cc)/(Pp-Pc) = (Cc-Cl)/(Pc-Pl) 259 // Solving this for Cp, we get 260 // Cp = Cc + (1-Pc)*(Cc-Cl)/Pc-Pl) 261 // Note that (Cc-Cl)/(Pc-Pl) is termed as 'rate' in the following 262 // section 263 264 long currentCpuUsage = getCurrentCPUUsage(); 265 // estimate the cpu usage rate 266 float rate = (currentCpuUsage - lastSeenCpuUsage) 267 / (currentProgress - lastSeenProgress); 268 long projectedUsage = 269 currentCpuUsage + (long)((1 - currentProgress) * rate); 270 271 if (projectedUsage < targetCpuUsage) { 272 // determine the correction factor between the current usage and the 273 // expected usage and add some weight to the target 274 long currentWeighedTarget = 275 (long)(targetCpuUsage 276 * getWeightForProgressInterval(currentProgress)); 277 278 while (getCurrentCPUUsage() < currentWeighedTarget) { 279 emulatorCore.compute(); 280 // sleep for 100ms 281 try { 282 Thread.sleep(100); 283 } catch (InterruptedException ie) { 284 String message = 285 "CumulativeCpuUsageEmulatorPlugin got interrupted. Exiting."; 286 throw new RuntimeException(message); 287 } 288 } 289 } 290 291 // set the last seen progress 292 lastSeenProgress = progress.getProgress(); 293 // set the last seen usage 294 lastSeenCpuUsage = getCurrentCPUUsage(); 295 } 296 } 297 } 298 299 @Override initialize(Configuration conf, ResourceUsageMetrics metrics, ResourceCalculatorPlugin monitor, Progressive progress)300 public void initialize(Configuration conf, ResourceUsageMetrics metrics, 301 ResourceCalculatorPlugin monitor, 302 Progressive progress) { 303 this.monitor = monitor; 304 this.progress = progress; 305 306 // get the target CPU usage 307 targetCpuUsage = metrics.getCumulativeCpuUsage(); 308 if (targetCpuUsage <= 0 ) { 309 enabled = false; 310 return; 311 } else { 312 enabled = true; 313 } 314 315 emulationInterval = conf.getFloat(CPU_EMULATION_PROGRESS_INTERVAL, 316 DEFAULT_EMULATION_FREQUENCY); 317 318 // calibrate the core cpu-usage utility 319 emulatorCore.calibrate(monitor, targetCpuUsage); 320 321 // initialize the states 322 lastSeenProgress = 0; 323 lastSeenCpuUsage = 0; 324 } 325 } 326