huangcm
2025-09-01 53d8e046ac1bf2ebe94f671983e3d3be059df91a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
import json
import os
 
from autotest_lib.server.hosts import file_store
from autotest_lib.client.common_lib import utils
from autotest_lib.tko import tast
from autotest_lib.tko import utils as tko_utils
 
 
class job(object):
    """Represents a job."""
 
    def __init__(self, dir, user, label, machine, queued_time, started_time,
                 finished_time, machine_owner, machine_group, aborted_by,
                 aborted_on, keyval_dict):
        self.dir = dir
        self.tests = []
        self.user = user
        self.label = label
        self.machine = machine
        self.queued_time = queued_time
        self.started_time = started_time
        self.finished_time = finished_time
        self.machine_owner = machine_owner
        self.machine_group = machine_group
        self.aborted_by = aborted_by
        self.aborted_on = aborted_on
        self.keyval_dict = keyval_dict
        self.afe_parent_job_id = None
        self.build_version = None
        self.suite = None
        self.board = None
        self.job_idx = None
        # id of the corresponding tko_task_references entry.
        # This table is used to refer to skylab task / afe job corresponding to
        # this tko_job.
        self.task_reference_id = None
 
    @staticmethod
    def read_keyval(dir):
        """
        Read job keyval files.
 
        @param dir: String name of directory containing job keyval files.
 
        @return A dictionary containing job keyvals.
 
        """
        dir = os.path.normpath(dir)
        top_dir = tko_utils.find_toplevel_job_dir(dir)
        if not top_dir:
            top_dir = dir
        assert(dir.startswith(top_dir))
 
        # Pull in and merge all the keyval files, with higher-level
        # overriding values in the lower-level ones.
        keyval = {}
        while True:
            try:
                upper_keyval = utils.read_keyval(dir)
                # HACK: exclude hostname from the override - this is a special
                # case where we want lower to override higher.
                if 'hostname' in upper_keyval and 'hostname' in keyval:
                    del upper_keyval['hostname']
                keyval.update(upper_keyval)
            except IOError:
                pass  # If the keyval can't be read just move on to the next.
            if dir == top_dir:
                break
            else:
                assert(dir != '/')
                dir = os.path.dirname(dir)
        return keyval
 
 
class kernel(object):
    """Represents a kernel."""
 
    def __init__(self, base, patches, kernel_hash):
        self.base = base
        self.patches = patches
        self.kernel_hash = kernel_hash
 
 
    @staticmethod
    def compute_hash(base, hashes):
        """Compute a hash given the base string and hashes for each patch.
 
        @param base: A string representing the kernel base.
        @param hashes: A list of hashes, where each hash is associated with a
            patch of this kernel.
 
        @return A string representing the computed hash.
 
        """
        key_string = ','.join([base] + hashes)
        return utils.hash('md5', key_string).hexdigest()
 
 
