blob: b92a878b21f62eff15f1fc7df71065ad1e2bcb2d [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
mblighe79ebb02008-04-17 15:39:22 +000031
32
jadmanskia8e302a2008-09-25 19:49:38 +000033 @staticmethod
34 def read_keyval(dir):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070035 """
36 Read job keyval files.
37
38 @param dir: String name of directory containing job keyval files.
39
40 @return A dictionary containing job keyvals.
41
42 """
jadmanskia8e302a2008-09-25 19:49:38 +000043 dir = os.path.normpath(dir)
44 top_dir = tko_utils.find_toplevel_job_dir(dir)
45 if not top_dir:
46 top_dir = dir
47 assert(dir.startswith(top_dir))
48
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070049 # Pull in and merge all the keyval files, with higher-level
50 # overriding values in the lower-level ones.
jadmanskia8e302a2008-09-25 19:49:38 +000051 keyval = {}
52 while True:
53 try:
jadmanskifc4e3382008-10-02 16:19:19 +000054 upper_keyval = utils.read_keyval(dir)
55 # HACK: exclude hostname from the override - this is a special
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070056 # case where we want lower to override higher.
57 if 'hostname' in upper_keyval and 'hostname' in keyval:
58 del upper_keyval['hostname']
jadmanski8e00afa2008-10-02 16:22:04 +000059 keyval.update(upper_keyval)
jadmanskia8e302a2008-09-25 19:49:38 +000060 except IOError:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070061 pass # If the keyval can't be read just move on to the next.
jadmanskia8e302a2008-09-25 19:49:38 +000062 if dir == top_dir:
63 break
64 else:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070065 assert(dir != '/')
jadmanskia8e302a2008-09-25 19:49:38 +000066 dir = os.path.dirname(dir)
67 return keyval
68
69
mblighe79ebb02008-04-17 15:39:22 +000070class kernel(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070071 """Represents a kernel."""
72
jadmanski0afbb632008-06-06 21:10:57 +000073 def __init__(self, base, patches, kernel_hash):
74 self.base = base
75 self.patches = patches
76 self.kernel_hash = kernel_hash
mblighe79ebb02008-04-17 15:39:22 +000077
78
jadmanski0afbb632008-06-06 21:10:57 +000079 @staticmethod
80 def compute_hash(base, hashes):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070081 """Compute a hash given the base string and hashes for each patch.
82
83 @param base: A string representing the kernel base.
84 @param hashes: A list of hashes, where each hash is associated with a
85 patch of this kernel.
86
87 @return A string representing the computed hash.
88
89 """
jadmanski0afbb632008-06-06 21:10:57 +000090 key_string = ','.join([base] + hashes)
lmr6f80e7a2010-02-04 03:18:28 +000091 return utils.hash('md5', key_string).hexdigest()
jadmanski6e8bf752008-05-14 00:17:48 +000092
93
mblighe79ebb02008-04-17 15:39:22 +000094class test(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070095 """Represents a test."""
96
jadmanski0afbb632008-06-06 21:10:57 +000097 def __init__(self, subdir, testname, status, reason, test_kernel,
98 machine, started_time, finished_time, iterations,
Dennis Jeffrey368c54b2013-07-24 11:19:03 -070099 attributes, perf_values, labels):
jadmanski0afbb632008-06-06 21:10:57 +0000100 self.subdir = subdir
101 self.testname = testname
102 self.status = status
103 self.reason = reason
104 self.kernel = test_kernel
105 self.machine = machine
106 self.started_time = started_time
107 self.finished_time = finished_time
108 self.iterations = iterations
109 self.attributes = attributes
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700110 self.perf_values = perf_values
jadmanski9b6babf2009-04-21 17:57:40 +0000111 self.labels = labels
mblighe79ebb02008-04-17 15:39:22 +0000112
113
jadmanski0afbb632008-06-06 21:10:57 +0000114 @staticmethod
115 def load_iterations(keyval_path):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700116 """Abstract method to load a list of iterations from a keyval file.
117
118 @param keyval_path: String path to a keyval file.
119
120 @return A list of iteration objects.
121
122 """
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700123 raise NotImplementedError
124
125
126 @staticmethod
127 def load_perf_values(perf_values_file):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700128 """Loads perf values from a perf measurements file.
129
130 @param perf_values_file: The string path to a perf measurements file.
131
132 @return A list of perf_value_iteration objects.
133
134 """
jadmanski0afbb632008-06-06 21:10:57 +0000135 raise NotImplementedError
jadmanskicc549172008-05-21 18:11:51 +0000136
137
jadmanski0afbb632008-06-06 21:10:57 +0000138 @classmethod
139 def parse_test(cls, job, subdir, testname, status, reason, test_kernel,
jadmanski74eebf32008-07-15 20:04:42 +0000140 started_time, finished_time, existing_instance=None):
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700141 """
142 Parse test result files to construct a complete test instance.
143
144 Given a job and the basic metadata about the test that can be
145 extracted from the status logs, parse the test result files (keyval
146 files and perf measurement files) and use them to construct a complete
147 test instance.
148
149 @param job: A job object.
150 @param subdir: The string subdirectory name for the given test.
151 @param testname: The name of the test.
152 @param status: The status of the test.
153 @param reason: The reason string for the test.
154 @param test_kernel: The kernel of the test.
155 @param started_time: The start time of the test.
156 @param finished_time: The finish time of the test.
157 @param existing_instance: An existing test instance.
158
159 @return A test instance that has the complete information.
160
161 """
jadmanski0afbb632008-06-06 21:10:57 +0000162 tko_utils.dprint("parsing test %s %s" % (subdir, testname))
jadmanskicc549172008-05-21 18:11:51 +0000163
jadmanski0afbb632008-06-06 21:10:57 +0000164 if subdir:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700165 # Grab iterations from the results keyval.
jadmanski0afbb632008-06-06 21:10:57 +0000166 iteration_keyval = os.path.join(job.dir, subdir,
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700167 'results', 'keyval')
jadmanski0afbb632008-06-06 21:10:57 +0000168 iterations = cls.load_iterations(iteration_keyval)
jadmanskicc549172008-05-21 18:11:51 +0000169
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700170 # Grab perf values from the perf measurements file.
171 perf_values_file = os.path.join(job.dir, subdir,
Keith Haddow1e5c7012016-03-09 16:05:37 -0800172 'results', 'results-chart.json')
173 perf_values = {}
174 if os.path.exists(perf_values_file):
175 with open(perf_values_file, 'r') as fp:
176 contents = fp.read()
177 if contents:
178 perf_values = json.loads(contents)
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700179
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700180 # Grab test attributes from the subdir keyval.
181 test_keyval = os.path.join(job.dir, subdir, 'keyval')
jadmanski0afbb632008-06-06 21:10:57 +0000182 attributes = test.load_attributes(test_keyval)
183 else:
184 iterations = []
Keith Haddow1e5c7012016-03-09 16:05:37 -0800185 perf_values = {}
jadmanski0afbb632008-06-06 21:10:57 +0000186 attributes = {}
jadmanskicc549172008-05-21 18:11:51 +0000187
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700188 # Grab test+host attributes from the host keyval.
showard71b94312009-08-20 23:40:02 +0000189 host_keyval = cls.parse_host_keyval(job.dir, job.machine)
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700190 attributes.update(dict(('host-%s' % k, v)
showard71b94312009-08-20 23:40:02 +0000191 for k, v in host_keyval.iteritems()))
jadmanskib591fba2008-09-10 16:19:22 +0000192
jadmanski74eebf32008-07-15 20:04:42 +0000193 if existing_instance:
194 def constructor(*args, **dargs):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700195 """Initializes an existing test instance."""
jadmanski74eebf32008-07-15 20:04:42 +0000196 existing_instance.__init__(*args, **dargs)
197 return existing_instance
198 else:
199 constructor = cls
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700200
jadmanski74eebf32008-07-15 20:04:42 +0000201 return constructor(subdir, testname, status, reason, test_kernel,
202 job.machine, started_time, finished_time,
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700203 iterations, attributes, perf_values, [])
jadmanski74eebf32008-07-15 20:04:42 +0000204
205
206 @classmethod
207 def parse_partial_test(cls, job, subdir, testname, reason, test_kernel,
208 started_time):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700209 """
210 Create a test instance representing a partial test result.
211
212 Given a job and the basic metadata available when a test is
jadmanski74eebf32008-07-15 20:04:42 +0000213 started, create a test instance representing the partial result.
214 Assume that since the test is not complete there are no results files
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700215 actually available for parsing.
jadmanski74eebf32008-07-15 20:04:42 +0000216
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700217 @param job: A job object.
218 @param subdir: The string subdirectory name for the given test.
219 @param testname: The name of the test.
220 @param reason: The reason string for the test.
221 @param test_kernel: The kernel of the test.
222 @param started_time: The start time of the test.
223
224 @return A test instance that has partial test information.
225
226 """
227 tko_utils.dprint('parsing partial test %s %s' % (subdir, testname))
228
229 return cls(subdir, testname, 'RUNNING', reason, test_kernel,
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700230 job.machine, started_time, None, [], {}, [], [])
jadmanskicc549172008-05-21 18:11:51 +0000231
232
jadmanski0afbb632008-06-06 21:10:57 +0000233 @staticmethod
234 def load_attributes(keyval_path):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700235 """
236 Load test attributes from a test keyval path.
237
238 Load the test attributes into a dictionary from a test
239 keyval path. Does not assume that the path actually exists.
240
241 @param keyval_path: The string path to a keyval file.
242
243 @return A dictionary representing the test keyvals.
244
245 """
jadmanski0afbb632008-06-06 21:10:57 +0000246 if not os.path.exists(keyval_path):
247 return {}
248 return utils.read_keyval(keyval_path)
jadmanskicc549172008-05-21 18:11:51 +0000249
250
jadmanskib591fba2008-09-10 16:19:22 +0000251 @staticmethod
Dan Shiba7b9812016-06-30 10:55:58 -0700252 def _parse_keyval(job_dir, sub_keyval_path):
253 """
254 Parse a file of keyvals.
255
256 @param job_dir: The string directory name of the associated job.
257 @param sub_keyval_path: Path to a keyval file relative to job_dir.
258
259 @return A dictionary representing the keyvals.
260
261 """
262 # The "real" job dir may be higher up in the directory tree.
263 job_dir = tko_utils.find_toplevel_job_dir(job_dir)
264 if not job_dir:
265 return {} # We can't find a top-level job dir with job keyvals.
266
267 # The keyval is <job_dir>/`sub_keyval_path` if it exists.
268 keyval_path = os.path.join(job_dir, sub_keyval_path)
269 if os.path.isfile(keyval_path):
270 return utils.read_keyval(keyval_path)
271 else:
272 return {}
273
274
275 @staticmethod
jadmanskib591fba2008-09-10 16:19:22 +0000276 def parse_host_keyval(job_dir, hostname):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700277 """
278 Parse host keyvals.
279
280 @param job_dir: The string directory name of the associated job.
281 @param hostname: The string hostname.
282
283 @return A dictionary representing the host keyvals.
284
285 """
Dan Shiba7b9812016-06-30 10:55:58 -0700286 # The host keyval is <job_dir>/host_keyvals/<hostname> if it exists.
287 return test._parse_keyval(job_dir,
288 os.path.join('host_keyvals', hostname))
jadmanskib591fba2008-09-10 16:19:22 +0000289
Dan Shiba7b9812016-06-30 10:55:58 -0700290
291 @staticmethod
292 def parse_job_keyval(job_dir):
293 """
294 Parse job keyvals.
295
296 @param job_dir: The string directory name of the associated job.
297
298 @return A dictionary representing the job keyvals.
299
300 """
301 # The job keyval is <job_dir>/keyval if it exists.
302 return test._parse_keyval(job_dir, 'keyval')
jadmanskib591fba2008-09-10 16:19:22 +0000303
304
mblighe79ebb02008-04-17 15:39:22 +0000305class patch(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700306 """Represents a patch."""
307
jadmanski0afbb632008-06-06 21:10:57 +0000308 def __init__(self, spec, reference, hash):
309 self.spec = spec
310 self.reference = reference
311 self.hash = hash
mblighe79ebb02008-04-17 15:39:22 +0000312
313
314class iteration(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700315 """Represents an iteration."""
316
jadmanski0afbb632008-06-06 21:10:57 +0000317 def __init__(self, index, attr_keyval, perf_keyval):
318 self.index = index
319 self.attr_keyval = attr_keyval
320 self.perf_keyval = perf_keyval
jadmanskicc549172008-05-21 18:11:51 +0000321
322
jadmanski0afbb632008-06-06 21:10:57 +0000323 @staticmethod
324 def parse_line_into_dicts(line, attr_dict, perf_dict):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700325 """
326 Abstract method to parse a keyval line and insert it into a dictionary.
327
328 @param line: The string line to parse.
329 @param attr_dict: Dictionary of generic iteration attributes.
330 @param perf_dict: Dictionary of iteration performance results.
331
jadmanski0afbb632008-06-06 21:10:57 +0000332 """
333 raise NotImplementedError
jadmanskicc549172008-05-21 18:11:51 +0000334
335
jadmanski0afbb632008-06-06 21:10:57 +0000336 @classmethod
337 def load_from_keyval(cls, keyval_path):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700338 """
339 Load a list of iterations from an iteration keyval file.
340
jadmanski0afbb632008-06-06 21:10:57 +0000341 Keyval data from separate iterations is separated by blank
342 lines. Makes use of the parse_line_into_dicts method to
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700343 actually parse the individual lines.
344
345 @param keyval_path: The string path to a keyval file.
346
347 @return A list of iteration objects.
348
349 """
jadmanski0afbb632008-06-06 21:10:57 +0000350 if not os.path.exists(keyval_path):
351 return []
jadmanskicc549172008-05-21 18:11:51 +0000352
jadmanski0afbb632008-06-06 21:10:57 +0000353 iterations = []
354 index = 1
355 attr, perf = {}, {}
356 for line in file(keyval_path):
357 line = line.strip()
358 if line:
359 cls.parse_line_into_dicts(line, attr, perf)
360 else:
361 iterations.append(cls(index, attr, perf))
362 index += 1
363 attr, perf = {}, {}
364 if attr or perf:
365 iterations.append(cls(index, attr, perf))
366 return iterations
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700367
368
369class perf_value_iteration(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700370 """Represents a perf value iteration."""
371
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700372 def __init__(self, index, perf_measurements):
373 """
374 Initializes the perf values for a particular test iteration.
375
376 @param index: The integer iteration number.
377 @param perf_measurements: A list of dictionaries, where each dictionary
378 contains the information for a measured perf metric from the
379 current iteration.
380
381 """
382 self.index = index
383 self.perf_measurements = perf_measurements
384
385
386 def add_measurement(self, measurement):
387 """
388 Appends to the list of perf measurements for this iteration.
389
390 @param measurement: A dictionary containing information for a measured
391 perf metric.
392
393 """
394 self.perf_measurements.append(measurement)
395
396
397 @staticmethod
398 def parse_line_into_dict(line):
399 """
400 Abstract method to parse an individual perf measurement line.
401
402 @param line: A string line from the perf measurement output file.
403
404 @return A dicionary representing the information for a measured perf
405 metric from one line of the perf measurement output file, or an
406 empty dictionary if the line cannot be parsed successfully.
407
408 """
409 raise NotImplementedError
410