1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 * 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * The contents of this file are subject to the terms of either the Universal Permissive License 7 * v 1.0 as shown at http://oss.oracle.com/licenses/upl 8 * 9 * or the following license: 10 * 11 * Redistribution and use in source and binary forms, with or without modification, are permitted 12 * provided that the following conditions are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions 15 * and the following disclaimer. 16 * 17 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 18 * conditions and the following disclaimer in the documentation and/or other materials provided with 19 * the distribution. 20 * 21 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 22 * endorse or promote products derived from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 26 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 31 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 package org.openjdk.jmc.rjmx.services.internal; 34 35 import java.io.IOException; 36 import java.util.ArrayList; 37 import java.util.Collection; 38 import java.util.Collections; 39 import java.util.HashMap; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.concurrent.Callable; 43 44 import javax.management.Descriptor; 45 import javax.management.InstanceNotFoundException; 46 import javax.management.MBeanException; 47 import javax.management.MBeanInfo; 48 import javax.management.MBeanOperationInfo; 49 import javax.management.MBeanServerConnection; 50 import javax.management.ObjectName; 51 import javax.management.ReflectionException; 52 53 import org.openjdk.jmc.rjmx.ConnectionToolkit; 54 import org.openjdk.jmc.rjmx.RJMXPlugin; 55 import org.openjdk.jmc.rjmx.ServiceNotAvailableException; 56 import org.openjdk.jmc.rjmx.services.IDiagnosticCommandService; 57 import org.openjdk.jmc.rjmx.services.IOperation.OperationImpact; 58 import org.openjdk.jmc.rjmx.services.IllegalOperandException; 59 import org.openjdk.jmc.rjmx.util.internal.SimpleAttributeInfo; 60 61 public class HotSpot24DiagnosticCommandService implements IDiagnosticCommandService { 62 63 private static final ObjectName DIAGNOSTIC_BEAN = ConnectionToolkit 64 .createObjectName("com.sun.management:type=DiagnosticCommand"); //$NON-NLS-1$ 65 private static final String OPERATION_UPDATE = "update"; //$NON-NLS-1$ 66 private final MBeanServerConnection m_mbeanServer; 67 private final Map<String, String> commandNameToOperation = new HashMap<>(); 68 private Collection<DiagnosticCommand> operations; 69 70 private static final String IMPACT = "dcmd.vmImpact"; //$NON-NLS-1$ 71 private static final String NAME = "dcmd.name"; //$NON-NLS-1$ 72 private static final String DESCRIPTION = "dcmd.description"; //$NON-NLS-1$ 73 // private final static String HELP = "dcmd.help"; //$NON-NLS-1$ 74 private static final String ARGUMENTS = "dcmd.arguments"; //$NON-NLS-1$ 75 private static final String ARGUMENT_NAME = "dcmd.arg.name"; //$NON-NLS-1$ 76 private static final String ARGUMENT_DESCRIPTION = "dcmd.arg.description"; //$NON-NLS-1$ 77 private static final String ARGUMENT_MANDATORY = "dcmd.arg.isMandatory"; //$NON-NLS-1$ 78 private static final String ARGUMENT_TYPE = "dcmd.arg.type"; //$NON-NLS-1$ 79 private static final String ARGUMENT_OPTION = "dcmd.arg.isOption"; //$NON-NLS-1$ 80 private static final String ARGUMENT_MULITPLE = "dcmd.arg.isMultiple"; //$NON-NLS-1$ 81 82 private static List<DiagnosticCommandParameter> extractSignature(Descriptor args) { 83 if (args != null) { 84 String[] argNames = args.getFieldNames(); 85 List<DiagnosticCommandParameter> parameters = new ArrayList<>(argNames.length); 86 for (String argName : argNames) { 87 Descriptor arg = (Descriptor) args.getFieldValue(argName); 88 parameters.add(new DiagnosticCommandParameter(arg)); 89 } 90 return parameters; 91 } else { 92 return Collections.emptyList(); 93 } 94 } 95 96 private static OperationImpact extractImpact(Descriptor d) { 97 String impact = d.getFieldValue(IMPACT).toString(); 98 if (impact.startsWith("Low")) { //$NON-NLS-1$ 99 return OperationImpact.IMPACT_LOW; 100 } 101 if (impact.startsWith("Medium")) { //$NON-NLS-1$ 102 return OperationImpact.IMPACT_MEDIUM; 103 } 104 if (impact.startsWith("High")) { //$NON-NLS-1$ 105 return OperationImpact.IMPACT_HIGH; 106 } 107 return OperationImpact.IMPACT_UNKNOWN; 108 } 109 110 private static String extractType(Descriptor d) { 111 boolean isMultiple = Boolean.parseBoolean(d.getFieldValue(ARGUMENT_MULITPLE).toString()); 112 String typeName = d.getFieldValue(ARGUMENT_TYPE).toString(); 113 if (isMultiple) { 114 if (typeName.equals("STRING SET")) { //$NON-NLS-1$ 115 return String[].class.getName(); 116 } else { 117 return typeName.toLowerCase().replace(' ', '_') + '*'; 118 } 119 } 120 if (typeName.equals("BOOLEAN")) { //$NON-NLS-1$ 121 return Boolean.class.getName(); 122 } else if (typeName.equals("STRING") || typeName.equals("NANOTIME") || typeName.equals("MEMORY SIZE")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 123 return String.class.getName(); 124 } else if (typeName.equals("JLONG")) { //$NON-NLS-1$ 125 return Long.class.getName(); 126 } else { 127 return typeName.toLowerCase().replace(' ', '_'); 128 } 129 } 130 131 private static String extractDescription(Descriptor d) { 132 // FIXME: Argument descriptions for JFR operations contains \" that should be ". Workaround for now. 133 String desc = d.getFieldValue(ARGUMENT_DESCRIPTION).toString().trim().replaceAll("\\\\\"", "\""); //$NON-NLS-1$ //$NON-NLS-2$ 134 return desc.length() > 0 ? desc : d.getFieldValue(ARGUMENT_NAME).toString().trim(); 135 } 136 137 private static class ArgumentBuilder { 138 private final List<String> arguments = new ArrayList<>(); 139 140 public void appendArgument(Object value, DiagnosticCommandParameter parameterInfo) 141 throws IllegalOperandException { 142 if (parameterInfo.isMultiple) { 143 if (value.getClass().isArray()) { 144 for (Object o : ((Object[]) value)) { 145 appendValue(o, parameterInfo); 146 } 147 } else { 148 throw new IllegalOperandException(parameterInfo); 149 } 150 } else { 151 appendValue(value, parameterInfo); 152 } 153 } 154 155 private void appendValue(Object value, DiagnosticCommandParameter parameterInfo) 156 throws IllegalOperandException { 157 StringBuilder sb = new StringBuilder(); 158 if (parameterInfo.isOption) { 159 sb.append(parameterInfo.parameterName).append('='); 160 } 161 String stringValue = String.valueOf(value); 162 if (stringValue.indexOf('"') >= 0) { 163 throw new IllegalOperandException(parameterInfo); 164 } else if (stringValue.indexOf(' ') >= 0) { 165 sb.append('"').append(stringValue).append('"'); 166 } else { 167 sb.append(stringValue); 168 } 169 arguments.add(sb.toString()); 170 } 171 172 public String[] asArray() { 173 return arguments.toArray(new String[arguments.size()]); 174 } 175 } 176 177 private static class DiagnosticCommandParameter extends SimpleAttributeInfo { 178 private final boolean isOption; 179 private final boolean isMultiple; 180 private final boolean isRequired; 181 private final String parameterName; 182 183 public DiagnosticCommandParameter(Descriptor d) { 184 super(d.getFieldValue(ARGUMENT_NAME).toString(), extractType(d), extractDescription(d)); 185 parameterName = d.getFieldValue(ARGUMENT_NAME).toString(); 186 isOption = Boolean.parseBoolean(d.getFieldValue(ARGUMENT_OPTION).toString()); 187 isMultiple = Boolean.parseBoolean(d.getFieldValue(ARGUMENT_MULITPLE).toString()); 188 isRequired = Boolean.parseBoolean(d.getFieldValue(ARGUMENT_MANDATORY).toString()); 189 RJMXPlugin.getDefault().getLogger() 190 .finest("DiagnosticCommandArg created: " + getType() + ' ' + getName() + ' ' + getDescription() //$NON-NLS-1$ 191 + (isRequired ? " isRequired" : "") //$NON-NLS-1$ //$NON-NLS-2$ 192 + (isOption ? " isOption" : "") + (isMultiple ? " isMultiple" : "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ 193 } 194 195 } 196 197 private class DiagnosticCommand extends AbstractOperation<DiagnosticCommandParameter> { 198 199 public DiagnosticCommand(Descriptor d, String returnType) { 200 super(d.getFieldValue(NAME).toString(), d.getFieldValue(DESCRIPTION).toString(), returnType, 201 extractSignature((Descriptor) d.getFieldValue(ARGUMENTS)), extractImpact(d)); 202 RJMXPlugin.getDefault().getLogger() 203 .finest("DiagnosticCommand created: " + getName() + ' ' + getReturnType() + ' ' + getImpact()); //$NON-NLS-1$ 204 } 205 206 @Override 207 public Callable<?> getInvocator(Object ... argValues) throws IllegalOperandException { 208 ArgumentBuilder ab = new ArgumentBuilder(); 209 List<DiagnosticCommandParameter> args = getSignature(); 210 for (int i = 0; i < args.size(); i++) { 211 if (i >= argValues.length || argValues[i] == null) { 212 if (args.get(i).isRequired) { 213 // Argument value is required but not provided 214 IllegalOperandException ex = new IllegalOperandException(args.get(i)); 215 while (++i < args.size()) { 216 // Check for other attributes with the same error 217 if (args.get(i).isRequired) { 218 ex.addInvalidValue(args.get(i)); 219 } 220 } 221 throw ex; 222 } else { 223 continue; 224 } 225 } 226 ab.appendArgument(argValues[i], args.get(i)); 227 } 228 final String[] arguments = ab.asArray(); 229 return new Callable<Object>() { 230 231 @Override 232 public Object call() throws Exception { 233 return execute(arguments); 234 } 235 236 @Override 237 public String toString() { 238 return getName() + ' ' + asString(arguments); 239 } 240 }; 241 } 242 243 private String asString(String[] array) { 244 StringBuilder sb = new StringBuilder(); 245 if (array != null) { 246 for (int i = 0; i < array.length; i += 1) { 247 sb.append(array[i]); 248 if (i + 1 < array.length) { 249 sb.append(' '); 250 } 251 } 252 } 253 return sb.toString(); 254 } 255 256 private String execute(String[] arguments) 257 throws InstanceNotFoundException, MBeanException, ReflectionException, IOException { 258 String operation = commandNameToOperation.get(getName()); 259 RJMXPlugin.getDefault().getLogger() 260 .fine("Running " + getName() + " |" + operation + '|' + asString(arguments) + '|'); //$NON-NLS-1$ //$NON-NLS-2$ 261 if (operation == null) { 262 throw new RuntimeException("Unavailable diagnostic command " + getName() + '!'); //$NON-NLS-1$ 263 } 264 if (getSignature().size() > 0) { 265 return (String) m_mbeanServer.invoke(DIAGNOSTIC_BEAN, operation, new Object[] {arguments}, 266 new String[] {String[].class.getName()}); 267 } else { 268 return (String) m_mbeanServer.invoke(DIAGNOSTIC_BEAN, operation, new Object[0], new String[0]); 269 } 270 } 271 272 } 273 274 public HotSpot24DiagnosticCommandService(MBeanServerConnection server) throws ServiceNotAvailableException { 275 m_mbeanServer = server; 276 try { 277 refreshOperations(); 278 } catch (Exception e) { 279 throw new ServiceNotAvailableException("Unable to retrieve diagnostic commands!"); //$NON-NLS-1$ 280 } 281 } 282 283 @Override 284 public synchronized Collection<DiagnosticCommand> getOperations() throws Exception { 285 refreshOperations(); 286 return operations; 287 } 288 289 private void refreshOperations() throws Exception { 290 RJMXPlugin.getDefault().getLogger().finer("Refreshing diagnostic operations"); //$NON-NLS-1$ 291 MBeanInfo info = m_mbeanServer.getMBeanInfo(DIAGNOSTIC_BEAN); 292 operations = new ArrayList<>(info.getOperations().length); 293 commandNameToOperation.clear(); 294 for (MBeanOperationInfo oper : info.getOperations()) { 295 if (!oper.getName().equals(OPERATION_UPDATE)) { 296 Descriptor descriptor = oper.getDescriptor(); 297 DiagnosticCommand c = new DiagnosticCommand(descriptor, oper.getReturnType()); 298 operations.add(c); 299 commandNameToOperation.put(c.getName(), oper.getName()); 300 } 301 } 302 } 303 304 @Override 305 public String runCtrlBreakHandlerWithResult(String command) throws Exception { 306 int index = command.indexOf(' '); 307 if (index > 0) { 308 String operationName = command.substring(0, index); 309 return findDiagnosticCommand(operationName).execute(splitArguments(command.substring(index + 1).trim())); 310 } else { 311 return findDiagnosticCommand(command).execute(null); 312 } 313 } 314 315 private String[] splitArguments(String commandArguments) { 316 List<String> arguments = new ArrayList<>(); 317 StringBuilder argument = new StringBuilder(); 318 boolean inCitation = false; 319 for (char c : commandArguments.toCharArray()) { 320 if (inCitation) { 321 if (c == '"') { 322 inCitation = false; 323 } 324 argument.append(c); 325 } else { 326 if (Character.isWhitespace(c)) { 327 if (argument.length() > 0) { 328 arguments.add(argument.toString()); 329 argument = new StringBuilder(); 330 } 331 } else { 332 if (c == '"') { 333 inCitation = true; 334 } 335 argument.append(c); 336 } 337 } 338 } 339 if (argument.length() > 0) { 340 arguments.add(argument.toString()); 341 } 342 return arguments.toArray(new String[arguments.size()]); 343 } 344 345 private synchronized DiagnosticCommand findDiagnosticCommand(String operationName) throws Exception { 346 for (DiagnosticCommand op : operations) { 347 if (op.getName().equals(operationName)) { 348 return op; 349 } 350 } 351 throw new IllegalArgumentException("Unavailable diagnostic command " + operationName + '!'); //$NON-NLS-1$ 352 } 353 354 }