
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
100 lines
3.4 KiB
Python
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])
|