379 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			379 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
| #!/usr/bin/python2
 | |
| 
 | |
| """
 | |
| Selects all rows and columns that satisfy the condition specified
 | |
| and draws the matrix. There is a seperate SQL query made for every (x,y)
 | |
| in the matrix.
 | |
| """
 | |
| 
 | |
| print "Content-type: text/html\n"
 | |
| 
 | |
| import sys, os, urllib, cgi, cgitb, re, datetime, time
 | |
| 
 | |
| total_wall_time_start = time.time()
 | |
| 
 | |
| import common
 | |
| from autotest_lib.tko import display, frontend, db, query_lib
 | |
| from autotest_lib.client.common_lib import kernel_versions
 | |
| 
 | |
| html_header = """\
 | |
| <form action="/tko/compose_query.cgi" method="get">
 | |
| <table border="0">
 | |
| <tr>
 | |
|   <td>Column: </td>
 | |
|   <td>Row: </td>
 | |
|   <td>Condition: </td>
 | |
|   <td align="center">
 | |
|   <a href="http://autotest.kernel.org/wiki/AutotestTKOCondition">Help</a>
 | |
|   </td>
 | |
| </tr>
 | |
| <tr>
 | |
|   <td>
 | |
|   <SELECT NAME="columns">
 | |
|   %s
 | |
|  </SELECT>
 | |
|   </td>
 | |
|   <td>
 | |
|   <SELECT NAME="rows">
 | |
|   %s
 | |
|   </SELECT>
 | |
|   </td>
 | |
|   <td>
 | |
|     <input type="text" name="condition" size="30" value="%s">
 | |
|     <input type="hidden" name="title" value="%s">
 | |
|   </td>
 | |
|   <td align="center"><input type="submit" value="Submit">
 | |
|   </td>
 | |
| </tr>
 | |
| </table>
 | |
| </form>
 | |
| <form action="/tko/save_query.cgi" method="get">
 | |
| <table border="0">
 | |
| <tr>
 | |
|  <td>Name your query:  </td>
 | |
|   <td>
 | |
|     <input type="text" name="label" size="15" value="">
 | |
|   </td>
 | |
|   <td align="center"> <input type="submit" value="Save Query">
 | |
|   </td>
 | |
|  <td>  <a href="/tko/query_history.cgi">View saved queries</a></td>
 | |
|   <td>
 | |
|     <input type="hidden" name="columns" value="%s">
 | |
|     <input type="hidden" name="rows" value="%s">
 | |
|     <input type="hidden" name="condition" value="%s">
 | |
|   </td>
 | |
| </tr>
 | |
| </table>
 | |
| </form>
 | |
| """
 | |
| 
 | |
| 
 | |
| next_field = {
 | |
|     'machine_group': 'hostname',
 | |
|     'hostname': 'tag',
 | |
|     'tag': 'tag',
 | |
| 
 | |
|     'kernel': 'test',
 | |
|     'test': 'label',
 | |
|     'label': 'tag',
 | |
| 
 | |
|     'reason': 'tag',
 | |
|     'user': 'tag',
 | |
|     'status': 'tag',
 | |
|    
 | |
|     'time': 'tag',
 | |
|     'time_daily': 'time',
 | |
| }
 | |
| 
 | |
| 
 | |
| def parse_field(form, form_field, field_default):
 | |
|     if not form_field in form:
 | |
|         return field_default
 | |
|     field_input = form[form_field].value.lower()
 | |
|     if field_input and field_input in frontend.test_view_field_dict:
 | |
|         return field_input
 | |
|     return field_default
 | |
| 
 | |
| 
 | |
| def parse_condition(form, form_field, field_default):
 | |
|     if not form_field in form:
 | |
|         return field_default
 | |
|     return form[form_field].value
 | |
| 
 | |
| 
 | |
| form = cgi.FieldStorage()
 | |
| 
 | |
| title_field = parse_condition(form, 'title', '')
 | |
| row = parse_field(form, 'rows', 'kernel')
 | |
| column = parse_field(form, 'columns', 'machine_group')
 | |
