1
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
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
72 public synchronized long getValue() {
73 return total;
74 }
75
76
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
115
116
117 long timeDelta = System.currentTimeMillis() - getLastPageLoad();
118 if (timeDelta <= 15 * 1000) {
119 try {
120 this.wait(15 * 1000);
121 } catch (InterruptedException e) {
122
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
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
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>");
235
236
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>");
258 writer.append("</td></tr></table>\n");
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 }