blob: aa41bf6f44f2e764278f7ced95c4484543f3c133 [file] [log] [blame]
Prathmesh Prabhu84a85952017-02-03 17:05:24 -08001import json
2import math
3import os
4import re
jadmanskicc549172008-05-21 18:11:51 +00005
Prathmesh Prabhu84a85952017-02-03 17:05:24 -08006import common
7from autotest_lib.tko import models
8from autotest_lib.tko import status_lib
9from autotest_lib.tko import utils as tko_utils
10from autotest_lib.tko.parsers import base
11from autotest_lib.tko.parsers import version_0
jadmanski6e8bf752008-05-14 00:17:48 +000012
13
jadmanski6f6299a2008-12-02 15:22:21 +000014class job(version_0.job):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070015 """Represents a job."""
16
jadmanski6f6299a2008-12-02 15:22:21 +000017 def exit_status(self):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070018 """Returns the string exit status of this job."""
19
20 # Find the .autoserv_execute path.
jadmanski165fe4c2009-01-07 17:06:00 +000021 top_dir = tko_utils.find_toplevel_job_dir(self.dir)
22 if not top_dir:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070023 return 'ABORT'
24 execute_path = os.path.join(top_dir, '.autoserv_execute')
jadmanski165fe4c2009-01-07 17:06:00 +000025
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070026 # If for some reason we can't read the status code, assume disaster.
jadmanski6f6299a2008-12-02 15:22:21 +000027 if not os.path.exists(execute_path):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070028 return 'ABORT'
jadmanski6f6299a2008-12-02 15:22:21 +000029 lines = open(execute_path).readlines()
30 if len(lines) < 2:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070031 return 'ABORT'
jadmanski6f6299a2008-12-02 15:22:21 +000032 try:
33 status_code = int(lines[1])
34 except ValueError:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070035 return 'ABORT'
jadmanski6f6299a2008-12-02 15:22:21 +000036
37 if not os.WIFEXITED(status_code):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070038 # Looks like a signal - an ABORT.
39 return 'ABORT'
jadmanski6f6299a2008-12-02 15:22:21 +000040 elif os.WEXITSTATUS(status_code) != 0:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070041 # Looks like a non-zero exit - a failure.
42 return 'FAIL'
jadmanski6f6299a2008-12-02 15:22:21 +000043 else:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070044 # Looks like exit code == 0.
45 return 'GOOD'
jadmanski6f6299a2008-12-02 15:22:21 +000046
47
jadmanski6e8bf752008-05-14 00:17:48 +000048class kernel(models.kernel):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070049 """Represents a kernel."""
50
jadmanski0afbb632008-06-06 21:10:57 +000051 def __init__(self, base, patches):
52 if base:
53 patches = [patch(*p.split()) for p in patches]
54 hashes = [p.hash for p in patches]
55 kernel_hash = self.compute_hash(base, hashes)
56 else:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070057 base = 'UNKNOWN'
jadmanski0afbb632008-06-06 21:10:57 +000058 patches = []
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070059 kernel_hash = 'UNKNOWN'
jadmanski0afbb632008-06-06 21:10:57 +000060 super(kernel, self).__init__(base, patches, kernel_hash)
jadmanski6e8bf752008-05-14 00:17:48 +000061
62
jadmanskicc549172008-05-21 18:11:51 +000063class test(models.test):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070064 """Represents a test."""
65
jadmanski0afbb632008-06-06 21:10:57 +000066 @staticmethod
67 def load_iterations(keyval_path):
68 return iteration.load_from_keyval(keyval_path)
jadmanskicc549172008-05-21 18:11:51 +000069
70
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -070071 @staticmethod
72 def load_perf_values(perf_values_file):
73 return perf_value_iteration.load_from_perf_values_file(
74 perf_values_file)
75
76
jadmanskicc549172008-05-21 18:11:51 +000077class iteration(models.iteration):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070078 """Represents an iteration."""
79
jadmanski0afbb632008-06-06 21:10:57 +000080 @staticmethod
81 def parse_line_into_dicts(line, attr_dict, perf_dict):
jadmanskid2e0f0c2009-07-20 17:52:57 +000082 key, val_type, value = "", "", ""
83
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070084 # Figure out what the key, value and keyval type are.
85 typed_match = re.search('^([^=]*)\{(\w*)\}=(.*)$', line)
jadmanski0afbb632008-06-06 21:10:57 +000086 if typed_match:
87 key, val_type, value = typed_match.groups()
jadmanski0afbb632008-06-06 21:10:57 +000088 else:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070089 # Old-fashioned untyped match, assume perf.
90 untyped_match = re.search('^([^=]*)=(.*)$', line)
jadmanskid2e0f0c2009-07-20 17:52:57 +000091 if untyped_match:
92 key, value = untyped_match.groups()
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070093 val_type = 'perf'
jadmanskid2e0f0c2009-07-20 17:52:57 +000094
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070095 # Parse the actual value into a dict.
Eric Li861b2d52011-02-04 14:50:35 -080096 try:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070097 if val_type == 'attr':
Eric Li861b2d52011-02-04 14:50:35 -080098 attr_dict[key] = value
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -070099 elif val_type == 'perf':
Alex Khouderchahb5f75eb2018-07-23 13:40:02 -0700100 # first check if value is in the form of 'mean+-deviation'
101 if isinstance(value, str):
102 r = re.compile('(\d+.?\d*)\+-(\d+.?\d*)')
103 match = r.match(value)
104 if match:
105 perf_dict[key] = float(match.group(1))
106 perf_dict['%s_dev' % key] = float(match.group(2))
107 return
108 # otherwise try to interpret as a regular float
Eric Li861b2d52011-02-04 14:50:35 -0800109 perf_dict[key] = float(value)
110 else:
111 raise ValueError
112 except ValueError:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700113 msg = ('WARNING: line "%s" found in test '
114 'iteration keyval could not be parsed')
jadmanskid2e0f0c2009-07-20 17:52:57 +0000115 msg %= line
116 tko_utils.dprint(msg)
jadmanskicc549172008-05-21 18:11:51 +0000117
118
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700119class perf_value_iteration(models.perf_value_iteration):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700120 """Represents a perf value iteration."""
121
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700122 @staticmethod
123 def parse_line_into_dict(line):
124 """
125 Parse a perf measurement text line into a dictionary.
126
127 The line is assumed to be a JSON-formatted string containing key/value
128 pairs, where each pair represents a piece of information associated
129 with a measured perf metric:
130
131 'description': a string description for the perf metric.
132 'value': a numeric value, or list of numeric values.
133 'units': the string units associated with the perf metric.
134 'higher_is_better': a boolean whether a higher value is considered
135 better. If False, a lower value is considered better.
Fang Deng7f24f0b2013-11-12 11:22:16 -0800136 'graph': a string indicating the name of the perf dashboard graph
137 on which the perf data will be displayed.
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700138
139 The resulting dictionary will also have a standard deviation key/value
140 pair, 'stddev'. If the perf measurement value is a list of values
141 instead of a single value, then the average and standard deviation of
142 the list of values is computed and stored. If a single value, the
143 value itself is used, and is associated with a standard deviation of 0.
144
145 @param line: A string line of JSON text from a perf measurements output
146 file.
147
148 @return A dictionary containing the parsed perf measurement information
149 along with a computed standard deviation value (key 'stddev'), or
150 an empty dictionary if the inputted line cannot be parsed.
151 """
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700152 try:
153 perf_dict = json.loads(line)
154 except ValueError:
155 msg = 'Could not parse perf measurements line as json: "%s"' % line
156 tko_utils.dprint(msg)
157 return {}
158
159 def mean_and_standard_deviation(data):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700160 """
161 Computes the mean and standard deviation of a list of numbers.
162
163 @param data: A list of numbers.
164
165 @return A 2-tuple (mean, standard_deviation) computed from the list
166 of numbers.
167
168 """
Dennis Jeffrey05eb4b12013-07-17 11:11:52 -0700169 n = len(data)
170 if n == 0:
171 return 0.0, 0.0
172 if n == 1:
173 return data[0], 0.0
174 mean = float(sum(data)) / n
175 # Divide by n-1 to compute "sample standard deviation".
176 variance = sum([(elem - mean) ** 2 for elem in data]) / (n - 1)
177 return mean, math.sqrt(variance)
178
179 value = perf_dict['value']
180 perf_dict['stddev'] = 0.0
181 if isinstance(value, list):
182 value, stddev = mean_and_standard_deviation(map(float, value))
183 perf_dict['value'] = value
184 perf_dict['stddev'] = stddev
185
186 return perf_dict
187
188
jadmanski6e8bf752008-05-14 00:17:48 +0000189class status_line(version_0.status_line):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700190 """Represents a status line."""
191
jadmanski807490c2008-09-15 19:15:02 +0000192 def __init__(self, indent, status, subdir, testname, reason,
193 optional_fields):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700194 # Handle INFO fields.
195 if status == 'INFO':
196 self.type = 'INFO'
jadmanski807490c2008-09-15 19:15:02 +0000197 self.indent = indent
198 self.status = self.subdir = self.testname = self.reason = None
199 self.optional_fields = optional_fields
200 else:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700201 # Everything else is backwards compatible.
jadmanski807490c2008-09-15 19:15:02 +0000202 super(status_line, self).__init__(indent, status, subdir,
203 testname, reason,
204 optional_fields)
205
206
jadmanski0afbb632008-06-06 21:10:57 +0000207 def is_successful_reboot(self, current_status):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700208 """
209 Checks whether the status represents a successful reboot.
210
211 @param current_status: A string representing the current status.
212
213 @return True, if the status represents a successful reboot, or False
214 if not.
215
216 """
217 # Make sure this is a reboot line.
218 if self.testname != 'reboot':
jadmanski0afbb632008-06-06 21:10:57 +0000219 return False
jadmanski6e8bf752008-05-14 00:17:48 +0000220
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700221 # Make sure this was not a failure.
222 if status_lib.is_worse_than_or_equal_to(current_status, 'FAIL'):
jadmanski0afbb632008-06-06 21:10:57 +0000223 return False
jadmanski6e8bf752008-05-14 00:17:48 +0000224
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700225 # It must have been a successful reboot.
jadmanski0afbb632008-06-06 21:10:57 +0000226 return True
jadmanski6e8bf752008-05-14 00:17:48 +0000227
228
jadmanski0afbb632008-06-06 21:10:57 +0000229 def get_kernel(self):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700230 # Get the base kernel version.
jadmanski0afbb632008-06-06 21:10:57 +0000231 fields = self.optional_fields
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700232 base = re.sub('-autotest$', '', fields.get('kernel', ''))
233 # Get a list of patches.
jadmanski0afbb632008-06-06 21:10:57 +0000234 patches = []
235 patch_index = 0
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700236 while ('patch%d' % patch_index) in fields:
237 patches.append(fields['patch%d' % patch_index])
jadmanski0afbb632008-06-06 21:10:57 +0000238 patch_index += 1
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700239 # Create a new kernel instance.
jadmanski0afbb632008-06-06 21:10:57 +0000240 return kernel(base, patches)
jadmanski6e8bf752008-05-14 00:17:48 +0000241
242
jadmanski0afbb632008-06-06 21:10:57 +0000243 def get_timestamp(self):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700244 return tko_utils.get_timestamp(self.optional_fields, 'timestamp')
jadmanski6e8bf752008-05-14 00:17:48 +0000245
246
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700247# The default implementations from version 0 will do for now.
jadmanski6e8bf752008-05-14 00:17:48 +0000248patch = version_0.patch
jadmanski6e8bf752008-05-14 00:17:48 +0000249
250
251class parser(base.parser):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700252 """Represents a parser."""
253
jadmanski0afbb632008-06-06 21:10:57 +0000254 @staticmethod
255 def make_job(dir):
256 return job(dir)
jadmanski6e8bf752008-05-14 00:17:48 +0000257
258
jadmanski0afbb632008-06-06 21:10:57 +0000259 @staticmethod
mblighb22c21f2008-11-27 00:40:38 +0000260 def make_dummy_abort(indent, subdir, testname, timestamp, reason):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700261 """
262 Creates an abort string.
263
264 @param indent: The number of indentation levels for the string.
265 @param subdir: The subdirectory name.
266 @param testname: The test name.
267 @param timestamp: The timestamp value.
268 @param reason: The reason string.
269
270 @return A string describing the abort.
271
272 """
273 indent = '\t' * indent
jadmanskifd3ba2b2008-07-28 19:30:54 +0000274 if not subdir:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700275 subdir = '----'
jadmanskifd3ba2b2008-07-28 19:30:54 +0000276 if not testname:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700277 testname = '----'
mblighb22c21f2008-11-27 00:40:38 +0000278
279 # There is no guarantee that this will be set.
280 timestamp_field = ''
281 if timestamp:
282 timestamp_field = '\ttimestamp=%s' % timestamp
283
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700284 msg = indent + 'END ABORT\t%s\t%s%s\t%s'
mblighb22c21f2008-11-27 00:40:38 +0000285 return msg % (subdir, testname, timestamp_field, reason)
jadmanski6e8bf752008-05-14 00:17:48 +0000286
287
jadmanskib69f2502008-08-27 19:49:30 +0000288 @staticmethod
mblighb22c21f2008-11-27 00:40:38 +0000289 def put_back_line_and_abort(
Sida Liuafe550a2017-09-03 19:03:40 -0700290 line_buffer, line, indent, subdir, testname, timestamp, reason):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700291 """
292 Appends a line to the line buffer and aborts.
293
294 @param line_buffer: A line_buffer object.
295 @param line: A line to append to the line buffer.
296 @param indent: The number of indentation levels.
297 @param subdir: The subdirectory name.
Sida Liuafe550a2017-09-03 19:03:40 -0700298 @param testname: The test name.
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700299 @param timestamp: The timestamp value.
300 @param reason: The reason string.
301
302 """
Luigi Semenzatoe7064812017-02-03 14:47:59 -0800303 tko_utils.dprint('Unexpected indent: aborting log parse')
jadmanskib69f2502008-08-27 19:49:30 +0000304 line_buffer.put_back(line)
mblighb22c21f2008-11-27 00:40:38 +0000305 abort = parser.make_dummy_abort(
Sida Liuafe550a2017-09-03 19:03:40 -0700306 indent, subdir, testname, timestamp, reason)
jadmanskib69f2502008-08-27 19:49:30 +0000307 line_buffer.put_back(abort)
308
309
jadmanski0afbb632008-06-06 21:10:57 +0000310 def state_iterator(self, buffer):
Sida Liuafe550a2017-09-03 19:03:40 -0700311 """
312 Yields a list of tests out of the buffer.
313
314 @param buffer: a buffer object
315
316 """
jadmanskifd3ba2b2008-07-28 19:30:54 +0000317 line = None
jadmanski0afbb632008-06-06 21:10:57 +0000318 new_tests = []
jadmanskiba1fa662008-07-11 21:18:30 +0000319 job_count, boot_count = 0, 0
jadmanski0afbb632008-06-06 21:10:57 +0000320 min_stack_size = 0
321 stack = status_lib.status_stack()
322 current_kernel = kernel("", []) # UNKNOWN
jadmanskic69138f2008-12-17 15:44:22 +0000323 current_status = status_lib.statuses[-1]
324 current_reason = None
jadmanski0afbb632008-06-06 21:10:57 +0000325 started_time_stack = [None]
326 subdir_stack = [None]
Sida Liuafe550a2017-09-03 19:03:40 -0700327 testname_stack = [None]
jadmanski74eebf32008-07-15 20:04:42 +0000328 running_test = None
jadmanskiaaac45e2009-05-06 20:25:46 +0000329 running_reasons = set()
Luigi Semenzatoe7064812017-02-03 14:47:59 -0800330 ignored_lines = []
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700331 yield [] # We're ready to start running.
jadmanski6f6299a2008-12-02 15:22:21 +0000332
Luigi Semenzato9aa4a032017-02-17 08:52:40 -0800333 def print_ignored_lines():
Sida Liuafe550a2017-09-03 19:03:40 -0700334 """
335 Prints the ignored_lines using tko_utils.dprint method.
336 """
Luigi Semenzatoe7064812017-02-03 14:47:59 -0800337 tko_utils.dprint('The following lines were ignored:')
338 for line in ignored_lines:
339 tko_utils.dprint(line)
340 tko_utils.dprint('---------------------------------')
Luigi Semenzatoe7064812017-02-03 14:47:59 -0800341
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700342 # Create a RUNNING SERVER_JOB entry to represent the entire test.
343 running_job = test.parse_partial_test(self.job, '----', 'SERVER_JOB',
344 '', current_kernel,
jadmanski6f6299a2008-12-02 15:22:21 +0000345 self.job.started_time)
346 new_tests.append(running_job)
jadmanski6e8bf752008-05-14 00:17:48 +0000347
jadmanski0afbb632008-06-06 21:10:57 +0000348 while True:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700349 # Are we finished with parsing?
jadmanski0afbb632008-06-06 21:10:57 +0000350 if buffer.size() == 0 and self.finished:
Luigi Semenzatoe7064812017-02-03 14:47:59 -0800351 if ignored_lines:
Luigi Semenzato9aa4a032017-02-17 08:52:40 -0800352 print_ignored_lines()
353 ignored_lines = []
jadmanski0afbb632008-06-06 21:10:57 +0000354 if stack.size() == 0:
355 break
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700356 # We have status lines left on the stack;
357 # we need to implicitly abort them first.
jadmanskifd3ba2b2008-07-28 19:30:54 +0000358 tko_utils.dprint('\nUnexpected end of job, aborting')
359 abort_subdir_stack = list(subdir_stack)
jadmanskif7fa2cc2008-10-01 14:13:23 +0000360 if self.job.aborted_by:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700361 reason = 'Job aborted by %s' % self.job.aborted_by
jadmanskif7fa2cc2008-10-01 14:13:23 +0000362 reason += self.job.aborted_on.strftime(
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700363 ' at %b %d %H:%M:%S')
jadmanskif7fa2cc2008-10-01 14:13:23 +0000364 else:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700365 reason = 'Job aborted unexpectedly'
mblighb22c21f2008-11-27 00:40:38 +0000366
367 timestamp = line.optional_fields.get('timestamp')
jadmanski0afbb632008-06-06 21:10:57 +0000368 for i in reversed(xrange(stack.size())):
jadmanskifd3ba2b2008-07-28 19:30:54 +0000369 if abort_subdir_stack:
370 subdir = abort_subdir_stack.pop()
371 else:
372 subdir = None
mblighb22c21f2008-11-27 00:40:38 +0000373 abort = self.make_dummy_abort(
374 i, subdir, subdir, timestamp, reason)
jadmanskifd3ba2b2008-07-28 19:30:54 +0000375 buffer.put(abort)
jadmanski6e8bf752008-05-14 00:17:48 +0000376
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700377 # Stop processing once the buffer is empty.
jadmanski0afbb632008-06-06 21:10:57 +0000378 if buffer.size() == 0:
379 yield new_tests
380 new_tests = []
381 continue
jadmanski6e8bf752008-05-14 00:17:48 +0000382
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700383 # Reinitialize the per-iteration state.
jadmanski0afbb632008-06-06 21:10:57 +0000384 started_time = None
385 finished_time = None
jadmanski6e8bf752008-05-14 00:17:48 +0000386
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700387 # Get the next line.
mbligh51ee7c72008-11-24 17:10:14 +0000388 raw_line = status_lib.clean_raw_line(buffer.get())
jadmanski0afbb632008-06-06 21:10:57 +0000389 line = status_line.parse_line(raw_line)
390 if line is None:
Luigi Semenzatoe7064812017-02-03 14:47:59 -0800391 ignored_lines.append(raw_line)
jadmanski0afbb632008-06-06 21:10:57 +0000392 continue
Luigi Semenzatoe7064812017-02-03 14:47:59 -0800393 elif ignored_lines:
Luigi Semenzato9aa4a032017-02-17 08:52:40 -0800394 print_ignored_lines()
395 ignored_lines = []
jadmanski6e8bf752008-05-14 00:17:48 +0000396
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700397 # Do an initial sanity check of the indentation.
jadmanskib82cb142009-02-04 18:33:08 +0000398 expected_indent = stack.size()
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700399 if line.type == 'END':
jadmanskib82cb142009-02-04 18:33:08 +0000400 expected_indent -= 1
401 if line.indent < expected_indent:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700402 # ABORT the current level if indentation was unexpectedly low.
jadmanskib82cb142009-02-04 18:33:08 +0000403 self.put_back_line_and_abort(
404 buffer, raw_line, stack.size() - 1, subdir_stack[-1],
Sida Liuafe550a2017-09-03 19:03:40 -0700405 testname_stack[-1], line.optional_fields.get('timestamp'),
406 line.reason)
jadmanskib82cb142009-02-04 18:33:08 +0000407 continue
408 elif line.indent > expected_indent:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700409 # Ignore the log if the indent was unexpectedly high.
Luigi Semenzatoe7064812017-02-03 14:47:59 -0800410 tko_utils.dprint('ignoring line because of extra indentation')
jadmanskib82cb142009-02-04 18:33:08 +0000411 continue
412
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700413 # Initial line processing.
414 if line.type == 'START':
jadmanski0afbb632008-06-06 21:10:57 +0000415 stack.start()
jadmanski74eebf32008-07-15 20:04:42 +0000416 started_time = line.get_timestamp()
Sida Liuafe550a2017-09-03 19:03:40 -0700417 testname = None
jadmanski717bb992008-10-08 14:29:23 +0000418 if (line.testname is None and line.subdir is None
419 and not running_test):
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700420 # We just started a client; all tests are relative to here.
jadmanski0afbb632008-06-06 21:10:57 +0000421 min_stack_size = stack.size()
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700422 # Start a "RUNNING" CLIENT_JOB entry.
423 job_name = 'CLIENT_JOB.%d' % job_count
jadmanskid1b0a132010-05-04 19:50:30 +0000424 running_client = test.parse_partial_test(self.job, None,
425 job_name,
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700426 '', current_kernel,
jadmanskid1b0a132010-05-04 19:50:30 +0000427 started_time)
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700428 msg = 'RUNNING: %s\n%s\n'
jadmanskid1b0a132010-05-04 19:50:30 +0000429 msg %= (running_client.status, running_client.testname)
jadmanski43b72102010-04-30 21:22:28 +0000430 tko_utils.dprint(msg)
jadmanskid1b0a132010-05-04 19:50:30 +0000431 new_tests.append(running_client)
Sida Liuafe550a2017-09-03 19:03:40 -0700432 testname = running_client.testname
jadmanski717bb992008-10-08 14:29:23 +0000433 elif stack.size() == min_stack_size + 1 and not running_test:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700434 # We just started a new test; insert a running record.
jadmanskiaaac45e2009-05-06 20:25:46 +0000435 running_reasons = set()
jadmanski78395482009-03-10 04:21:03 +0000436 if line.reason:
jadmanskiaaac45e2009-05-06 20:25:46 +0000437 running_reasons.add(line.reason)
jadmanski74eebf32008-07-15 20:04:42 +0000438 running_test = test.parse_partial_test(self.job,
439 line.subdir,
440 line.testname,
441 line.reason,
442 current_kernel,
443 started_time)
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700444 msg = 'RUNNING: %s\nSubdir: %s\nTestname: %s\n%s'
jadmanski74eebf32008-07-15 20:04:42 +0000445 msg %= (running_test.status, running_test.subdir,
446 running_test.testname, running_test.reason)
447 tko_utils.dprint(msg)
448 new_tests.append(running_test)
Sida Liuafe550a2017-09-03 19:03:40 -0700449 testname = running_test.testname
jadmanski74eebf32008-07-15 20:04:42 +0000450 started_time_stack.append(started_time)
jadmanski0afbb632008-06-06 21:10:57 +0000451 subdir_stack.append(line.subdir)
Sida Liuafe550a2017-09-03 19:03:40 -0700452 testname_stack.append(testname)
jadmanski0afbb632008-06-06 21:10:57 +0000453 continue
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700454 elif line.type == 'INFO':
Eric Li6f27d4f2010-09-29 10:55:17 -0700455 fields = line.optional_fields
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700456 # Update the current kernel if one is defined in the info.
457 if 'kernel' in fields:
jadmanski807490c2008-09-15 19:15:02 +0000458 current_kernel = line.get_kernel()
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700459 # Update the SERVER_JOB reason if one was logged for an abort.
460 if 'job_abort_reason' in fields:
461 running_job.reason = fields['job_abort_reason']
Eric Li6f27d4f2010-09-29 10:55:17 -0700462 new_tests.append(running_job)
jadmanski807490c2008-09-15 19:15:02 +0000463 continue
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700464 elif line.type == 'STATUS':
465 # Update the stacks.
jadmanskib69f2502008-08-27 19:49:30 +0000466 if line.subdir and stack.size() > min_stack_size:
jadmanski0afbb632008-06-06 21:10:57 +0000467 subdir_stack[-1] = line.subdir
Sida Liuafe550a2017-09-03 19:03:40 -0700468 testname_stack[-1] = line.testname
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700469 # Update the status, start and finished times.
jadmanskic66e93c2008-07-29 21:25:22 +0000470 stack.update(line.status)
jadmanskib6837962009-04-21 14:21:00 +0000471 if status_lib.is_worse_than_or_equal_to(line.status,
jadmanski262e1ab2009-04-01 18:27:38 +0000472 current_status):
jadmanskic69138f2008-12-17 15:44:22 +0000473 if line.reason:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700474 # Update the status of a currently running test.
jadmanski78395482009-03-10 04:21:03 +0000475 if running_test:
jadmanskiaaac45e2009-05-06 20:25:46 +0000476 running_reasons.add(line.reason)
jadmanski1f99f672009-07-01 16:23:09 +0000477 running_reasons = tko_utils.drop_redundant_messages(
478 running_reasons)
jadmanskiaaac45e2009-05-06 20:25:46 +0000479 sorted_reasons = sorted(running_reasons)
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700480 running_test.reason = ', '.join(sorted_reasons)
jadmanskib6837962009-04-21 14:21:00 +0000481 current_reason = running_test.reason
jadmanski78395482009-03-10 04:21:03 +0000482 new_tests.append(running_test)
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700483 msg = 'update RUNNING reason: %s' % line.reason
jadmanski78395482009-03-10 04:21:03 +0000484 tko_utils.dprint(msg)
jadmanskib6837962009-04-21 14:21:00 +0000485 else:
486 current_reason = line.reason
jadmanskic69138f2008-12-17 15:44:22 +0000487 current_status = stack.current_status()
jadmanskic66e93c2008-07-29 21:25:22 +0000488 started_time = None
489 finished_time = line.get_timestamp()
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700490 # If this is a non-test entry there's nothing else to do.
jadmanski0986b252009-04-01 18:26:59 +0000491 if line.testname is None and line.subdir is None:
492 continue
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700493 elif line.type == 'END':
494 # Grab the current subdir off of the subdir stack, or, if this
495 # is the end of a job, just pop it off.
jadmanski717bb992008-10-08 14:29:23 +0000496 if (line.testname is None and line.subdir is None
497 and not running_test):
jadmanski0afbb632008-06-06 21:10:57 +0000498 min_stack_size = stack.size() - 1
499 subdir_stack.pop()
Sida Liuafe550a2017-09-03 19:03:40 -0700500 testname_stack.pop()
jadmanski0afbb632008-06-06 21:10:57 +0000501 else:
502 line.subdir = subdir_stack.pop()
Sida Liuafe550a2017-09-03 19:03:40 -0700503 testname_stack.pop()
jadmanski64e63452009-06-10 17:22:01 +0000504 if not subdir_stack[-1] and stack.size() > min_stack_size:
505 subdir_stack[-1] = line.subdir
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700506 # Update the status, start and finished times.
jadmanski0afbb632008-06-06 21:10:57 +0000507 stack.update(line.status)
jadmanski0afbb632008-06-06 21:10:57 +0000508 current_status = stack.end()
jadmanski29e61ce2008-08-22 17:38:28 +0000509 if stack.size() > min_stack_size:
510 stack.update(current_status)
jadmanskic69138f2008-12-17 15:44:22 +0000511 current_status = stack.current_status()
jadmanskic66e93c2008-07-29 21:25:22 +0000512 started_time = started_time_stack.pop()
513 finished_time = line.get_timestamp()
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700514 # Update the current kernel.
jadmanski0afbb632008-06-06 21:10:57 +0000515 if line.is_successful_reboot(current_status):
516 current_kernel = line.get_kernel()
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700517 # Adjust the testname if this is a reboot.
518 if line.testname == 'reboot' and line.subdir is None:
519 line.testname = 'boot.%d' % boot_count
jadmanskic66e93c2008-07-29 21:25:22 +0000520 else:
521 assert False
jadmanski6e8bf752008-05-14 00:17:48 +0000522
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700523 # Have we just finished a test?
jadmanski0afbb632008-06-06 21:10:57 +0000524 if stack.size() <= min_stack_size:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700525 # If there was no testname, just use the subdir.
jadmanski0afbb632008-06-06 21:10:57 +0000526 if line.testname is None:
527 line.testname = line.subdir
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700528 # If there was no testname or subdir, use 'CLIENT_JOB'.
jadmanski0afbb632008-06-06 21:10:57 +0000529 if line.testname is None:
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700530 line.testname = 'CLIENT_JOB.%d' % job_count
jadmanskid1b0a132010-05-04 19:50:30 +0000531 running_test = running_client
jadmanskiba1fa662008-07-11 21:18:30 +0000532 job_count += 1
533 if not status_lib.is_worse_than_or_equal_to(
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700534 current_status, 'ABORT'):
535 # A job hasn't really failed just because some of the
536 # tests it ran have.
537 current_status = 'GOOD'
jadmanski6e8bf752008-05-14 00:17:48 +0000538
jadmanskic69138f2008-12-17 15:44:22 +0000539 if not current_reason:
540 current_reason = line.reason
jadmanski0afbb632008-06-06 21:10:57 +0000541 new_test = test.parse_test(self.job,
542 line.subdir,
543 line.testname,
544 current_status,
jadmanskic69138f2008-12-17 15:44:22 +0000545 current_reason,
jadmanski0afbb632008-06-06 21:10:57 +0000546 current_kernel,
547 started_time,
jadmanski74eebf32008-07-15 20:04:42 +0000548 finished_time,
549 running_test)
550 running_test = None
jadmanskid6f5c592009-01-10 00:26:21 +0000551 current_status = status_lib.statuses[-1]
jadmanskic69138f2008-12-17 15:44:22 +0000552 current_reason = None
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700553 if new_test.testname == ('boot.%d' % boot_count):
jadmanski1ce9fbb2008-10-21 16:25:05 +0000554 boot_count += 1
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700555 msg = 'ADD: %s\nSubdir: %s\nTestname: %s\n%s'
jadmanski0afbb632008-06-06 21:10:57 +0000556 msg %= (new_test.status, new_test.subdir,
557 new_test.testname, new_test.reason)
558 tko_utils.dprint(msg)
559 new_tests.append(new_test)
jadmanski6e8bf752008-05-14 00:17:48 +0000560
Dennis Jeffreyccbc9d42013-07-23 12:16:45 -0700561 # The job is finished; produce the final SERVER_JOB entry and exit.
562 final_job = test.parse_test(self.job, '----', 'SERVER_JOB',
Eric Li6f27d4f2010-09-29 10:55:17 -0700563 self.job.exit_status(), running_job.reason,
jadmanski6f6299a2008-12-02 15:22:21 +0000564 current_kernel,
565 self.job.started_time,
566 self.job.finished_time,
567 running_job)
568 new_tests.append(final_job)
jadmanski0afbb632008-06-06 21:10:57 +0000569 yield new_tests