Skip to content
This repository was archived by the owner on Jun 2, 2021. It is now read-only.

Commit fcab31a

Browse files
author
Gavin M. Roy
committed
Add UWSGI support (MeetMe#54)
1 parent 2b5ceb0 commit fcab31a

File tree

4 files changed

+178
-1
lines changed

4 files changed

+178
-1
lines changed

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ You can also use a single mapping like follows:
100100
host: localhost
101101
foo: bar
102102

103-
The fields for plugin configurations can vary due to a plugin's configuration requirements.
103+
The fields for plugin configurations can vary due to a plugin's configuration requirements. The name value in each stanza is only required when using multiple targets in a plugin. If it is only a single target, the name will be taken from the server's hostname.
104104

105105
Apache HTTPd Installation Notes
106106
-------------------------------
@@ -202,6 +202,10 @@ Riak Installation Notes
202202
-----------------------
203203
If you are monitoring Riak via a HTTPS connection you can use the verify_ssl_cert configuration value in the httpd configuration section to disable SSL certificate verification.
204204

205+
UWSGI Installation Notes
206+
------------------------
207+
The UWSGI plugin can communicate either over UNIX domain sockets using the path configuration variable or TCP/IP using the host and port variables. Do not include both.
208+
205209
Configuration Example
206210
---------------------
207211

etc/newrelic/newrelic_plugin_agent.cfg

+5
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ Application:
101101
# verify_ssl_cert: true
102102
# port: 8098
103103

104+
#uwsgi:
105+
# name: localhost
106+
# host: localhost
107+
# port: 1717
108+
# path: /path/to/unix/socket
104109

105110
Daemon:
106111
user: newrelic

newrelic_plugin_agent/agent.py

+6
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,12 @@ def start_plugin_polling(self):
287287
self.poll_plugin(plugin, riak.Riak,
288288
self.application_config.get(plugin))
289289

290+
elif plugin == 'uwsgi':
291+
if 'uwsgi' not in globals():
292+
from newrelic_plugin_agent.plugins import uwsgi
293+
self.poll_plugin(plugin, uwsgi.UWSGI,
294+
self.application_config.get(plugin))
295+
290296
@property
291297
def threads_running(self):
292298
for thread in self.threads:
+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
"""
2+
UWSGI
3+
4+
"""
5+
import json
6+
import logging
7+
from os import path
8+
import socket
9+
import time
10+
11+
from newrelic_plugin_agent.plugins import base
12+
13+
LOGGER = logging.getLogger(__name__)
14+
15+
16+
class UWSGI(base.Plugin):
17+
18+
GUID = 'com.meetme.newrelic_uwsgi_agent'
19+
20+
DEFAULT_HOST = 'localhost'
21+
DEFAULT_PORT = 1717
22+
23+
SOCKET_RECV_MAX = 10485760
24+
25+
def add_datapoints(self, stats):
26+
"""Add all of the data points for a node
27+
28+
:param dict stats: all of the nodes
29+
30+
"""
31+
self.add_gauge_value('Listen Queue Size', '',
32+
stats.get('listen_queue', 0))
33+
self.add_gauge_value('Listen Queue Errors', '',
34+
stats.get('listen_queue_errors', 0))
35+
for lock in stats.get('locks', list()):
36+
lock_name = lock.keys()[0]
37+
self.add_gauge_value('Locks/%s' % lock_name, '', lock[lock_name])
38+
39+
exceptions = 0
40+
harakiris = 0
41+
requests = 0
42+
respawns = 0
43+
signals = 0
44+
45+
apps = dict()
46+
47+
for worker in stats.get('workers', list()):
48+
id = worker['id']
49+
50+
# totals
51+
exceptions += worker.get('exceptions', 0)
52+
harakiris += worker.get('harakiris', 0)
53+
requests += worker.get('requests', 0)
54+
respawns += worker.get('respawns', 0)
55+
signals += worker.get('signals', 0)
56+
57+
# Add the per worker
58+
self.add_derive_value('Worker/%s/Exceptions' % id, '',
59+
worker.get('exceptions', 0))
60+
self.add_derive_value('Worker/%s/Harakiri' % id, '',
61+
worker.get('harakiri_count', 0))
62+
self.add_derive_value('Worker/%s/Requests' % id, '',
63+
worker.get('requests', 0))
64+
self.add_derive_value('Worker/%s/Respawns' % id, '',
65+
worker.get('respawn_count', 0))
66+
self.add_derive_value('Worker/%s/Signals' % id, '',
67+
worker.get('signals', 0))
68+
69+
for app in worker['apps']:
70+
if app['id'] not in apps:
71+
apps[app['id']] = {'exceptions': 0,
72+
'requests': 0}
73+
apps[app['id']]['exceptions'] += app['exceptions']
74+
apps[app['id']]['requests'] += app['requests']
75+
76+
for app in apps:
77+
self.add_derive_value('Application/%s/Exceptions' % app, '',
78+
apps[app].get('exceptions', 0))
79+
self.add_derive_value('Application/%s/Requests' % app, '',
80+
apps[app].get('requests', 0))
81+
82+
self.add_derive_value('Summary/Applications', '', len(apps))
83+
self.add_derive_value('Summary/Exceptions', '', exceptions)
84+
self.add_derive_value('Summary/Harakiris', '', harakiris)
85+
self.add_derive_value('Summary/Requests', '', requests)
86+
self.add_derive_value('Summary/Respawns', '', respawns)
87+
self.add_derive_value('Summary/Signals', '', signals)
88+
self.add_derive_value('Summary/Workers', '',
89+
len(stats.get('workers', ())))
90+
91+
def connect(self):
92+
"""Top level interface to create a socket and connect it to the
93+
UWSGI daemon.
94+
95+
:rtype: socket
96+
97+
"""
98+
try:
99+
connection = self._connect()
100+
except socket.error as error:
101+
LOGGER.error('Error connecting to memcached: %s', error)
102+
else:
103+
return connection
104+
105+
def _connect(self):
106+
"""Low level interface to create a socket and connect it to the
107+
UWSGI daemon.
108+
109+
:rtype: socket
110+
111+
"""
112+
if 'path' in self.config:
113+
if path.exists(self.config['path']):
114+
LOGGER.debug('Connecting to UNIX socket: %s',
115+
self.config['path'])
116+
connection = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
117+
connection.connect(self.config['path'])
118+
else:
119+
LOGGER.error('UWSGI UNIX socket path does not exist: %s',
120+
self.config['path'])
121+
else:
122+
connection = socket.socket()
123+
connection.connect((self.config.get('host', self.DEFAULT_HOST),
124+
self.config.get('port', self.DEFAULT_PORT)))
125+
return connection
126+
127+
def fetch_data(self, connection):
128+
"""Read the data from the socket
129+
130+
:param socket connection: The connection
131+
132+
"""
133+
LOGGER.debug('Fetching data')
134+
data = connection.recv(self.SOCKET_RECV_MAX)
135+
return json.loads(data)
136+
137+
def poll(self):
138+
"""This method is called after every sleep interval. If the intention
139+
is to use an IOLoop instead of sleep interval based daemon, override
140+
the run method.
141+
142+
"""
143+
LOGGER.info('Polling UWSGI')
144+
start_time = time.time()
145+
146+
# Initialize the values each iteration
147+
self.derive = dict()
148+
self.gauge = dict()
149+
self.rate = dict()
150+
self.consumers = 0
151+
152+
# Fetch the data from Memcached
153+
connection = self.connect()
154+
data = self.fetch_data(connection)
155+
connection.close()
156+
if data:
157+
# Create all of the metrics
158+
self.add_datapoints(data)
159+
LOGGER.info('Polling complete in %.2f seconds',
160+
time.time() - start_time)
161+
else:
162+
LOGGER.error('Unsuccessful attempt to collect stats from UWSGI')

0 commit comments

Comments
 (0)