blob: 0a533976822435cfb15d8462fedcab95ab5362af [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
Srikrishna Iyer09a81e92019-12-30 10:47:57 -08004
5import logging as log
Rupert Swarbrick6cc20112020-04-24 09:44:35 +01006import os
Srikrishna Iyer09a81e92019-12-30 10:47:57 -08007import pprint
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -08008import random
Srikrishna Iyer09a81e92019-12-30 10:47:57 -08009import re
10import shlex
Rupert Swarbrick6cc20112020-04-24 09:44:35 +010011import subprocess
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080012import sys
13import time
Srikrishna Iyer32aae3f2020-03-19 17:34:00 -070014from collections import OrderedDict
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080015
Srikrishna Iyer2a710a42020-02-10 10:39:15 -080016from tabulate import tabulate
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080017
Rupert Swarbrick6cc20112020-04-24 09:44:35 +010018from sim_utils import get_cov_summary_table
19from utils import VERBOSE, find_and_substitute_wildcards, run_cmd
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080020
21
22class Deploy():
23 """
24 Abstraction for deploying builds and runs.
25 """
26
Srikrishna Iyer6b869132020-03-23 23:48:35 -070027 # Timer in hours, minutes and seconds.
28 hh = 0
29 mm = 0
30 ss = 0
Srikrishna Iyer7702be52020-03-07 01:00:28 -080031
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -080032 # Maintain a list of dispatched items.
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080033 dispatch_counter = 0
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -080034
35 # Misc common deploy settings.
Srikrishna Iyer7702be52020-03-07 01:00:28 -080036 print_legend = True
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080037 print_interval = 5
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -080038 max_parallel = 16
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080039 max_odirs = 5
Weicai Yang82445bc2020-04-02 13:38:02 -070040 # Max jobs dispatched in one go.
41 slot_limit = 20
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080042
43 def __self_str__(self):
44 if log.getLogger().isEnabledFor(VERBOSE):
45 return pprint.pformat(self.__dict__)
46 else:
47 ret = self.cmd
Rupert Swarbrick6cc20112020-04-24 09:44:35 +010048 if self.sub != []:
49 ret += "\nSub:\n" + str(self.sub)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080050 return ret
51
52 def __str__(self):
53 return self.__self_str__()
54
55 def __repr__(self):
56 return self.__self_str__()
57
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -080058 def __init__(self, sim_cfg):
59 # Cross ref the whole cfg object for ease.
60 self.sim_cfg = sim_cfg
61
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080062 # Common vars
Srikrishna Iyer7702be52020-03-07 01:00:28 -080063 self.identifier = ""
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080064 self.cmd = ""
65 self.odir = ""
66 self.log = ""
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -080067 self.fail_msg = ""
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080068
69 # Flag to indicate whether to 'overwrite' if odir already exists,
70 # or to backup the existing one and create a new one.
71 # For builds, we want to overwrite existing to leverage the tools'
72 # incremental / partition compile features. For runs, we may want to
73 # create a new one.
74 self.renew_odir = False
75
76 # List of vars required to be exported to sub-shell
77 self.exports = {}
78
79 # Deploy sub commands
80 self.sub = []
81
82 # Process
83 self.process = None
84 self.log_fd = None
85 self.status = None
86
Srikrishna Iyer2a710a42020-02-10 10:39:15 -080087 # These are command, output directory and log file
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080088 self.mandatory_misc_attrs.update({
89 "name": False,
90 "build_mode": False,
91 "flow_makefile": False,
92 "exports": False,
93 "dry_run": False
94 })
95
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -080096 # Function to parse a dict and extract the mandatory cmd and misc attrs.
97 def parse_dict(self, ddict):
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080098 if not hasattr(self, "target"):
99 log.error(
100 "Class %s does not have the mandatory attribute \"target\" defined",
101 self.__class__.__name__)
102 sys.exit(1)
103
104 ddict_keys = ddict.keys()
105 for key in self.mandatory_cmd_attrs.keys():
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100106 if self.mandatory_cmd_attrs[key] is False:
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800107 if key in ddict_keys:
108 setattr(self, key, ddict[key])
109 self.mandatory_cmd_attrs[key] = True
110
111 for key in self.mandatory_misc_attrs.keys():
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100112 if self.mandatory_misc_attrs[key] is False:
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800113 if key in ddict_keys:
114 setattr(self, key, ddict[key])
115 self.mandatory_misc_attrs[key] = True
116
117 def __post_init__(self):
118 # Ensure all mandatory attrs are set
119 for attr in self.mandatory_cmd_attrs.keys():
120 if self.mandatory_cmd_attrs[attr] is False:
121 log.error("Attribute \"%s\" not found for \"%s\".", attr,
122 self.name)
123 sys.exit(1)
124
125 for attr in self.mandatory_misc_attrs.keys():
126 if self.mandatory_misc_attrs[attr] is False:
127 log.error("Attribute \"%s\" not found for \"%s\".", attr,
128 self.name)
129 sys.exit(1)
130
131 # Recursively search and replace wildcards
132 self.__dict__ = find_and_substitute_wildcards(self.__dict__,
133 self.__dict__)
134
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800135 # Set identifier.
136 self.identifier = self.sim_cfg.name + ":" + self.name
137
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800138 # Set the command, output dir and log
139 self.odir = getattr(self, self.target + "_dir")
140 # Set the output dir link name to the basename of odir (by default)
141 self.odir_ln = os.path.basename(os.path.normpath(self.odir))
142 self.log = self.odir + "/" + self.target + ".log"
143
144 # If using LSF, redirect stdout and err to the log file
145 self.cmd = self.construct_cmd()
146
147 def construct_cmd(self):
148 cmd = "make -f " + self.flow_makefile + " " + self.target
149 if self.dry_run is True:
150 cmd += " -n"
151 for attr in self.mandatory_cmd_attrs.keys():
152 value = getattr(self, attr)
153 if type(value) is list:
154 pretty_value = []
155 for item in value:
156 pretty_value.append(item.strip())
157 value = " ".join(pretty_value)
158 if type(value) is bool:
159 value = int(value)
160 if type(value) is str:
161 value = value.strip()
162 cmd += " " + attr + "=\"" + str(value) + "\""
163
164 # TODO: If not running locally, redirect stdout and err to the log file
165 # self.cmd += " > " + self.log + " 2>&1 &"
166 return cmd
167
168 def dispatch_cmd(self):
169 self.exports.update(os.environ)
170 args = shlex.split(self.cmd)
171 try:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800172 # If renew_odir flag is True - then move it.
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100173 if self.renew_odir:
174 self.odir_limiter(odir=self.odir)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800175 os.system("mkdir -p " + self.odir)
Srikrishna Iyer39ffebd2020-03-30 11:53:12 -0700176 # Dump all env variables for ease of debug.
Philipp Wagnerebeb7962020-05-05 11:13:59 +0100177 with open(self.odir + "/env_vars", "w", encoding="UTF-8") as f:
Srikrishna Iyer39ffebd2020-03-30 11:53:12 -0700178 for var in sorted(self.exports.keys()):
179 f.write("{}={}\n".format(var, self.exports[var]))
180 f.close()
Srikrishna Iyer544da8d2020-01-14 23:51:41 -0800181 os.system("ln -s " + self.odir + " " + self.sim_cfg.links['D'] +
182 '/' + self.odir_ln)
Philipp Wagnerebeb7962020-05-05 11:13:59 +0100183 f = open(self.log, "w", encoding="UTF-8")
Srikrishna Iyer5b7f5de2020-05-12 00:28:05 -0700184 f.write("[Executing]:\n{}\n\n".format(self.cmd))
185 f.flush()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800186 self.process = subprocess.Popen(args,
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800187 bufsize=4096,
188 universal_newlines=True,
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800189 stdout=f,
190 stderr=f,
191 env=self.exports)
192 self.log_fd = f
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800193 self.status = "D"
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800194 Deploy.dispatch_counter += 1
195 except IOError:
196 log.error('IO Error: See %s', self.log)
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100197 if self.log_fd:
198 self.log_fd.close()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800199 self.status = "K"
200
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800201 def odir_limiter(self, odir, max_odirs=-1):
202 '''Function to backup previously run output directory to maintain a
203 history of a limited number of output directories. It deletes the output
204 directory with the oldest timestamps, if the limit is reached. It returns
205 a list of directories that remain after deletion.
206 Arguments:
207 odir: The output directory to backup
208 max_odirs: Maximum output directories to maintain as history.
209
210 Returns:
211 dirs: Space-separated list of directories that remain after deletion.
212 '''
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800213 try:
214 # If output directory exists, back it up.
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800215 if os.path.exists(odir):
Srikrishna Iyer64009052020-01-13 11:27:39 -0800216 ts = run_cmd("date '+" + self.sim_cfg.ts_format + "' -d \"" +
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800217 "$(stat -c '%y' " + odir + ")\"")
218 os.system('mv ' + odir + " " + odir + "_" + ts)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800219 except IOError:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800220 log.error('Failed to back up existing output directory %s', odir)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800221
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800222 dirs = ""
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800223 # Delete older directories.
224 try:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800225 pdir = os.path.realpath(odir + "/..")
226 # Fatal out if pdir got set to root.
227 if pdir == "/":
228 log.fatal(
229 "Something went wrong while processing \"%s\": odir = \"%s\"",
230 self.name, odir)
231 sys.exit(1)
232
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800233 if os.path.exists(pdir):
234 find_cmd = "find " + pdir + " -mindepth 1 -maxdepth 1 -type d "
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800235 dirs = run_cmd(find_cmd)
236 dirs = dirs.replace('\n', ' ')
237 list_dirs = dirs.split()
238 num_dirs = len(list_dirs)
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100239 if max_odirs == -1:
240 max_odirs = self.max_odirs
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800241 num_rm_dirs = num_dirs - max_odirs
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800242 if num_rm_dirs > -1:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800243 rm_dirs = run_cmd(find_cmd +
244 "-printf '%T+ %p\n' | sort | head -n " +
245 str(num_rm_dirs + 1) +
246 " | awk '{print $2}'")
247 rm_dirs = rm_dirs.replace('\n', ' ')
248 dirs = dirs.replace(rm_dirs, "")
Srikrishna Iyer32aae3f2020-03-19 17:34:00 -0700249 os.system("/bin/rm -rf " + rm_dirs)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800250 except IOError:
251 log.error("Failed to delete old run directories!")
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800252 return dirs
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800253
254 def set_status(self):
255 self.status = 'P'
256 if self.dry_run is False:
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800257 for fail_pattern in self.fail_patterns:
Weicai Yang31029d22020-03-24 11:14:56 -0700258 # Return error messege with the following 4 lines.
259 grep_cmd = "grep -m 1 -A 4 -E \'" + fail_pattern + "\' " + self.log
260 (status, rslt) = subprocess.getstatusoutput(grep_cmd)
261 if rslt:
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800262 msg = "```\n{}\n```\n".format(rslt)
263 self.fail_msg += msg
264 log.log(VERBOSE, msg)
265 self.status = 'F'
266 break
267
Weicai Yang31029d22020-03-24 11:14:56 -0700268 # If fail patterns were not encountered, but the job returned with non-zero exit code
269 # for whatever reason, then show the last 10 lines of the log as the failure message,
270 # which might help with the debug.
271 if self.process.returncode != 0 and not self.fail_msg:
272 msg = "Last 10 lines of the log:<br>\n"
273 self.fail_msg += msg
274 log.log(VERBOSE, msg)
275 get_fail_msg_cmd = "tail -n 10 " + self.log
276 msg = run_cmd(get_fail_msg_cmd)
277 msg = "```\n{}\n```\n".format(msg)
278 self.fail_msg += msg
279 log.log(VERBOSE, msg)
280 self.status = "F"
281
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800282 # Return if status is fail - no need to look for pass patterns.
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100283 if self.status == 'F':
284 return
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800285
286 # If fail patterns were not found, ensure pass patterns indeed were.
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800287 for pass_pattern in self.pass_patterns:
288 grep_cmd = "grep -c -m 1 -E \'" + pass_pattern + "\' " + self.log
289 (status, rslt) = subprocess.getstatusoutput(grep_cmd)
290 if rslt == "0":
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800291 msg = "Pass pattern \"{}\" not found.<br>\n".format(
292 pass_pattern)
293 self.fail_msg += msg
294 log.log(VERBOSE, msg)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800295 self.status = 'F'
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800296 break
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800297
298 # Recursively set sub-item's status if parent item fails
299 def set_sub_status(self, status):
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800300 for sub_item in self.sub:
301 sub_item.status = status
302 sub_item.set_sub_status(status)
303
304 def link_odir(self):
305 if self.status == '.':
306 log.error("Method unexpectedly called!")
307 else:
Srikrishna Iyer228f1c12020-01-17 10:54:48 -0800308 old_link = self.sim_cfg.links['D'] + "/" + self.odir_ln
309 new_link = self.sim_cfg.links[self.status] + "/" + self.odir_ln
310 cmd = "ln -s " + self.odir + " " + new_link + "; "
311 cmd += "rm " + old_link
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100312 if os.system(cmd):
Srikrishna Iyer228f1c12020-01-17 10:54:48 -0800313 log.error("Cmd \"%s\" could not be run", cmd)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800314
315 def get_status(self):
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100316 if self.status != "D":
317 return
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800318 if self.process.poll() is not None:
319 self.log_fd.close()
Weicai Yang31029d22020-03-24 11:14:56 -0700320 self.set_status()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800321
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800322 log.debug("Item %s has completed execution: %s", self.name,
323 self.status)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800324 Deploy.dispatch_counter -= 1
325 self.link_odir()
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800326 del self.process
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800327
Weicai Yangfc2ff3b2020-03-19 18:05:14 -0700328 def kill(self):
329 '''Kill running processes.
330 '''
331 if self.status == "D" and self.process.poll() is None:
332 self.kill_remote_job()
333 self.process.kill()
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100334 if self.log_fd:
335 self.log_fd.close()
Weicai Yangfc2ff3b2020-03-19 18:05:14 -0700336 self.status = "K"
337 # recurisvely kill sub target
338 elif len(self.sub):
339 for item in self.sub:
340 item.kill()
341
342 def kill_remote_job(self):
343 '''
344 If jobs are run in remote server, need to use another command to kill them.
345 '''
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100346 # TODO: Currently only support lsf, may need to add support for GCP later.
Weicai Yangfc2ff3b2020-03-19 18:05:14 -0700347
348 # If use lsf, kill it by job ID.
349 if re.match("^bsub", self.sim_cfg.job_prefix):
350 # get job id from below string
351 # Job <xxxxxx> is submitted to default queue
352 grep_cmd = "grep -m 1 -E \'" + "^Job <" + "\' " + self.log
353 (status, rslt) = subprocess.getstatusoutput(grep_cmd)
354 if rslt != "":
355 job_id = rslt.split('Job <')[1].split('>')[0]
356 try:
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100357 subprocess.run(["bkill", job_id], check=True)
Weicai Yangfc2ff3b2020-03-19 18:05:14 -0700358 except Exception as e:
359 log.error("%s: Failed to run bkill\n", e)
360
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800361 @staticmethod
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700362 def increment_timer():
363 # sub function that increments with overflow = 60
364 def _incr_ovf_60(val):
365 if val >= 59:
366 val = 0
367 return val, True
368 else:
369 val += 1
370 return val, False
371
372 incr_hh = False
373 Deploy.ss, incr_mm = _incr_ovf_60(Deploy.ss)
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100374 if incr_mm:
375 Deploy.mm, incr_hh = _incr_ovf_60(Deploy.mm)
376 if incr_hh:
377 Deploy.hh += 1
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700378
379 @staticmethod
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800380 def deploy(items):
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800381 dispatched_items = []
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700382 queued_items = []
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800383
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700384 # Print timer val in hh:mm:ss.
385 def get_timer_val():
386 return "%02i:%02i:%02i" % (Deploy.hh, Deploy.mm, Deploy.ss)
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800387
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700388 # Check if elapsed time has reached the next print interval.
389 def has_print_interval_reached():
390 # Deploy.print_interval is expected to be < 1 hour.
391 return (((Deploy.mm * 60 + Deploy.ss) %
392 Deploy.print_interval) == 0)
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800393
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800394 def dispatch_items(items):
Srikrishna Iyer32aae3f2020-03-19 17:34:00 -0700395 item_names = OrderedDict()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800396 for item in items:
397 if item.target not in item_names.keys():
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800398 item_names[item.target] = ""
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800399 if item.status is None:
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800400 item_names[item.target] += item.identifier + ", "
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800401 item.dispatch_cmd()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800402
403 for target in item_names.keys():
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800404 if item_names[target] != "":
405 item_names[target] = " [" + item_names[target][:-2] + "]"
406 log.log(VERBOSE, "[%s]: [%s]: [dispatch]:\n%s",
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700407 get_timer_val(), target, item_names[target])
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800408
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700409 # Initialize status for a target, add '_stats_' for the said target
410 # and initialize counters for queued, dispatched, passed, failed,
411 # killed and total to 0. Also adds a boolean key to indicate if all
412 # items in a given target are done.
413 def init_status_target_stats(status, target):
414 status[target] = OrderedDict()
415 status[target]['_stats_'] = OrderedDict()
416 status[target]['_stats_']['Q'] = 0
417 status[target]['_stats_']['D'] = 0
418 status[target]['_stats_']['P'] = 0
419 status[target]['_stats_']['F'] = 0
420 status[target]['_stats_']['K'] = 0
421 status[target]['_stats_']['T'] = 0
422 status[target]['_done_'] = False
423
424 # Update status counter for a newly queued item.
425 def add_status_target_queued(status, item):
426 if item.target not in status.keys():
427 init_status_target_stats(status, item.target)
428 status[item.target][item] = "Q"
429 status[item.target]['_stats_']['Q'] += 1
430 status[item.target]['_stats_']['T'] += 1
431
432 # Update status counters for a target.
433 def update_status_target_stats(status, item):
434 old_status = status[item.target][item]
435 status[item.target]['_stats_'][old_status] -= 1
436 status[item.target]['_stats_'][item.status] += 1
437 status[item.target][item] = item.status
438
439 def check_if_done_and_print_status(status, print_status_flag):
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800440 all_done = True
441 for target in status.keys():
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700442 target_done_prev = status[target]['_done_']
443 target_done_curr = ((status[target]['_stats_']["Q"] == 0) and
444 (status[target]['_stats_']["D"] == 0))
445 status[target]['_done_'] = target_done_curr
446 all_done &= target_done_curr
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800447
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700448 # Print if flag is set and target_done is not True for two
449 # consecutive times.
450 if not (target_done_prev and
451 target_done_curr) and print_status_flag:
452 stats = status[target]['_stats_']
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800453 width = "0{}d".format(len(str(stats["T"])))
454 msg = "["
455 for s in stats.keys():
456 msg += s + ": {:{}}, ".format(stats[s], width)
457 msg = msg[:-2] + "]"
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700458 log.info("[%s]: [%s]: %s", get_timer_val(), target, msg)
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800459 return all_done
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800460
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700461 # Print legend once at the start of the run.
462 if Deploy.print_legend:
463 log.info("[legend]: [Q: queued, D: dispatched, "
464 "P: passed, F: failed, K: killed, T: total]")
465 Deploy.print_legend = False
466
Srikrishna Iyer32aae3f2020-03-19 17:34:00 -0700467 status = OrderedDict()
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700468 print_status_flag = True
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800469
470 # Queue all items
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700471 queued_items = items
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800472 for item in queued_items:
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700473 add_status_target_queued(status, item)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800474
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700475 all_done = False
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800476 while not all_done:
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700477 # Get status of dispatched items.
478 for item in dispatched_items:
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100479 if item.status == "D":
480 item.get_status()
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700481 if item.status != status[item.target][item]:
482 print_status_flag = True
483 if item.status != "D":
484 if item.status != "P":
485 # Kill its sub items if item did not pass.
486 item.set_sub_status("K")
487 log.error("[%s]: [%s]: [status] [%s: %s]",
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700488 get_timer_val(), item.target,
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700489 item.identifier, item.status)
490 else:
491 log.log(VERBOSE, "[%s]: [%s]: [status] [%s: %s]",
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700492 get_timer_val(), item.target,
493 item.identifier, item.status)
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700494 # Queue items' sub-items if it is done.
495 queued_items.extend(item.sub)
496 for sub_item in item.sub:
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700497 add_status_target_queued(status, sub_item)
498 update_status_target_stats(status, item)
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700499
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800500 # Dispatch items from the queue as slots free up.
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700501 all_done = (len(queued_items) == 0)
502 if not all_done:
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800503 num_slots = Deploy.max_parallel - Deploy.dispatch_counter
Weicai Yang82445bc2020-04-02 13:38:02 -0700504 if num_slots > Deploy.slot_limit:
505 num_slots = Deploy.slot_limit
Srikrishna Iyere19f42b2020-01-08 17:51:36 -0800506 if num_slots > 0:
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800507 if len(queued_items) > num_slots:
508 dispatch_items(queued_items[0:num_slots])
509 dispatched_items.extend(queued_items[0:num_slots])
510 queued_items = queued_items[num_slots:]
Srikrishna Iyere19f42b2020-01-08 17:51:36 -0800511 else:
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800512 dispatch_items(queued_items)
513 dispatched_items.extend(queued_items)
Cindy Chen8abbc602020-03-25 12:48:10 -0700514 queued_items = []
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800515
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800516 # Check if we are done and print the status periodically.
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700517 all_done &= check_if_done_and_print_status(status,
518 print_status_flag)
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700519
520 # Advance time by 1s if there is more work to do.
521 if not all_done:
522 time.sleep(1)
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700523 Deploy.increment_timer()
524 print_status_flag = has_print_interval_reached()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800525
526
527class CompileSim(Deploy):
528 """
529 Abstraction for building the simulation executable.
530 """
531
532 # Register all builds with the class
533 items = []
534
535 def __init__(self, build_mode, sim_cfg):
536 self.target = "build"
537 self.pass_patterns = []
538 self.fail_patterns = []
539
Srikrishna Iyer8ce80d02020-02-05 10:53:19 -0800540 self.mandatory_cmd_attrs = {
541 # tool srcs
Weicai Yang680f7e22020-02-26 18:10:24 -0800542 "tool_srcs": False,
543 "tool_srcs_dir": False,
Srikrishna Iyer8ce80d02020-02-05 10:53:19 -0800544
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800545 # Flist gen
546 "sv_flist_gen_cmd": False,
547 "sv_flist_gen_dir": False,
548 "sv_flist_gen_opts": False,
549
550 # Build
551 "build_dir": False,
552 "build_cmd": False,
553 "build_opts": False
554 }
555
Weicai Yang31029d22020-03-24 11:14:56 -0700556 self.mandatory_misc_attrs = {
557 "cov_db_dir": False,
558 "build_pass_patterns": False,
559 "build_fail_patterns": False
560 }
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800561
562 # Initialize
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800563 super().__init__(sim_cfg)
564 super().parse_dict(build_mode.__dict__)
565 # Call this method again with the sim_cfg dict passed as the object,
566 # since it may contain additional mandatory attrs.
567 super().parse_dict(sim_cfg.__dict__)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800568 self.build_mode = self.name
Weicai Yang31029d22020-03-24 11:14:56 -0700569 self.pass_patterns = self.build_pass_patterns
570 self.fail_patterns = self.build_fail_patterns
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800571 self.__post_init__()
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800572
573 # Start fail message construction
574 self.fail_msg = "\n**BUILD:** {}<br>\n".format(self.name)
Srikrishna Iyerf578e7c2020-01-29 13:11:58 -0800575 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
576 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800577
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800578 CompileSim.items.append(self)
579
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800580 def dispatch_cmd(self):
581 # Delete previous cov_db_dir if it exists before dispatching new build.
582 if os.path.exists(self.cov_db_dir):
583 os.system("rm -rf " + self.cov_db_dir)
584 super().dispatch_cmd()
585
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800586
Michael Schaffner8ac6c4c2020-03-03 15:00:20 -0800587class CompileOneShot(Deploy):
588 """
589 Abstraction for building the simulation executable.
590 """
591
592 # Register all builds with the class
593 items = []
594
595 def __init__(self, build_mode, sim_cfg):
596 self.target = "build"
597 self.pass_patterns = []
598 self.fail_patterns = []
599
600 self.mandatory_cmd_attrs = {
601 # tool srcs
602 "tool_srcs": False,
603 "tool_srcs_dir": False,
604
Michael Schaffner3d160992020-03-31 18:37:53 -0700605 # Flist gen
606 "sv_flist_gen_cmd": False,
607 "sv_flist_gen_dir": False,
608 "sv_flist_gen_opts": False,
609
Michael Schaffner8ac6c4c2020-03-03 15:00:20 -0800610 # Build
611 "build_dir": False,
612 "build_cmd": False,
613 "build_opts": False,
Michael Schaffner3d160992020-03-31 18:37:53 -0700614 "build_log": False,
Michael Schaffner8ac6c4c2020-03-03 15:00:20 -0800615
616 # Report processing
617 "report_cmd": False,
618 "report_opts": False
619 }
620
621 self.mandatory_misc_attrs = {}
622
623 # Initialize
624 super().__init__(sim_cfg)
625 super().parse_dict(build_mode.__dict__)
626 # Call this method again with the sim_cfg dict passed as the object,
627 # since it may contain additional mandatory attrs.
628 super().parse_dict(sim_cfg.__dict__)
629 self.build_mode = self.name
630 self.__post_init__()
631
632 # Start fail message construction
633 self.fail_msg = "\n**BUILD:** {}<br>\n".format(self.name)
634 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
635 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
636
637 CompileOneShot.items.append(self)
638
639
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800640class RunTest(Deploy):
641 """
642 Abstraction for running tests. This is one per seed for each test.
643 """
644
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800645 # Initial seed values when running tests (if available).
646 seeds = []
Srikrishna Iyer96e54102020-03-12 22:46:50 -0700647 fixed_seed = None
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800648
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800649 # Register all runs with the class
650 items = []
651
652 def __init__(self, index, test, sim_cfg):
653 self.target = "run"
654 self.pass_patterns = []
655 self.fail_patterns = []
656
657 self.mandatory_cmd_attrs = {
Srikrishna Iyer42d032f2020-03-04 23:55:44 -0800658 "proj_root": False,
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800659 "uvm_test": False,
660 "uvm_test_seq": False,
661 "run_opts": False,
Srikrishna Iyerc612bb02020-05-12 00:33:27 -0700662 "sw_test": False,
663 "sw_test_is_prebuilt": False,
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800664 "sw_build_device": False,
665 "sw_build_dir": False,
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800666 "run_dir": False,
667 "run_cmd": False,
668 "run_opts": False
669 }
670
671 self.mandatory_misc_attrs = {
672 "run_dir_name": False,
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800673 "cov_db_test_dir": False,
Weicai Yang31029d22020-03-24 11:14:56 -0700674 "run_pass_patterns": False,
675 "run_fail_patterns": False
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800676 }
677
678 self.index = index
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800679 self.seed = RunTest.get_seed()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800680
681 # Initialize
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800682 super().__init__(sim_cfg)
683 super().parse_dict(test.__dict__)
684 # Call this method again with the sim_cfg dict passed as the object,
685 # since it may contain additional mandatory attrs.
686 super().parse_dict(sim_cfg.__dict__)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800687 self.test = self.name
688 self.renew_odir = True
689 self.build_mode = test.build_mode.name
Weicai Yang31029d22020-03-24 11:14:56 -0700690 self.pass_patterns = self.run_pass_patterns
691 self.fail_patterns = self.run_fail_patterns
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800692 self.__post_init__()
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800693 # For output dir link, use run_dir_name instead.
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800694 self.odir_ln = self.run_dir_name
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800695
696 # Start fail message construction
697 self.fail_msg = "\n**TEST:** {}, ".format(self.name)
698 self.fail_msg += "**SEED:** {}<br>\n".format(self.seed)
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800699 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
700 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800701
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800702 RunTest.items.append(self)
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800703
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800704 def __post_init__(self):
705 super().__post_init__()
706 # Set identifier.
707 self.identifier = self.sim_cfg.name + ":" + self.run_dir_name
708
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800709 def get_status(self):
710 '''Override base class get_status implementation for additional post-status
711 actions.'''
712 super().get_status()
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800713 if self.status not in ["D", "P"]:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800714 # Delete the coverage data if available.
715 if os.path.exists(self.cov_db_test_dir):
716 log.log(VERBOSE, "Deleting coverage data of failing test:\n%s",
717 self.cov_db_test_dir)
Srikrishna Iyer32aae3f2020-03-19 17:34:00 -0700718 os.system("/bin/rm -rf " + self.cov_db_test_dir)
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800719
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800720 @staticmethod
721 def get_seed():
Srikrishna Iyer96e54102020-03-12 22:46:50 -0700722 # If --seeds option is passed, then those custom seeds are consumed
723 # first. If --fixed-seed <val> is also passed, the subsequent tests
724 # (once the custom seeds are consumed) will be run with the fixed seed.
Philipp Wagner9a521782020-02-25 11:49:53 +0000725 if not RunTest.seeds:
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100726 if RunTest.fixed_seed:
727 return RunTest.fixed_seed
Srikrishna Iyera821bbc2020-01-31 00:28:02 -0800728 for i in range(1000):
Philipp Wagner75fd6c72020-02-25 11:47:41 +0000729 seed = random.getrandbits(32)
Srikrishna Iyera821bbc2020-01-31 00:28:02 -0800730 RunTest.seeds.append(seed)
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800731 return RunTest.seeds.pop(0)
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800732
733
734class CovMerge(Deploy):
735 """
736 Abstraction for merging coverage databases. An item of this class is created AFTER
737 the regression is completed.
738 """
739
740 # Register all builds with the class
741 items = []
742
743 def __init__(self, sim_cfg):
744 self.target = "cov_merge"
745 self.pass_patterns = []
746 self.fail_patterns = []
747
748 # Construct local 'special' variable from cov directories that need to
749 # be merged.
750 self.cov_db_dirs = ""
751
752 self.mandatory_cmd_attrs = {
753 "cov_merge_cmd": False,
754 "cov_merge_opts": False
755 }
756
757 self.mandatory_misc_attrs = {
758 "cov_merge_dir": False,
759 "cov_merge_db_dir": False
760 }
761
762 # Initialize
763 super().__init__(sim_cfg)
764 super().parse_dict(sim_cfg.__dict__)
765 self.__post_init__()
766
767 # Override standard output and log patterns.
768 self.odir = self.cov_merge_db_dir
769 self.odir_ln = os.path.basename(os.path.normpath(self.odir))
770
771 # Start fail message construction
772 self.fail_msg = "\n**COV_MERGE:** {}<br>\n".format(self.name)
773 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
774 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
775
776 CovMerge.items.append(self)
777
778 def __post_init__(self):
779 # Add cov db dirs from all the builds that were kicked off.
780 for bld in self.sim_cfg.builds:
781 self.cov_db_dirs += bld.cov_db_dir + " "
782
783 # Recursively search and replace wildcards, ignoring cov_db_dirs for now.
784 # We need to resolve it later based on cov_db_dirs value set below.
785 self.__dict__ = find_and_substitute_wildcards(
786 self.__dict__, self.__dict__, ignored_wildcards=["cov_db_dirs"])
787
788 # Prune previous merged cov directories.
789 prev_cov_db_dirs = self.odir_limiter(odir=self.cov_merge_db_dir)
790
791 # If a merged cov data base exists from a previous run, then consider
792 # that as well for merging, if the --cov-merge-previous command line
793 # switch is passed.
794 if self.sim_cfg.cov_merge_previous:
795 self.cov_db_dirs += prev_cov_db_dirs
796
Srikrishna Iyer39ffebd2020-03-30 11:53:12 -0700797 # Append cov_db_dirs to the list of exports.
798 self.exports["cov_db_dirs"] = "\"{}\"".format(self.cov_db_dirs)
799
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800800 # Call base class __post_init__ to do checks and substitutions
801 super().__post_init__()
802
803
804class CovReport(Deploy):
805 """
806 Abstraction for coverage report generation. An item of this class is created AFTER
807 the regression is completed.
808 """
809
810 # Register all builds with the class
811 items = []
812
813 def __init__(self, sim_cfg):
814 self.target = "cov_report"
815 self.pass_patterns = []
816 self.fail_patterns = []
Weicai Yang680f7e22020-02-26 18:10:24 -0800817 self.cov_total = ""
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800818 self.cov_results = ""
819
820 self.mandatory_cmd_attrs = {
821 "cov_report_cmd": False,
822 "cov_report_opts": False
823 }
824
825 self.mandatory_misc_attrs = {
826 "cov_report_dir": False,
827 "cov_merge_db_dir": False,
Srikrishna Iyer39ffebd2020-03-30 11:53:12 -0700828 "cov_report_txt": False
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800829 }
830
831 # Initialize
832 super().__init__(sim_cfg)
833 super().parse_dict(sim_cfg.__dict__)
834 self.__post_init__()
835
836 # Start fail message construction
837 self.fail_msg = "\n**COV_REPORT:** {}<br>\n".format(self.name)
838 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
839 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
840
841 CovReport.items.append(self)
842
843 def get_status(self):
844 super().get_status()
845 # Once passed, extract the cov results summary from the dashboard.
846 if self.status == "P":
Srikrishna Iyer39ffebd2020-03-30 11:53:12 -0700847 results, self.cov_total, ex_msg = get_cov_summary_table(
848 self.cov_report_txt, self.sim_cfg.tool)
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800849
Srikrishna Iyer39ffebd2020-03-30 11:53:12 -0700850 if not ex_msg:
851 # Succeeded in obtaining the coverage data.
852 colalign = (("center", ) * len(results[0]))
853 self.cov_results = tabulate(results,
854 headers="firstrow",
855 tablefmt="pipe",
856 colalign=colalign)
857 else:
Srikrishna Iyer86f6bce2020-02-27 19:02:04 -0800858 self.fail_msg += ex_msg
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800859 log.error(ex_msg)
860 self.status = "F"
861
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800862 if self.status == "P":
863 # Delete the cov report - not needed.
864 os.system("rm -rf " + self.log)
865
866
867class CovAnalyze(Deploy):
868 """
869 Abstraction for coverage analysis tool.
870 """
871
872 # Register all builds with the class
873 items = []
874
875 def __init__(self, sim_cfg):
876 self.target = "cov_analyze"
877 self.pass_patterns = []
878 self.fail_patterns = []
879
880 self.mandatory_cmd_attrs = {
Srikrishna Iyer39ffebd2020-03-30 11:53:12 -0700881 # tool srcs
882 "tool_srcs": False,
883 "tool_srcs_dir": False,
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800884 "cov_analyze_cmd": False,
885 "cov_analyze_opts": False
886 }
887
888 self.mandatory_misc_attrs = {
889 "cov_analyze_dir": False,
890 "cov_merge_db_dir": False
891 }
892
893 # Initialize
894 super().__init__(sim_cfg)
895 super().parse_dict(sim_cfg.__dict__)
896 self.__post_init__()
897
898 # Start fail message construction
899 self.fail_msg = "\n**COV_ANALYZE:** {}<br>\n".format(self.name)
900 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
901 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
902
903 CovAnalyze.items.append(self)