From 8a576eb8207d0de79d3bf69354dd3a9bda417d14 Mon Sep 17 00:00:00 2001
From: Chad Metcalf <chad@cloudera.com>
Date: Tue, 15 Sep 2009 17:26:37 -0700
Subject: [PATCH] HADOOP-5442: The job history display needs to be paged

---
 src/webapps/job/jobhistory.jsp |  191 +++++++++++++++++++++++++++++++++++++--
 1 files changed, 181 insertions(+), 10 deletions(-)

diff --git a/src/webapps/job/jobhistory.jsp b/src/webapps/job/jobhistory.jsp
index 9632422..a399a79 100644
--- a/src/webapps/job/jobhistory.jsp
+++ b/src/webapps/job/jobhistory.jsp
@@ -10,23 +10,69 @@
   import="org.apache.hadoop.mapred.*"
   import="org.apache.hadoop.mapred.JobHistory.*"
 %>
+<%	
+  JobTracker tracker = (JobTracker) application.getAttribute("job.tracker");
+  String trackerName =
+           StringUtils.simpleHostname(tracker.getJobTrackerMachine());
+%>
 <%!	
   private static SimpleDateFormat dateFormat = 
                                     new SimpleDateFormat("d/MM HH:mm:ss");
 %>
 <html>
 <head>
-<title>Hadoop Map/Reduce Administration</title>
+<script type="text/JavaScript">
+<!--
+function showUserHistory(search)
+{
+var url
+if (search == null || "".equals(search)) {
+  url="jobhistory.jsp";
+} else {
+  url="jobhistory.jsp?pageno=1&search=" + search;
+}
+window.location.href = url;
+}
+//-->
+</script>
+<link rel="stylesheet" type="text/css" href="/static/hadoop.css">
+<title><%= trackerName %> Hadoop Map/Reduce History Viewer</title>
 <link rel="stylesheet" type="text/css" href="/static/hadoop.css">
 </head>
 <body>
-<h1>Hadoop Map/Reduce History Viewer</h1>
+<h1> <a href="jobtracker.jsp"><%= trackerName %></a> Hadoop Map/Reduce 
+     <a href="jobhistory.jsp">History Viewer</a></h1>
 <hr>
-<h2>Available History </h2>
 <%
+    final String search = (request.getParameter("search") == null)
+                          ? ""
+                          : request.getParameter("search");
+
+    String parts[] = search.split(":");
+
+    final String user = (parts.length >= 1)
+                        ? parts[0].toLowerCase()
+                        : "";
+    final String jobname = (parts.length >= 2)
+                           ? parts[1].toLowerCase()
+                           : "";
     PathFilter jobLogFileFilter = new PathFilter() {
+      private boolean matchUser(String fileName) {
+        // return true if 
+        //  - user is not specified
+        //  - user matches
+        return "".equals(user) || user.equals(fileName.split("_")[5]);
+      }
+
+      private boolean matchJobName(String fileName) {
+        // return true if 
+        //  - jobname is not specified
+        //  - jobname contains the keyword
+        return "".equals(jobname) || fileName.split("_")[6].toLowerCase().contains(jobname);
+      }
+
       public boolean accept(Path path) {
-        return !(path.getName().endsWith(".xml"));
+        return !(path.getName().endsWith(".xml")) && matchUser(path.getName()) && matchJobName(path.getName());
       }
     };
     
@@ -38,11 +84,85 @@
     }
     Path[] jobFiles = FileUtil.stat2Paths(fs.listStatus(new Path(historyLogDir),
                                           jobLogFileFilter));
-    if (null == jobFiles )  {
-      out.println("NULL files !!!"); 
+    out.println("<!--  user : " + user + ", jobname : " + jobname + "-->");
+    if (null == jobFiles || jobFiles.length == 0)  {
+      out.println("No files found!"); 
       return ; 
     }
 
+    // get the pageno
+    int pageno = request.getParameter("pageno") == null
+                ? 1
+                : Integer.parseInt(request.getParameter("pageno"));
+
+    // get the total number of files to display
+    int size = 100;
+
+    // if show-all is requested or jobfiles < size(100)
+    if (pageno == -1 || size > jobFiles.length) {
+      size = jobFiles.length;
+    }
+
+    if (pageno == -1) { // special case 'show all'
+      pageno = 1;
+    }
+
+    int maxPageNo = (int)Math.ceil((float)jobFiles.length / size);
+
+    // check and fix pageno
+    if (pageno < 1 || pageno > maxPageNo) {
+      out.println("Invalid page index");
+      return ;
+    }
+
+    int length = size ; // determine the length of job history files to be displayed
+    if (pageno == maxPageNo) {
+      // find the number of files to be shown on the last page
+      int startOnLast = ((pageno - 1) * size) + 1;
+      length = jobFiles.length - startOnLast + 1;
+    }
+
+    // Display the search box
+    out.println("<form name=search><b> Filter (username:jobname) </b>"); // heading
+    out.println("<input type=text name=search size=\"20\" value=\"" + search + "\">"); // search box
+    out.println("<input type=submit value=\"Filter!\" onClick=\"showUserHistory(document.getElementById('search').value)\"></form>");
+    out.println("<span class=\"small\">Example: 'smith' will display jobs either submitted by user 'smith'. 'smith:sort' will display jobs from user 'smith' having 'sort' keyword in the jobname.</span>"); // example
+    out.println("<hr>");
+
+    //Show the status
+    int start = (pageno - 1) * size + 1;
+
+    // DEBUG
+    out.println("<!-- pageno : " + pageno + ", size : " + size + ", length : " + length + ", start : " + start + ", maxpg : " + maxPageNo + "-->");
+
+    out.println("<font size=5><b>Available Jobs in History </b></font>");
+    // display the number of jobs, start index, end index
+    out.println("(<i> <span class=\"small\">Displaying <b>" + length + "</b> jobs from <b>" + start + "</b> to <b>" + (start + length - 1) + "</b> out of <b>" + jobFiles.length + "</b> jobs");
+    if (!"".equals(user)) {
+      out.println(" for user <b>" + user + "</b>"); // show the user if present
+    }
+    if (!"".equals(jobname)) {
+      out.println(" with jobname having the keyword <b>" + jobname + "</b> in it."); // show the jobname keyword if present
+    }
+    out.print("</span></i>)");
+
+    // show the 'show-all' link
+    out.println(" [<span class=\"small\"><a href=\"jobhistory.jsp?pageno=-1&search=" + search + "\">show all</a></span>]");
+
+    // show the 'first-page' link
+    if (pageno > 1) {
+      out.println(" [<span class=\"small\"><a href=\"jobhistory.jsp?pageno=1&search=" + search + "\">first page</a></span>]");
+    } else {
+      out.println("[<span class=\"small\">first page]</span>");
+    }
+
+    // show the 'last-page' link
+    if (pageno < maxPageNo) {
+      out.println(" [<span class=\"small\"><a href=\"jobhistory.jsp?pageno=" + maxPageNo + "&search=" + search + "\">last page</a></span>]");
+    } else {
+      out.println("<span class=\"small\">[last page]</span>");
+    }
+
     // sort the files on creation time.
     Arrays.sort(jobFiles, new Comparator<Path>() {
       public int compare(Path p1, Path p2) {
@@ -74,8 +194,12 @@
       }
     });
 
+    out.println("<br><br>");
+
+    // print the navigation info (top)
+    printNavigation(pageno, size, maxPageNo, search, out);
+
     out.print("<table align=center border=2 cellpadding=\"5\" cellspacing=\"2\">");
-    out.print("<tr><td align=\"center\" colspan=\"9\"><b>Available Jobs </b></td></tr>\n");
     out.print("<tr>");
     out.print("<td>Job tracker Host Name</td>" +
               "<td>Job tracker Start time</td>" +
@@ -83,7 +207,9 @@
     out.print("</tr>"); 
     
     Set<String> displayedJobs = new HashSet<String>();
-    for (Path jobFile: jobFiles) {
+    for (int i = start - 1; i < start + length - 1; ++i) {
+      Path jobFile = jobFiles[i];
+      
       String decodedJobFileName = 
           JobHistory.JobInfo.decodeJobHistoryFileName(jobFile.getName());
 
@@ -91,7 +217,7 @@
       String trackerHostName = jobDetails[0];
       String trackerStartTime = jobDetails[1];
       String jobId = jobDetails[2] + "_" +jobDetails[3] + "_" + jobDetails[4] ;
-      String user = jobDetails[5];
+      String userName = jobDetails[5];
       String jobName = jobDetails[6];
       
       // Check if the job is already displayed. There can be multiple job 
@@ -109,12 +235,16 @@
 <center>
 <%	
       printJob(trackerHostName, trackerStartTime, jobId,
-               jobName, user, new Path(jobFile.getParent(), encodedJobFileName), 
+               jobName, userName, new Path(jobFile.getParent(), encodedJobFileName), 
                out) ; 
 %>
 </center> 
 <%
     } // end while trackers 
+    out.print("</table>");
+
+    // show the navigation info (bottom)
+    printNavigation(pageno, size, maxPageNo, search, out);
 %>
 <%!
     private void printJob(String trackerHostName, String trackerid,
@@ -130,5 +260,46 @@
       out.print("<td>" + user + "</td>"); 
       out.print("</tr>");
     }
+
+    private void printNavigation(int pageno, int size, int max, String search, 
+                                 JspWriter out) throws IOException {
+      int numIndexToShow = 5; // num indexes to show on either side
+
+      //TODO check this on boundary cases
+      out.print("<center> <");
+
+      // show previous link
+      if (pageno > 1) {
+        out.println("<a href=\"jobhistory.jsp?pageno=" + (pageno - 1) + "&search=" + search + "\">Previous</a>");
+      }
+
+      // display the numbered index 1 2 3 4
+      int firstPage = pageno - numIndexToShow;
+      if (firstPage < 1) {
+        firstPage = 1; // boundary condition
+      }
+
+      int lastPage = pageno + numIndexToShow;
+      if (lastPage > max) {
+        lastPage = max; // boundary condition
+      }
+
+      // debug
+      out.println("<!--DEBUG : firstPage : " + firstPage + ", lastPage : " + lastPage + " -->");
+
+      for (int i = firstPage; i <= lastPage; ++i) {
+        if (i != pageno) {// needs hyperlink
+          out.println(" <a href=\"jobhistory.jsp?pageno=" + i + "&search=" + search + "\">" + i + "</a> ");
+        } else { // current page
+          out.println(i);
+        }
+      }
+
+      // show the next link
+      if (pageno < max) {
+        out.println("<a href=\"jobhistory.jsp?pageno=" + (pageno + 1) + "&search=" + search + "\">Next</a>");
+      }
+      out.print("></center>");
+    }
 %> 
 </body></html>
-- 
1.6.0.4