| condition_field = parse_condition(form, 'condition', '')
 | |
| 
 | |
| if 'brief' in form.keys() and form['brief'].value <> '0':
 | |
|     display.set_brief_mode()
 | |
| 
 | |
| ## caller can specify rows and columns that shall be included into the report
 | |
| ## regardless of whether actual test data is available yet
 | |
| force_row_field = parse_condition(form,'force_row','')
 | |
| force_column_field = parse_condition(form,'force_column','')
 | |
| 
 | |
| 
 | |
| def split_forced_fields(force_field):
 | |
|     if force_field:
 | |
|         return force_field.split()
 | |
|     else:
 | |
|         return []
 | |
| 
 | |
| force_row =  split_forced_fields(force_row_field)
 | |
| force_column =  split_forced_fields(force_column_field)
 | |
|   
 | |
| cgitb.enable()
 | |
| db_obj = db.db()
 | |
| 
 | |
| 
 | |
| def construct_link(x, y):
 | |
|     next_row = row
 | |
|     next_column = column
 | |
|     condition_list = []
 | |
|     if condition_field != '':
 | |
|         condition_list.append(condition_field)
 | |
|     if y:
 | |
|         next_row = next_field[row]
 | |
|         condition_list.append("%s='%s'" % (row, y))
 | |
|     if x:
 | |
|         next_column = next_field[column]
 | |
|         condition_list.append("%s='%s'" % (column, x))
 | |
|     next_condition = '&'.join(condition_list)
 | |
|     link = '/tko/compose_query.cgi?' + urllib.urlencode({'columns': next_column,
 | |
|                'rows': next_row, 'condition': next_condition,
 | |
|                'title': title_field})
 | |
|     return link
 | |
| 
 | |
| 
 | |
| def construct_logs_link(x, y, job_tag):
 | |
|     job_path = frontend.html_root + job_tag + '/'
 | |
|     test = ''
 | |
|     if (row == 'test' and
 | |
|         not y.split('.')[0] in ('boot', 'build', 'install')):
 | |
|         test = y
 | |
|     if (column == 'test' and
 | |
|         not x.split('.')[0] in ('boot', 'build', 'install')):
 | |
|         test = x
 | |
|     return '/tko/retrieve_logs.cgi?' + urllib.urlencode({'job' : job_path,
 | |
|          'test' : test})
 | |
| 
 | |
| 
 | |
| def create_select_options(selected_val):
 | |
|     ret = ""
 | |
|     for option in sorted(frontend.test_view_field_dict.keys()):
 | |
|         if selected_val == option:
 | |
|             selected = " SELECTED"
 | |
|         else:
 | |
|             selected = ""
 | |
| 
 | |
|         ret += '<OPTION VALUE="%s"%s>%s</OPTION>\n' % \
 | |
|                         (option, selected, option)
 | |
|     return ret
 | |
| 
 | |
| 
 | |
| def map_kernel_base(kernel_name):
 | |
|     ## insert <br> after each / in kernel name
 | |
|     ## but spare consequtive //
 | |
|     kernel_name = kernel_name.replace('/','/<br>')
 | |
|     kernel_name = kernel_name.replace('/<br>/<br>','//')
 | |
|     return kernel_name
 | |
| 
 | |
| 
 | |
| def header_tuneup(field_name, header):
 | |
|         ## header tune up depends on particular field name and may include:
 | |
|         ## - breaking header into several strings if it is long url
 | |
|         ## - creating date from datetime stamp
 | |
|         ## - possibly, expect more various refinements for different fields
 | |
|         if field_name == 'kernel':
 | |
|                 return  map_kernel_base(header)
 | |
|         else:
 | |
|                 return header
 | |
| 
 | |
| 
 | |
| # Kernel name mappings -- the kernels table 'printable' field is
 | |
| # effectively a sortable identifier for the kernel It encodes the base
 | |
| # release which is used for overall sorting, plus where patches are
 | |
| # applied it adds an increasing pNNN patch combination identifier
 | |
