blob: 605a05d3e31e19e01a996afde290674bea926c60 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.mapred;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.hadoop.mapred.FairScheduler.JobInfo;
import org.apache.hadoop.mapreduce.TaskType;
import org.apache.hadoop.util.StringUtils;
/**
* Servlet for displaying fair scheduler information, installed at
* [job tracker URL]/scheduler when the {@link FairScheduler} is in use.
*
* The main features are viewing each job's task count and fair share,
* and admin controls to change job priorities and pools from the UI.
*
* There is also an "advanced" view for debugging that can be turned on by
* going to [job tracker URL]/scheduler?advanced.
*/
public class FairSchedulerServlet extends HttpServlet {
private static final long serialVersionUID = 9104070533067306659L;
private static final DateFormat DATE_FORMAT =
new SimpleDateFormat("MMM dd, HH:mm");
private FairScheduler scheduler;
private JobTracker jobTracker;
private static long lastId = 0; // Used to generate unique element IDs
@Override
public void init() throws ServletException {
super.init();
ServletContext servletContext = this.getServletContext();
this.scheduler = (FairScheduler) servletContext.getAttribute("scheduler");
this.jobTracker = (JobTracker) scheduler.taskTrackerManager;
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doGet(req, resp); // Same handler for both GET and POST
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// If the request has a set* param, handle that and redirect to the regular
// view page so that the user won't resubmit the data if they hit refresh.
boolean advancedView = request.getParameter("advanced") != null;
if (JSPUtil.privateActionsAllowed(jobTracker.conf)
&& request.getParameter("setPool") != null) {
Collection<JobInProgress> runningJobs = getInitedJobs();
PoolManager poolMgr = null;
synchronized (scheduler) {
poolMgr = scheduler.getPoolManager();
}
String pool = request.getParameter("setPool");
String jobId = request.getParameter("jobid");
for (JobInProgress job: runningJobs) {
if (job.getProfile().getJobID().toString().equals(jobId)) {
synchronized(scheduler){
poolMgr.setPool(job, pool);
}
scheduler.update();
break;
}
}
response.sendRedirect("/scheduler" + (advancedView ? "?advanced" : ""));
return;
}
if (JSPUtil.privateActionsAllowed(jobTracker.conf)
&& request.getParameter("setPriority") != null) {
Collection<JobInProgress> runningJobs = getInitedJobs();
JobPriority priority = JobPriority.valueOf(request.getParameter(
"setPriority"));
String jobId = request.getParameter("jobid");
for (JobInProgress job: runningJobs) {
if (job.getProfile().getJobID().toString().equals(jobId)) {
job.setPriority(priority);
scheduler.update();
break;
}
}
response.sendRedirect("/scheduler" + (advancedView ? "?advanced" : ""));
return;
}
// Print out the normal response
response.setContentType("text/html");
// Because the client may read arbitrarily slow, and we hold locks while
// the servlet outputs, we want to write to our own buffer which we know
// won't block.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintWriter out = new PrintWriter(baos);
String hostname = StringUtils.simpleHostname(
jobTracker.getJobTrackerMachine());
out.print("<html><head>");
out.printf("<title>%s Fair Scheduler Admininstration</title>\n", hostname);
out.print("<link rel=\"stylesheet\" type=\"text/css\" " +
"href=\"/static/hadoop.css\">\n");
out.print("</head><body>\n");
out.printf("<h1><a href=\"/jobtracker.jsp\">%s</a> " +
"Fair Scheduler Administration</h1>\n", hostname);
showPools(out, advancedView);
showJobs(out, advancedView);
out.print("</body></html>\n");
out.close();
// Flush our buffer to the real servlet output
OutputStream servletOut = response.getOutputStream();
baos.writeTo(servletOut);
servletOut.close();
}
/**
* Print a view of pools to the given output writer.
*/
private void showPools(PrintWriter out, boolean advancedView) {
synchronized(scheduler) {
boolean warnInverted = false;
PoolManager poolManager = scheduler.getPoolManager();
out.print("<h2>Pools</h2>\n");
out.print("<table border=\"2\" cellpadding=\"5\" cellspacing=\"2\">\n");
out.print("<tr><th rowspan=2>Pool</th>" +
"<th rowspan=2>Running Jobs</th>" +
"<th colspan=4>Map Tasks</th>" +
"<th colspan=4>Reduce Tasks</th>" +
"<th rowspan=2>Scheduling Mode</th></tr>\n<tr>" +
"<th>Min Share</th><th>Max Share</th><th>Running</th><th>Fair Share</th>" +
"<th>Min Share</th><th>Max Share</th><th>Running</th><th>Fair Share</th></tr>\n");
List<Pool> pools = new ArrayList<Pool>(poolManager.getPools());
Collections.sort(pools, new Comparator<Pool>() {
public int compare(Pool p1, Pool p2) {
if (p1.isDefaultPool())
return 1;
else if (p2.isDefaultPool())
return -1;
else return p1.getName().compareTo(p2.getName());
}});
for (Pool pool: pools) {
String name = pool.getName();
int runningMaps = pool.getMapSchedulable().getRunningTasks();
int runningReduces = pool.getReduceSchedulable().getRunningTasks();
int maxMaps = poolManager.getMaxSlots(name, TaskType.MAP);
int maxReduces = poolManager.getMaxSlots(name, TaskType.REDUCE);
boolean invertedMaps = poolManager.invertedMinMax(TaskType.MAP, name);
boolean invertedReduces = poolManager.invertedMinMax(TaskType.REDUCE, name);
warnInverted = warnInverted || invertedMaps || invertedReduces;
out.print("<tr>");
out.printf("<td>%s</td>", name);
out.printf("<td>%d</td>", pool.getJobs().size());
// Map Tasks
out.printf("<td>%d</td>", poolManager.getAllocation(name,
TaskType.MAP));
out.print("<td>");
if(maxMaps == Integer.MAX_VALUE) {
out.print("-");
} else {
out.print(maxMaps);
}
if(invertedMaps) {
out.print("*");
}
out.print("</td>");
out.printf("<td>%d</td>", runningMaps);
out.printf("<td>%.1f</td>", pool.getMapSchedulable().getFairShare());
// Reduce Tasks
out.printf("<td>%d</td>", poolManager.getAllocation(name,
TaskType.REDUCE));
out.print("<td>");
if(maxReduces == Integer.MAX_VALUE) {
out.print("-");
} else {
out.print(maxReduces);
}
if(invertedReduces) {
out.print("*");
}
out.print("</td>");
out.printf("<td>%d</td>", runningReduces);
out.printf("<td>%.1f</td>", pool.getReduceSchedulable().getFairShare());
out.printf("<td>%s</td>", pool.getSchedulingMode());
out.print("</tr>\n");
}
out.print("</table>\n");
if(warnInverted) {
out.print("<p>* One or more pools have max share set lower than min share. Max share will be used and minimum will be treated as if set equal to max.</p>");
}
}
}
/**
* Print a view of running jobs to the given output writer.
*/
private void showJobs(PrintWriter out, boolean advancedView) {
out.print("<h2>Running Jobs</h2>\n");
out.print("<table border=\"2\" cellpadding=\"5\" cellspacing=\"2\">\n");
int colsPerTaskType = advancedView ? 4 : 3;
out.printf("<tr><th rowspan=2>Submitted</th>" +
"<th rowspan=2>JobID</th>" +
"<th rowspan=2>User</th>" +
"<th rowspan=2>Name</th>" +
"<th rowspan=2>Pool</th>" +
"<th rowspan=2>Priority</th>" +
"<th colspan=%d>Map Tasks</th>" +
"<th colspan=%d>Reduce Tasks</th>",
colsPerTaskType, colsPerTaskType);
out.print("</tr><tr>\n");
out.print("<th>Finished</th><th>Running</th><th>Fair Share</th>" +
(advancedView ? "<th>Weight</th>" : ""));
out.print("<th>Finished</th><th>Running</th><th>Fair Share</th>" +
(advancedView ? "<th>Weight</th>" : ""));
out.print("</tr>\n");
synchronized (jobTracker) {
Collection<JobInProgress> runningJobs = getInitedJobs();
synchronized (scheduler) {
for (JobInProgress job: runningJobs) {
JobProfile profile = job.getProfile();
JobInfo info = scheduler.infos.get(job);
if (info == null) { // Job finished, but let's show 0's for info
info = new JobInfo(null, null);
}
out.print("<tr>\n");
out.printf("<td>%s</td>\n", DATE_FORMAT.format(
new Date(job.getStartTime())));
out.printf("<td><a href=\"jobdetails.jsp?jobid=%s\">%s</a></td>",
profile.getJobID(), profile.getJobID());
out.printf("<td>%s</td>\n", profile.getUser());
out.printf("<td>%s</td>\n", profile.getJobName());
if (JSPUtil.privateActionsAllowed(jobTracker.conf)) {
out.printf("<td>%s</td>\n", generateSelect(scheduler
.getPoolManager().getPoolNames(), scheduler.getPoolManager()
.getPoolName(job), "/scheduler?setPool=<CHOICE>&jobid="
+ profile.getJobID() + (advancedView ? "&advanced" : "")));
out.printf("<td>%s</td>\n", generateSelect(Arrays
.asList(new String[] { "VERY_LOW", "LOW", "NORMAL", "HIGH",
"VERY_HIGH" }), job.getPriority().toString(),
"/scheduler?setPriority=<CHOICE>&jobid=" + profile.getJobID()
+ (advancedView ? "&advanced" : "")));
} else {
out.printf("<td>%s</td>\n", scheduler.getPoolManager().getPoolName(job));
out.printf("<td>%s</td>\n", job.getPriority().toString());
}
Pool pool = scheduler.getPoolManager().getPool(job);
String mapShare = (pool.getSchedulingMode() == SchedulingMode.FAIR) ?
String.format("%.1f", info.mapSchedulable.getFairShare()) : "NA";
out.printf("<td>%d / %d</td><td>%d</td><td>%s</td>\n",
job.finishedMaps(), job.desiredMaps(),
info.mapSchedulable.getRunningTasks(),
mapShare);
if (advancedView) {
out.printf("<td>%.1f</td>\n", info.mapSchedulable.getWeight());
}
String reduceShare = (pool.getSchedulingMode() == SchedulingMode.FAIR) ?
String.format("%.1f", info.reduceSchedulable.getFairShare()) : "NA";
out.printf("<td>%d / %d</td><td>%d</td><td>%s</td>\n",
job.finishedReduces(), job.desiredReduces(),
info.reduceSchedulable.getRunningTasks(),
reduceShare);
if (advancedView) {
out.printf("<td>%.1f</td>\n", info.reduceSchedulable.getWeight());
}
out.print("</tr>\n");
}
}
}
out.print("</table>\n");
}
/**
* Generate a HTML select control with a given list of choices and a given
* option selected. When the selection is changed, take the user to the
* <code>submitUrl</code>. The <code>submitUrl</code> can be made to include
* the option selected -- the first occurrence of the substring
* <code>&lt;CHOICE&gt;</code> will be replaced by the option chosen.
*/
private String generateSelect(Iterable<String> choices,
String selectedChoice, String submitUrl) {
StringBuilder html = new StringBuilder();
String id = "select" + lastId++;
html.append("<select id=\"" + id + "\" name=\"" + id + "\" " +
"onchange=\"window.location = '" + submitUrl +
"'.replace('<CHOICE>', document.getElementById('" + id +
"').value);\">\n");
for (String choice: choices) {
html.append(String.format("<option value=\"%s\"%s>%s</option>\n",
choice, (choice.equals(selectedChoice) ? " selected" : ""), choice));
}
html.append("</select>\n");
return html.toString();
}
/**
* Obtained all initialized jobs
*/
private Collection<JobInProgress> getInitedJobs() {
Collection<JobInProgress> runningJobs = jobTracker.getRunningJobs();
for (Iterator<JobInProgress> it = runningJobs.iterator(); it.hasNext();) {
JobInProgress job = it.next();
if (!job.inited()) {
it.remove();
}
}
return runningJobs;
}
}