xref: /aosp_15_r20/external/autotest/autotest_lib/tko/display.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Lifrom __future__ import absolute_import
3*9c5db199SXin Lifrom __future__ import division
4*9c5db199SXin Lifrom __future__ import print_function
5*9c5db199SXin Liimport re
6*9c5db199SXin Liimport reason_qualifier
7*9c5db199SXin Li
8*9c5db199SXin Li# pylint: disable=missing-docstring
9*9c5db199SXin Li
10*9c5db199SXin Licolor_map = {
11*9c5db199SXin Li        'header'        : '#e5e5c0', # greyish yellow
12*9c5db199SXin Li        'blank'         : '#ffffff', # white
13*9c5db199SXin Li        'plain_text'    : '#e5e5c0', # greyish yellow
14*9c5db199SXin Li        'borders'       : '#bbbbbb', # grey
15*9c5db199SXin Li        'white'         : '#ffffff', # white
16*9c5db199SXin Li        'green'         : '#66ff66', # green
17*9c5db199SXin Li        'yellow'        : '#fffc00', # yellow
18*9c5db199SXin Li        'red'           : '#ff6666', # red
19*9c5db199SXin Li
20*9c5db199SXin Li        #### additional keys for shaded color of a box
21*9c5db199SXin Li        #### depending on stats of GOOD/FAIL
22*9c5db199SXin Li        '100pct'  : '#32CD32', # green, 94% to 100% of success
23*9c5db199SXin Li        '95pct'   : '#c0ff80', # step twrds yellow, 88% to 94% of success
24*9c5db199SXin Li        '90pct'   : '#ffff00', # yellow, 82% to 88%
25*9c5db199SXin Li        '85pct'   : '#ffc040', # 76% to 82%
26*9c5db199SXin Li        '75pct'   : '#ff4040', # red, 1% to 76%
27*9c5db199SXin Li        '0pct'    : '#d080d0', # violet, <1% of success
28*9c5db199SXin Li
29*9c5db199SXin Li}
30*9c5db199SXin Li
31*9c5db199SXin Li_brief_mode = False
32*9c5db199SXin Li
33*9c5db199SXin Li
34*9c5db199SXin Lidef set_brief_mode():
35*9c5db199SXin Li    global _brief_mode
36*9c5db199SXin Li    _brief_mode = True
37*9c5db199SXin Li
38*9c5db199SXin Li
39*9c5db199SXin Lidef is_brief_mode():
40*9c5db199SXin Li    return _brief_mode
41*9c5db199SXin Li
42*9c5db199SXin Li
43*9c5db199SXin Lidef color_keys_row():
44*9c5db199SXin Li    """ Returns one row table with samples of 'NNpct' colors
45*9c5db199SXin Li            defined in the color_map
46*9c5db199SXin Li            and numbers of corresponding %%
47*9c5db199SXin Li    """
48*9c5db199SXin Li    ### This function does not require maintenance in case of
49*9c5db199SXin Li    ### color_map augmenting - as long as
50*9c5db199SXin Li    ### color keys for box shading have names that end with 'pct'
51*9c5db199SXin Li    keys = [key for key in color_map.keys() if key.endswith('pct')]
52*9c5db199SXin Li    def num_pct(key):
53*9c5db199SXin Li        return int(key.replace('pct',''))
54*9c5db199SXin Li    keys.sort(key=num_pct)
55*9c5db199SXin Li    html = ''
56*9c5db199SXin Li    for key in keys:
57*9c5db199SXin Li        html+= "\t\t\t<td bgcolor =%s>&nbsp;&nbsp;&nbsp;</td>\n"\
58*9c5db199SXin Li                        % color_map[key]
59*9c5db199SXin Li        hint = key.replace('pct',' %')
60*9c5db199SXin Li        if hint[0]!='0': ## anything but 0 %
61*9c5db199SXin Li            hint = 'to ' + hint
62*9c5db199SXin Li        html+= "\t\t\t<td> %s </td>\n" % hint
63*9c5db199SXin Li
64*9c5db199SXin Li    html = """
65*9c5db199SXin Li<table width = "500" border="0" cellpadding="2" cellspacing="2">\n
66*9c5db199SXin Li    <tbody>\n
67*9c5db199SXin Li            <tr>\n
68*9c5db199SXin Li%s
69*9c5db199SXin Li            </tr>\n
70*9c5db199SXin Li    </tbody>
71*9c5db199SXin Li</table><br>
72*9c5db199SXin Li""" % html
73*9c5db199SXin Li    return html
74*9c5db199SXin Li
75*9c5db199SXin Li
76*9c5db199SXin Lidef calculate_html(link, data, tooltip=None, row_label=None, column_label=None):
77*9c5db199SXin Li    if not is_brief_mode():
78*9c5db199SXin Li        hover_text = '%s:%s' % (row_label, column_label)
79*9c5db199SXin Li        if data:  ## cell is not empty
80*9c5db199SXin Li            hover_text += '<br>%s' % tooltip
81*9c5db199SXin Li        else:
82*9c5db199SXin Li            ## avoid "None" printed in empty cells
83*9c5db199SXin Li            data = '&nbsp;'
84*9c5db199SXin Li        html = ('<center><a class="info" href="%s">'
85*9c5db199SXin Li                '%s<span>%s</span></a></center>' %
86*9c5db199SXin Li                (link, data, hover_text))
87*9c5db199SXin Li        return html
88*9c5db199SXin Li    # no hover if embedded into AFE but links shall redirect to new window
89*9c5db199SXin Li    if data: ## cell is non empty
90*9c5db199SXin Li        html =  '<a href="%s" target="_blank">%s</a>' % (link, data)
91*9c5db199SXin Li        return html
92*9c5db199SXin Li    else: ## cell is empty
93*9c5db199SXin Li        return '&nbsp;'
94*9c5db199SXin Li
95*9c5db199SXin Li
96*9c5db199SXin Liclass box:
97*9c5db199SXin Li    def __init__(self, data, color_key = None, header = False, link = None,
98*9c5db199SXin Li                 tooltip = None, row_label = None, column_label = None):
99*9c5db199SXin Li
100*9c5db199SXin Li        ## in brief mode we display grid table only and nothing more
101*9c5db199SXin Li        ## - mouse hovering feature is stubbed in brief mode
102*9c5db199SXin Li        ## - any link opens new window or tab
103*9c5db199SXin Li
104*9c5db199SXin Li        redirect = ""
105*9c5db199SXin Li        if is_brief_mode():
106*9c5db199SXin Li            ## we are acting under AFE
107*9c5db199SXin Li            ## any link shall open new window
108*9c5db199SXin Li            redirect = " target=NEW"
109*9c5db199SXin Li
110*9c5db199SXin Li        if data:
111*9c5db199SXin Li            data = "<tt>%s</tt>" % data
112*9c5db199SXin Li
113*9c5db199SXin Li        if link and not tooltip:
114*9c5db199SXin Li            ## FlipAxis corner, column and row headers
115*9c5db199SXin Li            self.data = ('<a href="%s"%s>%s</a>' %
116*9c5db199SXin Li                         (link, redirect, data))
117*9c5db199SXin Li        else:
118*9c5db199SXin Li            self.data = calculate_html(link, data, tooltip,
119*9c5db199SXin Li                                       row_label, column_label)
120*9c5db199SXin Li
121*9c5db199SXin Li        if color_key in color_map:
122*9c5db199SXin Li            self.color = color_map[color_key]
123*9c5db199SXin Li        elif header:
124*9c5db199SXin Li            self.color = color_map['header']
125*9c5db199SXin Li        elif data:
126*9c5db199SXin Li            self.color = color_map['plain_text']
127*9c5db199SXin Li        else:
128*9c5db199SXin Li            self.color = color_map['blank']
129*9c5db199SXin Li        self.header = header
130*9c5db199SXin Li
131*9c5db199SXin Li
132*9c5db199SXin Li    def html(self):
133*9c5db199SXin Li        if self.data:
134*9c5db199SXin Li            data = self.data
135*9c5db199SXin Li        else:
136*9c5db199SXin Li            data = '&nbsp'
137*9c5db199SXin Li
138*9c5db199SXin Li        if self.header:
139*9c5db199SXin Li            box_html = 'th'
140*9c5db199SXin Li        else:
141*9c5db199SXin Li            box_html = 'td'
142*9c5db199SXin Li
143*9c5db199SXin Li        return "<%s bgcolor=%s>%s</%s>" % \
144*9c5db199SXin Li                                (box_html, self.color, data, box_html)
145*9c5db199SXin Li
146*9c5db199SXin Li
147*9c5db199SXin Lidef grade_from_status(status_idx, status):
148*9c5db199SXin Li    # % of goodness
149*9c5db199SXin Li    # GOOD (6)  -> 1
150*9c5db199SXin Li    # TEST_NA (8) is not counted
151*9c5db199SXin Li    # ##  If the test doesn't PASS, it FAILS
152*9c5db199SXin Li    # else -> 0
153*9c5db199SXin Li
154*9c5db199SXin Li    if status == status_idx['GOOD']:
155*9c5db199SXin Li        return 1.0
156*9c5db199SXin Li    else:
157*9c5db199SXin Li        return 0.0
158*9c5db199SXin Li
159*9c5db199SXin Li
160*9c5db199SXin Lidef average_grade_from_status_count(status_idx, status_count):
161*9c5db199SXin Li    average_grade = 0
162*9c5db199SXin Li    total_count = 0
163*9c5db199SXin Li    for key in status_count.keys():
164*9c5db199SXin Li        if key not in (status_idx['TEST_NA'], status_idx['RUNNING']):
165*9c5db199SXin Li            average_grade += (grade_from_status(status_idx, key)
166*9c5db199SXin Li                                    * status_count[key])
167*9c5db199SXin Li            total_count += status_count[key]
168*9c5db199SXin Li    if total_count != 0:
169*9c5db199SXin Li        average_grade = average_grade / total_count
170*9c5db199SXin Li    else:
171*9c5db199SXin Li        average_grade = 0.0
172*9c5db199SXin Li    return average_grade
173*9c5db199SXin Li
174*9c5db199SXin Li
175*9c5db199SXin Lidef shade_from_status_count(status_idx, status_count):
176*9c5db199SXin Li    if not status_count:
177*9c5db199SXin Li        return None
178*9c5db199SXin Li
179*9c5db199SXin Li    ## average_grade defines a shade of the box
180*9c5db199SXin Li    ## 0 -> violet
181*9c5db199SXin Li    ## 0.76 -> red
182*9c5db199SXin Li    ## 0.88-> yellow
183*9c5db199SXin Li    ## 1.0 -> green
184*9c5db199SXin Li    average_grade = average_grade_from_status_count(status_idx, status_count)
185*9c5db199SXin Li
186*9c5db199SXin Li    ## find appropiate keyword from color_map
187*9c5db199SXin Li    if average_grade<0.01:
188*9c5db199SXin Li        shade = '0pct'
189*9c5db199SXin Li    elif average_grade<0.75:
190*9c5db199SXin Li        shade = '75pct'
191*9c5db199SXin Li    elif average_grade<0.85:
192*9c5db199SXin Li        shade = '85pct'
193*9c5db199SXin Li    elif average_grade<0.90:
194*9c5db199SXin Li        shade = '90pct'
195*9c5db199SXin Li    elif average_grade<0.95:
196*9c5db199SXin Li        shade = '95pct'
197*9c5db199SXin Li    else:
198*9c5db199SXin Li        shade = '100pct'
199*9c5db199SXin Li
200*9c5db199SXin Li    return shade
201*9c5db199SXin Li
202*9c5db199SXin Li
203*9c5db199SXin Lidef status_html(db, box_data, shade):
204*9c5db199SXin Li    """
205*9c5db199SXin Li    status_count: dict mapping from status (integer key) to count
206*9c5db199SXin Li    eg. { 'GOOD' : 4, 'FAIL' : 1 }
207*9c5db199SXin Li    """
208*9c5db199SXin Li    status_count_subset = box_data.status_count.copy()
209*9c5db199SXin Li    test_na = db.status_idx['TEST_NA']
210*9c5db199SXin Li    running = db.status_idx['RUNNING']
211*9c5db199SXin Li    good = db.status_idx['GOOD']
212*9c5db199SXin Li
213*9c5db199SXin Li    status_count_subset[test_na] = 0  # Don't count TEST_NA
214*9c5db199SXin Li    status_count_subset[running] = 0  # Don't count RUNNING
215*9c5db199SXin Li    html = "%d&nbsp;/&nbsp;%d " % (status_count_subset.get(good, 0),
216*9c5db199SXin Li                                   sum(status_count_subset.values()))
217*9c5db199SXin Li    if test_na in box_data.status_count.keys():
218*9c5db199SXin Li        html += ' (%d&nbsp;N/A)' % box_data.status_count[test_na]
219*9c5db199SXin Li    if running in box_data.status_count.keys():
220*9c5db199SXin Li        html += ' (%d&nbsp;running)' % box_data.status_count[running]
221*9c5db199SXin Li
222*9c5db199SXin Li    if box_data.reasons_list:
223*9c5db199SXin Li        reasons_list = box_data.reasons_list
224*9c5db199SXin Li        aggregated_reasons_list = \
225*9c5db199SXin Li                reason_qualifier.aggregate_reason_fields(reasons_list)
226*9c5db199SXin Li        for reason in aggregated_reasons_list:
227*9c5db199SXin Li            ## a bit of more postprocessing
228*9c5db199SXin Li            ## to look nicer in a cell
229*9c5db199SXin Li            ## in future: to do subtable within the cell
230*9c5db199SXin Li            reason = reason.replace('<br>','\n')
231*9c5db199SXin Li            reason = reason.replace('<','[').replace('>',']')
232*9c5db199SXin Li            reason = reason.replace('|','\n').replace('&',' AND ')
233*9c5db199SXin Li            reason = reason.replace('\n','<br>')
234*9c5db199SXin Li            html += '<br>' + reason
235*9c5db199SXin Li
236*9c5db199SXin Li    tooltip = ""
237*9c5db199SXin Li    for status in sorted(box_data.status_count.keys(), reverse = True):
238*9c5db199SXin Li        status_word = db.status_word[status]
239*9c5db199SXin Li        tooltip += "%d %s " % (box_data.status_count[status], status_word)
240*9c5db199SXin Li    return (html,tooltip)
241*9c5db199SXin Li
242*9c5db199SXin Li
243*9c5db199SXin Lidef status_count_box(db, tests, link = None):
244*9c5db199SXin Li    """
245*9c5db199SXin Li    Display a ratio of total number of GOOD tests
246*9c5db199SXin Li    to total number of all tests in the group of tests.
247*9c5db199SXin Li    More info (e.g. 10 GOOD, 2 WARN, 3 FAIL) is in tooltips
248*9c5db199SXin Li    """
249*9c5db199SXin Li    if not tests:
250*9c5db199SXin Li        return box(None, None)
251*9c5db199SXin Li
252*9c5db199SXin Li    status_count = {}
253*9c5db199SXin Li    for test in tests:
254*9c5db199SXin Li        count = status_count.get(test.status_num, 0)
255*9c5db199SXin Li        status_count[test.status_num] = count + 1
256*9c5db199SXin Li    return status_precounted_box(db, status_count, link)
257*9c5db199SXin Li
258*9c5db199SXin Li
259*9c5db199SXin Lidef status_precounted_box(db, box_data, link = None,
260*9c5db199SXin Li                                 x_label = None, y_label = None):
261*9c5db199SXin Li    """
262*9c5db199SXin Li    Display a ratio of total number of GOOD tests
263*9c5db199SXin Li    to total number of all tests in the group of tests.
264*9c5db199SXin Li    More info (e.g. 10 GOOD, 2 WARN, 3 FAIL) is in tooltips
265*9c5db199SXin Li    """
266*9c5db199SXin Li    status_count = box_data.status_count
267*9c5db199SXin Li    if not status_count:
268*9c5db199SXin Li        return box(None, None)
269*9c5db199SXin Li
270*9c5db199SXin Li    shade = shade_from_status_count(db.status_idx, status_count)
271*9c5db199SXin Li    html,tooltip = status_html(db, box_data, shade)
272*9c5db199SXin Li    precounted_box = box(html, shade, False, link, tooltip,
273*9c5db199SXin Li                            x_label, y_label)
274*9c5db199SXin Li    return precounted_box
275*9c5db199SXin Li
276*9c5db199SXin Li
277*9c5db199SXin Lidef print_table(matrix):
278*9c5db199SXin Li    """
279*9c5db199SXin Li    matrix: list of lists of boxes, giving a matrix of data
280*9c5db199SXin Li    Each of the inner lists is a row, not a column.
281*9c5db199SXin Li
282*9c5db199SXin Li    Display the given matrix of data as a table.
283*9c5db199SXin Li    """
284*9c5db199SXin Li
285*9c5db199SXin Li    print(('<table bgcolor="%s" cellspacing="1" cellpadding="5" '
286*9c5db199SXin Li           'style="margin-right: 200px;">') % (
287*9c5db199SXin Li           color_map['borders']))
288*9c5db199SXin Li    for row in matrix:
289*9c5db199SXin Li        print('<tr>')
290*9c5db199SXin Li        for element in row:
291*9c5db199SXin Li            print(element.html())
292*9c5db199SXin Li        print('</tr>')
293*9c5db199SXin Li    print('</table>')
294*9c5db199SXin Li
295*9c5db199SXin Li
296*9c5db199SXin Lidef print_main_header():
297*9c5db199SXin Li    hover_css="""\
298*9c5db199SXin Lia.info{
299*9c5db199SXin Liposition:relative; /*this is the key*/
300*9c5db199SXin Liz-index:1
301*9c5db199SXin Licolor:#000;
302*9c5db199SXin Litext-decoration:none}
303*9c5db199SXin Li
304*9c5db199SXin Lia.info:hover{z-index:25;}
305*9c5db199SXin Li
306*9c5db199SXin Lia.info span{display: none}
307*9c5db199SXin Li
308*9c5db199SXin Lia.info:hover span{ /*the span will display just on :hover state*/
309*9c5db199SXin Lidisplay:block;
310*9c5db199SXin Liposition:absolute;
311*9c5db199SXin Litop:1em; left:1em;
312*9c5db199SXin Limin-width: 100px;
313*9c5db199SXin Lioverflow: visible;
314*9c5db199SXin Liborder:1px solid #036;
315*9c5db199SXin Libackground-color:#fff; color:#000;
316*9c5db199SXin Litext-align: left
317*9c5db199SXin Li}
318*9c5db199SXin Li"""
319*9c5db199SXin Li    print('<head><style type="text/css">')
320*9c5db199SXin Li    print('a { text-decoration: none }')
321*9c5db199SXin Li    print(hover_css)
322*9c5db199SXin Li    print('</style></head>')
323*9c5db199SXin Li    print('<h2>')
324*9c5db199SXin Li    print('<a href="compose_query.cgi">Functional</a>')
325*9c5db199SXin Li    print('&nbsp&nbsp&nbsp')
326*9c5db199SXin Li
327*9c5db199SXin Li
328*9c5db199SXin Lidef group_name(group):
329*9c5db199SXin Li    name = re.sub('_', '<br>', group.name)
330*9c5db199SXin Li    if re.search('/', name):
331*9c5db199SXin Li        (owner, machine) = name.split('/', 1)
332*9c5db199SXin Li        name = owner + '<br>' + machine
333*9c5db199SXin Li    return name
334