Skip to content

Commit ed658f0

Browse files
committed
Add a printer for line statistics with an option --line
1 parent ef0c76e commit ed658f0

1 file changed

Lines changed: 118 additions & 4 deletions

File tree

‎vmprof/show.py‎

Lines changed: 118 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
from __future__ import absolute_import
22

3+
import inspect
4+
import linecache
35
import os
46
import six
7+
import sys
8+
59
import vmprof
610
import argparse
711

@@ -137,15 +141,125 @@ def main():
137141
default=2,
138142
help='The indention per level within the call graph.')
139143

144+
parser.add_argument('--lines', dest='lines', action='store_true')
145+
parser.set_defaults(lines=False)
146+
147+
parser.add_argument('--filter', dest='filter', type=str, default=None)
148+
140149
args = parser.parse_args()
141150

142-
pp = PrettyPrinter(
143-
prune_percent=args.prune_percent,
144-
prune_level=args.prune_level,
145-
indent=args.indent)
151+
if args.lines:
152+
pp = LinesPrinter(filter=args.filter)
153+
else:
154+
pp = PrettyPrinter(
155+
prune_percent=args.prune_percent,
156+
prune_level=args.prune_level,
157+
indent=args.indent)
146158

147159
pp.show(args.profile)
148160

161+
class LinesPrinter(object):
162+
def __init__(self, filter=None):
163+
self.filter = filter
164+
165+
def show(self, profile):
166+
"""
167+
Read and display a vmprof profile file.
168+
169+
:param profile: The filename of the vmprof profile file to display.
170+
:type profile: str
171+
"""
172+
try:
173+
stats = vmprof.read_profile(profile)
174+
except Exception as e:
175+
print("Fatal: could not read vmprof profile file '{}': {}".format(profile, e))
176+
return
177+
178+
tree = stats.get_tree()
179+
180+
for (filename, funline, funname), line_stats in self.lines_stat(tree):
181+
if self.filter is None or funname.find(self.filter) != -1:
182+
self.show_func(filename, funline, funname, line_stats)
183+
184+
185+
def lines_stat(self, tree):
186+
funcs = {}
187+
188+
def walk(node, d):
189+
if node is None:
190+
return
191+
192+
parts = node.name.count(':')
193+
if parts == 3:
194+
block_type, funname, funline, filename = node.name.split(':')
195+
196+
lines = d.setdefault((filename, int(funline), funname), {})
197+
198+
for l, cnt in six.iteritems(node.lines):
199+
lines[l] = lines.get(l, 0) + cnt
200+
201+
for c in six.itervalues(node.children):
202+
walk(c, d)
203+
204+
walk(tree, funcs)
205+
206+
return six.iteritems(funcs)
207+
208+
def show_func(self, filename, start_lineno, func_name, timings, stream=None, stripzeros=False):
209+
""" Show results for a single function.
210+
"""
211+
if stream is None:
212+
stream = sys.stdout
213+
214+
template = '%6s %8s %8s %-s'
215+
d = {}
216+
total_hits = 0.0
217+
218+
linenos = []
219+
for lineno, nhits in six.iteritems(timings):
220+
total_hits += nhits
221+
linenos.append(lineno)
222+
223+
if stripzeros and total_hits == 0:
224+
return
225+
226+
stream.write("Total hits: %g s\n" % total_hits)
227+
if os.path.exists(filename) or filename.startswith("<ipython-input-"):
228+
stream.write("File: %s\n" % filename)
229+
stream.write("Function: %s at line %s\n" % (func_name, start_lineno))
230+
if os.path.exists(filename):
231+
# Clear the cache to ensure that we get up-to-date results.
232+
linecache.clearcache()
233+
all_lines = linecache.getlines(filename)
234+
sublines = inspect.getblock(all_lines[start_lineno-1:])
235+
else:
236+
stream.write("\n")
237+
stream.write("Could not find file %s\n" % filename)
238+
stream.write("Are you sure you are running this program from the same directory\n")
239+
stream.write("that you ran the profiler from?\n")
240+
stream.write("Continuing without the function's contents.\n")
241+
# Fake empty lines so we can see the timings, if not the code.
242+
nlines = max(linenos) - min(min(linenos), start_lineno) + 1
243+
sublines = [''] * nlines
244+
for lineno, nhits in six.iteritems(timings):
245+
d[lineno] = (nhits, '%5.1f' % (100* nhits / total_hits))
246+
linenos = range(start_lineno, start_lineno + len(sublines))
247+
empty = ('', '')
248+
header = template % ('Line #', 'Hits', '% Hits',
249+
'Line Contents')
250+
stream.write("\n")
251+
stream.write(header)
252+
stream.write("\n")
253+
stream.write('=' * len(header))
254+
stream.write("\n")
255+
for lineno, line in zip(linenos, sublines):
256+
nhits, percent = d.get(lineno, empty)
257+
txt = template % (lineno, nhits, percent,
258+
line.rstrip('\n').rstrip('\r'))
259+
stream.write(txt)
260+
stream.write("\n")
261+
stream.write("\n")
262+
149263

150264
if __name__ == '__main__':
151265
main()

0 commit comments

Comments
 (0)