summaryrefslogtreecommitdiffstats
path: root/keystone/contrib
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2012-06-28 14:08:10 +0000
committerGerrit Code Review <review@openstack.org>2012-06-28 14:08:10 +0000
commit43ee35ad58deb479f36374f2132da60a8d3649df (patch)
tree75d66f70eb70ff21d5b3adf6a463836dd5d66eeb /keystone/contrib
parent4f99a9dcba856623f5fe92f422563649d5cdcf73 (diff)
parentef58425b8e5be5818884108c92bfb208cdb3d741 (diff)
downloadkeystone-43ee35ad58deb479f36374f2132da60a8d3649df.tar.gz
keystone-43ee35ad58deb479f36374f2132da60a8d3649df.tar.xz
keystone-43ee35ad58deb479f36374f2132da60a8d3649df.zip
Merge "Basic request stats monitoring & reporting"
Diffstat (limited to 'keystone/contrib')
-rw-r--r--keystone/contrib/stats/__init__.py17
-rw-r--r--keystone/contrib/stats/backends/__init__.py0
-rw-r--r--keystone/contrib/stats/backends/kvs.py34
-rw-r--r--keystone/contrib/stats/core.py144
4 files changed, 195 insertions, 0 deletions
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