| #!/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() |