| # (actually the kernel_idx for the entry).  This allows sorting
 | |
| # as normal by the base kernel version and then sub-sorting by the
 | |
| # "first time we saw" a patch combination which should keep them in
 | |
| # approximatly date order.  This patch identifier is not suitable
 | |
| # for display, so we have to map it to a suitable html fragment for
 | |
| # display.  This contains the kernel base version plus the truncated
 | |
| # names of all the patches,
 | |
| #
 | |
| #     2.6.24-mm1 p112
 | |
| #     +add-new-string-functions-
 | |
| #     +x86-amd-thermal-interrupt
 | |
| # 
 | |
| # This mapping is produced when the first mapping is request, with
 | |
| # a single query over the patches table; the result is then cached.
 | |
| #
 | |
| # Note: that we only count a base version as patched if it contains
 | |
| # patches which are not already "expressed" in the base version.
 | |
| # This includes both -gitN and -mmN kernels.
 | |
| map_kernel_map = None
 | |
| 
 | |
| 
 | |
| def map_kernel_init():
 | |
|     fields = ['base', 'k.kernel_idx', 'name', 'url']
 | |
|     map = {}
 | |
|     for (base, idx, name, url) in db_obj.select(','.join(fields),
 | |
|             'tko_kernels k, tko_patches p', 'k.kernel_idx=p.kernel_idx'):
 | |
|         match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$', url)
 | |
|         if match:
 | |
|             continue
 | |
| 
 | |
|         key = base + ' p%d' % (idx)
 | |
|         if not map.has_key(key):
 | |
|             map[key] = map_kernel_base(base) + ' p%d' % (idx)
 | |
|         map[key] += ('<br>+<span title="' + name + '">' +
 | |
|                  name[0:25] + '</span>')
 | |
| 
 | |
|     return map
 | |
| 
 | |
| 
 | |
| def map_kernel(name):
 | |
|     global map_kernel_map
 | |
|     if map_kernel_map is None:
 | |
|         map_kernel_map = map_kernel_init()
 | |
| 
 | |
|     if map_kernel_map.has_key(name):
 | |
|         return map_kernel_map[name]
 | |
| 
 | |
|     return map_kernel_base(name.split(' ')[0])
 | |
| 
 | |
| 
 | |
| field_map = {
 | |
|     'kernel':map_kernel
 | |
| }
 | |
| 
 | |
| sql_wall_time = 0
 | |
| 
 | |
| def gen_matrix():
 | |
|     where = None
 | |
|     if condition_field.strip() != '':
 | |
|         try:
 | |
|             where = query_lib.parse_scrub_and_gen_condition(
 | |
|                 condition_field, frontend.test_view_field_dict)
 | |
|             print "<!-- where clause: %s -->" % (where,)
 | |
|         except:
 | |
|             msg = "Unspecified error when parsing condition"
 | |
|             return [[display.box(msg)]]
 | |
| 
 | |
|     wall_time_start = time.time()
 | |
|     try:
 | |
|         ## Unfortunately, we can not request reasons of failure always
 | |
|         ## because it may result in an inflated size of data transfer
 | |
|         ## (at the moment we fetch 500 bytes of reason descriptions into
 | |
|         ## each cell )
 | |
|         ## If 'status' in [row,column] then either width or height
 | |
|         ## of the table <=7, hence table is not really 2D, and
 | |
|         ## query_reason is relatively save.
 | |
|         ## At the same time view when either rows or columns grouped
 | |
|         ## by status is when users need reasons of failures the most.
 | |
|         
 | |
|         ## TO DO: implement [Show/Hide reasons] button or link in
 | |
|         ## all views and make thorough performance testing 
 | |
|         test_data = frontend.get_matrix_data(db_obj, column, row, where,
 | |
|                 query_reasons = ('status' in [row,column])
 | |
|                 )
 | |
|         global sql_wall_time
 | |
|         sql_wall_time = time.time() - wall_time_start
 | |
| 
 | |
|     except db.MySQLTooManyRows, error:
 | |
