1""" 2Print a stacktrace when sent a SIGUSR1 for debugging 3""" 4 5import inspect 6import os 7import signal 8import sys 9import tempfile 10import time 11import traceback 12 13import salt.utils.files 14import salt.utils.stringutils 15 16 17def _makepretty(printout, stack): 18 """ 19 Pretty print the stack trace and environment information 20 for debugging those hard to reproduce user problems. :) 21 """ 22 printout.write("======== Salt Debug Stack Trace =========\n") 23 traceback.print_stack(stack, file=printout) 24 printout.write("=========================================\n") 25 26 27def _handle_sigusr1(sig, stack): 28 """ 29 Signal handler for SIGUSR1, only available on Unix-like systems 30 """ 31 # When running in the foreground, do the right thing 32 # and spit out the debug info straight to the console 33 if sys.stderr.isatty(): 34 output = sys.stderr 35 _makepretty(output, stack) 36 else: 37 filename = "salt-debug-{}.log".format(int(time.time())) 38 destfile = os.path.join(tempfile.gettempdir(), filename) 39 with salt.utils.files.fopen(destfile, "w") as output: 40 _makepretty(output, stack) 41 42 43def _handle_sigusr2(sig, stack): 44 """ 45 Signal handler for SIGUSR2, only available on Unix-like systems 46 """ 47 try: 48 import yappi 49 except ImportError: 50 return 51 if yappi.is_running(): 52 yappi.stop() 53 filename = "callgrind.salt-{}-{}".format(int(time.time()), os.getpid()) 54 destfile = os.path.join(tempfile.gettempdir(), filename) 55 yappi.get_func_stats().save(destfile, type="CALLGRIND") 56 if sys.stderr.isatty(): 57 sys.stderr.write("Saved profiling data to: {}\n".format(destfile)) 58 yappi.clear_stats() 59 else: 60 if sys.stderr.isatty(): 61 sys.stderr.write("Profiling started\n") 62 yappi.start() 63 64 65def enable_sig_handler(signal_name, handler): 66 """ 67 Add signal handler for signal name if it exists on given platform 68 """ 69 if hasattr(signal, signal_name): 70 signal.signal(getattr(signal, signal_name), handler) 71 72 73def enable_sigusr1_handler(): 74 """ 75 Pretty print a stack trace to the console or a debug log under /tmp 76 when any of the salt daemons such as salt-master are sent a SIGUSR1 77 """ 78 enable_sig_handler("SIGUSR1", _handle_sigusr1) 79 # Also canonical BSD-way of printing progress is SIGINFO 80 # which on BSD-derivatives can be sent via Ctrl+T 81 enable_sig_handler("SIGINFO", _handle_sigusr1) 82 83 84def enable_sigusr2_handler(): 85 """ 86 Toggle YAPPI profiler 87 """ 88 enable_sig_handler("SIGUSR2", _handle_sigusr2) 89 90 91def inspect_stack(): 92 """ 93 Return a string of which function we are currently in. 94 """ 95 return {"co_name": inspect.stack()[1][3]} 96 97 98def caller_name(skip=2, include_lineno=False): 99 """ 100 Get a name of a caller in the format module.class.method 101 102 `skip` specifies how many levels of stack to skip while getting caller 103 name. skip=1 means "who calls me", skip=2 "who calls my caller" etc. 104 105 An empty string is returned if skipped levels exceed stack height 106 107 Source: https://gist.github.com/techtonik/2151727 108 """ 109 stack = inspect.stack() 110 start = 0 + skip 111 if len(stack) < start + 1: 112 return "" 113 parentframe = stack[start][0] 114 115 name = [] 116 if include_lineno is True: 117 try: 118 lineno = inspect.getframeinfo(parentframe).lineno 119 except: # pylint: disable=bare-except 120 lineno = None 121 module = inspect.getmodule(parentframe) 122 # `modname` can be None when frame is executed directly in console 123 # TODO(techtonik): consider using __main__ 124 if module: 125 name.append(module.__name__) 126 # detect classname 127 if "self" in parentframe.f_locals: 128 # I don't know any way to detect call from the object method 129 # XXX: there seems to be no way to detect static method call - it will 130 # be just a function call 131 name.append(parentframe.f_locals["self"].__class__.__name__) 132 codename = parentframe.f_code.co_name 133 if codename != "<module>": # top level usually 134 name.append(codename) # function or a method 135 del parentframe 136 fullname = ".".join(name) 137 if include_lineno and lineno: 138 fullname += ":{}".format(lineno) 139 return fullname 140