View Javadoc

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      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      long lastPageLoad = 0;
33  
34      public static class CounterName implements Comparable<CounterName> {
35          String counterName;
36          String stageName;
37  
38          public CounterName(String stageName, String counterName) {
39              this.stageName = stageName;
40              this.counterName = counterName;
41          }
42  
43          public String getCounterName() { return counterName; }
44          public String getStageName() { return stageName; }
45          public int compareTo(CounterName other) {
46              int result = stageName.compareTo(other.stageName);
47              if (result != 0) return result;
48              return counterName.compareTo(other.counterName);
49          }
50      }
51  
52      private void handleRefresh(HttpServletRequest request, PrintWriter writer) {
53          int refresh = 5;
54          if (request.getParameter("refresh") != null) {
55              try {
56                  refresh = Integer.parseInt(request.getParameter("refresh"));
57              } catch (Exception e) {
58                  // do nothing
59              }
60          }
61          if (refresh > 0) {
62              writer.append(String.format("<meta http-equiv=\"refresh\" content=\"%d\" />", refresh));
63          }
64      }
65  
66      /***
67       * An aggregate counter holds counter data from lots of instances and
68       * returns the sum.
69       */
70      class AggregateCounter {
71          /// Returns the total counter value from all instances.
72          public synchronized long getValue() {
73              return total;
74          }
75  
76          /// Updates the counter value for a particular instance.
77          public synchronized void setValue(String instance, long value) {
78              long oldValue = 0;
79              if (instances.containsKey(instance))
80                  oldValue = instances.get(instance);
81              long delta = value - oldValue;
82              total += delta;
83              instances.put(instance, value);
84          }
85  
86          HashMap<String, Long> instances = new HashMap();
87          long total = 0;
88      }
89  
90      public MasterWebHandler(JobExecutionStatus status) {
91          this.status = status;
92      }
93  
94      public synchronized void setLastPageLoad(long value) {
95          lastPageLoad = value;
96      }
97  
98      public synchronized long getLastPageLoad() {
99          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         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             long timeDelta = System.currentTimeMillis() - getLastPageLoad();
118             if (timeDelta <= 15 * 1000) {
119                 try {
120                     this.wait(15 * 1000);
121                 } catch (InterruptedException e) {
122                     // do nothing
123                 }
124             }
125         }        
126     }
127 
128     public synchronized void handleSetCounter(
129             HttpServletRequest request,
130             HttpServletResponse response) throws IOException {
131         try {
132             String instance = request.getParameter("instance");
133             String name = request.getParameter("counterName");
134             String stageName = request.getParameter("stageName");
135             String stringValue = request.getParameter("value");
136             Long longValue = new Long(stringValue);
137 
138             if (instance == null || stringValue == null || name == null || stageName == null)
139                 return;
140 
141             CounterName fullName = new CounterName(stageName, name);
142             if (!counters.containsKey(fullName)) {
143                 counters.put(fullName, new AggregateCounter());
144             }
145 
146             counters.get(fullName).setValue(instance, longValue);
147         } catch(Exception e) {
148             response.sendError(response.SC_NOT_ACCEPTABLE);
149         }
150 
151         response.setStatus(response.SC_OK);
152     }
153 
154     private String getElapsed(Date start) {
155         long remainingMs = System.currentTimeMillis() - start.getTime();
156         long hours = remainingMs / 3600000;
157         remainingMs = remainingMs % 3600000;
158         long minutes = remainingMs / 60000;
159         remainingMs = remainingMs % 60000;
160         long seconds = remainingMs / 1000;
161         
162         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         PrintWriter writer = response.getWriter();
169         response.setContentType("text/html");
170 
171         Map<String, StageExecutionStatus> stagesStatus = status.getStageStatus();
172         boolean isComplete = status.isComplete();
173         setLastPageLoad(System.currentTimeMillis());
174         
175         writer.append("<html>");
176         if (!isComplete) {
177             handleRefresh(request, writer);
178         }
179         writer.append("<head>\n");
180         writer.append("<style type=\"text/css\">\n");
181 
182         writer.append("table { border-collapse: collapse; }\n");
183         writer.append("tr.blocked td { background: #BBB; }\n");
184         writer.append("tr.running td { background: #8D8; }\n");
185         writer.append("tr.complete td { background: #5A5; }\n");
186         writer.append("td { padding: 5px; }\n");
187         writer.append("td.right { text-align: right; }\n");
188         writer.append("</style>");
189         writer.append("</head>\n");
190         writer.append("<body>\n");
191         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         writer.append("<table>");
199         writer.append(String.format("<tr><td>Start</td><td>%s</td></tr>\n",
200                 status.getStartDate().toString()));
201         writer.append(String.format("<tr><td>Elapsed</td><td>%s</td></tr>\n",
202                 getElapsed(status.getStartDate())));
203         writer.append(String.format("<tr><td>Max memory</td><td>%dM</td></tr>\n",
204                 status.getMaxMemory()/1048576));
205         writer.append(String.format("<tr><td>Free memory</td><td>%dM</td></tr>\n",
206                 status.getFreeMemory()/1048576));
207         if (isComplete) {
208             writer.append("<tr><td><b>Indexing Complete</b></td><td></td></tr>");
209         }
210         writer.append("</table>");
211         // Two-column table, stage data on left, counters on right
212         writer.append("<table><tr><td>\n");
213         writer.append("<table>\n");
214         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         for (Entry<String, StageExecutionStatus> entry : stagesStatus.entrySet()) {
217             StageExecutionStatus stageStatus = entry.getValue();
218 
219             if (stageStatus.getBlockedInstances() > 0) {
220                 writer.append("<tr class=\"blocked\">");
221             } else if (stageStatus.getQueuedInstances() + stageStatus.getRunningInstances() > 0) {
222                 writer.append("<tr class=\"running\">");
223             } else {
224                 writer.append("<tr class=\"complete\">");
225             }
226 
227             writer.append("<td>" + entry.getKey() + "</td>");
228             writer.append("<td class=\"right\">" + stageStatus.getBlockedInstances() + "</td>");
229             writer.append("<td class=\"right\">" + stageStatus.getQueuedInstances() + "</td>");
230             writer.append("<td class=\"right\">" + stageStatus.getRunningInstances() + "</td>");
231             writer.append("<td class=\"right\">" + stageStatus.getCompletedInstances() + "</td>");
232             writer.append("</tr>");
233         }
234         writer.append("</table>"); // end stage table
235 
236         // Now, print counter data:
237         writer.append("</td><td>\n");
238         writer.append("<table>");
239         writer.append("<tr><th>Stage</th><th>Counter</th><th>Value</th></tr>");
240         for (Entry<CounterName, AggregateCounter> entry : this.counters.entrySet()) {
241             if (entry.getValue().getValue() == 0) continue;
242             String stageName = entry.getKey().getStageName();
243             StageExecutionStatus stageStatus = stagesStatus.get(stageName);
244 
245             if (stageStatus != null &&
246                 stageStatus.getRunningInstances() + stageStatus.getQueuedInstances() > 0) {
247                 writer.append("<tr class=\"running\">");
248             } else {
249                 writer.append("<tr>");
250             }
251             writer.append("<td>" + entry.getKey().getStageName() + "</td>");
252             writer.append("<td>" + entry.getKey().getCounterName() + "</td>");
253             writer.append("<td>" + entry.getValue().getValue() + "</td>");
254             writer.append("</tr>");
255         }
256 
257         writer.append("</table>"); // end counter table
258         writer.append("</td></tr></table>\n"); // end two-column table
259         writer.append("</body>");
260         writer.append("</html>");
261         writer.close();
262 
263         if (isComplete) {
264             setLastPageLoad(0);
265             notifyAll();
266         }
267     }
268     
269     public synchronized void handle(
270             String target,
271             HttpServletRequest request,
272             HttpServletResponse response,
273             int dispatch)
274             throws IOException, ServletException {
275         if (request.getPathInfo().equals("/setcounter")) {
276             handleSetCounter(request, response);
277         } else {
278             handleStatus(request, response);
279         }
280     }
281 }