oslo.middleware/oslo_middleware/stats.py
Sean McGinnis caaac6c438
Fix invalid escape sequence warnings
Py36 is more strict about escape sequences in strings. Regex patterns
often cause warnings due to not being normal escape sequences. This gets
rid of a few warnings by switching these regex strings to use raw
strings.

Change-Id: Ied16e55780f2bf73fac54e0afc8394af468a67b8
Signed-off-by: Sean McGinnis <sean.mcginnis@gmail.com>
2019-04-11 06:10:18 -05:00

132 lines
4.5 KiB
Python

# 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(r"/(v[0-9]{1}\.[0-9]{1})")
UUID_REGEX = re.compile(
r'.*(\.[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(r'.*(\.[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)