xref: /aosp_15_r20/external/autotest/autotest_lib/tko/frontend.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Lifrom __future__ import absolute_import
3*9c5db199SXin Lifrom __future__ import division
4*9c5db199SXin Lifrom __future__ import print_function
5*9c5db199SXin Liimport os, re, db, sys, datetime
6*9c5db199SXin Liimport common
7*9c5db199SXin Lifrom autotest_lib.client.common_lib import kernel_versions
8*9c5db199SXin Lifrom six.moves import map
9*9c5db199SXin Li
10*9c5db199SXin LiMAX_RECORDS = 50000
11*9c5db199SXin LiMAX_CELLS = 500000
12*9c5db199SXin Li
13*9c5db199SXin Litko = os.path.dirname(os.path.realpath(os.path.abspath(__file__)))
14*9c5db199SXin Liroot_url_file = os.path.join(tko, '.root_url')
15*9c5db199SXin Liif os.path.exists(root_url_file):
16*9c5db199SXin Li    html_root = open(root_url_file, 'r').readline().rstrip()
17*9c5db199SXin Lielse:
18*9c5db199SXin Li    html_root = '/results/'
19*9c5db199SXin Li
20*9c5db199SXin Li
21*9c5db199SXin Liclass status_cell:
22*9c5db199SXin Li    # One cell in the matrix of status data.
23*9c5db199SXin Li    def __init__(self):
24*9c5db199SXin Li        # Count is a dictionary: status -> count of tests with status
25*9c5db199SXin Li        self.status_count = {}
26*9c5db199SXin Li        self.reasons_list = []
27*9c5db199SXin Li        self.job_tag = None
28*9c5db199SXin Li        self.job_tag_count = 0
29*9c5db199SXin Li
30*9c5db199SXin Li
31*9c5db199SXin Li    def add(self, status, count, job_tags, reasons = None):
32*9c5db199SXin Li        assert count > 0
33*9c5db199SXin Li
34*9c5db199SXin Li        self.job_tag = job_tags
35*9c5db199SXin Li        self.job_tag_count += count
36*9c5db199SXin Li        if self.job_tag_count > 1:
37*9c5db199SXin Li            self.job_tag = None
38*9c5db199SXin Li
39*9c5db199SXin Li        self.status_count[status] = count
40*9c5db199SXin Li        ### status == 6 means 'GOOD'
41*9c5db199SXin Li        if status != 6:
42*9c5db199SXin Li            ## None implies sorting problems and extra CRs in a cell
43*9c5db199SXin Li            if reasons:
44*9c5db199SXin Li                self.reasons_list.append(reasons)
45*9c5db199SXin Li
46*9c5db199SXin Li
47*9c5db199SXin Liclass status_data:
48*9c5db199SXin Li    def __init__(self, sql_rows, x_field, y_field, query_reasons = False):
49*9c5db199SXin Li        data = {}
50*9c5db199SXin Li        y_values = set()
51*9c5db199SXin Li
52*9c5db199SXin Li        # Walk through the query, filing all results by x, y info
53*9c5db199SXin Li        for row in sql_rows:
54*9c5db199SXin Li            if query_reasons:
55*9c5db199SXin Li                (x,y, status, count, job_tags, reasons) = row
56*9c5db199SXin Li            else:
57*9c5db199SXin Li                (x,y, status, count, job_tags) = row
58*9c5db199SXin Li                reasons = None
59*9c5db199SXin Li            if x not in data:
60*9c5db199SXin Li                data[x] = {}
61*9c5db199SXin Li            if y not in data[x]:
62*9c5db199SXin Li                y_values.add(y)
63*9c5db199SXin Li                data[x][y] = status_cell()
64*9c5db199SXin Li            data[x][y].add(status, count, job_tags, reasons)
65*9c5db199SXin Li
66*9c5db199SXin Li        # 2-d hash of data - [x-value][y-value]
67*9c5db199SXin Li        self.data = data
68*9c5db199SXin Li        # List of possible columns (x-values)
69*9c5db199SXin Li        self.x_values = smart_sort(list(data.keys()), x_field)
70*9c5db199SXin Li        # List of rows columns (y-values)
71*9c5db199SXin Li        self.y_values = smart_sort(list(y_values), y_field)
72*9c5db199SXin Li        nCells = len(self.y_values)*len(self.x_values)
73*9c5db199SXin Li        if nCells > MAX_CELLS:
74*9c5db199SXin Li            msg = 'Exceeded allowed number of cells in a table'
75*9c5db199SXin Li            raise db.MySQLTooManyRows(msg)
76*9c5db199SXin Li
77*9c5db199SXin Li
78*9c5db199SXin Lidef get_matrix_data(db_obj, x_axis, y_axis, where = None,
79*9c5db199SXin Li                    query_reasons = False):
80*9c5db199SXin Li    # Searches on the test_view table - x_axis and y_axis must both be
81*9c5db199SXin Li    # column names in that table.
82*9c5db199SXin Li    x_field = test_view_field_dict[x_axis]
83*9c5db199SXin Li    y_field = test_view_field_dict[y_axis]
84*9c5db199SXin Li    query_fields_list = [x_field, y_field, 'status','COUNT(status)']
85*9c5db199SXin Li    query_fields_list.append("LEFT(GROUP_CONCAT(job_tag),100)")
86*9c5db199SXin Li    if query_reasons:
87*9c5db199SXin Li        query_fields_list.append(
88*9c5db199SXin Li                "LEFT(GROUP_CONCAT(DISTINCT reason SEPARATOR '|'),500)"
89*9c5db199SXin Li                )
90*9c5db199SXin Li    fields = ','.join(query_fields_list)
91*9c5db199SXin Li
92*9c5db199SXin Li    group_by = '%s, %s, status' % (x_field, y_field)
93*9c5db199SXin Li    rows = db_obj.select(fields, 'tko_test_view',
94*9c5db199SXin Li                    where=where, group_by=group_by, max_rows = MAX_RECORDS)
95*9c5db199SXin Li    return status_data(rows, x_field, y_field, query_reasons)
96*9c5db199SXin Li
97*9c5db199SXin Li
98*9c5db199SXin Li# Dictionary used simply for fast lookups from short reference names for users
99*9c5db199SXin Li# to fieldnames in test_view
100*9c5db199SXin Litest_view_field_dict = {
101*9c5db199SXin Li        'kernel'        : 'kernel_printable',
102*9c5db199SXin Li        'hostname'      : 'machine_hostname',
103*9c5db199SXin Li        'test'          : 'test',
104*9c5db199SXin Li        'label'         : 'job_label',
105*9c5db199SXin Li        'machine_group' : 'machine_group',
106*9c5db199SXin Li        'reason'        : 'reason',
107*9c5db199SXin Li        'tag'           : 'job_tag',
108*9c5db199SXin Li        'user'          : 'job_username',
109*9c5db199SXin Li        'status'        : 'status_word',
110*9c5db199SXin Li        'time'          : 'test_finished_time',
111*9c5db199SXin Li        'start_time'    : 'test_started_time',
112*9c5db199SXin Li        'time_daily'    : 'DATE(test_finished_time)'
113*9c5db199SXin Li}
114*9c5db199SXin Li
115*9c5db199SXin Li
116*9c5db199SXin Lidef smart_sort(list, field):
117*9c5db199SXin Li    if field == 'kernel_printable':
118*9c5db199SXin Li        def kernel_encode(kernel):
119*9c5db199SXin Li            return kernel_versions.version_encode(kernel)
120*9c5db199SXin Li        list.sort(key = kernel_encode, reverse = True)
121*9c5db199SXin Li        return list
122*9c5db199SXin Li    ## old records may contain time=None
123*9c5db199SXin Li    ## make None comparable with timestamp datetime or date
124*9c5db199SXin Li    elif field == 'test_finished_time':
125*9c5db199SXin Li        def convert_None_to_datetime(date_time):
126*9c5db199SXin Li            if not date_time:
127*9c5db199SXin Li                return datetime.datetime(1970, 1, 1, 0, 0, 0)
128*9c5db199SXin Li            else:
129*9c5db199SXin Li                return date_time
130*9c5db199SXin Li        list = list(map(convert_None_to_datetime, list))
131*9c5db199SXin Li    elif field == 'DATE(test_finished_time)':
132*9c5db199SXin Li        def convert_None_to_date(date):
133*9c5db199SXin Li            if not date:
134*9c5db199SXin Li                return datetime.date(1970, 1, 1)
135*9c5db199SXin Li            else:
136*9c5db199SXin Li                return date
137*9c5db199SXin Li        list = list(map(convert_None_to_date, list))
138*9c5db199SXin Li    list.sort()
139*9c5db199SXin Li    return list
140*9c5db199SXin Li
141*9c5db199SXin Li
142*9c5db199SXin Liclass group:
143*9c5db199SXin Li    @classmethod
144*9c5db199SXin Li    def select(klass, db):
145*9c5db199SXin Li        """Return all possible machine groups"""
146*9c5db199SXin Li        rows = db.select('distinct machine_group', 'tko_machines',
147*9c5db199SXin Li                                        'machine_group is not null')
148*9c5db199SXin Li        groupnames = sorted([row[0] for row in rows])
149*9c5db199SXin Li        return [klass(db, groupname) for groupname in groupnames]
150*9c5db199SXin Li
151*9c5db199SXin Li
152*9c5db199SXin Li    def __init__(self, db, name):
153*9c5db199SXin Li        self.name = name
154*9c5db199SXin Li        self.db = db
155*9c5db199SXin Li
156*9c5db199SXin Li
157*9c5db199SXin Li    def machines(self):
158*9c5db199SXin Li        return machine.select(self.db, { 'machine_group' : self.name })
159*9c5db199SXin Li
160*9c5db199SXin Li
161*9c5db199SXin Li    def tests(self, where = {}):
162*9c5db199SXin Li        values = [self.name]
163*9c5db199SXin Li        sql = 't inner join tko_machines m on m.machine_idx=t.machine_idx'
164*9c5db199SXin Li        sql += ' where m.machine_group=%s'
165*9c5db199SXin Li        for key in where.keys():
166*9c5db199SXin Li            sql += ' and %s=%%s' % key
167*9c5db199SXin Li            values.append(where[key])
168*9c5db199SXin Li        return test.select_sql(self.db, sql, values)
169*9c5db199SXin Li
170*9c5db199SXin Li
171*9c5db199SXin Liclass machine:
172*9c5db199SXin Li    @classmethod
173*9c5db199SXin Li    def select(klass, db, where = {}):
174*9c5db199SXin Li        fields = ['machine_idx', 'hostname', 'machine_group', 'owner']
175*9c5db199SXin Li        machines = []
176*9c5db199SXin Li        for row in db.select(','.join(fields), 'tko_machines', where):
177*9c5db199SXin Li            machines.append(klass(db, *row))
178*9c5db199SXin Li        return machines
179*9c5db199SXin Li
180*9c5db199SXin Li
181*9c5db199SXin Li    def __init__(self, db, idx, hostname, group, owner):
182*9c5db199SXin Li        self.db = db
183*9c5db199SXin Li        self.idx = idx
184*9c5db199SXin Li        self.hostname = hostname
185*9c5db199SXin Li        self.group = group
186*9c5db199SXin Li        self.owner = owner
187*9c5db199SXin Li
188*9c5db199SXin Li
189*9c5db199SXin Liclass kernel:
190*9c5db199SXin Li    @classmethod
191*9c5db199SXin Li    def select(klass, db, where = {}):
192*9c5db199SXin Li        fields = ['kernel_idx', 'kernel_hash', 'base', 'printable']
193*9c5db199SXin Li        rows = db.select(','.join(fields), 'tko_kernels', where)
194*9c5db199SXin Li        return [klass(db, *row) for row in rows]
195*9c5db199SXin Li
196*9c5db199SXin Li
197*9c5db199SXin Li    def __init__(self, db, idx, hash, base, printable):
198*9c5db199SXin Li        self.db = db
199*9c5db199SXin Li        self.idx = idx
200*9c5db199SXin Li        self.hash = hash
201*9c5db199SXin Li        self.base = base
202*9c5db199SXin Li        self.printable = printable
203*9c5db199SXin Li        self.patches = []    # THIS SHOULD PULL IN PATCHES!
204*9c5db199SXin Li
205*9c5db199SXin Li
206*9c5db199SXin Liclass test:
207*9c5db199SXin Li    @classmethod
208*9c5db199SXin Li    def select(klass, db, where={}, distinct=False):
209*9c5db199SXin Li        fields = ['test_idx', 'job_idx', 'test', 'subdir',
210*9c5db199SXin Li                  'kernel_idx', 'status', 'reason', 'machine_idx']
211*9c5db199SXin Li        tests = []
212*9c5db199SXin Li        for row in db.select(','.join(fields), 'tko_tests', where,
213*9c5db199SXin Li                             distinct):
214*9c5db199SXin Li            tests.append(klass(db, *row))
215*9c5db199SXin Li        return tests
216*9c5db199SXin Li
217*9c5db199SXin Li
218*9c5db199SXin Li    @classmethod
219*9c5db199SXin Li    def select_sql(klass, db, sql, values):
220*9c5db199SXin Li        fields = ['test_idx', 'job_idx', 'test', 'subdir',
221*9c5db199SXin Li                  'kernel_idx', 'status', 'reason', 'machine_idx']
222*9c5db199SXin Li        fields = ['t.'+field for field in fields]
223*9c5db199SXin Li        rows = db.select_sql(','.join(fields), 'tko_tests', sql, values)
224*9c5db199SXin Li        return [klass(db, *row) for row in rows]
225*9c5db199SXin Li
226*9c5db199SXin Li
227*9c5db199SXin Li    def __init__(self, db, test_idx, job_idx, testname, subdir, kernel_idx,
228*9c5db199SXin Li                 status_num, reason, machine_idx):
229*9c5db199SXin Li        self.idx = test_idx
230*9c5db199SXin Li        self.job = job(db, job_idx)
231*9c5db199SXin Li        self.testname = testname
232*9c5db199SXin Li        self.subdir = subdir
233*9c5db199SXin Li        self.kernel_idx = kernel_idx
234*9c5db199SXin Li        self.__kernel = None
235*9c5db199SXin Li        self.__iterations = None
236*9c5db199SXin Li        self.machine_idx = machine_idx
237*9c5db199SXin Li        self.__machine = None
238*9c5db199SXin Li        self.status_num = status_num
239*9c5db199SXin Li        self.status_word = db.status_word[status_num]
240*9c5db199SXin Li        self.reason = reason
241*9c5db199SXin Li        self.db = db
242*9c5db199SXin Li        if self.subdir:
243*9c5db199SXin Li            self.url = html_root + self.job.tag + '/' + self.subdir
244*9c5db199SXin Li        else:
245*9c5db199SXin Li            self.url = None
246*9c5db199SXin Li
247*9c5db199SXin Li
248*9c5db199SXin Li    def iterations(self):
249*9c5db199SXin Li        """
250*9c5db199SXin Li        Caching function for iterations
251*9c5db199SXin Li        """
252*9c5db199SXin Li        if not self.__iterations:
253*9c5db199SXin Li            self.__iterations = {}
254*9c5db199SXin Li            # A dictionary - dict{key} = [value1, value2, ....]
255*9c5db199SXin Li            where = {'test_idx' : self.idx}
256*9c5db199SXin Li            for i in iteration.select(self.db, where):
257*9c5db199SXin Li                if i.key in self.__iterations:
258*9c5db199SXin Li                    self.__iterations[i.key].append(i.value)
259*9c5db199SXin Li                else:
260*9c5db199SXin Li                    self.__iterations[i.key] = [i.value]
261*9c5db199SXin Li        return self.__iterations
262*9c5db199SXin Li
263*9c5db199SXin Li
264*9c5db199SXin Li    def kernel(self):
265*9c5db199SXin Li        """
266*9c5db199SXin Li        Caching function for kernels
267*9c5db199SXin Li        """
268*9c5db199SXin Li        if not self.__kernel:
269*9c5db199SXin Li            where = {'kernel_idx' : self.kernel_idx}
270*9c5db199SXin Li            self.__kernel = kernel.select(self.db, where)[0]
271*9c5db199SXin Li        return self.__kernel
272*9c5db199SXin Li
273*9c5db199SXin Li
274*9c5db199SXin Li    def machine(self):
275*9c5db199SXin Li        """
276*9c5db199SXin Li        Caching function for kernels
277*9c5db199SXin Li        """
278*9c5db199SXin Li        if not self.__machine:
279*9c5db199SXin Li            where = {'machine_idx' : self.machine_idx}
280*9c5db199SXin Li            self.__machine = machine.select(self.db, where)[0]
281*9c5db199SXin Li        return self.__machine
282*9c5db199SXin Li
283*9c5db199SXin Li
284*9c5db199SXin Liclass job:
285*9c5db199SXin Li    def __init__(self, db, job_idx):
286*9c5db199SXin Li        where = {'job_idx' : job_idx}
287*9c5db199SXin Li        rows = db.select('tag, machine_idx', 'tko_jobs', where)
288*9c5db199SXin Li        if rows:
289*9c5db199SXin Li            self.tag, self.machine_idx = rows[0]
290*9c5db199SXin Li            self.job_idx = job_idx
291*9c5db199SXin Li
292*9c5db199SXin Li
293*9c5db199SXin Liclass iteration:
294*9c5db199SXin Li    @classmethod
295*9c5db199SXin Li    def select(klass, db, where):
296*9c5db199SXin Li        fields = ['iteration', 'attribute', 'value']
297*9c5db199SXin Li        iterations = []
298*9c5db199SXin Li        rows = db.select(','.join(fields), 'tko_iteration_result', where)
299*9c5db199SXin Li        for row in rows:
300*9c5db199SXin Li            iterations.append(klass(*row))
301*9c5db199SXin Li        return iterations
302*9c5db199SXin Li
303*9c5db199SXin Li
304*9c5db199SXin Li    def __init__(self, iteration, key, value):
305*9c5db199SXin Li        self.iteration = iteration
306*9c5db199SXin Li        self.key = key
307*9c5db199SXin Li        self.value = value
308*9c5db199SXin Li
309*9c5db199SXin Li# class patch:
310*9c5db199SXin Li#       def __init__(self):
311*9c5db199SXin Li#               self.spec = None
312