HADOOP-12916. Allow RPC scheduler/callqueue backoff using response times. Contributed by Xiaoyu Yao.
(cherry picked from commit d95c6eb32cec7768ac418fb467b1198ccf3cf0dc) (cherry picked from commit a0c001c0b95f9c98b747f0534c33e3725940adab)
This commit is contained in:
parent
dcad99892d
commit
6db3c9d394
@ -1570,6 +1570,10 @@ public long getTimeDuration(String name, long defaultValue, TimeUnit unit) {
|
|||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
vStr = vStr.trim();
|
vStr = vStr.trim();
|
||||||
|
return getTimeDurationHelper(name, vStr, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getTimeDurationHelper(String name, String vStr, TimeUnit unit) {
|
||||||
ParsedTimeDuration vUnit = ParsedTimeDuration.unitFor(vStr);
|
ParsedTimeDuration vUnit = ParsedTimeDuration.unitFor(vStr);
|
||||||
if (null == vUnit) {
|
if (null == vUnit) {
|
||||||
LOG.warn("No unit for " + name + "(" + vStr + ") assuming " + unit);
|
LOG.warn("No unit for " + name + "(" + vStr + ") assuming " + unit);
|
||||||
@ -1580,6 +1584,15 @@ public long getTimeDuration(String name, long defaultValue, TimeUnit unit) {
|
|||||||
return unit.convert(Long.parseLong(vStr), vUnit.unit());
|
return unit.convert(Long.parseLong(vStr), vUnit.unit());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long[] getTimeDurations(String name, TimeUnit unit) {
|
||||||
|
String[] strings = getTrimmedStrings(name);
|
||||||
|
long[] durations = new long[strings.length];
|
||||||
|
for (int i = 0; i < strings.length; i++) {
|
||||||
|
durations[i] = getTimeDurationHelper(name, strings[i], unit);
|
||||||
|
}
|
||||||
|
return durations;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value of the <code>name</code> property as a <code>Pattern</code>.
|
* Get the value of the <code>name</code> property as a <code>Pattern</code>.
|
||||||
* If no such property is specified, or if the specified value is not a valid
|
* If no such property is specified, or if the specified value is not a valid
|
||||||
|
@ -90,14 +90,22 @@ public class CommonConfigurationKeys extends CommonConfigurationKeysPublic {
|
|||||||
/**
|
/**
|
||||||
* CallQueue related settings. These are not used directly, but rather
|
* CallQueue related settings. These are not used directly, but rather
|
||||||
* combined with a namespace and port. For instance:
|
* combined with a namespace and port. For instance:
|
||||||
* IPC_CALLQUEUE_NAMESPACE + ".8020." + IPC_CALLQUEUE_IMPL_KEY
|
* IPC_NAMESPACE + ".8020." + IPC_CALLQUEUE_IMPL_KEY
|
||||||
*/
|
*/
|
||||||
public static final String IPC_CALLQUEUE_NAMESPACE = "ipc";
|
public static final String IPC_NAMESPACE = "ipc";
|
||||||
public static final String IPC_CALLQUEUE_IMPL_KEY = "callqueue.impl";
|
public static final String IPC_CALLQUEUE_IMPL_KEY = "callqueue.impl";
|
||||||
public static final String IPC_CALLQUEUE_IDENTITY_PROVIDER_KEY = "identity-provider.impl";
|
public static final String IPC_SCHEDULER_IMPL_KEY = "scheduler.impl";
|
||||||
|
public static final String IPC_IDENTITY_PROVIDER_KEY = "identity-provider.impl";
|
||||||
public static final String IPC_BACKOFF_ENABLE = "backoff.enable";
|
public static final String IPC_BACKOFF_ENABLE = "backoff.enable";
|
||||||
public static final boolean IPC_BACKOFF_ENABLE_DEFAULT = false;
|
public static final boolean IPC_BACKOFF_ENABLE_DEFAULT = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPC scheduler priority levels.
|
||||||
|
*/
|
||||||
|
public static final String IPC_SCHEDULER_PRIORITY_LEVELS_KEY =
|
||||||
|
"scheduler.priority.levels";
|
||||||
|
public static final int IPC_SCHEDULER_PRIORITY_LEVELS_DEFAULT_KEY = 4;
|
||||||
|
|
||||||
/** This is for specifying the implementation for the mappings from
|
/** This is for specifying the implementation for the mappings from
|
||||||
* hostnames to the racks they belong to
|
* hostnames to the racks they belong to
|
||||||
*/
|
*/
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.fs.CommonConfigurationKeys;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstracts queue operations for different blocking queues.
|
* Abstracts queue operations for different blocking queues.
|
||||||
@ -43,6 +44,13 @@ static <E> Class<? extends BlockingQueue<E>> convertQueueClass(
|
|||||||
Class<?> queueClass, Class<E> elementClass) {
|
Class<?> queueClass, Class<E> elementClass) {
|
||||||
return (Class<? extends BlockingQueue<E>>)queueClass;
|
return (Class<? extends BlockingQueue<E>>)queueClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
static Class<? extends RpcScheduler> convertSchedulerClass(
|
||||||
|
Class<?> schedulerClass) {
|
||||||
|
return (Class<? extends RpcScheduler>)schedulerClass;
|
||||||
|
}
|
||||||
|
|
||||||
private final boolean clientBackOffEnabled;
|
private final boolean clientBackOffEnabled;
|
||||||
|
|
||||||
// Atomic refs point to active callQueue
|
// Atomic refs point to active callQueue
|
||||||
@ -50,25 +58,76 @@ static <E> Class<? extends BlockingQueue<E>> convertQueueClass(
|
|||||||
private final AtomicReference<BlockingQueue<E>> putRef;
|
private final AtomicReference<BlockingQueue<E>> putRef;
|
||||||
private final AtomicReference<BlockingQueue<E>> takeRef;
|
private final AtomicReference<BlockingQueue<E>> takeRef;
|
||||||
|
|
||||||
|
private RpcScheduler scheduler;
|
||||||
|
|
||||||
public CallQueueManager(Class<? extends BlockingQueue<E>> backingClass,
|
public CallQueueManager(Class<? extends BlockingQueue<E>> backingClass,
|
||||||
|
Class<? extends RpcScheduler> schedulerClass,
|
||||||
boolean clientBackOffEnabled, int maxQueueSize, String namespace,
|
boolean clientBackOffEnabled, int maxQueueSize, String namespace,
|
||||||
Configuration conf) {
|
Configuration conf) {
|
||||||
|
int priorityLevels = parseNumLevels(namespace, conf);
|
||||||
|
this.scheduler = createScheduler(schedulerClass, priorityLevels,
|
||||||
|
namespace, conf);
|
||||||
BlockingQueue<E> bq = createCallQueueInstance(backingClass,
|
BlockingQueue<E> bq = createCallQueueInstance(backingClass,
|
||||||
maxQueueSize, namespace, conf);
|
priorityLevels, maxQueueSize, namespace, conf);
|
||||||
this.clientBackOffEnabled = clientBackOffEnabled;
|
this.clientBackOffEnabled = clientBackOffEnabled;
|
||||||
this.putRef = new AtomicReference<BlockingQueue<E>>(bq);
|
this.putRef = new AtomicReference<BlockingQueue<E>>(bq);
|
||||||
this.takeRef = new AtomicReference<BlockingQueue<E>>(bq);
|
this.takeRef = new AtomicReference<BlockingQueue<E>>(bq);
|
||||||
LOG.info("Using callQueue " + backingClass);
|
LOG.info("Using callQueue: " + backingClass + " scheduler: " +
|
||||||
|
schedulerClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends RpcScheduler> T createScheduler(
|
||||||
|
Class<T> theClass, int priorityLevels, String ns, Configuration conf) {
|
||||||
|
// Used for custom, configurable scheduler
|
||||||
|
try {
|
||||||
|
Constructor<T> ctor = theClass.getDeclaredConstructor(int.class,
|
||||||
|
String.class, Configuration.class);
|
||||||
|
return ctor.newInstance(priorityLevels, ns, conf);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw new RuntimeException(theClass.getName()
|
||||||
|
+ " could not be constructed.", e.getCause());
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Constructor<T> ctor = theClass.getDeclaredConstructor(int.class);
|
||||||
|
return ctor.newInstance(priorityLevels);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw new RuntimeException(theClass.getName()
|
||||||
|
+ " could not be constructed.", e.getCause());
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last attempt
|
||||||
|
try {
|
||||||
|
Constructor<T> ctor = theClass.getDeclaredConstructor();
|
||||||
|
return ctor.newInstance();
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw new RuntimeException(theClass.getName()
|
||||||
|
+ " could not be constructed.", e.getCause());
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing worked
|
||||||
|
throw new RuntimeException(theClass.getName() +
|
||||||
|
" could not be constructed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T extends BlockingQueue<E>> T createCallQueueInstance(
|
private <T extends BlockingQueue<E>> T createCallQueueInstance(
|
||||||
Class<T> theClass, int maxLen, String ns, Configuration conf) {
|
Class<T> theClass, int priorityLevels, int maxLen, String ns,
|
||||||
|
Configuration conf) {
|
||||||
|
|
||||||
// Used for custom, configurable callqueues
|
// Used for custom, configurable callqueues
|
||||||
try {
|
try {
|
||||||
Constructor<T> ctor = theClass.getDeclaredConstructor(int.class, String.class,
|
Constructor<T> ctor = theClass.getDeclaredConstructor(int.class,
|
||||||
Configuration.class);
|
int.class, String.class, Configuration.class);
|
||||||
return ctor.newInstance(maxLen, ns, conf);
|
return ctor.newInstance(priorityLevels, maxLen, ns, conf);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (InvocationTargetException e) {
|
} catch (InvocationTargetException e) {
|
||||||
@ -110,6 +169,22 @@ boolean isClientBackoffEnabled() {
|
|||||||
return clientBackOffEnabled;
|
return clientBackOffEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Based on policy to determine back off current call
|
||||||
|
boolean shouldBackOff(Schedulable e) {
|
||||||
|
return scheduler.shouldBackOff(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addResponseTime(String name, int priorityLevel, int queueTime,
|
||||||
|
int processingTime) {
|
||||||
|
scheduler.addResponseTime(name, priorityLevel, queueTime, processingTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should be only called once per call and cached in the call object
|
||||||
|
// each getPriorityLevel call will increment the counter for the caller
|
||||||
|
int getPriorityLevel(Schedulable e) {
|
||||||
|
return scheduler.getPriorityLevel(e);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert e into the backing queue or block until we can.
|
* Insert e into the backing queue or block until we can.
|
||||||
* If we block and the queue changes on us, we will insert while the
|
* If we block and the queue changes on us, we will insert while the
|
||||||
@ -146,15 +221,46 @@ public int size() {
|
|||||||
return takeRef.get().size();
|
return takeRef.get().size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the number of levels from the configuration.
|
||||||
|
* This will affect the FairCallQueue's overall capacity.
|
||||||
|
* @throws IllegalArgumentException on invalid queue count
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private static int parseNumLevels(String ns, Configuration conf) {
|
||||||
|
// Fair call queue levels (IPC_CALLQUEUE_PRIORITY_LEVELS_KEY)
|
||||||
|
// takes priority over the scheduler level key
|
||||||
|
// (IPC_SCHEDULER_PRIORITY_LEVELS_KEY)
|
||||||
|
int retval = conf.getInt(ns + "." +
|
||||||
|
FairCallQueue.IPC_CALLQUEUE_PRIORITY_LEVELS_KEY, 0);
|
||||||
|
if (retval == 0) { // No FCQ priority level configured
|
||||||
|
retval = conf.getInt(ns + "." +
|
||||||
|
CommonConfigurationKeys.IPC_SCHEDULER_PRIORITY_LEVELS_KEY,
|
||||||
|
CommonConfigurationKeys.IPC_SCHEDULER_PRIORITY_LEVELS_DEFAULT_KEY);
|
||||||
|
} else {
|
||||||
|
LOG.warn(ns + "." + FairCallQueue.IPC_CALLQUEUE_PRIORITY_LEVELS_KEY +
|
||||||
|
" is deprecated. Please use " + ns + "." +
|
||||||
|
CommonConfigurationKeys.IPC_SCHEDULER_PRIORITY_LEVELS_KEY + ".");
|
||||||
|
}
|
||||||
|
if(retval < 1) {
|
||||||
|
throw new IllegalArgumentException("numLevels must be at least 1");
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces active queue with the newly requested one and transfers
|
* Replaces active queue with the newly requested one and transfers
|
||||||
* all calls to the newQ before returning.
|
* all calls to the newQ before returning.
|
||||||
*/
|
*/
|
||||||
public synchronized void swapQueue(
|
public synchronized void swapQueue(
|
||||||
|
Class<? extends RpcScheduler> schedulerClass,
|
||||||
Class<? extends BlockingQueue<E>> queueClassToUse, int maxSize,
|
Class<? extends BlockingQueue<E>> queueClassToUse, int maxSize,
|
||||||
String ns, Configuration conf) {
|
String ns, Configuration conf) {
|
||||||
BlockingQueue<E> newQ = createCallQueueInstance(queueClassToUse, maxSize,
|
int priorityLevels = parseNumLevels(ns, conf);
|
||||||
ns, conf);
|
RpcScheduler newScheduler = createScheduler(schedulerClass, priorityLevels,
|
||||||
|
ns, conf);
|
||||||
|
BlockingQueue<E> newQ = createCallQueueInstance(queueClassToUse,
|
||||||
|
priorityLevels, maxSize, ns, conf);
|
||||||
|
|
||||||
// Our current queue becomes the old queue
|
// Our current queue becomes the old queue
|
||||||
BlockingQueue<E> oldQ = putRef.get();
|
BlockingQueue<E> oldQ = putRef.get();
|
||||||
@ -168,6 +274,8 @@ public synchronized void swapQueue(
|
|||||||
// Swap takeRef to handle new calls
|
// Swap takeRef to handle new calls
|
||||||
takeRef.set(newQ);
|
takeRef.set(newQ);
|
||||||
|
|
||||||
|
this.scheduler = newScheduler;
|
||||||
|
|
||||||
LOG.info("Old Queue: " + stringRepr(oldQ) + ", " +
|
LOG.info("Old Queue: " + stringRepr(oldQ) + ", " +
|
||||||
"Replacement: " + stringRepr(newQ));
|
"Replacement: " + stringRepr(newQ));
|
||||||
}
|
}
|
||||||
|
@ -27,17 +27,21 @@
|
|||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.atomic.AtomicLongArray;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import com.google.common.util.concurrent.AtomicDoubleArray;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.fs.CommonConfigurationKeys;
|
import org.apache.hadoop.fs.CommonConfigurationKeys;
|
||||||
import org.apache.hadoop.metrics2.util.MBeans;
|
import org.apache.hadoop.metrics2.util.MBeans;
|
||||||
|
|
||||||
import org.codehaus.jackson.map.ObjectMapper;
|
import org.codehaus.jackson.map.ObjectMapper;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The decay RPC scheduler counts incoming requests in a map, then
|
* The decay RPC scheduler counts incoming requests in a map, then
|
||||||
@ -49,22 +53,28 @@ public class DecayRpcScheduler implements RpcScheduler, DecayRpcSchedulerMXBean
|
|||||||
/**
|
/**
|
||||||
* Period controls how many milliseconds between each decay sweep.
|
* Period controls how many milliseconds between each decay sweep.
|
||||||
*/
|
*/
|
||||||
public static final String IPC_CALLQUEUE_DECAYSCHEDULER_PERIOD_KEY =
|
public static final String IPC_SCHEDULER_DECAYSCHEDULER_PERIOD_KEY =
|
||||||
|
"decay-scheduler.period-ms";
|
||||||
|
public static final long IPC_SCHEDULER_DECAYSCHEDULER_PERIOD_DEFAULT =
|
||||||
|
5000;
|
||||||
|
@Deprecated
|
||||||
|
public static final String IPC_FCQ_DECAYSCHEDULER_PERIOD_KEY =
|
||||||
"faircallqueue.decay-scheduler.period-ms";
|
"faircallqueue.decay-scheduler.period-ms";
|
||||||
public static final long IPC_CALLQUEUE_DECAYSCHEDULER_PERIOD_DEFAULT =
|
|
||||||
5000L;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decay factor controls how much each count is suppressed by on each sweep.
|
* Decay factor controls how much each count is suppressed by on each sweep.
|
||||||
* Valid numbers are > 0 and < 1. Decay factor works in tandem with period
|
* Valid numbers are > 0 and < 1. Decay factor works in tandem with period
|
||||||
* to control how long the scheduler remembers an identity.
|
* to control how long the scheduler remembers an identity.
|
||||||
*/
|
*/
|
||||||
public static final String IPC_CALLQUEUE_DECAYSCHEDULER_FACTOR_KEY =
|
public static final String IPC_SCHEDULER_DECAYSCHEDULER_FACTOR_KEY =
|
||||||
|
"decay-scheduler.decay-factor";
|
||||||
|
public static final double IPC_SCHEDULER_DECAYSCHEDULER_FACTOR_DEFAULT =
|
||||||
|
0.5;
|
||||||
|
@Deprecated
|
||||||
|
public static final String IPC_FCQ_DECAYSCHEDULER_FACTOR_KEY =
|
||||||
"faircallqueue.decay-scheduler.decay-factor";
|
"faircallqueue.decay-scheduler.decay-factor";
|
||||||
public static final double IPC_CALLQUEUE_DECAYSCHEDULER_FACTOR_DEFAULT =
|
|
||||||
0.5;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thresholds are specified as integer percentages, and specify which usage
|
* Thresholds are specified as integer percentages, and specify which usage
|
||||||
* range each queue will be allocated to. For instance, specifying the list
|
* range each queue will be allocated to. For instance, specifying the list
|
||||||
* 10, 40, 80
|
* 10, 40, 80
|
||||||
@ -74,15 +84,31 @@ public class DecayRpcScheduler implements RpcScheduler, DecayRpcSchedulerMXBean
|
|||||||
* - q1 from 10 up to 40
|
* - q1 from 10 up to 40
|
||||||
* - q0 otherwise.
|
* - q0 otherwise.
|
||||||
*/
|
*/
|
||||||
public static final String IPC_CALLQUEUE_DECAYSCHEDULER_THRESHOLDS_KEY =
|
public static final String IPC_DECAYSCHEDULER_THRESHOLDS_KEY =
|
||||||
"faircallqueue.decay-scheduler.thresholds";
|
"decay-scheduler.thresholds";
|
||||||
|
@Deprecated
|
||||||
|
public static final String IPC_FCQ_DECAYSCHEDULER_THRESHOLDS_KEY =
|
||||||
|
"faircallqueue.decay-scheduler.thresholds";
|
||||||
|
|
||||||
// Specifies the identity to use when the IdentityProvider cannot handle
|
// Specifies the identity to use when the IdentityProvider cannot handle
|
||||||
// a schedulable.
|
// a schedulable.
|
||||||
public static final String DECAYSCHEDULER_UNKNOWN_IDENTITY =
|
public static final String DECAYSCHEDULER_UNKNOWN_IDENTITY =
|
||||||
"IdentityProvider.Unknown";
|
"IdentityProvider.Unknown";
|
||||||
|
|
||||||
public static final Log LOG = LogFactory.getLog(DecayRpcScheduler.class);
|
public static final String
|
||||||
|
IPC_DECAYSCHEDULER_BACKOFF_RESPONSETIME_ENABLE_KEY =
|
||||||
|
"decay-scheduler.backoff.responsetime.enable";
|
||||||
|
public static final Boolean
|
||||||
|
IPC_DECAYSCHEDULER_BACKOFF_RESPONSETIME_ENABLE_DEFAULT = false;
|
||||||
|
|
||||||
|
// Specifies the average response time (ms) thresholds of each
|
||||||
|
// level to trigger backoff
|
||||||
|
public static final String
|
||||||
|
IPC_DECAYSCHEDULER_BACKOFF_RESPONSETIME_THRESHOLDS_KEY =
|
||||||
|
"decay-scheduler.backoff.responsetime.thresholds";
|
||||||
|
|
||||||
|
public static final Logger LOG =
|
||||||
|
LoggerFactory.getLogger(DecayRpcScheduler.class);
|
||||||
|
|
||||||
// Track the number of calls for each schedulable identity
|
// Track the number of calls for each schedulable identity
|
||||||
private final ConcurrentHashMap<Object, AtomicLong> callCounts =
|
private final ConcurrentHashMap<Object, AtomicLong> callCounts =
|
||||||
@ -91,6 +117,14 @@ public class DecayRpcScheduler implements RpcScheduler, DecayRpcSchedulerMXBean
|
|||||||
// Should be the sum of all AtomicLongs in callCounts
|
// Should be the sum of all AtomicLongs in callCounts
|
||||||
private final AtomicLong totalCalls = new AtomicLong();
|
private final AtomicLong totalCalls = new AtomicLong();
|
||||||
|
|
||||||
|
// Track total call count and response time in current decay window
|
||||||
|
private final AtomicLongArray responseTimeCountInCurrWindow;
|
||||||
|
private final AtomicLongArray responseTimeTotalInCurrWindow;
|
||||||
|
|
||||||
|
// Track average response time in previous decay window
|
||||||
|
private final AtomicDoubleArray responseTimeAvgInLastWindow;
|
||||||
|
private final AtomicLongArray responseTimeCountInLastWindow;
|
||||||
|
|
||||||
// Pre-computed scheduling decisions during the decay sweep are
|
// Pre-computed scheduling decisions during the decay sweep are
|
||||||
// atomically swapped in as a read-only map
|
// atomically swapped in as a read-only map
|
||||||
private final AtomicReference<Map<Object, Integer>> scheduleCacheRef =
|
private final AtomicReference<Map<Object, Integer>> scheduleCacheRef =
|
||||||
@ -98,10 +132,12 @@ public class DecayRpcScheduler implements RpcScheduler, DecayRpcSchedulerMXBean
|
|||||||
|
|
||||||
// Tune the behavior of the scheduler
|
// Tune the behavior of the scheduler
|
||||||
private final long decayPeriodMillis; // How long between each tick
|
private final long decayPeriodMillis; // How long between each tick
|
||||||
private final double decayFactor; // nextCount = currentCount / decayFactor
|
private final double decayFactor; // nextCount = currentCount * decayFactor
|
||||||
private final int numQueues; // affects scheduling decisions, from 0 to numQueues - 1
|
private final int numLevels;
|
||||||
private final double[] thresholds;
|
private final double[] thresholds;
|
||||||
private final IdentityProvider identityProvider;
|
private final IdentityProvider identityProvider;
|
||||||
|
private final boolean backOffByResponseTimeEnabled;
|
||||||
|
private final long[] backOffResponseTimeThresholds;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This TimerTask will call decayCurrentCounts until
|
* This TimerTask will call decayCurrentCounts until
|
||||||
@ -132,35 +168,46 @@ public void run() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a decay scheduler.
|
* Create a decay scheduler.
|
||||||
* @param numQueues number of queues to schedule for
|
* @param numLevels number of priority levels
|
||||||
* @param ns config prefix, so that we can configure multiple schedulers
|
* @param ns config prefix, so that we can configure multiple schedulers
|
||||||
* in a single instance.
|
* in a single instance.
|
||||||
* @param conf configuration to use.
|
* @param conf configuration to use.
|
||||||
*/
|
*/
|
||||||
public DecayRpcScheduler(int numQueues, String ns, Configuration conf) {
|
public DecayRpcScheduler(int numLevels, String ns, Configuration conf) {
|
||||||
if (numQueues < 1) {
|
if(numLevels < 1) {
|
||||||
throw new IllegalArgumentException("number of queues must be > 0");
|
throw new IllegalArgumentException("Number of Priority Levels must be " +
|
||||||
|
"at least 1");
|
||||||
}
|
}
|
||||||
|
this.numLevels = numLevels;
|
||||||
this.numQueues = numQueues;
|
|
||||||
this.decayFactor = parseDecayFactor(ns, conf);
|
this.decayFactor = parseDecayFactor(ns, conf);
|
||||||
this.decayPeriodMillis = parseDecayPeriodMillis(ns, conf);
|
this.decayPeriodMillis = parseDecayPeriodMillis(ns, conf);
|
||||||
this.identityProvider = this.parseIdentityProvider(ns, conf);
|
this.identityProvider = this.parseIdentityProvider(ns, conf);
|
||||||
this.thresholds = parseThresholds(ns, conf, numQueues);
|
this.thresholds = parseThresholds(ns, conf, numLevels);
|
||||||
|
this.backOffByResponseTimeEnabled = parseBackOffByResponseTimeEnabled(ns,
|
||||||
|
conf);
|
||||||
|
this.backOffResponseTimeThresholds =
|
||||||
|
parseBackOffResponseTimeThreshold(ns, conf, numLevels);
|
||||||
|
|
||||||
// Setup delay timer
|
// Setup delay timer
|
||||||
Timer timer = new Timer();
|
Timer timer = new Timer();
|
||||||
DecayTask task = new DecayTask(this, timer);
|
DecayTask task = new DecayTask(this, timer);
|
||||||
timer.scheduleAtFixedRate(task, decayPeriodMillis, decayPeriodMillis);
|
timer.scheduleAtFixedRate(task, decayPeriodMillis, decayPeriodMillis);
|
||||||
|
|
||||||
MetricsProxy prox = MetricsProxy.getInstance(ns);
|
// Setup response time metrics
|
||||||
|
responseTimeTotalInCurrWindow = new AtomicLongArray(numLevels);
|
||||||
|
responseTimeCountInCurrWindow = new AtomicLongArray(numLevels);
|
||||||
|
responseTimeAvgInLastWindow = new AtomicDoubleArray(numLevels);
|
||||||
|
responseTimeCountInLastWindow = new AtomicLongArray(numLevels);
|
||||||
|
|
||||||
|
MetricsProxy prox = MetricsProxy.getInstance(ns, numLevels);
|
||||||
prox.setDelegate(this);
|
prox.setDelegate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load configs
|
// Load configs
|
||||||
private IdentityProvider parseIdentityProvider(String ns, Configuration conf) {
|
private IdentityProvider parseIdentityProvider(String ns,
|
||||||
|
Configuration conf) {
|
||||||
List<IdentityProvider> providers = conf.getInstances(
|
List<IdentityProvider> providers = conf.getInstances(
|
||||||
ns + "." + CommonConfigurationKeys.IPC_CALLQUEUE_IDENTITY_PROVIDER_KEY,
|
ns + "." + CommonConfigurationKeys.IPC_IDENTITY_PROVIDER_KEY,
|
||||||
IdentityProvider.class);
|
IdentityProvider.class);
|
||||||
|
|
||||||
if (providers.size() < 1) {
|
if (providers.size() < 1) {
|
||||||
@ -174,10 +221,16 @@ private IdentityProvider parseIdentityProvider(String ns, Configuration conf) {
|
|||||||
|
|
||||||
private static double parseDecayFactor(String ns, Configuration conf) {
|
private static double parseDecayFactor(String ns, Configuration conf) {
|
||||||
double factor = conf.getDouble(ns + "." +
|
double factor = conf.getDouble(ns + "." +
|
||||||
IPC_CALLQUEUE_DECAYSCHEDULER_FACTOR_KEY,
|
IPC_FCQ_DECAYSCHEDULER_FACTOR_KEY, 0.0);
|
||||||
IPC_CALLQUEUE_DECAYSCHEDULER_FACTOR_DEFAULT
|
if (factor == 0.0) {
|
||||||
);
|
factor = conf.getDouble(ns + "." +
|
||||||
|
IPC_SCHEDULER_DECAYSCHEDULER_FACTOR_KEY,
|
||||||
|
IPC_SCHEDULER_DECAYSCHEDULER_FACTOR_DEFAULT);
|
||||||
|
} else if ((factor > 0.0) && (factor < 1)) {
|
||||||
|
LOG.warn(IPC_FCQ_DECAYSCHEDULER_FACTOR_KEY +
|
||||||
|
" is deprecated. Please use " +
|
||||||
|
IPC_SCHEDULER_DECAYSCHEDULER_FACTOR_KEY + ".");
|
||||||
|
}
|
||||||
if (factor <= 0 || factor >= 1) {
|
if (factor <= 0 || factor >= 1) {
|
||||||
throw new IllegalArgumentException("Decay Factor " +
|
throw new IllegalArgumentException("Decay Factor " +
|
||||||
"must be between 0 and 1");
|
"must be between 0 and 1");
|
||||||
@ -188,10 +241,17 @@ private static double parseDecayFactor(String ns, Configuration conf) {
|
|||||||
|
|
||||||
private static long parseDecayPeriodMillis(String ns, Configuration conf) {
|
private static long parseDecayPeriodMillis(String ns, Configuration conf) {
|
||||||
long period = conf.getLong(ns + "." +
|
long period = conf.getLong(ns + "." +
|
||||||
IPC_CALLQUEUE_DECAYSCHEDULER_PERIOD_KEY,
|
IPC_FCQ_DECAYSCHEDULER_PERIOD_KEY,
|
||||||
IPC_CALLQUEUE_DECAYSCHEDULER_PERIOD_DEFAULT
|
0);
|
||||||
);
|
if (period == 0) {
|
||||||
|
period = conf.getLong(ns + "." +
|
||||||
|
IPC_SCHEDULER_DECAYSCHEDULER_PERIOD_KEY,
|
||||||
|
IPC_SCHEDULER_DECAYSCHEDULER_PERIOD_DEFAULT);
|
||||||
|
} else if (period > 0) {
|
||||||
|
LOG.warn((IPC_FCQ_DECAYSCHEDULER_PERIOD_KEY +
|
||||||
|
" is deprecated. Please use " +
|
||||||
|
IPC_SCHEDULER_DECAYSCHEDULER_PERIOD_KEY));
|
||||||
|
}
|
||||||
if (period <= 0) {
|
if (period <= 0) {
|
||||||
throw new IllegalArgumentException("Period millis must be >= 0");
|
throw new IllegalArgumentException("Period millis must be >= 0");
|
||||||
}
|
}
|
||||||
@ -200,15 +260,24 @@ private static long parseDecayPeriodMillis(String ns, Configuration conf) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static double[] parseThresholds(String ns, Configuration conf,
|
private static double[] parseThresholds(String ns, Configuration conf,
|
||||||
int numQueues) {
|
int numLevels) {
|
||||||
int[] percentages = conf.getInts(ns + "." +
|
int[] percentages = conf.getInts(ns + "." +
|
||||||
IPC_CALLQUEUE_DECAYSCHEDULER_THRESHOLDS_KEY);
|
IPC_FCQ_DECAYSCHEDULER_THRESHOLDS_KEY);
|
||||||
|
|
||||||
if (percentages.length == 0) {
|
if (percentages.length == 0) {
|
||||||
return getDefaultThresholds(numQueues);
|
percentages = conf.getInts(ns + "." + IPC_DECAYSCHEDULER_THRESHOLDS_KEY);
|
||||||
} else if (percentages.length != numQueues-1) {
|
if (percentages.length == 0) {
|
||||||
|
return getDefaultThresholds(numLevels);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.warn(IPC_FCQ_DECAYSCHEDULER_THRESHOLDS_KEY +
|
||||||
|
" is deprecated. Please use " +
|
||||||
|
IPC_DECAYSCHEDULER_THRESHOLDS_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (percentages.length != numLevels-1) {
|
||||||
throw new IllegalArgumentException("Number of thresholds should be " +
|
throw new IllegalArgumentException("Number of thresholds should be " +
|
||||||
(numQueues-1) + ". Was: " + percentages.length);
|
(numLevels-1) + ". Was: " + percentages.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert integer percentages to decimals
|
// Convert integer percentages to decimals
|
||||||
@ -223,14 +292,14 @@ private static double[] parseThresholds(String ns, Configuration conf,
|
|||||||
/**
|
/**
|
||||||
* Generate default thresholds if user did not specify. Strategy is
|
* Generate default thresholds if user did not specify. Strategy is
|
||||||
* to halve each time, since queue usage tends to be exponential.
|
* to halve each time, since queue usage tends to be exponential.
|
||||||
* So if numQueues is 4, we would generate: double[]{0.125, 0.25, 0.5}
|
* So if numLevels is 4, we would generate: double[]{0.125, 0.25, 0.5}
|
||||||
* which specifies the boundaries between each queue's usage.
|
* which specifies the boundaries between each queue's usage.
|
||||||
* @param numQueues number of queues to compute for
|
* @param numLevels number of levels to compute for
|
||||||
* @return array of boundaries of length numQueues - 1
|
* @return array of boundaries of length numLevels - 1
|
||||||
*/
|
*/
|
||||||
private static double[] getDefaultThresholds(int numQueues) {
|
private static double[] getDefaultThresholds(int numLevels) {
|
||||||
double[] ret = new double[numQueues - 1];
|
double[] ret = new double[numLevels - 1];
|
||||||
double div = Math.pow(2, numQueues - 1);
|
double div = Math.pow(2, numLevels - 1);
|
||||||
|
|
||||||
for (int i = 0; i < ret.length; i++) {
|
for (int i = 0; i < ret.length; i++) {
|
||||||
ret[i] = Math.pow(2, i)/div;
|
ret[i] = Math.pow(2, i)/div;
|
||||||
@ -238,39 +307,89 @@ private static double[] getDefaultThresholds(int numQueues) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static long[] parseBackOffResponseTimeThreshold(String ns,
|
||||||
|
Configuration conf, int numLevels) {
|
||||||
|
long[] responseTimeThresholds = conf.getTimeDurations(ns + "." +
|
||||||
|
IPC_DECAYSCHEDULER_BACKOFF_RESPONSETIME_THRESHOLDS_KEY,
|
||||||
|
TimeUnit.MILLISECONDS);
|
||||||
|
// backoff thresholds not specified
|
||||||
|
if (responseTimeThresholds.length == 0) {
|
||||||
|
return getDefaultBackOffResponseTimeThresholds(numLevels);
|
||||||
|
}
|
||||||
|
// backoff thresholds specified but not match with the levels
|
||||||
|
if (responseTimeThresholds.length != numLevels) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"responseTimeThresholds must match with the number of priority " +
|
||||||
|
"levels");
|
||||||
|
}
|
||||||
|
// invalid thresholds
|
||||||
|
for (long responseTimeThreshold: responseTimeThresholds) {
|
||||||
|
if (responseTimeThreshold <= 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"responseTimeThreshold millis must be >= 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return responseTimeThresholds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10s for level 0, 20s for level 1, 30s for level 2, ...
|
||||||
|
private static long[] getDefaultBackOffResponseTimeThresholds(int numLevels) {
|
||||||
|
long[] ret = new long[numLevels];
|
||||||
|
for (int i = 0; i < ret.length; i++) {
|
||||||
|
ret[i] = 10000*(i+1);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Boolean parseBackOffByResponseTimeEnabled(String ns,
|
||||||
|
Configuration conf) {
|
||||||
|
return conf.getBoolean(ns + "." +
|
||||||
|
IPC_DECAYSCHEDULER_BACKOFF_RESPONSETIME_ENABLE_KEY,
|
||||||
|
IPC_DECAYSCHEDULER_BACKOFF_RESPONSETIME_ENABLE_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decay the stored counts for each user and clean as necessary.
|
* Decay the stored counts for each user and clean as necessary.
|
||||||
* This method should be called periodically in order to keep
|
* This method should be called periodically in order to keep
|
||||||
* counts current.
|
* counts current.
|
||||||
*/
|
*/
|
||||||
private void decayCurrentCounts() {
|
private void decayCurrentCounts() {
|
||||||
long total = 0;
|
try {
|
||||||
Iterator<Map.Entry<Object, AtomicLong>> it =
|
long total = 0;
|
||||||
callCounts.entrySet().iterator();
|
Iterator<Map.Entry<Object, AtomicLong>> it =
|
||||||
|
callCounts.entrySet().iterator();
|
||||||
|
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
Map.Entry<Object, AtomicLong> entry = it.next();
|
Map.Entry<Object, AtomicLong> entry = it.next();
|
||||||
AtomicLong count = entry.getValue();
|
AtomicLong count = entry.getValue();
|
||||||
|
|
||||||
// Compute the next value by reducing it by the decayFactor
|
// Compute the next value by reducing it by the decayFactor
|
||||||
long currentValue = count.get();
|
long currentValue = count.get();
|
||||||
long nextValue = (long)(currentValue * decayFactor);
|
long nextValue = (long) (currentValue * decayFactor);
|
||||||
total += nextValue;
|
total += nextValue;
|
||||||
count.set(nextValue);
|
count.set(nextValue);
|
||||||
|
|
||||||
if (nextValue == 0) {
|
if (nextValue == 0) {
|
||||||
// We will clean up unused keys here. An interesting optimization might
|
// We will clean up unused keys here. An interesting optimization
|
||||||
// be to have an upper bound on keyspace in callCounts and only
|
// might be to have an upper bound on keyspace in callCounts and only
|
||||||
// clean once we pass it.
|
// clean once we pass it.
|
||||||
it.remove();
|
it.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the total so that we remain in sync
|
||||||
|
totalCalls.set(total);
|
||||||
|
|
||||||
|
// Now refresh the cache of scheduling decisions
|
||||||
|
recomputeScheduleCache();
|
||||||
|
|
||||||
|
// Update average response time with decay
|
||||||
|
updateAverageResponseTime(true);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOG.error("decayCurrentCounts exception: " +
|
||||||
|
ExceptionUtils.getFullStackTrace(ex));
|
||||||
|
throw ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the total so that we remain in sync
|
|
||||||
totalCalls.set(total);
|
|
||||||
|
|
||||||
// Now refresh the cache of scheduling decisions
|
|
||||||
recomputeScheduleCache();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -324,7 +443,7 @@ private long getAndIncrement(Object identity) throws InterruptedException {
|
|||||||
/**
|
/**
|
||||||
* Given the number of occurrences, compute a scheduling decision.
|
* Given the number of occurrences, compute a scheduling decision.
|
||||||
* @param occurrences how many occurrences
|
* @param occurrences how many occurrences
|
||||||
* @return scheduling decision from 0 to numQueues - 1
|
* @return scheduling decision from 0 to numLevels - 1
|
||||||
*/
|
*/
|
||||||
private int computePriorityLevel(long occurrences) {
|
private int computePriorityLevel(long occurrences) {
|
||||||
long totalCallSnapshot = totalCalls.get();
|
long totalCallSnapshot = totalCalls.get();
|
||||||
@ -334,14 +453,14 @@ private int computePriorityLevel(long occurrences) {
|
|||||||
proportion = (double) occurrences / totalCallSnapshot;
|
proportion = (double) occurrences / totalCallSnapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start with low priority queues, since they will be most common
|
// Start with low priority levels, since they will be most common
|
||||||
for(int i = (numQueues - 1); i > 0; i--) {
|
for(int i = (numLevels - 1); i > 0; i--) {
|
||||||
if (proportion >= this.thresholds[i - 1]) {
|
if (proportion >= this.thresholds[i - 1]) {
|
||||||
return i; // We've found our queue number
|
return i; // We've found our level number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we get this far, we're at queue 0
|
// If we get this far, we're at level 0
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,7 +468,7 @@ private int computePriorityLevel(long occurrences) {
|
|||||||
* Returns the priority level for a given identity by first trying the cache,
|
* Returns the priority level for a given identity by first trying the cache,
|
||||||
* then computing it.
|
* then computing it.
|
||||||
* @param identity an object responding to toString and hashCode
|
* @param identity an object responding to toString and hashCode
|
||||||
* @return integer scheduling decision from 0 to numQueues - 1
|
* @return integer scheduling decision from 0 to numLevels - 1
|
||||||
*/
|
*/
|
||||||
private int cachedOrComputedPriorityLevel(Object identity) {
|
private int cachedOrComputedPriorityLevel(Object identity) {
|
||||||
try {
|
try {
|
||||||
@ -360,22 +479,29 @@ private int cachedOrComputedPriorityLevel(Object identity) {
|
|||||||
if (scheduleCache != null) {
|
if (scheduleCache != null) {
|
||||||
Integer priority = scheduleCache.get(identity);
|
Integer priority = scheduleCache.get(identity);
|
||||||
if (priority != null) {
|
if (priority != null) {
|
||||||
|
LOG.debug("Cache priority for: {} with priority: {}", identity,
|
||||||
|
priority);
|
||||||
return priority;
|
return priority;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache was no good, compute it
|
// Cache was no good, compute it
|
||||||
return computePriorityLevel(occurrences);
|
int priority = computePriorityLevel(occurrences);
|
||||||
|
LOG.debug("compute priority for " + identity + " priority " + priority);
|
||||||
|
return priority;
|
||||||
|
|
||||||
} catch (InterruptedException ie) {
|
} catch (InterruptedException ie) {
|
||||||
LOG.warn("Caught InterruptedException, returning low priority queue");
|
LOG.warn("Caught InterruptedException, returning low priority level");
|
||||||
return numQueues - 1;
|
LOG.debug("Fallback priority for: {} with priority: {}", identity,
|
||||||
|
numLevels - 1);
|
||||||
|
return numLevels - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the appropriate priority for a schedulable based on past requests.
|
* Compute the appropriate priority for a schedulable based on past requests.
|
||||||
* @param obj the schedulable obj to query and remember
|
* @param obj the schedulable obj to query and remember
|
||||||
* @return the queue index which we recommend scheduling in
|
* @return the level index which we recommend scheduling in
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int getPriorityLevel(Schedulable obj) {
|
public int getPriorityLevel(Schedulable obj) {
|
||||||
@ -389,6 +515,73 @@ public int getPriorityLevel(Schedulable obj) {
|
|||||||
return cachedOrComputedPriorityLevel(identity);
|
return cachedOrComputedPriorityLevel(identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldBackOff(Schedulable obj) {
|
||||||
|
Boolean backOff = false;
|
||||||
|
if (backOffByResponseTimeEnabled) {
|
||||||
|
int priorityLevel = obj.getPriorityLevel();
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
double[] responseTimes = getAverageResponseTime();
|
||||||
|
LOG.debug("Current Caller: {} Priority: {} ",
|
||||||
|
obj.getUserGroupInformation().getUserName(),
|
||||||
|
obj.getPriorityLevel());
|
||||||
|
for (int i = 0; i < numLevels; i++) {
|
||||||
|
LOG.debug("Queue: {} responseTime: {} backoffThreshold: {}", i,
|
||||||
|
responseTimes[i], backOffResponseTimeThresholds[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// High priority rpc over threshold triggers back off of low priority rpc
|
||||||
|
for (int i = 0; i < priorityLevel + 1; i++) {
|
||||||
|
if (responseTimeAvgInLastWindow.get(i) >
|
||||||
|
backOffResponseTimeThresholds[i]) {
|
||||||
|
backOff = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return backOff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addResponseTime(String name, int priorityLevel, int queueTime,
|
||||||
|
int processingTime) {
|
||||||
|
responseTimeCountInCurrWindow.getAndIncrement(priorityLevel);
|
||||||
|
responseTimeTotalInCurrWindow.getAndAdd(priorityLevel,
|
||||||
|
queueTime+processingTime);
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("addResponseTime for call: {} priority: {} queueTime: {} " +
|
||||||
|
"processingTime: {} ", name, priorityLevel, queueTime,
|
||||||
|
processingTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the cached average response time at the end of decay window
|
||||||
|
void updateAverageResponseTime(boolean enableDecay) {
|
||||||
|
for (int i = 0; i < numLevels; i++) {
|
||||||
|
double averageResponseTime = 0;
|
||||||
|
long totalResponseTime = responseTimeTotalInCurrWindow.get(i);
|
||||||
|
long responseTimeCount = responseTimeCountInCurrWindow.get(i);
|
||||||
|
if (responseTimeCount > 0) {
|
||||||
|
averageResponseTime = (double) totalResponseTime / responseTimeCount;
|
||||||
|
}
|
||||||
|
final double lastAvg = responseTimeAvgInLastWindow.get(i);
|
||||||
|
if (enableDecay && lastAvg > 0.0) {
|
||||||
|
final double decayed = decayFactor * lastAvg + averageResponseTime;
|
||||||
|
responseTimeAvgInLastWindow.set(i, decayed);
|
||||||
|
} else {
|
||||||
|
responseTimeAvgInLastWindow.set(i, averageResponseTime);
|
||||||
|
}
|
||||||
|
responseTimeCountInLastWindow.set(i, responseTimeCount);
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("updateAverageResponseTime queue: {} Average: {} Count: {}",
|
||||||
|
i, averageResponseTime, responseTimeCount);
|
||||||
|
}
|
||||||
|
// Reset for next decay window
|
||||||
|
responseTimeTotalInCurrWindow.set(i, 0);
|
||||||
|
responseTimeCountInCurrWindow.set(i, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// For testing
|
// For testing
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public double getDecayFactor() { return decayFactor; }
|
public double getDecayFactor() { return decayFactor; }
|
||||||
@ -429,16 +622,21 @@ private static final class MetricsProxy implements DecayRpcSchedulerMXBean {
|
|||||||
|
|
||||||
// Weakref for delegate, so we don't retain it forever if it can be GC'd
|
// Weakref for delegate, so we don't retain it forever if it can be GC'd
|
||||||
private WeakReference<DecayRpcScheduler> delegate;
|
private WeakReference<DecayRpcScheduler> delegate;
|
||||||
|
private double[] averageResponseTimeDefault;
|
||||||
|
private long[] callCountInLastWindowDefault;
|
||||||
|
|
||||||
private MetricsProxy(String namespace) {
|
private MetricsProxy(String namespace, int numLevels) {
|
||||||
|
averageResponseTimeDefault = new double[numLevels];
|
||||||
|
callCountInLastWindowDefault = new long[numLevels];
|
||||||
MBeans.register(namespace, "DecayRpcScheduler", this);
|
MBeans.register(namespace, "DecayRpcScheduler", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized MetricsProxy getInstance(String namespace) {
|
public static synchronized MetricsProxy getInstance(String namespace,
|
||||||
|
int numLevels) {
|
||||||
MetricsProxy mp = INSTANCES.get(namespace);
|
MetricsProxy mp = INSTANCES.get(namespace);
|
||||||
if (mp == null) {
|
if (mp == null) {
|
||||||
// We must create one
|
// We must create one
|
||||||
mp = new MetricsProxy(namespace);
|
mp = new MetricsProxy(namespace, numLevels);
|
||||||
INSTANCES.put(namespace, mp);
|
INSTANCES.put(namespace, mp);
|
||||||
}
|
}
|
||||||
return mp;
|
return mp;
|
||||||
@ -487,6 +685,25 @@ public long getTotalCallVolume() {
|
|||||||
return scheduler.getTotalCallVolume();
|
return scheduler.getTotalCallVolume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double[] getAverageResponseTime() {
|
||||||
|
DecayRpcScheduler scheduler = delegate.get();
|
||||||
|
if (scheduler == null) {
|
||||||
|
return averageResponseTimeDefault;
|
||||||
|
} else {
|
||||||
|
return scheduler.getAverageResponseTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long[] getResponseTimeCountInLastWindow() {
|
||||||
|
DecayRpcScheduler scheduler = delegate.get();
|
||||||
|
if (scheduler == null) {
|
||||||
|
return callCountInLastWindowDefault;
|
||||||
|
} else {
|
||||||
|
return scheduler.getResponseTimeCountInLastWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getUniqueIdentityCount() {
|
public int getUniqueIdentityCount() {
|
||||||
@ -497,6 +714,23 @@ public long getTotalCallVolume() {
|
|||||||
return totalCalls.get();
|
return totalCalls.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long[] getResponseTimeCountInLastWindow() {
|
||||||
|
long[] ret = new long[responseTimeCountInLastWindow.length()];
|
||||||
|
for (int i = 0; i < responseTimeCountInLastWindow.length(); i++) {
|
||||||
|
ret[i] = responseTimeCountInLastWindow.get(i);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double[] getAverageResponseTime() {
|
||||||
|
double[] ret = new double[responseTimeAvgInLastWindow.length()];
|
||||||
|
for (int i = 0; i < responseTimeAvgInLastWindow.length(); i++) {
|
||||||
|
ret[i] = responseTimeAvgInLastWindow.get(i);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
public String getSchedulingDecisionSummary() {
|
public String getSchedulingDecisionSummary() {
|
||||||
Map<Object, Integer> decisions = scheduleCacheRef.get();
|
Map<Object, Integer> decisions = scheduleCacheRef.get();
|
||||||
if (decisions == null) {
|
if (decisions == null) {
|
||||||
|
@ -27,4 +27,6 @@ public interface DecayRpcSchedulerMXBean {
|
|||||||
String getCallVolumeSummary();
|
String getCallVolumeSummary();
|
||||||
int getUniqueIdentityCount();
|
int getUniqueIdentityCount();
|
||||||
long getTotalCallVolume();
|
long getTotalCallVolume();
|
||||||
|
double[] getAverageResponseTime();
|
||||||
|
long[] getResponseTimeCountInLastWindow();
|
||||||
}
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* 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.ipc;
|
||||||
|
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No op default RPC scheduler.
|
||||||
|
*/
|
||||||
|
public class DefaultRpcScheduler implements RpcScheduler {
|
||||||
|
@Override
|
||||||
|
public int getPriorityLevel(Schedulable obj) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldBackOff(Schedulable obj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addResponseTime(String name, int priorityLevel, int queueTime,
|
||||||
|
int processingTime) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultRpcScheduler(int priorityLevels, String namespace,
|
||||||
|
Configuration conf) {
|
||||||
|
}
|
||||||
|
}
|
@ -44,8 +44,9 @@
|
|||||||
*/
|
*/
|
||||||
public class FairCallQueue<E extends Schedulable> extends AbstractQueue<E>
|
public class FairCallQueue<E extends Schedulable> extends AbstractQueue<E>
|
||||||
implements BlockingQueue<E> {
|
implements BlockingQueue<E> {
|
||||||
// Configuration Keys
|
@Deprecated
|
||||||
public static final int IPC_CALLQUEUE_PRIORITY_LEVELS_DEFAULT = 4;
|
public static final int IPC_CALLQUEUE_PRIORITY_LEVELS_DEFAULT = 4;
|
||||||
|
@Deprecated
|
||||||
public static final String IPC_CALLQUEUE_PRIORITY_LEVELS_KEY =
|
public static final String IPC_CALLQUEUE_PRIORITY_LEVELS_KEY =
|
||||||
"faircallqueue.priority-levels";
|
"faircallqueue.priority-levels";
|
||||||
|
|
||||||
@ -66,9 +67,6 @@ private void signalNotEmpty() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scheduler picks which queue to place in */
|
|
||||||
private RpcScheduler scheduler;
|
|
||||||
|
|
||||||
/* Multiplexer picks which queue to draw from */
|
/* Multiplexer picks which queue to draw from */
|
||||||
private RpcMultiplexer multiplexer;
|
private RpcMultiplexer multiplexer;
|
||||||
|
|
||||||
@ -83,8 +81,13 @@ private void signalNotEmpty() {
|
|||||||
* Notes: the FairCallQueue has no fixed capacity. Rather, it has a minimum
|
* Notes: the FairCallQueue has no fixed capacity. Rather, it has a minimum
|
||||||
* capacity of `capacity` and a maximum capacity of `capacity * number_queues`
|
* capacity of `capacity` and a maximum capacity of `capacity * number_queues`
|
||||||
*/
|
*/
|
||||||
public FairCallQueue(int capacity, String ns, Configuration conf) {
|
public FairCallQueue(int priorityLevels, int capacity, String ns,
|
||||||
int numQueues = parseNumQueues(ns, conf);
|
Configuration conf) {
|
||||||
|
if(priorityLevels < 1) {
|
||||||
|
throw new IllegalArgumentException("Number of Priority Levels must be " +
|
||||||
|
"at least 1");
|
||||||
|
}
|
||||||
|
int numQueues = priorityLevels;
|
||||||
LOG.info("FairCallQueue is in use with " + numQueues + " queues.");
|
LOG.info("FairCallQueue is in use with " + numQueues + " queues.");
|
||||||
|
|
||||||
this.queues = new ArrayList<BlockingQueue<E>>(numQueues);
|
this.queues = new ArrayList<BlockingQueue<E>>(numQueues);
|
||||||
@ -95,28 +98,12 @@ public FairCallQueue(int capacity, String ns, Configuration conf) {
|
|||||||
this.overflowedCalls.add(new AtomicLong(0));
|
this.overflowedCalls.add(new AtomicLong(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scheduler = new DecayRpcScheduler(numQueues, ns, conf);
|
|
||||||
this.multiplexer = new WeightedRoundRobinMultiplexer(numQueues, ns, conf);
|
this.multiplexer = new WeightedRoundRobinMultiplexer(numQueues, ns, conf);
|
||||||
|
|
||||||
// Make this the active source of metrics
|
// Make this the active source of metrics
|
||||||
MetricsProxy mp = MetricsProxy.getInstance(ns);
|
MetricsProxy mp = MetricsProxy.getInstance(ns);
|
||||||
mp.setDelegate(this);
|
mp.setDelegate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read the number of queues from the configuration.
|
|
||||||
* This will affect the FairCallQueue's overall capacity.
|
|
||||||
* @throws IllegalArgumentException on invalid queue count
|
|
||||||
*/
|
|
||||||
private static int parseNumQueues(String ns, Configuration conf) {
|
|
||||||
int retval = conf.getInt(ns + "." + IPC_CALLQUEUE_PRIORITY_LEVELS_KEY,
|
|
||||||
IPC_CALLQUEUE_PRIORITY_LEVELS_DEFAULT);
|
|
||||||
if(retval < 1) {
|
|
||||||
throw new IllegalArgumentException("numQueues must be at least 1");
|
|
||||||
}
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the first non-empty queue with equal or lesser priority
|
* Returns the first non-empty queue with equal or lesser priority
|
||||||
* than <i>startIdx</i>. Wraps around, searching a maximum of N
|
* than <i>startIdx</i>. Wraps around, searching a maximum of N
|
||||||
@ -144,7 +131,7 @@ private BlockingQueue<E> getFirstNonEmptyQueue(int startIdx) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Put and offer follow the same pattern:
|
* Put and offer follow the same pattern:
|
||||||
* 1. Get a priorityLevel from the scheduler
|
* 1. Get the assigned priorityLevel from the call by scheduler
|
||||||
* 2. Get the nth sub-queue matching this priorityLevel
|
* 2. Get the nth sub-queue matching this priorityLevel
|
||||||
* 3. delegate the call to this sub-queue.
|
* 3. delegate the call to this sub-queue.
|
||||||
*
|
*
|
||||||
@ -154,7 +141,7 @@ private BlockingQueue<E> getFirstNonEmptyQueue(int startIdx) {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void put(E e) throws InterruptedException {
|
public void put(E e) throws InterruptedException {
|
||||||
int priorityLevel = scheduler.getPriorityLevel(e);
|
int priorityLevel = e.getPriorityLevel();
|
||||||
|
|
||||||
final int numLevels = this.queues.size();
|
final int numLevels = this.queues.size();
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -185,7 +172,7 @@ public void put(E e) throws InterruptedException {
|
|||||||
@Override
|
@Override
|
||||||
public boolean offer(E e, long timeout, TimeUnit unit)
|
public boolean offer(E e, long timeout, TimeUnit unit)
|
||||||
throws InterruptedException {
|
throws InterruptedException {
|
||||||
int priorityLevel = scheduler.getPriorityLevel(e);
|
int priorityLevel = e.getPriorityLevel();
|
||||||
BlockingQueue<E> q = this.queues.get(priorityLevel);
|
BlockingQueue<E> q = this.queues.get(priorityLevel);
|
||||||
boolean ret = q.offer(e, timeout, unit);
|
boolean ret = q.offer(e, timeout, unit);
|
||||||
|
|
||||||
@ -196,7 +183,7 @@ public boolean offer(E e, long timeout, TimeUnit unit)
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean offer(E e) {
|
public boolean offer(E e) {
|
||||||
int priorityLevel = scheduler.getPriorityLevel(e);
|
int priorityLevel = e.getPriorityLevel();
|
||||||
BlockingQueue<E> q = this.queues.get(priorityLevel);
|
BlockingQueue<E> q = this.queues.get(priorityLevel);
|
||||||
boolean ret = q.offer(e);
|
boolean ret = q.offer(e);
|
||||||
|
|
||||||
@ -436,12 +423,6 @@ public long[] getOverflowedCalls() {
|
|||||||
return calls;
|
return calls;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For testing
|
|
||||||
@VisibleForTesting
|
|
||||||
public void setScheduler(RpcScheduler newScheduler) {
|
|
||||||
this.scheduler = newScheduler;
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public void setMultiplexer(RpcMultiplexer newMux) {
|
public void setMultiplexer(RpcMultiplexer newMux) {
|
||||||
this.multiplexer = newMux;
|
this.multiplexer = newMux;
|
||||||
|
@ -634,13 +634,7 @@ public Writable call(RPC.Server server, String protocol,
|
|||||||
String detailedMetricsName = (exception == null) ?
|
String detailedMetricsName = (exception == null) ?
|
||||||
methodName :
|
methodName :
|
||||||
exception.getClass().getSimpleName();
|
exception.getClass().getSimpleName();
|
||||||
server.rpcMetrics.addRpcQueueTime(qTime);
|
server.updateMetrics(detailedMetricsName, qTime, processingTime);
|
||||||
server.rpcMetrics.addRpcProcessingTime(processingTime);
|
|
||||||
server.rpcDetailedMetrics.addProcessingTime(detailedMetricsName,
|
|
||||||
processingTime);
|
|
||||||
if (server.isLogSlowRPC()) {
|
|
||||||
server.logSlowRpcCalls(methodName, processingTime);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return new RpcResponseWrapper(result);
|
return new RpcResponseWrapper(result);
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,17 @@
|
|||||||
package org.apache.hadoop.ipc;
|
package org.apache.hadoop.ipc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implement this interface to be used for RPC scheduling in the fair call queues.
|
* Implement this interface to be used for RPC scheduling and backoff.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public interface RpcScheduler {
|
public interface RpcScheduler {
|
||||||
/**
|
/**
|
||||||
* Returns priority level greater than zero as a hint for scheduling.
|
* Returns priority level greater than zero as a hint for scheduling.
|
||||||
*/
|
*/
|
||||||
int getPriorityLevel(Schedulable obj);
|
int getPriorityLevel(Schedulable obj);
|
||||||
|
|
||||||
|
boolean shouldBackOff(Schedulable obj);
|
||||||
|
|
||||||
|
void addResponseTime(String name, int priorityLevel, int queueTime,
|
||||||
|
int processingTime);
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,8 @@
|
|||||||
|
|
||||||
package org.apache.hadoop.ipc;
|
package org.apache.hadoop.ipc;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import org.apache.hadoop.classification.InterfaceAudience;
|
import org.apache.hadoop.classification.InterfaceAudience;
|
||||||
import org.apache.hadoop.security.UserGroupInformation;
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
import org.apache.hadoop.io.Writable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface which allows extracting information necessary to
|
* Interface which allows extracting information necessary to
|
||||||
@ -31,4 +28,6 @@
|
|||||||
@InterfaceAudience.Private
|
@InterfaceAudience.Private
|
||||||
public interface Schedulable {
|
public interface Schedulable {
|
||||||
public UserGroupInformation getUserGroupInformation();
|
public UserGroupInformation getUserGroupInformation();
|
||||||
|
|
||||||
|
int getPriorityLevel();
|
||||||
}
|
}
|
||||||
|
@ -393,6 +393,15 @@ public static boolean isRpcInvocation() {
|
|||||||
return CurCall.get() != null;
|
return CurCall.get() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the priority level assigned by call queue to an RPC
|
||||||
|
* Returns 0 in case no priority is assigned.
|
||||||
|
*/
|
||||||
|
public static int getPriorityLevel() {
|
||||||
|
Call call = CurCall.get();
|
||||||
|
return call != null? call.getPriorityLevel() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
private String bindAddress;
|
private String bindAddress;
|
||||||
private int port; // port we listen on
|
private int port; // port we listen on
|
||||||
private int handlerCount; // number of handler threads
|
private int handlerCount; // number of handler threads
|
||||||
@ -479,6 +488,18 @@ void logSlowRpcCalls(String methodName, int processingTime) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateMetrics(String name, int queueTime, int processingTime) {
|
||||||
|
rpcMetrics.addRpcQueueTime(queueTime);
|
||||||
|
rpcMetrics.addRpcProcessingTime(processingTime);
|
||||||
|
rpcDetailedMetrics.addProcessingTime(name, processingTime);
|
||||||
|
callQueue.addResponseTime(name, getPriorityLevel(), queueTime,
|
||||||
|
processingTime);
|
||||||
|
|
||||||
|
if (isLogSlowRPC()) {
|
||||||
|
logSlowRpcCalls(name, processingTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A convenience method to bind to a given address and report
|
* A convenience method to bind to a given address and report
|
||||||
* better exceptions if the address is not a valid host.
|
* better exceptions if the address is not a valid host.
|
||||||
@ -575,6 +596,10 @@ public ServiceAuthorizationManager getServiceAuthorizationManager() {
|
|||||||
return serviceAuthorizationManager;
|
return serviceAuthorizationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getQueueClassPrefix() {
|
||||||
|
return CommonConfigurationKeys.IPC_NAMESPACE + "." + port;
|
||||||
|
}
|
||||||
|
|
||||||
static Class<? extends BlockingQueue<Call>> getQueueClass(
|
static Class<? extends BlockingQueue<Call>> getQueueClass(
|
||||||
String prefix, Configuration conf) {
|
String prefix, Configuration conf) {
|
||||||
String name = prefix + "." + CommonConfigurationKeys.IPC_CALLQUEUE_IMPL_KEY;
|
String name = prefix + "." + CommonConfigurationKeys.IPC_CALLQUEUE_IMPL_KEY;
|
||||||
@ -582,8 +607,29 @@ static Class<? extends BlockingQueue<Call>> getQueueClass(
|
|||||||
return CallQueueManager.convertQueueClass(queueClass, Call.class);
|
return CallQueueManager.convertQueueClass(queueClass, Call.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getQueueClassPrefix() {
|
static Class<? extends RpcScheduler> getSchedulerClass(
|
||||||
return CommonConfigurationKeys.IPC_CALLQUEUE_NAMESPACE + "." + port;
|
String prefix, Configuration conf) {
|
||||||
|
String schedulerKeyname = prefix + "." + CommonConfigurationKeys
|
||||||
|
.IPC_SCHEDULER_IMPL_KEY;
|
||||||
|
Class<?> schedulerClass = conf.getClass(schedulerKeyname, null);
|
||||||
|
// Patch the configuration for legacy fcq configuration that does not have
|
||||||
|
// a separate scheduler setting
|
||||||
|
if (schedulerClass == null) {
|
||||||
|
String queueKeyName = prefix + "." + CommonConfigurationKeys
|
||||||
|
.IPC_CALLQUEUE_IMPL_KEY;
|
||||||
|
Class<?> queueClass = conf.getClass(queueKeyName, null);
|
||||||
|
if (queueClass != null) {
|
||||||
|
if (queueClass.getCanonicalName().equals(
|
||||||
|
FairCallQueue.class.getCanonicalName())) {
|
||||||
|
conf.setClass(schedulerKeyname, DecayRpcScheduler.class,
|
||||||
|
RpcScheduler.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
schedulerClass = conf.getClass(schedulerKeyname,
|
||||||
|
DefaultRpcScheduler.class);
|
||||||
|
|
||||||
|
return CallQueueManager.convertSchedulerClass(schedulerClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -592,7 +638,8 @@ private String getQueueClassPrefix() {
|
|||||||
public synchronized void refreshCallQueue(Configuration conf) {
|
public synchronized void refreshCallQueue(Configuration conf) {
|
||||||
// Create the next queue
|
// Create the next queue
|
||||||
String prefix = getQueueClassPrefix();
|
String prefix = getQueueClassPrefix();
|
||||||
callQueue.swapQueue(getQueueClass(prefix, conf), maxQueueSize, prefix, conf);
|
callQueue.swapQueue(getSchedulerClass(prefix, conf),
|
||||||
|
getQueueClass(prefix, conf), maxQueueSize, prefix, conf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -620,6 +667,8 @@ public static class Call implements Schedulable {
|
|||||||
private final byte[] clientId;
|
private final byte[] clientId;
|
||||||
private final TraceScope traceScope; // the HTrace scope on the server side
|
private final TraceScope traceScope; // the HTrace scope on the server side
|
||||||
private final CallerContext callerContext; // the call context
|
private final CallerContext callerContext; // the call context
|
||||||
|
private int priorityLevel;
|
||||||
|
// the priority level assigned by scheduler, 0 by default
|
||||||
|
|
||||||
private Call(Call call) {
|
private Call(Call call) {
|
||||||
this(call.callId, call.retryCount, call.rpcRequest, call.connection,
|
this(call.callId, call.retryCount, call.rpcRequest, call.connection,
|
||||||
@ -706,7 +755,16 @@ public void abortResponse(Throwable t) throws IOException {
|
|||||||
@Override
|
@Override
|
||||||
public UserGroupInformation getUserGroupInformation() {
|
public UserGroupInformation getUserGroupInformation() {
|
||||||
return connection.user;
|
return connection.user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriorityLevel() {
|
||||||
|
return this.priorityLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriorityLevel(int priorityLevel) {
|
||||||
|
this.priorityLevel = priorityLevel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Listens on the socket. Creates jobs for the handler threads*/
|
/** Listens on the socket. Creates jobs for the handler threads*/
|
||||||
@ -2066,6 +2124,9 @@ private void processRpcRequest(RpcRequestHeaderProto header,
|
|||||||
rpcRequest, this, ProtoUtil.convert(header.getRpcKind()),
|
rpcRequest, this, ProtoUtil.convert(header.getRpcKind()),
|
||||||
header.getClientId().toByteArray(), traceScope, callerContext);
|
header.getClientId().toByteArray(), traceScope, callerContext);
|
||||||
|
|
||||||
|
// Save the priority level assignment by the scheduler
|
||||||
|
call.setPriorityLevel(callQueue.getPriorityLevel(call));
|
||||||
|
|
||||||
if (callQueue.isClientBackoffEnabled()) {
|
if (callQueue.isClientBackoffEnabled()) {
|
||||||
// if RPC queue is full, we will ask the RPC client to back off by
|
// if RPC queue is full, we will ask the RPC client to back off by
|
||||||
// throwing RetriableException. Whether RPC client will honor
|
// throwing RetriableException. Whether RPC client will honor
|
||||||
@ -2081,9 +2142,10 @@ private void processRpcRequest(RpcRequestHeaderProto header,
|
|||||||
|
|
||||||
private void queueRequestOrAskClientToBackOff(Call call)
|
private void queueRequestOrAskClientToBackOff(Call call)
|
||||||
throws WrappedRpcServerException, InterruptedException {
|
throws WrappedRpcServerException, InterruptedException {
|
||||||
// If rpc queue is full, we will ask the client to back off.
|
// If rpc scheduler indicates back off based on performance
|
||||||
boolean isCallQueued = callQueue.offer(call);
|
// degradation such as response time or rpc queue is full,
|
||||||
if (!isCallQueued) {
|
// we will ask the client to back off.
|
||||||
|
if (callQueue.shouldBackOff(call) || !callQueue.offer(call)) {
|
||||||
rpcMetrics.incrClientBackoff();
|
rpcMetrics.incrClientBackoff();
|
||||||
RetriableException retriableException =
|
RetriableException retriableException =
|
||||||
new RetriableException("Server is too busy.");
|
new RetriableException("Server is too busy.");
|
||||||
@ -2427,6 +2489,7 @@ protected Server(String bindAddress, int port,
|
|||||||
// Setup appropriate callqueue
|
// Setup appropriate callqueue
|
||||||
final String prefix = getQueueClassPrefix();
|
final String prefix = getQueueClassPrefix();
|
||||||
this.callQueue = new CallQueueManager<Call>(getQueueClass(prefix, conf),
|
this.callQueue = new CallQueueManager<Call>(getQueueClass(prefix, conf),
|
||||||
|
getSchedulerClass(prefix, conf),
|
||||||
getClientBackoffEnable(prefix, conf), maxQueueSize, prefix, conf);
|
getClientBackoffEnable(prefix, conf), maxQueueSize, prefix, conf);
|
||||||
|
|
||||||
this.secretManager = (SecretManager<TokenIdentifier>) secretManager;
|
this.secretManager = (SecretManager<TokenIdentifier>) secretManager;
|
||||||
|
@ -34,7 +34,6 @@
|
|||||||
import org.apache.hadoop.io.retry.RetryPolicy;
|
import org.apache.hadoop.io.retry.RetryPolicy;
|
||||||
import org.apache.hadoop.ipc.Client.ConnectionId;
|
import org.apache.hadoop.ipc.Client.ConnectionId;
|
||||||
import org.apache.hadoop.ipc.RPC.RpcInvoker;
|
import org.apache.hadoop.ipc.RPC.RpcInvoker;
|
||||||
import org.apache.hadoop.ipc.VersionedProtocol;
|
|
||||||
import org.apache.hadoop.security.UserGroupInformation;
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
import org.apache.hadoop.security.token.SecretManager;
|
import org.apache.hadoop.security.token.SecretManager;
|
||||||
import org.apache.hadoop.security.token.TokenIdentifier;
|
import org.apache.hadoop.security.token.TokenIdentifier;
|
||||||
@ -502,13 +501,12 @@ public Writable call(org.apache.hadoop.ipc.RPC.Server server,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Invoke the protocol method
|
// Invoke the protocol method
|
||||||
long startTime = Time.now();
|
long startTime = Time.now();
|
||||||
int qTime = (int) (startTime-receivedTime);
|
int qTime = (int) (startTime-receivedTime);
|
||||||
Exception exception = null;
|
Exception exception = null;
|
||||||
try {
|
try {
|
||||||
Method method =
|
Method method =
|
||||||
protocolImpl.protocolClass.getMethod(call.getMethodName(),
|
protocolImpl.protocolClass.getMethod(call.getMethodName(),
|
||||||
call.getParameterClasses());
|
call.getParameterClasses());
|
||||||
@ -539,27 +537,20 @@ public Writable call(org.apache.hadoop.ipc.RPC.Server server,
|
|||||||
exception = ioe;
|
exception = ioe;
|
||||||
throw ioe;
|
throw ioe;
|
||||||
} finally {
|
} finally {
|
||||||
int processingTime = (int) (Time.now() - startTime);
|
int processingTime = (int) (Time.now() - startTime);
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
String msg = "Served: " + call.getMethodName() +
|
String msg = "Served: " + call.getMethodName() +
|
||||||
" queueTime= " + qTime +
|
" queueTime= " + qTime + " procesingTime= " + processingTime;
|
||||||
" procesingTime= " + processingTime;
|
if (exception != null) {
|
||||||
if (exception != null) {
|
msg += " exception= " + exception.getClass().getSimpleName();
|
||||||
msg += " exception= " + exception.getClass().getSimpleName();
|
}
|
||||||
}
|
LOG.debug(msg);
|
||||||
LOG.debug(msg);
|
|
||||||
}
|
|
||||||
String detailedMetricsName = (exception == null) ?
|
|
||||||
call.getMethodName() :
|
|
||||||
exception.getClass().getSimpleName();
|
|
||||||
server.rpcMetrics.addRpcQueueTime(qTime);
|
|
||||||
server.rpcMetrics.addRpcProcessingTime(processingTime);
|
|
||||||
server.rpcDetailedMetrics.addProcessingTime(detailedMetricsName,
|
|
||||||
processingTime);
|
|
||||||
if (server.isLogSlowRPC()) {
|
|
||||||
server.logSlowRpcCalls(call.getMethodName(), processingTime);
|
|
||||||
}
|
}
|
||||||
}
|
String detailedMetricsName = (exception == null) ?
|
||||||
|
call.getMethodName() :
|
||||||
|
exception.getClass().getSimpleName();
|
||||||
|
server.updateMetrics(detailedMetricsName, qTime, processingTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,17 +27,37 @@
|
|||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.fs.CommonConfigurationKeys;
|
||||||
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class TestCallQueueManager {
|
public class TestCallQueueManager {
|
||||||
private CallQueueManager<FakeCall> manager;
|
private CallQueueManager<FakeCall> manager;
|
||||||
|
private Configuration conf = new Configuration();
|
||||||
|
|
||||||
public class FakeCall {
|
public class FakeCall implements Schedulable {
|
||||||
public final int tag; // Can be used for unique identification
|
public final int tag; // Can be used for unique identification
|
||||||
|
private int priorityLevel;
|
||||||
|
UserGroupInformation fakeUgi = UserGroupInformation.createRemoteUser
|
||||||
|
("fakeUser");
|
||||||
public FakeCall(int tag) {
|
public FakeCall(int tag) {
|
||||||
this.tag = tag;
|
this.tag = tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserGroupInformation getUserGroupInformation() {
|
||||||
|
return fakeUgi;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriorityLevel() {
|
||||||
|
return priorityLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriorityLevel(int level) {
|
||||||
|
this.priorityLevel = level;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,7 +82,9 @@ public void run() {
|
|||||||
try {
|
try {
|
||||||
// Fill up to max (which is infinite if maxCalls < 0)
|
// Fill up to max (which is infinite if maxCalls < 0)
|
||||||
while (isRunning && (callsAdded < maxCalls || maxCalls < 0)) {
|
while (isRunning && (callsAdded < maxCalls || maxCalls < 0)) {
|
||||||
cq.put(new FakeCall(this.tag));
|
FakeCall call = new FakeCall(this.tag);
|
||||||
|
call.setPriorityLevel(cq.getPriorityLevel(call));
|
||||||
|
cq.put(call);
|
||||||
callsAdded++;
|
callsAdded++;
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
@ -135,7 +157,7 @@ public void assertCanPut(CallQueueManager<FakeCall> cq, int numberOfPuts,
|
|||||||
t.start();
|
t.start();
|
||||||
t.join(100);
|
t.join(100);
|
||||||
|
|
||||||
assertEquals(putter.callsAdded, numberOfPuts);
|
assertEquals(numberOfPuts, putter.callsAdded);
|
||||||
t.interrupt();
|
t.interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,23 +165,90 @@ public void assertCanPut(CallQueueManager<FakeCall> cq, int numberOfPuts,
|
|||||||
private static final Class<? extends BlockingQueue<FakeCall>> queueClass
|
private static final Class<? extends BlockingQueue<FakeCall>> queueClass
|
||||||
= CallQueueManager.convertQueueClass(LinkedBlockingQueue.class, FakeCall.class);
|
= CallQueueManager.convertQueueClass(LinkedBlockingQueue.class, FakeCall.class);
|
||||||
|
|
||||||
|
private static final Class<? extends RpcScheduler> schedulerClass
|
||||||
|
= CallQueueManager.convertSchedulerClass(DefaultRpcScheduler.class);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCallQueueCapacity() throws InterruptedException {
|
public void testCallQueueCapacity() throws InterruptedException {
|
||||||
manager = new CallQueueManager<FakeCall>(queueClass, false, 10, "", null);
|
manager = new CallQueueManager<FakeCall>(queueClass, schedulerClass, false,
|
||||||
|
10, "", conf);
|
||||||
|
|
||||||
assertCanPut(manager, 10, 20); // Will stop at 10 due to capacity
|
assertCanPut(manager, 10, 20); // Will stop at 10 due to capacity
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEmptyConsume() throws InterruptedException {
|
public void testEmptyConsume() throws InterruptedException {
|
||||||
manager = new CallQueueManager<FakeCall>(queueClass, false, 10, "", null);
|
manager = new CallQueueManager<FakeCall>(queueClass, schedulerClass, false,
|
||||||
|
10, "", conf);
|
||||||
|
|
||||||
assertCanTake(manager, 0, 1); // Fails since it's empty
|
assertCanTake(manager, 0, 1); // Fails since it's empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Class<? extends BlockingQueue<FakeCall>> getQueueClass(
|
||||||
|
String prefix, Configuration conf) {
|
||||||
|
String name = prefix + "." + CommonConfigurationKeys.IPC_CALLQUEUE_IMPL_KEY;
|
||||||
|
Class<?> queueClass = conf.getClass(name, LinkedBlockingQueue.class);
|
||||||
|
return CallQueueManager.convertQueueClass(queueClass, FakeCall.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFcqBackwardCompatibility() throws InterruptedException {
|
||||||
|
// Test BackwardCompatibility to ensure existing FCQ deployment still
|
||||||
|
// work without explicitly specifying DecayRpcScheduler
|
||||||
|
Configuration conf = new Configuration();
|
||||||
|
final String ns = CommonConfigurationKeys.IPC_NAMESPACE + ".0";
|
||||||
|
|
||||||
|
final String queueClassName = "org.apache.hadoop.ipc.FairCallQueue";
|
||||||
|
conf.setStrings(ns + "." + CommonConfigurationKeys.IPC_CALLQUEUE_IMPL_KEY,
|
||||||
|
queueClassName);
|
||||||
|
|
||||||
|
// Specify only Fair Call Queue without a scheduler
|
||||||
|
// Ensure the DecayScheduler will be added to avoid breaking.
|
||||||
|
Class<? extends RpcScheduler> scheduler = Server.getSchedulerClass(ns,
|
||||||
|
conf);
|
||||||
|
assertTrue(scheduler.getCanonicalName().
|
||||||
|
equals("org.apache.hadoop.ipc.DecayRpcScheduler"));
|
||||||
|
|
||||||
|
Class<? extends BlockingQueue<FakeCall>> queue =
|
||||||
|
(Class<? extends BlockingQueue<FakeCall>>) getQueueClass(ns, conf);
|
||||||
|
assertTrue(queue.getCanonicalName().equals(queueClassName));
|
||||||
|
|
||||||
|
manager = new CallQueueManager<FakeCall>(queue, scheduler, false,
|
||||||
|
2, "", conf);
|
||||||
|
|
||||||
|
// Default FCQ has 4 levels and the max capacity is 2 x 4
|
||||||
|
assertCanPut(manager, 3, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSchedulerWithoutFCQ() throws InterruptedException {
|
||||||
|
Configuration conf = new Configuration();
|
||||||
|
// Test DecayedRpcScheduler without FCQ
|
||||||
|
// Ensure the default LinkedBlockingQueue can work with DecayedRpcScheduler
|
||||||
|
final String ns = CommonConfigurationKeys.IPC_NAMESPACE + ".0";
|
||||||
|
final String schedulerClassName = "org.apache.hadoop.ipc.DecayRpcScheduler";
|
||||||
|
conf.setStrings(ns + "." + CommonConfigurationKeys.IPC_SCHEDULER_IMPL_KEY,
|
||||||
|
schedulerClassName);
|
||||||
|
|
||||||
|
Class<? extends BlockingQueue<FakeCall>> queue =
|
||||||
|
(Class<? extends BlockingQueue<FakeCall>>) getQueueClass(ns, conf);
|
||||||
|
assertTrue(queue.getCanonicalName().equals("java.util.concurrent." +
|
||||||
|
"LinkedBlockingQueue"));
|
||||||
|
|
||||||
|
manager = new CallQueueManager<FakeCall>(queue,
|
||||||
|
Server.getSchedulerClass(ns, conf), false,
|
||||||
|
3, "", conf);
|
||||||
|
|
||||||
|
// LinkedBlockingQueue with a capacity of 3 can put 3 calls
|
||||||
|
assertCanPut(manager, 3, 3);
|
||||||
|
// LinkedBlockingQueue with a capacity of 3 can't put 1 more call
|
||||||
|
assertCanPut(manager, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
@Test(timeout=60000)
|
@Test(timeout=60000)
|
||||||
public void testSwapUnderContention() throws InterruptedException {
|
public void testSwapUnderContention() throws InterruptedException {
|
||||||
manager = new CallQueueManager<FakeCall>(queueClass, false, 5000, "", null);
|
manager = new CallQueueManager<FakeCall>(queueClass, schedulerClass, false,
|
||||||
|
5000, "", conf);
|
||||||
|
|
||||||
ArrayList<Putter> producers = new ArrayList<Putter>();
|
ArrayList<Putter> producers = new ArrayList<Putter>();
|
||||||
ArrayList<Taker> consumers = new ArrayList<Taker>();
|
ArrayList<Taker> consumers = new ArrayList<Taker>();
|
||||||
@ -188,7 +277,7 @@ public void testSwapUnderContention() throws InterruptedException {
|
|||||||
Thread.sleep(500);
|
Thread.sleep(500);
|
||||||
|
|
||||||
for (int i=0; i < 5; i++) {
|
for (int i=0; i < 5; i++) {
|
||||||
manager.swapQueue(queueClass, 5000, "", null);
|
manager.swapQueue(schedulerClass, queueClass, 5000, "", conf);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop the producers
|
// Stop the producers
|
||||||
@ -223,24 +312,50 @@ public void testSwapUnderContention() throws InterruptedException {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class ExceptionFakeCall {
|
public static class ExceptionFakeCall {
|
||||||
|
|
||||||
public ExceptionFakeCall() {
|
public ExceptionFakeCall() {
|
||||||
throw new IllegalArgumentException("Exception caused by constructor.!!");
|
throw new IllegalArgumentException("Exception caused by call queue " +
|
||||||
|
"constructor.!!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Class<? extends BlockingQueue<ExceptionFakeCall>> exceptionQueueClass = CallQueueManager
|
public static class ExceptionFakeScheduler {
|
||||||
.convertQueueClass(ExceptionFakeCall.class, ExceptionFakeCall.class);
|
public ExceptionFakeScheduler() {
|
||||||
|
throw new IllegalArgumentException("Exception caused by " +
|
||||||
|
"scheduler constructor.!!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Class<? extends RpcScheduler>
|
||||||
|
exceptionSchedulerClass = CallQueueManager.convertSchedulerClass(
|
||||||
|
ExceptionFakeScheduler.class);
|
||||||
|
|
||||||
|
private static final Class<? extends BlockingQueue<ExceptionFakeCall>>
|
||||||
|
exceptionQueueClass = CallQueueManager.convertQueueClass(
|
||||||
|
ExceptionFakeCall.class, ExceptionFakeCall.class);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInvocationException() throws InterruptedException {
|
public void testCallQueueConstructorException() throws InterruptedException {
|
||||||
try {
|
try {
|
||||||
new CallQueueManager<ExceptionFakeCall>(exceptionQueueClass, false, 10,
|
new CallQueueManager<ExceptionFakeCall>(exceptionQueueClass,
|
||||||
"", null);
|
schedulerClass, false, 10, "", new Configuration());
|
||||||
fail();
|
fail();
|
||||||
} catch (RuntimeException re) {
|
} catch (RuntimeException re) {
|
||||||
assertTrue(re.getCause() instanceof IllegalArgumentException);
|
assertTrue(re.getCause() instanceof IllegalArgumentException);
|
||||||
assertEquals("Exception caused by constructor.!!", re.getCause()
|
assertEquals("Exception caused by call queue constructor.!!", re
|
||||||
|
.getCause()
|
||||||
|
.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSchedulerConstructorException() throws InterruptedException {
|
||||||
|
try {
|
||||||
|
new CallQueueManager<FakeCall>(queueClass, exceptionSchedulerClass,
|
||||||
|
false, 10, "", new Configuration());
|
||||||
|
fail();
|
||||||
|
} catch (RuntimeException re) {
|
||||||
|
assertTrue(re.getCause() instanceof IllegalArgumentException);
|
||||||
|
assertEquals("Exception caused by scheduler constructor.!!", re.getCause()
|
||||||
.getMessage());
|
.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,11 @@
|
|||||||
|
|
||||||
package org.apache.hadoop.ipc;
|
package org.apache.hadoop.ipc;
|
||||||
|
|
||||||
|
import static java.lang.Thread.sleep;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@ -31,8 +30,6 @@
|
|||||||
import org.apache.hadoop.security.UserGroupInformation;
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
|
||||||
import org.apache.hadoop.fs.CommonConfigurationKeys;
|
|
||||||
|
|
||||||
public class TestDecayRpcScheduler {
|
public class TestDecayRpcScheduler {
|
||||||
private Schedulable mockCall(String id) {
|
private Schedulable mockCall(String id) {
|
||||||
Schedulable mockCall = mock(Schedulable.class);
|
Schedulable mockCall = mock(Schedulable.class);
|
||||||
@ -57,30 +54,32 @@ public void testZeroScheduler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
public void testParsePeriod() {
|
public void testParsePeriod() {
|
||||||
// By default
|
// By default
|
||||||
scheduler = new DecayRpcScheduler(1, "", new Configuration());
|
scheduler = new DecayRpcScheduler(1, "", new Configuration());
|
||||||
assertEquals(DecayRpcScheduler.IPC_CALLQUEUE_DECAYSCHEDULER_PERIOD_DEFAULT,
|
assertEquals(DecayRpcScheduler.IPC_SCHEDULER_DECAYSCHEDULER_PERIOD_DEFAULT,
|
||||||
scheduler.getDecayPeriodMillis());
|
scheduler.getDecayPeriodMillis());
|
||||||
|
|
||||||
// Custom
|
// Custom
|
||||||
Configuration conf = new Configuration();
|
Configuration conf = new Configuration();
|
||||||
conf.setLong("ns." + DecayRpcScheduler.IPC_CALLQUEUE_DECAYSCHEDULER_PERIOD_KEY,
|
conf.setLong("ns." + DecayRpcScheduler.IPC_FCQ_DECAYSCHEDULER_PERIOD_KEY,
|
||||||
1058);
|
1058);
|
||||||
scheduler = new DecayRpcScheduler(1, "ns", conf);
|
scheduler = new DecayRpcScheduler(1, "ns", conf);
|
||||||
assertEquals(1058L, scheduler.getDecayPeriodMillis());
|
assertEquals(1058L, scheduler.getDecayPeriodMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
public void testParseFactor() {
|
public void testParseFactor() {
|
||||||
// Default
|
// Default
|
||||||
scheduler = new DecayRpcScheduler(1, "", new Configuration());
|
scheduler = new DecayRpcScheduler(1, "", new Configuration());
|
||||||
assertEquals(DecayRpcScheduler.IPC_CALLQUEUE_DECAYSCHEDULER_FACTOR_DEFAULT,
|
assertEquals(DecayRpcScheduler.IPC_SCHEDULER_DECAYSCHEDULER_FACTOR_DEFAULT,
|
||||||
scheduler.getDecayFactor(), 0.00001);
|
scheduler.getDecayFactor(), 0.00001);
|
||||||
|
|
||||||
// Custom
|
// Custom
|
||||||
Configuration conf = new Configuration();
|
Configuration conf = new Configuration();
|
||||||
conf.set("prefix." + DecayRpcScheduler.IPC_CALLQUEUE_DECAYSCHEDULER_FACTOR_KEY,
|
conf.set("prefix." + DecayRpcScheduler.IPC_FCQ_DECAYSCHEDULER_FACTOR_KEY,
|
||||||
"0.125");
|
"0.125");
|
||||||
scheduler = new DecayRpcScheduler(1, "prefix", conf);
|
scheduler = new DecayRpcScheduler(1, "prefix", conf);
|
||||||
assertEquals(0.125, scheduler.getDecayFactor(), 0.00001);
|
assertEquals(0.125, scheduler.getDecayFactor(), 0.00001);
|
||||||
@ -94,6 +93,7 @@ public void assertEqualDecimalArrays(double[] a, double[] b) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
public void testParseThresholds() {
|
public void testParseThresholds() {
|
||||||
// Defaults vary by number of queues
|
// Defaults vary by number of queues
|
||||||
Configuration conf = new Configuration();
|
Configuration conf = new Configuration();
|
||||||
@ -111,16 +111,17 @@ public void testParseThresholds() {
|
|||||||
|
|
||||||
// Custom
|
// Custom
|
||||||
conf = new Configuration();
|
conf = new Configuration();
|
||||||
conf.set("ns." + DecayRpcScheduler.IPC_CALLQUEUE_DECAYSCHEDULER_THRESHOLDS_KEY,
|
conf.set("ns." + DecayRpcScheduler.IPC_FCQ_DECAYSCHEDULER_THRESHOLDS_KEY,
|
||||||
"1, 10, 20, 50, 85");
|
"1, 10, 20, 50, 85");
|
||||||
scheduler = new DecayRpcScheduler(6, "ns", conf);
|
scheduler = new DecayRpcScheduler(6, "ns", conf);
|
||||||
assertEqualDecimalArrays(new double[]{0.01, 0.1, 0.2, 0.5, 0.85}, scheduler.getThresholds());
|
assertEqualDecimalArrays(new double[]{0.01, 0.1, 0.2, 0.5, 0.85}, scheduler.getThresholds());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
public void testAccumulate() {
|
public void testAccumulate() {
|
||||||
Configuration conf = new Configuration();
|
Configuration conf = new Configuration();
|
||||||
conf.set("ns." + DecayRpcScheduler.IPC_CALLQUEUE_DECAYSCHEDULER_PERIOD_KEY, "99999999"); // Never flush
|
conf.set("ns." + DecayRpcScheduler.IPC_FCQ_DECAYSCHEDULER_PERIOD_KEY, "99999999"); // Never flush
|
||||||
scheduler = new DecayRpcScheduler(1, "ns", conf);
|
scheduler = new DecayRpcScheduler(1, "ns", conf);
|
||||||
|
|
||||||
assertEquals(0, scheduler.getCallCountSnapshot().size()); // empty first
|
assertEquals(0, scheduler.getCallCountSnapshot().size()); // empty first
|
||||||
@ -138,10 +139,11 @@ public void testAccumulate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDecay() {
|
@SuppressWarnings("deprecation")
|
||||||
|
public void testDecay() throws Exception {
|
||||||
Configuration conf = new Configuration();
|
Configuration conf = new Configuration();
|
||||||
conf.set("ns." + DecayRpcScheduler.IPC_CALLQUEUE_DECAYSCHEDULER_PERIOD_KEY, "999999999"); // Never
|
conf.set("ns." + DecayRpcScheduler.IPC_FCQ_DECAYSCHEDULER_PERIOD_KEY, "999999999"); // Never
|
||||||
conf.set("ns." + DecayRpcScheduler.IPC_CALLQUEUE_DECAYSCHEDULER_FACTOR_KEY, "0.5");
|
conf.set("ns." + DecayRpcScheduler.IPC_FCQ_DECAYSCHEDULER_FACTOR_KEY, "0.5");
|
||||||
scheduler = new DecayRpcScheduler(1, "ns", conf);
|
scheduler = new DecayRpcScheduler(1, "ns", conf);
|
||||||
|
|
||||||
assertEquals(0, scheduler.getTotalCallSnapshot());
|
assertEquals(0, scheduler.getTotalCallSnapshot());
|
||||||
@ -150,6 +152,8 @@ public void testDecay() {
|
|||||||
scheduler.getPriorityLevel(mockCall("A"));
|
scheduler.getPriorityLevel(mockCall("A"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sleep(1000);
|
||||||
|
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
scheduler.getPriorityLevel(mockCall("B"));
|
scheduler.getPriorityLevel(mockCall("B"));
|
||||||
}
|
}
|
||||||
@ -184,10 +188,11 @@ public void testDecay() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
public void testPriority() {
|
public void testPriority() {
|
||||||
Configuration conf = new Configuration();
|
Configuration conf = new Configuration();
|
||||||
conf.set("ns." + DecayRpcScheduler.IPC_CALLQUEUE_DECAYSCHEDULER_PERIOD_KEY, "99999999"); // Never flush
|
conf.set("ns." + DecayRpcScheduler.IPC_FCQ_DECAYSCHEDULER_PERIOD_KEY, "99999999"); // Never flush
|
||||||
conf.set("ns." + DecayRpcScheduler.IPC_CALLQUEUE_DECAYSCHEDULER_THRESHOLDS_KEY,
|
conf.set("ns." + DecayRpcScheduler.IPC_FCQ_DECAYSCHEDULER_THRESHOLDS_KEY,
|
||||||
"25, 50, 75");
|
"25, 50, 75");
|
||||||
scheduler = new DecayRpcScheduler(4, "ns", conf);
|
scheduler = new DecayRpcScheduler(4, "ns", conf);
|
||||||
|
|
||||||
@ -204,10 +209,11 @@ public void testPriority() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout=2000)
|
@Test(timeout=2000)
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
public void testPeriodic() throws InterruptedException {
|
public void testPeriodic() throws InterruptedException {
|
||||||
Configuration conf = new Configuration();
|
Configuration conf = new Configuration();
|
||||||
conf.set("ns." + DecayRpcScheduler.IPC_CALLQUEUE_DECAYSCHEDULER_PERIOD_KEY, "10");
|
conf.set("ns." + DecayRpcScheduler.IPC_FCQ_DECAYSCHEDULER_PERIOD_KEY, "10");
|
||||||
conf.set("ns." + DecayRpcScheduler.IPC_CALLQUEUE_DECAYSCHEDULER_FACTOR_KEY, "0.5");
|
conf.set("ns." + DecayRpcScheduler.IPC_FCQ_DECAYSCHEDULER_FACTOR_KEY, "0.5");
|
||||||
scheduler = new DecayRpcScheduler(1, "ns", conf);
|
scheduler = new DecayRpcScheduler(1, "ns", conf);
|
||||||
|
|
||||||
assertEquals(10, scheduler.getDecayPeriodMillis());
|
assertEquals(10, scheduler.getDecayPeriodMillis());
|
||||||
@ -219,7 +225,7 @@ public void testPeriodic() throws InterruptedException {
|
|||||||
|
|
||||||
// It should eventually decay to zero
|
// It should eventually decay to zero
|
||||||
while (scheduler.getTotalCallSnapshot() > 0) {
|
while (scheduler.getTotalCallSnapshot() > 0) {
|
||||||
Thread.sleep(10);
|
sleep(10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -37,21 +37,24 @@
|
|||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.mockito.Matchers;
|
import org.mockito.Matchers;
|
||||||
|
|
||||||
import static org.apache.hadoop.ipc.FairCallQueue.IPC_CALLQUEUE_PRIORITY_LEVELS_KEY;
|
|
||||||
|
|
||||||
public class TestFairCallQueue extends TestCase {
|
public class TestFairCallQueue extends TestCase {
|
||||||
private FairCallQueue<Schedulable> fcq;
|
private FairCallQueue<Schedulable> fcq;
|
||||||
|
|
||||||
private Schedulable mockCall(String id) {
|
private Schedulable mockCall(String id, int priority) {
|
||||||
Schedulable mockCall = mock(Schedulable.class);
|
Schedulable mockCall = mock(Schedulable.class);
|
||||||
UserGroupInformation ugi = mock(UserGroupInformation.class);
|
UserGroupInformation ugi = mock(UserGroupInformation.class);
|
||||||
|
|
||||||
when(ugi.getUserName()).thenReturn(id);
|
when(ugi.getUserName()).thenReturn(id);
|
||||||
when(mockCall.getUserGroupInformation()).thenReturn(ugi);
|
when(mockCall.getUserGroupInformation()).thenReturn(ugi);
|
||||||
|
when(mockCall.getPriorityLevel()).thenReturn(priority);
|
||||||
|
|
||||||
return mockCall;
|
return mockCall;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Schedulable mockCall(String id) {
|
||||||
|
return mockCall(id, 0);
|
||||||
|
}
|
||||||
|
|
||||||
// A scheduler which always schedules into priority zero
|
// A scheduler which always schedules into priority zero
|
||||||
private RpcScheduler alwaysZeroScheduler;
|
private RpcScheduler alwaysZeroScheduler;
|
||||||
{
|
{
|
||||||
@ -60,11 +63,12 @@ private Schedulable mockCall(String id) {
|
|||||||
alwaysZeroScheduler = sched;
|
alwaysZeroScheduler = sched;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
Configuration conf = new Configuration();
|
Configuration conf = new Configuration();
|
||||||
conf.setInt("ns." + IPC_CALLQUEUE_PRIORITY_LEVELS_KEY, 2);
|
conf.setInt("ns." + FairCallQueue.IPC_CALLQUEUE_PRIORITY_LEVELS_KEY, 2);
|
||||||
|
|
||||||
fcq = new FairCallQueue<Schedulable>(5, "ns", conf);
|
fcq = new FairCallQueue<Schedulable>(2, 5, "ns", conf);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -85,7 +89,6 @@ public void testPollReturnsTopCallWhenNotEmpty() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testOfferSucceeds() {
|
public void testOfferSucceeds() {
|
||||||
fcq.setScheduler(alwaysZeroScheduler);
|
|
||||||
|
|
||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; i++) {
|
||||||
// We can fit 10 calls
|
// We can fit 10 calls
|
||||||
@ -96,7 +99,6 @@ public void testOfferSucceeds() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testOfferFailsWhenFull() {
|
public void testOfferFailsWhenFull() {
|
||||||
fcq.setScheduler(alwaysZeroScheduler);
|
|
||||||
for (int i = 0; i < 5; i++) { assertTrue(fcq.offer(mockCall("c"))); }
|
for (int i = 0; i < 5; i++) { assertTrue(fcq.offer(mockCall("c"))); }
|
||||||
|
|
||||||
assertFalse(fcq.offer(mockCall("c"))); // It's full
|
assertFalse(fcq.offer(mockCall("c"))); // It's full
|
||||||
@ -107,11 +109,10 @@ public void testOfferFailsWhenFull() {
|
|||||||
public void testOfferSucceedsWhenScheduledLowPriority() {
|
public void testOfferSucceedsWhenScheduledLowPriority() {
|
||||||
// Scheduler will schedule into queue 0 x 5, then queue 1
|
// Scheduler will schedule into queue 0 x 5, then queue 1
|
||||||
RpcScheduler sched = mock(RpcScheduler.class);
|
RpcScheduler sched = mock(RpcScheduler.class);
|
||||||
when(sched.getPriorityLevel(Matchers.<Schedulable>any())).thenReturn(0, 0, 0, 0, 0, 1, 0);
|
int mockedPriorities[] = {0, 0, 0, 0, 0, 1, 0};
|
||||||
fcq.setScheduler(sched);
|
for (int i = 0; i < 5; i++) { assertTrue(fcq.offer(mockCall("c", mockedPriorities[i]))); }
|
||||||
for (int i = 0; i < 5; i++) { assertTrue(fcq.offer(mockCall("c"))); }
|
|
||||||
|
|
||||||
assertTrue(fcq.offer(mockCall("c")));
|
assertTrue(fcq.offer(mockCall("c", mockedPriorities[5])));
|
||||||
|
|
||||||
assertEquals(6, fcq.size());
|
assertEquals(6, fcq.size());
|
||||||
}
|
}
|
||||||
@ -121,7 +122,7 @@ public void testPeekNullWhenEmpty() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testPeekNonDestructive() {
|
public void testPeekNonDestructive() {
|
||||||
Schedulable call = mockCall("c");
|
Schedulable call = mockCall("c", 0);
|
||||||
assertTrue(fcq.offer(call));
|
assertTrue(fcq.offer(call));
|
||||||
|
|
||||||
assertEquals(call, fcq.peek());
|
assertEquals(call, fcq.peek());
|
||||||
@ -130,8 +131,8 @@ public void testPeekNonDestructive() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testPeekPointsAtHead() {
|
public void testPeekPointsAtHead() {
|
||||||
Schedulable call = mockCall("c");
|
Schedulable call = mockCall("c", 0);
|
||||||
Schedulable next = mockCall("b");
|
Schedulable next = mockCall("b", 0);
|
||||||
fcq.offer(call);
|
fcq.offer(call);
|
||||||
fcq.offer(next);
|
fcq.offer(next);
|
||||||
|
|
||||||
@ -139,15 +140,11 @@ public void testPeekPointsAtHead() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testPollTimeout() throws InterruptedException {
|
public void testPollTimeout() throws InterruptedException {
|
||||||
fcq.setScheduler(alwaysZeroScheduler);
|
|
||||||
|
|
||||||
assertNull(fcq.poll(10, TimeUnit.MILLISECONDS));
|
assertNull(fcq.poll(10, TimeUnit.MILLISECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testPollSuccess() throws InterruptedException {
|
public void testPollSuccess() throws InterruptedException {
|
||||||
fcq.setScheduler(alwaysZeroScheduler);
|
Schedulable call = mockCall("c", 0);
|
||||||
|
|
||||||
Schedulable call = mockCall("c");
|
|
||||||
assertTrue(fcq.offer(call));
|
assertTrue(fcq.offer(call));
|
||||||
|
|
||||||
assertEquals(call, fcq.poll(10, TimeUnit.MILLISECONDS));
|
assertEquals(call, fcq.poll(10, TimeUnit.MILLISECONDS));
|
||||||
@ -156,7 +153,6 @@ public void testPollSuccess() throws InterruptedException {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testOfferTimeout() throws InterruptedException {
|
public void testOfferTimeout() throws InterruptedException {
|
||||||
fcq.setScheduler(alwaysZeroScheduler);
|
|
||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; i++) {
|
||||||
assertTrue(fcq.offer(mockCall("c"), 10, TimeUnit.MILLISECONDS));
|
assertTrue(fcq.offer(mockCall("c"), 10, TimeUnit.MILLISECONDS));
|
||||||
}
|
}
|
||||||
@ -166,13 +162,11 @@ public void testOfferTimeout() throws InterruptedException {
|
|||||||
assertEquals(5, fcq.size());
|
assertEquals(5, fcq.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
public void testDrainTo() {
|
public void testDrainTo() {
|
||||||
Configuration conf = new Configuration();
|
Configuration conf = new Configuration();
|
||||||
conf.setInt("ns." + IPC_CALLQUEUE_PRIORITY_LEVELS_KEY, 2);
|
conf.setInt("ns." + FairCallQueue.IPC_CALLQUEUE_PRIORITY_LEVELS_KEY, 2);
|
||||||
FairCallQueue<Schedulable> fcq2 = new FairCallQueue<Schedulable>(10, "ns", conf);
|
FairCallQueue<Schedulable> fcq2 = new FairCallQueue<Schedulable>(2, 10, "ns", conf);
|
||||||
|
|
||||||
fcq.setScheduler(alwaysZeroScheduler);
|
|
||||||
fcq2.setScheduler(alwaysZeroScheduler);
|
|
||||||
|
|
||||||
// Start with 3 in fcq, to be drained
|
// Start with 3 in fcq, to be drained
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
@ -185,13 +179,11 @@ public void testDrainTo() {
|
|||||||
assertEquals(3, fcq2.size());
|
assertEquals(3, fcq2.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
public void testDrainToWithLimit() {
|
public void testDrainToWithLimit() {
|
||||||
Configuration conf = new Configuration();
|
Configuration conf = new Configuration();
|
||||||
conf.setInt("ns." + IPC_CALLQUEUE_PRIORITY_LEVELS_KEY, 2);
|
conf.setInt("ns." + FairCallQueue.IPC_CALLQUEUE_PRIORITY_LEVELS_KEY, 2);
|
||||||
FairCallQueue<Schedulable> fcq2 = new FairCallQueue<Schedulable>(10, "ns", conf);
|
FairCallQueue<Schedulable> fcq2 = new FairCallQueue<Schedulable>(2, 10, "ns", conf);
|
||||||
|
|
||||||
fcq.setScheduler(alwaysZeroScheduler);
|
|
||||||
fcq2.setScheduler(alwaysZeroScheduler);
|
|
||||||
|
|
||||||
// Start with 3 in fcq, to be drained
|
// Start with 3 in fcq, to be drained
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
@ -209,27 +201,23 @@ public void testInitialRemainingCapacity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testFirstQueueFullRemainingCapacity() {
|
public void testFirstQueueFullRemainingCapacity() {
|
||||||
fcq.setScheduler(alwaysZeroScheduler);
|
|
||||||
while (fcq.offer(mockCall("c"))) ; // Queue 0 will fill up first, then queue 1
|
while (fcq.offer(mockCall("c"))) ; // Queue 0 will fill up first, then queue 1
|
||||||
|
|
||||||
assertEquals(5, fcq.remainingCapacity());
|
assertEquals(5, fcq.remainingCapacity());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testAllQueuesFullRemainingCapacity() {
|
public void testAllQueuesFullRemainingCapacity() {
|
||||||
RpcScheduler sched = mock(RpcScheduler.class);
|
int[] mockedPriorities = {0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0};
|
||||||
when(sched.getPriorityLevel(Matchers.<Schedulable>any())).thenReturn(0, 0, 0, 0, 0, 1, 1, 1, 1, 1);
|
int i = 0;
|
||||||
fcq.setScheduler(sched);
|
while (fcq.offer(mockCall("c", mockedPriorities[i++]))) ;
|
||||||
while (fcq.offer(mockCall("c"))) ;
|
|
||||||
|
|
||||||
assertEquals(0, fcq.remainingCapacity());
|
assertEquals(0, fcq.remainingCapacity());
|
||||||
assertEquals(10, fcq.size());
|
assertEquals(10, fcq.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testQueuesPartialFilledRemainingCapacity() {
|
public void testQueuesPartialFilledRemainingCapacity() {
|
||||||
RpcScheduler sched = mock(RpcScheduler.class);
|
int[] mockedPriorities = {0, 1, 0, 1, 0};
|
||||||
when(sched.getPriorityLevel(Matchers.<Schedulable>any())).thenReturn(0, 1, 0, 1, 0);
|
for (int i = 0; i < 5; i++) { fcq.offer(mockCall("c", mockedPriorities[i])); }
|
||||||
fcq.setScheduler(sched);
|
|
||||||
for (int i = 0; i < 5; i++) { fcq.offer(mockCall("c")); }
|
|
||||||
|
|
||||||
assertEquals(5, fcq.remainingCapacity());
|
assertEquals(5, fcq.remainingCapacity());
|
||||||
assertEquals(5, fcq.size());
|
assertEquals(5, fcq.size());
|
||||||
@ -351,16 +339,12 @@ public void assertCanPut(BlockingQueue<Schedulable> cq, int numberOfPuts,
|
|||||||
|
|
||||||
// Make sure put will overflow into lower queues when the top is full
|
// Make sure put will overflow into lower queues when the top is full
|
||||||
public void testPutOverflows() throws InterruptedException {
|
public void testPutOverflows() throws InterruptedException {
|
||||||
fcq.setScheduler(alwaysZeroScheduler);
|
|
||||||
|
|
||||||
// We can fit more than 5, even though the scheduler suggests the top queue
|
// We can fit more than 5, even though the scheduler suggests the top queue
|
||||||
assertCanPut(fcq, 8, 8);
|
assertCanPut(fcq, 8, 8);
|
||||||
assertEquals(8, fcq.size());
|
assertEquals(8, fcq.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testPutBlocksWhenAllFull() throws InterruptedException {
|
public void testPutBlocksWhenAllFull() throws InterruptedException {
|
||||||
fcq.setScheduler(alwaysZeroScheduler);
|
|
||||||
|
|
||||||
assertCanPut(fcq, 10, 10); // Fill up
|
assertCanPut(fcq, 10, 10); // Fill up
|
||||||
assertEquals(10, fcq.size());
|
assertEquals(10, fcq.size());
|
||||||
|
|
||||||
@ -369,12 +353,10 @@ public void testPutBlocksWhenAllFull() throws InterruptedException {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testTakeBlocksWhenEmpty() throws InterruptedException {
|
public void testTakeBlocksWhenEmpty() throws InterruptedException {
|
||||||
fcq.setScheduler(alwaysZeroScheduler);
|
|
||||||
assertCanTake(fcq, 0, 1);
|
assertCanTake(fcq, 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testTakeRemovesCall() throws InterruptedException {
|
public void testTakeRemovesCall() throws InterruptedException {
|
||||||
fcq.setScheduler(alwaysZeroScheduler);
|
|
||||||
Schedulable call = mockCall("c");
|
Schedulable call = mockCall("c");
|
||||||
fcq.offer(call);
|
fcq.offer(call);
|
||||||
|
|
||||||
@ -383,17 +365,14 @@ public void testTakeRemovesCall() throws InterruptedException {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testTakeTriesNextQueue() throws InterruptedException {
|
public void testTakeTriesNextQueue() throws InterruptedException {
|
||||||
// Make a FCQ filled with calls in q 1 but empty in q 0
|
|
||||||
RpcScheduler q1Scheduler = mock(RpcScheduler.class);
|
|
||||||
when(q1Scheduler.getPriorityLevel(Matchers.<Schedulable>any())).thenReturn(1);
|
|
||||||
fcq.setScheduler(q1Scheduler);
|
|
||||||
|
|
||||||
// A mux which only draws from q 0
|
// A mux which only draws from q 0
|
||||||
RpcMultiplexer q0mux = mock(RpcMultiplexer.class);
|
RpcMultiplexer q0mux = mock(RpcMultiplexer.class);
|
||||||
when(q0mux.getAndAdvanceCurrentIndex()).thenReturn(0);
|
when(q0mux.getAndAdvanceCurrentIndex()).thenReturn(0);
|
||||||
fcq.setMultiplexer(q0mux);
|
fcq.setMultiplexer(q0mux);
|
||||||
|
|
||||||
Schedulable call = mockCall("c");
|
// Make a FCQ filled with calls in q 1 but empty in q 0
|
||||||
|
Schedulable call = mockCall("c", 1);
|
||||||
fcq.put(call);
|
fcq.put(call);
|
||||||
|
|
||||||
// Take from q1 even though mux said q0, since q0 empty
|
// Take from q1 even though mux said q0, since q0 empty
|
||||||
|
@ -19,25 +19,15 @@
|
|||||||
package org.apache.hadoop.ipc;
|
package org.apache.hadoop.ipc;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Assume;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.After;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import org.apache.hadoop.classification.InterfaceAudience;
|
|
||||||
import org.apache.hadoop.security.UserGroupInformation;
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
import org.apache.hadoop.io.Writable;
|
|
||||||
|
|
||||||
import org.apache.hadoop.fs.CommonConfigurationKeys;
|
import org.apache.hadoop.fs.CommonConfigurationKeys;
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
@ -55,16 +45,20 @@ public UserGroupInformation getUserGroupInformation() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriorityLevel() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPluggableIdentityProvider() {
|
public void testPluggableIdentityProvider() {
|
||||||
Configuration conf = new Configuration();
|
Configuration conf = new Configuration();
|
||||||
conf.set(CommonConfigurationKeys.IPC_CALLQUEUE_IDENTITY_PROVIDER_KEY,
|
conf.set(CommonConfigurationKeys.IPC_IDENTITY_PROVIDER_KEY,
|
||||||
"org.apache.hadoop.ipc.UserIdentityProvider");
|
"org.apache.hadoop.ipc.UserIdentityProvider");
|
||||||
|
|
||||||
List<IdentityProvider> providers = conf.getInstances(
|
List<IdentityProvider> providers = conf.getInstances(
|
||||||
CommonConfigurationKeys.IPC_CALLQUEUE_IDENTITY_PROVIDER_KEY,
|
CommonConfigurationKeys.IPC_IDENTITY_PROVIDER_KEY,
|
||||||
IdentityProvider.class);
|
IdentityProvider.class);
|
||||||
|
|
||||||
assertTrue(providers.size() == 1);
|
assertTrue(providers.size() == 1);
|
||||||
|
@ -43,8 +43,10 @@
|
|||||||
import org.apache.hadoop.security.authorize.Service;
|
import org.apache.hadoop.security.authorize.Service;
|
||||||
import org.apache.hadoop.security.token.SecretManager;
|
import org.apache.hadoop.security.token.SecretManager;
|
||||||
import org.apache.hadoop.security.token.TokenIdentifier;
|
import org.apache.hadoop.security.token.TokenIdentifier;
|
||||||
|
import org.apache.hadoop.test.GenericTestUtils;
|
||||||
import org.apache.hadoop.test.MetricsAsserts;
|
import org.apache.hadoop.test.MetricsAsserts;
|
||||||
import org.apache.hadoop.test.MockitoUtil;
|
import org.apache.hadoop.test.MockitoUtil;
|
||||||
|
import org.apache.log4j.Level;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
@ -968,7 +970,7 @@ public void testRpcMetrics() throws Exception {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test RPC backoff.
|
* Test RPC backoff by queue full.
|
||||||
*/
|
*/
|
||||||
@Test (timeout=30000)
|
@Test (timeout=30000)
|
||||||
public void testClientBackOff() throws Exception {
|
public void testClientBackOff() throws Exception {
|
||||||
@ -981,7 +983,7 @@ public void testClientBackOff() throws Exception {
|
|||||||
final ExecutorService executorService =
|
final ExecutorService executorService =
|
||||||
Executors.newFixedThreadPool(numClients);
|
Executors.newFixedThreadPool(numClients);
|
||||||
conf.setInt(CommonConfigurationKeys.IPC_CLIENT_CONNECT_MAX_RETRIES_KEY, 0);
|
conf.setInt(CommonConfigurationKeys.IPC_CLIENT_CONNECT_MAX_RETRIES_KEY, 0);
|
||||||
conf.setBoolean(CommonConfigurationKeys.IPC_CALLQUEUE_NAMESPACE +
|
conf.setBoolean(CommonConfigurationKeys.IPC_NAMESPACE +
|
||||||
".0." + CommonConfigurationKeys.IPC_BACKOFF_ENABLE, true);
|
".0." + CommonConfigurationKeys.IPC_BACKOFF_ENABLE, true);
|
||||||
RPC.Builder builder = newServerBuilder(conf)
|
RPC.Builder builder = newServerBuilder(conf)
|
||||||
.setQueueSizePerHandler(1).setNumHandlers(1).setVerbose(true);
|
.setQueueSizePerHandler(1).setNumHandlers(1).setVerbose(true);
|
||||||
@ -1030,6 +1032,92 @@ public Void call() throws ServiceException, InterruptedException {
|
|||||||
assertTrue("RetriableException not received", succeeded);
|
assertTrue("RetriableException not received", succeeded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test RPC backoff by response time of each priority level.
|
||||||
|
*/
|
||||||
|
@Test (timeout=30000)
|
||||||
|
public void testClientBackOffByResponseTime() throws Exception {
|
||||||
|
Server server;
|
||||||
|
final TestRpcService proxy;
|
||||||
|
boolean succeeded = false;
|
||||||
|
final int numClients = 1;
|
||||||
|
final int queueSizePerHandler = 3;
|
||||||
|
|
||||||
|
GenericTestUtils.setLogLevel(DecayRpcScheduler.LOG, Level.DEBUG);
|
||||||
|
GenericTestUtils.setLogLevel(RPC.LOG, Level.DEBUG);
|
||||||
|
|
||||||
|
final List<Future<Void>> res = new ArrayList<Future<Void>>();
|
||||||
|
final ExecutorService executorService =
|
||||||
|
Executors.newFixedThreadPool(numClients);
|
||||||
|
conf.setInt(CommonConfigurationKeys.IPC_CLIENT_CONNECT_MAX_RETRIES_KEY, 0);
|
||||||
|
final String ns = CommonConfigurationKeys.IPC_NAMESPACE + ".0.";
|
||||||
|
conf.setBoolean(ns + CommonConfigurationKeys.IPC_BACKOFF_ENABLE, true);
|
||||||
|
conf.setStrings(ns + CommonConfigurationKeys.IPC_CALLQUEUE_IMPL_KEY,
|
||||||
|
"org.apache.hadoop.ipc.FairCallQueue");
|
||||||
|
conf.setStrings(ns + CommonConfigurationKeys.IPC_SCHEDULER_IMPL_KEY,
|
||||||
|
"org.apache.hadoop.ipc.DecayRpcScheduler");
|
||||||
|
conf.setInt(ns + CommonConfigurationKeys.IPC_SCHEDULER_PRIORITY_LEVELS_KEY,
|
||||||
|
2);
|
||||||
|
conf.setBoolean(ns +
|
||||||
|
DecayRpcScheduler.IPC_DECAYSCHEDULER_BACKOFF_RESPONSETIME_ENABLE_KEY,
|
||||||
|
true);
|
||||||
|
// set a small thresholds 2s and 4s for level 0 and level 1 for testing
|
||||||
|
conf.set(ns +
|
||||||
|
DecayRpcScheduler.IPC_DECAYSCHEDULER_BACKOFF_RESPONSETIME_THRESHOLDS_KEY
|
||||||
|
, "2s, 4s");
|
||||||
|
|
||||||
|
// Set max queue size to 3 so that 2 calls from the test won't trigger
|
||||||
|
// back off because the queue is full.
|
||||||
|
RPC.Builder builder = newServerBuilder(conf)
|
||||||
|
.setQueueSizePerHandler(queueSizePerHandler).setNumHandlers(1)
|
||||||
|
.setVerbose(true);
|
||||||
|
server = setupTestServer(builder);
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
CallQueueManager<Call> spy = spy((CallQueueManager<Call>) Whitebox
|
||||||
|
.getInternalState(server, "callQueue"));
|
||||||
|
Whitebox.setInternalState(server, "callQueue", spy);
|
||||||
|
|
||||||
|
Exception lastException = null;
|
||||||
|
proxy = getClient(addr, conf);
|
||||||
|
try {
|
||||||
|
// start a sleep RPC call that sleeps 3s.
|
||||||
|
for (int i = 0; i < numClients; i++) {
|
||||||
|
res.add(executorService.submit(
|
||||||
|
new Callable<Void>() {
|
||||||
|
@Override
|
||||||
|
public Void call() throws ServiceException, InterruptedException {
|
||||||
|
proxy.sleep(null, newSleepRequest(3000));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
verify(spy, timeout(500).times(i + 1)).offer(Mockito.<Call>anyObject());
|
||||||
|
}
|
||||||
|
// Start another sleep RPC call and verify the call is backed off due to
|
||||||
|
// avg response time(3s) exceeds threshold (2s).
|
||||||
|
try {
|
||||||
|
// wait for the 1st response time update
|
||||||
|
Thread.sleep(5500);
|
||||||
|
proxy.sleep(null, newSleepRequest(100));
|
||||||
|
} catch (ServiceException e) {
|
||||||
|
RemoteException re = (RemoteException) e.getCause();
|
||||||
|
IOException unwrapExeption = re.unwrapRemoteException();
|
||||||
|
if (unwrapExeption instanceof RetriableException) {
|
||||||
|
succeeded = true;
|
||||||
|
} else {
|
||||||
|
lastException = unwrapExeption;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
executorService.shutdown();
|
||||||
|
stop(server, proxy);
|
||||||
|
}
|
||||||
|
if (lastException != null) {
|
||||||
|
LOG.error("Last received non-RetriableException:", lastException);
|
||||||
|
}
|
||||||
|
assertTrue("RetriableException not received", succeeded);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test RPC timeout.
|
* Test RPC timeout.
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user