
The main difficulty in tracing async requests which are made during the rendering of Angular-based pages is tracking the root request (the one which was made to render the skeleton page which then initiated all other requests) throughout other requests, so they form a cohesive trace. This is solved by capturing the root request id and exposing it as a profiler module constant in the process of interpolating _scripts.html Django template. Then if that constant is not an empty dictionary, profiler module intercepts all Angular requests and adds osprofiler headers to them. This patch also fixes issues which arose after transitioning to the new version of angular-bootstrap. Change-Id: I656028b969289a473f54594681d9313ff8a07fd8
135 lines
4.7 KiB
Python
135 lines
4.7 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
|
|
import json
|
|
|
|
from django.conf import settings
|
|
from osprofiler import _utils as utils
|
|
from osprofiler.drivers.base import get_driver as profiler_get_driver
|
|
from osprofiler import notifier
|
|
from osprofiler import profiler
|
|
from osprofiler import web
|
|
from six.moves.urllib.parse import urlparse
|
|
|
|
|
|
ROOT_HEADER = 'PARENT_VIEW_TRACE_ID'
|
|
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
|
|
finished = _data['info']['finished']
|
|
for child in _data['children']:
|
|
__, child_finished = rec(child, level + 1)
|
|
# NOTE(tsufiev): in case of async requests the root request usually
|
|
# finishes before the dependent requests do so, to we need to
|
|
# normalize the duration of all requests by the finishing time of
|
|
# the one which took longest
|
|
if child_finished > finished:
|
|
finished = child_finished
|
|
return _data, finished
|
|
|
|
engine = _get_engine(request)
|
|
trace = engine.get_report(trace_id)
|
|
# NOTE(tsufiev): throw away toplevel node which is dummy and doesn't
|
|
# contain any info, use its first and only child as the toplevel node
|
|
data, max_finished = rec(trace['children'][0])
|
|
data['info']['max_finished'] = max_finished
|
|
return data
|
|
|
|
|
|
def update_trace_headers(keys, **kwargs):
|
|
trace_headers = web.get_trace_id_headers()
|
|
trace_info = utils.signed_unpack(
|
|
trace_headers[web.X_TRACE_INFO], trace_headers[web.X_TRACE_HMAC],
|
|
keys)
|
|
trace_info.update(kwargs)
|
|
p = profiler.get()
|
|
trace_data = utils.signed_pack(trace_info, p.hmac_key)
|
|
return json.dumps({web.X_TRACE_INFO: trace_data[0],
|
|
web.X_TRACE_HMAC: trace_data[1]})
|
|
|
|
|
|
if not PROFILER_SETTINGS.get('enabled', False):
|
|
def trace(function):
|
|
return function
|
|
else:
|
|
def trace(function):
|
|
func_name = function.__module__ + '.' + function.__name__
|
|
decorator = profiler.trace(func_name)
|
|
return decorator(function)
|