eric6/DebugClients/Python/PyProfile.py

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

eric ide

mercurial