From 70494a87113c83ae39a359f7f795febc381a147c Mon Sep 17 00:00:00 2001 From: Maria Guseva Date: Thu, 6 Jul 2017 13:58:36 +0300 Subject: [PATCH] Add upstream LLVM coverage-report-server.py script for sancov dumps visualization --- scripts/coverage-report-server.py | 203 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100755 scripts/coverage-report-server.py diff --git a/scripts/coverage-report-server.py b/scripts/coverage-report-server.py new file mode 100755 index 0000000..428276f --- /dev/null +++ b/scripts/coverage-report-server.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +#===- symcov-report-server.py - Coverage Reports HTTP Serve --*- python -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# +'''(EXPERIMENTAL) HTTP server to browse coverage reports from .symcov files. + +Coverage reports for big binaries are too huge, generating them statically +makes no sense. Start the server and go to localhost:8001 instead. + +Usage: + ./tools/sancov/symcov-report-server.py \ + --symcov coverage_data.symcov \ + --srcpath root_src_dir + +Other options: + --port port_number - specifies the port to use (8001) + --host host_name - host name to bind server to (127.0.0.1) +''' + +import argparse +import http.server +import json +import socketserver +import time +import html +import os +import string +import math + +INDEX_PAGE_TMPL = """ + + + Coverage Report + + + + + + +$filenames +
FileCoverage
Files with 0 coverage are not shown.
+ + +""" + +CONTENT_PAGE_TMPL = """ + + + $path + + + +
+$content
+
+ + +""" + +class SymcovData: + def __init__(self, symcov_json): + self.covered_points = frozenset(symcov_json['covered-points']) + self.point_symbol_info = symcov_json['point-symbol-info'] + self.file_coverage = self.compute_filecoverage() + + def filenames(self): + return self.point_symbol_info.keys() + + def has_file(self, filename): + return filename in self.point_symbol_info + + def compute_linemap(self, filename): + """Build a line_number->css_class map.""" + points = self.point_symbol_info.get(filename, dict()) + + line_to_points = dict() + for fn, points in points.items(): + for point, loc in points.items(): + line = int(loc.split(":")[0]) + line_to_points.setdefault(line, []).append(point) + + result = dict() + for line, points in line_to_points.items(): + status = "covered" + covered_points = self.covered_points & set(points) + if not len(covered_points): + status = "not-covered" + elif len(covered_points) != len(points): + status = "partially-covered" + result[line] = status + return result + + def compute_filecoverage(self): + """Build a filename->pct coverage.""" + result = dict() + for filename, fns in self.point_symbol_info.items(): + file_points = [] + for fn, points in fns.items(): + file_points.extend(points.keys()) + covered_points = self.covered_points & set(file_points) + result[filename] = int(math.ceil( + len(covered_points) * 100 / len(file_points))) + return result + + +def format_pct(pct): + pct_str = str(max(0, min(100, pct))) + zeroes = '0' * (3 - len(pct_str)) + if zeroes: + zeroes = '{0}'.format(zeroes) + return zeroes + pct_str + +class ServerHandler(http.server.BaseHTTPRequestHandler): + symcov_data = None + src_path = None + + def do_GET(self): + if self.path == '/': + self.send_response(200) + self.send_header("Content-type", "text/html; charset=utf-8") + self.end_headers() + + filelist = [] + for filename in sorted(self.symcov_data.filenames()): + file_coverage = self.symcov_data.file_coverage[filename] + if not file_coverage: + continue + filelist.append( + "{name}" + "{coverage}%".format( + name=html.escape(filename, quote=True), + coverage=format_pct(file_coverage))) + + response = string.Template(INDEX_PAGE_TMPL).safe_substitute( + filenames='\n'.join(filelist)) + self.wfile.write(response.encode('UTF-8', 'replace')) + elif self.symcov_data.has_file(self.path[1:]): + filename = self.path[1:] + filepath = os.path.join(self.src_path, filename) + if not os.path.exists(filepath): + self.send_response(404) + self.end_headers() + return + + self.send_response(200) + self.send_header("Content-type", "text/html; charset=utf-8") + self.end_headers() + + linemap = self.symcov_data.compute_linemap(filename) + + with open(filepath, 'r') as f: + content = "\n".join( + ["{line} ".format( + line=html.escape(line.rstrip()), + cls=linemap.get(line_no, "")) + for line_no, line in enumerate(f, start=1)]) + + response = string.Template(CONTENT_PAGE_TMPL).safe_substitute( + path=self.path[1:], + content=content) + + self.wfile.write(response.encode('UTF-8', 'replace')) + else: + self.send_response(404) + self.end_headers() + + +def main(): + parser = argparse.ArgumentParser(description="symcov report http server.") + parser.add_argument('--host', default='127.0.0.1') + parser.add_argument('--port', default=8001) + parser.add_argument('--symcov', required=True, type=argparse.FileType('r')) + parser.add_argument('--srcpath', required=True) + args = parser.parse_args() + + print("Loading coverage...") + symcov_json = json.load(args.symcov) + ServerHandler.symcov_data = SymcovData(symcov_json) + ServerHandler.src_path = args.srcpath + + socketserver.TCPServer.allow_reuse_address = True + httpd = socketserver.TCPServer((args.host, args.port), ServerHandler) + print("Serving at {host}:{port}".format(host=args.host, port=args.port)) + try: + httpd.serve_forever() + except KeyboardInterrupt: + pass + httpd.server_close() + +if __name__ == '__main__': + main() -- 2.7.4