|         return [[display.box(str(error))]]            
 | |
|     
 | |
|     for f_row in force_row:
 | |
|         if not f_row in test_data.y_values:
 | |
|             test_data.y_values.append(f_row)
 | |
|     for f_column in force_column:
 | |
|         if not f_column in test_data.x_values:
 | |
|             test_data.x_values.append(f_column)
 | |
| 
 | |
|     if not test_data.y_values:
 | |
|         msg = "There are no results for this query (yet?)."
 | |
|         return [[display.box(msg)]]
 | |
| 
 | |
|     dict_url = {'columns': row,
 | |
|                'rows': column, 'condition': condition_field,
 | |
|                'title': title_field}
 | |
|     link = '/tko/compose_query.cgi?' + urllib.urlencode(dict_url)
 | |
|     header_row = [display.box("<center>(Flip Axis)</center>", link=link)]
 | |
| 
 | |
|     for x in test_data.x_values:
 | |
|         dx = x
 | |
|         if field_map.has_key(column):
 | |
|             dx = field_map[column](x)
 | |
|         x_header = header_tuneup(column, dx)
 | |
|         link = construct_link(x, None)
 | |
|         header_row.append(display.box(x_header,header=True,link=link))
 | |
| 
 | |
|     matrix = [header_row]
 | |
|     # For each row, we are looping horizontally over the columns.
 | |
|     for y in test_data.y_values:
 | |
|         dy = y
 | |
|         if field_map.has_key(row):
 | |
|             dy = field_map[row](y)
 | |
|         y_header = header_tuneup(row, dy)
 | |
|         link = construct_link(None, y)
 | |
|         cur_row = [display.box(y_header, header=True, link=link)]
 | |
|         for x in test_data.x_values:
 | |
|             ## next 2 lines: temporary, until non timestamped
 | |
|             ## records are in the database
 | |
|             if x==datetime.datetime(1970,1,1): x = None
 | |
|             if y==datetime.datetime(1970,1,1): y = None
 | |
|             try:
 | |
|                 box_data = test_data.data[x][y]
 | |
|             except:
 | |
|                 cur_row.append(display.box(None, None, 
 | |
|                            row_label=y, column_label=x))
 | |
|                 continue
 | |
|             job_tag = test_data.data[x][y].job_tag
 | |
|             if job_tag:
 | |
|                 link = construct_logs_link(x, y, job_tag)
 | |
|             else:
 | |
|                 link = construct_link(x, y)
 | |
| 
 | |
|             apnd = display.status_precounted_box(db_obj, box_data,
 | |
|                                  link, y, x)
 | |
|             cur_row.append(apnd)
 | |
|         matrix.append(cur_row)
 | |
|     return matrix
 | |
| 
 | |
| 
 | |
| def main():
 | |
|     if display.is_brief_mode():
 | |
|         ## create main grid table only as provided by gen_matrix()
 | |
|         display.print_table(gen_matrix())
 | |
|     else:
 | |
|         # create the actual page 
 | |
|         print '<html><head><title>'
 | |
|         print 'Filtered Autotest Results'
 | |
|         print '</title></head><body>'
 | |
|         display.print_main_header()
 | |
|         print html_header % (create_select_options(column),
 | |
|                      create_select_options(row),
 | |
|                      condition_field, title_field,
 | |
|                      ## history form
 | |
|                      column,row,condition_field)
 | |
|         if title_field:
 | |
|             print '<h1> %s </h1>' % (title_field)
 | |
|         print display.color_keys_row()
 | |
|         display.print_table(gen_matrix())
 | |
|         print display.color_keys_row()
 | |
|         total_wall_time = time.time() - total_wall_time_start
 | |
|         
 | |
|         perf_info = '<p style="font-size:x-small;">'
 | |
|         perf_info += 'sql access wall time = %s secs,' % sql_wall_time
 | |
|         perf_info += 'total wall time = %s secs</p>' % total_wall_time
 | |
|         print perf_info
 | |
|         print '</body></html>'
 | |
| 
 | |
| 
 | |
| main()
 |