Coverage for cc_modules/cc_debug.py: 22%

37 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-08 23:14 +0000

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/cc_modules/cc_debug.py 

5 

6=============================================================================== 

7 

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

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

10 

11 This file is part of CamCOPS. 

12 

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

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

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

16 (at your option) any later version. 

17 

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

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

20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

21 GNU General Public License for more details. 

22 

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

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

25 

26=============================================================================== 

27 

28**Debugging utilities** 

29 

30""" 

31 

32import cProfile 

33from types import FrameType 

34from typing import Any, Callable, Set, Union 

35 

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

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

38# recursive) or None. 

39 

40 

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

42def profile(func): 

43 """ 

44 Decorator to generate profiler output for slow code 

45 from camcops_server.cc_debug import profile. 

46 

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

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

49 

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

51 """ 

52 

53 def wrapper(*args, **kwargs): 

54 datafn = func.__name__ + ".profile" 

55 prof = cProfile.Profile() 

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

57 prof.dump_stats(datafn) 

58 

59 return retval 

60 

61 return wrapper 

62 

63 

64# noinspection PyUnusedLocal 

65def trace_calls( 

66 frame: FrameType, event: str, arg: Any 

67) -> Union[TraceFuncType, None]: 

68 """ 

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

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

71 """ 

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

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

74 

75 # Function calls only 

76 if event != "call": 

77 return 

78 co = frame.f_code 

79 filename = co.co_filename 

80 func_name = co.co_name 

81 line_no = frame.f_lineno 

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

83 

84 

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

86 """ 

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

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

89 

90 Args: 

91 file_only: 

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

93 """ 

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

95 

96 # noinspection PyUnusedLocal 

97 def _trace_calls( 

98 frame: FrameType, event: str, arg: Any 

99 ) -> Union[TraceFuncType, None]: 

100 nonlocal called 

101 if event != "call": 

102 return 

103 co = frame.f_code 

104 filename = co.co_filename 

105 if file_only: 

106 signature = filename 

107 else: 

108 func_name = co.co_name 

109 line_no = frame.f_lineno 

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

111 if signature not in called: 

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

113 called.add(signature) 

114 

115 return _trace_calls