Coverage for cc_modules/cc_debug.py: 19%

1"""

2 camcops_server/cc_modules/cc_debug.py

3

4===============================================================================

5

6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.

7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

8

9 This file is part of CamCOPS.

10

11 CamCOPS is free software: you can redistribute it and/or modify

12 it under the terms of the GNU General Public License as published by

13 the Free Software Foundation, either version 3 of the License, or

14 (at your option) any later version.

15

16 CamCOPS is distributed in the hope that it will be useful,

17 but WITHOUT ANY WARRANTY; without even the implied warranty of

18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

19 GNU General Public License for more details.

20

21 You should have received a copy of the GNU General Public License

22 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.

23

24===============================================================================

25

26**Debugging utilities**

27

28"""

29

30 import cProfile

31 from types import FrameType

32 from typing import Any, Callable, Set, Union

33

34 TraceFuncType = Callable[[FrameType, str, Any], Union[Callable, None]]

35# ... returns a trace function (but we can't make the type definition

36# recursive) or None.

37

38

39# https://stackoverflow.com/questions/5375624/a-decorator-that-profiles-a-method-call-and-logs-the-profiling-result # noqa

40 def profile(func: Callable) -> Any:

41 """

42 Decorator to generate profiler output for slow code

43 from camcops_server.cc_debug import profile.

44

45 Add @profile to the function you want to profile.

46 Will generate a file called <function name>.profile.

47

48 Can be visualised with e.g. SnakeViz (pip install snakeviz)

49 """

50

51 def wrapper(*args: Any, **kwargs: Any) -> int:

52 datafn = func.__name__ + ".profile"

53 prof = cProfile.Profile()

54 retval = prof.runcall(func, *args, **kwargs)

55 prof.dump_stats(datafn)

56

57 return retval

58

59 return wrapper

60

61

62# noinspection PyUnusedLocal

63 def trace_calls(frame: FrameType, event: str, arg: Any) -> None:

64 """

65 A function that can be used as an argument to ``sys.settrace``. It prints

66 details of every function called (filename, line number, function name).

67 """

68 # https://pymotw.com/2/sys/tracing.html

69 # https://docs.python.org/3/library/sys.html#sys.settrace

70

71 # Function calls only

72 if event != "call":

73 return

74 co = frame.f_code

75 filename = co.co_filename

76 func_name = co.co_name

77 line_no = frame.f_lineno

78 print(f"- Call to {filename}:{line_no}:{func_name}")

79

80

81 def makefunc_trace_unique_calls(file_only: bool = False) -> TraceFuncType:

82 """

83 Creates a function that you can use as an argument to ``sys.settrace()``.

84 When you execute a trace, it shows only new call to each function.

85

86 Args:

87 file_only:

88 Shows files called only, not functions with line numbers.

89 """

90 called = set() # type: Set[str]

91

92 # noinspection PyUnusedLocal

93 def _trace_calls(frame: FrameType, event: str, arg: Any) -> None:

94 nonlocal called

95 if event != "call":

96 return

97 co = frame.f_code

98 filename = co.co_filename

99 if file_only:

100 signature = filename

101 else:

102 func_name = co.co_name

103 line_no = frame.f_lineno

104 signature = f"{filename}:{line_no}:{func_name}"

105 if signature not in called:

106 print(f"- First call to {signature}")

107 called.add(signature)

108

109 return _trace_calls