blob: 4ae922c9a8ee313d516d73b71fc6d4efd4286dff [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
mblighe79ebb02008-04-17 15:39:22 +000027
28
jadmanskia8e302a2008-09-25 19:49:38 +000029 @staticmethod
30 def read_keyval(dir):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070031 """
32 Read job keyval files.
33
34 @param dir: String name of directory containing job keyval files.
35
36 @return A dictionary containing job keyvals.
37
38 """
jadmanskia8e302a2008-09-25 19:49:38 +000039 dir = os.path.normpath(dir)
40 top_dir = tko_utils.find_toplevel_job_dir(dir)
41 if not top_dir:
42 top_dir = dir
43 assert(dir.startswith(top_dir))
44
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070045 # Pull in and merge all the keyval files, with higher-level
46 # overriding values in the lower-level ones.
jadmanskia8e302a2008-09-25 19:49:38 +000047 keyval = {}
48 while True:
49 try:
jadmanskifc4e3382008-10-02 16:19:19 +000050 upper_keyval = utils.read_keyval(dir)
51 # HACK: exclude hostname from the override - this is a special
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070052 # case where we want lower to override higher.
53 if 'hostname' in upper_keyval and 'hostname' in keyval:
54 del upper_keyval['hostname']
jadmanski8e00afa2008-10-02 16:22:04 +000055 keyval.update(upper_keyval)
jadmanskia8e302a2008-09-25 19:49:38 +000056 except IOError:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070057 pass # If the keyval can't be read just move on to the next.
jadmanskia8e302a2008-09-25 19:49:38 +000058 if dir == top_dir:
59 break
60 else:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070061 assert(dir != '/')
jadmanskia8e302a2008-09-25 19:49:38 +000062 dir = os.path.dirname(dir)
63 return keyval
64
65
mblighe79ebb02008-04-17 15:39:22 +000066class kernel(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070067 """Represents a kernel."""
68
jadmanski0afbb632008-06-06 21:10:57 +000069 def __init__(self, base, patches, kernel_hash):
70 self.base = base
71 self.patches = patches
72 self.kernel_hash = kernel_hash
mblighe79ebb02008-04-17 15:39:22 +000073
74
jadmanski0afbb632008-06-06 21:10:57 +000075 @staticmethod
76 def compute_hash(base, hashes):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070077 """Compute a hash given the base string and hashes for each patch.
78
79 @param base: A string representing the kernel base.
80 @param hashes: A list of hashes, where each hash is associated with a
81 patch of this kernel.
82
83 @return A string representing the computed hash.
84
85 """
jadmanski0afbb632008-06-06 21:10:57 +000086 key_string = ','.join([base] + hashes)
lmr6f80e7a2010-02-04 03:18:28 +000087 return utils.hash('md5', key_string).hexdigest()
jadmanski6e8bf752008-05-14 00:17:48 +000088
89
mblighe79ebb02008-04-17 15:39:22 +000090class test(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070091 """Represents a test."""
92
jadmanski0afbb632008-06-06 21:10:57 +000093 def __init__(self, subdir, testname, status, reason, test_kernel,
94 machine, started_time, finished_time, iterations,
Dennis Jeffrey368c54b2013-07-24 11:19:03 -070095 attributes, perf_values, labels):
jadmanski0afbb632008-06-06 21:10:57 +000096 self.subdir = subdir
97 self.testname = testname
98 self.status = status
99 self.reason = reason
100 self.kernel = test_kernel
101 self.machine = machine
102 self.started_time = started_time
103 self.finished_time = finished_time
104 self.iterations = iterations
105 self.attributes = attributes
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700106 self.perf_values = perf_values
jadmanski9b6babf2009-04-21 17:57:40 +0000107 self.labels = labels
mblighe79ebb02008-04-17 15:39:22 +0000108
109
jadmanski0afbb632008-06-06 21:10:57 +0000110 @staticmethod
111 def load_iterations(keyval_path):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700112 """Abstract method to load a list of iterations from a keyval file.
113
114 @param keyval_path: String path to a keyval file.
115
116 @return A list of iteration objects.
117
118 """
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700119 raise NotImplementedError
120
121
122 @staticmethod
123 def load_perf_values(perf_values_file):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700124 """Loads perf values from a perf measurements file.
125
126 @param perf_values_file: The string path to a perf measurements file.
127
128 @return A list of perf_value_iteration objects.
129
130 """
jadmanski0afbb632008-06-06 21:10:57 +0000131 raise NotImplementedError
jadmanskicc549172008-05-21 18:11:51 +0000132
133
jadmanski0afbb632008-06-06 21:10:57 +0000134 @classmethod
135 def parse_test(cls, job, subdir, testname, status, reason, test_kernel,
jadmanski74eebf32008-07-15 20:04:42 +0000136 started_time, finished_time, existing_instance=None):
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700137 """
138 Parse test result files to construct a complete test instance.
139
140 Given a job and the basic metadata about the test that can be
141 extracted from the status logs, parse the test result files (keyval
142 files and perf measurement files) and use them to construct a complete
143 test instance.
144
145 @param job: A job object.
146 @param subdir: The string subdirectory name for the given test.
147 @param testname: The name of the test.
148 @param status: The status of the test.
149 @param reason: The reason string for the test.
150 @param test_kernel: The kernel of the test.
151 @param started_time: The start time of the test.
152 @param finished_time: The finish time of the test.
153 @param existing_instance: An existing test instance.
154
155 @return A test instance that has the complete information.
156
157 """
jadmanski0afbb632008-06-06 21:10:57 +0000158 tko_utils.dprint("parsing test %s %s" % (subdir, testname))
jadmanskicc549172008-05-21 18:11:51 +0000159
jadmanski0afbb632008-06-06 21:10:57 +0000160 if subdir:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700161 # Grab iterations from the results keyval.
jadmanski0afbb632008-06-06 21:10:57 +0000162 iteration_keyval = os.path.join(job.dir, subdir,
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700163 'results', 'keyval')
jadmanski0afbb632008-06-06 21:10:57 +0000164 iterations = cls.load_iterations(iteration_keyval)
jadmanskicc549172008-05-21 18:11:51 +0000165
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700166 # Grab perf values from the perf measurements file.
167 perf_values_file = os.path.join(job.dir, subdir,
Keith Haddow1e5c7012016-03-09 16:05:37 -0800168 'results', 'results-chart.json')
169 perf_values = {}
170 if os.path.exists(perf_values_file):
171 with open(perf_values_file, 'r') as fp:
172 contents = fp.read()
173 if contents:
174 perf_values = json.loads(contents)
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700175
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700176 # Grab test attributes from the subdir keyval.
177 test_keyval = os.path.join(job.dir, subdir, 'keyval')
jadmanski0afbb632008-06-06 21:10:57 +0000178 attributes = test.load_attributes(test_keyval)
179 else:
180 iterations = []
Keith Haddow1e5c7012016-03-09 16:05:37 -0800181 perf_values = {}
jadmanski0afbb632008-06-06 21:10:57 +0000182 attributes = {}
jadmanskicc549172008-05-21 18:11:51 +0000183
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700184 # Grab test+host attributes from the host keyval.
showard71b94312009-08-20 23:40:02 +0000185 host_keyval = cls.parse_host_keyval(job.dir, job.machine)
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700186 attributes.update(dict(('host-%s' % k, v)
showard71b94312009-08-20 23:40:02 +0000187 for k, v in host_keyval.iteritems()))
jadmanskib591fba2008-09-10 16:19:22 +0000188
jadmanski74eebf32008-07-15 20:04:42 +0000189 if existing_instance:
190 def constructor(*args, **dargs):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700191 """Initializes an existing test instance."""
jadmanski74eebf32008-07-15 20:04:42 +0000192 existing_instance.__init__(*args, **dargs)
193 return existing_instance
194 else:
195 constructor = cls
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700196
jadmanski74eebf32008-07-15 20:04:42 +0000197 return constructor(subdir, testname, status, reason, test_kernel,
198 job.machine, started_time, finished_time,
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700199 iterations, attributes, perf_values, [])
jadmanski74eebf32008-07-15 20:04:42 +0000200
201
202 @classmethod
203 def parse_partial_test(cls, job, subdir, testname, reason, test_kernel,
204 started_time):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700205 """
206 Create a test instance representing a partial test result.
207
208 Given a job and the basic metadata available when a test is
jadmanski74eebf32008-07-15 20:04:42 +0000209 started, create a test instance representing the partial result.
210 Assume that since the test is not complete there are no results files
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700211 actually available for parsing.
jadmanski74eebf32008-07-15 20:04:42 +0000212
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700213 @param job: A job object.
214 @param subdir: The string subdirectory name for the given test.
215 @param testname: The name of the test.
216 @param reason: The reason string for the test.
217 @param test_kernel: The kernel of the test.
218 @param started_time: The start time of the test.
219
220 @return A test instance that has partial test information.
221
222 """
223 tko_utils.dprint('parsing partial test %s %s' % (subdir, testname))
224
225 return cls(subdir, testname, 'RUNNING', reason, test_kernel,
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700226 job.machine, started_time, None, [], {}, [], [])
jadmanskicc549172008-05-21 18:11:51 +0000227
228
jadmanski0afbb632008-06-06 21:10:57 +0000229 @staticmethod
230 def load_attributes(keyval_path):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700231 """
232 Load test attributes from a test keyval path.
233
234 Load the test attributes into a dictionary from a test
235 keyval path. Does not assume that the path actually exists.
236
237 @param keyval_path: The string path to a keyval file.
238
239 @return A dictionary representing the test keyvals.
240
241 """
jadmanski0afbb632008-06-06 21:10:57 +0000242 if not os.path.exists(keyval_path):
243 return {}
244 return utils.read_keyval(keyval_path)
jadmanskicc549172008-05-21 18:11:51 +0000245
246
jadmanskib591fba2008-09-10 16:19:22 +0000247 @staticmethod
248 def parse_host_keyval(job_dir, hostname):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700249 """
250 Parse host keyvals.
251
252 @param job_dir: The string directory name of the associated job.
253 @param hostname: The string hostname.
254
255 @return A dictionary representing the host keyvals.
256
257 """
258 # The "real" job dir may be higher up in the directory tree.
jadmanskia8e302a2008-09-25 19:49:38 +0000259 job_dir = tko_utils.find_toplevel_job_dir(job_dir)
260 if not job_dir:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700261 return {} # We can't find a top-level job dir with host keyvals.
jadmanskib591fba2008-09-10 16:19:22 +0000262
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700263 # The keyval is <job_dir>/host_keyvals/<hostname> if it exists.
264 keyval_path = os.path.join(job_dir, 'host_keyvals', hostname)
jadmanskib591fba2008-09-10 16:19:22 +0000265 if os.path.isfile(keyval_path):
showard71b94312009-08-20 23:40:02 +0000266 return utils.read_keyval(keyval_path)
jadmanskib591fba2008-09-10 16:19:22 +0000267 else:
268 return {}
269
270
mblighe79ebb02008-04-17 15:39:22 +0000271class patch(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700272 """Represents a patch."""
273
jadmanski0afbb632008-06-06 21:10:57 +0000274 def __init__(self, spec, reference, hash):
275 self.spec = spec
276 self.reference = reference
277 self.hash = hash
mblighe79ebb02008-04-17 15:39:22 +0000278
279
280class iteration(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700281 """Represents an iteration."""
282
jadmanski0afbb632008-06-06 21:10:57 +0000283 def __init__(self, index, attr_keyval, perf_keyval):
284 self.index = index
285 self.attr_keyval = attr_keyval
286 self.perf_keyval = perf_keyval
jadmanskicc549172008-05-21 18:11:51 +0000287
288
jadmanski0afbb632008-06-06 21:10:57 +0000289 @staticmethod
290 def parse_line_into_dicts(line, attr_dict, perf_dict):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700291 """
292 Abstract method to parse a keyval line and insert it into a dictionary.
293
294 @param line: The string line to parse.
295 @param attr_dict: Dictionary of generic iteration attributes.
296 @param perf_dict: Dictionary of iteration performance results.
297
jadmanski0afbb632008-06-06 21:10:57 +0000298 """
299 raise NotImplementedError
jadmanskicc549172008-05-21 18:11:51 +0000300
301
jadmanski0afbb632008-06-06 21:10:57 +0000302 @classmethod
303 def load_from_keyval(cls, keyval_path):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700304 """
305 Load a list of iterations from an iteration keyval file.
306
jadmanski0afbb632008-06-06 21:10:57 +0000307 Keyval data from separate iterations is separated by blank
308 lines. Makes use of the parse_line_into_dicts method to
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700309 actually parse the individual lines.
310
311 @param keyval_path: The string path to a keyval file.
312
313 @return A list of iteration objects.
314
315 """
jadmanski0afbb632008-06-06 21:10:57 +0000316 if not os.path.exists(keyval_path):
317 return []
jadmanskicc549172008-05-21 18:11:51 +0000318
jadmanski0afbb632008-06-06 21:10:57 +0000319 iterations = []
320 index = 1
321 attr, perf = {}, {}
322 for line in file(keyval_path):
323 line = line.strip()
324 if line:
325 cls.parse_line_into_dicts(line, attr, perf)
326 else:
327 iterations.append(cls(index, attr, perf))
328 index += 1
329 attr, perf = {}, {}
330 if attr or perf:
331 iterations.append(cls(index, attr, perf))
332 return iterations
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700333
334
335class perf_value_iteration(object):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700336 """Represents a perf value iteration."""
337
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700338 def __init__(self, index, perf_measurements):
339 """
340 Initializes the perf values for a particular test iteration.
341
342 @param index: The integer iteration number.
343 @param perf_measurements: A list of dictionaries, where each dictionary
344 contains the information for a measured perf metric from the
345 current iteration.
346
347 """
348 self.index = index
349 self.perf_measurements = perf_measurements
350
351
352 def add_measurement(self, measurement):
353 """
354 Appends to the list of perf measurements for this iteration.
355
356 @param measurement: A dictionary containing information for a measured
357 perf metric.
358
359 """
360 self.perf_measurements.append(measurement)
361
362
363 @staticmethod
364 def parse_line_into_dict(line):
365 """
366 Abstract method to parse an individual perf measurement line.
367
368 @param line: A string line from the perf measurement output file.
369
370 @return A dicionary representing the information for a measured perf
371 metric from one line of the perf measurement output file, or an
372 empty dictionary if the line cannot be parsed successfully.
373
374 """
375 raise NotImplementedError
376