View Javadoc
1   /* 
2    * Licensed under the Apache License, Version 2.0 (the "License");
3    * you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    * http://www.apache.org/licenses/LICENSE-2.0
7    *
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS,
10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   * See the License for the specific language governing permissions and
12   * limitations under the License.
13   *
14   */
15  
16  package org.esigate.server;
17  
18  import java.io.IOException;
19  import java.io.Writer;
20  import java.lang.management.ManagementFactory;
21  import java.util.Map;
22  import java.util.Map.Entry;
23  import java.util.TreeMap;
24  
25  import javax.servlet.ServletException;
26  import javax.servlet.http.HttpServletRequest;
27  import javax.servlet.http.HttpServletResponse;
28  
29  import org.eclipse.jetty.server.Request;
30  import org.eclipse.jetty.server.Server;
31  import org.eclipse.jetty.server.handler.AbstractHandler;
32  
33  import com.codahale.metrics.Counter;
34  import com.codahale.metrics.Gauge;
35  import com.codahale.metrics.Meter;
36  import com.codahale.metrics.MetricRegistry;
37  import com.codahale.metrics.Timer;
38  
39  /**
40   * Handle commands to the control port. Work in progress.
41   * 
42   * <p>
43   * Commands
44   * <ul>
45   * <li>POST /shutdown</li>
46   * <li>POST /status</li>
47   * </ul>
48   * 
49   * @author Nicolas Richeton
50   * 
51   */
52  public class ControlHandler extends AbstractHandler {
53      private static final String PREFIX_CONTEXT = "org.eclipse.jetty.webapp.WebAppContext.main.";
54      private static final String PREFIX_THREAD_POOL = "org.eclipse.jetty.util.thread.QueuedThreadPool.esigate.";
55      /**
56       * Human-readable status
57       */
58      private static final String URL_STATUS = "/server-status";
59      /**
60       * Machine-readable status.
61       * 
62       * <p>
63       * Sample :
64       * 
65       * <pre>
66       * Total Accesses: 157678
67       * Total kBytes: 176421
68       * CPULoad: .0190435
69       * Uptime: 2214828
70       * ReqPerSec: .071192
71       * BytesPerSec: 81.5662
72       * BytesPerReq: 1145.72
73       * BusyWorkers: 1
74       * IdleWorkers: 4
75       * </pre>
76       */
77      private final MetricRegistry registry;
78  
79      /**
80       * Control handler for administration tasks.
81       * 
82       * @param registry
83       *            metrics registry.
84       */
85      public ControlHandler(MetricRegistry registry) {
86          this.registry = registry;
87      }
88  
89      private static boolean fromControlConnection(Request serverRequest) {
90          return EsigateServer.getControlPort() == serverRequest.getLocalPort();
91      }
92  
93      /**
94       * Perform shutdown.
95       * 
96       * @param port
97       *            control handler port.
98       */
99      public static void shutdown(int port) {
100         Http.doPOST("http://127.0.0.1:" + port + "/shutdown");
101     }
102 
103     /**
104      * Display status.
105      * 
106      * @param port
107      *            control handler port.
108      */
109     public static void status(int port) {
110         Http.doGET("http://127.0.0.1:" + port + URL_STATUS);
111     }
112 
113     @Override
114     public void handle(String target, Request serverRequest, HttpServletRequest request, HttpServletResponse response)
115             throws IOException, ServletException {
116 
117         if (fromControlConnection(serverRequest)) {
118             serverRequest.setHandled(true);
119 
120             switch (target) {
121 
122             case "/shutdown":
123                 if ("POST".equals(serverRequest.getMethod())) {
124                     response.setStatus(HttpServletResponse.SC_OK);
125                     stopServer();
126                 }
127                 break;
128 
129             case URL_STATUS:
130                 if ("GET".equals(serverRequest.getMethod())) {
131 
132                     if (request.getParameter("auto") != null) {
133                         response.setStatus(HttpServletResponse.SC_OK);
134                         try (Writer sos = response.getWriter()) {
135                             Map<String, Object> status = getServerStatus();
136                             for (String key : status.keySet()) {
137                                 sos.append(key).append(": ").append(String.valueOf(status.get(key))).append("\n");
138                             }
139                         }
140 
141                     } else {
142                         response.setStatus(HttpServletResponse.SC_OK);
143                         try (Writer sos = response.getWriter()) {
144                             sos.append("Esigate Server Status\n");
145                             Map<String, Object> status = getServerStatus();
146                             for (String key : status.keySet()) {
147                                 sos.append(key).append(": ").append(String.valueOf(status.get(key))).append("\n");
148                             }
149                         }
150                     }
151 
152                 }
153                 break;
154 
155             default:
156                 response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
157                 break;
158             }
159 
160         }
161 
162     }
163 
164     private Map<String, Object> getServerStatus() {
165         Map<String, Object> result = new TreeMap<>();
166 
167         Map<String, Counter> counters = this.registry.getCounters();
168         for (Entry<String, Counter> c : counters.entrySet()) {
169             result.put(cleanupStatusKey(c.getKey()), String.valueOf(c.getValue().getCount()));
170         }
171 
172         Map<String, Meter> meters = this.registry.getMeters();
173         for (Entry<String, Meter> c : meters.entrySet()) {
174             result.put(cleanupStatusKey(c.getKey()), String.valueOf(c.getValue().getCount()));
175             result.put(cleanupStatusKey(c.getKey()) + "PerSec", String.valueOf(c.getValue().getOneMinuteRate()));
176         }
177 
178         Map<String, Gauge> gauges = this.registry.getGauges();
179         for (Entry<String, Gauge> c : gauges.entrySet()) {
180             result.put(cleanupStatusKey(c.getKey()), String.valueOf(c.getValue().getValue()));
181         }
182 
183         Map<String, Timer> timers = this.registry.getTimers();
184         for (Entry<String, Timer> c : timers.entrySet()) {
185             result.put(cleanupStatusKey(c.getKey()), String.valueOf(c.getValue().getOneMinuteRate()));
186         }
187 
188         // Get total accesses
189         Long accesses =
190                 meters.get(PREFIX_CONTEXT + "1xx-responses").getCount()
191                         + meters.get(PREFIX_CONTEXT + "2xx-responses").getCount()
192                         + meters.get(PREFIX_CONTEXT + "3xx-responses").getCount()
193                         + meters.get(PREFIX_CONTEXT + "4xx-responses").getCount()
194                         + meters.get(PREFIX_CONTEXT + "5xx-responses").getCount();
195         result.put("Total Accesses", accesses);
196 
197         // Get ReqPerSec
198         Double reqPerSec =
199                 meters.get(PREFIX_CONTEXT + "1xx-responses").getOneMinuteRate()
200                         + meters.get(PREFIX_CONTEXT + "2xx-responses").getOneMinuteRate()
201                         + meters.get(PREFIX_CONTEXT + "3xx-responses").getOneMinuteRate()
202                         + meters.get(PREFIX_CONTEXT + "4xx-responses").getOneMinuteRate()
203                         + meters.get(PREFIX_CONTEXT + "5xx-responses").getOneMinuteRate();
204         result.put("ReqPerSec", reqPerSec);
205 
206         // Get uptime
207         result.put("Uptime", ManagementFactory.getRuntimeMXBean().getUptime());
208 
209         // Get CPULoad
210         Double cpuLoad = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage();
211         if (cpuLoad >= 0d) {
212             result.put("CPULoad", cpuLoad);
213         }
214 
215         return result;
216 
217     }
218 
219     /**
220      * Remove unnecessary prefix from Metrics meters id.
221      * 
222      * @param s
223      * @return
224      */
225     private static String cleanupStatusKey(String s) {
226         String result = s;
227         if (s.startsWith(PREFIX_CONTEXT)) {
228             result = s.substring(PREFIX_CONTEXT.length());
229 
230         }
231 
232         if (s.startsWith(PREFIX_THREAD_POOL)) {
233             result = s.substring(PREFIX_THREAD_POOL.length());
234         }
235 
236         return result;
237     }
238 
239     /**
240      * Start a new thread to shutdown the server
241      */
242     private void stopServer() {
243         // Get current server
244         final Server targetServer = this.getServer();
245 
246         // Start a new thread in order to escape the destruction of this Handler
247         // during the stop process.
248         new Thread() {
249             @Override
250             public void run() {
251                 try {
252                     targetServer.stop();
253                 } catch (Exception e) {
254                     // ignore
255                 }
256             }
257         }.start();
258 
259     }
260 }