src/eric7/DebugClients/Python/PyProfile.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9473
3f23dbf37dbe
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
8 8
9 import os 9 import os
10 import marshal 10 import marshal
11 import profile 11 import profile
12 import atexit 12 import atexit
13 import pickle # secok 13 import pickle # secok
14 import contextlib 14 import contextlib
15 15
16 16
17 class PyProfile(profile.Profile): 17 class PyProfile(profile.Profile):
18 """ 18 """
19 Class extending the standard Python profiler with additional methods. 19 Class extending the standard Python profiler with additional methods.
20 20
21 This class extends the standard Python profiler by the functionality to 21 This class extends the standard Python profiler by the functionality to
22 save the collected timing data in a timing cache, to restore these data 22 save the collected timing data in a timing cache, to restore these data
23 on subsequent calls, to store a profile dump to a standard filename and 23 on subsequent calls, to store a profile dump to a standard filename and
24 to erase these caches. 24 to erase these caches.
25 """ 25 """
26
26 def __init__(self, basename, timer=None, bias=None): 27 def __init__(self, basename, timer=None, bias=None):
27 """ 28 """
28 Constructor 29 Constructor
29 30
30 @param basename name of the script to be profiled (string) 31 @param basename name of the script to be profiled (string)
31 @param timer function defining the timing calculation 32 @param timer function defining the timing calculation
32 @param bias calibration value (float) 33 @param bias calibration value (float)
33 """ 34 """
34 try: 35 try:
35 profile.Profile.__init__(self, timer, bias) 36 profile.Profile.__init__(self, timer, bias)
36 except TypeError: 37 except TypeError:
37 profile.Profile.__init__(self, timer) 38 profile.Profile.__init__(self, timer)
38 39
39 self.dispatch = self.__class__.dispatch 40 self.dispatch = self.__class__.dispatch
40 41
41 basename = os.path.splitext(basename)[0] 42 basename = os.path.splitext(basename)[0]
42 self.profileCache = "{0}.profile".format(basename) 43 self.profileCache = "{0}.profile".format(basename)
43 self.timingCache = "{0}.timings".format(basename) 44 self.timingCache = "{0}.timings".format(basename)
44 45
45 self.__restore() 46 self.__restore()
46 atexit.register(self.save) 47 atexit.register(self.save)
47 48
48 def __restore(self): 49 def __restore(self):
49 """ 50 """
50 Private method to restore the timing data from the timing cache. 51 Private method to restore the timing data from the timing cache.
51 """ 52 """
52 if not os.path.exists(self.timingCache): 53 if not os.path.exists(self.timingCache):
53 return 54 return
54 55
55 with contextlib.suppress(OSError, EOFError, ValueError, TypeError), \ 56 with contextlib.suppress(OSError, EOFError, ValueError, TypeError), open(
56 open(self.timingCache, 'rb') as cache: 57 self.timingCache, "rb"
57 timings = marshal.load(cache) # secok 58 ) as cache:
59 timings = marshal.load(cache) # secok
58 if isinstance(timings, dict): 60 if isinstance(timings, dict):
59 self.timings = timings 61 self.timings = timings
60 62
61 def save(self): 63 def save(self):
62 """ 64 """
63 Public method to store the collected profile data. 65 Public method to store the collected profile data.
64 """ 66 """
65 # dump the raw timing data 67 # dump the raw timing data
66 with contextlib.suppress(OSError), \ 68 with contextlib.suppress(OSError), open(self.timingCache, "wb") as cache:
67 open(self.timingCache, 'wb') as cache:
68 marshal.dump(self.timings, cache) 69 marshal.dump(self.timings, cache)
69 70
70 # dump the profile data 71 # dump the profile data
71 self.dump_stats(self.profileCache) 72 self.dump_stats(self.profileCache)
72 73
73 def dump_stats(self, file): 74 def dump_stats(self, file):
74 """ 75 """
75 Public method to dump the statistics data. 76 Public method to dump the statistics data.
76 77
77 @param file name of the file to write to (string) 78 @param file name of the file to write to (string)
78 """ 79 """
79 self.create_stats() 80 self.create_stats()
80 with contextlib.suppress(OSError, pickle.PickleError), \ 81 with contextlib.suppress(OSError, pickle.PickleError), open(file, "wb") as f:
81 open(file, 'wb') as f:
82 pickle.dump(self.stats, f, 4) 82 pickle.dump(self.stats, f, 4)
83 83
84 def erase(self): 84 def erase(self):
85 """ 85 """
86 Public method to erase the collected timing data. 86 Public method to erase the collected timing data.
90 os.remove(self.timingCache) 90 os.remove(self.timingCache)
91 91
92 def fix_frame_filename(self, frame): 92 def fix_frame_filename(self, frame):
93 """ 93 """
94 Public method used to fixup the filename for a given frame. 94 Public method used to fixup the filename for a given frame.
95 95
96 The logic employed here is that if a module was loaded 96 The logic employed here is that if a module was loaded
97 from a .pyc file, then the correct .py to operate with 97 from a .pyc file, then the correct .py to operate with
98 should be in the same path as the .pyc. The reason this 98 should be in the same path as the .pyc. The reason this
99 logic is needed is that when a .pyc file is generated, the 99 logic is needed is that when a .pyc file is generated, the
100 filename embedded and thus what is readable in the code object 100 filename embedded and thus what is readable in the code object
101 of the frame object is the fully qualified filepath when the 101 of the frame object is the fully qualified filepath when the
102 pyc is generated. If files are moved from machine to machine 102 pyc is generated. If files are moved from machine to machine
103 this can break debugging as the .pyc will refer to the .py 103 this can break debugging as the .pyc will refer to the .py
104 on the original machine. Another case might be sharing 104 on the original machine. Another case might be sharing
105 code over a network... This logic deals with that. 105 code over a network... This logic deals with that.
106 106
107 @param frame the frame object 107 @param frame the frame object
108 @return fixed up file name (string) 108 @return fixed up file name (string)
109 """ 109 """
110 versionExt = '.py3' 110 versionExt = ".py3"
111 111
112 # get module name from __file__ 112 # get module name from __file__
113 if (not isinstance(frame, profile.Profile.fake_frame) and 113 if (
114 '__file__' in frame.f_globals): 114 not isinstance(frame, profile.Profile.fake_frame)
115 root, ext = os.path.splitext(frame.f_globals['__file__']) 115 and "__file__" in frame.f_globals
116 if ext in ['.pyc', '.py', versionExt, '.pyo']: 116 ):
117 fixedName = root + '.py' 117 root, ext = os.path.splitext(frame.f_globals["__file__"])
118 if ext in [".pyc", ".py", versionExt, ".pyo"]:
119 fixedName = root + ".py"
118 if os.path.exists(fixedName): 120 if os.path.exists(fixedName):
119 return fixedName 121 return fixedName
120 122
121 fixedName = root + versionExt 123 fixedName = root + versionExt
122 if os.path.exists(fixedName): 124 if os.path.exists(fixedName):
123 return fixedName 125 return fixedName
124 126
125 return frame.f_code.co_filename 127 return frame.f_code.co_filename
126 128
127 def trace_dispatch_call(self, frame, t): 129 def trace_dispatch_call(self, frame, t):
128 """ 130 """
129 Public method used to trace functions calls. 131 Public method used to trace functions calls.
130 132
131 This is a variant of the one found in the standard Python 133 This is a variant of the one found in the standard Python
132 profile.py calling fix_frame_filename above. 134 profile.py calling fix_frame_filename above.
133 135
134 @param frame reference to the call frame 136 @param frame reference to the call frame
135 @param t arguments 137 @param t arguments
136 @return flag indicating a successful handling (boolean) 138 @return flag indicating a successful handling (boolean)
137 """ 139 """
138 if self.cur and frame.f_back is not self.cur[-2]: 140 if self.cur and frame.f_back is not self.cur[-2]:
139 rpt, rit, ret, rfn, rframe, rcur = self.cur 141 rpt, rit, ret, rfn, rframe, rcur = self.cur
140 if not isinstance(rframe, profile.Profile.fake_frame): 142 if not isinstance(rframe, profile.Profile.fake_frame):
141 assert rframe.f_back is frame.f_back, ( # secok 143 assert rframe.f_back is frame.f_back, ( # secok
142 "Bad call", rfn, rframe, rframe.f_back, 144 "Bad call",
143 frame, frame.f_back) 145 rfn,
146 rframe,
147 rframe.f_back,
148 frame,
149 frame.f_back,
150 )
144 self.trace_dispatch_return(rframe, 0) 151 self.trace_dispatch_return(rframe, 0)
145 assert (self.cur is None or # secok 152 assert self.cur is None or frame.f_back is self.cur[-2], ( # secok
146 frame.f_back is self.cur[-2]), ("Bad call", 153 "Bad call",
147 self.cur[-3]) 154 self.cur[-3],
155 )
148 fcode = frame.f_code 156 fcode = frame.f_code
149 fn = (self.fix_frame_filename(frame), 157 fn = (self.fix_frame_filename(frame), fcode.co_firstlineno, fcode.co_name)
150 fcode.co_firstlineno, fcode.co_name)
151 self.cur = (t, 0, 0, fn, frame, self.cur) 158 self.cur = (t, 0, 0, fn, frame, self.cur)
152 timings = self.timings 159 timings = self.timings
153 if fn in timings: 160 if fn in timings:
154 cc, ns, tt, ct, callers = timings[fn] 161 cc, ns, tt, ct, callers = timings[fn]
155 timings[fn] = cc, ns + 1, tt, ct, callers 162 timings[fn] = cc, ns + 1, tt, ct, callers
156 else: 163 else:
157 timings[fn] = 0, 0, 0, 0, {} 164 timings[fn] = 0, 0, 0, 0, {}
158 return 1 165 return 1
159 166
160 dispatch = { 167 dispatch = {
161 "call": trace_dispatch_call, 168 "call": trace_dispatch_call,
162 "exception": profile.Profile.trace_dispatch_exception, 169 "exception": profile.Profile.trace_dispatch_exception,
163 "return": profile.Profile.trace_dispatch_return, 170 "return": profile.Profile.trace_dispatch_return,
164 "c_call": profile.Profile.trace_dispatch_c_call, 171 "c_call": profile.Profile.trace_dispatch_c_call,

eric ide

mercurial