class test(object):
    """Represents a test."""
 
    def __init__(self, subdir, testname, status, reason, test_kernel,
                 machine, started_time, finished_time, iterations,
                 attributes, perf_values, labels):
        self.subdir = subdir
        self.testname = testname
        self.status = status
        self.reason = reason
        self.kernel = test_kernel
        self.machine = machine
        self.started_time = started_time
        self.finished_time = finished_time
        self.iterations = iterations
        self.attributes = attributes
        self.perf_values = perf_values
        self.labels = labels
 
 
    @staticmethod
    def load_iterations(keyval_path):
        """Abstract method to load a list of iterations from a keyval file.
 
        @param keyval_path: String path to a keyval file.
 
        @return A list of iteration objects.
 
        """
        raise NotImplementedError
 
 
    @staticmethod
    def load_perf_values(perf_values_file):
        """Loads perf values from a perf measurements file.
 
        @param perf_values_file: The string path to a perf measurements file.
 
        @return A list of perf_value_iteration objects.
 
        """
        raise NotImplementedError
 
 
    @classmethod
    def parse_test(cls, job, subdir, testname, status, reason, test_kernel,
                   started_time, finished_time, existing_instance=None):
        """
        Parse test result files to construct a complete test instance.
 
        Given a job and the basic metadata about the test that can be
        extracted from the status logs, parse the test result files (keyval
        files and perf measurement files) and use them to construct a complete
        test instance.
 
        @param job: A job object.
        @param subdir: The string subdirectory name for the given test.
        @param testname: The name of the test.
        @param status: The status of the test.
        @param reason: The reason string for the test.
        @param test_kernel: The kernel of the test.
        @param started_time: The start time of the test.
        @param finished_time: The finish time of the test.
        @param existing_instance: An existing test instance.
 
        @return A test instance that has the complete information.
 
        """
        tko_utils.dprint("parsing test %s %s" % (subdir, testname))
 
        if tast.is_tast_test(testname):
            attributes, perf_values = tast.load_tast_test_aux_results(job,
                                                                      testname)
            iterations = []
        elif subdir:
            # Grab iterations from the results keyval.
            iteration_keyval = os.path.join(job.dir, subdir,
                                            'results', 'keyval')
            iterations = cls.load_iterations(iteration_keyval)
 
            # Grab perf values from the perf measurements file.
            perf_values_file = os.path.join(job.dir, subdir,
                                            'results', 'results-chart.json')
            perf_values = {}
            if os.path.exists(perf_values_file):
                with open(perf_values_file, 'r') as fp:
                    contents = fp.read()
                if contents:
                    perf_values = json.loads(contents)
 
            # Grab test attributes from the subdir keyval.
            test_keyval = os.path.join(job.dir, subdir, 'keyval')
            attributes = test.load_attributes(test_keyval)
        else:
            iterations = []
            perf_values = {}
            attributes = {}
 
        # Grab test+host attributes from the host keyval.
        host_keyval = cls.parse_host_keyval(job.dir, job.machine)
        attributes.update(dict(('host-%s' % k, v)
                               for k, v in host_keyval.iteritems()))
 
        if existing_instance:
            def constructor(*args, **dargs):
                """Initializes an existing test instance."""
                existing_instance.__init__(*args, **dargs)
                return existing_instance
        else:
            constructor = cls
 
        return constructor(subdir, testname, status, reason, test_kernel,
                           job.machine, started_time, finished_time,
                           iterations, attributes, perf_values, [])
 
 
    @classmethod
    def parse_partial_test(cls, job, subdir, testname, reason, test_kernel,
                           started_time):
        """
        Create a test instance representing a partial test result.
 
        Given a job and the basic metadata available when a test is
        started, create a test instance representing the partial result.
        Assume that since the test is not complete there are no results files
        actually available for parsing.
 
        @param job: A job object.
        @param subdir: The string subdirectory name for the given test.
        @param testname: The name of the test.
        @param reason: The reason string for the test.
        @param test_kernel: The kernel of the test.
        @param started_time: The start time of the test.
 
        @return A test instance that has partial test information.
 
        """
        tko_utils.dprint('parsing partial test %s %s' % (subdir, testname))
 
        return cls(subdir, testname, 'RUNNING', reason, test_kernel,
                   job.machine, started_time, None, [], {}, [], [])
 
 
    @staticmethod
    def load_attributes(keyval_path):
        """
        Load test attributes from a test keyval path.
 
        Load the test attributes into a dictionary from a test
        keyval path. Does not assume that the path actually exists.
 
        @param keyval_path: The string path to a keyval file.
 
        @return A dictionary representing the test keyvals.
 
        """
        if not os.path.exists(keyval_path):
            return {}
        return utils.read_keyval(keyval_path)
 
 
    @staticmethod
    def _parse_keyval(job_dir, sub_keyval_path):
        """
        Parse a file of keyvals.
 
        @param job_dir: The string directory name of the associated job.
        @param sub_keyval_path: Path to a keyval file relative to job_dir.
 
        @return A dictionary representing the keyvals.
 
        """
        # The "real" job dir may be higher up in the directory tree.
        job_dir = tko_utils.find_toplevel_job_dir(job_dir)
        if not job_dir:
            return {}  # We can't find a top-level job dir with job keyvals.
 
        # The keyval is <job_dir>/`sub_keyval_path` if it exists.
        keyval_path = os.path.join(job_dir, sub_keyval_path)
        if os.path.isfile(keyval_path):
            return utils.read_keyval(keyval_path)
        else:
            return {}
 
 
    @staticmethod
    def parse_host_keyval(job_dir, hostname):
        """
        Parse host keyvals.
 
        @param job_dir: The string directory name of the associated job.
        @param hostname: The string hostname.
 
        @return A dictionary representing the host keyvals.
 
        """
        keyval_path = os.path.join('host_keyvals', hostname)
        # The host keyval is <job_dir>/host_keyvals/<hostname> if it exists.
        # Otherwise we're running on Skylab which uses hostinfo.
        if not os.path.exists(os.path.join(job_dir, keyval_path)):
            tko_utils.dprint("trying to use hostinfo")
            try:
                return _parse_hostinfo_keyval(job_dir, hostname)
            except Exception as e:
                # If anything goes wrong, log it and just use the old flow.
                tko_utils.dprint("tried using hostinfo: %s" % e)
        return test._parse_keyval(job_dir, keyval_path)
 
 
    @staticmethod
    def parse_job_keyval(job_dir):
        """
        Parse job keyvals.
 
        @param job_dir: The string directory name of the associated job.
 
        @return A dictionary representing the job keyvals.
 
        """
        # The job keyval is <job_dir>/keyval if it exists.
        return test._parse_keyval(job_dir, 'keyval')
 
 
