Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update nginx check to support nginx plus status format #876

Merged
merged 1 commit into from
Apr 2, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 101 additions & 17 deletions checks.d/nginx.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import re
import urllib2

from util import headers
from util import headers, json
from checks import AgentCheck
from checks.utils import add_basic_auth

Expand All @@ -25,41 +25,125 @@ def check(self, instance):
raise Exception('NginX instance missing "nginx_status_url" value.')
tags = instance.get('tags', [])

response = self._get_data(instance)
self._get_metrics(response, tags)
response, content_type = self._get_data(instance)
if content_type == 'application/json':
metrics = self.parse_json(response, tags)
else:
metrics = self.parse_text(response, tags)

funcs = {
'gauge': self.gauge,
'rate': self.rate
}
for row in metrics:
try:
name, value, tags, metric_type = row
func = funcs[metric_type]
func(name, value, tags)
except Exception:
self.log.error(u'Could not submit metric: %s' % repr(row))

def _get_data(self, instance):
url = instance.get('nginx_status_url')
req = urllib2.Request(url, None, headers(self.agentConfig))
if 'user' in instance and 'password' in instance:
add_basic_auth(req, instance['user'], instance['password'])
request = urllib2.urlopen(req)
return request.read()

response = urllib2.urlopen(req)
body = response.read()
resp_headers = response.info()
return body, resp_headers.get('Content-Type', 'text/plain')

def _get_metrics(self, response, tags):
@classmethod
def parse_text(cls, raw, tags):
# Thanks to http://hostingfu.com/files/nginx/nginxstats.py for this code
# Connections
parsed = re.search(r'Active connections:\s+(\d+)', response)
output = []
parsed = re.search(r'Active connections:\s+(\d+)', raw)
if parsed:
connections = int(parsed.group(1))
self.gauge("nginx.net.connections", connections, tags=tags)
output.append(('nginx.net.connections', connections, tags, 'gauge'))

# Requests per second
parsed = re.search(r'\s*(\d+)\s+(\d+)\s+(\d+)', response)
parsed = re.search(r'\s*(\d+)\s+(\d+)\s+(\d+)', raw)
if parsed:
conn = int(parsed.group(1))
requests = int(parsed.group(3))
self.rate("nginx.net.conn_opened_per_s", conn, tags=tags)
self.rate("nginx.net.request_per_s", requests, tags=tags)
output.extend([('nginx.net.conn_opened_per_s', conn, tags, 'rate'),
('nginx.net.request_per_s', requests, tags, 'rate')])

# Connection states, reading, writing or waiting for clients
parsed = re.search(r'Reading: (\d+)\s+Writing: (\d+)\s+Waiting: (\d+)', response)
parsed = re.search(r'Reading: (\d+)\s+Writing: (\d+)\s+Waiting: (\d+)', raw)
if parsed:
reading, writing, waiting = map(int, parsed.groups())
self.gauge("nginx.net.reading", reading, tags=tags)
self.gauge("nginx.net.writing", writing, tags=tags)
self.gauge("nginx.net.waiting", waiting, tags=tags)
reading, writing, waiting = parsed.groups()
output.extend([
("nginx.net.reading", int(reading), tags, 'gauge'),
("nginx.net.writing", int(writing), tags, 'gauge'),
("nginx.net.waiting", int(waiting), tags, 'gauge'),
])
return output

@classmethod
def parse_json(cls, raw, tags=None):
if tags is None:
tags = []
parsed = json.loads(raw)
metric_base = 'nginx'
output = []
all_keys = parsed.keys()

tagged_keys = [('caches', 'cache'), ('server_zones', 'server_zone'),
('upstreams', 'upstream')]

# Process the special keys that should turn into tags instead of
# getting concatenated to the metric name
for key, tag_name in tagged_keys:
metric_name = '%s.%s' % (metric_base, tag_name)
for tag_val, data in parsed.get(key, {}).iteritems():
tag = '%s:%s' % (tag_name, tag_val)
output.extend(cls._flatten_json(metric_name, data, tags + [tag]))

# Process the rest of the keys
rest = set(all_keys) - set([k for k, _ in tagged_keys])
for key in rest:
metric_name = '%s.%s' % (metric_base, key)
output.extend(cls._flatten_json(metric_name, parsed[key], tags))

return output

@classmethod
def _flatten_json(cls, metric_base, val, tags):
''' Recursively flattens the nginx json object. Returns the following:
[(metric_name, value, tags)]
'''
output = []
if isinstance(val, dict):
# Pull out the server as a tag instead of trying to read as a metric
if 'server' in val and val['server']:
server = 'server:%s' % val.pop('server')
if tags is None:
tags = [server]
else:
tags = tags + [server]
for key, val2 in val.iteritems():
metric_name = '%s.%s' % (metric_base, key)
output.extend(cls._flatten_json(metric_name, val2, tags))

elif isinstance(val, list):
for val2 in val:
output.extend(cls._flatten_json(metric_base, val2, tags))

elif isinstance(val, bool):
# Turn bools into 0/1 values
if val:
val = 1
else:
val = 0
output.append((metric_base, val, tags, 'gauge'))

elif isinstance(val, (int, float)):
output.append((metric_base, val, tags, 'gauge'))

return output

@staticmethod
def parse_agent_config(agentConfig):
Expand Down
3 changes: 3 additions & 0 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,6 @@ def get_check(name, config_str):

return check_class.from_yaml(yaml_text=config_str, check_name=name,
agentConfig=agentConfig)

def read_test_data(filename):
return open(os.path.join(os.path.dirname(__file__), 'data', filename)).read()
Loading