Coverage Report - org.galagosearch.tupleflow.execution.MasterWebHandler
 
Classes in this File Line Coverage Branch Coverage Complexity
MasterWebHandler
0%
0/121
0%
0/38
0
MasterWebHandler$AggregateCounter
0%
0/11
0%
0/2
0
MasterWebHandler$CounterName
0%
0/10
0%
0/2
0
 
 1  
 // BSD License (http://www.galagosearch.org/license)
 2  
 
 3  
 package org.galagosearch.tupleflow.execution;
 4  
 
 5  
 import java.io.IOException;
 6  
 import java.io.PrintWriter;
 7  
 import java.util.Date;
 8  
 import java.util.HashMap;
 9  
 import java.util.Map;
 10  
 import java.util.Map.Entry;
 11  
 import java.util.TreeMap;
 12  
 import javax.servlet.ServletException;
 13  
 import javax.servlet.http.HttpServletRequest;
 14  
 import javax.servlet.http.HttpServletResponse;
 15  
 import org.galagosearch.tupleflow.execution.JobExecutor.JobExecutionStatus;
 16  
 import org.mortbay.jetty.handler.AbstractHandler;
 17  
 
 18  
 /**
 19  
  * This handler creates a web interface for checking on the status of a
 20  
  * running TupleFlow job.
 21  
  *
 22  
  * @author trevor
 23  
  */
 24  
 public class MasterWebHandler extends AbstractHandler {
 25  
     JobExecutionStatus status;
 26  0
     Map<CounterName, AggregateCounter> counters = new TreeMap<CounterName, AggregateCounter>();
 27  
 
 28  
     /**
 29  
      * Time, in milliseconds, of the last page load.  Can be 0 if the page has never been
 30  
      * loaded, or if the page was loaded since the job completed.
 31  
      */
 32  0
     long lastPageLoad = 0;
 33  
 
 34  0
     public static class CounterName implements Comparable<CounterName> {
 35  
         String counterName;
 36  
         String stageName;
 37  
 
 38  0
         public CounterName(String stageName, String counterName) {
 39  0
             this.stageName = stageName;
 40  0
             this.counterName = counterName;
 41  0
         }
 42  
 
 43  0
         public String getCounterName() { return counterName; }
 44  0
         public String getStageName() { return stageName; }
 45  
         public int compareTo(CounterName other) {
 46  0
             int result = stageName.compareTo(other.stageName);
 47  0
             if (result != 0) return result;
 48  0
             return counterName.compareTo(other.counterName);
 49  
         }
 50  
     }
 51  
 
 52  
     private void handleRefresh(HttpServletRequest request, PrintWriter writer) {
 53  0
         int refresh = 5;
 54  0
         if (request.getParameter("refresh") != null) {
 55  
             try {
 56  0
                 refresh = Integer.parseInt(request.getParameter("refresh"));
 57  0
             } catch (Exception e) {
 58  
                 // do nothing
 59  0
             }
 60  
         }
 61  0
         if (refresh > 0) {
 62  0
             writer.append(String.format("<meta http-equiv=\"refresh\" content=\"%d\" />", refresh));
 63  
         }
 64  0
     }
 65  
 
 66  
     /**
 67  
      * An aggregate counter holds counter data from lots of instances and
 68  
      * returns the sum.
 69  
      */
 70  0
     class AggregateCounter {
 71  
         /// Returns the total counter value from all instances.
 72  
         public synchronized long getValue() {
 73  0
             return total;
 74  
         }
 75  
 
 76  
         /// Updates the counter value for a particular instance.
 77  
         public synchronized void setValue(String instance, long value) {
 78  0
             long oldValue = 0;
 79  0
             if (instances.containsKey(instance))
 80  0
                 oldValue = instances.get(instance);
 81  0
             long delta = value - oldValue;
 82  0
             total += delta;
 83  0
             instances.put(instance, value);
 84  0
         }
 85  
 
 86  0
         HashMap<String, Long> instances = new HashMap();
 87  0
         long total = 0;
 88  
     }
 89  
 
 90  0
     public MasterWebHandler(JobExecutionStatus status) {
 91  0
         this.status = status;
 92  0
     }
 93  
 
 94  
     public synchronized void setLastPageLoad(long value) {
 95  0
         lastPageLoad = value;
 96  0
     }
 97  
 
 98  
     public synchronized long getLastPageLoad() {
 99  0
         return lastPageLoad;
 100  
     }
 101  
 
 102  
     /**
 103  
      * <p>Waits a bit longer for someone to load a final status page.</p>
 104  
      *
 105  
      * <p>This method should be called immediately after a job is complete.
 106  
      * If the page was loaded recently and the job wasn't complete, this
 107  
      * method will wait up to 15 seconds for the browser to load the indexing
 108  
      * complete page, which has no auto-refresh logic.  This avoids the
 109  
      * ugly experience of showing a "cannot connect to server" error in the
 110  
      * browser after indexing completes.</p>
 111  
      */
 112  
     public void waitForFinalPage() {
 113  0
         synchronized(this) {
 114  
             // If someone loaded a page within the last 15 seconds, we'll
 115  
             // wait a little bit more for a final refresh.  We'll get
 116  
             // signaled by the final load.
 117  0
             long timeDelta = System.currentTimeMillis() - getLastPageLoad();
 118  0
             if (timeDelta <= 15 * 1000) {
 119  
                 try {
 120  0
                     this.wait(15 * 1000);
 121  0
                 } catch (InterruptedException e) {
 122  
                     // do nothing
 123  0
                 }
 124  
             }
 125  0
         }        
 126  0
     }
 127  
 
 128  
     public synchronized void handleSetCounter(
 129  
             HttpServletRequest request,
 130  
             HttpServletResponse response) throws IOException {
 131  
         try {
 132  0
             String instance = request.getParameter("instance");
 133  0
             String name = request.getParameter("counterName");
 134  0
             String stageName = request.getParameter("stageName");
 135  0
             String stringValue = request.getParameter("value");
 136  0
             Long longValue = new Long(stringValue);
 137  
 
 138  0
             if (instance == null || stringValue == null || name == null || stageName == null)
 139  0
                 return;
 140  
 
 141  0
             CounterName fullName = new CounterName(stageName, name);
 142  0
             if (!counters.containsKey(fullName)) {
 143  0
                 counters.put(fullName, new AggregateCounter());
 144  
             }
 145  
 
 146  0
             counters.get(fullName).setValue(instance, longValue);
 147  0
         } catch(Exception e) {
 148  0
             response.sendError(response.SC_NOT_ACCEPTABLE);
 149  0
         }
 150  
 
 151  0
         response.setStatus(response.SC_OK);
 152  0
     }
 153  
 
 154  
     private String getElapsed(Date start) {
 155  0
         long remainingMs = System.currentTimeMillis() - start.getTime();
 156  0
         long hours = remainingMs / 3600000;
 157  0
         remainingMs = remainingMs % 3600000;
 158  0
         long minutes = remainingMs / 60000;
 159  0
         remainingMs = remainingMs % 60000;
 160  0
         long seconds = remainingMs / 1000;
 161  
         
 162  0
         return String.format("%d:%02d:%02d", hours, minutes, seconds);
 163  
     }
 164  
 
 165  
     public synchronized void handleStatus(
 166  
             HttpServletRequest request,
 167  
             HttpServletResponse response) throws IOException {
 168  0
         PrintWriter writer = response.getWriter();
 169  0
         response.setContentType("text/html");
 170  
 
 171  0
         Map<String, StageExecutionStatus> stagesStatus = status.getStageStatus();
 172  0
         boolean isComplete = status.isComplete();
 173  0
         setLastPageLoad(System.currentTimeMillis());
 174  
         
 175  0
         writer.append("<html>");
 176  0
         if (!isComplete) {
 177  0
             handleRefresh(request, writer);
 178  
         }
 179  0
         writer.append("<head>\n");
 180  0
         writer.append("<style type=\"text/css\">\n");
 181  
 
 182  0
         writer.append("table { border-collapse: collapse; }\n");
 183  0
         writer.append("tr.blocked td { background: #BBB; }\n");
 184  0
         writer.append("tr.running td { background: #8D8; }\n");
 185  0
         writer.append("tr.complete td { background: #5A5; }\n");
 186  0
         writer.append("td { padding: 5px; }\n");
 187  0
         writer.append("td.right { text-align: right; }\n");
 188  0
         writer.append("</style>");
 189  0
         writer.append("</head>\n");
 190  0
         writer.append("<body>\n");
 191  0
         writer.append("<font size=\"-3\">Refresh: <a href=\"/?refresh=1\">1 second</a> " +
 192  
                                "<a href=\"/?refresh=5\">5 seconds</a> " +
 193  
                                "<a href=\"/?refresh=15\">15 seconds</a> " +
 194  
                                "<a href=\"/?refresh=60\">1 minute</a> " +
 195  
                                "<a href=\"/?refresh=-1\">never</a></font><br/>" );
 196  
 
 197  
         // The first table contains format information:
 198  0
         writer.append("<table>");
 199  0
         writer.append(String.format("<tr><td>Start</td><td>%s</td></tr>\n",
 200  
                 status.getStartDate().toString()));
 201  0
         writer.append(String.format("<tr><td>Elapsed</td><td>%s</td></tr>\n",
 202  
                 getElapsed(status.getStartDate())));
 203  0
         writer.append(String.format("<tr><td>Max memory</td><td>%dM</td></tr>\n",
 204  
                 status.getMaxMemory()/1048576));
 205  0
         writer.append(String.format("<tr><td>Free memory</td><td>%dM</td></tr>\n",
 206  
                 status.getFreeMemory()/1048576));
 207  0
         if (isComplete) {
 208  0
             writer.append("<tr><td><b>Indexing Complete</b></td><td></td></tr>");
 209  
         }
 210  0
         writer.append("</table>");
 211  
         // Two-column table, stage data on left, counters on right
 212  0
         writer.append("<table><tr><td>\n");
 213  0
         writer.append("<table>\n");
 214  0
         writer.append(String.format("<tr><th>%s</th><th>%s</th><th>%s</th><th>%s</th><th>%s</th></tr>\n",
 215  
                                     "Stage", "Blocked", "Queued", "Running", "Completed"));
 216  0
         for (Entry<String, StageExecutionStatus> entry : stagesStatus.entrySet()) {
 217  0
             StageExecutionStatus stageStatus = entry.getValue();
 218  
 
 219  0
             if (stageStatus.getBlockedInstances() > 0) {
 220  0
                 writer.append("<tr class=\"blocked\">");
 221  0
             } else if (stageStatus.getQueuedInstances() + stageStatus.getRunningInstances() > 0) {
 222  0
                 writer.append("<tr class=\"running\">");
 223  
             } else {
 224  0
                 writer.append("<tr class=\"complete\">");
 225  
             }
 226  
 
 227  0
             writer.append("<td>" + entry.getKey() + "</td>");
 228  0
             writer.append("<td class=\"right\">" + stageStatus.getBlockedInstances() + "</td>");
 229  0
             writer.append("<td class=\"right\">" + stageStatus.getQueuedInstances() + "</td>");
 230  0
             writer.append("<td class=\"right\">" + stageStatus.getRunningInstances() + "</td>");
 231  0
             writer.append("<td class=\"right\">" + stageStatus.getCompletedInstances() + "</td>");
 232  0
             writer.append("</tr>");
 233  0
         }
 234  0
         writer.append("</table>"); // end stage table
 235  
 
 236  
         // Now, print counter data:
 237  0
         writer.append("</td><td>\n");
 238  0
         writer.append("<table>");
 239  0
         writer.append("<tr><th>Stage</th><th>Counter</th><th>Value</th></tr>");
 240  0
         for (Entry<CounterName, AggregateCounter> entry : this.counters.entrySet()) {
 241  0
             if (entry.getValue().getValue() == 0) continue;
 242  0
             String stageName = entry.getKey().getStageName();
 243  0
             StageExecutionStatus stageStatus = stagesStatus.get(stageName);
 244  
 
 245  0
             if (stageStatus != null &&
 246  
                 stageStatus.getRunningInstances() + stageStatus.getQueuedInstances() > 0) {
 247  0
                 writer.append("<tr class=\"running\">");
 248  
             } else {
 249  0
                 writer.append("<tr>");
 250  
             }
 251  0
             writer.append("<td>" + entry.getKey().getStageName() + "</td>");
 252  0
             writer.append("<td>" + entry.getKey().getCounterName() + "</td>");
 253  0
             writer.append("<td>" + entry.getValue().getValue() + "</td>");
 254  0
             writer.append("</tr>");
 255  0
         }
 256  
 
 257  0
         writer.append("</table>"); // end counter table
 258  0
         writer.append("</td></tr></table>\n"); // end two-column table
 259  0
         writer.append("</body>");
 260  0
         writer.append("</html>");
 261  0
         writer.close();
 262  
 
 263  0
         if (isComplete) {
 264  0
             setLastPageLoad(0);
 265  0
             notifyAll();
 266  
         }
 267  0
     }
 268  
     
 269  
     public synchronized void handle(
 270  
             String target,
 271  
             HttpServletRequest request,
 272  
             HttpServletResponse response,
 273  
             int dispatch)
 274  
             throws IOException, ServletException {
 275  0
         if (request.getPathInfo().equals("/setcounter")) {
 276  0
             handleSetCounter(request, response);
 277  
         } else {
 278  0
             handleStatus(request, response);
 279  
         }
 280  0
     }
 281  
 }