1 /*
   2  * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package sun.management;
  26 
  27 import java.io.BufferedInputStream;
  28 import java.io.File;
  29 import java.io.FileInputStream;
  30 import java.io.FileNotFoundException;
  31 import java.io.IOException;
  32 import java.io.InputStream;
  33 import java.lang.management.ManagementFactory;
  34 import java.lang.reflect.InvocationTargetException;
  35 import java.lang.reflect.Method;
  36 import java.net.InetAddress;
  37 import java.net.UnknownHostException;
  38 import java.text.MessageFormat;
  39 import java.util.MissingResourceException;
  40 import java.util.Properties;
  41 import java.util.ResourceBundle;
  42 
  43 import javax.management.remote.JMXConnectorServer;
  44 import javax.management.remote.JMXServiceURL;
  45 
  46 import static sun.management.AgentConfigurationError.*;
  47 import sun.management.jmxremote.ConnectorBootstrap;
  48 import sun.management.jdp.JdpController;
  49 import sun.management.jdp.JdpException;
  50 import sun.misc.VMSupport;
  51 
  52 /**
  53  * This Agent is started by the VM when -Dcom.sun.management.snmp or
  54  * -Dcom.sun.management.jmxremote is set. This class will be loaded by the
  55  * system class loader. Also jmx framework could be started by jcmd
  56  */
  57 public class Agent {
  58     // management properties
  59 
  60     private static Properties mgmtProps;
  61     private static ResourceBundle messageRB;
  62     private static final String CONFIG_FILE =
  63             "com.sun.management.config.file";
  64     private static final String SNMP_PORT =
  65             "com.sun.management.snmp.port";
  66     private static final String JMXREMOTE =
  67             "com.sun.management.jmxremote";
  68     private static final String JMXREMOTE_PORT =
  69             "com.sun.management.jmxremote.port";
  70     private static final String RMI_PORT =
  71             "com.sun.management.jmxremote.rmi.port";
  72     private static final String ENABLE_THREAD_CONTENTION_MONITORING =
  73             "com.sun.management.enableThreadContentionMonitoring";
  74     private static final String LOCAL_CONNECTOR_ADDRESS_PROP =
  75             "com.sun.management.jmxremote.localConnectorAddress";
  76     private static final String SNMP_ADAPTOR_BOOTSTRAP_CLASS_NAME =
  77             "sun.management.snmp.AdaptorBootstrap";
  78 
  79     private static final String JDP_DEFAULT_ADDRESS = "224.0.23.178";
  80     private static final int JDP_DEFAULT_PORT = 7095;
  81 
  82     // The only active agent allowed
  83     private static JMXConnectorServer jmxServer = null;
  84 
  85     // Parse string com.sun.management.prop=xxx,com.sun.management.prop=yyyy
  86     // and return property set if args is null or empty
  87     // return empty property set
  88     private static Properties parseString(String args) {
  89         Properties argProps = new Properties();
  90         if (args != null && !args.trim().equals("")) {
  91             for (String option : args.split(",")) {
  92                 String s[] = option.split("=", 2);
  93                 String name = s[0].trim();
  94                 String value = (s.length > 1) ? s[1].trim() : "";
  95 
  96                 if (!name.startsWith("com.sun.management.")) {
  97                     error(INVALID_OPTION, name);
  98                 }
  99 
 100                 argProps.setProperty(name, value);
 101             }
 102         }
 103 
 104         return argProps;
 105     }
 106 
 107     // invoked by -javaagent or -Dcom.sun.management.agent.class
 108     public static void premain(String args) throws Exception {
 109         agentmain(args);
 110     }
 111 
 112     // invoked by attach mechanism
 113     public static void agentmain(String args) throws Exception {
 114         if (args == null || args.length() == 0) {
 115             args = JMXREMOTE;           // default to local management
 116         }
 117 
 118         Properties arg_props = parseString(args);
 119 
 120         // Read properties from the config file
 121         Properties config_props = new Properties();
 122         String fname = arg_props.getProperty(CONFIG_FILE);
 123         readConfiguration(fname, config_props);
 124 
 125         // Arguments override config file
 126         config_props.putAll(arg_props);
 127         startAgent(config_props);
 128     }
 129 
 130     // jcmd ManagementAgent.start_local entry point
 131     // Also called due to command-line via startAgent()
 132     private static synchronized void startLocalManagementAgent() {
 133         Properties agentProps = VMSupport.getAgentProperties();
 134 
 135         // start local connector if not started
 136         if (agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP) == null) {
 137             JMXConnectorServer cs = ConnectorBootstrap.startLocalConnectorServer();
 138             String address = cs.getAddress().toString();
 139             // Add the local connector address to the agent properties
 140             agentProps.put(LOCAL_CONNECTOR_ADDRESS_PROP, address);
 141 
 142             try {
 143                 // export the address to the instrumentation buffer
 144                 ConnectorAddressLink.export(address);
 145             } catch (Exception x) {
 146                 // Connector server started but unable to export address
 147                 // to instrumentation buffer - non-fatal error.
 148                 warning(EXPORT_ADDRESS_FAILED, x.getMessage());
 149             }
 150         }
 151     }
 152 
 153     // jcmd ManagementAgent.start entry point
 154     // This method starts the remote JMX agent and starts neither
 155     // the local JMX agent nor the SNMP agent
 156     // @see #startLocalManagementAgent and also @see #startAgent.
 157     private static synchronized void startRemoteManagementAgent(String args) throws Exception {
 158         if (jmxServer != null) {
 159             throw new RuntimeException(getText(INVALID_STATE, "Agent already started"));
 160         }
 161 
 162         try {
 163             Properties argProps = parseString(args);
 164             Properties configProps = new Properties();
 165 
 166             // Load the management properties from the config file
 167             // if config file is not specified readConfiguration implicitly
 168             // reads <java.home>/conf/management/management.properties
 169 
 170             String fname = System.getProperty(CONFIG_FILE);
 171             readConfiguration(fname, configProps);
 172 
 173             // management properties can be overridden by system properties
 174             // which take precedence
 175             Properties sysProps = System.getProperties();
 176             synchronized (sysProps) {
 177                 configProps.putAll(sysProps);
 178             }
 179 
 180             // if user specifies config file into command line for either
 181             // jcmd utilities or attach command it overrides properties set in
 182             // command line at the time of VM start
 183             String fnameUser = argProps.getProperty(CONFIG_FILE);
 184             if (fnameUser != null) {
 185                 readConfiguration(fnameUser, configProps);
 186             }
 187 
 188             // arguments specified in command line of jcmd utilities
 189             // override both system properties and one set by config file
 190             // specified in jcmd command line
 191             configProps.putAll(argProps);
 192 
 193             // jcmd doesn't allow to change ThreadContentionMonitoring, but user
 194             // can specify this property inside config file, so enable optional
 195             // monitoring functionality if this property is set
 196             final String enableThreadContentionMonitoring =
 197                     configProps.getProperty(ENABLE_THREAD_CONTENTION_MONITORING);
 198 
 199             if (enableThreadContentionMonitoring != null) {
 200                 ManagementFactory.getThreadMXBean().
 201                         setThreadContentionMonitoringEnabled(true);
 202             }
 203 
 204             String jmxremotePort = configProps.getProperty(JMXREMOTE_PORT);
 205             if (jmxremotePort != null) {
 206                 jmxServer = ConnectorBootstrap.
 207                         startRemoteConnectorServer(jmxremotePort, configProps);
 208 
 209                 startDiscoveryService(configProps);
 210             } else {
 211                 throw new AgentConfigurationError(INVALID_JMXREMOTE_PORT, "No port specified");
 212             }
 213         } catch (JdpException e) {
 214             error(e);
 215         } catch (AgentConfigurationError err) {
 216             error(err.getError(), err.getParams());
 217         }
 218     }
 219 
 220     private static synchronized void stopRemoteManagementAgent() throws Exception {
 221 
 222         JdpController.stopDiscoveryService();
 223 
 224         if (jmxServer != null) {
 225             ConnectorBootstrap.unexportRegistry();
 226 
 227             // Attempt to stop already stopped agent
 228             // Don't cause any errors.
 229             jmxServer.stop();
 230             jmxServer = null;
 231         }
 232     }
 233 
 234     private static void startAgent(Properties props) throws Exception {
 235         String snmpPort = props.getProperty(SNMP_PORT);
 236         String jmxremote = props.getProperty(JMXREMOTE);
 237         String jmxremotePort = props.getProperty(JMXREMOTE_PORT);
 238 
 239         // Enable optional monitoring functionality if requested
 240         final String enableThreadContentionMonitoring =
 241                 props.getProperty(ENABLE_THREAD_CONTENTION_MONITORING);
 242         if (enableThreadContentionMonitoring != null) {
 243             ManagementFactory.getThreadMXBean().
 244                     setThreadContentionMonitoringEnabled(true);
 245         }
 246 
 247         try {
 248             if (snmpPort != null) {
 249                 loadSnmpAgent(snmpPort, props);
 250             }
 251 
 252             /*
 253              * If the jmxremote.port property is set then we start the
 254              * RMIConnectorServer for remote M&M.
 255              *
 256              * If the jmxremote or jmxremote.port properties are set then
 257              * we start a RMIConnectorServer for local M&M. The address
 258              * of this "local" server is exported as a counter to the jstat
 259              * instrumentation buffer.
 260              */
 261             if (jmxremote != null || jmxremotePort != null) {
 262                 if (jmxremotePort != null) {
 263                     jmxServer = ConnectorBootstrap.
 264                             startRemoteConnectorServer(jmxremotePort, props);
 265                     startDiscoveryService(props);
 266                 }
 267                 startLocalManagementAgent();
 268             }
 269 
 270         } catch (AgentConfigurationError e) {
 271             error(e.getError(), e.getParams());
 272         } catch (Exception e) {
 273             error(e);
 274         }
 275     }
 276 
 277     private static void startDiscoveryService(Properties props)
 278             throws IOException, JdpException {
 279         // Start discovery service if requested
 280         String discoveryPort = props.getProperty("com.sun.management.jdp.port");
 281         String discoveryAddress = props.getProperty("com.sun.management.jdp.address");
 282         String discoveryShouldStart = props.getProperty("com.sun.management.jmxremote.autodiscovery");
 283 
 284         // Decide whether we should start autodicovery service.
 285         // To start autodiscovery following conditions should be met:
 286         // autodiscovery==true OR (autodicovery==null AND jdp.port != NULL)
 287 
 288         boolean shouldStart = false;
 289         if (discoveryShouldStart == null){
 290             shouldStart = (discoveryPort != null);
 291         }
 292         else{
 293             try{
 294                shouldStart = Boolean.parseBoolean(discoveryShouldStart);
 295             } catch (NumberFormatException e) {
 296                 throw new AgentConfigurationError(AGENT_EXCEPTION, "Couldn't parse autodiscovery argument");
 297             }
 298         }
 299 
 300         if (shouldStart) {
 301             // port and address are required arguments and have no default values
 302             InetAddress address;
 303             try {
 304                 address = (discoveryAddress == null) ?
 305                         InetAddress.getByName(JDP_DEFAULT_ADDRESS) : InetAddress.getByName(discoveryAddress);
 306             } catch (UnknownHostException e) {
 307                 throw new AgentConfigurationError(AGENT_EXCEPTION, e, "Unable to broadcast to requested address");
 308             }
 309 
 310             int port = JDP_DEFAULT_PORT;
 311             if (discoveryPort != null) {
 312                try {
 313                   port = Integer.parseInt(discoveryPort);
 314                } catch (NumberFormatException e) {
 315                  throw new AgentConfigurationError(AGENT_EXCEPTION, "Couldn't parse JDP port argument");
 316                }
 317             }
 318 
 319             // Rebuilding service URL to broadcast it
 320             String jmxremotePort = props.getProperty(JMXREMOTE_PORT);
 321             String rmiPort = props.getProperty(RMI_PORT);
 322 
 323             JMXServiceURL url = jmxServer.getAddress();
 324             String hostname = url.getHost();
 325 
 326             String jmxUrlStr = (rmiPort != null)
 327                     ? String.format(
 328                     "service:jmx:rmi://%s:%s/jndi/rmi://%s:%s/jmxrmi",
 329                     hostname, rmiPort, hostname, jmxremotePort)
 330                     : String.format(
 331                     "service:jmx:rmi:///jndi/rmi://%s:%s/jmxrmi", hostname, jmxremotePort);
 332 
 333             String instanceName = props.getProperty("com.sun.management.jdp.name");
 334 
 335             JdpController.startDiscoveryService(address, port, instanceName, jmxUrlStr);
 336         }
 337     }
 338 
 339     public static Properties loadManagementProperties() {
 340         Properties props = new Properties();
 341 
 342         // Load the management properties from the config file
 343 
 344         String fname = System.getProperty(CONFIG_FILE);
 345         readConfiguration(fname, props);
 346 
 347         // management properties can be overridden by system properties
 348         // which take precedence
 349         Properties sysProps = System.getProperties();
 350         synchronized (sysProps) {
 351             props.putAll(sysProps);
 352         }
 353 
 354         return props;
 355     }
 356 
 357     public static synchronized Properties getManagementProperties() {
 358         if (mgmtProps == null) {
 359             String configFile = System.getProperty(CONFIG_FILE);
 360             String snmpPort = System.getProperty(SNMP_PORT);
 361             String jmxremote = System.getProperty(JMXREMOTE);
 362             String jmxremotePort = System.getProperty(JMXREMOTE_PORT);
 363 
 364             if (configFile == null && snmpPort == null
 365                     && jmxremote == null && jmxremotePort == null) {
 366                 // return if out-of-the-management option is not specified
 367                 return null;
 368             }
 369             mgmtProps = loadManagementProperties();
 370         }
 371         return mgmtProps;
 372     }
 373 
 374     private static void loadSnmpAgent(String snmpPort, Properties props) {
 375         try {
 376             // invoke the following through reflection:
 377             //     AdaptorBootstrap.initialize(snmpPort, props);
 378             final Class<?> adaptorClass =
 379                     Class.forName(SNMP_ADAPTOR_BOOTSTRAP_CLASS_NAME, true, null);
 380             final Method initializeMethod =
 381                     adaptorClass.getMethod("initialize",
 382                     String.class, Properties.class);
 383             initializeMethod.invoke(null, snmpPort, props);
 384         } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException x) {
 385             // snmp runtime doesn't exist - initialization fails
 386             throw new UnsupportedOperationException("Unsupported management property: " + SNMP_PORT, x);
 387         } catch (InvocationTargetException x) {
 388             final Throwable cause = x.getCause();
 389             if (cause instanceof RuntimeException) {
 390                 throw (RuntimeException) cause;
 391             } else if (cause instanceof Error) {
 392                 throw (Error) cause;
 393             }
 394             // should not happen...
 395             throw new UnsupportedOperationException("Unsupported management property: " + SNMP_PORT, cause);
 396         }
 397     }
 398 
 399     // read config file and initialize the properties
 400     private static void readConfiguration(String fname, Properties p) {
 401         if (fname == null) {
 402             String home = System.getProperty("java.home");
 403             if (home == null) {
 404                 throw new Error("Can't find java.home ??");
 405             }
 406             StringBuilder defaultFileName = new StringBuilder(home);
 407             defaultFileName.append(File.separator).append("conf");
 408             defaultFileName.append(File.separator).append("management");
 409             defaultFileName.append(File.separator).append("management.properties");
 410             // Set file name
 411             fname = defaultFileName.toString();
 412         }
 413         final File configFile = new File(fname);
 414         if (!configFile.exists()) {
 415             error(CONFIG_FILE_NOT_FOUND, fname);
 416         }
 417 
 418         InputStream in = null;
 419         try {
 420             in = new FileInputStream(configFile);
 421             BufferedInputStream bin = new BufferedInputStream(in);
 422             p.load(bin);
 423         } catch (FileNotFoundException e) {
 424             error(CONFIG_FILE_OPEN_FAILED, e.getMessage());
 425         } catch (IOException e) {
 426             error(CONFIG_FILE_OPEN_FAILED, e.getMessage());
 427         } catch (SecurityException e) {
 428             error(CONFIG_FILE_ACCESS_DENIED, fname);
 429         } finally {
 430             if (in != null) {
 431                 try {
 432                     in.close();
 433                 } catch (IOException e) {
 434                     error(CONFIG_FILE_CLOSE_FAILED, fname);
 435                 }
 436             }
 437         }
 438     }
 439 
 440     public static void startAgent() throws Exception {
 441         String prop = System.getProperty("com.sun.management.agent.class");
 442 
 443         // -Dcom.sun.management.agent.class not set so read management
 444         // properties and start agent
 445         if (prop == null) {
 446             // initialize management properties
 447             Properties props = getManagementProperties();
 448             if (props != null) {
 449                 startAgent(props);
 450             }
 451             return;
 452         }
 453 
 454         // -Dcom.sun.management.agent.class=<agent classname>:<agent args>
 455         String[] values = prop.split(":");
 456         if (values.length < 1 || values.length > 2) {
 457             error(AGENT_CLASS_INVALID, "\"" + prop + "\"");
 458         }
 459         String cname = values[0];
 460         String args = (values.length == 2 ? values[1] : null);
 461 
 462         if (cname == null || cname.length() == 0) {
 463             error(AGENT_CLASS_INVALID, "\"" + prop + "\"");
 464         }
 465 
 466         if (cname != null) {
 467             try {
 468                 // Instantiate the named class.
 469                 // invoke the premain(String args) method
 470                 Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(cname);
 471                 Method premain = clz.getMethod("premain",
 472                         new Class<?>[]{String.class});
 473                 premain.invoke(null, /* static */
 474                         new Object[]{args});
 475             } catch (ClassNotFoundException ex) {
 476                 error(AGENT_CLASS_NOT_FOUND, "\"" + cname + "\"");
 477             } catch (NoSuchMethodException ex) {
 478                 error(AGENT_CLASS_PREMAIN_NOT_FOUND, "\"" + cname + "\"");
 479             } catch (SecurityException ex) {
 480                 error(AGENT_CLASS_ACCESS_DENIED);
 481             } catch (Exception ex) {
 482                 String msg = (ex.getCause() == null
 483                         ? ex.getMessage()
 484                         : ex.getCause().getMessage());
 485                 error(AGENT_CLASS_FAILED, msg);
 486             }
 487         }
 488     }
 489 
 490     public static void error(String key) {
 491         String keyText = getText(key);
 492         System.err.print(getText("agent.err.error") + ": " + keyText);
 493         throw new RuntimeException(keyText);
 494     }
 495 
 496     public static void error(String key, String[] params) {
 497         if (params == null || params.length == 0) {
 498             error(key);
 499         } else {
 500             StringBuilder message = new StringBuilder(params[0]);
 501             for (int i = 1; i < params.length; i++) {
 502                 message.append(" " + params[i]);
 503             }
 504             error(key, message.toString());
 505         }
 506     }
 507 
 508     public static void error(String key, String message) {
 509         String keyText = getText(key);
 510         System.err.print(getText("agent.err.error") + ": " + keyText);
 511         System.err.println(": " + message);
 512         throw new RuntimeException(keyText + ": " + message);
 513     }
 514 
 515     public static void error(Exception e) {
 516         e.printStackTrace();
 517         System.err.println(getText(AGENT_EXCEPTION) + ": " + e.toString());
 518         throw new RuntimeException(e);
 519     }
 520 
 521     public static void warning(String key, String message) {
 522         System.err.print(getText("agent.err.warning") + ": " + getText(key));
 523         System.err.println(": " + message);
 524     }
 525 
 526     private static void initResource() {
 527         try {
 528             messageRB =
 529                     ResourceBundle.getBundle("sun.management.resources.agent");
 530         } catch (MissingResourceException e) {
 531             throw new Error("Fatal: Resource for management agent is missing");
 532         }
 533     }
 534 
 535     public static String getText(String key) {
 536         if (messageRB == null) {
 537             initResource();
 538         }
 539         try {
 540             return messageRB.getString(key);
 541         } catch (MissingResourceException e) {
 542             return "Missing management agent resource bundle: key = \"" + key + "\"";
 543         }
 544     }
 545 
 546     public static String getText(String key, String... args) {
 547         if (messageRB == null) {
 548             initResource();
 549         }
 550         String format = messageRB.getString(key);
 551         if (format == null) {
 552             format = "missing resource key: key = \"" + key + "\", "
 553                     + "arguments = \"{0}\", \"{1}\", \"{2}\"";
 554         }
 555         return MessageFormat.format(format, (Object[]) args);
 556     }
 557 }