blob: 52944b65de1706d25920597f71bda2b358b33644 [file] [log] [blame]
/**
* 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.Random;
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 cumulative CPU
* usage by performing certain CPU intensive operations. Performing such CPU
* intensive operations essentially uses up some CPU. Every
* {@link ResourceUsageEmulatorPlugin} is configured with a feedback module i.e
* a {@link ResourceCalculatorPlugin}, to monitor the resource usage.</p>
*
* <p>{@link CumulativeCpuUsageEmulatorPlugin} emulates the CPU usage in steps.
* The frequency of emulation can be configured via
* {@link #CPU_EMULATION_PROGRESS_INTERVAL}.
* CPU usage values are matched via emulation only on the interval boundaries.
* </p>
*
* {@link CumulativeCpuUsageEmulatorPlugin} is a wrapper program for managing
* the CPU usage emulation feature. It internally uses an emulation algorithm
* (called as core and described using {@link CpuUsageEmulatorCore}) for
* performing the actual emulation. Multiple calls to this core engine should
* use up some amount of CPU.<br>
*
* <p>{@link CumulativeCpuUsageEmulatorPlugin} provides a calibration feature
* via {@link #initialize(Configuration, ResourceUsageMetrics,
* ResourceCalculatorPlugin, Progressive)} to calibrate
* the plugin and its core for the underlying hardware. As a result of
* calibration, every call to the emulation engine's core should roughly use up
* 1% of the total usage value to be emulated. This makes sure that the
* underlying hardware is profiled before use and that the plugin doesn't
* accidently overuse the CPU. With 1% as the unit emulation target value for
* the core engine, there will be roughly 100 calls to the engine resulting in
* roughly 100 calls to the feedback (resource usage monitor) module.
* Excessive usage of the feedback module is discouraged as
* it might result into excess CPU usage resulting into no real CPU emulation.
* </p>
*/
public class CumulativeCpuUsageEmulatorPlugin
implements ResourceUsageEmulatorPlugin {
protected CpuUsageEmulatorCore emulatorCore;
private ResourceCalculatorPlugin monitor;
private Progressive progress;
private boolean enabled = true;
private float emulationInterval; // emulation interval
private long targetCpuUsage = 0;
private float lastSeenProgress = 0;
private long lastSeenCpuUsage = 0;
// Configuration parameters
public static final String CPU_EMULATION_PROGRESS_INTERVAL =
"gridmix.emulators.resource-usage.cpu.emulation-interval";
private static final float DEFAULT_EMULATION_FREQUENCY = 0.1F; // 10 times
/**
* This is the core CPU usage emulation algorithm. This is the core engine
* which actually performs some CPU intensive operations to consume some
* amount of CPU. Multiple calls of {@link #compute()} should help the
* plugin emulate the desired level of CPU usage. This core engine can be
* calibrated using the {@link #calibrate(ResourceCalculatorPlugin, long)}
* API to suit the underlying hardware better. It also can be used to optimize
* the emulation cycle.
*/
public interface CpuUsageEmulatorCore {
/**
* Performs some computation to use up some CPU.
*/
public void compute();
/**
* Allows the core to calibrate itself.
*/
public void calibrate(ResourceCalculatorPlugin monitor,
long totalCpuUsage);
}
/**
* This is the core engine to emulate the CPU usage. The only responsibility
* of this class is to perform certain math intensive operations to make sure
* that some desired value of CPU is used.
*/
public static class DefaultCpuUsageEmulator implements CpuUsageEmulatorCore {
// number of times to loop for performing the basic unit computation
private int numIterations;
private final Random random;
/**
* This is to fool the JVM and make it think that we need the value
* stored in the unit computation i.e {@link #compute()}. This will prevent
* the JVM from optimizing the code.
*/
protected double returnValue;
/**
* Initialized the {@link DefaultCpuUsageEmulator} with default values.
* Note that the {@link DefaultCpuUsageEmulator} should be calibrated
* (see {@link #calibrate(ResourceCalculatorPlugin, long)}) when initialized
* using this constructor.
*/
public DefaultCpuUsageEmulator() {
this(-1);
}
DefaultCpuUsageEmulator(int numIterations) {
this.numIterations = numIterations;
random = new Random();
}
/**
* This will consume some desired level of CPU. This API will try to use up
* 'X' percent of the target cumulative CPU usage. Currently X is set to
* 10%.
*/
public void compute() {
for (int i = 0; i < numIterations; ++i) {
performUnitComputation();
}
}
// Perform unit computation. The complete CPU emulation will be based on
// multiple invocations to this unit computation module.
protected void performUnitComputation() {
//TODO can this be configurable too. Users/emulators should be able to
// pick and choose what MATH operations to run.
// Example :
// BASIC : ADD, SUB, MUL, DIV
// ADV : SQRT, SIN, COSIN..
// COMPO : (BASIC/ADV)*
// Also define input generator. For now we can use the random number
// generator. Later this can be changed to accept multiple sources.
int randomData = random.nextInt();
int randomDataCube = randomData * randomData * randomData;
double randomDataCubeRoot = Math.cbrt(randomData);
returnValue = Math.log(Math.tan(randomDataCubeRoot
* Math.exp(randomDataCube))
* Math.sqrt(randomData));
}
/**
* This will calibrate the algorithm such that a single invocation of
* {@link #compute()} emulates roughly 1% of the total desired resource
* usage value.
*/
public void calibrate(ResourceCalculatorPlugin monitor,
long totalCpuUsage) {
long initTime = monitor.getCumulativeCpuTime();
long defaultLoopSize = 0;
long finalTime = initTime;
//TODO Make this configurable
while (finalTime - initTime < 100) { // 100 ms
++defaultLoopSize;
performUnitComputation(); //perform unit computation
finalTime = monitor.getCumulativeCpuTime();
}
long referenceRuntime = finalTime - initTime;
// time for one loop = (final-time - init-time) / total-loops
float timePerLoop = ((float)referenceRuntime) / defaultLoopSize;
// compute the 1% of the total CPU usage desired
//TODO Make this configurable
long onePercent = totalCpuUsage / 100;
// num-iterations for 1% = (total-desired-usage / 100) / time-for-one-loop
numIterations = Math.max(1, (int)((float)onePercent/timePerLoop));
System.out.println("Calibration done. Basic computation runtime : "
+ timePerLoop + " milliseconds. Optimal number of iterations (1%): "
+ numIterations);
}
}
public CumulativeCpuUsageEmulatorPlugin() {
this(new DefaultCpuUsageEmulator());
}
/**
* For testing.
*/
public CumulativeCpuUsageEmulatorPlugin(CpuUsageEmulatorCore core) {
emulatorCore = core;
}
// Note that this weighing function uses only the current progress. In future,
// this might depend on progress, emulation-interval and expected target.
private float getWeightForProgressInterval(float progress) {
// we want some kind of exponential growth function that gives less weight
// on lower progress boundaries but high (exact emulation) near progress
// value of 1.
// so here is how the current growth function looks like
// progress weight
// 0.1 0.0001
// 0.2 0.0016
// 0.3 0.0081
// 0.4 0.0256
// 0.5 0.0625
// 0.6 0.1296
// 0.7 0.2401
// 0.8 0.4096
// 0.9 0.6561
// 1.0 1.000
return progress * progress * progress * progress;
}
private synchronized long getCurrentCPUUsage() {
return monitor.getCumulativeCpuTime();
}
@Override
public float getProgress() {
return enabled
? Math.min(1f, ((float)getCurrentCPUUsage())/targetCpuUsage)
: 1.0f;
}
@Override
//TODO Multi-threading for speedup?
public void emulate() throws IOException, InterruptedException {
if (enabled) {
float currentProgress = progress.getProgress();
if (lastSeenProgress < currentProgress
&& ((currentProgress - lastSeenProgress) >= emulationInterval
|| currentProgress == 1)) {
// Estimate the final cpu usage
//
// Consider the following
// Cl/Cc/Cp : Last/Current/Projected Cpu usage
// Pl/Pc/Pp : Last/Current/Projected progress
// Then
// (Cp-Cc)/(Pp-Pc) = (Cc-Cl)/(Pc-Pl)
// Solving this for Cp, we get
// Cp = Cc + (1-Pc)*(Cc-Cl)/Pc-Pl)
// Note that (Cc-Cl)/(Pc-Pl) is termed as 'rate' in the following
// section
long currentCpuUsage = getCurrentCPUUsage();
// estimate the cpu usage rate
float rate = (currentCpuUsage - lastSeenCpuUsage)
/ (currentProgress - lastSeenProgress);
long projectedUsage =
currentCpuUsage + (long)((1 - currentProgress) * rate);
if (projectedUsage < targetCpuUsage) {
// determine the correction factor between the current usage and the
// expected usage and add some weight to the target
long currentWeighedTarget =
(long)(targetCpuUsage
* getWeightForProgressInterval(currentProgress));
while (getCurrentCPUUsage() < currentWeighedTarget) {
emulatorCore.compute();
// sleep for 100ms
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
String message =
"CumulativeCpuUsageEmulatorPlugin got interrupted. Exiting.";
throw new RuntimeException(message);
}
}
}
// set the last seen progress
lastSeenProgress = progress.getProgress();
// set the last seen usage
lastSeenCpuUsage = getCurrentCPUUsage();
}
}
}
@Override
public void initialize(Configuration conf, ResourceUsageMetrics metrics,
ResourceCalculatorPlugin monitor,
Progressive progress) {
this.monitor = monitor;
this.progress = progress;
// get the target CPU usage
targetCpuUsage = metrics.getCumulativeCpuUsage();
if (targetCpuUsage <= 0 ) {
enabled = false;
return;
} else {
enabled = true;
}
emulationInterval = conf.getFloat(CPU_EMULATION_PROGRESS_INTERVAL,
DEFAULT_EMULATION_FREQUENCY);
// calibrate the core cpu-usage utility
emulatorCore.calibrate(monitor, targetCpuUsage);
// initialize the states
lastSeenProgress = 0;
lastSeenCpuUsage = 0;
}
}