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