blob: 9bf91ae2670c30c3ca7d4f60be60ccfbbb14c0cb [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
4from autotest_lib.client.common_lib import utils
5from autotest_lib.tko import utils as tko_utils
jadmanski6e8bf752008-05-14 00:17:48 +00006
7
mblighe79ebb02008-04-17 15:39:22 +00008class job(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -07009 """Represents a job."""
10
jadmanskif7fa2cc2008-10-01 14:13:23 +000011 def __init__(self, dir, user, label, machine, queued_time, started_time,
showard71b94312009-08-20 23:40:02 +000012 finished_time, machine_owner, machine_group, aborted_by,
showardc1a98d12010-01-15 00:22:22 +000013 aborted_on, keyval_dict):
jadmanski0afbb632008-06-06 21:10:57 +000014 self.dir = dir
15 self.tests = []
16 self.user = user
17 self.label = label
18 self.machine = machine
19 self.queued_time = queued_time
20 self.started_time = started_time
21 self.finished_time = finished_time
22 self.machine_owner = machine_owner
showard71b94312009-08-20 23:40:02 +000023 self.machine_group = machine_group
jadmanskif7fa2cc2008-10-01 14:13:23 +000024 self.aborted_by = aborted_by
25 self.aborted_on = aborted_on
showardc1a98d12010-01-15 00:22:22 +000026 self.keyval_dict = keyval_dict
Michael Tang5f74ffd2016-10-31 10:34:53 -070027 self.afe_parent_job_id = None
28 self.build_version = None
29 self.suite = None
30 self.board = None
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -070031 self.job_idx = None
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -070032 # id of the corresponding tko_task_references entry.
33 # This table is used to refer to skylab task / afe job corresponding to
34 # this tko_job.
35 self.task_reference_id = None
mblighe79ebb02008-04-17 15:39:22 +000036
jadmanskia8e302a2008-09-25 19:49:38 +000037 @staticmethod
38 def read_keyval(dir):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070039 """
40 Read job keyval files.
41
42 @param dir: String name of directory containing job keyval files.
43
44 @return A dictionary containing job keyvals.
45
46 """
jadmanskia8e302a2008-09-25 19:49:38 +000047 dir = os.path.normpath(dir)
48 top_dir = tko_utils.find_toplevel_job_dir(dir)
49 if not top_dir:
50 top_dir = dir
51 assert(dir.startswith(top_dir))
52
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070053 # Pull in and merge all the keyval files, with higher-level
54 # overriding values in the lower-level ones.
jadmanskia8e302a2008-09-25 19:49:38 +000055 keyval = {}
56 while True:
57 try:
jadmanskifc4e3382008-10-02 16:19:19 +000058 upper_keyval = utils.read_keyval(dir)
59 # HACK: exclude hostname from the override - this is a special
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070060 # case where we want lower to override higher.
61 if 'hostname' in upper_keyval and 'hostname' in keyval:
62 del upper_keyval['hostname']
jadmanski8e00afa2008-10-02 16:22:04 +000063 keyval.update(upper_keyval)
jadmanskia8e302a2008-09-25 19:49:38 +000064 except IOError:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070065 pass # If the keyval can't be read just move on to the next.
jadmanskia8e302a2008-09-25 19:49:38 +000066 if dir == top_dir:
67 break
68 else:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070069 assert(dir != '/')
jadmanskia8e302a2008-09-25 19:49:38 +000070 dir = os.path.dirname(dir)
71 return keyval
72
73
mblighe79ebb02008-04-17 15:39:22 +000074class kernel(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070075 """Represents a kernel."""
76
jadmanski0afbb632008-06-06 21:10:57 +000077 def __init__(self, base, patches, kernel_hash):
78 self.base = base
79 self.patches = patches
80 self.kernel_hash = kernel_hash
mblighe79ebb02008-04-17 15:39:22 +000081
82
jadmanski0afbb632008-06-06 21:10:57 +000083 @staticmethod
84 def compute_hash(base, hashes):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070085 """Compute a hash given the base string and hashes for each patch.
86
87 @param base: A string representing the kernel base.
88 @param hashes: A list of hashes, where each hash is associated with a
89 patch of this kernel.
90
91 @return A string representing the computed hash.
92
93 """
jadmanski0afbb632008-06-06 21:10:57 +000094 key_string = ','.join([base] + hashes)
lmr6f80e7a2010-02-04 03:18:28 +000095 return utils.hash('md5', key_string).hexdigest()
jadmanski6e8bf752008-05-14 00:17:48 +000096
97
mblighe79ebb02008-04-17 15:39:22 +000098class test(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070099 """Represents a test."""
100
jadmanski0afbb632008-06-06 21:10:57 +0000101 def __init__(self, subdir, testname, status, reason, test_kernel,
102 machine, started_time, finished_time, iterations,
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700103 attributes, perf_values, labels):
jadmanski0afbb632008-06-06 21:10:57 +0000104 self.subdir = subdir
105 self.testname = testname
106 self.status = status
107 self.reason = reason
108 self.kernel = test_kernel
109 self.machine = machine
110 self.started_time = started_time
111 self.finished_time = finished_time
112 self.iterations = iterations
113 self.attributes = attributes
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700114 self.perf_values = perf_values
jadmanski9b6babf2009-04-21 17:57:40 +0000115 self.labels = labels
mblighe79ebb02008-04-17 15:39:22 +0000116
117
jadmanski0afbb632008-06-06 21:10:57 +0000118 @staticmethod
119 def load_iterations(keyval_path):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700120 """Abstract method to load a list of iterations from a keyval file.
121
122 @param keyval_path: String path to a keyval file.
123
124 @return A list of iteration objects.
125
126 """
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700127 raise NotImplementedError
128
129
130 @staticmethod
131 def load_perf_values(perf_values_file):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700132 """Loads perf values from a perf measurements file.
133
134 @param perf_values_file: The string path to a perf measurements file.
135
136 @return A list of perf_value_iteration objects.
137
138 """
jadmanski0afbb632008-06-06 21:10:57 +0000139 raise NotImplementedError
jadmanskicc549172008-05-21 18:11:51 +0000140
141
jadmanski0afbb632008-06-06 21:10:57 +0000142 @classmethod
143 def parse_test(cls, job, subdir, testname, status, reason, test_kernel,
jadmanski74eebf32008-07-15 20:04:42 +0000144 started_time, finished_time, existing_instance=None):
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700145 """
146 Parse test result files to construct a complete test instance.
147
148 Given a job and the basic metadata about the test that can be
149 extracted from the status logs, parse the test result files (keyval
150 files and perf measurement files) and use them to construct a complete
151 test instance.
152
153 @param job: A job object.
154 @param subdir: The string subdirectory name for the given test.
155 @param testname: The name of the test.
156 @param status: The status of the test.
157 @param reason: The reason string for the test.
158 @param test_kernel: The kernel of the test.
159 @param started_time: The start time of the test.
160 @param finished_time: The finish time of the test.
161 @param existing_instance: An existing test instance.
162
163 @return A test instance that has the complete information.
164
165 """
jadmanski0afbb632008-06-06 21:10:57 +0000166 tko_utils.dprint("parsing test %s %s" % (subdir, testname))
jadmanskicc549172008-05-21 18:11:51 +0000167
jadmanski0afbb632008-06-06 21:10:57 +0000168 if subdir:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700169 # Grab iterations from the results keyval.
jadmanski0afbb632008-06-06 21:10:57 +0000170 iteration_keyval = os.path.join(job.dir, subdir,
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700171 'results', 'keyval')
jadmanski0afbb632008-06-06 21:10:57 +0000172 iterations = cls.load_iterations(iteration_keyval)
jadmanskicc549172008-05-21 18:11:51 +0000173
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700174 # Grab perf values from the perf measurements file.
175 perf_values_file = os.path.join(job.dir, subdir,
Keith Haddow1e5c7012016-03-09 16:05:37 -0800176 'results', 'results-chart.json')
177 perf_values = {}
178 if os.path.exists(perf_values_file):
179 with open(perf_values_file, 'r') as fp:
180 contents = fp.read()
181 if contents:
182 perf_values = json.loads(contents)
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700183
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700184 # Grab test attributes from the subdir keyval.
185 test_keyval = os.path.join(job.dir, subdir, 'keyval')
jadmanski0afbb632008-06-06 21:10:57 +0000186 attributes = test.load_attributes(test_keyval)
187 else:
188 iterations = []
Keith Haddow1e5c7012016-03-09 16:05:37 -0800189 perf_values = {}
jadmanski0afbb632008-06-06 21:10:57 +0000190 attributes = {}
jadmanskicc549172008-05-21 18:11:51 +0000191
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700192 # Grab test+host attributes from the host keyval.
showard71b94312009-08-20 23:40:02 +0000193 host_keyval = cls.parse_host_keyval(job.dir, job.machine)
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700194 attributes.update(dict(('host-%s' % k, v)
showard71b94312009-08-20 23:40:02 +0000195 for k, v in host_keyval.iteritems()))
jadmanskib591fba2008-09-10 16:19:22 +0000196
jadmanski74eebf32008-07-15 20:04:42 +0000197 if existing_instance:
198 def constructor(*args, **dargs):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700199 """Initializes an existing test instance."""
jadmanski74eebf32008-07-15 20:04:42 +0000200 existing_instance.__init__(*args, **dargs)
201 return existing_instance
202 else:
203 constructor = cls
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700204
jadmanski74eebf32008-07-15 20:04:42 +0000205 return constructor(subdir, testname, status, reason, test_kernel,
206 job.machine, started_time, finished_time,
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700207 iterations, attributes, perf_values, [])
jadmanski74eebf32008-07-15 20:04:42 +0000208
209
210 @classmethod
211 def parse_partial_test(cls, job, subdir, testname, reason, test_kernel,
212 started_time):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700213 """
214 Create a test instance representing a partial test result.
215
216 Given a job and the basic metadata available when a test is
jadmanski74eebf32008-07-15 20:04:42 +0000217 started, create a test instance representing the partial result.
218 Assume that since the test is not complete there are no results files
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700219 actually available for parsing.
jadmanski74eebf32008-07-15 20:04:42 +0000220
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700221 @param job: A job object.
222 @param subdir: The string subdirectory name for the given test.
223 @param testname: The name of the test.
224 @param reason: The reason string for the test.
225 @param test_kernel: The kernel of the test.
226 @param started_time: The start time of the test.
227
228 @return A test instance that has partial test information.
229
230 """
231 tko_utils.dprint('parsing partial test %s %s' % (subdir, testname))
232
233 return cls(subdir, testname, 'RUNNING', reason, test_kernel,
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700234 job.machine, started_time, None, [], {}, [], [])
jadmanskicc549172008-05-21 18:11:51 +0000235
236
jadmanski0afbb632008-06-06 21:10:57 +0000237 @staticmethod
238 def load_attributes(keyval_path):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700239 """
240 Load test attributes from a test keyval path.
241
242 Load the test attributes into a dictionary from a test
243 keyval path. Does not assume that the path actually exists.
244
245 @param keyval_path: The string path to a keyval file.
246
247 @return A dictionary representing the test keyvals.
248
249 """
jadmanski0afbb632008-06-06 21:10:57 +0000250 if not os.path.exists(keyval_path):
251 return {}
252 return utils.read_keyval(keyval_path)
jadmanskicc549172008-05-21 18:11:51 +0000253
254
jadmanskib591fba2008-09-10 16:19:22 +0000255 @staticmethod
Dan Shiba7b9812016-06-30 10:55:58 -0700256 def _parse_keyval(job_dir, sub_keyval_path):
257 """
258 Parse a file of keyvals.
259
260 @param job_dir: The string directory name of the associated job.
261 @param sub_keyval_path: Path to a keyval file relative to job_dir.
262
263 @return A dictionary representing the keyvals.
264
265 """
266 # The "real" job dir may be higher up in the directory tree.
267 job_dir = tko_utils.find_toplevel_job_dir(job_dir)
268 if not job_dir:
269 return {} # We can't find a top-level job dir with job keyvals.
270
271 # The keyval is <job_dir>/`sub_keyval_path` if it exists.
272 keyval_path = os.path.join(job_dir, sub_keyval_path)
273 if os.path.isfile(keyval_path):
274 return utils.read_keyval(keyval_path)
275 else:
276 return {}
277
278
279 @staticmethod
jadmanskib591fba2008-09-10 16:19:22 +0000280 def parse_host_keyval(job_dir, hostname):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700281 """
282 Parse host keyvals.
283
284 @param job_dir: The string directory name of the associated job.
285 @param hostname: The string hostname.
286
287 @return A dictionary representing the host keyvals.
288
289 """
Dan Shiba7b9812016-06-30 10:55:58 -0700290 # The host keyval is <job_dir>/host_keyvals/<hostname> if it exists.
291 return test._parse_keyval(job_dir,
292 os.path.join('host_keyvals', hostname))
jadmanskib591fba2008-09-10 16:19:22 +0000293
Dan Shiba7b9812016-06-30 10:55:58 -0700294
295 @staticmethod
296 def parse_job_keyval(job_dir):
297 """
298 Parse job keyvals.
299
300 @param job_dir: The string directory name of the associated job.
301
302 @return A dictionary representing the job keyvals.
303
304 """
305 # The job keyval is <job_dir>/keyval if it exists.
306 return test._parse_keyval(job_dir, 'keyval')
jadmanskib591fba2008-09-10 16:19:22 +0000307
308
mblighe79ebb02008-04-17 15:39:22 +0000309class patch(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700310 """Represents a patch."""
311
jadmanski0afbb632008-06-06 21:10:57 +0000312 def __init__(self, spec, reference, hash):
313 self.spec = spec
314 self.reference = reference
315 self.hash = hash
mblighe79ebb02008-04-17 15:39:22 +0000316
317
318class iteration(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700319 """Represents an iteration."""
320
jadmanski0afbb632008-06-06 21:10:57 +0000321 def __init__(self, index, attr_keyval, perf_keyval):
322 self.index = index
323 self.attr_keyval = attr_keyval
324 self.perf_keyval = perf_keyval
jadmanskicc549172008-05-21 18:11:51 +0000325
326
jadmanski0afbb632008-06-06 21:10:57 +0000327 @staticmethod
328 def parse_line_into_dicts(line, attr_dict, perf_dict):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700329 """
330 Abstract method to parse a keyval line and insert it into a dictionary.
331
332 @param line: The string line to parse.
333 @param attr_dict: Dictionary of generic iteration attributes.
334 @param perf_dict: Dictionary of iteration performance results.
335
jadmanski0afbb632008-06-06 21:10:57 +0000336 """
337 raise NotImplementedError
jadmanskicc549172008-05-21 18:11:51 +0000338
339
jadmanski0afbb632008-06-06 21:10:57 +0000340 @classmethod
341 def load_from_keyval(cls, keyval_path):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700342 """
343 Load a list of iterations from an iteration keyval file.
344
jadmanski0afbb632008-06-06 21:10:57 +0000345 Keyval data from separate iterations is separated by blank
346 lines. Makes use of the parse_line_into_dicts method to
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700347 actually parse the individual lines.
348
349 @param keyval_path: The string path to a keyval file.
350
351 @return A list of iteration objects.
352
353 """
jadmanski0afbb632008-06-06 21:10:57 +0000354 if not os.path.exists(keyval_path):
355 return []
jadmanskicc549172008-05-21 18:11:51 +0000356
jadmanski0afbb632008-06-06 21:10:57 +0000357 iterations = []
358 index = 1
359 attr, perf = {}, {}
360 for line in file(keyval_path):
361 line = line.strip()
362 if line:
363 cls.parse_line_into_dicts(line, attr, perf)
364 else:
365 iterations.append(cls(index, attr, perf))
366 index += 1
367 attr, perf = {}, {}
368 if attr or perf:
369 iterations.append(cls(index, attr, perf))
370 return iterations
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700371
372
373class perf_value_iteration(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700374 """Represents a perf value iteration."""
375
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700376 def __init__(self, index, perf_measurements):
377 """
378 Initializes the perf values for a particular test iteration.
379
380 @param index: The integer iteration number.
381 @param perf_measurements: A list of dictionaries, where each dictionary
382 contains the information for a measured perf metric from the
383 current iteration.
384
385 """
386 self.index = index
387 self.perf_measurements = perf_measurements
388
389
390 def add_measurement(self, measurement):
391 """
392 Appends to the list of perf measurements for this iteration.
393
394 @param measurement: A dictionary containing information for a measured
395 perf metric.
396
397 """
398 self.perf_measurements.append(measurement)
399
400
401 @staticmethod
402 def parse_line_into_dict(line):
403 """
404 Abstract method to parse an individual perf measurement line.
405
406 @param line: A string line from the perf measurement output file.
407
408 @return A dicionary representing the information for a measured perf
409 metric from one line of the perf measurement output file, or an
410 empty dictionary if the line cannot be parsed successfully.
411
412 """
413 raise NotImplementedError
414