summaryrefslogtreecommitdiff
path: root/trove/common/debug_utils.py
blob: d115f8f675620da6077c19c571dbe31d5741f6c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# Copyright 2011 OpenStack Foundation
# 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.
#

"""Help utilities for debugging"""

import sys

from oslo_config import cfg
from oslo_log import log as logging


LOG = logging.getLogger(__name__)
CONF = cfg.CONF

__debug_state = None

pydev_debug_opts = [
    cfg.StrOpt("pydev_debug",
               choices=("disabled", "enabled", "auto"),
               default="disabled",
               help="Enable or disable pydev remote debugging. "
                    "If value is 'auto' tries to connect to remote "
                    "debugger server, but in case of error "
                    "continues running with debugging disabled."),

    cfg.StrOpt("pydev_debug_host",
               help="Pydev debug server host (localhost by default)."),

    cfg.PortOpt("pydev_debug_port",
                default=5678,
                help="Pydev debug server port (5678 by default)."),

    cfg.StrOpt("pydev_path",
               help="Set path to pydevd library, used if pydevd is "
                    "not found in python sys.path.")
]

CONF.register_opts(pydev_debug_opts)


def setup():
    """
    Analyze configuration for pydev remote debugging and establish
    connection to remote debugger service if needed

    @return: True if remote debugging was enabled successfully,
        otherwise - False
    """

    global __debug_state

    if CONF.pydev_debug == "enabled":
        __debug_state = __setup_remote_pydev_debug(
            pydev_debug_host=CONF.pydev_debug_host,
            pydev_debug_port=CONF.pydev_debug_port,
            pydev_path=CONF.pydev_path)
    elif CONF.pydev_debug == "auto":
        __debug_state = __setup_remote_pydev_debug_safe(
            pydev_debug_host=CONF.pydev_debug_host,
            pydev_debug_port=CONF.pydev_debug_port,
            pydev_path=CONF.pydev_path)
    else:
        __debug_state = False


def enabled():
    """
    @return: True if connection to remote debugger established, otherwise False
    """
    assert __debug_state is not None, ("debug_utils are not initialized. "
                                       "Please call setup() method first")

    # if __debug_state is set and we have monkey patched
    # eventlet.thread, issue a warning.
    # You can't safely use eventlet.is_monkey_patched() on the
    # threading module so you have to do this little dance.
    # Discovered after much head scratching, see also
    #
    # http://stackoverflow.com/questions/32452110/
    #     does-eventlet-do-monkey-patch-for-threading-module
    #
    # note multi-line URL
    if __debug_state:
        import threading
        if threading.current_thread.__module__ == 'eventlet.green.threading':
            LOG.warning("Enabling debugging with eventlet monkey"
                        " patched produce unexpected behavior.")

    return __debug_state


def __setup_remote_pydev_debug_safe(pydev_debug_host=None,
                                    pydev_debug_port=5678, pydev_path=None):
    """
    Safe version of __setup_remote_pydev_debug method. In error case returns
    False as result instead of Exception raising

    @see: __setup_remote_pydev_debug
    """

    try:
        return __setup_remote_pydev_debug(
            pydev_debug_host=pydev_debug_host,
            pydev_debug_port=pydev_debug_port,
            pydev_path=pydev_path)
    except Exception as e:
        LOG.warning("Can't connect to remote debug server."
                    " Continuing to work in standard mode."
                    " Error: %s.", e)
        return False


def __setup_remote_pydev_debug(pydev_debug_host=None, pydev_debug_port=None,
                               pydev_path=None):
    """
    Method connects to remote debug server, and attach current thread trace
    to debugger. Also thread.start_new_thread thread.start_new are patched to
    enable debugging of new threads

    @param pydev_debug_host: remote debug server host hame, 'localhost'
        if not specified or None
    @param pydev_debug_port: remote debug server port, 5678
        if not specified or None
    @param pydev_path: optional path to pydevd library, used it pydevd is not
        found in python sys.path
    @return: True if debugging initialized,
        otherwise exception should be raised
    """

    try:
        import pydevd
        LOG.debug("pydevd module was imported from system path")
    except ImportError:
        LOG.debug("Can't load pydevd module from system path. Try loading it "
                  "from pydev_path: %s", pydev_path)
        assert pydev_path, "pydev_path is not set"
        if pydev_path not in sys.path:
            sys.path.append(pydev_path)
        import pydevd
        LOG.debug("pydevd module was imported from pydev_path: %s", pydev_path)
    pydevd.settrace(
        host=pydev_debug_host,
        port=pydev_debug_port,
        stdoutToServer=True,
        stderrToServer=True,
        trace_only_current_thread=False,
        suspend=False,
    )
    return True