Coverage for cc_modules/cc_debug.py: 19%

36 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-15 14:23 +0100

1""" 

2camcops_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 

30import cProfile 

31from types import FrameType 

32from typing import Any, Callable, Set, Union 

33 

34TraceFuncType = 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 

40def 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 

63def 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 

81def 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