xref: /aosp_15_r20/external/autotest/client/bin/result_tools/view.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Li# Copyright 2017 The Chromium OS Authors. All rights reserved.
3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
4*9c5db199SXin Li# found in the LICENSE file.
5*9c5db199SXin Li
6*9c5db199SXin Li"""
7*9c5db199SXin LiThis is a utility to build an html page based on the directory summaries
8*9c5db199SXin Licollected during the test.
9*9c5db199SXin Li"""
10*9c5db199SXin Li
11*9c5db199SXin Liimport os
12*9c5db199SXin Liimport re
13*9c5db199SXin Li
14*9c5db199SXin Liimport common
15*9c5db199SXin Lifrom autotest_lib.client.bin.result_tools import utils_lib
16*9c5db199SXin Lifrom autotest_lib.client.common_lib import global_config
17*9c5db199SXin Li
18*9c5db199SXin Li
19*9c5db199SXin LiCONFIG = global_config.global_config
20*9c5db199SXin Li# Base url to open a file from Google Storage
21*9c5db199SXin LiGS_FILE_BASE_URL = CONFIG.get_config_value('CROS', 'gs_file_base_url')
22*9c5db199SXin Li
23*9c5db199SXin Li# Default width of `size_trimmed_width`. If throttle is not applied, the block
24*9c5db199SXin Li# of `size_trimmed_width` will be set to minimum to make the view more compact.
25*9c5db199SXin LiDEFAULT_SIZE_TRIMMED_WIDTH = 50
26*9c5db199SXin Li
27*9c5db199SXin LiDEFAULT_RESULT_SUMMARY_NAME = 'result_summary.html'
28*9c5db199SXin Li
29*9c5db199SXin LiDIR_SUMMARY_PATTERN = 'dir_summary_\d+.json'
30*9c5db199SXin Li
31*9c5db199SXin Li# ==================================================
32*9c5db199SXin Li# Following are key names used in the html templates:
33*9c5db199SXin Li
34*9c5db199SXin LiCSS = 'css'
35*9c5db199SXin LiDIRS = 'dirs'
36*9c5db199SXin LiGS_FILE_BASE_URL_KEY = 'gs_file_base_url'
37*9c5db199SXin LiINDENTATION_KEY = 'indentation'
38*9c5db199SXin LiJAVASCRIPT = 'javascript'
39*9c5db199SXin LiJOB_DIR = 'job_dir'
40*9c5db199SXin LiNAME = 'name'
41*9c5db199SXin LiPATH = 'path'
42*9c5db199SXin Li
43*9c5db199SXin LiSIZE_CLIENT_COLLECTED = 'size_client_collected'
44*9c5db199SXin Li
45*9c5db199SXin LiSIZE_INFO = 'size_info'
46*9c5db199SXin LiSIZE_ORIGINAL = 'size_original'
47*9c5db199SXin LiSIZE_PERCENT = 'size_percent'
48*9c5db199SXin LiSIZE_PERCENT_CLASS = 'size_percent_class'
49*9c5db199SXin LiSIZE_PERCENT_CLASS_REGULAR = 'size_percent'
50*9c5db199SXin LiSIZE_PERCENT_CLASS_TOP = 'top_size_percent'
51*9c5db199SXin LiSIZE_SUMMARY = 'size_summary'
52*9c5db199SXin LiSIZE_TRIMMED = 'size_trimmed'
53*9c5db199SXin Li
54*9c5db199SXin Li# Width of `size_trimmed` block`
55*9c5db199SXin LiSIZE_TRIMMED_WIDTH = 'size_trimmed_width'
56*9c5db199SXin Li
57*9c5db199SXin LiSUBDIRS = 'subdirs'
58*9c5db199SXin LiSUMMARY_TREE = 'summary_tree'
59*9c5db199SXin Li# ==================================================
60*9c5db199SXin Li
61*9c5db199SXin Li# Text to show when test result is not throttled.
62*9c5db199SXin LiNOT_THROTTLED = '(Not throttled)'
63*9c5db199SXin Li
64*9c5db199SXin Li
65*9c5db199SXin LiPAGE_TEMPLATE = """
66*9c5db199SXin Li<!DOCTYPE html>
67*9c5db199SXin Li  <html>
68*9c5db199SXin Li    <body onload="init()">
69*9c5db199SXin Li      <h3>Summary of test results</h3>
70*9c5db199SXin Li%(size_summary)s
71*9c5db199SXin Li      <p>
72*9c5db199SXin Li      <b>
73*9c5db199SXin Li        Display format of a file or directory:
74*9c5db199SXin Li      </b>
75*9c5db199SXin Li      </p>
76*9c5db199SXin Li      <p>
77*9c5db199SXin Li        <span class="size_percent" style="width:auto">
78*9c5db199SXin Li          [percentage of size in the parent directory]
79*9c5db199SXin Li        </span>
80*9c5db199SXin Li        <span class="size_original" style="width:auto">
81*9c5db199SXin Li          [original size]
82*9c5db199SXin Li        </span>
83*9c5db199SXin Li        <span class="size_trimmed" style="width:auto">
84*9c5db199SXin Li          [size after throttling (empty if not throttled)]
85*9c5db199SXin Li        </span>
86*9c5db199SXin Li        [file name (<strike>strikethrough</strike> if file was deleted due to
87*9c5db199SXin Li            throttling)]
88*9c5db199SXin Li      </p>
89*9c5db199SXin Li
90*9c5db199SXin Li      <button onclick="expandAll();">Expand All</button>
91*9c5db199SXin Li      <button onclick="collapseAll();">Collapse All</button>
92*9c5db199SXin Li
93*9c5db199SXin Li%(summary_tree)s
94*9c5db199SXin Li
95*9c5db199SXin Li%(css)s
96*9c5db199SXin Li%(javascript)s
97*9c5db199SXin Li
98*9c5db199SXin Li    </body>
99*9c5db199SXin Li</html>
100*9c5db199SXin Li"""
101*9c5db199SXin Li
102*9c5db199SXin LiCSS_TEMPLATE = """
103*9c5db199SXin Li<style>
104*9c5db199SXin Li  body {
105*9c5db199SXin Li      font-family: Arial;
106*9c5db199SXin Li  }
107*9c5db199SXin Li
108*9c5db199SXin Li  td.table_header {
109*9c5db199SXin Li      font-weight: normal;
110*9c5db199SXin Li  }
111*9c5db199SXin Li
112*9c5db199SXin Li  span.size_percent {
113*9c5db199SXin Li      color: #e8773e;
114*9c5db199SXin Li      display: inline-block;
115*9c5db199SXin Li      font-size: 75%%;
116*9c5db199SXin Li      text-align: right;
117*9c5db199SXin Li      width: 35px;
118*9c5db199SXin Li  }
119*9c5db199SXin Li
120*9c5db199SXin Li  span.top_size_percent {
121*9c5db199SXin Li      color: #e8773e;
122*9c5db199SXin Li      background-color: yellow;
123*9c5db199SXin Li      display: inline-block;
124*9c5db199SXin Li      font-size: 75%%;
125*9c5db199SXin Li      fount-weight: bold;
126*9c5db199SXin Li      text-align: right;
127*9c5db199SXin Li      width: 35px;
128*9c5db199SXin Li  }
129*9c5db199SXin Li
130*9c5db199SXin Li  span.size_original {
131*9c5db199SXin Li      color: sienna;
132*9c5db199SXin Li      display: inline-block;
133*9c5db199SXin Li      font-size: 75%%;
134*9c5db199SXin Li      text-align: right;
135*9c5db199SXin Li      width: 50px;
136*9c5db199SXin Li  }
137*9c5db199SXin Li
138*9c5db199SXin Li  span.size_trimmed {
139*9c5db199SXin Li      color: green;
140*9c5db199SXin Li      display: inline-block;
141*9c5db199SXin Li      font-size: 75%%;
142*9c5db199SXin Li      text-align: right;
143*9c5db199SXin Li      width: %(size_trimmed_width)dpx;
144*9c5db199SXin Li  }
145*9c5db199SXin Li
146*9c5db199SXin Li  ul.tree li {
147*9c5db199SXin Li      list-style-type: none;
148*9c5db199SXin Li      position: relative;
149*9c5db199SXin Li  }
150*9c5db199SXin Li
151*9c5db199SXin Li  ul.tree li ul {
152*9c5db199SXin Li      display: none;
153*9c5db199SXin Li  }
154*9c5db199SXin Li
155*9c5db199SXin Li  ul.tree li.open > ul {
156*9c5db199SXin Li      display: block;
157*9c5db199SXin Li  }
158*9c5db199SXin Li
159*9c5db199SXin Li  ul.tree li a {
160*9c5db199SXin Li    color: black;
161*9c5db199SXin Li    text-decoration: none;
162*9c5db199SXin Li  }
163*9c5db199SXin Li
164*9c5db199SXin Li  ul.tree li a.file {
165*9c5db199SXin Li    color: blue;
166*9c5db199SXin Li    text-decoration: underline;
167*9c5db199SXin Li  }
168*9c5db199SXin Li
169*9c5db199SXin Li  ul.tree li a:before {
170*9c5db199SXin Li      height: 1em;
171*9c5db199SXin Li      padding:0 .1em;
172*9c5db199SXin Li      font-size: .8em;
173*9c5db199SXin Li      display: block;
174*9c5db199SXin Li      position: absolute;
175*9c5db199SXin Li      left: -1.3em;
176*9c5db199SXin Li      top: .2em;
177*9c5db199SXin Li  }
178*9c5db199SXin Li
179*9c5db199SXin Li  ul.tree li > a:not(:last-child):before {
180*9c5db199SXin Li      content: '+';
181*9c5db199SXin Li  }
182*9c5db199SXin Li
183*9c5db199SXin Li  ul.tree li.open > a:not(:last-child):before {
184*9c5db199SXin Li      content: '-';
185*9c5db199SXin Li  }
186*9c5db199SXin Li</style>
187*9c5db199SXin Li"""
188*9c5db199SXin Li
189*9c5db199SXin LiJAVASCRIPT_TEMPLATE = """
190*9c5db199SXin Li<script>
191*9c5db199SXin Lifunction init() {
192*9c5db199SXin Li    var tree = document.querySelectorAll('ul.tree a:not(:last-child)');
193*9c5db199SXin Li    for(var i = 0; i < tree.length; i++){
194*9c5db199SXin Li        tree[i].addEventListener('click', function(e) {
195*9c5db199SXin Li            var parent = e.target.parentElement;
196*9c5db199SXin Li            var classList = parent.classList;
197*9c5db199SXin Li            if(classList.contains("open")) {
198*9c5db199SXin Li                classList.remove('open');
199*9c5db199SXin Li                var opensubs = parent.querySelectorAll(':scope .open');
200*9c5db199SXin Li                for(var i = 0; i < opensubs.length; i++){
201*9c5db199SXin Li                    opensubs[i].classList.remove('open');
202*9c5db199SXin Li                }
203*9c5db199SXin Li            } else {
204*9c5db199SXin Li                classList.add('open');
205*9c5db199SXin Li            }
206*9c5db199SXin Li        });
207*9c5db199SXin Li    }
208*9c5db199SXin Li}
209*9c5db199SXin Li
210*9c5db199SXin Lifunction expandAll() {
211*9c5db199SXin Li    var tree = document.querySelectorAll('ul.tree a:not(:last-child)');
212*9c5db199SXin Li    for(var i = 0; i < tree.length; i++){
213*9c5db199SXin Li        var classList = tree[i].parentElement.classList;
214*9c5db199SXin Li        if(classList.contains("close")) {
215*9c5db199SXin Li            classList.remove('close');
216*9c5db199SXin Li        }
217*9c5db199SXin Li        classList.add('open');
218*9c5db199SXin Li    }
219*9c5db199SXin Li}
220*9c5db199SXin Li
221*9c5db199SXin Lifunction collapseAll() {
222*9c5db199SXin Li    var tree = document.querySelectorAll('ul.tree a:not(:last-child)');
223*9c5db199SXin Li    for(var i = 0; i < tree.length; i++){
224*9c5db199SXin Li        var classList = tree[i].parentElement.classList;
225*9c5db199SXin Li        if(classList.contains("open")) {
226*9c5db199SXin Li            classList.remove('open');
227*9c5db199SXin Li        }
228*9c5db199SXin Li        classList.add('close');
229*9c5db199SXin Li    }
230*9c5db199SXin Li}
231*9c5db199SXin Li
232*9c5db199SXin Li// If the current url has `gs_url`, it means the file is opened from Google
233*9c5db199SXin Li// Storage.
234*9c5db199SXin Livar gs_url = 'apidata.googleusercontent.com';
235*9c5db199SXin Li// Base url to open a file from Google Storage
236*9c5db199SXin Livar gs_file_base_url = '%(gs_file_base_url)s'
237*9c5db199SXin Li// Path to the result.
238*9c5db199SXin Livar job_dir = '%(job_dir)s'
239*9c5db199SXin Li
240*9c5db199SXin Lifunction openFile(path) {
241*9c5db199SXin Li    if(window.location.href.includes(gs_url)) {
242*9c5db199SXin Li        url = gs_file_base_url + job_dir + '/' + path.substring(3);
243*9c5db199SXin Li    } else {
244*9c5db199SXin Li        url = window.location.href + '/' + path;
245*9c5db199SXin Li    }
246*9c5db199SXin Li    window.open(url, '_blank');
247*9c5db199SXin Li}
248*9c5db199SXin Li</script>
249*9c5db199SXin Li"""
250*9c5db199SXin Li
251*9c5db199SXin LiSIZE_SUMMARY_TEMPLATE = """
252*9c5db199SXin Li<table>
253*9c5db199SXin Li  <tr>
254*9c5db199SXin Li    <td class="table_header">Results collected from test device: </td>
255*9c5db199SXin Li    <td><span>%(size_client_collected)s</span> </td>
256*9c5db199SXin Li  </tr>
257*9c5db199SXin Li  <tr>
258*9c5db199SXin Li    <td class="table_header">Original size of test results:</td>
259*9c5db199SXin Li    <td>
260*9c5db199SXin Li      <span class="size_original" style="font-size:100%%;width:auto">
261*9c5db199SXin Li        %(size_original)s
262*9c5db199SXin Li      </span>
263*9c5db199SXin Li    </td>
264*9c5db199SXin Li  </tr>
265*9c5db199SXin Li  <tr>
266*9c5db199SXin Li    <td class="table_header">Size of test results after throttling:</td>
267*9c5db199SXin Li    <td>
268*9c5db199SXin Li      <span class="size_trimmed" style="font-size:100%%;width:auto">
269*9c5db199SXin Li        %(size_trimmed)s
270*9c5db199SXin Li      </span>
271*9c5db199SXin Li    </td>
272*9c5db199SXin Li  </tr>
273*9c5db199SXin Li</table>
274*9c5db199SXin Li"""
275*9c5db199SXin Li
276*9c5db199SXin LiSIZE_INFO_TEMPLATE = """
277*9c5db199SXin Li%(indentation)s<span class="%(size_percent_class)s">%(size_percent)s</span>
278*9c5db199SXin Li%(indentation)s<span class="size_original">%(size_original)s</span>
279*9c5db199SXin Li%(indentation)s<span class="size_trimmed">%(size_trimmed)s</span> """
280*9c5db199SXin Li
281*9c5db199SXin LiFILE_ENTRY_TEMPLATE = """
282*9c5db199SXin Li%(indentation)s<li>
283*9c5db199SXin Li%(indentation)s\t<div>
284*9c5db199SXin Li%(size_info)s
285*9c5db199SXin Li%(indentation)s\t\t<a class="file" href="javascript:openFile('%(path)s');" >
286*9c5db199SXin Li%(indentation)s\t\t\t%(name)s
287*9c5db199SXin Li%(indentation)s\t\t</a>
288*9c5db199SXin Li%(indentation)s\t</div>
289*9c5db199SXin Li%(indentation)s</li>"""
290*9c5db199SXin Li
291*9c5db199SXin LiDELETED_FILE_ENTRY_TEMPLATE = """
292*9c5db199SXin Li%(indentation)s<li>
293*9c5db199SXin Li%(indentation)s\t<div>
294*9c5db199SXin Li%(size_info)s
295*9c5db199SXin Li%(indentation)s\t\t<strike>%(name)s</strike>
296*9c5db199SXin Li%(indentation)s\t</div>
297*9c5db199SXin Li%(indentation)s</li>"""
298*9c5db199SXin Li
299*9c5db199SXin LiDIR_ENTRY_TEMPLATE = """
300*9c5db199SXin Li%(indentation)s<li><a>%(size_info)s %(name)s</a>
301*9c5db199SXin Li%(subdirs)s
302*9c5db199SXin Li%(indentation)s</li>"""
303*9c5db199SXin Li
304*9c5db199SXin LiSUBDIRS_WRAPPER_TEMPLATE = """
305*9c5db199SXin Li%(indentation)s<ul class="tree">
306*9c5db199SXin Li%(dirs)s
307*9c5db199SXin Li%(indentation)s</ul>"""
308*9c5db199SXin Li
309*9c5db199SXin LiINDENTATION = '\t'
310*9c5db199SXin Li
311*9c5db199SXin Lidef _get_size_percent(size_original, total_bytes):
312*9c5db199SXin Li    """Get the percentage of file size in the parent directory before throttled.
313*9c5db199SXin Li
314*9c5db199SXin Li    @param size_original: Original size of the file, in bytes.
315*9c5db199SXin Li    @param total_bytes: Total size of all files under the parent directory, in
316*9c5db199SXin Li            bytes.
317*9c5db199SXin Li    @return: A formatted string of the percentage of file size in the parent
318*9c5db199SXin Li            directory before throttled.
319*9c5db199SXin Li    """
320*9c5db199SXin Li    if total_bytes == 0:
321*9c5db199SXin Li        return '0%'
322*9c5db199SXin Li    return '%.1f%%' % (100*float(size_original)/total_bytes)
323*9c5db199SXin Li
324*9c5db199SXin Li
325*9c5db199SXin Lidef _get_dirs_html(dirs, parent_path, total_bytes, indentation):
326*9c5db199SXin Li    """Get the html string for the given directory.
327*9c5db199SXin Li
328*9c5db199SXin Li    @param dirs: A list of ResultInfo.
329*9c5db199SXin Li    @param parent_path: Path to the parent directory.
330*9c5db199SXin Li    @param total_bytes: Total of the original size of files in the given
331*9c5db199SXin Li            directories in bytes.
332*9c5db199SXin Li    @param indentation: Indentation to be used for the html.
333*9c5db199SXin Li    """
334*9c5db199SXin Li    if not dirs:
335*9c5db199SXin Li        return ''
336*9c5db199SXin Li    summary_html = ''
337*9c5db199SXin Li    top_size_limit = max([entry.original_size for entry in dirs])
338*9c5db199SXin Li    # A map between file name to ResultInfo that contains the summary of the
339*9c5db199SXin Li    # file.
340*9c5db199SXin Li    entries = dict((list(entry.keys())[0], entry) for entry in dirs)
341*9c5db199SXin Li    for name in sorted(entries.keys()):
342*9c5db199SXin Li        entry = entries[name]
343*9c5db199SXin Li        if not entry.is_dir and re.match(DIR_SUMMARY_PATTERN, name):
344*9c5db199SXin Li            # Do not include directory summary json files in the html, as they
345*9c5db199SXin Li            # will be deleted.
346*9c5db199SXin Li            continue
347*9c5db199SXin Li
348*9c5db199SXin Li        size_data = {SIZE_PERCENT: _get_size_percent(entry.original_size,
349*9c5db199SXin Li                                                     total_bytes),
350*9c5db199SXin Li                     SIZE_ORIGINAL:
351*9c5db199SXin Li                        utils_lib.get_size_string(entry.original_size),
352*9c5db199SXin Li                     SIZE_TRIMMED:
353*9c5db199SXin Li                        utils_lib.get_size_string(entry.trimmed_size),
354*9c5db199SXin Li                     INDENTATION_KEY: indentation + 2*INDENTATION}
355*9c5db199SXin Li        if entry.original_size < top_size_limit:
356*9c5db199SXin Li            size_data[SIZE_PERCENT_CLASS] = SIZE_PERCENT_CLASS_REGULAR
357*9c5db199SXin Li        else:
358*9c5db199SXin Li            size_data[SIZE_PERCENT_CLASS] = SIZE_PERCENT_CLASS_TOP
359*9c5db199SXin Li        if entry.trimmed_size == entry.original_size:
360*9c5db199SXin Li            size_data[SIZE_TRIMMED] = ''
361*9c5db199SXin Li
362*9c5db199SXin Li        entry_path = '%s/%s' % (parent_path, name)
363*9c5db199SXin Li        if not entry.is_dir:
364*9c5db199SXin Li            # This is a file
365*9c5db199SXin Li            data = {NAME: name,
366*9c5db199SXin Li                    PATH: entry_path,
367*9c5db199SXin Li                    SIZE_INFO: SIZE_INFO_TEMPLATE % size_data,
368*9c5db199SXin Li                    INDENTATION_KEY: indentation}
369*9c5db199SXin Li            if entry.original_size > 0 and entry.trimmed_size == 0:
370*9c5db199SXin Li                summary_html += DELETED_FILE_ENTRY_TEMPLATE % data
371*9c5db199SXin Li            else:
372*9c5db199SXin Li                summary_html += FILE_ENTRY_TEMPLATE % data
373*9c5db199SXin Li        else:
374*9c5db199SXin Li            subdir_total_size = entry.original_size
375*9c5db199SXin Li            sub_indentation = indentation + INDENTATION
376*9c5db199SXin Li            subdirs_html = (
377*9c5db199SXin Li                    SUBDIRS_WRAPPER_TEMPLATE %
378*9c5db199SXin Li                    {DIRS: _get_dirs_html(
379*9c5db199SXin Li                            entry.files, entry_path, subdir_total_size,
380*9c5db199SXin Li                            sub_indentation),
381*9c5db199SXin Li                     INDENTATION_KEY: indentation})
382*9c5db199SXin Li            data = {NAME: entry.name,
383*9c5db199SXin Li                    SIZE_INFO: SIZE_INFO_TEMPLATE % size_data,
384*9c5db199SXin Li                    SUBDIRS: subdirs_html,
385*9c5db199SXin Li                    INDENTATION_KEY: indentation}
386*9c5db199SXin Li            summary_html += DIR_ENTRY_TEMPLATE % data
387*9c5db199SXin Li    return summary_html
388*9c5db199SXin Li
389*9c5db199SXin Li
390*9c5db199SXin Lidef build(client_collected_bytes, summary, html_file):
391*9c5db199SXin Li    """Generate an HTML file to visualize the given directory summary.
392*9c5db199SXin Li
393*9c5db199SXin Li    @param client_collected_bytes: The total size of results collected from
394*9c5db199SXin Li            the DUT. The number can be larger than the total file size of the
395*9c5db199SXin Li            given path, as files can be overwritten or removed.
396*9c5db199SXin Li    @param summary: A ResultInfo instance containing the directory summary.
397*9c5db199SXin Li    @param html_file: Path to save the html file to.
398*9c5db199SXin Li    """
399*9c5db199SXin Li    size_original = summary.original_size
400*9c5db199SXin Li    size_trimmed = summary.trimmed_size
401*9c5db199SXin Li    size_summary_data = {SIZE_CLIENT_COLLECTED:
402*9c5db199SXin Li                             utils_lib.get_size_string(client_collected_bytes),
403*9c5db199SXin Li                         SIZE_ORIGINAL:
404*9c5db199SXin Li                             utils_lib.get_size_string(size_original),
405*9c5db199SXin Li                         SIZE_TRIMMED:
406*9c5db199SXin Li                             utils_lib.get_size_string(size_trimmed)}
407*9c5db199SXin Li    size_trimmed_width = DEFAULT_SIZE_TRIMMED_WIDTH
408*9c5db199SXin Li    if size_original == size_trimmed:
409*9c5db199SXin Li        size_summary_data[SIZE_TRIMMED] = NOT_THROTTLED
410*9c5db199SXin Li        size_trimmed_width = 0
411*9c5db199SXin Li
412*9c5db199SXin Li    size_summary = SIZE_SUMMARY_TEMPLATE % size_summary_data
413*9c5db199SXin Li
414*9c5db199SXin Li    indentation = INDENTATION
415*9c5db199SXin Li    dirs_html = _get_dirs_html(
416*9c5db199SXin Li            summary.files, '..', size_original, indentation + INDENTATION)
417*9c5db199SXin Li    summary_tree = SUBDIRS_WRAPPER_TEMPLATE % {DIRS: dirs_html,
418*9c5db199SXin Li                                               INDENTATION_KEY: indentation}
419*9c5db199SXin Li
420*9c5db199SXin Li    # job_dir is the path between Autotest `results` folder and the summary html
421*9c5db199SXin Li    # file, e.g., 123-debug_user/host1. Assume it always contains 2 levels.
422*9c5db199SXin Li    job_dir_sections = html_file.split(os.sep)[:-1]
423*9c5db199SXin Li    try:
424*9c5db199SXin Li        job_dir = '/'.join(job_dir_sections[
425*9c5db199SXin Li                (job_dir_sections.index('results')+1):])
426*9c5db199SXin Li    except ValueError:
427*9c5db199SXin Li        # 'results' is not in the path, default to two levels up of the summary
428*9c5db199SXin Li        # file.
429*9c5db199SXin Li        job_dir = '/'.join(job_dir_sections[-2:])
430*9c5db199SXin Li
431*9c5db199SXin Li    javascript = (JAVASCRIPT_TEMPLATE %
432*9c5db199SXin Li                  {GS_FILE_BASE_URL_KEY: GS_FILE_BASE_URL,
433*9c5db199SXin Li                   JOB_DIR: job_dir})
434*9c5db199SXin Li    css = CSS_TEMPLATE % {SIZE_TRIMMED_WIDTH: size_trimmed_width}
435*9c5db199SXin Li    html = PAGE_TEMPLATE % {SIZE_SUMMARY: size_summary,
436*9c5db199SXin Li                            SUMMARY_TREE: summary_tree,
437*9c5db199SXin Li                            CSS: css,
438*9c5db199SXin Li                            JAVASCRIPT: javascript}
439*9c5db199SXin Li    with open(html_file, 'w') as f:
440*9c5db199SXin Li        f.write(html)
441