Last active
May 28, 2019 05:03
-
-
Save hajimeni/7e6d780d74df2963cca3c600f349fce2 to your computer and use it in GitHub Desktop.
DataDog Jolokia AgentCheck
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# stdlib | |
import re | |
import copy | |
# 3rd party | |
import requests | |
# project | |
from checks import AgentCheck | |
class JolokiaCheck(AgentCheck): | |
"""Tracks customized jolokia metrics via the status module | |
config example | |
--- | |
init_configs: [] | |
instances: | |
- host: 192.168.65.1 | |
port: 3333 | |
beans: | |
- { "mbean": "com.zaxxer.hikari:type=Pool (*" } ## -> jolokia.com_zaxxer_hikari_type_pool_db.active_connections | |
- { "mbean": "com.zaxxer.hikari:type=Pool (*", "mbean_re":"com\\.zaxxer\\.hikari:type=Pool\\ \\((?P<name>.+)\\)", "metric_key": "hikaricp_pool.{name}.{attribute}" } ## -> jolokia.hiraricp_pool.db.active_connections | |
tags: | |
- "host:hoge" | |
- "jolokia:test" | |
""" | |
DEFAULT_BEANS= [ | |
{ 'mbean': 'java.lang:type=Threading', 'attribute': ['ThreadCount','TotalStartedThreadCount'], 'metric_key': '{type}.{attribute}', 'mbean_re': 'java.lang:type=(?P<type>Threading)' }, | |
{ 'mbean': 'java.lang:name=*,type=MemoryPool', 'attribute': ['Usage'], 'metric_key': '{type}.{name}.{attribute}.{key}', 'mbean_re': 'java.lang:name=(?P<name>.+),type=(?P<type>MemoryPool)' }, | |
{ 'mbean': 'java.lang:name=*,type=GarbageCollector', 'attribute': 'CollectionTime,CollectionCount', 'metric_key': '{type}.{name}.{attribute}', 'mbean_re': 'java.lang:name=(?P<name>.+),type=(?P<type>GarbageCollector)' }, | |
] | |
JOLOKIA_PREFIX = 'jolokia' | |
def check(self, instance): | |
self.log.debug('======== instance:{}'.format(instance)) | |
if 'host' not in instance and 'port' not in instance: | |
raise Exception('Jolokia instance missing "host" or "port" value.') | |
beans = [] | |
beans.extend(JolokiaCheck.DEFAULT_BEANS) | |
beans.extend(instance.get('beans', [])) | |
payload = [self.__to_payload_cassette(b) for b in beans] | |
host = instance['host'] | |
port = instance['port'] | |
tags = instance.get('tags', []) | |
service_check_tags = ['host:%s' % host, 'port:%s' % port] | |
service_check_name = JolokiaCheck.JOLOKIA_PREFIX + '.can_connect' | |
base_url = 'http://{}:{}/jolokia/'.format(host, port) | |
try: | |
res = requests.post(base_url, json=payload) | |
res.raise_for_status() | |
except Exception: | |
self.service_check(service_check_name, AgentCheck.CRITICAL, | |
tags=service_check_tags) | |
raise | |
else: | |
self.service_check(service_check_name, AgentCheck.OK, | |
tags=service_check_tags) | |
json_data = res.json() | |
res_values = [] | |
if len(json_data) != len(beans): | |
raise Exception('Response length not equals to request') | |
for info, bean in zip(json_data, beans): | |
if not 'value' in info: | |
self.log.warning('Jolokia response error: {}'.format(info)) | |
continue | |
value = info['value'] | |
if '*' in bean['mbean']: | |
for mbean_name, attr_name_value in value.items(): | |
for attr_name, attr_value in attr_name_value.items(): | |
res_values.extend(self.__extract_attr_value(mbean_name, attr_name, attr_value, bean)) | |
else: | |
for attr_name, attr_value in value.items(): | |
res_values.extend(self.__extract_attr_value(bean['mbean'], attr_name, attr_value, bean)) | |
funcs = { | |
'gauge': self.gauge, | |
'rate': self.rate, | |
'count': self.count | |
} | |
for v in res_values: | |
func = funcs[v['metric_func']] | |
func(str(v['metric_name']), v['value'], tags) | |
return | |
def __extract_attr_value(self, mbean_name, attr_name, attr_value, bean): | |
res = [] | |
d_template = { | |
'mbean': JolokiaCheck.normalize_bean_name(mbean_name), | |
'attribute': JolokiaCheck.normalize_bean_name(attr_name) | |
} | |
if 'mbean_re' in bean: | |
match = re.match(bean['mbean_re'], mbean_name) | |
if match: | |
for k, v in match.groupdict().items(): | |
d_template[k] = JolokiaCheck.normalize_bean_name(v) | |
metric_key = bean.get('metric_key', '{mbean}.{attribute}') | |
if isinstance(attr_value, dict): | |
for key, value in attr_value.items(): | |
if 'include_keys' in bean and not key in bean['include_keys']: | |
continue | |
if 'exlude_keys' in bean and key in bean['exclude_keys']: | |
continue | |
res.append( | |
{ | |
'metric_name': JolokiaCheck.JOLOKIA_PREFIX + '.' + metric_key.format(key=key, **d_template), | |
'value': value, | |
'metric_func': bean.get('metric_type', 'gauge') | |
} | |
) | |
else: | |
res.append( | |
{ | |
'metric_name': JolokiaCheck.JOLOKIA_PREFIX + '.' + metric_key.format(**d_template), | |
'value': attr_value, | |
'metric_func': bean.get('metric_type', 'gauge') | |
} | |
) | |
return res | |
def __to_payload_cassette(self, bean): | |
cassette = { | |
'type': 'read', | |
'mbean': bean['mbean'] | |
} | |
if 'attribute' in bean: | |
attr = bean['attribute'] | |
if isinstance(attr, str): | |
attr = attr.split(',') | |
cassette['attribute'] = attr | |
if 'path' in bean: | |
cassette['path'] = bean['path'] | |
return cassette | |
@classmethod | |
def normalize_bean_name(cls, name): | |
if not name: | |
return None | |
n = TO_SNAKECASE_RE.sub(r'_\1', name).lower().strip('_') ## camplecase to snakecase | |
return TO_NORMALIZE_RE.sub('_', n) ## replace not alphanumeric to underscore | |
TO_SNAKECASE_RE = re.compile('((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))') | |
TO_NORMALIZE_RE = re.compile('[^a-zA-Z0-9]+|__+') | |
if __name__ == '__main__': | |
check, instances = JolokiaCheck.from_yaml('/etc/dd-agent/checks.d/jolokia.yaml') | |
for instance in instances: | |
print "\nRunning the check against url: %s" % (instance['host']) | |
check.check(instance) | |
if check.has_events(): | |
print 'Events: %s' % (check.get_events()) | |
ms = check.get_metrics() | |
ms.sort() | |
print 'Metrics:================(count={})'.format(len(ms)) | |
for e in ms: | |
print('{}'.format(e)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment