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

100 lines
3.4 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.
import contextlib
from django.conf import settings
from osprofiler.drivers.base import get_driver as profiler_get_driver
from osprofiler import notifier
from osprofiler import profiler
from six.moves.urllib.parse import urlparse
PROFILER_SETTINGS = getattr(settings, 'OPENSTACK_PROFILER', {})
def init_notifier(connection_str, host="localhost"):
_notifier = notifier.create(
connection_str, project='horizon', service='horizon', host=host)
notifier.set(_notifier)
@contextlib.contextmanager
def traced(request, name, info=None):
if info is None:
info = {}
profiler_instance = profiler.get()
if profiler_instance is not None:
trace_id = profiler_instance.get_base_id()
info['user_id'] = request.user.id
with profiler.Trace(name, info=info):
yield trace_id
else:
yield
def _get_engine_kwargs(request, connection_str):
from openstack_dashboard.api import base
engines_kwargs = {
# NOTE(tsufiev): actually Horizon doesn't use ceilometer backend (too
# slow for UI), but since osprofiler still supports it (due to API
# deprecation cycle limitations), Horizon also should support this
# option
'ceilometer': lambda req: {
'endpoint': base.url_for(req, 'metering'),
'insecure': getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False),
'cacert': getattr(settings, 'OPENSTACK_SSL_CACERT', None),
'token': (lambda: req.user.token.id),
'ceilometer_api_version': '2'
}
}
engine = urlparse(connection_str).scheme
return engines_kwargs.get(engine, lambda req: {})(request)
def _get_engine(request):
connection_str = PROFILER_SETTINGS.get(
'receiver_connection_string', "mongodb://")
kwargs = _get_engine_kwargs(request, connection_str)
return profiler_get_driver(connection_str, **kwargs)
def list_traces(request):
engine = _get_engine(request)
query = {"info.user_id": request.user.id}
fields = ['base_id', 'timestamp', 'info.request.path']
traces = engine.list_traces(query, fields)
return [{'id': trace['base_id'],
'timestamp': trace['timestamp'],
'origin': trace['info']['request']['path']} for trace in traces]
def get_trace(request, trace_id):
def rec(_data, level=0):
_data['level'] = level
_data['is_leaf'] = not len(_data['children'])
_data['visible'] = True
_data['childrenVisible'] = True
for child in _data['children']:
rec(child, level + 1)
return _data
engine = _get_engine(request)
trace = engine.get_report(trace_id)
# throw away toplevel node which is dummy and doesn't contain any info,
# use its first and only child as the toplevel node
return rec(trace['children'][0])