Index: tools/telemetry/third_party/coverage/lab/parser.py |
diff --git a/tools/telemetry/third_party/coverage/lab/parser.py b/tools/telemetry/third_party/coverage/lab/parser.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..1343f4ceba7c60689037ff786c0e007daed6b682 |
--- /dev/null |
+++ b/tools/telemetry/third_party/coverage/lab/parser.py |
@@ -0,0 +1,202 @@ |
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt |
+ |
+"""Parser.py: a main for invoking code in coverage/parser.py""" |
+ |
+from __future__ import division |
+ |
+import glob, os, sys |
+import collections |
+from optparse import OptionParser |
+ |
+import disgen |
+ |
+from coverage.misc import CoverageException |
+from coverage.parser import ByteParser, PythonParser |
+from coverage.python import get_python_source |
+ |
+opcode_counts = collections.Counter() |
+ |
+class ParserMain(object): |
+ """A main for code parsing experiments.""" |
+ |
+ def main(self, args): |
+ """A main function for trying the code from the command line.""" |
+ |
+ parser = OptionParser() |
+ parser.add_option( |
+ "-c", action="store_true", dest="chunks", |
+ help="Show basic block chunks" |
+ ) |
+ parser.add_option( |
+ "-d", action="store_true", dest="dis", |
+ help="Disassemble" |
+ ) |
+ parser.add_option( |
+ "-H", action="store_true", dest="histogram", |
+ help="Count occurrences of opcodes" |
+ ) |
+ parser.add_option( |
+ "-R", action="store_true", dest="recursive", |
+ help="Recurse to find source files" |
+ ) |
+ parser.add_option( |
+ "-s", action="store_true", dest="source", |
+ help="Show analyzed source" |
+ ) |
+ parser.add_option( |
+ "-t", action="store_true", dest="tokens", |
+ help="Show tokens" |
+ ) |
+ |
+ options, args = parser.parse_args() |
+ if options.recursive: |
+ if args: |
+ root = args[0] |
+ else: |
+ root = "." |
+ for root, _, _ in os.walk(root): |
+ for f in glob.glob(root + "/*.py"): |
+ self.one_file(options, f) |
+ elif not args: |
+ parser.print_help() |
+ else: |
+ self.one_file(options, args[0]) |
+ |
+ if options.histogram: |
+ total = sum(opcode_counts.values()) |
+ print("{} total opcodes".format(total)) |
+ for opcode, number in opcode_counts.most_common(): |
+ print("{:20s} {:6d} {:.1%}".format(opcode, number, number/total)) |
+ |
+ |
+ def one_file(self, options, filename): |
+ """Process just one file.""" |
+ |
+ try: |
+ text = get_python_source(filename) |
+ bp = ByteParser(text, filename=filename) |
+ except Exception as err: |
+ print("%s" % (err,)) |
+ return |
+ |
+ if options.dis: |
+ print("Main code:") |
+ self.disassemble(bp, histogram=options.histogram) |
+ |
+ arcs = bp._all_arcs() |
+ if options.chunks:# and not options.dis: |
+ chunks = bp._all_chunks() |
+ if options.recursive: |
+ print("%6d: %s" % (len(chunks), filename)) |
+ else: |
+ print("Chunks: %r" % chunks) |
+ print("Arcs: %r" % sorted(arcs)) |
+ |
+ if options.source or options.tokens: |
+ cp = PythonParser(filename=filename, exclude=r"no\s*cover") |
+ cp.show_tokens = options.tokens |
+ cp._raw_parse() |
+ |
+ if options.source: |
+ if options.chunks: |
+ arc_width, arc_chars = self.arc_ascii_art(arcs) |
+ else: |
+ arc_width, arc_chars = 0, {} |
+ |
+ exit_counts = cp.exit_counts() |
+ |
+ for lineno, ltext in enumerate(cp.lines, start=1): |
+ m0 = m1 = m2 = m3 = a = ' ' |
+ if lineno in cp.statement_starts: |
+ m0 = '-' |
+ exits = exit_counts.get(lineno, 0) |
+ if exits > 1: |
+ m1 = str(exits) |
+ if lineno in cp.docstrings: |
+ m2 = '"' |
+ if lineno in cp.classdefs: |
+ m2 = 'C' |
+ if lineno in cp.excluded: |
+ m3 = 'x' |
+ a = arc_chars[lineno].ljust(arc_width) |
+ print("%4d %s%s%s%s%s %s" % |
+ (lineno, m0, m1, m2, m3, a, ltext) |
+ ) |
+ |
+ def disassemble(self, byte_parser, histogram=False): |
+ """Disassemble code, for ad-hoc experimenting.""" |
+ |
+ for bp in byte_parser.child_parsers(): |
+ chunks = bp._split_into_chunks() |
+ chunkd = dict((chunk.byte, chunk) for chunk in chunks) |
+ if bp.text: |
+ srclines = bp.text.splitlines() |
+ else: |
+ srclines = None |
+ print("\n%s: " % bp.code) |
+ upto = None |
+ for disline in disgen.disgen(bp.code): |
+ if histogram: |
+ opcode_counts[disline.opcode] += 1 |
+ continue |
+ if disline.first: |
+ if srclines: |
+ upto = upto or disline.lineno-1 |
+ while upto <= disline.lineno-1: |
+ print("%100s%s" % ("", srclines[upto])) |
+ upto += 1 |
+ elif disline.offset > 0: |
+ print("") |
+ line = disgen.format_dis_line(disline) |
+ chunk = chunkd.get(disline.offset) |
+ if chunk: |
+ chunkstr = ":: %r" % chunk |
+ else: |
+ chunkstr = "" |
+ print("%-70s%s" % (line, chunkstr)) |
+ |
+ print("") |
+ |
+ def arc_ascii_art(self, arcs): |
+ """Draw arcs as ascii art. |
+ |
+ Returns a width of characters needed to draw all the arcs, and a |
+ dictionary mapping line numbers to ascii strings to draw for that line. |
+ |
+ """ |
+ arc_chars = collections.defaultdict(str) |
+ for lfrom, lto in sorted(arcs): |
+ if lfrom < 0: |
+ arc_chars[lto] += 'v' |
+ elif lto < 0: |
+ arc_chars[lfrom] += '^' |
+ else: |
+ if lfrom == lto - 1: |
+ # Don't show obvious arcs. |
+ continue |
+ if lfrom < lto: |
+ l1, l2 = lfrom, lto |
+ else: |
+ l1, l2 = lto, lfrom |
+ w = max(len(arc_chars[l]) for l in range(l1, l2+1)) |
+ for l in range(l1, l2+1): |
+ if l == lfrom: |
+ ch = '<' |
+ elif l == lto: |
+ ch = '>' |
+ else: |
+ ch = '|' |
+ arc_chars[l] = arc_chars[l].ljust(w) + ch |
+ arc_width = 0 |
+ |
+ if arc_chars: |
+ arc_width = max(len(a) for a in arc_chars.values()) |
+ else: |
+ arc_width = 0 |
+ |
+ return arc_width, arc_chars |
+ |
+if __name__ == '__main__': |
+ ParserMain().main(sys.argv[1:]) |
+ |