196 lines
7.8 KiB
Python
196 lines
7.8 KiB
Python
# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
from monasca_agent.collector.checks import AgentCheck
|
|
|
|
# Reference: http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt
|
|
# Name Type Meaning
|
|
# ----------------------------------
|
|
# pid 32u Process id of this server process
|
|
# uptime 32u Number of seconds this server has been running
|
|
# time 32u current UNIX time according to the server
|
|
# version string Version string of this server
|
|
# pointer_size 32 Default size of pointers on the host OS
|
|
# (generally 32 or 64)
|
|
# rusage_user 32u:32u Accumulated user time for this process
|
|
# (seconds:microseconds)
|
|
# rusage_system 32u:32u Accumulated system time for this process
|
|
# (seconds:microseconds)
|
|
# curr_items 32u Current number of items stored by the server
|
|
# total_items 32u Total number of items stored by this server
|
|
# ever since it started
|
|
# bytes 64u Current number of bytes used by this server
|
|
# to store items
|
|
# curr_connections 32u Number of open connections
|
|
# total_connections 32u Total number of connections opened since
|
|
# the server started running
|
|
# connection_structures 32u Number of connection structures allocated
|
|
# by the server
|
|
# cmd_get 64u Cumulative number of retrieval requests
|
|
# cmd_set 64u Cumulative number of storage requests
|
|
# get_hits 64u Number of keys that have been requested and
|
|
# found present
|
|
# get_misses 64u Number of items that have been requested
|
|
# and not found
|
|
# evictions 64u Number of valid items removed from cache
|
|
# to free memory for new items
|
|
# bytes_read 64u Total number of bytes read by this server
|
|
# from network
|
|
# bytes_written 64u Total number of bytes sent by this server to
|
|
# network
|
|
# limit_maxbytes 32u Number of bytes this server is allowed to
|
|
# use for storage.
|
|
# threads 32u Number of worker threads requested.
|
|
# (see doc/threads.txt)
|
|
# >>> mc.get_stats()
|
|
# [('127.0.0.1:11211 (1)', {'pid': '2301', 'total_items': '2',
|
|
# 'uptime': '80', 'listen_disabled_num': '0', 'version': '1.2.8',
|
|
# 'limit_maxbytes': '67108864', 'rusage_user': '0.002532',
|
|
# 'bytes_read': '51', 'accepting_conns': '1', 'rusage_system':
|
|
# '0.007445', 'cmd_get': '0', 'curr_connections': '4', 'threads': '2',
|
|
# 'total_connections': '5', 'cmd_set': '2', 'curr_items': '0',
|
|
# 'get_misses': '0', 'cmd_flush': '0', 'evictions': '0', 'bytes': '0',
|
|
# 'connection_structures': '5', 'bytes_written': '25', 'time':
|
|
# '1306364220', 'pointer_size': '64', 'get_hits': '0'})]
|
|
|
|
# For Membase it gets worse
|
|
# http://www.couchbase.org/wiki/display/membase/Membase+Statistics
|
|
# https://github.com/membase/ep-engine/blob/master/docs/stats.org
|
|
|
|
|
|
class Memcache(AgentCheck):
|
|
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",
|
|
"evictions",
|
|
"bytes_read",
|
|
"bytes_written",
|
|
"total_connections"
|
|
]
|
|
|
|
@staticmethod
|
|
def get_library_versions():
|
|
try:
|
|
import memcache
|
|
version = memcache.__version__
|
|
except ImportError:
|
|
version = "Not Found"
|
|
except AttributeError:
|
|
version = "Unknown"
|
|
|
|
return {"memcache": version}
|
|
|
|
def _get_metrics(self, server, port, dimensions, memcache):
|
|
mc = None # client
|
|
try:
|
|
self.log.debug("Connecting to %s:%s dimensions:%s", server, port, dimensions)
|
|
mc = memcache.Client(["%s:%d" % (server, port)])
|
|
raw_stats = mc.get_stats()
|
|
|
|
assert len(raw_stats) == 1 and len(
|
|
raw_stats[0]) == 2, "Malformed response: %s" % raw_stats
|
|
# Access the dict
|
|
stats = raw_stats[0][1]
|
|
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]), dimensions=dimensions)
|
|
|
|
# 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(metric.lower() + "_rate", 'memcache')
|
|
self.rate(our_metric, float(stats[metric]), dimensions=dimensions)
|
|
|
|
# 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"]),
|
|
dimensions=dimensions,
|
|
)
|
|
except ZeroDivisionError:
|
|
pass
|
|
|
|
try:
|
|
self.gauge(
|
|
"memcache.fill_percent",
|
|
100.0 * float(stats["bytes"]) / float(stats["limit_maxbytes"]),
|
|
dimensions=dimensions,
|
|
)
|
|
except ZeroDivisionError:
|
|
pass
|
|
|
|
try:
|
|
self.gauge(
|
|
"memcache.avg_item_size",
|
|
float(stats["bytes"]) / float(stats["curr_items"]),
|
|
dimensions=dimensions,
|
|
)
|
|
except ZeroDivisionError:
|
|
pass
|
|
except AssertionError:
|
|
raise Exception("Unable to retrieve stats from memcache instance: " +
|
|
server + ":" + str(port) + ". Please check your configuration")
|
|
|
|
if mc is not None:
|
|
mc.disconnect_all()
|
|
self.log.debug("Disconnected from memcached")
|
|
del mc
|
|
|
|
def check(self, instance):
|
|
server = instance.get('url', None)
|
|
if not server:
|
|
raise Exception("Missing or null 'url' in mcache config")
|
|
|
|
try:
|
|
import memcache
|
|
except ImportError:
|
|
raise Exception(
|
|
"Cannot import memcache module. Check the instructions to install"
|
|
"this module at https://app.datadoghq.com/account/settings#integrations/mcache")
|
|
|
|
# Hacky monkeypatch to fix a memory leak in the memcache library.
|
|
# See https://github.com/DataDog/dd-agent/issues/278 for details.
|
|
try:
|
|
memcache.Client.debuglog = None
|
|
except Exception: # nosec
|
|
pass
|
|
|
|
port = int(instance.get('port', self.DEFAULT_PORT))
|
|
dimensions = self._set_dimensions(None, instance)
|
|
|
|
self._get_metrics(server, port, dimensions, memcache)
|