Home | History | Annotate | Download | only in tko
      1 #!/usr/bin/python
      2 
      3 """
      4 Selects all rows and columns that satisfy the condition specified
      5 and draws the matrix. There is a seperate SQL query made for every (x,y)
      6 in the matrix.
      7 """
      8 
      9 print "Content-type: text/html\n"
     10 
     11 import sys, os, urllib, cgi, cgitb, re, datetime, time
     12 
     13 total_wall_time_start = time.time()
     14 
     15 import common
     16 from autotest_lib.tko import display, frontend, db, query_lib
     17 from autotest_lib.client.common_lib import kernel_versions
     18 
     19 html_header = """\
     20 <form action="/tko/compose_query.cgi" method="get">
     21 <table border="0">
     22 <tr>
     23   <td>Column: </td>
     24   <td>Row: </td>
     25   <td>Condition: </td>
     26   <td align="center">
     27   <a href="http://autotest.kernel.org/wiki/AutotestTKOCondition">Help</a>
     28   </td>
     29 </tr>
     30 <tr>
     31   <td>
     32   <SELECT NAME="columns">
     33   %s
     34  </SELECT>
     35   </td>
     36   <td>
     37   <SELECT NAME="rows">
     38   %s
     39   </SELECT>
     40   </td>
     41   <td>
     42     <input type="text" name="condition" size="30" value="%s">
     43     <input type="hidden" name="title" value="%s">
     44   </td>
     45   <td align="center"><input type="submit" value="Submit">
     46   </td>
     47 </tr>
     48 </table>
     49 </form>
     50 <form action="/tko/save_query.cgi" method="get">
     51 <table border="0">
     52 <tr>
     53  <td>Name your query:&nbsp;&nbsp;</td>
     54   <td>
     55     <input type="text" name="label" size="15" value="">
     56   </td>
     57   <td align="center">&nbsp;<input type="submit" value="Save Query">
     58   </td>
     59  <td>&nbsp;&nbsp;<a href="/tko/query_history.cgi">View saved queries</a></td>
     60   <td>
     61     <input type="hidden" name="columns" value="%s">
     62     <input type="hidden" name="rows" value="%s">
     63     <input type="hidden" name="condition" value="%s">
     64   </td>
     65 </tr>
     66 </table>
     67 </form>
     68 """
     69 
     70 
     71 next_field = {
     72     'machine_group': 'hostname',
     73     'hostname': 'tag',
     74     'tag': 'tag',
     75 
     76     'kernel': 'test',
     77     'test': 'label',
     78     'label': 'tag',
     79 
     80     'reason': 'tag',
     81     'user': 'tag',
     82     'status': 'tag',
     83    
     84     'time': 'tag',
     85     'time_daily': 'time',
     86 }
     87 
     88 
     89 def parse_field(form, form_field, field_default):
     90     if not form_field in form:
     91         return field_default
     92     field_input = form[form_field].value.lower()
     93     if field_input and field_input in frontend.test_view_field_dict:
     94         return field_input
     95     return field_default
     96 
     97 
     98 def parse_condition(form, form_field, field_default):
     99     if not form_field in form:
    100         return field_default
    101     return form[form_field].value
    102 
    103 
    104 form = cgi.FieldStorage()
    105 
    106 title_field = parse_condition(form, 'title', '')
    107 row = parse_field(form, 'rows', 'kernel')
    108 column = parse_field(form, 'columns', 'machine_group')
    109 condition_field = parse_condition(form, 'condition', '')
    110 
    111 if 'brief' in form.keys() and form['brief'].value <> '0':
    112     display.set_brief_mode()
    113 
    114 ## caller can specify rows and columns that shall be included into the report
    115 ## regardless of whether actual test data is available yet
    116 force_row_field = parse_condition(form,'force_row','')
    117 force_column_field = parse_condition(form,'force_column','')
    118 
    119 
    120 def split_forced_fields(force_field):
    121     if force_field:
    122         return force_field.split()
    123     else:
    124         return []
    125 
    126 force_row =  split_forced_fields(force_row_field)
    127 force_column =  split_forced_fields(force_column_field)
    128   
    129 cgitb.enable()
    130 db_obj = db.db()
    131 
    132 
    133 def construct_link(x, y):
    134     next_row = row
    135     next_column = column
    136     condition_list = []
    137     if condition_field != '':
    138         condition_list.append(condition_field)
    139     if y:
    140         next_row = next_field[row]
    141         condition_list.append("%s='%s'" % (row, y))
    142     if x:
    143         next_column = next_field[column]
    144         condition_list.append("%s='%s'" % (column, x))
    145     next_condition = '&'.join(condition_list)
    146     link = '/tko/compose_query.cgi?' + urllib.urlencode({'columns': next_column,
    147                'rows': next_row, 'condition': next_condition,
    148                'title': title_field})
    149     return link
    150 
    151 
    152 def construct_logs_link(x, y, job_tag):
    153     job_path = frontend.html_root + job_tag + '/'
    154     test = ''
    155     if (row == 'test' and
    156         not y.split('.')[0] in ('boot', 'build', 'install')):
    157         test = y
    158     if (column == 'test' and
    159         not x.split('.')[0] in ('boot', 'build', 'install')):
    160         test = x
    161     return '/tko/retrieve_logs.cgi?' + urllib.urlencode({'job' : job_path,
    162          'test' : test})
    163 
    164 
    165 def create_select_options(selected_val):
    166     ret = ""
    167     for option in sorted(frontend.test_view_field_dict.keys()):
    168         if selected_val == option:
    169             selected = " SELECTED"
    170         else:
    171             selected = ""
    172 
    173         ret += '<OPTION VALUE="%s"%s>%s</OPTION>\n' % \
    174                         (option, selected, option)
    175     return ret
    176 
    177 
    178 def map_kernel_base(kernel_name):
    179     ## insert <br> after each / in kernel name
    180     ## but spare consequtive //
    181     kernel_name = kernel_name.replace('/','/<br>')
    182     kernel_name = kernel_name.replace('/<br>/<br>','//')
    183     return kernel_name
    184 
    185 
    186 def header_tuneup(field_name, header):
    187         ## header tune up depends on particular field name and may include:
    188         ## - breaking header into several strings if it is long url
    189         ## - creating date from datetime stamp
    190         ## - possibly, expect more various refinements for different fields
    191         if field_name == 'kernel':
    192                 return  map_kernel_base(header)
    193         else:
    194                 return header
    195 
    196 
    197 # Kernel name mappings -- the kernels table 'printable' field is
    198 # effectively a sortable identifier for the kernel It encodes the base
    199 # release which is used for overall sorting, plus where patches are
    200 # applied it adds an increasing pNNN patch combination identifier
    201 # (actually the kernel_idx for the entry).  This allows sorting
    202 # as normal by the base kernel version and then sub-sorting by the
    203 # "first time we saw" a patch combination which should keep them in
    204 # approximatly date order.  This patch identifier is not suitable
    205 # for display, so we have to map it to a suitable html fragment for
    206 # display.  This contains the kernel base version plus the truncated
    207 # names of all the patches,
    208 #
    209 #     2.6.24-mm1 p112
    210 #     +add-new-string-functions-
    211 #     +x86-amd-thermal-interrupt
    212 # 
    213 # This mapping is produced when the first mapping is request, with
    214 # a single query over the patches table; the result is then cached.
    215 #
    216 # Note: that we only count a base version as patched if it contains
    217 # patches which are not already "expressed" in the base version.
    218 # This includes both -gitN and -mmN kernels.
    219 map_kernel_map = None
    220 
    221 
    222 def map_kernel_init():
    223     fields = ['base', 'k.kernel_idx', 'name', 'url']
    224     map = {}
    225     for (base, idx, name, url) in db_obj.select(','.join(fields),
    226             'tko_kernels k, tko_patches p', 'k.kernel_idx=p.kernel_idx'):
    227         match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$', url)
    228         if match:
    229             continue
    230 
    231         key = base + ' p%d' % (idx)
    232         if not map.has_key(key):
    233             map[key] = map_kernel_base(base) + ' p%d' % (idx)
    234         map[key] += ('<br>+<span title="' + name + '">' +
    235                  name[0:25] + '</span>')
    236 
    237     return map
    238 
    239 
    240 def map_kernel(name):
    241     global map_kernel_map
    242     if map_kernel_map is None:
    243         map_kernel_map = map_kernel_init()
    244 
    245     if map_kernel_map.has_key(name):
    246         return map_kernel_map[name]
    247 
    248     return map_kernel_base(name.split(' ')[0])
    249 
    250 
    251 field_map = {
    252     'kernel':map_kernel
    253 }
    254 
    255 sql_wall_time = 0
    256 
    257 def gen_matrix():
    258     where = None
    259     if condition_field.strip() != '':
    260         try:
    261             where = query_lib.parse_scrub_and_gen_condition(
    262                 condition_field, frontend.test_view_field_dict)
    263             print "<!-- where clause: %s -->" % (where,)
    264         except:
    265             msg = "Unspecified error when parsing condition"
    266             return [[display.box(msg)]]
    267 
    268     wall_time_start = time.time()
    269     try:
    270         ## Unfortunately, we can not request reasons of failure always
    271         ## because it may result in an inflated size of data transfer
    272         ## (at the moment we fetch 500 bytes of reason descriptions into
    273         ## each cell )
    274         ## If 'status' in [row,column] then either width or height
    275         ## of the table <=7, hence table is not really 2D, and
    276         ## query_reason is relatively save.
    277         ## At the same time view when either rows or columns grouped
    278         ## by status is when users need reasons of failures the most.
    279         
    280         ## TO DO: implement [Show/Hide reasons] button or link in
    281         ## all views and make thorough performance testing 
    282         test_data = frontend.get_matrix_data(db_obj, column, row, where,
    283                 query_reasons = ('status' in [row,column])
    284                 )
    285         global sql_wall_time
    286         sql_wall_time = time.time() - wall_time_start
    287 
    288     except db.MySQLTooManyRows, error:
    289         return [[display.box(str(error))]]            
    290     
    291     for f_row in force_row:
    292         if not f_row in test_data.y_values:
    293             test_data.y_values.append(f_row)
    294     for f_column in force_column:
    295         if not f_column in test_data.x_values:
    296             test_data.x_values.append(f_column)
    297 
    298     if not test_data.y_values:
    299         msg = "There are no results for this query (yet?)."
    300         return [[display.box(msg)]]
    301 
    302     dict_url = {'columns': row,
    303                'rows': column, 'condition': condition_field,
    304                'title': title_field}
    305     link = '/tko/compose_query.cgi?' + urllib.urlencode(dict_url)
    306     header_row = [display.box("<center>(Flip Axis)</center>", link=link)]
    307 
    308     for x in test_data.x_values:
    309         dx = x
    310         if field_map.has_key(column):
    311             dx = field_map[column](x)
    312         x_header = header_tuneup(column, dx)
    313         link = construct_link(x, None)
    314         header_row.append(display.box(x_header,header=True,link=link))
    315 
    316     matrix = [header_row]
    317     # For each row, we are looping horizontally over the columns.
    318     for y in test_data.y_values:
    319         dy = y
    320         if field_map.has_key(row):
    321             dy = field_map[row](y)
    322         y_header = header_tuneup(row, dy)
    323         link = construct_link(None, y)
    324         cur_row = [display.box(y_header, header=True, link=link)]
    325         for x in test_data.x_values:
    326             ## next 2 lines: temporary, until non timestamped
    327             ## records are in the database
    328             if x==datetime.datetime(1970,1,1): x = None
    329             if y==datetime.datetime(1970,1,1): y = None
    330             try:
    331                 box_data = test_data.data[x][y]
    332             except:
    333                 cur_row.append(display.box(None, None, 
    334                            row_label=y, column_label=x))
    335                 continue
    336             job_tag = test_data.data[x][y].job_tag
    337             if job_tag:
    338                 link = construct_logs_link(x, y, job_tag)
    339             else:
    340                 link = construct_link(x, y)
    341 
    342             apnd = display.status_precounted_box(db_obj, box_data,
    343                                  link, y, x)
    344             cur_row.append(apnd)
    345         matrix.append(cur_row)
    346     return matrix
    347 
    348 
    349 def main():
    350     if display.is_brief_mode():
    351         ## create main grid table only as provided by gen_matrix()
    352         display.print_table(gen_matrix())
    353     else:
    354         # create the actual page 
    355         print '<html><head><title>'
    356         print 'Filtered Autotest Results'
    357         print '</title></head><body>'
    358         display.print_main_header()
    359         print html_header % (create_select_options(column),
    360                      create_select_options(row),
    361                      condition_field, title_field,
    362                      ## history form
    363                      column,row,condition_field)
    364         if title_field:
    365             print '<h1> %s </h1>' % (title_field)
    366         print display.color_keys_row()
    367         display.print_table(gen_matrix())
    368         print display.color_keys_row()
    369         total_wall_time = time.time() - total_wall_time_start
    370         
    371         perf_info = '<p style="font-size:x-small;">'
    372         perf_info += 'sql access wall time = %s secs,' % sql_wall_time
    373         perf_info += 'total wall time = %s secs</p>' % total_wall_time
    374         print perf_info
    375         print '</body></html>'
    376 
    377 
    378 main()
    379