Files
horizon/openstack_dashboard/contrib/developer/profiler/middleware.py
Timur Sufiev dd98e10dbf Provide the bones of profiler: api and middleware
Middleware is the backbone of the whole profiler facility. It adds an
encoded payload to each request that should be profiled with
osprofiler library. This library is embedded into other OpenStack
services where it tries to decode the message with a known set of keys
(so Horizon middleware should use one of these keys) and if
successful, sends a message to MongoDB (via osprofiler driver). Every
message has its own id, the base id (root message id) and parent
id. Using these 3 ids a tree of trace messages is assembled.

Actually, Horizon Django application uses 2 middleware classes:
ProfilerClientMIddleware and
ProfilerMiddleware. ProfilerClientMiddleware is used to enable Horizon
self-profiling (profiling from the UI): if a specific key is found in
cookies, then 2 standard osprofiler headers are added to a
request. These headers are processed by ProfilerMiddleware which
should always be the last middleware class in a Django config, since
it's defines `process_view` method which returns HttpResponse object
effectively terminating the middleware process_view chain. Assuming
that all API calls happen during rendering a view, Horizon sets a
tracepoint there which becomes a root node of the trace calls tree.

Implements-blueprint: openstack-profiler-at-developer-dashboard
Change-Id: Ib896676e304f2984c011bd1b610c86d1f24d46b9
2016-11-09 11:51:06 +03:00

119 lines
4.2 KiB
Python

# Copyright 2016 Mirantis Inc.
# All Rights Reserved.
#
# 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 django.conf import settings
from django.core import exceptions
from django.core.urlresolvers import reverse
from django.utils import safestring
from django.utils.translation import ugettext_lazy as _
from osprofiler import _utils as profiler_utils
from osprofiler import profiler
from osprofiler import web
import six
from horizon import messages
from openstack_dashboard.contrib.developer.profiler import api
_REQUIRED_KEYS = ("base_id", "hmac_key")
_OPTIONAL_KEYS = ("parent_id",)
PROFILER_SETTINGS = getattr(settings, 'OPENSTACK_PROFILER', {})
class ProfilerClientMiddleware(object):
def process_request(self, request):
if 'profile_page' in request.COOKIES:
hmac_key = PROFILER_SETTINGS.get('keys')[0]
profiler.init(hmac_key)
for hdr_key, hdr_value in web.get_trace_id_headers().items():
request.META[hdr_key] = hdr_value
return None
class ProfilerMiddleware(object):
def __init__(self):
self.name = PROFILER_SETTINGS.get('facility_name', 'horizon')
self.hmac_keys = PROFILER_SETTINGS.get('keys')
self._enabled = PROFILER_SETTINGS.get('enabled', False)
if self._enabled:
api.init_notifier(PROFILER_SETTINGS.get(
'notifier_connection_string', 'mongodb://'))
else:
raise exceptions.MiddlewareNotUsed()
@staticmethod
def is_authenticated(request):
return hasattr(request, "user") and request.user.is_authenticated()
def is_enabled(self, request):
return self.is_authenticated(request) and settings.DEBUG
@staticmethod
def _trace_is_valid(trace_info):
if not isinstance(trace_info, dict):
return False
trace_keys = set(six.iterkeys(trace_info))
if not all(k in trace_keys for k in _REQUIRED_KEYS):
return False
if trace_keys.difference(_REQUIRED_KEYS + _OPTIONAL_KEYS):
return False
return True
def process_view(self, request, view_func, view_args, view_kwargs):
# do not profile ajax requests for now
if not self.is_enabled(request) or request.is_ajax():
return None
trace_info = profiler_utils.signed_unpack(
request.META.get('X-Trace-Info'),
request.META.get('X-Trace-HMAC'),
self.hmac_keys)
if not self._trace_is_valid(trace_info):
return None
profiler.init(**trace_info)
info = {
'request': {
'path': request.path,
'query': request.GET.urlencode(),
'method': request.method,
'scheme': request.scheme
}
}
with api.traced(request, view_func.__name__, info) as trace_id:
response = view_func(request, *view_args, **view_kwargs)
url = reverse('horizon:developer:profiler:index')
message = safestring.mark_safe(
_('Traced with id %(id)s. Go to <a href="%(url)s">page</a>') %
{'id': trace_id, 'url': url})
messages.info(request, message)
return response
@staticmethod
def clear_profiling_cookies(request, response):
"""Expire any cookie that initiated profiling request."""
if 'profile_page' in request.COOKIES:
path = request.path[:-1]
response.set_cookie('profile_page', max_age=0, path=path)
def process_response(self, request, response):
self.clear_profiling_cookies(request, response)
# do not profile ajax requests for now
if not self.is_enabled(request) or request.is_ajax():
return response
return response