/* * 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.corona; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.PriorityQueue; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Manages sessions in a pool for a given type. */ public class PoolSchedulable extends Schedulable { /** Logger */ private static final Log LOG = LogFactory.getLog(PoolSchedulable.class); /** The configuration manager */ private final ConfigManager configManager; /** A lookup table from id to the session */ private final Map<String, SessionSchedulable> idToSession; /** The snapshot of the SessionSchedulables */ private final Collection<SessionSchedulable> snapshotSessions; /** The queue of sessions sorted for scheduling */ private Queue<SessionSchedulable> scheduleQueue; /** The queue of sessions sorted for preemption */ private Queue<SessionSchedulable> preemptQueue; /** Last time the pool was above minimum allocation */ private long lastTimeAboveMinimum; /** Last time the pool was above starving share */ private long lastTimeAboveStarvingShare; /** Last time the pool was preempted */ private long lastPreemptTime; /** A flag to indicate that the pool needs its share redistributed */ private boolean needRedistributed; /** The max allocation for the pool */ private int maximum; /** The minimum allocation for the pool */ private int minimum; /** The weight of the pool */ private double weight; /** The ratio for starving */ private double shareStarvingRatio; /** Is this pool preemptable or not */ private boolean preemptable; /** The minimum amount of time to wait between preemptions */ private long minPreemptPeriod; /** The amount of time the pool has been starving for the share */ private long starvingTimeForShare; /** The amount of time the pool has been starving for the minimum */ private long starvingTimeForMinimum; /** Pool info names for this pool */ private final PoolInfo poolInfo; /** Priority for this pool */ private int priority; /** * Create a PoolSchedulable for a given pool and a resource type with a * provided configuration manager * @param poolInfo Pool info for this pool * @param type the resource type of this pool * @param configManager the configuration manager for this pool */ public PoolSchedulable(PoolInfo poolInfo, ResourceType type, ConfigManager configManager) { super(PoolInfo.createStringFromPoolInfo(poolInfo), type); this.poolInfo = poolInfo; this.configManager = configManager; this.scheduleQueue = null; this.preemptQueue = null; this.snapshotSessions = new ArrayList<SessionSchedulable>(); this.lastTimeAboveMinimum = -1L; this.lastTimeAboveStarvingShare = -1L; this.needRedistributed = true; // This needs to be thread safe because addSession() and getSortedSessions() // are called from different threads idToSession = new ConcurrentHashMap<String, SessionSchedulable>(); } public PoolInfo getPoolInfo() { return poolInfo; } @Override public void snapshot() { granted = 0; requested = 0; pending = 0; snapshotSessions.clear(); Set<String> toBeDeleted = new HashSet<String>(); for (Entry<String, SessionSchedulable> entry : idToSession.entrySet()) { SessionSchedulable schedulable = entry.getValue(); Session session = entry.getValue().getSession(); synchronized (session) { if (session.isDeleted()) { toBeDeleted.add(entry.getKey()); } else { schedulable.snapshot(); snapshotSessions.add(schedulable); granted += schedulable.getGranted(); requested += schedulable.getRequested(); pending += schedulable.getPending(); } } } if (LOG.isDebugEnabled()) { LOG.debug("Snapshot for pool " + getName() + ":" + getType() + " {requested = " + requested + ", pending = " + pending + ", granted = " + granted + "}"); } for (String id : toBeDeleted) { idToSession.remove(id); } snapshotConfig(); scheduleQueue = null; preemptQueue = null; needRedistributed = true; } /** * Get the snapshot of the configuration from the configuration manager. * Synchronized on config manager to ensure that config is atomically updated * per pool. */ private void snapshotConfig() { synchronized (configManager) { maximum = configManager.getPoolMaximum(poolInfo, getType()); minimum = configManager.getPoolMinimum(poolInfo, getType()); weight = configManager.getWeight(poolInfo); priority = configManager.getPriority(poolInfo); preemptable = configManager.isPoolPreemptable(poolInfo); shareStarvingRatio = configManager.getShareStarvingRatio(); minPreemptPeriod = configManager.getMinPreemptPeriod(); starvingTimeForShare = configManager.getStarvingTimeForShare(); starvingTimeForMinimum = configManager.getStarvingTimeForMinimum(); } } @Override public int getMaximum() { return maximum; } @Override public int getMinimum() { return minimum; } @Override public double getWeight() { return weight; } @Override public long getDeadline() { // Pools have no deadlines return -1L; } @Override public int getPriority() { return priority; } /** * Check if the pool's share has reached its maximum share * @return true if the pool's share has reached its maximum, false * otherwise */ public boolean reachedMaximum() { return granted >= getMaximum() || granted >= requested; } /** * Add a session to the pool * @param id the id of the session * @param session the session to add to the pool */ public void addSession(String id, Session session) { synchronized (session) { SessionSchedulable schedulable = new SessionSchedulable(session, getType()); idToSession.put(id, schedulable); } } /** * Get the queue of sessions sorted for scheduling * @return the queue of sessions sorted for scheduling */ public Queue<SessionSchedulable> getScheduleQueue() { if (scheduleQueue == null) { scheduleQueue = createSessionQueue(configManager.getPoolComparator(poolInfo)); } return scheduleQueue; } /** * Get the queue of sessions sorted for preemption * @return the queue of sessions sorted for preemption */ public Queue<SessionSchedulable> getPreemptQueue() { if (preemptQueue == null) { ScheduleComparator comparator = null; switch (configManager.getPoolComparator(poolInfo)) { case FIFO: comparator = ScheduleComparator.FIFO_PREEMPT; break; case FAIR: comparator = ScheduleComparator.FAIR_PREEMPT; break; case DEADLINE: comparator = ScheduleComparator.DEADLINE_PREEMPT; break; default: throw new IllegalArgumentException("Unknown comparator"); } preemptQueue = createSessionQueue(comparator); } return preemptQueue; } /** * Get the queue of sessions in the pool sorted by comparator * @param comparator the comparator to use when sorting sessions * @return the queue of the sessions sorted by a given comparator */ public Queue<SessionSchedulable> createSessionQueue( ScheduleComparator comparator) { int initCapacity = snapshotSessions.size() == 0 ? 1 : snapshotSessions.size(); Queue<SessionSchedulable> sessionQueue = new PriorityQueue<SessionSchedulable>(initCapacity, comparator); sessionQueue.addAll(snapshotSessions); return sessionQueue; } /** * Distribute the pool's share between the sessions in the pool */ public void distributeShare() { if (needRedistributed) { ScheduleComparator comparator = configManager.getPoolComparator(poolInfo); Schedulable.distributeShare(getShare(), snapshotSessions, comparator); } needRedistributed = false; } /** * Check if the pool is starving now or not * @param now current timestampe * @return true if the pool is starving now, false otherwise */ public boolean isStarving(long now) { double starvingShare = getShare() * shareStarvingRatio; if (getGranted() >= Math.ceil(starvingShare)) { lastTimeAboveStarvingShare = now; } if (getGranted() >= Math.min(getShare(), getMinimum())) { lastTimeAboveMinimum = now; } if (now - lastPreemptTime < getMinPreemptPeriod()) { // Prevent duplicate preemption return false; } if (LOG.isDebugEnabled()) { LOG.debug("Pool:" + getName() + " lastTimeAboveMinimum:" + lastTimeAboveMinimum + " lastTimeAboveStarvingShare:" + lastTimeAboveStarvingShare + " minimumStarvingTime:" + getMinimumStarvingTime(now) + " shareStarvingTime:" + getShareStarvingTime(now) + " starvingTime:" + getStarvingTime(now)); } if (getMinimumStarvingTime(now) >= 0) { LOG.info("Pool:" + getName() + " for type:" + getType() + " is starving min:" + getMinimum() + " granted:" + getGranted()); lastPreemptTime = now; return true; } if (getShareStarvingTime(now) >= 0) { LOG.info("Pool:" + getName() + " for type:" + getType() + " is starving share:" + getShare() + " starvingRatio:" + shareStarvingRatio + " starvingShare:" + starvingShare + " granted:" + getGranted()); lastPreemptTime = now; return true; } return false; } /** * Get the amount of time the pool was starving for either its min share * or its share * @param now the current timestamp * @return the amount of time the pool was starving */ public long getStarvingTime(long now) { long starvingTime = Math.max( getMinimumStarvingTime(now), getShareStarvingTime(now)); return starvingTime; } /** * @return The minimum time between preemptions. */ public long getMinPreemptPeriod() { return minPreemptPeriod; } /** * Get the amount of time the pool was starving for its min share * @param now the current timestamp * @return the amount of time the pool was starving for the min share */ private long getMinimumStarvingTime(long now) { return (now - lastTimeAboveMinimum) - starvingTimeForMinimum; } /** * Get the amount of time the pool was starving for its share * @param now the current timestamp * @return the amount of time the pool was starving for share */ private long getShareStarvingTime(long now) { return (now - lastTimeAboveStarvingShare) - starvingTimeForShare; } /** * Is the pool preemptable or not * @return true if the pool is preemptable false otherwise */ public boolean isPreemptable() { return preemptable; } }