xref: /aosp_15_r20/external/autotest/frontend/tko/tko_rpc_utils.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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