diff options
author | Jenkins <jenkins@review.openstack.org> | 2012-06-28 14:08:10 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2012-06-28 14:08:10 +0000 |
commit | 43ee35ad58deb479f36374f2132da60a8d3649df (patch) | |
tree | 75d66f70eb70ff21d5b3adf6a463836dd5d66eeb /keystone | |
parent | 4f99a9dcba856623f5fe92f422563649d5cdcf73 (diff) | |
parent | ef58425b8e5be5818884108c92bfb208cdb3d741 (diff) | |
download | keystone-43ee35ad58deb479f36374f2132da60a8d3649df.tar.gz keystone-43ee35ad58deb479f36374f2132da60a8d3649df.tar.xz keystone-43ee35ad58deb479f36374f2132da60a8d3649df.zip |
Merge "Basic request stats monitoring & reporting"
Diffstat (limited to 'keystone')
-rw-r--r-- | keystone/config.py | 2 | ||||
-rw-r--r-- | keystone/contrib/stats/__init__.py | 17 | ||||
-rw-r--r-- | keystone/contrib/stats/backends/__init__.py | 0 | ||||
-rw-r--r-- | keystone/contrib/stats/backends/kvs.py | 34 | ||||
-rw-r--r-- | keystone/contrib/stats/core.py | 144 |
5 files changed, 197 insertions, 0 deletions
diff --git a/keystone/config.py b/keystone/config.py index 5c22c845..87efc637 100644 --- a/keystone/config.py +++ b/keystone/config.py @@ -140,6 +140,8 @@ register_str('driver', group='token', default='keystone.token.backends.kvs.Token') register_str('driver', group='ec2', default='keystone.contrib.ec2.backends.kvs.Ec2') +register_str('driver', group='stats', + default='keystone.contrib.stats.backends.kvs.Stats') #ldap diff --git a/keystone/contrib/stats/__init__.py b/keystone/contrib/stats/__init__.py new file mode 100644 index 00000000..c4a07cbc --- /dev/null +++ b/keystone/contrib/stats/__init__.py @@ -0,0 +1,17 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC +# +# 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 keystone.contrib.stats.core import * diff --git a/keystone/contrib/stats/backends/__init__.py b/keystone/contrib/stats/backends/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/keystone/contrib/stats/backends/__init__.py diff --git a/keystone/contrib/stats/backends/kvs.py b/keystone/contrib/stats/backends/kvs.py new file mode 100644 index 00000000..139c44c1 --- /dev/null +++ b/keystone/contrib/stats/backends/kvs.py @@ -0,0 +1,34 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC +# +# 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 keystone.contrib import stats +from keystone.common import kvs + + +class Stats(kvs.Base, stats.Driver): + def get_stats(self, api): + return self.db.get('stats-%s' % api, {}) + + def set_stats(self, api, stats_ref): + self.db.set('stats-%s' % api, stats_ref) + + def increment_stat(self, api, category, value): + """Increment a statistic counter, or create it if it doesn't exist.""" + stats = self.get_stats(api) + stats.setdefault(category, dict()) + counter = stats[category].setdefault(value, 0) + stats[category][value] = counter + 1 + self.set_stats(api, stats) diff --git a/keystone/contrib/stats/core.py b/keystone/contrib/stats/core.py new file mode 100644 index 00000000..a5973f3a --- /dev/null +++ b/keystone/contrib/stats/core.py @@ -0,0 +1,144 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC +# +# 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 keystone import config +from keystone import exception +from keystone import identity +from keystone import policy +from keystone import token +from keystone.common import logging +from keystone.common import manager +from keystone.common import wsgi + + +CONF = config.CONF +LOG = logging.getLogger(__name__) + + +class Manager(manager.Manager): + """Default pivot point for the Stats backend. + + See :mod:`keystone.common.manager.Manager` for more details on how this + dynamically calls the backend. + + """ + + def __init__(self): + super(Manager, self).__init__(CONF.stats.driver) + + +class Driver(object): + """Interface description for a Stats driver.""" + + def get_stats(self, api): + """Retrieve all previously-captured statistics for an interface.""" + raise exception.NotImplemented() + + def set_stats(self, api, stats_ref): + """Update statistics for an interface.""" + raise exception.NotImplemented() + + def increment_stat(self, api, category, value): + """Increment the counter for an individual statistic.""" + raise exception.NotImplemented() + + +class StatsExtension(wsgi.ExtensionRouter): + """Reports on previously-collected request/response statistics.""" + + def add_routes(self, mapper): + stats_controller = StatsController() + + mapper.connect('/OS-STATS/stats', controller=stats_controller, + action='get_stats', + conditions=dict(method=['GET'])) + mapper.connect('/OS-STATS/stats', controller=stats_controller, + action='reset_stats', + conditions=dict(method=['DELETE'])) + + +class StatsController(wsgi.Application): + def __init__(self): + self.identity_api = identity.Manager() + self.policy_api = policy.Manager() + self.stats_api = Manager() + self.token_api = token.Manager() + super(StatsController, self).__init__() + + def get_stats(self, context): + self.assert_admin(context) + return {'OS-STATS:stats': [ + { + 'type': 'identity', + 'api': 'admin', + 'extra': self.stats_api.get_stats(context, 'admin'), + }, + { + 'type': 'identity', + 'api': 'public', + 'extra': self.stats_api.get_stats(context, 'public'), + }, + ] + } + + def reset_stats(self, context): + self.assert_admin(context) + self.stats_api.set_stats(context, 'public', dict()) + self.stats_api.set_stats(context, 'admin', dict()) + + +class StatsMiddleware(wsgi.Middleware): + """Monitors various request/response attribute statistics.""" + + request_attributes = ['application_url', + 'method', + 'path', + 'path_qs', + 'remote_addr'] + + response_attributes = ['status_int'] + + def __init__(self, *args, **kwargs): + self.stats_api = Manager() + return super(StatsMiddleware, self).__init__(*args, **kwargs) + + def _resolve_api(self, host): + if str(CONF.admin_port) in host: + return 'admin' + elif str(CONF.public_port) in host: + return 'public' + else: + # NOTE(dolph): I don't think this is actually reachable, but hey + msg = 'Unable to resolve API as either public or admin: %s' % host + LOG.warning(msg) + return host + + def capture_stats(self, host, obj, attributes): + """Collect each attribute from the given object.""" + for attribute in attributes: + self.stats_api.increment_stat(None, + self._resolve_api(host), + attribute, + getattr(obj, attribute)) + + def process_request(self, request): + """Monitor incoming request attributes.""" + self.capture_stats(request.host, request, self.request_attributes) + + def process_response(self, request, response): + """Monitor outgoing response attributes.""" + self.capture_stats(request.host, response, self.response_attributes) + return response |