1# Copyright 2016 Mirantis Inc.
2# All Rights Reserved.
3#
4#    Licensed under the Apache License, Version 2.0 (the "License"); you may
5#    not use this file except in compliance with the License. You may obtain
6#    a copy of the License at
7#
8#         http://www.apache.org/licenses/LICENSE-2.0
9#
10#    Unless required by applicable law or agreed to in writing, software
11#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13#    License for the specific language governing permissions and limitations
14#    under the License.
15
16import contextlib
17import json
18
19from osprofiler import _utils as utils
20from osprofiler.drivers.base import get_driver as profiler_get_driver
21from osprofiler import notifier
22from osprofiler import profiler
23from osprofiler import web
24
25from horizon.utils import settings as horizon_settings
26
27
28ROOT_HEADER = 'PARENT_VIEW_TRACE_ID'
29
30
31def init_notifier(connection_str, host="localhost"):
32    _notifier = notifier.create(
33        connection_str, project='horizon', service='horizon', host=host)
34    notifier.set(_notifier)
35
36
37@contextlib.contextmanager
38def traced(request, name, info=None):
39    if info is None:
40        info = {}
41    profiler_instance = profiler.get()
42    if profiler_instance is not None:
43        trace_id = profiler_instance.get_base_id()
44        info['user_id'] = request.user.id
45        with profiler.Trace(name, info=info):
46            yield trace_id
47    else:
48        yield
49
50
51def _get_engine():
52    connection_str = horizon_settings.get_dict_config(
53        'OPENSTACK_PROFILER', 'receiver_connection_string')
54    return profiler_get_driver(connection_str)
55
56
57def list_traces():
58    engine = _get_engine()
59    fields = ['base_id', 'timestamp', 'info.request.path', 'info']
60    traces = engine.list_traces(fields)
61    return [{'id': trace['base_id'],
62             'timestamp': trace['timestamp'],
63             'origin': trace['info']['request']['path']} for trace in traces]
64
65
66def get_trace(trace_id):
67    def rec(_data, level=0):
68        _data['level'] = level
69        _data['is_leaf'] = not _data['children']
70        _data['visible'] = True
71        _data['childrenVisible'] = True
72        finished = _data['info']['finished']
73        for child in _data['children']:
74            __, child_finished = rec(child, level + 1)
75            # NOTE(tsufiev): in case of async requests the root request usually
76            # finishes before the dependent requests do so, to we need to
77            # normalize the duration of all requests by the finishing time of
78            # the one which took longest
79            if child_finished > finished:
80                finished = child_finished
81        return _data, finished
82
83    engine = _get_engine()
84    trace = engine.get_report(trace_id)
85    data, max_finished = rec(trace)
86    data['info']['max_finished'] = max_finished
87    return data
88
89
90def update_trace_headers(keys, **kwargs):
91    trace_headers = web.get_trace_id_headers()
92    trace_info = utils.signed_unpack(
93        trace_headers[web.X_TRACE_INFO], trace_headers[web.X_TRACE_HMAC],
94        keys)
95    trace_info.update(kwargs)
96    p = profiler.get()
97    trace_data = utils.signed_pack(trace_info, p.hmac_key)
98    trace_data = [key.decode() if isinstance(key, bytes)
99                  else key for key in trace_data]
100    return json.dumps({web.X_TRACE_INFO: trace_data[0],
101                       web.X_TRACE_HMAC: trace_data[1]})
102
103
104if not horizon_settings.get_dict_config('OPENSTACK_PROFILER', 'enabled'):
105    def trace(function):
106        return function
107else:
108    def trace(function):
109        func_name = function.__module__ + '.' + function.__name__
110        decorator = profiler.trace(func_name)
111        return decorator(function)
112