monasca-agent/monagent/collector/checks_d/couchbase.py

140 lines
5.2 KiB
Python

import json
import urllib2
import re
import sys
from monagent.collector.checks import AgentCheck
from monagent.collector.checks.utils import add_basic_auth
from monagent.common.util import headers
# Constants
COUCHBASE_STATS_PATH = '/pools/nodes'
DEFAULT_TIMEOUT = 10
class Couchbase(AgentCheck):
"""Extracts stats from Couchbase via its REST API
http://docs.couchbase.com/couchbase-manual-2.0/#using-the-rest-api
"""
def _create_metrics(self, data, dimensions=None):
storage_totals = data['stats']['storageTotals']
for key, storage_type in storage_totals.items():
for metric_name, val in storage_type.items():
if val is not None:
metric_name = '.'.join(
['couchbase', key, self.camel_case_to_joined_lower(metric_name)])
self.gauge(metric_name, val, dimensions=dimensions)
for bucket_name, bucket_stats in data['buckets'].items():
for metric_name, val in bucket_stats.items():
if val is not None:
metric_name = '.'.join(
['couchbase', 'by_bucket', self.camel_case_to_joined_lower(metric_name)])
metric_dimensions = dimensions.copy()
metric_dimensions['bucket'] = bucket_name
self.gauge(
metric_name, val[0], dimensions=metric_dimensions, device_name=bucket_name)
for node_name, node_stats in data['nodes'].items():
for metric_name, val in node_stats['interestingStats'].items():
if val is not None:
metric_name = '.'.join(
['couchbase', 'by_node', self.camel_case_to_joined_lower(metric_name)])
metric_dimensions = dimensions.copy()
metric_dimensions['node'] = node_name
self.gauge(
metric_name, val, dimensions=metric_dimensions, device_name=node_name)
def _get_stats(self, url, instance):
"""Hit a given URL and return the parsed json.
"""
self.log.debug('Fetching Couchbase stats at url: %s' % url)
req = urllib2.Request(url, None, headers(self.agent_config))
if 'user' in instance and 'password' in instance:
add_basic_auth(req, instance['user'], instance['password'])
if instance['is_recent_python']:
timeout = instance.get('timeout', DEFAULT_TIMEOUT)
request = urllib2.urlopen(req, timeout=timeout)
else:
request = urllib2.urlopen(req)
response = request.read()
return json.loads(response)
def check(self, instance):
server = instance.get('server', None)
if server is None:
raise Exception("The server must be specified")
dimensions = instance.get('dimensions', {})
dimensions['instance'] = server
instance['is_recent_python'] = sys.version_info >= (2, 6, 0)
data = self.get_data(server, instance)
self._create_metrics(data, dimensions=dimensions)
def get_data(self, server, instance):
# The dictionary to be returned.
couchbase = {'stats': None,
'buckets': {},
'nodes': {}
}
# build couchbase stats entry point
url = '%s%s' % (server, COUCHBASE_STATS_PATH)
overall_stats = self._get_stats(url, instance)
# No overall stats? bail out now
if overall_stats is None:
raise Exception("No data returned from couchbase endpoint: %s" % url)
couchbase['stats'] = overall_stats
nodes = overall_stats['nodes']
# Next, get all the nodes
if nodes is not None:
for node in nodes:
couchbase['nodes'][node['hostname']] = node
# Next, get all buckets .
endpoint = overall_stats['buckets']['uri']
url = '%s%s' % (server, endpoint)
buckets = self._get_stats(url, instance)
if buckets is not None:
for bucket in buckets:
bucket_name = bucket['name']
# We have to manually build the URI for the stats bucket, as this is not
# auto discoverable
url = '%s/pools/nodes/buckets/%s/stats' % (server, bucket_name)
bucket_stats = self._get_stats(url, instance)
bucket_samples = bucket_stats['op']['samples']
if bucket_samples is not None:
couchbase['buckets'][bucket['name']] = bucket_samples
return couchbase
# Takes a camelCased variable and returns a joined_lower equivalent.
# Returns input if non-camelCase variable is detected.
def camel_case_to_joined_lower(self, variable):
# replace non-word with _
converted_variable = re.sub('\W+', '_', variable)
# insert _ in front of capital letters and lowercase the string
converted_variable = re.sub('([A-Z])', '_\g<1>', converted_variable).lower()
# remove duplicate _
converted_variable = re.sub('_+', '_', converted_variable)
# handle special case of starting/ending underscores
converted_variable = re.sub('^_|_$', '', converted_variable)
return converted_variable