| /** |
| * 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><CHOICE></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; |
| } |
| |
| } |