blob: 26c5ff4a79eb0f52e989b4a2386ac9ee312470cc [file] [log] [blame]
/*
* 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 org.apache.commons.io.IOUtils;
import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.client.solrj.impl.XMLResponseParser;
import org.apache.solr.core.SolrInfoBean;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.util.ContentStream;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.response.BinaryResponseWriter;
import org.apache.solr.response.SolrQueryResponse;
import java.io.StringReader;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.Set;
import java.util.Map;
import java.util.HashSet;
/**
* A request handler that provides info about all
* registered SolrInfoMBeans.
*/
@SuppressWarnings("unchecked")
public class SolrInfoMBeanHandler extends RequestHandlerBase {
/**
* Take an array of any type and generate a Set containing the toString.
* Set is guarantee to never be null (but may be empty)
*/
private Set<String> arrayToSet(Object[] arr) {
HashSet<String> r = new HashSet<>();
if (null == arr) return r;
for (Object o : arr) {
if (null != o) r.add(o.toString());
}
return r;
}
@Override
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
NamedList<NamedList<NamedList<Object>>> cats = getMBeanInfo(req);
if(req.getParams().getBool("diff", false)) {
ContentStream body = null;
try {
body = req.getContentStreams().iterator().next();
}
catch(Exception ex) {
throw new SolrException(ErrorCode.BAD_REQUEST, "missing content-stream for diff");
}
String content = IOUtils.toString(body.getReader());
NamedList<NamedList<NamedList<Object>>> ref = fromXML(content);
// Normalize the output
SolrQueryResponse wrap = new SolrQueryResponse();
wrap.add("solr-mbeans", cats);
cats = (NamedList<NamedList<NamedList<Object>>>)
BinaryResponseWriter.getParsedResponse(req, wrap).get("solr-mbeans");
// Get rid of irrelevant things
ref = normalize(ref);
cats = normalize(cats);
// Only the changes
boolean showAll = req.getParams().getBool("all", false);
rsp.add("solr-mbeans", getDiff(ref,cats, showAll));
}
else {
rsp.add("solr-mbeans", cats);
}
rsp.setHttpCaching(false); // never cache, no matter what init config looks like
}
static NamedList<NamedList<NamedList<Object>>> fromXML(String content) {
int idx = content.indexOf("<response>");
if(idx<0) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Body does not appear to be an XML response");
}
try {
XMLResponseParser parser = new XMLResponseParser();
return (NamedList<NamedList<NamedList<Object>>>)
parser.processResponse(new StringReader(content)).get("solr-mbeans");
}
catch(Exception ex) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Unable to read original XML", ex);
}
}
protected NamedList<NamedList<NamedList<Object>>> getMBeanInfo(SolrQueryRequest req) {
NamedList<NamedList<NamedList<Object>>> cats = new NamedList<>();
String[] requestedCats = req.getParams().getParams("cat");
if (null == requestedCats || 0 == requestedCats.length) {
for (SolrInfoBean.Category cat : SolrInfoBean.Category.values()) {
cats.add(cat.name(), new SimpleOrderedMap<NamedList<Object>>());
}
} else {
for (String catName : requestedCats) {
cats.add(catName,new SimpleOrderedMap<NamedList<Object>>());
}
}
Set<String> requestedKeys = arrayToSet(req.getParams().getParams("key"));
Map<String, SolrInfoBean> reg = req.getCore().getInfoRegistry();
for (Map.Entry<String, SolrInfoBean> entry : reg.entrySet()) {
addMBean(req, cats, requestedKeys, entry.getKey(),entry.getValue());
}
for (SolrInfoBean infoMBean : req.getCore().getCoreContainer().getResourceLoader().getInfoMBeans()) {
addMBean(req,cats,requestedKeys,infoMBean.getName(),infoMBean);
}
return cats;
}
private void addMBean(SolrQueryRequest req, NamedList<NamedList<NamedList<Object>>> cats, Set<String> requestedKeys, String key, SolrInfoBean m) {
if ( ! ( requestedKeys.isEmpty() || requestedKeys.contains(key) ) ) return;
NamedList<NamedList<Object>> catInfo = cats.get(m.getCategory().name());
if ( null == catInfo ) return;
NamedList<Object> mBeanInfo = new SimpleOrderedMap<>();
mBeanInfo.add("class", m.getName());
mBeanInfo.add("description", m.getDescription());
if (req.getParams().getFieldBool(key, "stats", false))
mBeanInfo.add("stats", m.getMetricsSnapshot());
catInfo.add(key, mBeanInfo);
}
protected NamedList<NamedList<NamedList<Object>>> getDiff(
NamedList<NamedList<NamedList<Object>>> ref,
NamedList<NamedList<NamedList<Object>>> now,
boolean includeAll ) {
NamedList<NamedList<NamedList<Object>>> changed = new NamedList<>();
// Cycle through each category
for(int i=0;i<ref.size();i++) {
String category = ref.getName(i);
NamedList<NamedList<Object>> ref_cat = ref.get(category);
NamedList<NamedList<Object>> now_cat = now.get(category);
if(now_cat != null) {
String ref_txt = ref_cat+"";
String now_txt = now_cat+"";
if(!ref_txt.equals(now_txt)) {
// Something in the category changed
// Now iterate the real beans
NamedList<NamedList<Object>> cat = new SimpleOrderedMap<>();
for(int j=0;j<ref_cat.size();j++) {
String name = ref_cat.getName(j);
NamedList<Object> ref_bean = ref_cat.get(name);
NamedList<Object> now_bean = now_cat.get(name);
ref_txt = ref_bean+"";
now_txt = now_bean+"";
if(!ref_txt.equals(now_txt)) {
// System.out.println( "----" );
// System.out.println( category +" : " + name );
// System.out.println( "REF: " + ref_txt );
// System.out.println( "NOW: " + now_txt );
// Calculate the differences
@SuppressWarnings({"rawtypes"})
NamedList diff = diffNamedList(ref_bean,now_bean);
diff.add( "_changed_", true ); // flag the changed thing
cat.add(name, diff);
}
else if(includeAll) {
cat.add(name, ref_bean);
}
}
if(cat.size()>0) {
changed.add(category, cat);
}
}
else if(includeAll) {
changed.add(category, ref_cat);
}
}
}
return changed;
}
@SuppressWarnings({"rawtypes"})
public NamedList diffNamedList(NamedList ref, NamedList now) {
NamedList out = new SimpleOrderedMap();
for(int i=0; i<ref.size(); i++) {
String name = ref.getName(i);
Object r = ref.getVal(i);
Object n = now.get(name);
if (n == null) {
if (r != null) {
out.add("REMOVE " + name, r);
now.remove(name);
}
}
else if (r != null) {
out.add(name, diffObject(r, n));
now.remove(name);
}
}
for(int i=0; i<now.size(); i++) {
String name = now.getName(i);
Object v = now.getVal(i);
if(v!=null) {
out.add("ADD "+name, v);
}
}
return out;
}
@SuppressWarnings({"rawtypes"})
public Object diffObject(Object ref, Object now) {
if (now instanceof Map) {
now = new NamedList((Map)now);
}
if(ref instanceof NamedList) {
return diffNamedList((NamedList)ref, (NamedList)now);
}
if(ref.equals(now)) {
return ref;
}
StringBuilder str = new StringBuilder();
str.append("Was: ")
.append(ref).append(", Now: ").append(now);
if(ref instanceof Number) {
NumberFormat nf = NumberFormat.getIntegerInstance(Locale.ROOT);
if((ref instanceof Double) || (ref instanceof Float)) {
nf = NumberFormat.getInstance(Locale.ROOT);
}
double dref = ((Number)ref).doubleValue();
double dnow = ((Number)now).doubleValue();
double diff = Double.NaN;
if(Double.isNaN(dref)) {
diff = dnow;
}
else if(Double.isNaN(dnow)) {
diff = dref;
}
else {
diff = dnow-dref;
}
str.append( ", Delta: ").append(nf.format(diff));
}
return str.toString();
}
/**
* The 'avgRequestsPerSecond' field will make everything look like it changed
*/
@SuppressWarnings({"rawtypes"})
public NamedList normalize(NamedList input) {
input.remove("avgRequestsPerSecond");
for(int i=0; i<input.size(); i++) {
Object v = input.getVal(i);
if(v instanceof NamedList) {
input.setVal(i, normalize((NamedList)v));
}
}
return input;
}
@Override
public String getDescription() {
return "Get Info (and statistics) for registered SolrInfoMBeans";
}
@Override
public Category getCategory() {
return Category.ADMIN;
}
}