[llvm] r282637 - [sancov] a simple .symcov coverage report server

Mike Aizatsky via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 28 14:27:58 PDT 2016


Author: aizatsky
Date: Wed Sep 28 16:27:58 2016
New Revision: 282637

URL: http://llvm.org/viewvc/llvm-project?rev=282637&view=rev
Log:
[sancov] a simple .symcov coverage report server

Coverage reports for gigabyte-sized binaries are huge. There's no
practical reason to generate them statically.

Implementing an experiment http coverage report server. The server
loads .symcov file and serves interactive coverage pages.

Added:
    llvm/trunk/tools/sancov/symcov-report-server.py   (with props)

Added: llvm/trunk/tools/sancov/symcov-report-server.py
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/tools/sancov/symcov-report-server.py?rev=282637&view=auto
==============================================================================
--- llvm/trunk/tools/sancov/symcov-report-server.py (added)
+++ llvm/trunk/tools/sancov/symcov-report-server.py Wed Sep 28 16:27:58 2016
@@ -0,0 +1,203 @@
+#!/usr/bin/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 = """
+<html>
+<head>
+  <title>Coverage Report</title>
+  <style>
+    .lz { color: lightgray; }
+  </style>
+</head>
+<body>
+    <table>
+      <tr><th>File</th><th>Coverage</th></tr>
+      <tr><td><em>Files with 0 coverage are not shown.</em></td></tr>
+$filenames
+    </table>
+</body>
+</html>
+"""
+
+CONTENT_PAGE_TMPL = """
+<html>
+<head>
+  <title>$path</title>
+  <style>
+    .covered { background: lightgreen; }
+    .not-covered { background: lightcoral; }
+    .partially-covered { background: navajowhite; }
+    .lz { color: lightgray; }
+  </style>
+</head>
+<body>
+<pre>
+$content
+</pre>
+</body>
+</html>
+"""
+
+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 = '<span class="lz">{0}</span>'.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(
+                        "<tr><td><a href=\"/{name}\">{name}</a></td>"
+                        "<td>{coverage}%</td></tr>".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(
+                        ["<span class='{cls}'>{line} </span>".format(
+                            line=html.escape(line.rstrip()), 
+                            cls=linemap.get(line_no, ""))
+                            for line_no, line in enumerate(f)])
+
+            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()

Propchange: llvm/trunk/tools/sancov/symcov-report-server.py
------------------------------------------------------------------------------
    svn:executable = *




More information about the llvm-commits mailing list