summaryrefslogtreecommitdiff
path: root/oslo_middleware/stats.py
diff options
context:
space:
mode:
Diffstat (limited to 'oslo_middleware/stats.py')
-rw-r--r--oslo_middleware/stats.py131
1 files changed, 131 insertions, 0 deletions
diff --git a/oslo_middleware/stats.py b/oslo_middleware/stats.py
new file mode 100644
index 0000000..9839fd5
--- /dev/null
+++ b/oslo_middleware/stats.py
@@ -0,0 +1,131 @@
+# Copyright (c) 2016 Cisco Systems
+#
+# 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.
+
+import logging
+import re
+
+import statsd
+import webob.dec
+
+from oslo_middleware import base
+
+LOG = logging.getLogger(__name__)
+VERSION_REGEX = re.compile("/(v[0-9]{1}\.[0-9]{1})")
+UUID_REGEX = re.compile(
+ '.*(\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}).*a',
+ re.IGNORECASE)
+# UUIDs without the - char, used in some places in Nova URLs.
+SHORT_UUID_REGEX = re.compile('.*(\.[0-9a-fA-F]{32}).*')
+
+
+class StatsMiddleware(base.ConfigurableMiddleware):
+ """Send stats to statsd based on API requests.
+
+ Examines the URL path and request method, and sends a stat count and timer
+ to a statsd host based on the path/method.
+
+ If your statsd is configured to send stats to Graphite, you'll end up with
+ stat names of the form::
+
+ timer.<appname>.<METHOD>.<path>.<from>.<url>
+
+ Note that URLs with versions in them (pretty much all of Openstack)
+ are always processed to replace the dot with _, so for example v2.0
+ becomes v2_0, and v1.1 becomes v1_1, since a dot '.' has special
+ meaning in Graphite.
+
+ The original StatsD is written in nodejs. If you want a Python
+ implementation, install Bucky instead as it's a drop-in replacement
+ (and much nicer IMO).
+
+ The Paste config must contain some parameters. Configure a filter like
+ this::
+
+ [filter:stats]
+ paste.filter_factory = oslo_middleware.stats:StatsMiddleware.factory
+ name = my_application_name # e.g. 'glance'
+ stats_host = my_statsd_host.example.com
+ # Optional args to further process the stat name that's generated:
+ remove_uuid = True
+ remove_short_uuid = True
+ # The above uuid processing is required in, e.g. Nova, if you want to
+ # collect generic stats rather than one per server instance.
+ """
+
+ def __init__(self, application, conf):
+ super(StatsMiddleware, self).__init__(application, conf)
+ self.application = application
+ self.stat_name = conf.get('name')
+ if self.stat_name is None:
+ raise AttributeError('name must be specified')
+ self.stats_host = conf.get('stats_host')
+ if self.stats_host is None:
+ raise AttributeError('stats_host must be specified')
+ self.remove_uuid = conf.get('remove_uuid', False)
+ self.remove_short_uuid = conf.get('remove_short_uuid', False)
+ self.statsd = statsd.StatsClient(self.stats_host)
+
+ @staticmethod
+ def strip_short_uuid(path):
+ """Remove short-form UUID from supplied path.
+
+ Only call after replacing slashes with dots in path.
+ """
+ match = SHORT_UUID_REGEX.match(path)
+ if match is None:
+ return path
+ return path.replace(match.group(1), '')
+
+ @staticmethod
+ def strip_uuid(path):
+ """Remove normal-form UUID from supplied path.
+
+ Only call after replacing slashes with dots in path.
+ """
+ match = UUID_REGEX.match(path)
+ if match is None:
+ return path
+ return path.replace(match.group(1), '')
+
+ @staticmethod
+ def strip_dot_from_version(path):
+ # Replace vN.N with vNN.
+ match = VERSION_REGEX.match(path)
+ if match is None:
+ return path
+ return path.replace(match.group(1), match.group(1).replace('.', ''))
+
+ @webob.dec.wsgify
+ def __call__(self, request):
+ path = request.path
+ path = self.strip_dot_from_version(path)
+
+ # Remove leading slash, if any, so we can be sure of the number
+ # of dots just below.
+ path = path.lstrip('/')
+
+ stat = "{name}.{method}".format(
+ name=self.stat_name, method=request.method)
+ if path != '':
+ stat += '.' + path.replace('/', '.')
+
+ if self.remove_short_uuid:
+ stat = self.strip_short_uuid(stat)
+
+ if self.remove_uuid:
+ stat = self.strip_uuid(stat)
+
+ LOG.debug("Incrementing stat count %s", stat)
+ with self.statsd.timer(stat):
+ return request.get_response(self.application)