/* * Copyright 2013 Netflix, Inc. * * Licensed 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 netflix.adminresources.resources.jmx; import org.codehaus.jettison.json.JSONArray; import org.codehaus.jettison.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.MBeanAttributeInfo; import javax.management.MBeanInfo; import javax.management.MBeanOperationInfo; import javax.management.MBeanParameterInfo; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.openmbean.CompositeDataSupport; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import java.io.StringWriter; import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; /** * Resource to expose JMX via JSON * * @author elandau * */ @Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_HTML }) @Path("/jmx") public class JMXResource { private static final Logger LOG = LoggerFactory.getLogger(JMXResource.class); private static final String CURRENT_VALUE = "CurrentValue"; private JmxService jmx; public JMXResource() { LOG.info("JMXResource created"); jmx = JmxService.getInstance(); } /** * Return JSON representing the entire tree of MBeans in DynaTree format. * * @param key * @param mode */ @GET public Response getMBeans( @QueryParam("key") @DefaultValue("root") String key, @QueryParam("mode") @DefaultValue("") String mode, @QueryParam("jsonp") @DefaultValue("") String jsonp) throws Exception { LOG.info("key" + key); DynaTreeNode root = new DynaTreeNode(); for (String domain : jmx.getDomainList()) { root.putChild(jmx.getDomainTree(domain).setTitle(domain) .setMode("domain")); } StringWriter out = new StringWriter(); if (jsonp.isEmpty()) { root.getChildrenJSONArray().write(out); } else { out.append(jsonp).append("("); root.getChildrenJSONArray().write(out); out.append(");"); } return Response.ok(out.toString()).header("Pragma", "no-cache") .header("Cache-Control", "no-cache").header("Expires", "0") .build(); } /** * Return all the attributes and operations for a single mbean * * @param key * Exact object name of MBean in String form * @param jsonp */ @GET @Path("{key}") public Response getMBean(@PathParam("key") String key, @QueryParam("jsonp") @DefaultValue("") String jsonp) throws Exception { LOG.info("key: " + key); JSONObject json = new JSONObject(); ObjectName name = new ObjectName(key); json.put("domain", name.getDomain()); json.put("property", name.getKeyPropertyList()); if (key.contains("*")) { JSONObject keys = new JSONObject(); for (Entry<String, Map<String, String>> attrs : jmx .getMBeanAttributesByRegex(key).entrySet()) { keys.put(attrs.getKey(), attrs.getValue()); } json.put("attributes", keys); json.put("multikey", true); } else { json.put("attributes", jmx.getMBeanAttributes(key)); json.put("multikey", false); MBeanOperationInfo[] operations = jmx.getMBeanOperations(key); JSONArray ar = new JSONArray(); for (MBeanOperationInfo operation : operations) { JSONObject obj = new JSONObject(); obj.put("name", operation.getName()); obj.put("description", operation.getDescription()); obj.put("returnType", operation.getReturnType()); obj.put("impact", operation.getImpact()); JSONArray params = new JSONArray(); for (MBeanParameterInfo param : operation.getSignature()) { JSONObject p = new JSONObject(); p.put("name", param.getName()); p.put("type", param.getType()); params.put(p); } obj.put("params", params); ar.put(obj); } json.put("operations", ar); } StringWriter out = new StringWriter(); if (jsonp.isEmpty()) { json.write(out); } else { out.append(jsonp).append("("); json.write(out); out.append(");"); } return Response.ok(out.toString()).type(MediaType.APPLICATION_JSON) .build(); } /** * Execute an operation on an mbean. * * @param formParams * @param key * @param jsonp * @param name */ @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Path("{key}/{op}") public Response invokeMbeanOperation( MultivaluedMap<String, String> formParams, @PathParam("key") String key, @QueryParam("jsonp") String jsonp, @PathParam("op") String name) throws Exception { LOG.info("invoke " + key + " op=" + name); MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); Map<String, String> params = new TreeMap<String, String>(); for (Entry<String, List<String>> entry : formParams.entrySet()) { if (entry.getKey().equals("op")) continue; if (entry.getValue().size() > 0) params.put(entry.getKey(), entry.getValue().get(0)); else params.put(entry.getKey(), ""); } ObjectName objName = new ObjectName(key); MBeanInfo info = mBeanServer.getMBeanInfo(objName); for (MBeanOperationInfo op : info.getOperations()) { if (op.getName().equals(name)) { List<String> signature = new ArrayList<String>(); for (MBeanParameterInfo s : op.getSignature()) { signature.add(s.getType()); } Object result = mBeanServer.invoke(objName, name, params .values().toArray(new String[params.size()]), signature .toArray(new String[signature.size()])); JSONObject json = new JSONObject(); json.put("key", key); json.put("operation", name); if (result != null) { json.put("response", result.toString()); } json.put("type", op.getReturnType()); StringWriter out = new StringWriter(); if (jsonp.isEmpty()) { json.write(out); } else { out.append(jsonp).append("("); json.write(out); out.append(");"); } return Response.ok(out.toString()) .type(MediaType.APPLICATION_JSON).build(); } } return Response.serverError().build(); } /** * Return all the attributes and operations for a MBeans whose * {@link ObjectName}s match the passed in regex. * * @param objNameRegex * regex for {@link ObjectName} of MBean(s). The * {@link ObjectName} documentation explains what kinds of regex * expressions are valid * @param jsonp * if non-empty, Jsonp output is returned instead of Json */ @GET @Path("/mbeans") public Response getMBeansByRegex( @QueryParam("objNameRegex") String objNameRegex, @QueryParam("jsonp") @DefaultValue("") String jsonp) { try { ObjectName objNameForRegex = new ObjectName(objNameRegex); MBeanServer mBeanServer = ManagementFactory .getPlatformMBeanServer(); Set<ObjectName> objs = mBeanServer .queryNames(objNameForRegex, null); JSONObject result = new JSONObject(); for (ObjectName objName : objs) { JSONObject json = new JSONObject(); try { json.put("attributes", emitAttributes(objName)); } catch (Exception e) { json.put("attributes", emitAttributes(objName)); } json.put("operations", emitOperations(objName)); result.put(objName.getCanonicalName(), json); } StringWriter out = new StringWriter(); if (jsonp.isEmpty()) { result.write(out); } else { out.append(jsonp).append("("); result.write(out); out.append(");"); } return Response.ok(out.toString()).type(MediaType.APPLICATION_JSON) .build(); } catch (Exception e) { LOG.error( "Error while retrieving mbeans for regex:" + objNameRegex, e); return Response.serverError().entity(e.getMessage()).build(); } } /** * Generate JSON for the MBean attributes * * @return * @throws Exception */ private JSONObject emitAttributes(ObjectName objName) throws Exception { MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); MBeanInfo mBeanInfo = mBeanServer.getMBeanInfo(objName); JSONObject resp = new JSONObject(); if (mBeanInfo != null) { MBeanAttributeInfo[] attrs = mBeanInfo.getAttributes(); if (attrs != null) { List<String> attrNames = new ArrayList<String>(attrs.length); for (MBeanAttributeInfo attr : attrs) { attrNames.add(attr.getName()); } AttributeList attrList = mBeanServer.getAttributes(objName, attrNames.toArray(new String[0])); for (Attribute attr : attrList.asList()) { Object value = attr.getValue(); String attrName = attr.getName(); if (attrName != null && value != null) { String attrValue = null; if (value instanceof CompositeDataSupport) { CompositeDataSupport compositeValue = (CompositeDataSupport) value; if (compositeValue != null) { try { if (compositeValue.containsKey(CURRENT_VALUE)) { Object curValue = compositeValue .get(CURRENT_VALUE); attrValue = (curValue == null ? "null" : curValue.toString()); } } catch (Exception e) { attrValue = compositeValue.toString(); } } } if (attrValue == null) { attrValue = value.toString(); } resp.put(attrName, (attrValue == null ? "null" : attrValue)); } } } } return resp; } /** * Generate JSON for the MBean operations * * @param objName * @return * @throws Exception */ private JSONArray emitOperations(ObjectName objName) throws Exception { MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); MBeanInfo mBeanInfo = mBeanServer.getMBeanInfo(objName); JSONArray ar = new JSONArray(); MBeanOperationInfo[] operations = mBeanInfo.getOperations(); for (MBeanOperationInfo operation : operations) { JSONObject obj = new JSONObject(); obj.put("name", operation.getName()); obj.put("description", operation.getDescription()); obj.put("returnType", operation.getReturnType()); obj.put("impact", operation.getImpact()); JSONArray params = new JSONArray(); for (MBeanParameterInfo param : operation.getSignature()) { JSONObject p = new JSONObject(); p.put("name", param.getName()); p.put("type", param.getType()); params.put(p); } obj.put("params", params); ar.put(obj); } return ar; } }