DebugClients/Python/PyProfile.py

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

eric ide

mercurial