From 2f061879cd0bda590b45fe1d82095b73f43f2041 Mon Sep 17 00:00:00 2001 From: Federico Ceratto Date: Fri, 27 May 2016 17:05:13 +0100 Subject: [PATCH] Add Monasca-statsd metric generation Statsd integration is disabled by default. Add a simple local daemon for testing/devstack use. Change-Id: I990472fa059afde37f7e4a1284360c17162aab49 --- designate/mdns/notify.py | 4 +- designate/mdns/xfr.py | 2 + designate/metrics.py | 112 ++++++++++++++++++++++++++++ designate/service.py | 2 + devstack/statsd_mock_server.py | 60 +++++++++++++++ doc/source/index.rst | 2 +- doc/source/metrics.rst | 15 ++++ etc/designate/designate.conf.sample | 11 +++ requirements.txt | 1 + 9 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 designate/metrics.py create mode 100755 devstack/statsd_mock_server.py create mode 100644 doc/source/metrics.rst diff --git a/designate/mdns/notify.py b/designate/mdns/notify.py index ad731c2c5..b7c3632ea 100644 --- a/designate/mdns/notify.py +++ b/designate/mdns/notify.py @@ -28,9 +28,10 @@ import dns.opcode from oslo_config import cfg from oslo_log import log as logging -from designate.mdns import base from designate.i18n import _LI from designate.i18n import _LW +from designate.mdns import base +from designate.metrics import metrics dns_query = eventlet.import_patched('dns.query') @@ -42,6 +43,7 @@ class NotifyEndpoint(base.BaseEndpoint): RPC_API_VERSION = '2.0' RPC_API_NAMESPACE = 'notify' + @metrics.timed('mdns.notify_zone_changed') def notify_zone_changed(self, context, zone, host, port, timeout, retry_interval, max_retries, delay): """ diff --git a/designate/mdns/xfr.py b/designate/mdns/xfr.py index c2b4b43e0..2e07b403e 100644 --- a/designate/mdns/xfr.py +++ b/designate/mdns/xfr.py @@ -20,6 +20,7 @@ from oslo_log import log as logging from designate import dnsutils from designate import exceptions from designate.mdns import base +from designate.metrics import metrics LOG = logging.getLogger(__name__) @@ -29,6 +30,7 @@ class XFRMixin(object): """ Utility mixin that holds common methods for XFR functionality. """ + @metrics.timed('mdns.xfr.zone_sync') def zone_sync(self, context, zone, servers=None): servers = servers or zone.masters servers = servers.to_list() diff --git a/designate/metrics.py b/designate/metrics.py new file mode 100644 index 000000000..894239d15 --- /dev/null +++ b/designate/metrics.py @@ -0,0 +1,112 @@ +# Copyright 2016 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. + +""" +Monasca-Statsd based metrics +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Based on metrics-and-stats blueprint + +Usage examples: + +.. code-block:: python + + from designate.metrics import metrics + + @metrics.timed('dot.separated.name') + def your_function(): + pass + + with metrics.time('dot.separated.name'): + pass + + # Increment and decrement a counter. + metrics.counter(name='foo.bar').increment() + metrics.counter(name='foo.bar') -= 10 + +""" + +import monascastatsd +from oslo_config import cfg +from oslo_log import log as logging + +from designate.i18n import _LI + +LOG = logging.getLogger(__name__) + +CFG_GROUP = 'monasca:statsd' +cfg.CONF.register_group(cfg.OptGroup( + name=CFG_GROUP, title="Configuration for Monasca Statsd" +)) + +cfg.CONF.register_opts([ + cfg.BoolOpt('enabled', default=False, help='enable'), + cfg.IntOpt('port', default=8125, help='UDP port'), + cfg.StrOpt('hostname', default='127.0.0.1', help='hostname') +], group=CFG_GROUP) + + +# Global metrics client to be imported by other modules +metrics = None + + +class Metrics(object): + + def __init__(self): + """Initialize Monasca-Statsd client with its default configuration. + Do not start sending metrics yet. + """ + self._client = monascastatsd.Client(dimensions={ + 'service_name': 'dns' + }) + # cfg.CONF is not available at this time + # Buffer all metrics until init() is called + # https://bugs.launchpad.net/monasca/+bug/1616060 + self._client.connection.open_buffer() + self._client.connection.max_buffer_size = 50000 + + def init(self): + """Setup client connection or disable metrics based on configuration. + This is called once the cfg.CONF is ready. + """ + conf = cfg.CONF[CFG_GROUP] + if conf.enabled: + LOG.info(_LI("Statsd reports to %(host)s %(port)d") % { + 'host': conf.hostname, + 'port': conf.port + }) + self._client.connection._flush_buffer() + self._client.connection.close_buffer() + self._client.connection.connect(conf.hostname, conf.port) + else: + LOG.info(_LI("Statsd disabled")) + # The client cannot be disabled: mock out report() + self._client.connection.report = lambda *a, **kw: None + # There's no clean way to drain the outgoing buffer + + def counter(self, *a, **kw): + return self._client.get_counter(*a, **kw) + + def gauge(self, *a, **kw): + return self._client.get_gauge(*a, **kw) + + @property + def timed(self): + return self._client.get_timer().timed + + def timer(self): + return self._client.get_timer() + + +metrics = Metrics() diff --git a/designate/service.py b/designate/service.py index 1515d4f59..44438b39e 100644 --- a/designate/service.py +++ b/designate/service.py @@ -35,6 +35,7 @@ from designate.i18n import _ from designate.i18n import _LE from designate.i18n import _LI from designate.i18n import _LW +from designate.metrics import metrics from designate import policy from designate import rpc from designate import service_status @@ -74,6 +75,7 @@ class Service(service.Service): self._service_config = CONF['service:%s' % self.service_name] policy.init() + metrics.init() # NOTE(kiall): All services need RPC initialized, as this is used # for clients AND servers. Hence, this is common to diff --git a/devstack/statsd_mock_server.py b/devstack/statsd_mock_server.py new file mode 100755 index 000000000..d65a45816 --- /dev/null +++ b/devstack/statsd_mock_server.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# Copyright 2016 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. + +""" +A simple mock UDP server to receive monasca-statsd traffic +Log to stdout or to a file. +""" + +from argparse import ArgumentParser +from time import gmtime +from time import strftime +import SocketServer + + +def parse_args(): + ap = ArgumentParser() + ap.add_argument('--addr', default='127.0.0.1', + help='Listen IP addr (default: 127.0.0.1)') + ap.add_argument('--port', default=8125, type=int, + help='UDP port (default: 8125)') + ap.add_argument('--output-fname', default=None, + help='Output file (default: stdout)') + return ap.parse_args() + + +class StatsdMessageHandler(SocketServer.BaseRequestHandler): + + def handle(self): + data = self.request[0].strip() + tstamp = strftime("%Y-%m-%dT%H:%M:%S", gmtime()) + if self._output_fd: + self._output_fd.write("%s %s\n" % (tstamp, data)) + else: + print("%s %s" % (tstamp, data)) + + +def main(): + args = parse_args() + fd = open(args.output_fname, 'a') if args.output_fname else None + StatsdMessageHandler._output_fd = fd + server = SocketServer.UDPServer( + (args.addr, args.port), + StatsdMessageHandler, + ) + server.serve_forever() + +if __name__ == "__main__": + main() diff --git a/doc/source/index.rst b/doc/source/index.rst index 4b01307ea..072eb74e5 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -77,7 +77,7 @@ Designate in general service_status notifications support-matrix - + metrics Developer Documentation ======================= diff --git a/doc/source/metrics.rst b/doc/source/metrics.rst new file mode 100644 index 000000000..46ec78c1c --- /dev/null +++ b/doc/source/metrics.rst @@ -0,0 +1,15 @@ +.. _metrics: + +**** +metrics +**** + +metrics Base +========= +.. automodule:: designate.metrics + :members: + :special-members: + :private-members: + :undoc-members: + :show-inheritance: + diff --git a/etc/designate/designate.conf.sample b/etc/designate/designate.conf.sample index 165dd3ef2..26be269ae 100644 --- a/etc/designate/designate.conf.sample +++ b/etc/designate/designate.conf.sample @@ -568,3 +568,14 @@ debug = False # name = '%s.%s' % (func.__module__, func.__name__) # [hook_point:designate.api.v2.controllers.zones.get_one] + +################## +## Monasca Statsd +################## +[monasca:statsd] +# Disabled by default +# enabled = False +# Statsd server hostname +# hostname = 127.0.0.1 +# Statsd server UDP port +# port = 8125 diff --git a/requirements.txt b/requirements.txt index d4eb86175..13b599fcc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -48,3 +48,4 @@ python-memcached>=1.56 # PSF tooz>=1.28.0 # Apache-2.0 debtcollector>=1.2.0 # Apache-2.0 os-win>=0.2.3 # Apache-2.0 +monasca-statsd>=1.1.0 # Apache-2.0