mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2025-01-31 12:41:49 +01:00
[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. llvm-svn: 282637
This commit is contained in:
parent
7e2a5223b4
commit
963a3bfc75
203
tools/sancov/symcov-report-server.py
Executable file
203
tools/sancov/symcov-report-server.py
Executable file
@ -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()
|
Loading…
x
Reference in New Issue
Block a user