/* * #%L * Wisdom-Framework * %% * Copyright (C) 2013 - 2014 Wisdom Framework * %% * 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. * #L% */ package org.wisdom.monitor.extensions.dashboard; import com.codahale.metrics.*; import com.codahale.metrics.graphite.Graphite; import com.codahale.metrics.graphite.GraphiteReporter; import com.codahale.metrics.jvm.BufferPoolMetricSet; import com.codahale.metrics.jvm.GarbageCollectorMetricSet; import com.codahale.metrics.jvm.MemoryUsageGaugeSet; import com.codahale.metrics.jvm.ThreadStatesGaugeSet; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.ImmutableMap; import org.apache.felix.ipojo.annotations.Context; import org.apache.felix.ipojo.annotations.Invalidate; import org.apache.felix.ipojo.annotations.Requires; import org.apache.felix.ipojo.annotations.Validate; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import org.wisdom.api.DefaultController; import org.wisdom.api.annotations.Controller; import org.wisdom.api.annotations.Path; import org.wisdom.api.annotations.Route; import org.wisdom.api.annotations.View; import org.wisdom.api.concurrent.ManagedScheduledExecutorService; import org.wisdom.api.configuration.ApplicationConfiguration; import org.wisdom.api.content.Json; import org.wisdom.api.http.HttpMethod; import org.wisdom.api.http.Result; import org.wisdom.api.http.websockets.Publisher; import org.wisdom.api.security.Authenticated; import org.wisdom.api.templates.Template; import org.wisdom.monitor.service.HealthCheck; import org.wisdom.monitor.service.MonitorExtension; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.net.InetSocketAddress; import java.util.List; import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import static org.wisdom.monitor.extensions.dashboard.HealthState.ko; /** * Monitor extension handling the main dashboard. */ @Controller @Path("/monitor/dashboard") @Authenticated("Monitor-Authenticator") public class DashboardExtension extends DefaultController implements MonitorExtension { @Requires Publisher publisher; @Requires ApplicationConfiguration configuration; @Requires Json json; @Requires(filter = "(name=" + ManagedScheduledExecutorService.SYSTEM + ")", proxy = false) protected ScheduledExecutorService scheduler; @Requires(specification = HealthCheck.class, optional = true) List<HealthCheck> healthChecks; @View("monitor/dashboard") Template monitor; @Context BundleContext bc; final MetricRegistry registry; private ScheduledFuture task; private HttpMetricFilter httpMetricFilter; private ServiceRegistration<MetricRegistry> reg; /** * Creates the instance of dashboard extension. */ public DashboardExtension() { this.registry = new MetricRegistry(); } /** * Starts the dashboard. */ @Validate public void start() { logger().info("Registering JVM metrics"); registry.register("jvm.memory", new MemoryUsageGaugeSet()); registry.register("jvm.garbage", new GarbageCollectorMetricSet()); registry.register("jvm.threads", new ThreadStatesGaugeSet()); registry.register("jvm.buffers", new BufferPoolMetricSet(ManagementFactory.getPlatformMBeanServer())); registry.register("jvm.cpu", new CpuGaugeSet()); registry.register("jvm.runtime", new RuntimeGaugeSet()); if (configuration.getBooleanWithDefault("monitor.http.enabled", true)) { logger().info("Registering HTTP metrics"); this.httpMetricFilter = new HttpMetricFilter(bc, configuration, registry); httpMetricFilter.start(); } if (configuration.getBooleanWithDefault("monitor.jmx.enabled", true)) { logger().info("Initializing Metrics JMX reporting"); final JmxReporter jmxReporter = JmxReporter.forRegistry(registry).build(); jmxReporter.start(); } if (configuration.getBooleanWithDefault("monitor.graphite.enabled", false)) { logger().info("Initializing Metrics Graphite reporting"); String graphiteHost = configuration.getOrDie("monitor.graphite.host"); int graphitePort = configuration.getIntegerOrDie("monitor.graphite.port"); Graphite graphite = new Graphite(new InetSocketAddress(graphiteHost, graphitePort)); GraphiteReporter graphiteReporter = GraphiteReporter.forRegistry(registry) .convertRatesTo(TimeUnit.SECONDS) .convertDurationsTo(TimeUnit.MILLISECONDS) .build(graphite); graphiteReporter.start(1, TimeUnit.MINUTES); } logger().info("Registering the metric registry as service"); reg = bc.registerService(MetricRegistry.class, registry, null); task = scheduler.scheduleAtFixedRate(new Runnable() { /** * Sends updated data to the websocket. */ public void run() { publisher.publish("/monitor/update", json.toJson(getData())); } }, 0, 10, TimeUnit.SECONDS); } /** * Sends the current metrics. * * @return the metrics. */ @Route(method = HttpMethod.GET, uri = "/metrics") public Result metrics() { return ok(getData()).json(); } /** * Build an immutable map containing the current metrics. * * @return the current metrics. */ private ImmutableMap<String, ?> getData() { long active = 0; Counter counter = registry.counter("http.activeRequests"); if (counter != null) { active = counter.getCount(); } return ImmutableMap.<String, Object>builder() .put("gauges", registry.getGauges()) .put("activeRequests", active) .put("timers", registry.getTimers()) .put("counters", registry.getCounters()) .put("meters", registry.getMeters()) .put("histograms", registry.getHistograms()) .put("health", getHealth()) .build(); } /** * @return the map name - heath check of all health sensors. */ private SortedMap<String, HealthState> getHealth() { SortedMap<String, HealthState> map = new TreeMap<>(); for (HealthCheck hc : healthChecks) { try { if (hc.check()) { map.put(hc.name(), HealthState.ok()); } else { map.put(hc.name(), ko()); } } catch (Exception e) { //NOSONAR map.put(hc.name(), ko(e)); } } return map; } /** * @return information about threads. */ @Route(method = HttpMethod.GET, uri = "/threads") public Result threads() { ArrayNode array = json.newArray(); ThreadMXBean bean = ManagementFactory.getThreadMXBean(); for (long id : bean.getAllThreadIds()) { ObjectNode node = json.newObject(); ThreadInfo ti = bean.getThreadInfo(id, 10); node .put("threadName", ti.getThreadName()) .put("threadId", ti.getThreadId()) .put("blockedTime", ti.getBlockedTime()) .put("blockedCount", ti.getBlockedCount()) .put("lockName", ti.getLockName()) .put("waitedTime", ti.getWaitedTime()) .put("waitedCount", ti.getWaitedCount()) .put("threadState", ti.getThreadState().toString()) .put("stack", stack(ti.getStackTrace())); array.add(node); } return ok(array); } private String stack(StackTraceElement[] stackTrace) { StringBuilder stack = new StringBuilder(); for (StackTraceElement element : stackTrace) { if (stack.length() != 0) { stack.append('\n'); } stack .append(element.getClassName()).append(".").append(element.getMethodName()) .append(" (").append(element.getFileName()).append(':').append(element.getLineNumber()).append(')'); } return stack.toString(); } /** * Stops the dashboard. * It stops the web socket publication, and the sensors. */ @Invalidate public void stop() { if (reg != null) { reg.unregister(); reg = null; } if (task != null && !task.isCancelled()) { task.cancel(true); } if (httpMetricFilter != null) { httpMetricFilter.stop(); } registry.removeMatching(new MetricFilter() { /** * Returns true to remove all metrics. * @param s the name * @param metric the metric * @return {@code true} */ public boolean matches(String s, Metric metric) { return true; } }); } /** * @return the dashboard page. */ @Route(method = HttpMethod.GET, uri = "") public Result index() { return ok(render(monitor)); } /** * @return "Dashboard" */ @Override public String label() { return "Dashboard"; } /** * @return the dashboard page url. */ @Override public String url() { return "/monitor/dashboard"; } /** * @return the "root" category. */ @Override public String category() { return "root"; } }