/*
* 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.apache.solr.handler.admin;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.metrics.SolrMetricManager;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.security.AuthorizationContext;
import org.apache.solr.security.PermissionNameProvider;
import org.apache.solr.util.stats.MetricUtils;
/**
* Request handler to return metrics
*/
public class MetricsHandler extends RequestHandlerBase implements PermissionNameProvider {
final CoreContainer container;
final SolrMetricManager metricManager;
public static final String COMPACT_PARAM = "compact";
public static final String PREFIX_PARAM = "prefix";
public static final String REGEX_PARAM = "regex";
public static final String PROPERTY_PARAM = "property";
public static final String REGISTRY_PARAM = "registry";
public static final String GROUP_PARAM = "group";
public static final String TYPE_PARAM = "type";
public static final String ALL = "all";
public MetricsHandler() {
this.container = null;
this.metricManager = null;
}
public MetricsHandler(CoreContainer container) {
this.container = container;
this.metricManager = this.container.getMetricManager();
}
@Override
public Name getPermissionName(AuthorizationContext request) {
return Name.METRICS_READ_PERM;
}
@Override
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
if (container == null) {
throw new SolrException(SolrException.ErrorCode.INVALID_STATE, "Core container instance not initialized");
}
boolean compact = req.getParams().getBool(COMPACT_PARAM, true);
MetricFilter mustMatchFilter = parseMustMatchFilter(req);
MetricUtils.PropertyFilter propertyFilter = parsePropertyFilter(req);
List<MetricType> metricTypes = parseMetricTypes(req);
List<MetricFilter> metricFilters = metricTypes.stream().map(MetricType::asMetricFilter).collect(Collectors.toList());
Set<String> requestedRegistries = parseRegistries(req);
NamedList response = new SimpleOrderedMap();
for (String registryName : requestedRegistries) {
MetricRegistry registry = metricManager.registry(registryName);
SimpleOrderedMap result = new SimpleOrderedMap();
MetricUtils.toMaps(registry, metricFilters, mustMatchFilter, propertyFilter, false,
false, compact, false, (k, v) -> result.add(k, v));
if (result.size() > 0) {
response.add(registryName, result);
}
}
rsp.getValues().add("metrics", response);
}
private MetricFilter parseMustMatchFilter(SolrQueryRequest req) {
String[] prefixes = req.getParams().getParams(PREFIX_PARAM);
MetricFilter prefixFilter = null;
if (prefixes != null && prefixes.length > 0) {
Set<String> prefixSet = new HashSet<>();
for (String prefix : prefixes) {
prefixSet.addAll(StrUtils.splitSmart(prefix, ','));
}
prefixFilter = new SolrMetricManager.PrefixFilter(prefixSet);
}
String[] regexes = req.getParams().getParams(REGEX_PARAM);
MetricFilter regexFilter = null;
if (regexes != null && regexes.length > 0) {
regexFilter = new SolrMetricManager.RegexFilter(regexes);
}
MetricFilter mustMatchFilter;
if (prefixFilter == null && regexFilter == null) {
mustMatchFilter = MetricFilter.ALL;
} else {
if (prefixFilter == null) {
mustMatchFilter = regexFilter;
} else if (regexFilter == null) {
mustMatchFilter = prefixFilter;
} else {
mustMatchFilter = new SolrMetricManager.OrFilter(prefixFilter, regexFilter);
}
}
return mustMatchFilter;
}
private MetricUtils.PropertyFilter parsePropertyFilter(SolrQueryRequest req) {
String[] props = req.getParams().getParams(PROPERTY_PARAM);
if (props == null || props.length == 0) {
return MetricUtils.PropertyFilter.ALL;
}
final Set<String> filter = new HashSet<>();
for (String prop : props) {
if (prop != null && !prop.trim().isEmpty()) {
filter.add(prop.trim());
}
}
if (filter.isEmpty()) {
return MetricUtils.PropertyFilter.ALL;
} else {
return (name) -> filter.contains(name);
}
}
private Set<String> parseRegistries(SolrQueryRequest req) {
String[] groupStr = req.getParams().getParams(GROUP_PARAM);
String[] registryStr = req.getParams().getParams(REGISTRY_PARAM);
if ((groupStr == null || groupStr.length == 0) && (registryStr == null || registryStr.length == 0)) {
// return all registries
return container.getMetricManager().registryNames();
}
boolean allRegistries = false;
Set<String> initialPrefixes = Collections.emptySet();
if (groupStr != null && groupStr.length > 0) {
initialPrefixes = new HashSet<>();
for (String g : groupStr) {
List<String> split = StrUtils.splitSmart(g, ',');
for (String s : split) {
if (s.trim().equals(ALL)) {
allRegistries = true;
break;
}
initialPrefixes.add(SolrMetricManager.overridableRegistryName(s.trim()));
}
if (allRegistries) {
return container.getMetricManager().registryNames();
}
}
}
if (registryStr != null && registryStr.length > 0) {
if (initialPrefixes.isEmpty()) {
initialPrefixes = new HashSet<>();
}
for (String r : registryStr) {
List<String> split = StrUtils.splitSmart(r, ',');
for (String s : split) {
if (s.trim().equals(ALL)) {
allRegistries = true;
break;
}
initialPrefixes.add(SolrMetricManager.overridableRegistryName(s.trim()));
}
if (allRegistries) {
return container.getMetricManager().registryNames();
}
}
}
Set<String> validRegistries = new HashSet<>();
for (String r : container.getMetricManager().registryNames()) {
for (String prefix : initialPrefixes) {
if (r.startsWith(prefix)) {
validRegistries.add(r);
break;
}
}
}
return validRegistries;
}
private List<MetricType> parseMetricTypes(SolrQueryRequest req) {
String[] typeStr = req.getParams().getParams(TYPE_PARAM);
List<String> types = Collections.emptyList();
if (typeStr != null && typeStr.length > 0) {
types = new ArrayList<>();
for (String type : typeStr) {
types.addAll(StrUtils.splitSmart(type, ','));
}
}
List<MetricType> metricTypes = Collections.singletonList(MetricType.all); // include all metrics by default
try {
if (types.size() > 0) {
metricTypes = types.stream().map(String::trim).map(MetricType::valueOf).collect(Collectors.toList());
}
} catch (IllegalArgumentException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid metric type in: " + types +
" specified. Must be one of " + MetricType.SUPPORTED_TYPES_MSG, e);
}
return metricTypes;
}
@Override
public String getDescription() {
return "A handler to return all the metrics gathered by Solr";
}
@Override
public Category getCategory() {
return Category.ADMIN;
}
enum MetricType {
histogram(Histogram.class),
meter(Meter.class),
timer(Timer.class),
counter(Counter.class),
gauge(Gauge.class),
all(null);
public static final String SUPPORTED_TYPES_MSG = EnumSet.allOf(MetricType.class).toString();
private final Class klass;
MetricType(Class klass) {
this.klass = klass;
}
public MetricFilter asMetricFilter() {
return (name, metric) -> klass == null || klass.isInstance(metric);
}
}
}