ansible-roles/couchdb/files/couchdb.py

335 lines
12 KiB
Python

### This script reports couchdb metrics to ganglia.
### License to use, modify, and distribute under the GPL
### http://www.gnu.org/licenses/gpl.txt
import logging
import os
import subprocess
import sys
import threading
import time
import traceback
import urllib2
import json
logging.basicConfig(level=logging.ERROR)
_Worker_Thread = None
class UpdateCouchdbThread(threading.Thread):
def __init__(self, params):
threading.Thread.__init__(self)
self.running = False
self.shuttingdown = False
self.refresh_rate = int(params['refresh_rate'])
self.metrics = {}
self.settings = {}
self.stats_url = params['stats_url']
self.stats_url_username = params['stats_url_username']
self.stats_url_password = params['stats_url_password']
self._metrics_lock = threading.Lock()
self._settings_lock = threading.Lock()
def shutdown(self):
self.shuttingdown = True
if not self.running:
return
self.join()
def run(self):
global _Lock
self.running = True
while not self.shuttingdown:
time.sleep(self.refresh_rate)
self.refresh_metrics()
self.running = False
@staticmethod
def _get_couchdb_stats(url, username, password, refresh_rate):
if refresh_rate == 60 or refresh_rate == 300 or refresh_rate == 900:
url += '?range=' + str(refresh_rate)
else:
logging.warning('The specified refresh_rate of %d is invalid and has been substituted with 60!' % refresh_rate)
url += '?range=60'
if username != "":
passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
passman.add_password(None, url, username, password)
urllib2.install_opener(urllib2.build_opener(urllib2.HTTPBasicAuthHandler(passman)))
request = urllib2.Request(url)
# Set time out for urlopen to 2 seconds otherwise we run into the possibility of hosing gmond
c = urllib2.urlopen(request, None, 2)
json_data = c.read()
c.close()
data = json.loads(json_data)
couchdb = data['couchdb']
httpd = data['httpd']
request_methods = data['httpd_request_methods']
status_codes = data['httpd_status_codes']
result = {}
for first_level_key in data:
for second_level_key in data[first_level_key]:
value = data[first_level_key][second_level_key]['current']
if value is None:
value = 0
else:
if second_level_key in ['open_databases', 'open_os_files', 'clients_requesting_changes']:
print second_level_key + ': ' + str(value)
value = int(value)
else:
# We need to devide by the range as couchdb provides no per second values
value = float(value) / refresh_rate
result['couchdb_' + first_level_key + '_' + second_level_key ] = value
return result
def refresh_metrics(self):
logging.debug('refresh metrics')
try:
logging.debug(' opening URL: ' + str(self.stats_url))
data = UpdateCouchdbThread._get_couchdb_stats(self.stats_url, self.stats_url_username, self.stats_url_password, self.refresh_rate)
except:
logging.warning('error refreshing metrics')
logging.warning(traceback.print_exc(file=sys.stdout))
try:
self._metrics_lock.acquire()
self.metrics = {}
for k, v in data.items():
self.metrics[k] = v
except:
logging.warning('error refreshing metrics')
logging.warning(traceback.print_exc(file=sys.stdout))
return False
finally:
self._metrics_lock.release()
if not self.metrics:
logging.warning('error refreshing metrics')
return False
logging.debug('success refreshing metrics')
logging.debug('metrics: ' + str(self.metrics))
return True
def metric_of(self, name):
logging.debug('getting metric: ' + name)
try:
if name in self.metrics:
try:
self._metrics_lock.acquire()
logging.debug('metric: %s = %s' % (name, self.metrics[name]))
return self.metrics[name]
finally:
self._metrics_lock.release()
except:
logging.warning('failed to fetch ' + name)
return 0
def setting_of(self, name):
logging.debug('getting setting: ' + name)
try:
if name in self.settings:
try:
self._settings_lock.acquire()
logging.debug('setting: %s = %s' % (name, self.settings[name]))
return self.settings[name]
finally:
self._settings_lock.release()
except:
logging.warning('failed to fetch ' + name)
return 0
def metric_init(params):
logging.debug('init: ' + str(params))
global _Worker_Thread
METRIC_DEFAULTS = {
'units': 'requests/s',
'groups': 'couchdb',
'slope': 'both',
'value_type': 'float',
'format': '%.3f',
'description': '',
'call_back': metric_of
}
descriptions = dict(
couchdb_couchdb_auth_cache_hits={
'units': 'hits/s',
'description': 'Number of authentication cache hits'},
couchdb_couchdb_auth_cache_misses={
'units': 'misses/s',
'description': 'Number of authentication cache misses'},
couchdb_couchdb_database_reads={
'units': 'reads/s',
'description': 'Number of times a document was read from a database'},
couchdb_couchdb_database_writes={
'units': 'writes/s',
'description': 'Number of times a document was changed'},
couchdb_couchdb_open_databases={
'value_type': 'uint',
'format': '%d',
'units': 'databases',
'description': 'Number of open databases'},
couchdb_couchdb_open_os_files={
'value_type': 'uint',
'format': '%d',
'units': 'files',
'description': 'Number of file descriptors CouchDB has open'},
couchdb_couchdb_request_time={
'units': 'ms',
'description': 'Request time'},
couchdb_httpd_bulk_requests={
'description': 'Number of bulk requests'},
couchdb_httpd_clients_requesting_changes={
'value_type': 'uint',
'format': '%d',
'units': 'clients',
'description': 'Number of clients for continuous _changes'},
couchdb_httpd_requests={
'description': 'Number of HTTP requests'},
couchdb_httpd_temporary_view_reads={
'units': 'reads',
'description': 'Number of temporary view reads'},
couchdb_httpd_view_reads={
'description': 'Number of view reads'},
couchdb_httpd_request_methods_COPY={
'description': 'Number of HTTP COPY requests'},
couchdb_httpd_request_methods_DELETE={
'description': 'Number of HTTP DELETE requests'},
couchdb_httpd_request_methods_GET={
'description': 'Number of HTTP GET requests'},
couchdb_httpd_request_methods_HEAD={
'description': 'Number of HTTP HEAD requests'},
couchdb_httpd_request_methods_POST={
'description': 'Number of HTTP POST requests'},
couchdb_httpd_request_methods_PUT={
'description': 'Number of HTTP PUT requests'},
couchdb_httpd_status_codes_200={
'units': 'responses/s',
'description': 'Number of HTTP 200 OK responses'},
couchdb_httpd_status_codes_201={
'units': 'responses/s',
'description': 'Number of HTTP 201 Created responses'},
couchdb_httpd_status_codes_202={
'units': 'responses/s',
'description': 'Number of HTTP 202 Accepted responses'},
couchdb_httpd_status_codes_301={
'units': 'responses/s',
'description': 'Number of HTTP 301 Moved Permanently responses'},
couchdb_httpd_status_codes_304={
'units': 'responses/s',
'description': 'Number of HTTP 304 Not Modified responses'},
couchdb_httpd_status_codes_400={
'units': 'responses/s',
'description': 'Number of HTTP 400 Bad Request responses'},
couchdb_httpd_status_codes_401={
'units': 'responses/s',
'description': 'Number of HTTP 401 Unauthorized responses'},
couchdb_httpd_status_codes_403={
'units': 'responses/s',
'description': 'Number of HTTP 403 Forbidden responses'},
couchdb_httpd_status_codes_404={
'units': 'responses/s',
'description': 'Number of HTTP 404 Not Found responses'},
couchdb_httpd_status_codes_405={
'units': 'responses/s',
'description': 'Number of HTTP 405 Method Not Allowed responses'},
couchdb_httpd_status_codes_409={
'units': 'responses/s',
'description': 'Number of HTTP 409 Conflict responses'},
couchdb_httpd_status_codes_412={
'units': 'responses/s',
'description': 'Number of HTTP 412 Precondition Failed responses'},
couchdb_httpd_status_codes_500={
'units': 'responses/s',
'description': 'Number of HTTP 500 Internal Server Error responses'})
if _Worker_Thread is not None:
raise Exception('Worker thread already exists')
_Worker_Thread = UpdateCouchdbThread(params)
_Worker_Thread.refresh_metrics()
_Worker_Thread.start()
descriptors = []
for name, desc in descriptions.iteritems():
d = desc.copy()
d['name'] = str(name)
[ d.setdefault(key, METRIC_DEFAULTS[key]) for key in METRIC_DEFAULTS.iterkeys() ]
descriptors.append(d)
return descriptors
def metric_of(name):
global _Worker_Thread
return _Worker_Thread.metric_of(name)
def setting_of(name):
global _Worker_Thread
return _Worker_Thread.setting_of(name)
def metric_cleanup():
global _Worker_Thread
if _Worker_Thread is not None:
_Worker_Thread.shutdown()
logging.shutdown()
pass
if __name__ == '__main__':
from optparse import OptionParser
try:
logging.debug('running from the cmd line')
parser = OptionParser()
parser.add_option('-u', '--URL', dest='stats_url', default='http://127.0.0.1:5984/_stats', help='URL for couchdb stats page')
parser.add_option('-U', '--user', dest='stats_url_username', default='')
parser.add_option('-P', '--password', dest='stats_url_password', default='')
parser.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False)
parser.add_option('-r', '--refresh-rate', dest='refresh_rate', default=60)
parser.add_option('-d', '--debug', dest='debug', action='store_true', default=False)
(options, args) = parser.parse_args()
descriptors = metric_init({
'stats_url': options.stats_url,
'stats_url_username': options.stats_url_username,
'stats_url_password': options.stats_url_password,
'refresh_rate': options.refresh_rate
})
if options.debug:
from pprint import pprint
pprint(descriptors)
for d in descriptors:
v = d['call_back'](d['name'])
if not options.quiet:
print ' {0}: {1} {2} [{3}]' . format(d['name'], v, d['units'], d['description'])
os._exit(1)
except KeyboardInterrupt:
time.sleep(0.2)
os._exit(1)
except StandardError:
traceback.print_exc()
os._exit(1)
finally:
metric_cleanup()