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

Couchbase plugin #457

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ NewRelic platform. Currently supported backend systems are:
- Alternative PHP Cache
- Apache HTTP Server
- CouchDB
- Couchbase
- Elasticsearch
- HAProxy
- Memcached
Expand Down Expand Up @@ -298,6 +299,11 @@ Configuration Example
#username: foo
#password: bar

couchbase:
name: localhost
host: localhost
port: 8091

elasticsearch:
name: clustername
host: localhost
Expand Down
5 changes: 5 additions & 0 deletions docker/base/newrelic-plugin-agent.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ Application:
# username: foo
# password: bar

#couchbase:
# name: localhost
# host: localhost
# port: 8091

#elasticsearch:
# name: Clustername
# host: localhost
Expand Down
5 changes: 5 additions & 0 deletions etc/newrelic/newrelic-plugin-agent.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ Application:
# username: foo
# password: bar

#couchbase:
# name: localhost
# host: localhost
# port: 8091

#elasticsearch:
# name: Clustername
# host: localhost
Expand Down
1 change: 1 addition & 0 deletions newrelic_plugin_agent/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
available = {
'apache_httpd': 'newrelic_plugin_agent.plugins.apache_httpd.ApacheHTTPD',
'couchdb': 'newrelic_plugin_agent.plugins.couchdb.CouchDB',
'couchbase': 'newrelic_plugin_agent.plugins.couchbase.Couchbase',
'edgecast': 'newrelic_plugin_agent.plugins.edgecast.Edgecast',
'elasticsearch':
'newrelic_plugin_agent.plugins.elasticsearch.ElasticSearch',
Expand Down
144 changes: 144 additions & 0 deletions newrelic_plugin_agent/plugins/couchbase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
"""
couchbase

"""
from newrelic_plugin_agent.plugins import base


class Couchbase(base.JSONStatsPlugin):

GUID = 'com.meetme.newrelic_couchbase_agent'

# metrics are according to api reference v3.0 or v3.1
METRICS = [
{'type': 'cluster', 'label': 'storageTotals.ram.total', 'suffix': 'bytes'},
{'type': 'cluster', 'label': 'storageTotals.ram.used', 'suffix': 'bytes'},
{'type': 'cluster', 'label': 'storageTotals.ram.usedByData', 'suffix': 'bytes'},
{'type': 'cluster', 'label': 'storageTotals.ram.quotaTotal', 'suffix': 'bytes'},
{'type': 'cluster', 'label': 'storageTotals.ram.quotaUsed', 'suffix': 'bytes'},

{'type': 'cluster', 'label': 'storageTotals.hdd.total', 'suffix': 'bytes'},
{'type': 'cluster', 'label': 'storageTotals.hdd.used', 'suffix': 'bytes'},
{'type': 'cluster', 'label': 'storageTotals.hdd.usedByData', 'suffix': 'bytes'},
{'type': 'cluster', 'label': 'storageTotals.hdd.quotaTotal', 'suffix': 'bytes'},
{'type': 'cluster', 'label': 'storageTotals.hdd.free', 'suffix': 'bytes'},

{'type': 'cluster', 'label': 'counters.rebalance_success', 'suffix': 'count'},
{'type': 'cluster', 'label': 'counters.rebalance_start', 'suffix': 'count'},
{'type': 'cluster', 'label': 'counters.rebalance_fail', 'suffix': 'count'},
{'type': 'cluster', 'label': 'counters.rebalance_node', 'suffix': 'count'},

{'type': 'nodes', 'label': 'systemStats.cpu_utilization_rate', 'suffix': 'count'},
{'type': 'nodes', 'label': 'systemStats.swap_total', 'suffix': 'byte'},
{'type': 'nodes', 'label': 'systemStats.swap_used', 'suffix': 'byte'},
{'type': 'nodes', 'label': 'systemStats.mem_total', 'suffix': 'byte'},
{'type': 'nodes', 'label': 'systemStats.mem_free', 'suffix': 'byte'},

{'type': 'nodes', 'label': 'interestingStats.couch_docs_actual_disk_size', 'suffix': 'byte'},
{'type': 'nodes', 'label': 'interestingStats.couch_docs_data_size', 'suffix': 'byte'},
{'type': 'nodes', 'label': 'interestingStats.couch_views_actual_disk_size', 'suffix': 'byte'},
{'type': 'nodes', 'label': 'interestingStats.couch_views_data_size', 'suffix': 'byte'},
{'type': 'nodes', 'label': 'interestingStats.mem_used', 'suffix': 'byte'},
{'type': 'nodes', 'label': 'interestingStats.ops', 'suffix': 'count'},
{'type': 'nodes', 'label': 'interestingStats.curr_items', 'suffix': 'count'},
{'type': 'nodes', 'label': 'interestingStats.curr_items_tot', 'suffix': 'count'},
{'type': 'nodes', 'label': 'interestingStats.vb_replica_curr_items', 'suffix': 'count'},

{'type': 'nodes', 'scoreboard': True, 'score_value': 'active', 'label': 'clusterMembership', 'suffix': 'count'},
{'type': 'nodes', 'scoreboard': True, 'score_value': 'healthy', 'label': 'status', 'suffix': 'count'},

{'type': 'buckets', 'label': 'basicStats.quotaPercentUsed', 'suffix': 'percent'},
{'type': 'buckets', 'label': 'basicStats.opsPerSec', 'suffix': 'ops'},
{'type': 'buckets', 'label': 'basicStats.diskFetches', 'suffix': 'percent'},
{'type': 'buckets', 'label': 'basicStats.itemCount', 'suffix': 'percent'},
{'type': 'buckets', 'label': 'basicStats.diskUsed', 'suffix': 'byte'},
{'type': 'buckets', 'label': 'basicStats.dataUsed', 'suffix': 'byte'},
{'type': 'buckets', 'label': 'basicStats.memUsed', 'suffix': 'byte'},
]

def add_datapoints(self, data):
"""Add data points for all couchbase nodes.

:param dict stats: stats for all nodes

"""
# fetch metrics for each metric type (cluster, nodes, buckets)
for typ, stats in data.iteritems():
# set items to fetch stats from,
# and set item key name, where the item's name will be fetched from
if typ == 'cluster':
items = stats
name_key = 'name'
elif typ == 'nodes':
items = stats['nodes']
name_key = 'hostname'
elif typ == 'buckets':
items = stats
name_key = 'name'

for metric in [m for m in self.METRICS if m['type'] == typ]:
# count score for scoreboard metrics,
# otherwise just add the gauge value from the API repsonse
#
# A "scoreboard" metric is a counter of a specific value for
# a field. E.g How many times the pair {"status": "healthy"}
# is found in a list of objects.
#
# NOTE that scoreboard metrics are meant to be used on lists
# of objects e.g. a list of nodes.
if metric.get('scoreboard', False):
self.add_gauge_value(
'%s/_scoreboard/%s' % (typ, metric['label']),
metric['suffix'],
sum(d[metric['label']] == metric['score_value']
for d in items))
else:
# add gauge value for current metric.
# NOTE cluster metrics are not repeated,
# as there is a single cluster.
# nodes and bucket metrics are repeated,
# as there are (usually) several of them
if typ == 'cluster':

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are quite a few branching in this for loop. maybe it would be cleaner to have a for loop per typ

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there isn't much way around it, currently this is the easiest to grasp imo

self._add_gauge_value(
metric, typ, items[name_key], items)
else:
for item in items:
self._add_gauge_value(
metric, typ, item[name_key], item)

def _add_gauge_value(self, metric, typ, name, items):
"""Adds a gauge value for a nested metric.

Some stats are missing from memcached bucket types,
thus we use dict.get()

:param dict m: metric as defined at the top of this class.
:param str typ: metric type: cluster, nodes, buckets.
:param str name: cluster, node, or bucket name.
:param dict items: stats to lookup the metric in.

"""
label = metric['label']
value = items
for key in label.split('.'):
value = value.get(key, 0)
self.add_gauge_value('%s/%s/%s' % (typ, name, label),
metric['suffix'], value)

def fetch_data(self):
"""Fetch data from multiple couchbase stats URLs.

Returns a dictionary with three keys: cluster, nodes, buckets.
Each key holds the JSON response from the API request.

:rtype: dict

"""
data = {}
for path, typ in [('pools/default', 'cluster'),
('pools/nodes', 'nodes'),
('pools/default/buckets', 'buckets')]:
res = self.http_get(self.stats_url + path)
data[typ] = res and res.json() or {}

return data