Repository URL to install this package:
|
Version:
1:7.26.0-1 ▾
|
datadog-agent
/
opt
/
datadog-agent
/
embedded
/
lib
/
python3.8
/
site-packages
/
datadog_checks
/
mcache
/
mcache.py
|
|---|
# (C) Datadog, Inc. 2018-present
# All rights reserved
# Licensed under Simplified BSD License (see LICENSE)
from __future__ import division
import bmemcached
import pkg_resources
from six import iteritems, itervalues
from datadog_checks.base import AgentCheck, ConfigurationError
class BadResponseError(ConfigurationError):
pass
class InvalidConfigError(ConfigurationError):
pass
class Memcache(AgentCheck):
SOURCE_TYPE_NAME = 'memcached'
DEFAULT_PORT = 11211
GAUGES = [
"total_items",
"curr_items",
"limit_maxbytes",
"uptime",
"bytes",
"curr_connections",
"connection_structures",
"threads",
"pointer_size",
]
RATES = [
"rusage_user",
"rusage_system",
"cmd_get",
"cmd_set",
"cmd_flush",
"get_hits",
"get_misses",
"delete_misses",
"delete_hits",
"evictions",
"bytes_read",
"bytes_written",
"cas_misses",
"cas_hits",
"cas_badval",
"total_connections",
"listen_disabled_num",
]
ITEMS_RATES = [
"evicted",
"evicted_nonzero",
"expired_unfetched",
"evicted_unfetched",
"outofmemory",
"tailrepairs",
"moves_to_cold",
"moves_to_warm",
"moves_within_lru",
"reclaimed",
"crawler_reclaimed",
"lrutail_reflocked",
"direct_reclaims",
]
ITEMS_GAUGES = ["number", "number_hot", "number_warm", "number_cold", "number_noexp", "age", "evicted_time"]
SLABS_RATES = [
"get_hits",
"cmd_set",
"delete_hits",
"incr_hits",
"decr_hits",
"cas_hits",
"cas_badval",
"touch_hits",
"used_chunks",
]
SLABS_GAUGES = [
"chunk_size",
"chunks_per_page",
"total_pages",
"total_chunks",
"used_chunks",
"free_chunks",
"free_chunks_end",
"mem_requested",
"active_slabs",
"total_malloced",
]
# format - "key": (rates, gauges, handler)
OPTIONAL_STATS = {"items": [ITEMS_RATES, ITEMS_GAUGES, None], "slabs": [SLABS_RATES, SLABS_GAUGES, None]}
SERVICE_CHECK = 'memcache.can_connect'
@classmethod
def get_library_versions(cls):
return {"memcache": pkg_resources.get_distribution("python-binary-memcached").version}
def _process_response(self, response):
"""
Examine the response and raise an error is something is off
"""
if len(response) != 1:
raise BadResponseError("Malformed response: {}".format(response))
stats = list(itervalues(response))[0]
if not len(stats):
raise BadResponseError("Malformed response for host: {}".format(stats))
if 'version' in stats:
self.set_metadata('version', stats['version'].decode('utf-8'))
else:
self.log.debug('Cannot determine Memcached version')
return stats
def _get_metrics(self, client, tags, service_check_tags=None):
try:
stats = self._process_response(client.stats())
for metric in stats:
# Check if metric is a gauge or rate
if metric in self.GAUGES:
our_metric = self.normalize(metric.lower(), 'memcache')
self.gauge(our_metric, float(stats[metric]), tags=tags)
# Tweak the name if it's a rate so that we don't use the exact
# same metric name as the memcache documentation
if metric in self.RATES:
our_metric = self.normalize("{0}_rate".format(metric.lower()), 'memcache')
self.rate(our_metric, float(stats[metric]), tags=tags)
# calculate some metrics based on other metrics.
# stats should be present, but wrap in try/except
# and log an exception just in case.
try:
self.gauge(
"memcache.get_hit_percent", 100.0 * float(stats["get_hits"]) / float(stats["cmd_get"]), tags=tags
)
except ZeroDivisionError:
pass
try:
self.gauge(
"memcache.fill_percent", 100.0 * float(stats["bytes"]) / float(stats["limit_maxbytes"]), tags=tags
)
except ZeroDivisionError:
pass
try:
self.gauge("memcache.avg_item_size", float(stats["bytes"]) / float(stats["curr_items"]), tags=tags)
except ZeroDivisionError:
pass
uptime = stats.get("uptime", 0)
self.service_check(
self.SERVICE_CHECK,
AgentCheck.OK,
tags=service_check_tags,
message="Server has been up for %s seconds" % uptime,
)
except BadResponseError:
raise
def _get_optional_metrics(self, client, tags, options=None):
for arg, metrics_args in iteritems(self.OPTIONAL_STATS):
if not options or options.get(arg, False):
try:
optional_rates = metrics_args[0]
optional_gauges = metrics_args[1]
optional_fn = metrics_args[2]
stats = self._process_response(client.stats(arg))
prefix = "memcache.{}".format(arg)
for metric, val in iteritems(stats):
# Check if metric is a gauge or rate
metric_tags = []
if optional_fn:
metric, metric_tags, val = optional_fn(metric, val)
if optional_gauges and metric in optional_gauges:
our_metric = self.normalize(metric.lower(), prefix)
self.gauge(our_metric, float(val), tags=tags + metric_tags)
if optional_rates and metric in optional_rates:
our_metric = self.normalize("{0}_rate".format(metric.lower()), prefix)
self.rate(our_metric, float(val), tags=tags + metric_tags)
except BadResponseError:
self.log.warning(
"Unable to retrieve optional stats from memcache instance, "
"running 'stats %s' they could be empty or bad configuration.",
arg,
)
except Exception as e:
self.log.exception("Unable to retrieve optional stats from memcache instance: %s", e)
@staticmethod
def get_items_stats(key, value):
"""
Optional metric handler for 'items' stats
key: "items:<slab_id>:<metric_name>" format
value: return untouched
Like all optional metric handlers returns metric, tags, value
"""
itemized_key = key.split(':')
slab_id = itemized_key[1]
metric = itemized_key[2]
tags = ["slab:{}".format(slab_id)]
return metric, tags, value
@staticmethod
def get_slabs_stats(key, value):
"""
Optional metric handler for 'items' stats
key: "items:<slab_id>:<metric_name>" format
value: return untouched
Like all optional metric handlers returns metric, tags, value
"""
slabbed_key = key.split(':')
tags = []
if len(slabbed_key) == 2:
slab_id = slabbed_key[0]
metric = slabbed_key[1]
tags = ["slab:{}".format(slab_id)]
else:
metric = slabbed_key[0]
return metric, tags, value
def check(self, instance):
socket = instance.get('socket')
server = instance.get('url')
options = instance.get('options', {})
username = instance.get('username')
password = instance.get('password')
if not server and not socket:
raise InvalidConfigError('Either "url" or "socket" must be configured')
if socket:
server = 'unix'
port = socket
connection_server = "{}".format(port)
else:
port = int(instance.get('port', self.DEFAULT_PORT))
connection_server = "{}:{}".format(server, port)
custom_tags = instance.get('tags') or []
tags = ["url:{0}:{1}".format(server, port)] + custom_tags
service_check_tags = ["host:%s" % server, "port:%s" % port] + custom_tags
try:
self.log.debug("Connecting to %s, tags:%s", connection_server, tags)
client = bmemcached.Client(connection_server, username, password)
self._get_metrics(client, tags, service_check_tags)
if options:
# setting specific handlers
self.OPTIONAL_STATS["items"][2] = Memcache.get_items_stats
self.OPTIONAL_STATS["slabs"][2] = Memcache.get_slabs_stats
self._get_optional_metrics(client, tags, options)
except BadResponseError as e:
self.service_check(
self.SERVICE_CHECK,
AgentCheck.CRITICAL,
tags=service_check_tags,
message="Unable to fetch stats from server",
)
raise ConfigurationError(
"Unable to retrieve stats from memcache instance: {}:{}."
"Please check your configuration. ({})".format(server, port, e)
)
else:
client.disconnect_all()
self.log.debug("Disconnected from memcached")
del client