[TableGen] Fix validateOperandClass for non Phyical Reg (#118146)
[llvm-project.git] / llvm / tools / sancov / coverage-report-server.py
blob7b0b494218cc1f28e365849cae630263aa892b08
1 #!/usr/bin/env python3
2 # ===- symcov-report-server.py - Coverage Reports HTTP Serve --*- python -*--===#
4 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5 # See https://llvm.org/LICENSE.txt for license information.
6 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8 # ===------------------------------------------------------------------------===#
9 """(EXPERIMENTAL) HTTP server to browse coverage reports from .symcov files.
11 Coverage reports for big binaries are too huge, generating them statically
12 makes no sense. Start the server and go to localhost:8001 instead.
14 Usage:
15 ./tools/sancov/symcov-report-server.py \
16 --symcov coverage_data.symcov \
17 --srcpath root_src_dir
19 Other options:
20 --port port_number - specifies the port to use (8001)
21 --host host_name - host name to bind server to (127.0.0.1)
22 """
24 from __future__ import print_function
26 import argparse
27 import http.server
28 import json
29 import socketserver
30 import time
31 import html
32 import os
33 import string
34 import math
35 import urllib
37 INDEX_PAGE_TMPL = """
38 <html>
39 <head>
40 <title>Coverage Report</title>
41 <style>
42 .lz { color: lightgray; }
43 </style>
44 </head>
45 <body>
46 <table>
47 <tr><th>File</th><th>Coverage</th></tr>
48 <tr><td><em>Files with 0 coverage are not shown.</em></td></tr>
49 $filenames
50 </table>
51 </body>
52 </html>
53 """
55 CONTENT_PAGE_TMPL = """
56 <html>
57 <head>
58 <title>$path</title>
59 <style>
60 .covered { background: lightgreen; }
61 .not-covered { background: lightcoral; }
62 .partially-covered { background: navajowhite; }
63 .lz { color: lightgray; }
64 </style>
65 </head>
66 <body>
67 <pre>
68 $content
69 </pre>
70 </body>
71 </html>
72 """
74 FILE_URI_PREFIX = "/file/"
77 class SymcovData:
78 def __init__(self, symcov_json):
79 self.covered_points = frozenset(symcov_json["covered-points"])
80 self.point_symbol_info = symcov_json["point-symbol-info"]
81 self.file_coverage = self.compute_filecoverage()
83 def filenames(self):
84 return self.point_symbol_info.keys()
86 def has_file(self, filename):
87 return filename in self.point_symbol_info
89 def compute_linemap(self, filename):
90 """Build a line_number->css_class map."""
91 points = self.point_symbol_info.get(filename, dict())
93 line_to_points = dict()
94 for fn, points in points.items():
95 for point, loc in points.items():
96 line = int(loc.split(":")[0])
97 line_to_points.setdefault(line, []).append(point)
99 result = dict()
100 for line, points in line_to_points.items():
101 status = "covered"
102 covered_points = self.covered_points & set(points)
103 if not len(covered_points):
104 status = "not-covered"
105 elif len(covered_points) != len(points):
106 status = "partially-covered"
107 result[line] = status
108 return result
110 def compute_filecoverage(self):
111 """Build a filename->pct coverage."""
112 result = dict()
113 for filename, fns in self.point_symbol_info.items():
114 file_points = []
115 for fn, points in fns.items():
116 file_points.extend(points.keys())
117 covered_points = self.covered_points & set(file_points)
118 result[filename] = int(
119 math.ceil(len(covered_points) * 100 / len(file_points))
121 return result
124 def format_pct(pct):
125 pct_str = str(max(0, min(100, pct)))
126 zeroes = "0" * (3 - len(pct_str))
127 if zeroes:
128 zeroes = '<span class="lz">{0}</span>'.format(zeroes)
129 return zeroes + pct_str
132 class ServerHandler(http.server.BaseHTTPRequestHandler):
133 symcov_data = None
134 src_path = None
136 def do_GET(self):
137 norm_path = os.path.normpath(
138 urllib.parse.unquote(self.path[len(FILE_URI_PREFIX) :])
140 if self.path == "/":
141 self.send_response(200)
142 self.send_header("Content-type", "text/html; charset=utf-8")
143 self.end_headers()
145 filelist = []
146 for filename in sorted(self.symcov_data.filenames()):
147 file_coverage = self.symcov_data.file_coverage[filename]
148 if not file_coverage:
149 continue
150 filelist.append(
151 '<tr><td><a href="{prefix}{name}">{name}</a></td>'
152 "<td>{coverage}%</td></tr>".format(
153 prefix=FILE_URI_PREFIX,
154 name=html.escape(filename, quote=True),
155 coverage=format_pct(file_coverage),
159 response = string.Template(INDEX_PAGE_TMPL).safe_substitute(
160 filenames="\n".join(filelist)
162 self.wfile.write(response.encode("UTF-8", "replace"))
163 elif self.symcov_data.has_file(norm_path):
164 filename = norm_path
165 filepath = os.path.join(self.src_path, filename)
166 if not os.path.exists(filepath):
167 self.send_response(404)
168 self.end_headers()
169 return
171 self.send_response(200)
172 self.send_header("Content-type", "text/html; charset=utf-8")
173 self.end_headers()
175 linemap = self.symcov_data.compute_linemap(filename)
177 with open(filepath, "r", encoding="utf8") as f:
178 content = "\n".join(
180 "<span class='{cls}'>{line}&nbsp;</span>".format(
181 line=html.escape(line.rstrip()),
182 cls=linemap.get(line_no, ""),
184 for line_no, line in enumerate(f, start=1)
188 response = string.Template(CONTENT_PAGE_TMPL).safe_substitute(
189 path=self.path[1:], content=content
192 self.wfile.write(response.encode("UTF-8", "replace"))
193 else:
194 self.send_response(404)
195 self.end_headers()
198 def main():
199 parser = argparse.ArgumentParser(description="symcov report http server.")
200 parser.add_argument("--host", default="127.0.0.1")
201 parser.add_argument("--port", default=8001)
202 parser.add_argument("--symcov", required=True, type=argparse.FileType("r"))
203 parser.add_argument("--srcpath", required=True)
204 args = parser.parse_args()
206 print("Loading coverage...")
207 symcov_json = json.load(args.symcov)
208 ServerHandler.symcov_data = SymcovData(symcov_json)
209 ServerHandler.src_path = args.srcpath
211 socketserver.TCPServer.allow_reuse_address = True
212 httpd = socketserver.TCPServer((args.host, args.port), ServerHandler)
213 print("Serving at {host}:{port}".format(host=args.host, port=args.port))
214 try:
215 httpd.serve_forever()
216 except KeyboardInterrupt:
217 pass
218 httpd.server_close()
221 if __name__ == "__main__":
222 main()