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
= io
.open(os
.path
.join(output_dir
, optrecord
.html_file_name(filename
)), 'w', encoding
='utf-8')
59 self
.source_stream
= io
.open(existing_filename
, encoding
='utf-8')
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
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
)
120 # Create expanded message and link if we have a multiline message.
121 lines
= r
.message
.split('\n')
123 expand_link
= '<a style="text-decoration: none;" href="" onclick="toggleExpandedMessage(this); return false;">+</a>'
125 expand_message
= u
'''
126 <div class="full-info" style="display:none;">
127 <div class="col-left"><pre style="display:inline">{}</pre></div>
128 <div class="expanded col-left"><pre>{}</pre></div>
129 </div>'''.format(indent
, '\n'.join(lines
[1:]))
137 <td>{r.RelativeHotness}</td>
138 <td class=\"column-entry-{r.color}\">{r.PassWithDiffPrefix}</td>
139 <td><pre style="display:inline">{indent}</pre><span class=\"column-entry-yellow\">{expand_link} {message} </span>{expand_message}</td>
140 <td class=\"column-entry-yellow\">{inlining_context}</td>
141 </tr>'''.format(**locals()), file=self
.stream
)
143 def render(self
, line_remarks
):
144 if not self
.source_stream
:
150 <meta charset="utf-8" />
152 <link rel='stylesheet' type='text/css' href='style.css'>
153 <script type="text/javascript">
154 /* Simple helper to show/hide the expanded message of a remark. */
155 function toggleExpandedMessage(e) {{
156 var FullTextElems = e.parentElement.parentElement.getElementsByClassName("full-info");
157 if (!FullTextElems || FullTextElems.length < 1) {{
160 var FullText = FullTextElems[0];
161 if (FullText.style.display == 'none') {{
163 FullText.style.display = 'block';
166 FullText.style.display = 'none';
172 <div class="centered">
173 <table class="source">
176 <th style="width: 2%">Line</td>
177 <th style="width: 3%">Hotness</td>
178 <th style="width: 10%">Optimization</td>
179 <th style="width: 70%">Source</td>
180 <th style="width: 15%">Inline Context</td>
183 <tbody>'''.format(os
.path
.basename(self
.filename
)), file=self
.stream
)
184 self
.render_source_lines(self
.source_stream
, line_remarks
)
190 </html>''', file=self
.stream
)
194 def __init__(self
, output_dir
, should_display_hotness
, max_hottest_remarks_on_index
):
195 self
.stream
= io
.open(os
.path
.join(output_dir
, 'index.html'), 'w', encoding
='utf-8')
196 self
.should_display_hotness
= should_display_hotness
197 self
.max_hottest_remarks_on_index
= max_hottest_remarks_on_index
199 def render_entry(self
, r
, odd
):
200 escaped_name
= html
.escape(r
.DemangledFunctionName
)
203 <td class=\"column-entry-{odd}\"><a href={r.Link}>{r.DebugLocString}</a></td>
204 <td class=\"column-entry-{odd}\">{r.RelativeHotness}</td>
205 <td class=\"column-entry-{odd}\">{escaped_name}</td>
206 <td class=\"column-entry-{r.color}\">{r.PassWithDiffPrefix}</td>
207 </tr>'''.format(**locals()), file=self
.stream
)
209 def render(self
, all_remarks
):
212 <meta charset="utf-8" />
214 <link rel='stylesheet' type='text/css' href='style.css'>
217 <div class="centered">
220 <td>Source Location</td>
224 </tr>''', file=self
.stream
)
227 if self
.should_display_hotness
:
228 max_entries
= self
.max_hottest_remarks_on_index
230 for i
, remark
in enumerate(all_remarks
[:max_entries
]):
231 if not suppress(remark
):
232 self
.render_entry(remark
, i
% 2)
236 </html>''', file=self
.stream
)
239 def _render_file(source_dir
, output_dir
, ctx
, no_highlight
, entry
, filter_
):
242 filename
, remarks
= entry
243 SourceFileRenderer(source_dir
, output_dir
, filename
, no_highlight
).render(remarks
)
246 def map_remarks(all_remarks
):
247 # Set up a map between function names and their source location for
248 # function where inlining happened
249 for remark
in optrecord
.itervalues(all_remarks
):
250 if isinstance(remark
, optrecord
.Passed
) and remark
.Pass
== "inline" and remark
.Name
== "Inlined":
251 for arg
in remark
.Args
:
252 arg_dict
= dict(list(arg
))
253 caller
= arg_dict
.get('Caller')
256 context
.caller_loc
[caller
] = arg_dict
['DebugLoc']
261 def generate_report(all_remarks
,
266 should_display_hotness
,
267 max_hottest_remarks_on_index
,
269 should_print_progress
):
271 os
.makedirs(output_dir
)
273 if e
.errno
== errno
.EEXIST
and os
.path
.isdir(output_dir
):
278 if should_print_progress
:
279 print('Rendering index page...')
280 if should_display_hotness
:
281 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)
283 sorted_remarks
= sorted(optrecord
.itervalues(all_remarks
), key
=lambda r
: (r
.File
, r
.Line
, r
.Column
, r
.PassWithDiffPrefix
, r
.yaml_tag
, r
.Function
))
284 IndexRenderer(output_dir
, should_display_hotness
, max_hottest_remarks_on_index
).render(sorted_remarks
)
286 shutil
.copy(os
.path
.join(os
.path
.dirname(os
.path
.realpath(__file__
)),
287 "style.css"), output_dir
)
289 _render_file_bound
= functools
.partial(_render_file
, source_dir
, output_dir
, context
, no_highlight
)
290 if should_print_progress
:
291 print('Rendering HTML files...')
292 optpmap
.pmap(_render_file_bound
,
293 file_remarks
.items(),
295 should_print_progress
)
299 parser
= argparse
.ArgumentParser(description
=desc
)
301 'yaml_dirs_or_files',
303 help='List of optimization record files or directories searched '
304 'for optimization record files.')
309 help='Path to a directory where generated HTML files will be output. '
310 'If the directory does not already exist, it will be created. '
311 '"%(default)s" by default.')
317 help='Max job count (defaults to %(default)s, the current CPU count)')
322 help='set source directory')
324 '--no-progress-indicator',
328 help='Do not display any indicator of how many YAML files were read '
329 'or rendered into HTML.')
331 '--max-hottest-remarks-on-index',
334 help='Maximum number of the hottest remarks to appear on the index page')
339 help='Do not use a syntax highlighter when rendering the source code')
342 help='Set the demangler to be used (defaults to %s)' % optrecord
.Remark
.default_demangler
)
347 help='Only display remarks from passes matching filter expression')
349 # Do not make this a global variable. Values needed to be propagated through
350 # to individual classes and functions to be portable with multiprocessing across
351 # Windows and non-Windows.
352 args
= parser
.parse_args()
354 print_progress
= not args
.no_progress_indicator
356 optrecord
.Remark
.set_demangler(args
.demangler
)
358 files
= optrecord
.find_opt_files(*args
.yaml_dirs_or_files
)
360 parser
.error("No *.opt.yaml files found")
363 all_remarks
, file_remarks
, should_display_hotness
= \
364 optrecord
.gather_results(files
, args
.jobs
, print_progress
, args
.filter)
366 map_remarks(all_remarks
)
368 generate_report(all_remarks
,
373 should_display_hotness
,
374 args
.max_hottest_remarks_on_index
,
378 if __name__
== '__main__':