/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.esigate.server; import java.io.IOException; import java.io.Writer; import java.lang.management.ManagementFactory; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; import com.codahale.metrics.Counter; import com.codahale.metrics.Gauge; import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; /** * Handle commands to the control port. Work in progress. * * <p> * Commands * <ul> * <li>POST /shutdown</li> * <li>POST /status</li> * </ul> * * @author Nicolas Richeton * */ public class ControlHandler extends AbstractHandler { private static final String PREFIX_CONTEXT = "org.eclipse.jetty.webapp.WebAppContext.main."; private static final String PREFIX_THREAD_POOL = "org.eclipse.jetty.util.thread.QueuedThreadPool.esigate."; /** * Human-readable status */ private static final String URL_STATUS = "/server-status"; /** * Machine-readable status. * * <p> * Sample : * * <pre> * Total Accesses: 157678 * Total kBytes: 176421 * CPULoad: .0190435 * Uptime: 2214828 * ReqPerSec: .071192 * BytesPerSec: 81.5662 * BytesPerReq: 1145.72 * BusyWorkers: 1 * IdleWorkers: 4 * </pre> */ private final MetricRegistry registry; /** * Control handler for administration tasks. * * @param registry * metrics registry. */ public ControlHandler(MetricRegistry registry) { this.registry = registry; } private static boolean fromControlConnection(Request serverRequest) { return EsigateServer.getControlPort() == serverRequest.getLocalPort(); } /** * Perform shutdown. * * @param port * control handler port. */ public static void shutdown(int port) { Http.doPOST("http://127.0.0.1:" + port + "/shutdown"); } /** * Display status. * * @param port * control handler port. */ public static void status(int port) { Http.doGET("http://127.0.0.1:" + port + URL_STATUS); } @Override public void handle(String target, Request serverRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { if (fromControlConnection(serverRequest)) { serverRequest.setHandled(true); switch (target) { case "/shutdown": if ("POST".equals(serverRequest.getMethod())) { response.setStatus(HttpServletResponse.SC_OK); stopServer(); } break; case URL_STATUS: if ("GET".equals(serverRequest.getMethod())) { if (request.getParameter("auto") != null) { response.setStatus(HttpServletResponse.SC_OK); try (Writer sos = response.getWriter()) { Map<String, Object> status = getServerStatus(); for (String key : status.keySet()) { sos.append(key).append(": ").append(String.valueOf(status.get(key))).append("\n"); } } } else { response.setStatus(HttpServletResponse.SC_OK); try (Writer sos = response.getWriter()) { sos.append("Esigate Server Status\n"); Map<String, Object> status = getServerStatus(); for (String key : status.keySet()) { sos.append(key).append(": ").append(String.valueOf(status.get(key))).append("\n"); } } } } break; default: response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE); break; } } } private Map<String, Object> getServerStatus() { Map<String, Object> result = new TreeMap<>(); Map<String, Counter> counters = this.registry.getCounters(); for (Entry<String, Counter> c : counters.entrySet()) { result.put(cleanupStatusKey(c.getKey()), String.valueOf(c.getValue().getCount())); } Map<String, Meter> meters = this.registry.getMeters(); for (Entry<String, Meter> c : meters.entrySet()) { result.put(cleanupStatusKey(c.getKey()), String.valueOf(c.getValue().getCount())); result.put(cleanupStatusKey(c.getKey()) + "PerSec", String.valueOf(c.getValue().getOneMinuteRate())); } Map<String, Gauge> gauges = this.registry.getGauges(); for (Entry<String, Gauge> c : gauges.entrySet()) { result.put(cleanupStatusKey(c.getKey()), String.valueOf(c.getValue().getValue())); } Map<String, Timer> timers = this.registry.getTimers(); for (Entry<String, Timer> c : timers.entrySet()) { result.put(cleanupStatusKey(c.getKey()), String.valueOf(c.getValue().getOneMinuteRate())); } // Get total accesses Long accesses = meters.get(PREFIX_CONTEXT + "1xx-responses").getCount() + meters.get(PREFIX_CONTEXT + "2xx-responses").getCount() + meters.get(PREFIX_CONTEXT + "3xx-responses").getCount() + meters.get(PREFIX_CONTEXT + "4xx-responses").getCount() + meters.get(PREFIX_CONTEXT + "5xx-responses").getCount(); result.put("Total Accesses", accesses); // Get ReqPerSec Double reqPerSec = meters.get(PREFIX_CONTEXT + "1xx-responses").getOneMinuteRate() + meters.get(PREFIX_CONTEXT + "2xx-responses").getOneMinuteRate() + meters.get(PREFIX_CONTEXT + "3xx-responses").getOneMinuteRate() + meters.get(PREFIX_CONTEXT + "4xx-responses").getOneMinuteRate() + meters.get(PREFIX_CONTEXT + "5xx-responses").getOneMinuteRate(); result.put("ReqPerSec", reqPerSec); // Get uptime result.put("Uptime", ManagementFactory.getRuntimeMXBean().getUptime()); // Get CPULoad Double cpuLoad = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage(); if (cpuLoad >= 0d) { result.put("CPULoad", cpuLoad); } return result; } /** * Remove unnecessary prefix from Metrics meters id. * * @param s * @return */ private static String cleanupStatusKey(String s) { String result = s; if (s.startsWith(PREFIX_CONTEXT)) { result = s.substring(PREFIX_CONTEXT.length()); } if (s.startsWith(PREFIX_THREAD_POOL)) { result = s.substring(PREFIX_THREAD_POOL.length()); } return result; } /** * Start a new thread to shutdown the server */ private void stopServer() { // Get current server final Server targetServer = this.getServer(); // Start a new thread in order to escape the destruction of this Handler // during the stop process. new Thread() { @Override public void run() { try { targetServer.stop(); } catch (Exception e) { // ignore } } }.start(); } }