blob: d8be245df4c45d0a95bc28f5dd326e51d05759a3 [file] [log] [blame]
Keith Haddow1e5c7012016-03-09 16:05:37 -08001import json
lmr6f80e7a2010-02-04 03:18:28 +00002import os
jadmanskicc549172008-05-21 18:11:51 +00003
Allen Li5977e5c2018-10-18 17:44:46 -07004from autotest_lib.server.hosts import file_store
jadmanskicc549172008-05-21 18:11:51 +00005from autotest_lib.client.common_lib import utils
Shuhei Takahashi0ae6c4d2018-07-31 16:36:08 +09006from autotest_lib.tko import tast
jadmanskicc549172008-05-21 18:11:51 +00007from autotest_lib.tko import utils as tko_utils
jadmanski6e8bf752008-05-14 00:17:48 +00008
9
mblighe79ebb02008-04-17 15:39:22 +000010class job(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070011 """Represents a job."""
12
jadmanskif7fa2cc2008-10-01 14:13:23 +000013 def __init__(self, dir, user, label, machine, queued_time, started_time,
showard71b94312009-08-20 23:40:02 +000014 finished_time, machine_owner, machine_group, aborted_by,
showardc1a98d12010-01-15 00:22:22 +000015 aborted_on, keyval_dict):
jadmanski0afbb632008-06-06 21:10:57 +000016 self.dir = dir
17 self.tests = []
18 self.user = user
19 self.label = label
20 self.machine = machine
21 self.queued_time = queued_time
22 self.started_time = started_time
23 self.finished_time = finished_time
24 self.machine_owner = machine_owner
showard71b94312009-08-20 23:40:02 +000025 self.machine_group = machine_group
jadmanskif7fa2cc2008-10-01 14:13:23 +000026 self.aborted_by = aborted_by
27 self.aborted_on = aborted_on
showardc1a98d12010-01-15 00:22:22 +000028 self.keyval_dict = keyval_dict
Michael Tang5f74ffd2016-10-31 10:34:53 -070029 self.afe_parent_job_id = None
30 self.build_version = None
31 self.suite = None
32 self.board = None
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -070033 self.job_idx = None
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -070034 # id of the corresponding tko_task_references entry.
35 # This table is used to refer to skylab task / afe job corresponding to
36 # this tko_job.
37 self.task_reference_id = None
mblighe79ebb02008-04-17 15:39:22 +000038
jadmanskia8e302a2008-09-25 19:49:38 +000039 @staticmethod
40 def read_keyval(dir):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070041 """
42 Read job keyval files.
43
44 @param dir: String name of directory containing job keyval files.
45
46 @return A dictionary containing job keyvals.
47
48 """
jadmanskia8e302a2008-09-25 19:49:38 +000049 dir = os.path.normpath(dir)
50 top_dir = tko_utils.find_toplevel_job_dir(dir)
51 if not top_dir:
52 top_dir = dir
53 assert(dir.startswith(top_dir))
54
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070055 # Pull in and merge all the keyval files, with higher-level
56 # overriding values in the lower-level ones.
jadmanskia8e302a2008-09-25 19:49:38 +000057 keyval = {}
58 while True:
59 try:
jadmanskifc4e3382008-10-02 16:19:19 +000060 upper_keyval = utils.read_keyval(dir)
61 # HACK: exclude hostname from the override - this is a special
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070062 # case where we want lower to override higher.
63 if 'hostname' in upper_keyval and 'hostname' in keyval:
64 del upper_keyval['hostname']
jadmanski8e00afa2008-10-02 16:22:04 +000065 keyval.update(upper_keyval)
jadmanskia8e302a2008-09-25 19:49:38 +000066 except IOError:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070067 pass # If the keyval can't be read just move on to the next.
jadmanskia8e302a2008-09-25 19:49:38 +000068 if dir == top_dir:
69 break
70 else:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070071 assert(dir != '/')
jadmanskia8e302a2008-09-25 19:49:38 +000072 dir = os.path.dirname(dir)
73 return keyval
74
75
mblighe79ebb02008-04-17 15:39:22 +000076class kernel(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070077 """Represents a kernel."""
78
jadmanski0afbb632008-06-06 21:10:57 +000079 def __init__(self, base, patches, kernel_hash):
80 self.base = base
81 self.patches = patches
82 self.kernel_hash = kernel_hash
mblighe79ebb02008-04-17 15:39:22 +000083
84
jadmanski0afbb632008-06-06 21:10:57 +000085 @staticmethod
86 def compute_hash(base, hashes):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070087 """Compute a hash given the base string and hashes for each patch.
88
89 @param base: A string representing the kernel base.
90 @param hashes: A list of hashes, where each hash is associated with a
91 patch of this kernel.
92
93 @return A string representing the computed hash.
94
95 """
jadmanski0afbb632008-06-06 21:10:57 +000096 key_string = ','.join([base] + hashes)
lmr6f80e7a2010-02-04 03:18:28 +000097 return utils.hash('md5', key_string).hexdigest()
jadmanski6e8bf752008-05-14 00:17:48 +000098
99
mblighe79ebb02008-04-17 15:39:22 +0000100class test(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700101 """Represents a test."""
102
jadmanski0afbb632008-06-06 21:10:57 +0000103 def __init__(self, subdir, testname, status, reason, test_kernel,
104 machine, started_time, finished_time, iterations,
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700105 attributes, perf_values, labels):
jadmanski0afbb632008-06-06 21:10:57 +0000106 self.subdir = subdir
107 self.testname = testname
108 self.status = status
109 self.reason = reason
110 self.kernel = test_kernel
111 self.machine = machine
112 self.started_time = started_time
113 self.finished_time = finished_time
114 self.iterations = iterations
115 self.attributes = attributes
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700116 self.perf_values = perf_values
jadmanski9b6babf2009-04-21 17:57:40 +0000117 self.labels = labels
mblighe79ebb02008-04-17 15:39:22 +0000118
119
jadmanski0afbb632008-06-06 21:10:57 +0000120 @staticmethod
121 def load_iterations(keyval_path):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700122 """Abstract method to load a list of iterations from a keyval file.
123
124 @param keyval_path: String path to a keyval file.
125
126 @return A list of iteration objects.
127
128 """
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700129 raise NotImplementedError
130
131
132 @staticmethod
133 def load_perf_values(perf_values_file):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700134 """Loads perf values from a perf measurements file.
135
136 @param perf_values_file: The string path to a perf measurements file.
137
138 @return A list of perf_value_iteration objects.
139
140 """
jadmanski0afbb632008-06-06 21:10:57 +0000141 raise NotImplementedError
jadmanskicc549172008-05-21 18:11:51 +0000142
143
jadmanski0afbb632008-06-06 21:10:57 +0000144 @classmethod
145 def parse_test(cls, job, subdir, testname, status, reason, test_kernel,
jadmanski74eebf32008-07-15 20:04:42 +0000146 started_time, finished_time, existing_instance=None):
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700147 """
148 Parse test result files to construct a complete test instance.
149
150 Given a job and the basic metadata about the test that can be
151 extracted from the status logs, parse the test result files (keyval
152 files and perf measurement files) and use them to construct a complete
153 test instance.
154
155 @param job: A job object.
156 @param subdir: The string subdirectory name for the given test.
157 @param testname: The name of the test.
158 @param status: The status of the test.
159 @param reason: The reason string for the test.
160 @param test_kernel: The kernel of the test.
161 @param started_time: The start time of the test.
162 @param finished_time: The finish time of the test.
163 @param existing_instance: An existing test instance.
164
165 @return A test instance that has the complete information.
166
167 """
jadmanski0afbb632008-06-06 21:10:57 +0000168 tko_utils.dprint("parsing test %s %s" % (subdir, testname))
jadmanskicc549172008-05-21 18:11:51 +0000169
Shuhei Takahashi0ae6c4d2018-07-31 16:36:08 +0900170 if tast.is_tast_test(testname):
171 attributes, perf_values = tast.load_tast_test_aux_results(job,
172 testname)
173 iterations = []
174 elif subdir:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700175 # Grab iterations from the results keyval.
jadmanski0afbb632008-06-06 21:10:57 +0000176 iteration_keyval = os.path.join(job.dir, subdir,
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700177 'results', 'keyval')
jadmanski0afbb632008-06-06 21:10:57 +0000178 iterations = cls.load_iterations(iteration_keyval)
jadmanskicc549172008-05-21 18:11:51 +0000179
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700180 # Grab perf values from the perf measurements file.
181 perf_values_file = os.path.join(job.dir, subdir,
Keith Haddow1e5c7012016-03-09 16:05:37 -0800182 'results', 'results-chart.json')
183 perf_values = {}
184 if os.path.exists(perf_values_file):
185 with open(perf_values_file, 'r') as fp:
186 contents = fp.read()
187 if contents:
188 perf_values = json.loads(contents)
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700189
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700190 # Grab test attributes from the subdir keyval.
191 test_keyval = os.path.join(job.dir, subdir, 'keyval')
jadmanski0afbb632008-06-06 21:10:57 +0000192 attributes = test.load_attributes(test_keyval)
193 else:
194 iterations = []
Keith Haddow1e5c7012016-03-09 16:05:37 -0800195 perf_values = {}
jadmanski0afbb632008-06-06 21:10:57 +0000196 attributes = {}
jadmanskicc549172008-05-21 18:11:51 +0000197
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700198 # Grab test+host attributes from the host keyval.
showard71b94312009-08-20 23:40:02 +0000199 host_keyval = cls.parse_host_keyval(job.dir, job.machine)
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700200 attributes.update(dict(('host-%s' % k, v)
showard71b94312009-08-20 23:40:02 +0000201 for k, v in host_keyval.iteritems()))
jadmanskib591fba2008-09-10 16:19:22 +0000202
jadmanski74eebf32008-07-15 20:04:42 +0000203 if existing_instance:
204 def constructor(*args, **dargs):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700205 """Initializes an existing test instance."""
jadmanski74eebf32008-07-15 20:04:42 +0000206 existing_instance.__init__(*args, **dargs)
207 return existing_instance
208 else:
209 constructor = cls
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700210
jadmanski74eebf32008-07-15 20:04:42 +0000211 return constructor(subdir, testname, status, reason, test_kernel,
212 job.machine, started_time, finished_time,
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700213 iterations, attributes, perf_values, [])
jadmanski74eebf32008-07-15 20:04:42 +0000214
215
216 @classmethod
217 def parse_partial_test(cls, job, subdir, testname, reason, test_kernel,
218 started_time):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700219 """
220 Create a test instance representing a partial test result.
221
222 Given a job and the basic metadata available when a test is
jadmanski74eebf32008-07-15 20:04:42 +0000223 started, create a test instance representing the partial result.
224 Assume that since the test is not complete there are no results files
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700225 actually available for parsing.
jadmanski74eebf32008-07-15 20:04:42 +0000226
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700227 @param job: A job object.
228 @param subdir: The string subdirectory name for the given test.
229 @param testname: The name of the test.
230 @param reason: The reason string for the test.
231 @param test_kernel: The kernel of the test.
232 @param started_time: The start time of the test.
233
234 @return A test instance that has partial test information.
235
236 """
237 tko_utils.dprint('parsing partial test %s %s' % (subdir, testname))
238
239 return cls(subdir, testname, 'RUNNING', reason, test_kernel,
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700240 job.machine, started_time, None, [], {}, [], [])
jadmanskicc549172008-05-21 18:11:51 +0000241
242
jadmanski0afbb632008-06-06 21:10:57 +0000243 @staticmethod
244 def load_attributes(keyval_path):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700245 """
246 Load test attributes from a test keyval path.
247
248 Load the test attributes into a dictionary from a test
249 keyval path. Does not assume that the path actually exists.
250
251 @param keyval_path: The string path to a keyval file.
252
253 @return A dictionary representing the test keyvals.
254
255 """
jadmanski0afbb632008-06-06 21:10:57 +0000256 if not os.path.exists(keyval_path):
257 return {}
258 return utils.read_keyval(keyval_path)
jadmanskicc549172008-05-21 18:11:51 +0000259
260
jadmanskib591fba2008-09-10 16:19:22 +0000261 @staticmethod
Dan Shiba7b9812016-06-30 10:55:58 -0700262 def _parse_keyval(job_dir, sub_keyval_path):
263 """
264 Parse a file of keyvals.
265
266 @param job_dir: The string directory name of the associated job.
267 @param sub_keyval_path: Path to a keyval file relative to job_dir.
268
269 @return A dictionary representing the keyvals.
270
271 """
272 # The "real" job dir may be higher up in the directory tree.
273 job_dir = tko_utils.find_toplevel_job_dir(job_dir)
274 if not job_dir:
275 return {} # We can't find a top-level job dir with job keyvals.
276
277 # The keyval is <job_dir>/`sub_keyval_path` if it exists.
278 keyval_path = os.path.join(job_dir, sub_keyval_path)
279 if os.path.isfile(keyval_path):
280 return utils.read_keyval(keyval_path)
281 else:
282 return {}
283
284
285 @staticmethod
jadmanskib591fba2008-09-10 16:19:22 +0000286 def parse_host_keyval(job_dir, hostname):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700287 """
288 Parse host keyvals.
289
290 @param job_dir: The string directory name of the associated job.
291 @param hostname: The string hostname.
292
293 @return A dictionary representing the host keyvals.
294
295 """
Allen Li5977e5c2018-10-18 17:44:46 -0700296 keyval_path = os.path.join('host_keyvals', hostname)
Dan Shiba7b9812016-06-30 10:55:58 -0700297 # The host keyval is <job_dir>/host_keyvals/<hostname> if it exists.
Allen Li5977e5c2018-10-18 17:44:46 -0700298 # Otherwise we're running on Skylab which uses hostinfo.
299 if not os.path.exists(keyval_path):
300 try:
301 return _parse_hostinfo_keyval(job_dir, hostname)
302 except Exception as e:
303 # If anything goes wrong, log it and just use the old flow.
304 tko_utils.dprint("tried using hostinfo: %s" % e)
305 return test._parse_keyval(job_dir, keyval_path)
jadmanskib591fba2008-09-10 16:19:22 +0000306
Dan Shiba7b9812016-06-30 10:55:58 -0700307
308 @staticmethod
309 def parse_job_keyval(job_dir):
310 """
311 Parse job keyvals.
312
313 @param job_dir: The string directory name of the associated job.
314
315 @return A dictionary representing the job keyvals.
316
317 """
318 # The job keyval is <job_dir>/keyval if it exists.
319 return test._parse_keyval(job_dir, 'keyval')
jadmanskib591fba2008-09-10 16:19:22 +0000320
321
Allen Li5977e5c2018-10-18 17:44:46 -0700322def _parse_hostinfo_keyval(job_dir, hostname):
323 """
324 Parse host keyvals from hostinfo.
325
326 @param job_dir: The string directory name of the associated job.
327 @param hostname: The string hostname.
328
329 @return A dictionary representing the host keyvals.
330
331 """
332 # The hostinfo path looks like:
333 # host_info_store/dir_e499300b-8bba-4ad3-a404-8c12a9367f17/
334 # chromeos6-row4-rack11-host6.store
335 #
336 # TODO(ayatane): We should pass hostinfo path explicitly.
337 subdir = 'host_info_store'
Alex Zamorzaev60755252018-11-06 06:37:27 +0000338 subdir = os.path.join(subdir, os.listdir(subdir)[0])
339 hostinfo_path = os.path.join(job_dir, subdir, hostname + '.store')
Allen Li5977e5c2018-10-18 17:44:46 -0700340 store = file_store.FileStore(hostinfo_path)
341 hostinfo = store.get()
342 # TODO(ayatane): Investigate if urllib.quote is better.
343 label_string = ','.join(label.replace(':', '%3A')
344 for label in hostinfo.labels)
Allen Lic3cdbdd2018-10-30 13:46:09 -0700345 return {
346 'host-labels': label_string,
347 'host-platform': hostinfo.model,
348 }
Allen Li5977e5c2018-10-18 17:44:46 -0700349
350
mblighe79ebb02008-04-17 15:39:22 +0000351class patch(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700352 """Represents a patch."""
353
jadmanski0afbb632008-06-06 21:10:57 +0000354 def __init__(self, spec, reference, hash):
355 self.spec = spec
356 self.reference = reference
357 self.hash = hash
mblighe79ebb02008-04-17 15:39:22 +0000358
359
360class iteration(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700361 """Represents an iteration."""
362
jadmanski0afbb632008-06-06 21:10:57 +0000363 def __init__(self, index, attr_keyval, perf_keyval):
364 self.index = index
365 self.attr_keyval = attr_keyval
366 self.perf_keyval = perf_keyval
jadmanskicc549172008-05-21 18:11:51 +0000367
368
jadmanski0afbb632008-06-06 21:10:57 +0000369 @staticmethod
370 def parse_line_into_dicts(line, attr_dict, perf_dict):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700371 """
372 Abstract method to parse a keyval line and insert it into a dictionary.
373
374 @param line: The string line to parse.
375 @param attr_dict: Dictionary of generic iteration attributes.
376 @param perf_dict: Dictionary of iteration performance results.
377
jadmanski0afbb632008-06-06 21:10:57 +0000378 """
379 raise NotImplementedError
jadmanskicc549172008-05-21 18:11:51 +0000380
381
jadmanski0afbb632008-06-06 21:10:57 +0000382 @classmethod
383 def load_from_keyval(cls, keyval_path):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700384 """
385 Load a list of iterations from an iteration keyval file.
386
jadmanski0afbb632008-06-06 21:10:57 +0000387 Keyval data from separate iterations is separated by blank
388 lines. Makes use of the parse_line_into_dicts method to
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700389 actually parse the individual lines.
390
391 @param keyval_path: The string path to a keyval file.
392
393 @return A list of iteration objects.
394
395 """
jadmanski0afbb632008-06-06 21:10:57 +0000396 if not os.path.exists(keyval_path):
397 return []
jadmanskicc549172008-05-21 18:11:51 +0000398
jadmanski0afbb632008-06-06 21:10:57 +0000399 iterations = []
400 index = 1
401 attr, perf = {}, {}
402 for line in file(keyval_path):
403 line = line.strip()
404 if line:
405 cls.parse_line_into_dicts(line, attr, perf)
406 else:
407 iterations.append(cls(index, attr, perf))
408 index += 1
409 attr, perf = {}, {}
410 if attr or perf:
411 iterations.append(cls(index, attr, perf))
412 return iterations
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700413
414
415class perf_value_iteration(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700416 """Represents a perf value iteration."""
417
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700418 def __init__(self, index, perf_measurements):
419 """
420 Initializes the perf values for a particular test iteration.
421
422 @param index: The integer iteration number.
423 @param perf_measurements: A list of dictionaries, where each dictionary
424 contains the information for a measured perf metric from the
425 current iteration.
426
427 """
428 self.index = index
429 self.perf_measurements = perf_measurements
430
431
432 def add_measurement(self, measurement):
433 """
434 Appends to the list of perf measurements for this iteration.
435
436 @param measurement: A dictionary containing information for a measured
437 perf metric.
438
439 """
440 self.perf_measurements.append(measurement)
441
442
443 @staticmethod
444 def parse_line_into_dict(line):
445 """
446 Abstract method to parse an individual perf measurement line.
447
448 @param line: A string line from the perf measurement output file.
449
450 @return A dicionary representing the information for a measured perf
451 metric from one line of the perf measurement output file, or an
452 empty dictionary if the line cannot be parsed successfully.
453
454 """
455 raise NotImplementedError