DebugClients/Python3/PyProfile.py

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

eric ide

mercurial