/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.jooby.metrics; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.TimeUnit; import org.jooby.Request; import org.jooby.Response; import org.jooby.Route.Handler; import org.jooby.Status; import com.codahale.metrics.Counter; import com.codahale.metrics.Gauge; import com.codahale.metrics.Histogram; import com.codahale.metrics.Meter; import com.codahale.metrics.Metered; import com.codahale.metrics.Metric; import com.codahale.metrics.MetricFilter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Sampling; import com.codahale.metrics.Snapshot; import com.codahale.metrics.Timer; /** * Produces a: * <ul> * <li>501: if the registry is empty (no metrics)</li> * <li>200: with metrics information</li> * </ul> * * @author edgar * @since 0.13.0 */ public class MetricHandler implements Handler { private static final String CACHE_CONTROL = "Cache-Control"; private static final String NO_CACHE = "must-revalidate,no-cache,no-store"; @Override public void handle(final Request req, final Response rsp) throws Throwable { MetricRegistry registry = req.require(MetricRegistry.class); Map<String, Metric> allmetrics = registry.getMetrics(); if (allmetrics.isEmpty()) { rsp.status(Status.NOT_IMPLEMENTED) .header(CACHE_CONTROL, NO_CACHE) .send(allmetrics); } else { /** params & filters . */ String type = req.param("type").value("*"); MetricFilter filter = req.param("name").toOptional() .<MetricFilter> map(name -> (n, m) -> n.startsWith(name)) .orElse(MetricFilter.ALL); TimeUnit unit = TimeUnit.valueOf(req.param("unit").value("seconds").toUpperCase()); String rateUnitLabel = calculateRateUnit(unit, "ops"); double rateFactor = unit.toSeconds(1); String durationUnitLabel = unit.toString().toLowerCase(Locale.US); double durationFactor = 1.0 / unit.toNanos(1); boolean showSamples = req.param("showSamples").booleanValue(false); /** Dump metrics. */ Map<String, Object> metrics = new TreeMap<>(); Map<String, Object> counters = counters(registry.getCounters(filter)); if (counters.size() > 0) { metrics.put("counters", counters); } Map<String, Object> gauges = gauges(registry.getGauges(filter)); if (gauges.size() > 0) { metrics.put("gauges", gauges); } Map<String, Object> histograms = histograms(registry.getHistograms(filter), showSamples); if (histograms.size() > 0) { metrics.put("histograms", histograms); } Map<String, Object> meters = meters(registry.getMeters(filter), rateUnitLabel, rateFactor, durationUnitLabel, durationFactor); if (meters.size() > 0) { metrics.put("meters", meters); } Map<String, Object> timers = timers(registry.getTimers(filter), rateUnitLabel, rateFactor, durationUnitLabel, durationFactor, showSamples); if (timers.size() > 0) { metrics.put("timers", timers); } // send rsp.status(Status.OK) .header(CACHE_CONTROL, NO_CACHE) .send(metrics.getOrDefault(type, metrics)); } } private static Map<String, Object> timers(final SortedMap<String, Timer> timers, final String rateUnit, final double rateFactor, final String durationUnit, final double durationFactor, final boolean showSamples) { Map<String, Object> result = new TreeMap<>(); timers.forEach((name, timer) -> { result.put(name, timer(timer, rateUnit, rateFactor, durationUnit, durationFactor, showSamples)); }); return result; } private static Map<String, Object> timer(final Timer timer, final String rateUnit, final double rateFactor, final String durationUnit, final double durationFactor, final boolean showSamples) { Map<String, Object> result = meter(timer, rateUnit, rateFactor, durationUnit, durationFactor); result.putAll(snapshot(timer, durationFactor, showSamples)); return result; } private static Map<String, Object> snapshot(final Sampling sampling, final double durationFactor, final boolean showSamples) { Map<String, Object> result = new TreeMap<>(); final Snapshot snapshot = sampling.getSnapshot(); result.put("max", snapshot.getMax() * durationFactor); result.put("mean", snapshot.getMean() * durationFactor); result.put("min", snapshot.getMin() * durationFactor); result.put("p50", snapshot.getMedian() * durationFactor); result.put("p75", snapshot.get75thPercentile() * durationFactor); result.put("p95", snapshot.get95thPercentile() * durationFactor); result.put("p98", snapshot.get98thPercentile() * durationFactor); result.put("p99", snapshot.get99thPercentile() * durationFactor); result.put("p999", snapshot.get999thPercentile() * durationFactor); if (showSamples) { final long[] values = snapshot.getValues(); List<Double> scaledValues = new ArrayList<>(values.length); for (long value : values) { scaledValues.add(value * durationFactor); } result.put("values", scaledValues); } return result; } @SuppressWarnings("rawtypes") private static Map<String, Object> gauges(final SortedMap<String, Gauge> gauges) { Map<String, Object> result = new TreeMap<>(); gauges.forEach((name, gauge) -> { try { result.put(name, gauge.getValue()); } catch (Exception ex) { result.put(name, ex.toString()); } }); return result; } private static Map<String, Object> counters(final SortedMap<String, Counter> counters) { Map<String, Object> result = new TreeMap<>(); counters.forEach((name, c) -> { result.put(name, c.getCount()); }); return result; } private static Map<String, Object> histograms(final SortedMap<String, Histogram> histograms, final boolean showSamples) { Map<String, Object> result = new TreeMap<>(); histograms.forEach((name, timer) -> { result.put(name, snapshot(timer, 1, showSamples)); }); return result; } private static Map<String, Object> meters(final SortedMap<String, Meter> timers, final String rateUnit, final double rateFactor, final String durationUnit, final double durationFactor) { Map<String, Object> result = new TreeMap<>(); timers.forEach((name, timer) -> { result.put(name, meter(timer, rateUnit, rateFactor, durationUnit, durationFactor)); }); return result; } private static Map<String, Object> meter(final Metered meter, final String rateUnit, final double rateFactor, final String durationUnit, final double durationFactor) { Map<String, Object> result = new TreeMap<>(); result.put("count", meter.getCount()); result.put("m15_rate", meter.getFifteenMinuteRate() * rateFactor); result.put("m1_rate", meter.getOneMinuteRate() * rateFactor); result.put("m5_rate", meter.getFiveMinuteRate() * rateFactor); result.put("mean_rate", meter.getMeanRate() * rateFactor); result.put("duration_units", durationUnit); result.put("rate_units", rateUnit); return result; } private static String calculateRateUnit(final TimeUnit unit, final String name) { final String s = unit.toString().toLowerCase(Locale.US); return name + '/' + s.substring(0, s.length() - 1); } }