1 #!/usr/bin/env python2.7
3 from __future__
import print_function
10 from multiprocessing
import cpu_count
16 from pygments
import highlight
17 from pygments
.lexers
.c_cpp
import CppLexer
18 from pygments
.formatters
import HtmlFormatter
24 desc
= '''Generate HTML output to visualize optimization records from the YAML files
25 generated with -fsave-optimization-record and -fdiagnostics-show-hotness.
27 The tools requires PyYAML and Pygments Python packages.'''
30 # This allows passing the global context to the child processes.
32 def __init__(self
, caller_loc
= dict()):
33 # Map function names to their source location for function where inlining happened
34 self
.caller_loc
= caller_loc
39 if remark
.Name
== 'sil.Specialized':
40 return remark
.getArgDict()['Function'][0].startswith('\"Swift.')
41 elif remark
.Name
== 'sil.Inlined':
42 return remark
.getArgDict()['Callee'][0].startswith(('\"Swift.', '\"specialized Swift.'))
45 class SourceFileRenderer
:
46 def __init__(self
, source_dir
, output_dir
, filename
, no_highlight
):
47 self
.filename
= filename
48 existing_filename
= None
49 if os
.path
.exists(filename
):
50 existing_filename
= filename
52 fn
= os
.path
.join(source_dir
, filename
)
53 if os
.path
.exists(fn
):
54 existing_filename
= fn
56 self
.no_highlight
= no_highlight
57 self
.stream
= codecs
.open(os
.path
.join(output_dir
, optrecord
.html_file_name(filename
)), 'w', encoding
='utf-8')
59 self
.source_stream
= open(existing_filename
)
61 self
.source_stream
= None
64 <h1>Unable to locate file {}</h1>
66 '''.format(filename
), file=self
.stream
)
68 self
.html_formatter
= HtmlFormatter(encoding
='utf-8')
69 self
.cpp_lexer
= CppLexer(stripnl
=False)
71 def render_source_lines(self
, stream
, line_remarks
):
72 file_text
= stream
.read()
75 html_highlighted
= file_text
.decode('utf-8')
77 html_highlighted
= highlight(
82 # Note that the API is different between Python 2 and 3. On
83 # Python 3, pygments.highlight() returns a bytes object, so we
84 # have to decode. On Python 2, the output is str but since we
85 # support unicode characters and the output streams is unicode we
87 html_highlighted
= html_highlighted
.decode('utf-8')
89 # Take off the header and footer, these must be
90 # reapplied line-wise, within the page structure
91 html_highlighted
= html_highlighted
.replace('<div class="highlight"><pre>', '')
92 html_highlighted
= html_highlighted
.replace('</pre></div>', '')
94 for (linenum
, html_line
) in enumerate(html_highlighted
.split('\n'), start
=1):
97 <td><a name=\"L{linenum}\">{linenum}</a></td>
100 <td><div class="highlight"><pre>{html_line}</pre></div></td>
101 </tr>'''.format(**locals()), file=self
.stream
)
103 for remark
in line_remarks
.get(linenum
, []):
104 if not suppress(remark
):
105 self
.render_inline_remarks(remark
, html_line
)
107 def render_inline_remarks(self
, r
, line
):
108 inlining_context
= r
.DemangledFunctionName
109 dl
= context
.caller_loc
.get(r
.Function
)
111 dl_dict
= dict(list(dl
))
112 link
= optrecord
.make_link(dl_dict
['File'], dl_dict
['Line'] - 2)
113 inlining_context
= "<a href={link}>{r.DemangledFunctionName}</a>".format(**locals())
115 # Column is the number of characters *including* tabs, keep those and
116 # replace everything else with spaces.
117 indent
= line
[:max(r
.Column
, 1) - 1]
118 indent
= re
.sub('\S', ' ', indent
)
123 <td>{r.RelativeHotness}</td>
124 <td class=\"column-entry-{r.color}\">{r.PassWithDiffPrefix}</td>
125 <td><pre style="display:inline">{indent}</pre><span class=\"column-entry-yellow\"> {r.message} </span></td>
126 <td class=\"column-entry-yellow\">{inlining_context}</td>
127 </tr>'''.format(**locals()), file=self
.stream
)
129 def render(self
, line_remarks
):
130 if not self
.source_stream
:
136 <meta charset="utf-8" />
138 <link rel='stylesheet' type='text/css' href='style.css'>
141 <div class="centered">
142 <table class="source">
145 <th style="width: 2%">Line</td>
146 <th style="width: 3%">Hotness</td>
147 <th style="width: 10%">Optimization</td>
148 <th style="width: 70%">Source</td>
149 <th style="width: 15%">Inline Context</td>
152 <tbody>'''.format(os
.path
.basename(self
.filename
)), file=self
.stream
)
153 self
.render_source_lines(self
.source_stream
, line_remarks
)
159 </html>''', file=self
.stream
)
163 def __init__(self
, output_dir
, should_display_hotness
, max_hottest_remarks_on_index
):
164 self
.stream
= codecs
.open(os
.path
.join(output_dir
, 'index.html'), 'w', encoding
='utf-8')
165 self
.should_display_hotness
= should_display_hotness
166 self
.max_hottest_remarks_on_index
= max_hottest_remarks_on_index
168 def render_entry(self
, r
, odd
):
169 escaped_name
= cgi
.escape(r
.DemangledFunctionName
)
172 <td class=\"column-entry-{odd}\"><a href={r.Link}>{r.DebugLocString}</a></td>
173 <td class=\"column-entry-{odd}\">{r.RelativeHotness}</td>
174 <td class=\"column-entry-{odd}\">{escaped_name}</td>
175 <td class=\"column-entry-{r.color}\">{r.PassWithDiffPrefix}</td>
176 </tr>'''.format(**locals()), file=self
.stream
)
178 def render(self
, all_remarks
):
181 <meta charset="utf-8" />
183 <link rel='stylesheet' type='text/css' href='style.css'>
186 <div class="centered">
189 <td>Source Location</td>
193 </tr>''', file=self
.stream
)
196 if self
.should_display_hotness
:
197 max_entries
= self
.max_hottest_remarks_on_index
199 for i
, remark
in enumerate(all_remarks
[:max_entries
]):
200 if not suppress(remark
):
201 self
.render_entry(remark
, i
% 2)
205 </html>''', file=self
.stream
)
208 def _render_file(source_dir
, output_dir
, ctx
, no_highlight
, entry
):
211 filename
, remarks
= entry
212 SourceFileRenderer(source_dir
, output_dir
, filename
, no_highlight
).render(remarks
)
215 def map_remarks(all_remarks
):
216 # Set up a map between function names and their source location for
217 # function where inlining happened
218 for remark
in optrecord
.itervalues(all_remarks
):
219 if isinstance(remark
, optrecord
.Passed
) and remark
.Pass
== "inline" and remark
.Name
== "Inlined":
220 for arg
in remark
.Args
:
221 arg_dict
= dict(list(arg
))
222 caller
= arg_dict
.get('Caller')
225 context
.caller_loc
[caller
] = arg_dict
['DebugLoc']
230 def generate_report(all_remarks
,
235 should_display_hotness
,
236 max_hottest_remarks_on_index
,
238 should_print_progress
):
240 os
.makedirs(output_dir
)
242 if e
.errno
== errno
.EEXIST
and os
.path
.isdir(output_dir
):
247 if should_print_progress
:
248 print('Rendering index page...')
249 if should_display_hotness
:
250 sorted_remarks
= sorted(optrecord
.itervalues(all_remarks
), key
=lambda r
: (r
.Hotness
, r
.File
, r
.Line
, r
.Column
, r
.PassWithDiffPrefix
, r
.yaml_tag
, r
.Function
), reverse
=True)
252 sorted_remarks
= sorted(optrecord
.itervalues(all_remarks
), key
=lambda r
: (r
.File
, r
.Line
, r
.Column
, r
.PassWithDiffPrefix
, r
.yaml_tag
, r
.Function
))
253 IndexRenderer(output_dir
, should_display_hotness
, max_hottest_remarks_on_index
).render(sorted_remarks
)
255 shutil
.copy(os
.path
.join(os
.path
.dirname(os
.path
.realpath(__file__
)),
256 "style.css"), output_dir
)
258 _render_file_bound
= functools
.partial(_render_file
, source_dir
, output_dir
, context
, no_highlight
)
259 if should_print_progress
:
260 print('Rendering HTML files...')
261 optpmap
.pmap(_render_file_bound
,
262 file_remarks
.items(),
264 should_print_progress
)
268 parser
= argparse
.ArgumentParser(description
=desc
)
270 'yaml_dirs_or_files',
272 help='List of optimization record files or directories searched '
273 'for optimization record files.')
278 help='Path to a directory where generated HTML files will be output. '
279 'If the directory does not already exist, it will be created. '
280 '"%(default)s" by default.')
286 help='Max job count (defaults to %(default)s, the current CPU count)')
291 help='set source directory')
293 '--no-progress-indicator',
297 help='Do not display any indicator of how many YAML files were read '
298 'or rendered into HTML.')
300 '--max-hottest-remarks-on-index',
303 help='Maximum number of the hottest remarks to appear on the index page')
308 help='Do not use a syntax highlighter when rendering the source code')
311 help='Set the demangler to be used (defaults to %s)' % optrecord
.Remark
.default_demangler
)
313 # Do not make this a global variable. Values needed to be propagated through
314 # to individual classes and functions to be portable with multiprocessing across
315 # Windows and non-Windows.
316 args
= parser
.parse_args()
318 print_progress
= not args
.no_progress_indicator
320 optrecord
.Remark
.set_demangler(args
.demangler
)
322 files
= optrecord
.find_opt_files(*args
.yaml_dirs_or_files
)
324 parser
.error("No *.opt.yaml files found")
327 all_remarks
, file_remarks
, should_display_hotness
= \
328 optrecord
.gather_results(files
, args
.jobs
, print_progress
)
330 map_remarks(all_remarks
)
332 generate_report(all_remarks
,
337 should_display_hotness
,
338 args
.max_hottest_remarks_on_index
,
342 if __name__
== '__main__':