From a30fe1d79dfa90811b5d4664b4599df231421e33 Mon Sep 17 00:00:00 2001 From: Major Hayden Date: Thu, 27 Apr 2017 07:35:45 -0500 Subject: [PATCH] Add memcache plugin This patch adds a memcache plugin along with tests and documentation. Implements: blueprint monitorstack Change-Id: Iefb0c0912078713c26387e02e135d70d7a96a7dc --- doc/source/plugins-base/plugin-memcache.rst | 105 ++++++++++++++++++++ monitorstack/plugins/memcache.py | 57 +++++++++++ requirements.txt | 1 + tests/test_plugin_memcache.py | 78 +++++++++++++++ 4 files changed, 241 insertions(+) create mode 100644 doc/source/plugins-base/plugin-memcache.rst create mode 100644 monitorstack/plugins/memcache.py create mode 100644 tests/test_plugin_memcache.py diff --git a/doc/source/plugins-base/plugin-memcache.rst b/doc/source/plugins-base/plugin-memcache.rst new file mode 100644 index 0000000..2ed69e2 --- /dev/null +++ b/doc/source/plugins-base/plugin-memcache.rst @@ -0,0 +1,105 @@ +``memcache`` - get statistics from a memcache server +==================================================== + +The memcache plugin connects to a memcache server to retrieve statistics. + +Usage +----- + +The plugin has two optional arguments: + +* ``host`` - the hostname or IP address of the memcache server +* ``port`` - the port number of the memcache server + +.. code-block:: console + + Usage: monitorstack memcache [OPTIONS] + + Get memcached stats. + + Options: + --host TEXT memcached host to query + --port INTEGER memcached server port + --help Show this message and exit. + + +Example +------- + +Run the plugin: + +.. code-block:: console + + $ monitorstack memcache + +Example output in JSON format: + +.. code-block:: json + + { + "variables": { + "auth_cmds": 0, + "crawler_items_checked": 0, + "reclaimed": 0, + "get_expired": 0, + "curr_items": 0, + "pid": 24627, + "malloc_fails": 0, + "time_in_listen_disabled_us": 0, + "expired_unfetched": 0, + "hash_is_expanding": false, + "cas_hits": 0, + "uptime": 8, + "touch_hits": 0, + "delete_misses": 0, + "listen_disabled_num": 0, + "cas_misses": 0, + "decr_hits": 0, + "cmd_touch": 0, + "incr_hits": 0, + "version": "1.4.33", + "limit_maxbytes": 67108864, + "total_items": 0, + "bytes_written": 0, + "incr_misses": 0, + "accepting_conns": 1, + "rusage_system": 0.014981, + "log_watcher_sent": 0, + "get_flushed": 0, + "cmd_get": 0, + "curr_connections": 4, + "log_worker_written": 0, + "log_watcher_skipped": 0, + "touch_misses": 0, + "threads": 4, + "total_connections": 5, + "cmd_set": 0, + "libevent": "2.0.22-stable", + "conn_yields": 0, + "get_misses": 0, + "reserved_fds": 20, + "bytes_read": 8, + "hash_bytes": 524288, + "evicted_unfetched": 0, + "cas_badval": 0, + "cmd_flush": 0, + "lrutail_reflocked": 0, + "evictions": 0, + "bytes": 0, + "crawler_reclaimed": 0, + "connection_structures": 5, + "hash_power_level": 16, + "log_worker_dropped": 0, + "auth_errors": 0, + "rusage_user": 0.005598, + "time": 1493240773, + "delete_hits": 0, + "pointer_size": 64, + "decr_misses": 0, + "get_hits": 0 + }, + "message": "memcached is ok", + "meta": {}, + "exit_code": 0, + "measurement_name": "memcache" + } diff --git a/monitorstack/plugins/memcache.py b/monitorstack/plugins/memcache.py new file mode 100644 index 0000000..35511e0 --- /dev/null +++ b/monitorstack/plugins/memcache.py @@ -0,0 +1,57 @@ +# Copyright 2017, Major Hayden +# +# 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. +"""Get memcached stats.""" +import click + +from monitorstack import utils +from monitorstack.cli import pass_context + +from pymemcache.client.base import Client + +DOC = """Get memcached stats.""" +COMMAND_NAME = 'memcache' + + +@click.command(COMMAND_NAME, short_help=DOC) +@click.option('--host', help='memcached host to query', default='127.0.0.1') +@click.option('--port', help='memcached server port', default=11211) +@pass_context +def cli(ctx, host, port): + """Get memcached stats.""" + output = { + 'exit_code': 0, + 'message': 'memcached is ok', + 'measurement_name': 'memcache', + 'meta': {}, + 'variables': {} + } + + # Connect to memcache and retrieve our stats + try: + stats = get_memcached_stats(host, port) + output['variables'] = stats + except Exception as exp: + output['exit_code'] = 1 + output['message'] = '{} failed -- {}'.format( + COMMAND_NAME, + utils.log_exception(exp=exp) + ) + + return output + + +def get_memcached_stats(host, port): + """Connect to memcache server for stats.""" + conn = Client((host, port)) + return conn.stats() diff --git a/requirements.txt b/requirements.txt index 7f8733b..9a4811c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ click diskcache openstacksdk>=0.9.14 +pymemcache>=1.2.9,!=1.3.0 # Apache 2.0 License psutil>=5.2.0 six stevedore diff --git a/tests/test_plugin_memcache.py b/tests/test_plugin_memcache.py new file mode 100644 index 0000000..75cbe6c --- /dev/null +++ b/tests/test_plugin_memcache.py @@ -0,0 +1,78 @@ +# Copyright 2017, Major Hayden +# +# 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. +"""Tests for the memcache plugin.""" +from monitorstack.plugins import memcache as monitorstack_memcache + +import pymemcache + +import tests + + +class TestMemcache(object): + """Tests for the memcache plugin.""" + + def test_success(self, monkeypatch): + """Ensure the run() method works.""" + def mock_get_memcached_stats(host, port): + """Mock the get_memcached_stats() method.""" + return {'parameter': 'value'} + + monkeypatch.setattr( + monitorstack_memcache, + 'get_memcached_stats', + mock_get_memcached_stats + ) + result = tests.runner('memcache') + assert result['variables']['parameter'] == 'value' + assert result['measurement_name'] == "memcache" + assert result['exit_code'] == 0 + + def test_failure(self, monkeypatch): + """Ensure the run() method works.""" + def mock_get_memcached_stats(host, port): + """Mock the get_memcached_stats() method.""" + raise Exception('Connection failed') + + monkeypatch.setattr( + monitorstack_memcache, + 'get_memcached_stats', + mock_get_memcached_stats + ) + result = tests.runner('memcache') + assert 'Connection failed' in result['message'] + assert result['measurement_name'] == "memcache" + assert result['exit_code'] == 1 + + def test_get_memcached_stats(self, monkeypatch): + """Ensure that get_memcached_stats() works.""" + def mock_memcache_client(cls, (conn_tuple)): + """Mock a memcache client class.""" + return None + + def mock_memcache_stats(toot): + """Mock a memcache client class.""" + return {'parameter': 'value'} + + monkeypatch.setattr( + pymemcache.client.base.Client, + '__init__', + mock_memcache_client + ) + monkeypatch.setattr( + pymemcache.client.base.Client, + 'stats', + mock_memcache_stats + ) + result = monitorstack_memcache.get_memcached_stats('localhost', 11211) + assert result['parameter'] == 'value'