blob: f8b0c94dc14667e300210eb7fe161e922cbf4707 [file] [log] [blame]
lmr6f80e7a2010-02-04 03:18:28 +00001import os
jadmanskicc549172008-05-21 18:11:51 +00002
3from autotest_lib.client.common_lib import utils
4from autotest_lib.tko import utils as tko_utils
jadmanski6e8bf752008-05-14 00:17:48 +00005
6
mblighe79ebb02008-04-17 15:39:22 +00007class job(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -07008 """Represents a job."""
9
jadmanskif7fa2cc2008-10-01 14:13:23 +000010 def __init__(self, dir, user, label, machine, queued_time, started_time,
showard71b94312009-08-20 23:40:02 +000011 finished_time, machine_owner, machine_group, aborted_by,
showardc1a98d12010-01-15 00:22:22 +000012 aborted_on, keyval_dict):
jadmanski0afbb632008-06-06 21:10:57 +000013 self.dir = dir
14 self.tests = []
15 self.user = user
16 self.label = label
17 self.machine = machine
18 self.queued_time = queued_time
19 self.started_time = started_time
20 self.finished_time = finished_time
21 self.machine_owner = machine_owner
showard71b94312009-08-20 23:40:02 +000022 self.machine_group = machine_group
jadmanskif7fa2cc2008-10-01 14:13:23 +000023 self.aborted_by = aborted_by
24 self.aborted_on = aborted_on
showardc1a98d12010-01-15 00:22:22 +000025 self.keyval_dict = keyval_dict
mblighe79ebb02008-04-17 15:39:22 +000026
27
jadmanskia8e302a2008-09-25 19:49:38 +000028 @staticmethod
29 def read_keyval(dir):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070030 """
31 Read job keyval files.
32
33 @param dir: String name of directory containing job keyval files.
34
35 @return A dictionary containing job keyvals.
36
37 """
jadmanskia8e302a2008-09-25 19:49:38 +000038 dir = os.path.normpath(dir)
39 top_dir = tko_utils.find_toplevel_job_dir(dir)
40 if not top_dir:
41 top_dir = dir
42 assert(dir.startswith(top_dir))
43
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070044 # Pull in and merge all the keyval files, with higher-level
45 # overriding values in the lower-level ones.
jadmanskia8e302a2008-09-25 19:49:38 +000046 keyval = {}
47 while True:
48 try:
jadmanskifc4e3382008-10-02 16:19:19 +000049 upper_keyval = utils.read_keyval(dir)
50 # HACK: exclude hostname from the override - this is a special
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070051 # case where we want lower to override higher.
52 if 'hostname' in upper_keyval and 'hostname' in keyval:
53 del upper_keyval['hostname']
jadmanski8e00afa2008-10-02 16:22:04 +000054 keyval.update(upper_keyval)
jadmanskia8e302a2008-09-25 19:49:38 +000055 except IOError:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070056 pass # If the keyval can't be read just move on to the next.
jadmanskia8e302a2008-09-25 19:49:38 +000057 if dir == top_dir:
58 break
59 else:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070060 assert(dir != '/')
jadmanskia8e302a2008-09-25 19:49:38 +000061 dir = os.path.dirname(dir)
62 return keyval
63
64
mblighe79ebb02008-04-17 15:39:22 +000065class kernel(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070066 """Represents a kernel."""
67
jadmanski0afbb632008-06-06 21:10:57 +000068 def __init__(self, base, patches, kernel_hash):
69 self.base = base
70 self.patches = patches
71 self.kernel_hash = kernel_hash
mblighe79ebb02008-04-17 15:39:22 +000072
73
jadmanski0afbb632008-06-06 21:10:57 +000074 @staticmethod
75 def compute_hash(base, hashes):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070076 """Compute a hash given the base string and hashes for each patch.
77
78 @param base: A string representing the kernel base.
79 @param hashes: A list of hashes, where each hash is associated with a
80 patch of this kernel.
81
82 @return A string representing the computed hash.
83
84 """
jadmanski0afbb632008-06-06 21:10:57 +000085 key_string = ','.join([base] + hashes)
lmr6f80e7a2010-02-04 03:18:28 +000086 return utils.hash('md5', key_string).hexdigest()
jadmanski6e8bf752008-05-14 00:17:48 +000087
88
mblighe79ebb02008-04-17 15:39:22 +000089class test(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070090 """Represents a test."""
91
jadmanski0afbb632008-06-06 21:10:57 +000092 def __init__(self, subdir, testname, status, reason, test_kernel,
93 machine, started_time, finished_time, iterations,
Dennis Jeffrey368c54b2013-07-24 11:19:03 -070094 attributes, perf_values, labels):
jadmanski0afbb632008-06-06 21:10:57 +000095 self.subdir = subdir
96 self.testname = testname
97 self.status = status
98 self.reason = reason
99 self.kernel = test_kernel
100 self.machine = machine
101 self.started_time = started_time
102 self.finished_time = finished_time
103 self.iterations = iterations
104 self.attributes = attributes
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700105 self.perf_values = perf_values
jadmanski9b6babf2009-04-21 17:57:40 +0000106 self.labels = labels
mblighe79ebb02008-04-17 15:39:22 +0000107
108
jadmanski0afbb632008-06-06 21:10:57 +0000109 @staticmethod
110 def load_iterations(keyval_path):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700111 """Abstract method to load a list of iterations from a keyval file.
112
113 @param keyval_path: String path to a keyval file.
114
115 @return A list of iteration objects.
116
117 """
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700118 raise NotImplementedError
119
120
121 @staticmethod
122 def load_perf_values(perf_values_file):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700123 """Loads perf values from a perf measurements file.
124
125 @param perf_values_file: The string path to a perf measurements file.
126
127 @return A list of perf_value_iteration objects.
128
129 """
jadmanski0afbb632008-06-06 21:10:57 +0000130 raise NotImplementedError
jadmanskicc549172008-05-21 18:11:51 +0000131
132
jadmanski0afbb632008-06-06 21:10:57 +0000133 @classmethod
134 def parse_test(cls, job, subdir, testname, status, reason, test_kernel,
jadmanski74eebf32008-07-15 20:04:42 +0000135 started_time, finished_time, existing_instance=None):
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700136 """
137 Parse test result files to construct a complete test instance.
138
139 Given a job and the basic metadata about the test that can be
140 extracted from the status logs, parse the test result files (keyval
141 files and perf measurement files) and use them to construct a complete
142 test instance.
143
144 @param job: A job object.
145 @param subdir: The string subdirectory name for the given test.
146 @param testname: The name of the test.
147 @param status: The status of the test.
148 @param reason: The reason string for the test.
149 @param test_kernel: The kernel of the test.
150 @param started_time: The start time of the test.
151 @param finished_time: The finish time of the test.
152 @param existing_instance: An existing test instance.
153
154 @return A test instance that has the complete information.
155
156 """
jadmanski0afbb632008-06-06 21:10:57 +0000157 tko_utils.dprint("parsing test %s %s" % (subdir, testname))
jadmanskicc549172008-05-21 18:11:51 +0000158
jadmanski0afbb632008-06-06 21:10:57 +0000159 if subdir:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700160 # Grab iterations from the results keyval.
jadmanski0afbb632008-06-06 21:10:57 +0000161 iteration_keyval = os.path.join(job.dir, subdir,
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700162 'results', 'keyval')
jadmanski0afbb632008-06-06 21:10:57 +0000163 iterations = cls.load_iterations(iteration_keyval)
jadmanskicc549172008-05-21 18:11:51 +0000164
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700165 # Grab perf values from the perf measurements file.
166 perf_values_file = os.path.join(job.dir, subdir,
167 'results', 'perf_measurements')
168 perf_values = cls.load_perf_values(perf_values_file)
169
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700170 # Grab test attributes from the subdir keyval.
171 test_keyval = os.path.join(job.dir, subdir, 'keyval')
jadmanski0afbb632008-06-06 21:10:57 +0000172 attributes = test.load_attributes(test_keyval)
173 else:
174 iterations = []
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700175 perf_values = []
jadmanski0afbb632008-06-06 21:10:57 +0000176 attributes = {}
jadmanskicc549172008-05-21 18:11:51 +0000177
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700178 # Grab test+host attributes from the host keyval.
showard71b94312009-08-20 23:40:02 +0000179 host_keyval = cls.parse_host_keyval(job.dir, job.machine)
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700180 attributes.update(dict(('host-%s' % k, v)
showard71b94312009-08-20 23:40:02 +0000181 for k, v in host_keyval.iteritems()))
jadmanskib591fba2008-09-10 16:19:22 +0000182
jadmanski74eebf32008-07-15 20:04:42 +0000183 if existing_instance:
184 def constructor(*args, **dargs):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700185 """Initializes an existing test instance."""
jadmanski74eebf32008-07-15 20:04:42 +0000186 existing_instance.__init__(*args, **dargs)
187 return existing_instance
188 else:
189 constructor = cls
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700190
jadmanski74eebf32008-07-15 20:04:42 +0000191 return constructor(subdir, testname, status, reason, test_kernel,
192 job.machine, started_time, finished_time,
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700193 iterations, attributes, perf_values, [])
jadmanski74eebf32008-07-15 20:04:42 +0000194
195
196 @classmethod
197 def parse_partial_test(cls, job, subdir, testname, reason, test_kernel,
198 started_time):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700199 """
200 Create a test instance representing a partial test result.
201
202 Given a job and the basic metadata available when a test is
jadmanski74eebf32008-07-15 20:04:42 +0000203 started, create a test instance representing the partial result.
204 Assume that since the test is not complete there are no results files
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700205 actually available for parsing.
jadmanski74eebf32008-07-15 20:04:42 +0000206
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700207 @param job: A job object.
208 @param subdir: The string subdirectory name for the given test.
209 @param testname: The name of the test.
210 @param reason: The reason string for the test.
211 @param test_kernel: The kernel of the test.
212 @param started_time: The start time of the test.
213
214 @return A test instance that has partial test information.
215
216 """
217 tko_utils.dprint('parsing partial test %s %s' % (subdir, testname))
218
219 return cls(subdir, testname, 'RUNNING', reason, test_kernel,
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700220 job.machine, started_time, None, [], {}, [], [])
jadmanskicc549172008-05-21 18:11:51 +0000221
222
jadmanski0afbb632008-06-06 21:10:57 +0000223 @staticmethod
224 def load_attributes(keyval_path):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700225 """
226 Load test attributes from a test keyval path.
227
228 Load the test attributes into a dictionary from a test
229 keyval path. Does not assume that the path actually exists.
230
231 @param keyval_path: The string path to a keyval file.
232
233 @return A dictionary representing the test keyvals.
234
235 """
jadmanski0afbb632008-06-06 21:10:57 +0000236 if not os.path.exists(keyval_path):
237 return {}
238 return utils.read_keyval(keyval_path)
jadmanskicc549172008-05-21 18:11:51 +0000239
240
jadmanskib591fba2008-09-10 16:19:22 +0000241 @staticmethod
242 def parse_host_keyval(job_dir, hostname):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700243 """
244 Parse host keyvals.
245
246 @param job_dir: The string directory name of the associated job.
247 @param hostname: The string hostname.
248
249 @return A dictionary representing the host keyvals.
250
251 """
252 # The "real" job dir may be higher up in the directory tree.
jadmanskia8e302a2008-09-25 19:49:38 +0000253 job_dir = tko_utils.find_toplevel_job_dir(job_dir)
254 if not job_dir:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700255 return {} # We can't find a top-level job dir with host keyvals.
jadmanskib591fba2008-09-10 16:19:22 +0000256
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700257 # The keyval is <job_dir>/host_keyvals/<hostname> if it exists.
258 keyval_path = os.path.join(job_dir, 'host_keyvals', hostname)
jadmanskib591fba2008-09-10 16:19:22 +0000259 if os.path.isfile(keyval_path):
showard71b94312009-08-20 23:40:02 +0000260 return utils.read_keyval(keyval_path)
jadmanskib591fba2008-09-10 16:19:22 +0000261 else:
262 return {}
263
264
mblighe79ebb02008-04-17 15:39:22 +0000265class patch(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700266 """Represents a patch."""
267
jadmanski0afbb632008-06-06 21:10:57 +0000268 def __init__(self, spec, reference, hash):
269 self.spec = spec
270 self.reference = reference
271 self.hash = hash
mblighe79ebb02008-04-17 15:39:22 +0000272
273
274class iteration(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700275 """Represents an iteration."""
276
jadmanski0afbb632008-06-06 21:10:57 +0000277 def __init__(self, index, attr_keyval, perf_keyval):
278 self.index = index
279 self.attr_keyval = attr_keyval
280 self.perf_keyval = perf_keyval
jadmanskicc549172008-05-21 18:11:51 +0000281
282
jadmanski0afbb632008-06-06 21:10:57 +0000283 @staticmethod
284 def parse_line_into_dicts(line, attr_dict, perf_dict):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700285 """
286 Abstract method to parse a keyval line and insert it into a dictionary.
287
288 @param line: The string line to parse.
289 @param attr_dict: Dictionary of generic iteration attributes.
290 @param perf_dict: Dictionary of iteration performance results.
291
jadmanski0afbb632008-06-06 21:10:57 +0000292 """
293 raise NotImplementedError
jadmanskicc549172008-05-21 18:11:51 +0000294
295
jadmanski0afbb632008-06-06 21:10:57 +0000296 @classmethod
297 def load_from_keyval(cls, keyval_path):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700298 """
299 Load a list of iterations from an iteration keyval file.
300
jadmanski0afbb632008-06-06 21:10:57 +0000301 Keyval data from separate iterations is separated by blank
302 lines. Makes use of the parse_line_into_dicts method to
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700303 actually parse the individual lines.
304
305 @param keyval_path: The string path to a keyval file.
306
307 @return A list of iteration objects.
308
309 """
jadmanski0afbb632008-06-06 21:10:57 +0000310 if not os.path.exists(keyval_path):
311 return []
jadmanskicc549172008-05-21 18:11:51 +0000312
jadmanski0afbb632008-06-06 21:10:57 +0000313 iterations = []
314 index = 1
315 attr, perf = {}, {}
316 for line in file(keyval_path):
317 line = line.strip()
318 if line:
319 cls.parse_line_into_dicts(line, attr, perf)
320 else:
321 iterations.append(cls(index, attr, perf))
322 index += 1
323 attr, perf = {}, {}
324 if attr or perf:
325 iterations.append(cls(index, attr, perf))
326 return iterations
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700327
328
329class perf_value_iteration(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700330 """Represents a perf value iteration."""
331
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700332 def __init__(self, index, perf_measurements):
333 """
334 Initializes the perf values for a particular test iteration.
335
336 @param index: The integer iteration number.
337 @param perf_measurements: A list of dictionaries, where each dictionary
338 contains the information for a measured perf metric from the
339 current iteration.
340
341 """
342 self.index = index
343 self.perf_measurements = perf_measurements
344
345
346 def add_measurement(self, measurement):
347 """
348 Appends to the list of perf measurements for this iteration.
349
350 @param measurement: A dictionary containing information for a measured
351 perf metric.
352
353 """
354 self.perf_measurements.append(measurement)
355
356
357 @staticmethod
358 def parse_line_into_dict(line):
359 """
360 Abstract method to parse an individual perf measurement line.
361
362 @param line: A string line from the perf measurement output file.
363
364 @return A dicionary representing the information for a measured perf
365 metric from one line of the perf measurement output file, or an
366 empty dictionary if the line cannot be parsed successfully.
367
368 """
369 raise NotImplementedError
370
371
372 @classmethod
373 def load_from_perf_values_file(cls, perf_values_file):
374 """
375 Load perf values from each iteration in a perf measurements file.
376
377 Multiple measurements for the same perf metric description are assumed
378 to come from different iterations. Makes use of the
379 parse_line_into_dict function to actually parse the individual lines.
380
381 @param perf_values_file: The string name of the output file containing
382 perf measurements.
383
384 @return A list of |perf_value_iteration| objects, where position 0 of
385 the list contains the object representing the first iteration,
386 position 1 contains the object representing the second iteration,
387 and so forth.
388
389 """
390 if not os.path.exists(perf_values_file):
391 return []
392
393 perf_value_iterations = []
394 # For each description string representing a unique perf metric, keep
395 # track of the next iteration that it belongs to (multiple occurrences
396 # of the same description are assumed to come from different
397 # iterations).
398 desc_to_next_iter = {}
399 with open(perf_values_file) as fp:
400 for line in [ln for ln in fp if ln.strip()]:
401 perf_value_dict = cls.parse_line_into_dict(line)
402 if not perf_value_dict:
403 continue
404 desc = perf_value_dict['description']
405 iter_to_set = desc_to_next_iter.setdefault(desc, 1)
406 desc_to_next_iter[desc] = iter_to_set + 1
407 if iter_to_set > len(perf_value_iterations):
408 # We have information that needs to go into a new
409 # |perf_value_iteration| object.
410 perf_value_iterations.append(cls(iter_to_set, []))
411 # Add the perf measurement to the appropriate
412 # |perf_value_iteration| object.
413 perf_value_iterations[iter_to_set - 1].add_measurement(
414 perf_value_dict)
415 return perf_value_iterations