1# Copyright 2017 The TensorFlow Authors. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14# ============================================================================== 15"""Make HTML tables that report where TF and TFLite failed to convert models. 16 17This is primarily used by generate_examples.py. See it or 18`make_report_table` for more details on usage. 19""" 20import html 21import json 22import re 23 24FAILED = "FAILED" 25SUCCESS = "SUCCESS" 26NOTRUN = "NOTRUN" 27 28 29def make_report_table(fp, title, reports): 30 """Make an HTML report of the success/failure reports. 31 32 Args: 33 fp: File-like object in which to put the html. 34 title: "Title of the zip file this pertains to." 35 reports: a list of conversion attempts. (report_args, report_vals) i.e. 36 ({"shape": [1,2,3], "type": "tf.float32"}, 37 {"tf": "SUCCESS", "tflite_converter": "FAILURE", 38 "tf_log": "", "tflite_converter_log": "Unsupported type."}) 39 """ 40 # sort reports by if TFLite converter failure and then TF failure (reversed) 41 reports.sort(key=lambda x: x[1]["tflite_converter"], reverse=False) 42 reports.sort(key=lambda x: x[1]["tf"], reverse=True) 43 def result_cell(x, row, col): 44 """Produce a cell with the condition string `x`.""" 45 s = html.escape(repr(x), quote=True) 46 color = "#44ff44" if x == SUCCESS else ( 47 "#ff4444" if x == FAILED else "#eeeeee") 48 handler = "ShowLog(%d, %d)" % (row, col) 49 fp.write("<td style='background-color: %s' onclick='%s'>%s</td>\n" % ( 50 color, handler, s)) 51 52 fp.write("""<html> 53<head> 54<title>tflite report</title> 55<style> 56body { font-family: Arial; } 57th { background-color: #555555; color: #eeeeee; } 58td { vertical-align: top; } 59td.horiz {width: 50%;} 60pre { white-space: pre-wrap; word-break: keep-all; } 61table {width: 100%;} 62</style> 63</head> 64""") 65 # Write the log data to a javascript variable and also make a function 66 # in javascript to show the log when an item is clicked. 67 fp.write("<script> \n") 68 fp.write(""" 69function ShowLog(row, col) { 70 71var log = document.getElementById("log"); 72log.innerHTML = "<pre>" + data[row][col] + "</pre>"; 73} 74""") 75 fp.write("var data = \n") 76 logs = json.dumps([[escape_and_normalize(x[1]["tf_log"]), 77 escape_and_normalize(x[1]["tflite_converter_log"]) 78 ] for x in reports]) 79 fp.write(logs) 80 fp.write(";</script>\n") 81 82 # Write the main table and use onclick on the items that have log items. 83 fp.write(""" 84<body> 85<h1>TensorFlow Lite Conversion</h1> 86<h2>%s</h2> 87""" % title) 88 89 # Get a list of keys that are in any of the records. 90 param_keys = {} 91 for params, _ in reports: 92 for k in params.keys(): 93 param_keys[k] = True 94 95 fp.write("<table>\n") 96 fp.write("<tr><td class='horiz'>\n") 97 fp.write("<div style='height:1000px; overflow:auto'>\n") 98 fp.write("<table>\n") 99 fp.write("<tr>\n") 100 for p in param_keys: 101 fp.write("<th>%s</th>\n" % html.escape(p, quote=True)) 102 fp.write("<th>TensorFlow</th>\n") 103 fp.write("<th>TensorFlow Lite Converter</th>\n") 104 fp.write("</tr>\n") 105 for idx, (params, vals) in enumerate(reports): 106 fp.write("<tr>\n") 107 for p in param_keys: 108 fp.write(" <td>%s</td>\n" % 109 html.escape(repr(params.get(p, None)), quote=True)) 110 111 result_cell(vals["tf"], idx, 0) 112 result_cell(vals["tflite_converter"], idx, 1) 113 fp.write("</tr>\n") 114 fp.write("</table>\n") 115 fp.write("</div>\n") 116 fp.write("</td>\n") 117 fp.write("<td class='horiz' id='log'></td></tr>\n") 118 fp.write("</table>\n") 119 fp.write("<script>\n") 120 fp.write("</script>\n") 121 fp.write(""" 122 </body> 123 </html> 124 """) 125 126 127def escape_and_normalize(log): 128 # These logs contain paths like /tmp/tmpgmypg3xa that are inconsistent between 129 # builds. This replaces these inconsistent paths with a consistent placeholder 130 # so the output is deterministic. 131 log = re.sub(r"/tmp/[^ ]+ ", "/NORMALIZED_TMP_FILE_PATH ", log) 132 log = re.sub(r"/build/work/[^/]+", "/NORMALIZED_BUILD_PATH", log) 133 return html.escape(log, quote=True) 134