//Dstl (c) Crown Copyright 2017 package uk.gov.dstl.baleen.core.web.servlets; import java.io.IOException; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.gov.dstl.baleen.core.web.security.WebPermission; import com.codahale.metrics.Metric; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.json.MetricsModule; /** * Outputs all metrics in the registry as JSON. * * Accepts an optional parameter <i>filter</i>, which will filter the output * based on the metric name. Single *'s should be used to replace one 'level', * whereas double *'s can be used to replace multiple levels (but not multiple 'sections'). * For instance: * <ul> * <li><b>baleen:*:example</b> would match the metric baleen:foo:example, but * not baleen:foo:bar:example</li> * <li><b>baleen:**:example</b> would match the metric baleen:foo:example and * baleen:foo:bar:example</li> * <li><b>baleen:**:**</b> would match all metrics from the baleen pipeline, * whereas <b>**:foo.bar:**</b> would match all metrics for foo.bar across * any pipeline</li> * </ul> * * In practice, this equates to a single * being replaced with the regular * expression * * <pre> * [a-z0-9\\-]{0,} * </pre> * * and a double ** being replaced with the regular expression * * <pre> * [a-z0-9\\-\\.]{0,} * </pre> * * The comparison is done case insensitively. * * If using authentication, the user will need the "metrics" role to access this * resource. * * * * */ public class MetricsServlet extends AbstractApiServlet { public static final String PARAM_FILTER = "filter"; private static final Logger LOGGER = LoggerFactory.getLogger(MetricsServlet.class); private static final long serialVersionUID = 1L; private static final String ROLES = "metrics"; private final transient MetricRegistry registry; /** * New instance, which will report on the supplied metrics. * * @param registry * the metrics registry to provide metrics from */ public MetricsServlet(MetricRegistry registry) { super(LOGGER, MetricsServlet.class); this.registry = registry; getMapper().registerModule(new MetricsModule(TimeUnit.SECONDS, TimeUnit.SECONDS, false)); } @Override public WebPermission[] getPermissions() { return new WebPermission[] { new WebPermission("Access Metrics", ROLES) }; } @Override protected void get(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String[] filters = req.getParameterValues(PARAM_FILTER); Map<String, Metric> metrics; if (filters == null || filters.length == 0) { metrics = registry.getMetrics(); } else { metrics = registry.getMetrics().entrySet().stream().filter(e -> { for (String s : filters) { return filterMetric(e.getKey(), s); } return false; }).collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())); } respondWithJson(resp, metrics); } /** * Test the metricName against the pattern and return true if it matches. We * replace ** and * with the appropriate regular expressions to do the * package matching. * * @param metricName * The name of the metric to test * @param pattern * The pattern to test against * @return True or false */ public static boolean filterMetric(String metricName, String pattern) { String regexPattern = pattern.replaceAll("\\*\\*", "[a-z0-9\\.\\-]{0,}"); regexPattern = regexPattern.replaceAll("\\*", "[a-z0-9\\-]{0,}"); Pattern p = Pattern.compile(regexPattern, Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(metricName); return m.matches(); } }