# (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)
