|
1 | 1 | from __future__ import absolute_import |
2 | 2 |
|
| 3 | +import inspect |
| 4 | +import linecache |
3 | 5 | import os |
4 | 6 | import six |
| 7 | +import sys |
| 8 | + |
5 | 9 | import vmprof |
6 | 10 | import argparse |
7 | 11 |
|
@@ -137,15 +141,125 @@ def main(): |
137 | 141 | default=2, |
138 | 142 | help='The indention per level within the call graph.') |
139 | 143 |
|
| 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 | + |
140 | 149 | args = parser.parse_args() |
141 | 150 |
|
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) |
146 | 158 |
|
147 | 159 | pp.show(args.profile) |
148 | 160 |
|
| 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 | + |
149 | 263 |
|
150 | 264 | if __name__ == '__main__': |
151 | 265 | main() |
0 commit comments