def _parse_hostinfo_keyval(job_dir, hostname):
    """
    Parse host keyvals from hostinfo.
 
    @param job_dir: The string directory name of the associated job.
    @param hostname: The string hostname.
 
    @return A dictionary representing the host keyvals.
 
    """
    # The hostinfo path looks like:
    # host_info_store/chromeos6-row4-rack11-host6.store
    #
    # TODO(ayatane): We should pass hostinfo path explicitly.
    subdir = 'host_info_store'
    hostinfo_path = os.path.join(job_dir, subdir, hostname + '.store')
    store = file_store.FileStore(hostinfo_path)
    hostinfo = store.get()
    # TODO(ayatane): Investigate if urllib.quote is better.
    label_string = ','.join(label.replace(':', '%3A')
                            for label in hostinfo.labels)
    return {'labels': label_string, 'platform': hostinfo.model}
 
 
class patch(object):
    """Represents a patch."""
 
    def __init__(self, spec, reference, hash):
        self.spec = spec
        self.reference = reference
        self.hash = hash
 
 
class iteration(object):
    """Represents an iteration."""
 
    def __init__(self, index, attr_keyval, perf_keyval):
        self.index = index
        self.attr_keyval = attr_keyval
        self.perf_keyval = perf_keyval
 
 
    @staticmethod
    def parse_line_into_dicts(line, attr_dict, perf_dict):
        """
        Abstract method to parse a keyval line and insert it into a dictionary.
 
        @param line: The string line to parse.
        @param attr_dict: Dictionary of generic iteration attributes.
        @param perf_dict: Dictionary of iteration performance results.
 
        """
        raise NotImplementedError
 
 
    @classmethod
    def load_from_keyval(cls, keyval_path):
        """
        Load a list of iterations from an iteration keyval file.
 
        Keyval data from separate iterations is separated by blank
        lines. Makes use of the parse_line_into_dicts method to
        actually parse the individual lines.
 
        @param keyval_path: The string path to a keyval file.
 
        @return A list of iteration objects.
 
        """
        if not os.path.exists(keyval_path):
            return []
 
        iterations = []
        index = 1
        attr, perf = {}, {}
        for line in file(keyval_path):
            line = line.strip()
            if line:
                cls.parse_line_into_dicts(line, attr, perf)
            else:
                iterations.append(cls(index, attr, perf))
                index += 1
                attr, perf = {}, {}
        if attr or perf:
            iterations.append(cls(index, attr, perf))
        return iterations
 
 
class perf_value_iteration(object):
    """Represents a perf value iteration."""
 
    def __init__(self, index, perf_measurements):
        """
        Initializes the perf values for a particular test iteration.
 
        @param index: The integer iteration number.
        @param perf_measurements: A list of dictionaries, where each dictionary
            contains the information for a measured perf metric from the
            current iteration.
 
        """
        self.index = index
        self.perf_measurements = perf_measurements
 
 
    def add_measurement(self, measurement):
        """
        Appends to the list of perf measurements for this iteration.
 
        @param measurement: A dictionary containing information for a measured
            perf metric.
 
        """
        self.perf_measurements.append(measurement)
 
 
    @staticmethod
    def parse_line_into_dict(line):
        """
        Abstract method to parse an individual perf measurement line.
 
        @param line: A string line from the perf measurement output file.
 
        @return A dicionary representing the information for a measured perf
            metric from one line of the perf measurement output file, or an
            empty dictionary if the line cannot be parsed successfully.
 
        """
        raise NotImplementedError