1*9c5db199SXin Lifrom autotest_lib.frontend.afe import rpc_utils 2*9c5db199SXin Lifrom autotest_lib.client.common_lib import kernel_versions 3*9c5db199SXin Lifrom autotest_lib.frontend.tko import models 4*9c5db199SXin Li 5*9c5db199SXin Liclass TooManyRowsError(Exception): 6*9c5db199SXin Li """ 7*9c5db199SXin Li Raised when a database query returns too many rows. 8*9c5db199SXin Li """ 9*9c5db199SXin Li 10*9c5db199SXin Li 11*9c5db199SXin Liclass KernelString(str): 12*9c5db199SXin Li """ 13*9c5db199SXin Li Custom string class that uses correct kernel version comparisons. 14*9c5db199SXin Li """ 15*9c5db199SXin Li def _map(self): 16*9c5db199SXin Li return kernel_versions.version_encode(self) 17*9c5db199SXin Li 18*9c5db199SXin Li 19*9c5db199SXin Li def __hash__(self): 20*9c5db199SXin Li return hash(self._map()) 21*9c5db199SXin Li 22*9c5db199SXin Li 23*9c5db199SXin Li def __eq__(self, other): 24*9c5db199SXin Li return self._map() == other._map() 25*9c5db199SXin Li 26*9c5db199SXin Li 27*9c5db199SXin Li def __ne__(self, other): 28*9c5db199SXin Li return self._map() != other._map() 29*9c5db199SXin Li 30*9c5db199SXin Li 31*9c5db199SXin Li def __lt__(self, other): 32*9c5db199SXin Li return self._map() < other._map() 33*9c5db199SXin Li 34*9c5db199SXin Li 35*9c5db199SXin Li def __lte__(self, other): 36*9c5db199SXin Li return self._map() <= other._map() 37*9c5db199SXin Li 38*9c5db199SXin Li 39*9c5db199SXin Li def __gt__(self, other): 40*9c5db199SXin Li return self._map() > other._map() 41*9c5db199SXin Li 42*9c5db199SXin Li 43*9c5db199SXin Li def __gte__(self, other): 44*9c5db199SXin Li return self._map() >= other._map() 45*9c5db199SXin Li 46*9c5db199SXin Li 47*9c5db199SXin Li# SQL expression to compute passed test count for test groups 48*9c5db199SXin Li_PASS_COUNT_NAME = 'pass_count' 49*9c5db199SXin Li_COMPLETE_COUNT_NAME = 'complete_count' 50*9c5db199SXin Li_INCOMPLETE_COUNT_NAME = 'incomplete_count' 51*9c5db199SXin Li# Using COUNT instead of SUM here ensures the resulting row has the right type 52*9c5db199SXin Li# (i.e. numeric, not string). I don't know why. 53*9c5db199SXin Li_PASS_COUNT_SQL = 'COUNT(IF(status="GOOD", 1, NULL))' 54*9c5db199SXin Li_COMPLETE_COUNT_SQL = ('COUNT(IF(status NOT IN ("TEST_NA", "RUNNING", ' 55*9c5db199SXin Li '"NOSTATUS"), 1, NULL))') 56*9c5db199SXin Li_INCOMPLETE_COUNT_SQL = 'COUNT(IF(status="RUNNING", 1, NULL))' 57*9c5db199SXin LiSTATUS_FIELDS = {_PASS_COUNT_NAME : _PASS_COUNT_SQL, 58*9c5db199SXin Li _COMPLETE_COUNT_NAME : _COMPLETE_COUNT_SQL, 59*9c5db199SXin Li _INCOMPLETE_COUNT_NAME : _INCOMPLETE_COUNT_SQL} 60*9c5db199SXin Li_INVALID_STATUSES = ('TEST_NA', 'NOSTATUS') 61*9c5db199SXin Li 62*9c5db199SXin Li 63*9c5db199SXin Lidef add_status_counts(group_dict, status): 64*9c5db199SXin Li pass_count = complete_count = incomplete_count = 0 65*9c5db199SXin Li if status == 'GOOD': 66*9c5db199SXin Li pass_count = complete_count = 1 67*9c5db199SXin Li elif status == 'RUNNING': 68*9c5db199SXin Li incomplete_count = 1 69*9c5db199SXin Li else: 70*9c5db199SXin Li complete_count = 1 71*9c5db199SXin Li group_dict[_PASS_COUNT_NAME] = pass_count 72*9c5db199SXin Li group_dict[_COMPLETE_COUNT_NAME] = complete_count 73*9c5db199SXin Li group_dict[_INCOMPLETE_COUNT_NAME] = incomplete_count 74*9c5db199SXin Li group_dict[models.TestView.objects._GROUP_COUNT_NAME] = 1 75*9c5db199SXin Li 76*9c5db199SXin Li 77*9c5db199SXin Lidef _construct_machine_label_header_sql(machine_labels): 78*9c5db199SXin Li """ 79*9c5db199SXin Li Example result for machine_labels=['Index', 'Diskful']: 80*9c5db199SXin Li CONCAT_WS(",", 81*9c5db199SXin Li IF(FIND_IN_SET("Diskful", tko_test_attributes_host_labels.value), 82*9c5db199SXin Li "Diskful", NULL), 83*9c5db199SXin Li IF(FIND_IN_SET("Index", tko_test_attributes_host_labels.value), 84*9c5db199SXin Li "Index", NULL)) 85*9c5db199SXin Li 86*9c5db199SXin Li This would result in field values "Diskful,Index", "Diskful", "Index", NULL. 87*9c5db199SXin Li """ 88*9c5db199SXin Li machine_labels = sorted(machine_labels) 89*9c5db199SXin Li if_clauses = [] 90*9c5db199SXin Li for label in machine_labels: 91*9c5db199SXin Li if_clauses.append( 92*9c5db199SXin Li 'IF(FIND_IN_SET("%s", tko_test_attributes_host_labels.value), ' 93*9c5db199SXin Li '"%s", NULL)' % (label, label)) 94*9c5db199SXin Li return 'CONCAT_WS(",", %s)' % ', '.join(if_clauses) 95*9c5db199SXin Li 96*9c5db199SXin Li 97*9c5db199SXin Liclass GroupDataProcessor(object): 98*9c5db199SXin Li _MAX_GROUP_RESULTS = 80000 99*9c5db199SXin Li 100*9c5db199SXin Li def __init__(self, query, group_by, header_groups, fixed_headers): 101*9c5db199SXin Li self._query = query 102*9c5db199SXin Li self._group_by = self.uniqify(group_by) 103*9c5db199SXin Li self._header_groups = header_groups 104*9c5db199SXin Li self._fixed_headers = dict((field, set(values)) 105*9c5db199SXin Li for field, values 106*9c5db199SXin Li in fixed_headers.iteritems()) 107*9c5db199SXin Li 108*9c5db199SXin Li self._num_group_fields = len(group_by) 109*9c5db199SXin Li self._header_value_sets = [set() for i 110*9c5db199SXin Li in xrange(len(header_groups))] 111*9c5db199SXin Li self._group_dicts = [] 112*9c5db199SXin Li 113*9c5db199SXin Li 114*9c5db199SXin Li @staticmethod 115*9c5db199SXin Li def uniqify(values): 116*9c5db199SXin Li return list(set(values)) 117*9c5db199SXin Li 118*9c5db199SXin Li 119*9c5db199SXin Li def _restrict_header_values(self): 120*9c5db199SXin Li for header_field, values in self._fixed_headers.iteritems(): 121*9c5db199SXin Li self._query = self._query.filter(**{header_field + '__in' : values}) 122*9c5db199SXin Li 123*9c5db199SXin Li 124*9c5db199SXin Li def _fetch_data(self): 125*9c5db199SXin Li self._restrict_header_values() 126*9c5db199SXin Li self._group_dicts = models.TestView.objects.execute_group_query( 127*9c5db199SXin Li self._query, self._group_by) 128*9c5db199SXin Li 129*9c5db199SXin Li 130*9c5db199SXin Li @staticmethod 131*9c5db199SXin Li def _get_field(group_dict, field): 132*9c5db199SXin Li """ 133*9c5db199SXin Li Use special objects for certain fields to achieve custom sorting. 134*9c5db199SXin Li -Wrap kernel versions with a KernelString 135*9c5db199SXin Li -Replace null dates with special values 136*9c5db199SXin Li """ 137*9c5db199SXin Li value = group_dict[field] 138*9c5db199SXin Li if field == 'kernel': 139*9c5db199SXin Li return KernelString(value) 140*9c5db199SXin Li if value is None: # handle null dates as later than everything else 141*9c5db199SXin Li if field.startswith('DATE('): 142*9c5db199SXin Li return rpc_utils.NULL_DATE 143*9c5db199SXin Li if field.endswith('_time'): 144*9c5db199SXin Li return rpc_utils.NULL_DATETIME 145*9c5db199SXin Li return value 146*9c5db199SXin Li 147*9c5db199SXin Li 148*9c5db199SXin Li def _process_group_dict(self, group_dict): 149*9c5db199SXin Li # compute and aggregate header groups 150*9c5db199SXin Li for i, group in enumerate(self._header_groups): 151*9c5db199SXin Li header = tuple(self._get_field(group_dict, field) 152*9c5db199SXin Li for field in group) 153*9c5db199SXin Li self._header_value_sets[i].add(header) 154*9c5db199SXin Li group_dict.setdefault('header_values', []).append(header) 155*9c5db199SXin Li 156*9c5db199SXin Li # frontend's SelectionManager needs a unique ID 157*9c5db199SXin Li group_values = [group_dict[field] for field in self._group_by] 158*9c5db199SXin Li group_dict['id'] = str(group_values) 159*9c5db199SXin Li return group_dict 160*9c5db199SXin Li 161*9c5db199SXin Li 162*9c5db199SXin Li def _find_header_value_set(self, field): 163*9c5db199SXin Li for i, group in enumerate(self._header_groups): 164*9c5db199SXin Li if [field] == group: 165*9c5db199SXin Li return self._header_value_sets[i] 166*9c5db199SXin Li raise RuntimeError('Field %s not found in header groups %s' % 167*9c5db199SXin Li (field, self._header_groups)) 168*9c5db199SXin Li 169*9c5db199SXin Li 170*9c5db199SXin Li def _add_fixed_headers(self): 171*9c5db199SXin Li for field, extra_values in self._fixed_headers.iteritems(): 172*9c5db199SXin Li header_value_set = self._find_header_value_set(field) 173*9c5db199SXin Li for value in extra_values: 174*9c5db199SXin Li header_value_set.add((value,)) 175*9c5db199SXin Li 176*9c5db199SXin Li 177*9c5db199SXin Li def _get_sorted_header_values(self): 178*9c5db199SXin Li self._add_fixed_headers() 179*9c5db199SXin Li sorted_header_values = [sorted(value_set) 180*9c5db199SXin Li for value_set in self._header_value_sets] 181*9c5db199SXin Li # construct dicts mapping headers to their indices, for use in 182*9c5db199SXin Li # replace_headers_with_indices() 183*9c5db199SXin Li self._header_index_maps = [] 184*9c5db199SXin Li for value_list in sorted_header_values: 185*9c5db199SXin Li index_map = dict((value, i) for i, value in enumerate(value_list)) 186*9c5db199SXin Li self._header_index_maps.append(index_map) 187*9c5db199SXin Li 188*9c5db199SXin Li return sorted_header_values 189*9c5db199SXin Li 190*9c5db199SXin Li 191*9c5db199SXin Li def _replace_headers_with_indices(self, group_dict): 192*9c5db199SXin Li group_dict['header_indices'] = [index_map[header_value] 193*9c5db199SXin Li for index_map, header_value 194*9c5db199SXin Li in zip(self._header_index_maps, 195*9c5db199SXin Li group_dict['header_values'])] 196*9c5db199SXin Li for field in self._group_by + ['header_values']: 197*9c5db199SXin Li del group_dict[field] 198*9c5db199SXin Li 199*9c5db199SXin Li 200*9c5db199SXin Li def process_group_dicts(self): 201*9c5db199SXin Li self._fetch_data() 202*9c5db199SXin Li if len(self._group_dicts) > self._MAX_GROUP_RESULTS: 203*9c5db199SXin Li raise TooManyRowsError( 204*9c5db199SXin Li 'Query yielded %d rows, exceeding maximum %d' % ( 205*9c5db199SXin Li len(self._group_dicts), self._MAX_GROUP_RESULTS)) 206*9c5db199SXin Li 207*9c5db199SXin Li for group_dict in self._group_dicts: 208*9c5db199SXin Li self._process_group_dict(group_dict) 209*9c5db199SXin Li self._header_values = self._get_sorted_header_values() 210*9c5db199SXin Li if self._header_groups: 211*9c5db199SXin Li for group_dict in self._group_dicts: 212*9c5db199SXin Li self._replace_headers_with_indices(group_dict) 213*9c5db199SXin Li 214*9c5db199SXin Li 215*9c5db199SXin Li def get_info_dict(self): 216*9c5db199SXin Li return {'groups' : self._group_dicts, 217*9c5db199SXin Li 'header_values' : self._header_values} 218