package org.apache.hadoop.corona; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.Collection; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import org.xml.sax.SAXException; /** * Reloads corona scheduling parameters periodically. Needs to be thread safe * * The following is a corona.xml example: * * <?xml version="1.0"?> * <configuration> * <defaultSchedulingMode>FAIR</defaultSchedulingMode> * <nodeLocalityWaitM>0</nodeLocalityWaitM> * <rackLocalityWaitM>5000</rackLocalityWaitM> * <preemptedTaskMaxRunningTime>60000</preemptedTaskMaxRunningTime> * <shareStarvingRatio>0.9</shareStarvingRatio> * <starvingTimeForShare>60000</starvingTimeForShare> * <starvingTimeForMinimum>30000</starvingTimeForMinimum> * <pool name="poolA"> * <minM>100</minM> * <minR>100</minR> * <maxM>200</maxM> * <maxR>200</maxR> * <weight>2.0</weight> * <schedulingMode>FIFO</schedulingMode> * <pool name="poolB"> * <maxM>200</maxM> * <maxR>200</maxR> * <weight>3.0</weight> * </configuration> * * Note that the type string "M" and "R" must be defined in {@link CoronaConf} */ public class ConfigManager { static final public Log LOG = LogFactory.getLog(ConfigManager.class); static final private long CONFIG_RELOAD_PERIOD = 3 * 60 * 1000L; // 3 minutes final private Collection<String> TYPES; final private ScheduleComparator DEFAULT_COMPARATOR = ScheduleComparator.FIFO; final private long DEFAULT_PREEMPT_TASK_MAX_RUNNING_TIME = 5 * 60 * 1000L; final private double DEFAULT_SHARE_STARVING_RATIO = 0.7; final private long DEFAULT_STARVING_TIME_FOR_SHARE = 5 * 60 * 1000L; final private long DEFAULT_STARVING_TIME_FOR_MINIMUM = 3 * 60 * 1000L; final private ClassLoader classLoader; private Map<String, Map<String, Integer>> typeToPoolToMax; private Map<String, Map<String, Integer>> typeToPoolToMin; private Map<String, Long> typeToNodeWait; private Map<String, Long> typeToRackWait; private Map<String, ScheduleComparator> poolToComparator; private Map<String, Double> poolToWeight; private ScheduleComparator defaultComparator; private double shareStarvingRatio; private long starvingTimeForShare; private long starvingTimeForMinimum; private long preemptedTaskMaxRunningTime; private volatile boolean running; private ReloadThread reloadThread; private long lastSuccessfulReload = -1L; private String configFileName = null; private void findConfigFile() { if (configFileName != null) return; URL u = classLoader.getResource(CoronaConf.POOL_CONFIG_FILE); LOG.info ("Found config file: " + (u != null ? u.getPath() : "")); configFileName = (u != null) ? u.getPath() : null; } public ConfigManager(Collection<String> types) { this(types, null); } public ConfigManager(Collection<String> types, String configFileName) { this.TYPES = types; typeToPoolToMax = new IdentityHashMap<String, Map<String, Integer>>(); typeToPoolToMin = new IdentityHashMap<String, Map<String, Integer>>(); typeToNodeWait = new IdentityHashMap<String, Long>(); typeToRackWait = new IdentityHashMap<String, Long>(); poolToComparator = new HashMap<String, ScheduleComparator>(); poolToWeight = new HashMap<String, Double>(); defaultComparator = DEFAULT_COMPARATOR; shareStarvingRatio = DEFAULT_SHARE_STARVING_RATIO; starvingTimeForMinimum = DEFAULT_STARVING_TIME_FOR_MINIMUM; starvingTimeForShare = DEFAULT_STARVING_TIME_FOR_SHARE; preemptedTaskMaxRunningTime = DEFAULT_PREEMPT_TASK_MAX_RUNNING_TIME; reloadThread = new ReloadThread(); reloadThread.setName("Config reload thread"); reloadThread.setDaemon(true); for (String type : TYPES) { typeToPoolToMax.put(type, new HashMap<String, Integer>()); typeToPoolToMin.put(type, new HashMap<String, Integer>()); typeToNodeWait.put(type, 0L); typeToRackWait.put(type, 0L); } ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl == null) { cl = ConfigManager.class.getClassLoader(); } classLoader = cl; this.configFileName = configFileName; try { findConfigFile(); reloadConfig(); } catch (Exception e) { LOG.error("Failed to load " + configFileName, e); } } /** * Used for unit tests */ public ConfigManager() { TYPES = null; classLoader = ConfigManager.class.getClassLoader(); } public void start() { reloadThread.start(); } public void close() { running = false; reloadThread.interrupt(); } public synchronized int getMaximum(String name, String type) { Map<String, Integer> poolToMax = typeToPoolToMax.get(type); if (poolToMax == null) { throw new IllegalArgumentException("Unknown type:" + type); } Integer max = poolToMax.get(name); return max == null ? Integer.MAX_VALUE : max; } public synchronized int getMinimum(String name, String type) { Map<String, Integer> poolToMin = typeToPoolToMin.get(type); if (poolToMin == null) { throw new IllegalArgumentException("Unknown type:" + type); } Integer min = poolToMin.get(name); return min == null ? 0 : min; } public synchronized double getWeight(String name) { Double weight = poolToWeight.get(name); return weight == null ? 1.0 : weight; } public synchronized ScheduleComparator getComparator(String name) { ScheduleComparator comparator = poolToComparator.get(name); return comparator == null ? defaultComparator : comparator; } public synchronized long getPreemptedTaskMaxRunningTime() { return preemptedTaskMaxRunningTime; } public synchronized double getShareStarvingRatio() { return shareStarvingRatio; } public synchronized long getStarvingTimeForShare() { return starvingTimeForShare; } public synchronized long getStarvingTimeForMinimum() { return starvingTimeForMinimum; } public synchronized long getLocalityWait(String type, LocalityLevel level) { if (level == LocalityLevel.ANY) { return 0L; } Long wait = level == LocalityLevel.NODE ? typeToNodeWait.get(type) : typeToRackWait.get(type); if (wait == null) { throw new IllegalArgumentException("Unknown type:" + type); } return wait; } private class ReloadThread extends Thread { @Override public void run() { long lastReloadAttempt = -1L; while (running) { long now = ClusterManager.clock.getTime(); if (lastReloadAttempt - now > CONFIG_RELOAD_PERIOD) { lastReloadAttempt = now; findConfigFile(); try { reloadConfig(); } catch (Exception e) { LOG.error("Failed to reload " + configFileName, e); } } try { Thread.sleep(CONFIG_RELOAD_PERIOD / 10); } catch (InterruptedException e) { } } } } void reloadConfig() throws IOException, SAXException, ParserConfigurationException { if (!isConfigChanged()) { return; } Map<String, Map<String, Integer>> typeToPoolToMax; Map<String, Map<String, Integer>> typeToPoolToMin; Map<String, Long> typeToNodeWait; Map<String, Long> typeToRackWait; Map<String, ScheduleComparator> poolToComparator; Map<String, Double> poolToWeight; ScheduleComparator defaultComparator = DEFAULT_COMPARATOR; double shareStarvingRatio = DEFAULT_SHARE_STARVING_RATIO; long starvingTimeForMinimum = DEFAULT_STARVING_TIME_FOR_MINIMUM; long starvingTimeForShare = DEFAULT_STARVING_TIME_FOR_SHARE; long preemptedTaskMaxRunningTime = DEFAULT_PREEMPT_TASK_MAX_RUNNING_TIME; typeToPoolToMax = new IdentityHashMap<String, Map<String, Integer>>(); typeToPoolToMin = new IdentityHashMap<String, Map<String, Integer>>(); typeToNodeWait = new IdentityHashMap<String, Long>(); typeToRackWait = new IdentityHashMap<String, Long>(); poolToComparator = new HashMap<String, ScheduleComparator>(); poolToWeight = new HashMap<String, Double>(); for (String type : TYPES) { typeToPoolToMax.put(type, new HashMap<String, Integer>()); typeToPoolToMin.put(type, new HashMap<String, Integer>()); typeToNodeWait.put(type, 0L); typeToRackWait.put(type, 0L); } Element root = getRootElement(); NodeList elements = root.getChildNodes(); for (int i = 0; i < elements.getLength(); ++i) { Node node = elements.item(i); if (!(node instanceof Element)) { continue; } Element element = (Element)node; for (String type : TYPES) { // Note that the type string is separately defined in CoronaConf if (matched(element, "nodeLocalityWait" + type)) { long val = Long.parseLong(getText(element)); typeToNodeWait.put(type, val); } if (matched(element, "rackLocalityWait" + type)) { long val = Long.parseLong(getText(element)); typeToRackWait.put(type, val); } } if (matched(element, "pool")) { String poolName = element.getAttribute("name"); NodeList fields = element.getChildNodes(); for (int j = 0; j < fields.getLength(); ++j) { Node fieldNode = fields.item(j); if (!(fieldNode instanceof Element)) { continue; } Element field = (Element) fieldNode; for (String type : TYPES) { // Note that the type string is separately defined in CoronaConf if (matched(field, "min" + type)) { int val = Integer.parseInt(getText(field)); Map<String, Integer> poolToMin = typeToPoolToMin.get(type); poolToMin.put(poolName, val); } if (matched(field, "max" + type)) { int val = Integer.parseInt(getText(field)); Map<String, Integer> poolToMax = typeToPoolToMax.get(type); poolToMax.put(poolName, val); } } if (matched(field, "schedulingMode")) { ScheduleComparator val = ScheduleComparator.valueOf(getText(field)); poolToComparator.put(poolName, val); } if (matched(field, "weight")) { double val = Double.parseDouble(getText(field)); poolToWeight.put(poolName, val); } } } if (matched(element, "defaultSchedulingMode")) { defaultComparator = ScheduleComparator.valueOf(getText(element)); } if (matched(element, "shareStarvingRatio")) { shareStarvingRatio = Double.parseDouble(getText(element)); if (shareStarvingRatio < 0 || shareStarvingRatio > 1.0) { LOG.error("Illegal shareStarvingRatio:" + shareStarvingRatio); shareStarvingRatio = DEFAULT_SHARE_STARVING_RATIO; } } if (matched(element, "starvingTimeForShare")) { starvingTimeForShare = Long.parseLong(getText(element)); if (starvingTimeForShare < 0) { LOG.error("Illegal starvingTimeForShare:" + starvingTimeForShare); starvingTimeForShare = DEFAULT_STARVING_TIME_FOR_SHARE; } } if (matched(element, "starvingTimeForMinimum")) { starvingTimeForMinimum = Long.parseLong(getText(element)); if (starvingTimeForMinimum < 0) { LOG.error("Illegal starvingTimeForMinimum:" + starvingTimeForMinimum); starvingTimeForMinimum = DEFAULT_STARVING_TIME_FOR_MINIMUM; } } if (matched(element, "preemptedTaskMaxRunningTime")) { preemptedTaskMaxRunningTime = Long.parseLong(getText(element)); if (preemptedTaskMaxRunningTime < 0) { LOG.error("Illegal preemptedTaskMaxRunningTime:" + preemptedTaskMaxRunningTime); preemptedTaskMaxRunningTime = DEFAULT_PREEMPT_TASK_MAX_RUNNING_TIME; } } } synchronized (this) { this.typeToPoolToMax = typeToPoolToMax; this.typeToPoolToMin = typeToPoolToMin; this.typeToNodeWait = typeToNodeWait; this.typeToRackWait = typeToRackWait; this.poolToComparator = poolToComparator; this.poolToWeight = poolToWeight; this.defaultComparator = defaultComparator; this.lastSuccessfulReload = ClusterManager.clock.getTime(); this.shareStarvingRatio = shareStarvingRatio; this.starvingTimeForMinimum = starvingTimeForMinimum; this.starvingTimeForShare = starvingTimeForShare; this.preemptedTaskMaxRunningTime = preemptedTaskMaxRunningTime; } } private boolean isConfigChanged() { if (configFileName == null) return false; File file = new File(configFileName); return file.lastModified() == 0 || file.lastModified() > lastSuccessfulReload; } private Element getRootElement() throws IOException, SAXException, ParserConfigurationException { DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); docBuilderFactory.setIgnoringComments(true); DocumentBuilder builder = docBuilderFactory.newDocumentBuilder(); Document doc = builder.parse(new File(configFileName)); Element root = doc.getDocumentElement(); if (!matched(root, "configuration")) { throw new IOException("Bad " + configFileName); } return root; } private static boolean matched(Element element, String tagName) { return tagName.equals(element.getTagName()); } private static String getText(Element element) { return ((Text)element.getFirstChild()).getData().trim(); } }