-
Notifications
You must be signed in to change notification settings - Fork 814
/
Copy pathnginx.py
172 lines (146 loc) · 6.06 KB
/
nginx.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# stdlib
import re
import urlparse
# 3rd party
import requests
import simplejson as json
# project
from checks import AgentCheck
from util import headers
class Nginx(AgentCheck):
"""Tracks basic nginx metrics via the status module
* number of connections
* number of requets per second
Requires nginx to have the status option compiled.
See http://wiki.nginx.org/HttpStubStatusModule for more details
$ curl http://localhost:81/nginx_status/
Active connections: 8
server accepts handled requests
1156958 1156958 4491319
Reading: 0 Writing: 2 Waiting: 6
"""
def check(self, instance):
if 'nginx_status_url' not in instance:
raise Exception('NginX instance missing "nginx_status_url" value.')
tags = instance.get('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')
auth = None
if 'user' in instance and 'password' in instance:
auth = (instance['user'], instance['password'])
# Submit a service check for status page availability.
parsed_url = urlparse.urlparse(url)
nginx_host = parsed_url.hostname
nginx_port = parsed_url.port or 80
service_check_name = 'nginx.can_connect'
service_check_tags = ['host:%s' % nginx_host, 'port:%s' % nginx_port]
try:
r = requests.get(url, auth=auth, headers=headers(self.agentConfig))
r.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)
body = r.content
resp_headers = r.headers
return body, resp_headers.get('content-type', 'text/plain')
@classmethod
def parse_text(cls, raw, tags):
# Thanks to http://hostingfu.com/files/nginx/nginxstats.py for this code
# Connections
output = []
parsed = re.search(r'Active connections:\s+(\d+)', raw)
if parsed:
connections = int(parsed.group(1))
output.append(('nginx.net.connections', connections, tags, 'gauge'))
# Requests per second
parsed = re.search(r'\s*(\d+)\s+(\d+)\s+(\d+)', raw)
if parsed:
conn = int(parsed.group(1))
handled = int(parsed.group(2))
requests = int(parsed.group(3))
output.extend([('nginx.net.conn_opened_per_s', conn, tags, 'rate'),
('nginx.net.conn_dropped_per_s', conn - handled, 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+)', raw)
if parsed:
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