/**
* 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);
}
}