Simple python script to access jmx via jolokia
root
I’ve never been much of a python guy. Now there’s this production environment where we’ve got a few basic things like bash, ruby (since we’re using puppet) and python. Our Java based software has a nice JMX interface with a bunch of lifesavers in it and each JVM has the jolokia agent running (http/json access to jmx) so we can access this goodness fairly easily from scripts and/or the shell. So far what we’d be doing would be something like this:
$ curl http://server:port/jolokia/read/java.lang:type=OperatingSystem
This would return a ton of json and is very hard to read. Later we found out there’s this niftly little python module and so we get nicely (and readable) json output by just piping it trough python:
$ curl http://server:port/jolokia/read/java.lang:type=OperatingSystem | python -m json.tool
So far so good. Now because I’m lazy and don’t like typing or even copying these urls into a shell, a bash script was born, which was a collection of calls like you see above. You’d source the script and use the functions contained in it like they were system commands. Pretty nice, I thought. Until we ran into a problem that needed checking if something had a certain state and then changing that state, thus actually working with json in a bash – good luck with that!
Since we’ve already used that nifty json.tool I ventured into using python for this and it turned out it’s pretty damn awesome. We can now call a jolokia url, work with the return value and do some more calls based on that. I did that using the subprocess module and calling the shell functions but that’s ugly. So I went ahead and ported the shell script to python.
What you see below is a python script that can be used either as a module or directly from the commandline. Thanks to python’s cool inspection/reflection capabilities (As a java guy I’m used to that anyway), you can extend this script by simply adding another url and correspondingly named function. If something’s off it tries to print an error as good as possible (includes argument names, how cool is that!) and if everything’s right it just works as you’d expect.
I hope there are other jolokia users out there who will find this useful. And to the python wizards: Let me know if and how I can improve on this script (especially the main function).
So yeah, I’ve never been much of a python guy – until now. Enjoy my first python script!
#!/usr/bin/python
import inspect
import json
import sys
import time
import urllib2
# for each function below there must be a key with the function's name in the
# urls dictionary to lookup the url for each call
urls = {}
urls['totalPhysicalMemory'] = 'http://{host:s}/jolokia/read/java.lang:type=OperatingSystem/TotalPhysicalMemorySize'
urls['javaLangOperatingSystem'] = 'http://{host:s}/jolokia/read/java.lang:type=OperatingSystem'
urls['gc'] = 'http://{host:s}/jolokia/exec/java.lang:type=Memory/gc'
urls['memoryVerbose'] = 'http://{host:s}/jolokia/write/java.lang:type=Memory/Verbose/{verbose:s}'
# this is where the magic happens
# we take the function name of the caller of _httpCall and use that to lookup
# the url in the urls dictionary. then we'll use the args which is another
# dictionary (named list of the caller's arguments) and use them to format the
# url string, call the url and return whatever we get back as parsed json
def _httpCall(args):
cmd = inspect.stack()[1][3]
url = urls[cmd].format( **args )
request = urllib2.Request(url)
opener = urllib2.build_opener()
stream = opener.open(request)
return json.load(stream)
# here a few demo calls, of course one function can call another and do some
# calculations on the returned result. to further demonstrate the generic
# features of this script (and jolokia) we'll also do some write/exec calls
# that require more than just 1 parameter
def javaLangOperatingSystem(host):
return _httpCall(locals())
def totalPhysicalMemory(host):
return _httpCall(locals())
def totalPhysicalMemoryMB(host):
return "%dMB" % (totalPhysicalMemory(host)['value'] / (1024 * 1024))
def gc(host):
return _httpCall(locals())
def memoryVerbose(host, verbose):
return _httpCall(locals())
# the main method is here to call the functions from the shell. for that, you
# must call it like this:
# $ ./pythonrules.py <hostname> <functionname> [optional args]
# thus:
# argv[0] = script name
# argv[1] = host:port => 1st argument to each function
# argv[2] = internal python function to call
# argv[3..n] = remaining arguments(if any) to the function
# this script can also be imported as a module and called directly from other
# python code. the returned data can of course go trough further processing
def main(argv=None):
if argv is None:
argv = sys.argv
theGlobals = globals()
if len(argv) < 3:
print '!!! usage: %s <host> <function> [function args]' % argv[0]
print '!!! where host may also be \'host:port\''
print '!!! available functions are:'
for globName in theGlobals:
if callable(theGlobals[globName]) and not globName.startswith('_') and not globName.startswith('main'):
print ' %s%s' %(globName, theGlobals[globName].func_code.co_varnames)
return 10
funcName = argv[2]
try:
func = theGlobals[funcName]
if not callable(func):
raise Exception('not callable: ' + funcName)
except Exception, e:
print '!!! Exception: ' + str(e)
print '!!! %s is not a valid function name in this script' % funcName
return 11
try:
callArgs = {}
argNames = func.func_code.co_varnames
cidx = 0
for idx, val in enumerate(argv):
if idx != 0 and idx != 2:
callArgs[argNames[cidx]] = argv[idx]
cidx += 1
ret = func(**callArgs)
print json.dumps(ret, ensure_ascii=True, indent=4)
return 0
except Exception, e:
print '!!! Exception: ' + str(e)
print '!!! arguments are: ' + str(argNames)
return 12
return 1
if __name__ == '__main__':
sys.exit(main())