blob: 7ec8494feb09a8d6a114d4e585c1cb802825b7e6 [file] [log] [blame]
Srikrishna Iyer09a81e92019-12-30 10:47:57 -08001# Copyright lowRISC contributors.
2# Licensed under the Apache License, Version 2.0, see LICENSE for details.
3# SPDX-License-Identifier: Apache-2.0
4r"""
5Classes
6"""
7
8import logging as log
9import pprint
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -080010import random
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080011import re
12import shlex
13import sys
14import time
Srikrishna Iyer32aae3f2020-03-19 17:34:00 -070015from collections import OrderedDict
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080016
17import hjson
Srikrishna Iyer2a710a42020-02-10 10:39:15 -080018from tabulate import tabulate
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080019
Udi Jonnalagaddadf49fb82020-03-17 11:05:17 -070020from utils import *
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080021
22
23class Deploy():
24 """
25 Abstraction for deploying builds and runs.
26 """
27
Srikrishna Iyer7702be52020-03-07 01:00:28 -080028 # Timer.
29 num_secs = 0
30
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -080031 # Maintain a list of dispatched items.
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080032 dispatch_counter = 0
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -080033
34 # Misc common deploy settings.
Srikrishna Iyer7702be52020-03-07 01:00:28 -080035 print_legend = True
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080036 print_interval = 5
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -080037 max_parallel = 16
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080038 max_odirs = 5
39
40 def __self_str__(self):
41 if log.getLogger().isEnabledFor(VERBOSE):
42 return pprint.pformat(self.__dict__)
43 else:
44 ret = self.cmd
45 if self.sub != []: ret += "\nSub:\n" + str(self.sub)
46 return ret
47
48 def __str__(self):
49 return self.__self_str__()
50
51 def __repr__(self):
52 return self.__self_str__()
53
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -080054 def __init__(self, sim_cfg):
55 # Cross ref the whole cfg object for ease.
56 self.sim_cfg = sim_cfg
57
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080058 # Common vars
Srikrishna Iyer7702be52020-03-07 01:00:28 -080059 self.identifier = ""
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080060 self.cmd = ""
61 self.odir = ""
62 self.log = ""
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -080063 self.fail_msg = ""
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080064
65 # Flag to indicate whether to 'overwrite' if odir already exists,
66 # or to backup the existing one and create a new one.
67 # For builds, we want to overwrite existing to leverage the tools'
68 # incremental / partition compile features. For runs, we may want to
69 # create a new one.
70 self.renew_odir = False
71
72 # List of vars required to be exported to sub-shell
73 self.exports = {}
74
75 # Deploy sub commands
76 self.sub = []
77
78 # Process
79 self.process = None
80 self.log_fd = None
81 self.status = None
82
Srikrishna Iyer2a710a42020-02-10 10:39:15 -080083 # These are command, output directory and log file
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080084 self.mandatory_misc_attrs.update({
85 "name": False,
86 "build_mode": False,
87 "flow_makefile": False,
88 "exports": False,
89 "dry_run": False
90 })
91
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -080092 # Function to parse a dict and extract the mandatory cmd and misc attrs.
93 def parse_dict(self, ddict):
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080094 if not hasattr(self, "target"):
95 log.error(
96 "Class %s does not have the mandatory attribute \"target\" defined",
97 self.__class__.__name__)
98 sys.exit(1)
99
100 ddict_keys = ddict.keys()
101 for key in self.mandatory_cmd_attrs.keys():
102 if self.mandatory_cmd_attrs[key] == False:
103 if key in ddict_keys:
104 setattr(self, key, ddict[key])
105 self.mandatory_cmd_attrs[key] = True
106
107 for key in self.mandatory_misc_attrs.keys():
108 if self.mandatory_misc_attrs[key] == False:
109 if key in ddict_keys:
110 setattr(self, key, ddict[key])
111 self.mandatory_misc_attrs[key] = True
112
113 def __post_init__(self):
114 # Ensure all mandatory attrs are set
115 for attr in self.mandatory_cmd_attrs.keys():
116 if self.mandatory_cmd_attrs[attr] is False:
117 log.error("Attribute \"%s\" not found for \"%s\".", attr,
118 self.name)
119 sys.exit(1)
120
121 for attr in self.mandatory_misc_attrs.keys():
122 if self.mandatory_misc_attrs[attr] is False:
123 log.error("Attribute \"%s\" not found for \"%s\".", attr,
124 self.name)
125 sys.exit(1)
126
127 # Recursively search and replace wildcards
128 self.__dict__ = find_and_substitute_wildcards(self.__dict__,
129 self.__dict__)
130
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800131 # Set identifier.
132 self.identifier = self.sim_cfg.name + ":" + self.name
133
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800134 # Set the command, output dir and log
135 self.odir = getattr(self, self.target + "_dir")
136 # Set the output dir link name to the basename of odir (by default)
137 self.odir_ln = os.path.basename(os.path.normpath(self.odir))
138 self.log = self.odir + "/" + self.target + ".log"
139
140 # If using LSF, redirect stdout and err to the log file
141 self.cmd = self.construct_cmd()
142
143 def construct_cmd(self):
144 cmd = "make -f " + self.flow_makefile + " " + self.target
145 if self.dry_run is True:
146 cmd += " -n"
147 for attr in self.mandatory_cmd_attrs.keys():
148 value = getattr(self, attr)
149 if type(value) is list:
150 pretty_value = []
151 for item in value:
152 pretty_value.append(item.strip())
153 value = " ".join(pretty_value)
154 if type(value) is bool:
155 value = int(value)
156 if type(value) is str:
157 value = value.strip()
158 cmd += " " + attr + "=\"" + str(value) + "\""
159
160 # TODO: If not running locally, redirect stdout and err to the log file
161 # self.cmd += " > " + self.log + " 2>&1 &"
162 return cmd
163
164 def dispatch_cmd(self):
165 self.exports.update(os.environ)
166 args = shlex.split(self.cmd)
167 try:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800168 # If renew_odir flag is True - then move it.
169 if self.renew_odir: self.odir_limiter(odir=self.odir)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800170 os.system("mkdir -p " + self.odir)
Srikrishna Iyer544da8d2020-01-14 23:51:41 -0800171 os.system("ln -s " + self.odir + " " + self.sim_cfg.links['D'] +
172 '/' + self.odir_ln)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800173 f = open(self.log, "w")
174 self.process = subprocess.Popen(args,
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800175 bufsize=4096,
176 universal_newlines=True,
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800177 stdout=f,
178 stderr=f,
179 env=self.exports)
180 self.log_fd = f
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800181 self.status = "D"
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800182 Deploy.dispatch_counter += 1
183 except IOError:
184 log.error('IO Error: See %s', self.log)
185 if self.log_fd: self.log_fd.close()
186 self.status = "K"
187
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800188 def odir_limiter(self, odir, max_odirs=-1):
189 '''Function to backup previously run output directory to maintain a
190 history of a limited number of output directories. It deletes the output
191 directory with the oldest timestamps, if the limit is reached. It returns
192 a list of directories that remain after deletion.
193 Arguments:
194 odir: The output directory to backup
195 max_odirs: Maximum output directories to maintain as history.
196
197 Returns:
198 dirs: Space-separated list of directories that remain after deletion.
199 '''
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800200 try:
201 # If output directory exists, back it up.
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800202 if os.path.exists(odir):
Srikrishna Iyer64009052020-01-13 11:27:39 -0800203 ts = run_cmd("date '+" + self.sim_cfg.ts_format + "' -d \"" +
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800204 "$(stat -c '%y' " + odir + ")\"")
205 os.system('mv ' + odir + " " + odir + "_" + ts)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800206 except IOError:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800207 log.error('Failed to back up existing output directory %s', odir)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800208
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800209 dirs = ""
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800210 # Delete older directories.
211 try:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800212 pdir = os.path.realpath(odir + "/..")
213 # Fatal out if pdir got set to root.
214 if pdir == "/":
215 log.fatal(
216 "Something went wrong while processing \"%s\": odir = \"%s\"",
217 self.name, odir)
218 sys.exit(1)
219
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800220 if os.path.exists(pdir):
221 find_cmd = "find " + pdir + " -mindepth 1 -maxdepth 1 -type d "
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800222 dirs = run_cmd(find_cmd)
223 dirs = dirs.replace('\n', ' ')
224 list_dirs = dirs.split()
225 num_dirs = len(list_dirs)
226 if max_odirs == -1: max_odirs = self.max_odirs
227 num_rm_dirs = num_dirs - max_odirs
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800228 if num_rm_dirs > -1:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800229 rm_dirs = run_cmd(find_cmd +
230 "-printf '%T+ %p\n' | sort | head -n " +
231 str(num_rm_dirs + 1) +
232 " | awk '{print $2}'")
233 rm_dirs = rm_dirs.replace('\n', ' ')
234 dirs = dirs.replace(rm_dirs, "")
Srikrishna Iyer32aae3f2020-03-19 17:34:00 -0700235 os.system("/bin/rm -rf " + rm_dirs)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800236 except IOError:
237 log.error("Failed to delete old run directories!")
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800238 return dirs
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800239
240 def set_status(self):
241 self.status = 'P'
242 if self.dry_run is False:
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800243 for fail_pattern in self.fail_patterns:
244 grep_cmd = "grep -m 1 -E \'" + fail_pattern + "\' " + self.log
245 (status, rslt) = subprocess.getstatusoutput(grep_cmd + " -c")
246 if rslt != "0":
247 (status, rslt) = subprocess.getstatusoutput(grep_cmd)
248 msg = "```\n{}\n```\n".format(rslt)
249 self.fail_msg += msg
250 log.log(VERBOSE, msg)
251 self.status = 'F'
252 break
253
254 # Return if status is fail - no need to look for pass patterns.
255 if self.status == 'F': return
256
257 # If fail patterns were not found, ensure pass patterns indeed were.
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800258 for pass_pattern in self.pass_patterns:
259 grep_cmd = "grep -c -m 1 -E \'" + pass_pattern + "\' " + self.log
260 (status, rslt) = subprocess.getstatusoutput(grep_cmd)
261 if rslt == "0":
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800262 msg = "Pass pattern \"{}\" not found.<br>\n".format(
263 pass_pattern)
264 self.fail_msg += msg
265 log.log(VERBOSE, msg)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800266 self.status = 'F'
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800267 break
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800268
269 # Recursively set sub-item's status if parent item fails
270 def set_sub_status(self, status):
271 if self.sub == []: return
272 for sub_item in self.sub:
273 sub_item.status = status
274 sub_item.set_sub_status(status)
275
276 def link_odir(self):
277 if self.status == '.':
278 log.error("Method unexpectedly called!")
279 else:
Srikrishna Iyer228f1c12020-01-17 10:54:48 -0800280 old_link = self.sim_cfg.links['D'] + "/" + self.odir_ln
281 new_link = self.sim_cfg.links[self.status] + "/" + self.odir_ln
282 cmd = "ln -s " + self.odir + " " + new_link + "; "
283 cmd += "rm " + old_link
284 try:
285 os.system(cmd)
286 except Exception as e:
287 log.error("Cmd \"%s\" could not be run", cmd)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800288
289 def get_status(self):
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800290 if self.status != "D": return
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800291 if self.process.poll() is not None:
292 self.log_fd.close()
293 if self.process.returncode != 0:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800294 msg = "Last 10 lines of the log:<br>\n"
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800295 self.fail_msg += msg
296 log.log(VERBOSE, msg)
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800297 get_fail_msg_cmd = "tail -n 10 " + self.log
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800298 msg = run_cmd(get_fail_msg_cmd)
299 msg = "```\n{}\n```\n".format(msg)
300 self.fail_msg += msg
301 log.log(VERBOSE, msg)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800302 self.status = "F"
303 else:
304 self.set_status()
305
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800306 log.debug("Item %s has completed execution: %s", self.name,
307 self.status)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800308 Deploy.dispatch_counter -= 1
309 self.link_odir()
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800310 del self.process
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800311
312 @staticmethod
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800313 def deploy(items):
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800314 dispatched_items = []
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700315 queued_items = []
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800316
317 if Deploy.print_legend:
318 # Print legend once at the start of the run.
319 log.info("[legend]: [Q: queued, D: dispatched, " + \
320 "P: passed, F: failed, K: killed, T: total]")
321 Deploy.print_legend = False
322
323 def get_etime():
324 # Convert num_secs to hh:mm:ss
325 hh = Deploy.num_secs // (3600)
326 ss = Deploy.num_secs % (3600)
327 mm = ss // 60
328 ss %= 60
329 return "%02i:%02i:%02i" % (hh, mm, ss)
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800330
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800331 def dispatch_items(items):
Srikrishna Iyer32aae3f2020-03-19 17:34:00 -0700332 item_names = OrderedDict()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800333 for item in items:
334 if item.target not in item_names.keys():
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800335 item_names[item.target] = ""
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800336 if item.status is None:
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800337 item_names[item.target] += item.identifier + ", "
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800338 item.dispatch_cmd()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800339
340 for target in item_names.keys():
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800341 if item_names[target] != "":
342 item_names[target] = " [" + item_names[target][:-2] + "]"
343 log.log(VERBOSE, "[%s]: [%s]: [dispatch]:\n%s",
344 get_etime(), target, item_names[target])
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800345
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700346 def track_progress(status, print_status_flag):
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800347 all_done = True
348 for target in status.keys():
349 if "target_done" in status[target].keys(): continue
Srikrishna Iyer32aae3f2020-03-19 17:34:00 -0700350 stats = OrderedDict()
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800351 stats["Q"] = 0
352 stats["D"] = 0
353 stats["P"] = 0
354 stats["F"] = 0
355 stats["K"] = 0
356 stats["T"] = 0
357 target_done = False
358 for item in status[target].keys():
359 stats[status[target][item]] += 1
360 stats["T"] += 1
361 if stats["Q"] == 0 and stats["D"] == 0:
362 target_done = True
363 all_done &= target_done
364
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700365 if print_status_flag:
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800366 width = "0{}d".format(len(str(stats["T"])))
367 msg = "["
368 for s in stats.keys():
369 msg += s + ": {:{}}, ".format(stats[s], width)
370 msg = msg[:-2] + "]"
371 log.info("[%s]: [%s]: %s", get_etime(), target, msg)
372 if target_done: status[target]["target_done"] = True
373 return all_done
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800374
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800375 all_done = False
Srikrishna Iyer32aae3f2020-03-19 17:34:00 -0700376 status = OrderedDict()
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700377 print_status_flag = True
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800378
379 # Queue all items
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700380 queued_items.extend(items)
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800381 for item in queued_items:
382 if item.target not in status.keys():
Srikrishna Iyer32aae3f2020-03-19 17:34:00 -0700383 status[item.target] = OrderedDict()
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800384 status[item.target][item] = "Q"
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800385
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800386 while not all_done:
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700387 # Get status of dispatched items.
388 for item in dispatched_items:
389 if item.status == "D": item.get_status()
390 if item.status != status[item.target][item]:
391 print_status_flag = True
392 if item.status != "D":
393 if item.status != "P":
394 # Kill its sub items if item did not pass.
395 item.set_sub_status("K")
396 log.error("[%s]: [%s]: [status] [%s: %s]",
397 get_etime(), item.target,
398 item.identifier, item.status)
399 else:
400 log.log(VERBOSE, "[%s]: [%s]: [status] [%s: %s]",
401 get_etime(), item.target, item.identifier,
402 item.status)
403 # Queue items' sub-items if it is done.
404 queued_items.extend(item.sub)
405 for sub_item in item.sub:
406 if sub_item.target not in status.keys():
Srikrishna Iyer32aae3f2020-03-19 17:34:00 -0700407 status[sub_item.target] = OrderedDict()
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700408 status[sub_item.target][sub_item] = "Q"
409 status[item.target][item] = item.status
410
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800411 # Dispatch items from the queue as slots free up.
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700412 all_done = (len(queued_items) == 0)
413 if not all_done:
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800414 num_slots = Deploy.max_parallel - Deploy.dispatch_counter
Srikrishna Iyere19f42b2020-01-08 17:51:36 -0800415 if num_slots > 0:
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800416 if len(queued_items) > num_slots:
417 dispatch_items(queued_items[0:num_slots])
418 dispatched_items.extend(queued_items[0:num_slots])
419 queued_items = queued_items[num_slots:]
Srikrishna Iyere19f42b2020-01-08 17:51:36 -0800420 else:
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800421 dispatch_items(queued_items)
422 dispatched_items.extend(queued_items)
423 queued_items = []
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800424
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800425 # Check if we are done and print the status periodically.
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700426 all_done &= track_progress(status, print_status_flag)
427
428 # Advance time by 1s if there is more work to do.
429 if not all_done:
430 time.sleep(1)
431 Deploy.num_secs += 1
432 print_status_flag = ((Deploy.num_secs %
433 Deploy.print_interval) == 0)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800434
435
436class CompileSim(Deploy):
437 """
438 Abstraction for building the simulation executable.
439 """
440
441 # Register all builds with the class
442 items = []
443
444 def __init__(self, build_mode, sim_cfg):
445 self.target = "build"
446 self.pass_patterns = []
447 self.fail_patterns = []
448
Srikrishna Iyer8ce80d02020-02-05 10:53:19 -0800449 self.mandatory_cmd_attrs = {
450 # tool srcs
Weicai Yang680f7e22020-02-26 18:10:24 -0800451 "tool_srcs": False,
452 "tool_srcs_dir": False,
Srikrishna Iyer8ce80d02020-02-05 10:53:19 -0800453
454 # RAL gen
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800455 "skip_ral": False,
456 "gen_ral_pkg_cmd": False,
457 "gen_ral_pkg_dir": False,
458 "gen_ral_pkg_opts": False,
459
460 # Flist gen
461 "sv_flist_gen_cmd": False,
462 "sv_flist_gen_dir": False,
463 "sv_flist_gen_opts": False,
464
465 # Build
466 "build_dir": False,
467 "build_cmd": False,
468 "build_opts": False
469 }
470
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800471 self.mandatory_misc_attrs = {"cov_db_dir": False}
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800472
473 # Initialize
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800474 super().__init__(sim_cfg)
475 super().parse_dict(build_mode.__dict__)
476 # Call this method again with the sim_cfg dict passed as the object,
477 # since it may contain additional mandatory attrs.
478 super().parse_dict(sim_cfg.__dict__)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800479 self.build_mode = self.name
480 self.__post_init__()
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800481
482 # Start fail message construction
483 self.fail_msg = "\n**BUILD:** {}<br>\n".format(self.name)
Srikrishna Iyerf578e7c2020-01-29 13:11:58 -0800484 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
485 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800486
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800487 CompileSim.items.append(self)
488
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800489 def dispatch_cmd(self):
490 # Delete previous cov_db_dir if it exists before dispatching new build.
491 if os.path.exists(self.cov_db_dir):
492 os.system("rm -rf " + self.cov_db_dir)
493 super().dispatch_cmd()
494
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800495
Michael Schaffner8ac6c4c2020-03-03 15:00:20 -0800496class CompileOneShot(Deploy):
497 """
498 Abstraction for building the simulation executable.
499 """
500
501 # Register all builds with the class
502 items = []
503
504 def __init__(self, build_mode, sim_cfg):
505 self.target = "build"
506 self.pass_patterns = []
507 self.fail_patterns = []
508
509 self.mandatory_cmd_attrs = {
510 # tool srcs
511 "tool_srcs": False,
512 "tool_srcs_dir": False,
513
514 # Build
515 "build_dir": False,
516 "build_cmd": False,
517 "build_opts": False,
518
519 # Report processing
520 "report_cmd": False,
521 "report_opts": False
522 }
523
524 self.mandatory_misc_attrs = {}
525
526 # Initialize
527 super().__init__(sim_cfg)
528 super().parse_dict(build_mode.__dict__)
529 # Call this method again with the sim_cfg dict passed as the object,
530 # since it may contain additional mandatory attrs.
531 super().parse_dict(sim_cfg.__dict__)
532 self.build_mode = self.name
533 self.__post_init__()
534
535 # Start fail message construction
536 self.fail_msg = "\n**BUILD:** {}<br>\n".format(self.name)
537 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
538 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
539
540 CompileOneShot.items.append(self)
541
542
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800543class RunTest(Deploy):
544 """
545 Abstraction for running tests. This is one per seed for each test.
546 """
547
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800548 # Initial seed values when running tests (if available).
549 seeds = []
Srikrishna Iyer96e54102020-03-12 22:46:50 -0700550 fixed_seed = None
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800551
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800552 # Register all runs with the class
553 items = []
554
555 def __init__(self, index, test, sim_cfg):
556 self.target = "run"
557 self.pass_patterns = []
558 self.fail_patterns = []
559
560 self.mandatory_cmd_attrs = {
Srikrishna Iyer42d032f2020-03-04 23:55:44 -0800561 "proj_root": False,
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800562 "uvm_test": False,
563 "uvm_test_seq": False,
564 "run_opts": False,
565 "sw_dir": False,
566 "sw_name": False,
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800567 "sw_build_device": False,
568 "sw_build_dir": False,
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800569 "run_dir": False,
570 "run_cmd": False,
571 "run_opts": False
572 }
573
574 self.mandatory_misc_attrs = {
575 "run_dir_name": False,
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800576 "cov_db_test_dir": False,
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800577 "pass_patterns": False,
578 "fail_patterns": False
579 }
580
581 self.index = index
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800582 self.seed = RunTest.get_seed()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800583
584 # Initialize
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800585 super().__init__(sim_cfg)
586 super().parse_dict(test.__dict__)
587 # Call this method again with the sim_cfg dict passed as the object,
588 # since it may contain additional mandatory attrs.
589 super().parse_dict(sim_cfg.__dict__)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800590 self.test = self.name
591 self.renew_odir = True
592 self.build_mode = test.build_mode.name
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800593 self.__post_init__()
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800594 # For output dir link, use run_dir_name instead.
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800595 self.odir_ln = self.run_dir_name
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800596
597 # Start fail message construction
598 self.fail_msg = "\n**TEST:** {}, ".format(self.name)
599 self.fail_msg += "**SEED:** {}<br>\n".format(self.seed)
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800600 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
601 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800602
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800603 RunTest.items.append(self)
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800604
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800605 def __post_init__(self):
606 super().__post_init__()
607 # Set identifier.
608 self.identifier = self.sim_cfg.name + ":" + self.run_dir_name
609
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800610 def get_status(self):
611 '''Override base class get_status implementation for additional post-status
612 actions.'''
613 super().get_status()
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800614 if self.status not in ["D", "P"]:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800615 # Delete the coverage data if available.
616 if os.path.exists(self.cov_db_test_dir):
617 log.log(VERBOSE, "Deleting coverage data of failing test:\n%s",
618 self.cov_db_test_dir)
Srikrishna Iyer32aae3f2020-03-19 17:34:00 -0700619 os.system("/bin/rm -rf " + self.cov_db_test_dir)
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800620
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800621 @staticmethod
622 def get_seed():
Srikrishna Iyer96e54102020-03-12 22:46:50 -0700623 # If --seeds option is passed, then those custom seeds are consumed
624 # first. If --fixed-seed <val> is also passed, the subsequent tests
625 # (once the custom seeds are consumed) will be run with the fixed seed.
Philipp Wagner9a521782020-02-25 11:49:53 +0000626 if not RunTest.seeds:
Srikrishna Iyer96e54102020-03-12 22:46:50 -0700627 if RunTest.fixed_seed: return RunTest.fixed_seed
Srikrishna Iyera821bbc2020-01-31 00:28:02 -0800628 for i in range(1000):
Philipp Wagner75fd6c72020-02-25 11:47:41 +0000629 seed = random.getrandbits(32)
Srikrishna Iyera821bbc2020-01-31 00:28:02 -0800630 RunTest.seeds.append(seed)
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800631 return RunTest.seeds.pop(0)
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800632
633
634class CovMerge(Deploy):
635 """
636 Abstraction for merging coverage databases. An item of this class is created AFTER
637 the regression is completed.
638 """
639
640 # Register all builds with the class
641 items = []
642
643 def __init__(self, sim_cfg):
644 self.target = "cov_merge"
645 self.pass_patterns = []
646 self.fail_patterns = []
647
648 # Construct local 'special' variable from cov directories that need to
649 # be merged.
650 self.cov_db_dirs = ""
651
652 self.mandatory_cmd_attrs = {
653 "cov_merge_cmd": False,
654 "cov_merge_opts": False
655 }
656
657 self.mandatory_misc_attrs = {
658 "cov_merge_dir": False,
659 "cov_merge_db_dir": False
660 }
661
662 # Initialize
663 super().__init__(sim_cfg)
664 super().parse_dict(sim_cfg.__dict__)
665 self.__post_init__()
666
667 # Override standard output and log patterns.
668 self.odir = self.cov_merge_db_dir
669 self.odir_ln = os.path.basename(os.path.normpath(self.odir))
670
671 # Start fail message construction
672 self.fail_msg = "\n**COV_MERGE:** {}<br>\n".format(self.name)
673 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
674 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
675
676 CovMerge.items.append(self)
677
678 def __post_init__(self):
679 # Add cov db dirs from all the builds that were kicked off.
680 for bld in self.sim_cfg.builds:
681 self.cov_db_dirs += bld.cov_db_dir + " "
682
683 # Recursively search and replace wildcards, ignoring cov_db_dirs for now.
684 # We need to resolve it later based on cov_db_dirs value set below.
685 self.__dict__ = find_and_substitute_wildcards(
686 self.__dict__, self.__dict__, ignored_wildcards=["cov_db_dirs"])
687
688 # Prune previous merged cov directories.
689 prev_cov_db_dirs = self.odir_limiter(odir=self.cov_merge_db_dir)
690
691 # If a merged cov data base exists from a previous run, then consider
692 # that as well for merging, if the --cov-merge-previous command line
693 # switch is passed.
694 if self.sim_cfg.cov_merge_previous:
695 self.cov_db_dirs += prev_cov_db_dirs
696
697 # Call base class __post_init__ to do checks and substitutions
698 super().__post_init__()
699
700
701class CovReport(Deploy):
702 """
703 Abstraction for coverage report generation. An item of this class is created AFTER
704 the regression is completed.
705 """
706
707 # Register all builds with the class
708 items = []
709
710 def __init__(self, sim_cfg):
711 self.target = "cov_report"
712 self.pass_patterns = []
713 self.fail_patterns = []
Weicai Yang680f7e22020-02-26 18:10:24 -0800714 self.cov_total = ""
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800715 self.cov_results = ""
716
717 self.mandatory_cmd_attrs = {
718 "cov_report_cmd": False,
719 "cov_report_opts": False
720 }
721
722 self.mandatory_misc_attrs = {
723 "cov_report_dir": False,
724 "cov_merge_db_dir": False,
725 "cov_report_dashboard": False
726 }
727
728 # Initialize
729 super().__init__(sim_cfg)
730 super().parse_dict(sim_cfg.__dict__)
731 self.__post_init__()
732
733 # Start fail message construction
734 self.fail_msg = "\n**COV_REPORT:** {}<br>\n".format(self.name)
735 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
736 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
737
738 CovReport.items.append(self)
739
740 def get_status(self):
741 super().get_status()
742 # Once passed, extract the cov results summary from the dashboard.
743 if self.status == "P":
744 try:
745 with open(self.cov_report_dashboard, 'r') as f:
746 for line in f:
747 match = re.match("total coverage summary", line,
748 re.IGNORECASE)
749 if match:
750 results = []
751 # Metrics on the next line.
752 line = f.readline().strip()
753 results.append(line.split())
754 # Values on the next.
755 line = f.readline().strip()
756 # Pretty up the values - add % sign for ease of post
757 # processing.
758 values = []
759 for val in line.split():
760 val += " %"
761 values.append(val)
Weicai Yang680f7e22020-02-26 18:10:24 -0800762 # first row is coverage total
763 self.cov_total = values[0]
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800764 results.append(values)
765 colalign = (("center", ) * len(values))
766 self.cov_results = tabulate(results,
767 headers="firstrow",
768 tablefmt="pipe",
769 colalign=colalign)
770 break
771
772 except Exception as e:
773 ex_msg = "Failed to parse \"{}\":\n{}".format(
774 self.cov_report_dashboard, str(e))
Srikrishna Iyer86f6bce2020-02-27 19:02:04 -0800775 self.fail_msg += ex_msg
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800776 log.error(ex_msg)
777 self.status = "F"
778
779 if self.cov_results == "":
780 nf_msg = "Coverage summary not found in the reports dashboard!"
Srikrishna Iyer86f6bce2020-02-27 19:02:04 -0800781 self.fail_msg += nf_msg
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800782 log.error(nf_msg)
783 self.status = "F"
784
785 if self.status == "P":
786 # Delete the cov report - not needed.
787 os.system("rm -rf " + self.log)
788
789
790class CovAnalyze(Deploy):
791 """
792 Abstraction for coverage analysis tool.
793 """
794
795 # Register all builds with the class
796 items = []
797
798 def __init__(self, sim_cfg):
799 self.target = "cov_analyze"
800 self.pass_patterns = []
801 self.fail_patterns = []
802
803 self.mandatory_cmd_attrs = {
804 "cov_analyze_cmd": False,
805 "cov_analyze_opts": False
806 }
807
808 self.mandatory_misc_attrs = {
809 "cov_analyze_dir": False,
810 "cov_merge_db_dir": False
811 }
812
813 # Initialize
814 super().__init__(sim_cfg)
815 super().parse_dict(sim_cfg.__dict__)
816 self.__post_init__()
817
818 # Start fail message construction
819 self.fail_msg = "\n**COV_ANALYZE:** {}<br>\n".format(self.name)
820 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
821 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
822
823 CovAnalyze.items.append(self)