import agent_util
import logging
import sys
logger = logging.getLogger(__name__)
def parse_jmx_config(config):
"""
Split the jmx configuration into multiple instances based on the number
of comma separated instances there are.
:param config: Dict
:return: List of Dicts
"""
configs = {}
jvm_path = None
for key, value in config.items():
if key == 'jvm_path':
jvm_path = value
elif key == 'debug':
continue
else:
for i, inner in enumerate(value.split(',')):
if i not in configs.keys():
configs[i] = {}
if len(value) > 0:
configs[i][key] = inner.strip(' ')
for key in configs:
configs[key]['jvm_path'] = jvm_path
if sys.version_info >=(3,0):
return list(configs.values())
else:
return configs.values()
class JMXPlugin(agent_util.Plugin):
textkey = "jmx"
label = "JMX"
@classmethod
def get_metadata(self, config):
status = agent_util.SUPPORTED
msg = None
# Check for jmx configuration block
if not config:
self.log.info("No JMX configuration found")
return {}
configs = parse_jmx_config(config)
# Check for config setting sin jmx configuration block
invalid_configs = []
missing_keys = []
for entry in configs:
for key in ['port', 'host', 'jvm_path']:
if key not in entry.keys():
invalid_configs.append(entry)
missing_keys.append(key)
configs.remove(entry)
if len(invalid_configs) == len(configs):
msg = "Missing value for %s in the [jmx] block of the agent config file." % missing_keys
self.log.info(msg)
status = agent_util.MISCONFIGURED
try:
import jpype
except:
msg = "Unable to access JMX metrics due to missing jpype library."
self.log.info(msg)
status = agent_util.MISCONFIGURED
try:
if status == agent_util.SUPPORTED and not jpype.isJVMStarted(): jpype.startJVM(config['jvm_path'])
except:
msg = "Unable to access JMX metrics because JVM cannot be started."
self.log.info(msg)
status = agent_util.MISCONFIGURED
if status == agent_util.SUPPORTED:
invalid_configs = []
for entry in configs:
try:
from jpype import java, javax
jhash = java.util.HashMap()
if config.get('username') and config.get('password'):
jarray = jpype.JArray(java.lang.String)([config['username'], config['password']])
jhash.put(javax.management.remote.JMXConnector.CREDENTIALS, jarray)
url = "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi" % (entry['host'], int(entry['port']))
jmxurl = javax.management.remote.JMXServiceURL(url)
javax.management.remote.JMXConnectorFactory.connect(jmxurl, jhash)
except:
self.log.exception('Unable to connect to JMX %s' % str(entry))
invalid_configs.append(entry)
if len(invalid_configs) == len(configs):
msg = "Unable to access JMX metrics, JMX is not running or not installed. Check configs %s" % (
str(invalid_configs)
)
self.log.info(msg)
status = agent_util.MISCONFIGURED
metadata = {
"jmx.custom": {
"label": "Custom JMX Metric",
"options": None,
"status": status,
"error_message": msg,
"option_string": True
}
}
return metadata
def check(self, textkey, data, config):
try:
import jpype
from jpype import java, javax
configs = parse_jmx_config(config)
jvm_path = configs[0]['jvm_path']
try:
data, port = data.split('::')
except ValueError:
port = None
# No port came in the data.
if len(configs) > 1:
self.log.error(
"Port information missing from mBean. Unable to pick environment to execute. Aborting"
)
return
if port:
found = False
for config in configs:
if config['port'] == port:
found = True
break
if not found:
# Check sent a port that doesn't match.
self.log.error(
'Port %s is not one of the configured ones. Check cannot execute' % port
)
return
if not jpype.isJVMStarted():
jpype.startJVM(jvm_path)
jhash = java.util.HashMap()
if config.get('username') and config.get('password'):
jarray = jpype.JArray(java.lang.String)([config['username'], config['password']])
jhash.put(javax.management.remote.JMXConnector.CREDENTIALS, jarray)
url = "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi" % (config['host'], int(config['port']))
jmxurl = javax.management.remote.JMXServiceURL(url)
jmxsoc = javax.management.remote.JMXConnectorFactory.connect(jmxurl, jhash)
connection = jmxsoc.getMBeanServerConnection()
'''
Look for two Mbeans || separated and calculate the %.
The expected input will be: MBean1||MBean2. Example for MemoryHeapUsage:
java.lang:type=Memory/HeapMemoryUsage/used||java.lang:type=Memory/HeapMemoryUsage/committed
'''
if "||" in data:
data = data.split("||")
# Create a list from the two MBeans || separated.
calc = []
for i in data:
# Iterate the list to get each MBean value that will be set for the % calculation.
res = self._get_bean_value(i, connection)
if res is None:
return None
calc.append(res)
return 100*calc[0]/calc[1]
return self._get_bean_value(data, connection)
except:
self.log.exception("Error gathering JMX metric")
return
def _get_bean_value(self, bean_str, connection):
try:
from jpype import javax
last_slash = bean_str.rfind('/')
if -1 == last_slash:
return None
obj_name = bean_str[:last_slash]
java_obj = javax.management.ObjectName(obj_name)
attribute = bean_str[last_slash +1:]
#self.log.debug('Checking object name {} attr {}'.format(obj_name, attribute))
if connection.isRegistered(java_obj):
res = connection.getAttribute(java_obj, attribute)
return res.floatValue()
last_slash = obj_name.rfind('/')
if -1 == last_slash:
return None
key = attribute
next_obj_name = obj_name[:last_slash]
java_obj = javax.management.ObjectName(next_obj_name)
attribute = obj_name[last_slash+1:]
#self.log.debug('Checking object name {} attr {}'.format(next_obj_name, attribute))
if connection.isRegistered(java_obj):
res = connection.getAttribute(java_obj, attribute)
if hasattr(res, 'contents'):
res = res.contents.get(key)
else:
res = res.get(key)
return res.floatValue()
self.log.error('Could not find object name %s' % bean_str)
return None
except:
self.log.exception('_get_bean_value %s caught an exception' % bean_str)
return None