/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.mapred;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.mapred.JobQueueJobInProgressListener.JobSchedulingInfo;
import org.apache.hadoop.mapred.JobStatusChangeEvent.EventType;
/**
* A {@link JobInProgressListener} that maintains the jobs being managed in
* one or more queues.
*/
class JobQueuesManager extends JobInProgressListener {
/*
* If a queue supports priorities, jobs must be
* sorted on priorities, and then on their start times (technically,
* their insertion time.
* If a queue doesn't support priorities, jobs are
* sorted based on their start time.
*/
// comparator for jobs in queues that don't support priorities
private static final Comparator<JobSchedulingInfo> STARTTIME_JOB_COMPARATOR
= new Comparator<JobSchedulingInfo>() {
public int compare(JobSchedulingInfo o1, JobSchedulingInfo o2) {
// the job that started earlier wins
if (o1.getStartTime() < o2.getStartTime()) {
return -1;
} else {
return (o1.getStartTime() == o2.getStartTime()
? o1.getJobID().compareTo(o2.getJobID())
: 1);
}
}
};
// class to store queue info
private static class QueueInfo {
// whether the queue supports priorities
boolean supportsPriorities;
Map<JobSchedulingInfo, JobInProgress> waitingJobs; // for waiting jobs
Map<JobSchedulingInfo, JobInProgress> runningJobs; // for running jobs
public Comparator<JobSchedulingInfo> comparator;
QueueInfo(boolean prio) {
this.supportsPriorities = prio;
if (supportsPriorities) {
// use the default priority-aware comparator
comparator = JobQueueJobInProgressListener.FIFO_JOB_QUEUE_COMPARATOR;
}
else {
comparator = STARTTIME_JOB_COMPARATOR;
}
waitingJobs = new TreeMap<JobSchedulingInfo, JobInProgress>(comparator);
runningJobs = new TreeMap<JobSchedulingInfo, JobInProgress>(comparator);
}
Collection<JobInProgress> getWaitingJobs() {
synchronized (waitingJobs) {
return Collections.unmodifiableCollection(
new LinkedList<JobInProgress>(waitingJobs.values()));
}
}
Collection<JobInProgress> getRunningJobs() {
synchronized (runningJobs) {
return Collections.unmodifiableCollection(
new LinkedList<JobInProgress>(runningJobs.values()));
}
}
void addRunningJob(JobInProgress job) {
synchronized (runningJobs) {
runningJobs.put(new JobSchedulingInfo(job),job);
}
}
JobInProgress removeRunningJob(JobSchedulingInfo jobInfo) {
synchronized (runningJobs) {
return runningJobs.remove(jobInfo);
}
}
JobInProgress removeWaitingJob(JobSchedulingInfo schedInfo) {
synchronized (waitingJobs) {
return waitingJobs.remove(schedInfo);
}
}
void addWaitingJob(JobInProgress job) {
synchronized (waitingJobs) {
waitingJobs.put(new JobSchedulingInfo(job), job);
}
}
int getWaitingJobCount() {
synchronized (waitingJobs) {
return waitingJobs.size();
}
}
}
// we maintain a hashmap of queue-names to queue info
private Map<String, QueueInfo> jobQueues =
new HashMap<String, QueueInfo>();
private static final Log LOG = LogFactory.getLog(JobQueuesManager.class);
private CapacityTaskScheduler scheduler;
JobQueuesManager(CapacityTaskScheduler s) {
this.scheduler = s;
}
/**
* create an empty queue with the default comparator
* @param queueName The name of the queue
* @param supportsPriotities whether the queue supports priorities
*/
public void createQueue(String queueName, boolean supportsPriotities) {
jobQueues.put(queueName, new QueueInfo(supportsPriotities));
}
/**
* Returns the queue of running jobs associated with the name
*/
public Collection<JobInProgress> getRunningJobQueue(String queueName) {
return jobQueues.get(queueName).getRunningJobs();
}
/**
* Returns the queue of waiting jobs associated with queue name.
*
*/
Collection<JobInProgress> getWaitingJobs(String queueName) {
return jobQueues.get(queueName).getWaitingJobs();
}
@Override
public void jobAdded(JobInProgress job) throws IOException {
LOG.info("Job submitted to queue " + job.getProfile().getQueueName());
// add job to the right queue
QueueInfo qi = jobQueues.get(job.getProfile().getQueueName());
if (null == qi) {
// job was submitted to a queue we're not aware of
LOG.warn("Invalid queue " + job.getProfile().getQueueName() +
" specified for job" + job.getProfile().getJobID() +
". Ignoring job.");
return;
}
// add job to waiting queue. It will end up in the right place,
// based on priority.
qi.addWaitingJob(job);
// let scheduler know.
scheduler.jobAdded(job);
}
/*
* Method removes the jobs from both running and waiting job queue in
* job queue manager.
*/
private void jobCompleted(JobInProgress job, JobSchedulingInfo oldInfo,
QueueInfo qi) {
LOG.info("Job " + job.getJobID().toString() + " submitted to queue "
+ job.getProfile().getQueueName() + " has completed");
//remove jobs from both queue's a job can be in
//running and waiting queue at the same time.
qi.removeRunningJob(oldInfo);
qi.removeWaitingJob(oldInfo);
// let scheduler know
scheduler.jobCompleted(job);
}
// Note that job is removed when the job completes i.e in jobUpated()
@Override
public void jobRemoved(JobInProgress job) {}
// This is used to reposition a job in the queue. A job can get repositioned
// because of the change in the job priority or job start-time.
private void reorderJobs(JobInProgress job, JobSchedulingInfo oldInfo,
QueueInfo qi) {
if(qi.removeWaitingJob(oldInfo) != null) {
qi.addWaitingJob(job);
}
if(qi.removeRunningJob(oldInfo) != null) {
qi.addRunningJob(job);
}
}
// This is used to move a job from the waiting queue to the running queue.
private void makeJobRunning(JobInProgress job, JobSchedulingInfo oldInfo,
QueueInfo qi) {
// Removing of the job from job list is responsibility of the
//initialization poller.
// Add the job to the running queue
qi.addRunningJob(job);
}
// Update the scheduler as job's state has changed
private void jobStateChanged(JobStatusChangeEvent event, QueueInfo qi) {
JobInProgress job = event.getJobInProgress();
JobSchedulingInfo oldJobStateInfo =
new JobSchedulingInfo(event.getOldStatus());
// Check if the ordering of the job has changed
// For now priority and start-time can change the job ordering
if (event.getEventType() == EventType.PRIORITY_CHANGED
|| event.getEventType() == EventType.START_TIME_CHANGED) {
// Make a priority change
reorderJobs(job, oldJobStateInfo, qi);
} else if (event.getEventType() == EventType.RUN_STATE_CHANGED) {
// Check if the job is complete
int runState = job.getStatus().getRunState();
if (runState == JobStatus.SUCCEEDED
|| runState == JobStatus.FAILED
|| runState == JobStatus.KILLED) {
jobCompleted(job, oldJobStateInfo, qi);
} else if (runState == JobStatus.RUNNING) {
makeJobRunning(job, oldJobStateInfo, qi);
}
}
}
@Override
public void jobUpdated(JobChangeEvent event) {
JobInProgress job = event.getJobInProgress();
QueueInfo qi = jobQueues.get(job.getProfile().getQueueName());
if (null == qi) {
// can't find queue for job. Shouldn't happen.
LOG.warn("Could not find queue " + job.getProfile().getQueueName() +
" when updating job " + job.getProfile().getJobID());
return;
}
// Check if this is the status change
if (event instanceof JobStatusChangeEvent) {
jobStateChanged((JobStatusChangeEvent)event, qi);
}
}
void removeJobFromWaitingQueue(JobInProgress job) {
String queue = job.getProfile().getQueueName();
QueueInfo qi = jobQueues.get(queue);
qi.removeWaitingJob(new JobSchedulingInfo(job));
}
Comparator<JobSchedulingInfo> getComparator(String queue) {
return jobQueues.get(queue).comparator;
}
int getWaitingJobCount(String queue) {
QueueInfo qi = jobQueues.get(queue);
return qi.getWaitingJobCount();
}
boolean doesQueueSupportPriorities(String queueName) {
return jobQueues.get(queueName).supportsPriorities;
}
}