blob: b0dde6ac76ddc90237eefd2984a2836d46458cfc [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 Wagnerc041ff72020-05-20 10:00:40 +0100177 with open(self.odir + "/env_vars",
178 "w",
179 encoding="UTF-8",
180 errors="surrogateescape") as f:
Srikrishna Iyer39ffebd2020-03-30 11:53:12 -0700181 for var in sorted(self.exports.keys()):
182 f.write("{}={}\n".format(var, self.exports[var]))
183 f.close()
Srikrishna Iyer544da8d2020-01-14 23:51:41 -0800184 os.system("ln -s " + self.odir + " " + self.sim_cfg.links['D'] +
185 '/' + self.odir_ln)
Philipp Wagnerc041ff72020-05-20 10:00:40 +0100186 f = open(self.log, "w", encoding="UTF-8", errors="surrogateescape")
Srikrishna Iyer5b7f5de2020-05-12 00:28:05 -0700187 f.write("[Executing]:\n{}\n\n".format(self.cmd))
188 f.flush()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800189 self.process = subprocess.Popen(args,
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800190 bufsize=4096,
191 universal_newlines=True,
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800192 stdout=f,
193 stderr=f,
194 env=self.exports)
195 self.log_fd = f
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800196 self.status = "D"
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800197 Deploy.dispatch_counter += 1
198 except IOError:
199 log.error('IO Error: See %s', self.log)
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100200 if self.log_fd:
201 self.log_fd.close()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800202 self.status = "K"
203
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800204 def odir_limiter(self, odir, max_odirs=-1):
205 '''Function to backup previously run output directory to maintain a
206 history of a limited number of output directories. It deletes the output
207 directory with the oldest timestamps, if the limit is reached. It returns
208 a list of directories that remain after deletion.
209 Arguments:
210 odir: The output directory to backup
211 max_odirs: Maximum output directories to maintain as history.
212
213 Returns:
214 dirs: Space-separated list of directories that remain after deletion.
215 '''
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800216 try:
217 # If output directory exists, back it up.
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800218 if os.path.exists(odir):
Srikrishna Iyer64009052020-01-13 11:27:39 -0800219 ts = run_cmd("date '+" + self.sim_cfg.ts_format + "' -d \"" +
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800220 "$(stat -c '%y' " + odir + ")\"")
221 os.system('mv ' + odir + " " + odir + "_" + ts)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800222 except IOError:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800223 log.error('Failed to back up existing output directory %s', odir)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800224
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800225 dirs = ""
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800226 # Delete older directories.
227 try:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800228 pdir = os.path.realpath(odir + "/..")
229 # Fatal out if pdir got set to root.
230 if pdir == "/":
231 log.fatal(
232 "Something went wrong while processing \"%s\": odir = \"%s\"",
233 self.name, odir)
234 sys.exit(1)
235
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800236 if os.path.exists(pdir):
237 find_cmd = "find " + pdir + " -mindepth 1 -maxdepth 1 -type d "
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800238 dirs = run_cmd(find_cmd)
239 dirs = dirs.replace('\n', ' ')
240 list_dirs = dirs.split()
241 num_dirs = len(list_dirs)
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100242 if max_odirs == -1:
243 max_odirs = self.max_odirs
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800244 num_rm_dirs = num_dirs - max_odirs
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800245 if num_rm_dirs > -1:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800246 rm_dirs = run_cmd(find_cmd +
247 "-printf '%T+ %p\n' | sort | head -n " +
248 str(num_rm_dirs + 1) +
249 " | awk '{print $2}'")
250 rm_dirs = rm_dirs.replace('\n', ' ')
251 dirs = dirs.replace(rm_dirs, "")
Srikrishna Iyer32aae3f2020-03-19 17:34:00 -0700252 os.system("/bin/rm -rf " + rm_dirs)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800253 except IOError:
254 log.error("Failed to delete old run directories!")
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800255 return dirs
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800256
257 def set_status(self):
258 self.status = 'P'
259 if self.dry_run is False:
Rupert Swarbrick0441f322020-06-05 12:10:52 +0100260 seen_fail_pattern = False
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800261 for fail_pattern in self.fail_patterns:
Rupert Swarbrick0441f322020-06-05 12:10:52 +0100262 # Return error message with the following 4 lines.
Weicai Yang31029d22020-03-24 11:14:56 -0700263 grep_cmd = "grep -m 1 -A 4 -E \'" + fail_pattern + "\' " + self.log
264 (status, rslt) = subprocess.getstatusoutput(grep_cmd)
265 if rslt:
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800266 msg = "```\n{}\n```\n".format(rslt)
267 self.fail_msg += msg
268 log.log(VERBOSE, msg)
269 self.status = 'F'
Rupert Swarbrick0441f322020-06-05 12:10:52 +0100270 seen_fail_pattern = True
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800271 break
272
Weicai Yang31029d22020-03-24 11:14:56 -0700273 # If fail patterns were not encountered, but the job returned with non-zero exit code
274 # for whatever reason, then show the last 10 lines of the log as the failure message,
275 # which might help with the debug.
Rupert Swarbrick0441f322020-06-05 12:10:52 +0100276 if self.process.returncode != 0 and not seen_fail_pattern:
Weicai Yang31029d22020-03-24 11:14:56 -0700277 msg = "Last 10 lines of the log:<br>\n"
278 self.fail_msg += msg
279 log.log(VERBOSE, msg)
280 get_fail_msg_cmd = "tail -n 10 " + self.log
281 msg = run_cmd(get_fail_msg_cmd)
282 msg = "```\n{}\n```\n".format(msg)
283 self.fail_msg += msg
284 log.log(VERBOSE, msg)
285 self.status = "F"
286
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800287 # Return if status is fail - no need to look for pass patterns.
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100288 if self.status == 'F':
289 return
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800290
291 # If fail patterns were not found, ensure pass patterns indeed were.
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800292 for pass_pattern in self.pass_patterns:
293 grep_cmd = "grep -c -m 1 -E \'" + pass_pattern + "\' " + self.log
294 (status, rslt) = subprocess.getstatusoutput(grep_cmd)
295 if rslt == "0":
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800296 msg = "Pass pattern \"{}\" not found.<br>\n".format(
297 pass_pattern)
298 self.fail_msg += msg
299 log.log(VERBOSE, msg)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800300 self.status = 'F'
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800301 break
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800302
303 # Recursively set sub-item's status if parent item fails
304 def set_sub_status(self, status):
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800305 for sub_item in self.sub:
306 sub_item.status = status
307 sub_item.set_sub_status(status)
308
309 def link_odir(self):
310 if self.status == '.':
311 log.error("Method unexpectedly called!")
312 else:
Srikrishna Iyer228f1c12020-01-17 10:54:48 -0800313 old_link = self.sim_cfg.links['D'] + "/" + self.odir_ln
314 new_link = self.sim_cfg.links[self.status] + "/" + self.odir_ln
315 cmd = "ln -s " + self.odir + " " + new_link + "; "
316 cmd += "rm " + old_link
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100317 if os.system(cmd):
Srikrishna Iyer228f1c12020-01-17 10:54:48 -0800318 log.error("Cmd \"%s\" could not be run", cmd)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800319
320 def get_status(self):
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100321 if self.status != "D":
322 return
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800323 if self.process.poll() is not None:
324 self.log_fd.close()
Weicai Yang31029d22020-03-24 11:14:56 -0700325 self.set_status()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800326
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800327 log.debug("Item %s has completed execution: %s", self.name,
328 self.status)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800329 Deploy.dispatch_counter -= 1
330 self.link_odir()
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800331 del self.process
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800332
Weicai Yangfc2ff3b2020-03-19 18:05:14 -0700333 def kill(self):
334 '''Kill running processes.
335 '''
336 if self.status == "D" and self.process.poll() is None:
337 self.kill_remote_job()
338 self.process.kill()
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100339 if self.log_fd:
340 self.log_fd.close()
Weicai Yangfc2ff3b2020-03-19 18:05:14 -0700341 self.status = "K"
342 # recurisvely kill sub target
343 elif len(self.sub):
344 for item in self.sub:
345 item.kill()
346
347 def kill_remote_job(self):
348 '''
349 If jobs are run in remote server, need to use another command to kill them.
350 '''
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100351 # TODO: Currently only support lsf, may need to add support for GCP later.
Weicai Yangfc2ff3b2020-03-19 18:05:14 -0700352
353 # If use lsf, kill it by job ID.
354 if re.match("^bsub", self.sim_cfg.job_prefix):
355 # get job id from below string
356 # Job <xxxxxx> is submitted to default queue
357 grep_cmd = "grep -m 1 -E \'" + "^Job <" + "\' " + self.log
358 (status, rslt) = subprocess.getstatusoutput(grep_cmd)
359 if rslt != "":
360 job_id = rslt.split('Job <')[1].split('>')[0]
361 try:
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100362 subprocess.run(["bkill", job_id], check=True)
Weicai Yangfc2ff3b2020-03-19 18:05:14 -0700363 except Exception as e:
364 log.error("%s: Failed to run bkill\n", e)
365
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800366 @staticmethod
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700367 def increment_timer():
368 # sub function that increments with overflow = 60
369 def _incr_ovf_60(val):
370 if val >= 59:
371 val = 0
372 return val, True
373 else:
374 val += 1
375 return val, False
376
377 incr_hh = False
378 Deploy.ss, incr_mm = _incr_ovf_60(Deploy.ss)
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100379 if incr_mm:
380 Deploy.mm, incr_hh = _incr_ovf_60(Deploy.mm)
381 if incr_hh:
382 Deploy.hh += 1
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700383
384 @staticmethod
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800385 def deploy(items):
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800386 dispatched_items = []
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700387 queued_items = []
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800388
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700389 # Print timer val in hh:mm:ss.
390 def get_timer_val():
391 return "%02i:%02i:%02i" % (Deploy.hh, Deploy.mm, Deploy.ss)
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800392
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700393 # Check if elapsed time has reached the next print interval.
394 def has_print_interval_reached():
395 # Deploy.print_interval is expected to be < 1 hour.
396 return (((Deploy.mm * 60 + Deploy.ss) %
397 Deploy.print_interval) == 0)
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800398
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800399 def dispatch_items(items):
Srikrishna Iyer32aae3f2020-03-19 17:34:00 -0700400 item_names = OrderedDict()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800401 for item in items:
402 if item.target not in item_names.keys():
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800403 item_names[item.target] = ""
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800404 if item.status is None:
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800405 item_names[item.target] += item.identifier + ", "
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800406 item.dispatch_cmd()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800407
408 for target in item_names.keys():
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800409 if item_names[target] != "":
410 item_names[target] = " [" + item_names[target][:-2] + "]"
411 log.log(VERBOSE, "[%s]: [%s]: [dispatch]:\n%s",
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700412 get_timer_val(), target, item_names[target])
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800413
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700414 # Initialize status for a target, add '_stats_' for the said target
415 # and initialize counters for queued, dispatched, passed, failed,
416 # killed and total to 0. Also adds a boolean key to indicate if all
417 # items in a given target are done.
418 def init_status_target_stats(status, target):
419 status[target] = OrderedDict()
420 status[target]['_stats_'] = OrderedDict()
421 status[target]['_stats_']['Q'] = 0
422 status[target]['_stats_']['D'] = 0
423 status[target]['_stats_']['P'] = 0
424 status[target]['_stats_']['F'] = 0
425 status[target]['_stats_']['K'] = 0
426 status[target]['_stats_']['T'] = 0
427 status[target]['_done_'] = False
428
429 # Update status counter for a newly queued item.
430 def add_status_target_queued(status, item):
431 if item.target not in status.keys():
432 init_status_target_stats(status, item.target)
433 status[item.target][item] = "Q"
434 status[item.target]['_stats_']['Q'] += 1
435 status[item.target]['_stats_']['T'] += 1
436
437 # Update status counters for a target.
438 def update_status_target_stats(status, item):
439 old_status = status[item.target][item]
440 status[item.target]['_stats_'][old_status] -= 1
441 status[item.target]['_stats_'][item.status] += 1
442 status[item.target][item] = item.status
443
444 def check_if_done_and_print_status(status, print_status_flag):
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800445 all_done = True
446 for target in status.keys():
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700447 target_done_prev = status[target]['_done_']
448 target_done_curr = ((status[target]['_stats_']["Q"] == 0) and
449 (status[target]['_stats_']["D"] == 0))
450 status[target]['_done_'] = target_done_curr
451 all_done &= target_done_curr
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800452
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700453 # Print if flag is set and target_done is not True for two
454 # consecutive times.
455 if not (target_done_prev and
456 target_done_curr) and print_status_flag:
457 stats = status[target]['_stats_']
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800458 width = "0{}d".format(len(str(stats["T"])))
459 msg = "["
460 for s in stats.keys():
461 msg += s + ": {:{}}, ".format(stats[s], width)
462 msg = msg[:-2] + "]"
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700463 log.info("[%s]: [%s]: %s", get_timer_val(), target, msg)
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800464 return all_done
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800465
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700466 # Print legend once at the start of the run.
467 if Deploy.print_legend:
468 log.info("[legend]: [Q: queued, D: dispatched, "
469 "P: passed, F: failed, K: killed, T: total]")
470 Deploy.print_legend = False
471
Srikrishna Iyer32aae3f2020-03-19 17:34:00 -0700472 status = OrderedDict()
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700473 print_status_flag = True
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800474
475 # Queue all items
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700476 queued_items = items
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800477 for item in queued_items:
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700478 add_status_target_queued(status, item)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800479
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700480 all_done = False
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800481 while not all_done:
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700482 # Get status of dispatched items.
483 for item in dispatched_items:
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100484 if item.status == "D":
485 item.get_status()
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700486 if item.status != status[item.target][item]:
487 print_status_flag = True
488 if item.status != "D":
489 if item.status != "P":
490 # Kill its sub items if item did not pass.
491 item.set_sub_status("K")
492 log.error("[%s]: [%s]: [status] [%s: %s]",
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700493 get_timer_val(), item.target,
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700494 item.identifier, item.status)
495 else:
496 log.log(VERBOSE, "[%s]: [%s]: [status] [%s: %s]",
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700497 get_timer_val(), item.target,
498 item.identifier, item.status)
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700499 # Queue items' sub-items if it is done.
500 queued_items.extend(item.sub)
501 for sub_item in item.sub:
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700502 add_status_target_queued(status, sub_item)
503 update_status_target_stats(status, item)
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700504
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800505 # Dispatch items from the queue as slots free up.
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700506 all_done = (len(queued_items) == 0)
507 if not all_done:
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800508 num_slots = Deploy.max_parallel - Deploy.dispatch_counter
Weicai Yang82445bc2020-04-02 13:38:02 -0700509 if num_slots > Deploy.slot_limit:
510 num_slots = Deploy.slot_limit
Srikrishna Iyere19f42b2020-01-08 17:51:36 -0800511 if num_slots > 0:
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800512 if len(queued_items) > num_slots:
513 dispatch_items(queued_items[0:num_slots])
514 dispatched_items.extend(queued_items[0:num_slots])
515 queued_items = queued_items[num_slots:]
Srikrishna Iyere19f42b2020-01-08 17:51:36 -0800516 else:
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800517 dispatch_items(queued_items)
518 dispatched_items.extend(queued_items)
Cindy Chen8abbc602020-03-25 12:48:10 -0700519 queued_items = []
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800520
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800521 # Check if we are done and print the status periodically.
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700522 all_done &= check_if_done_and_print_status(status,
523 print_status_flag)
Srikrishna Iyerfbaa01a2020-03-19 15:32:23 -0700524
525 # Advance time by 1s if there is more work to do.
526 if not all_done:
527 time.sleep(1)
Srikrishna Iyer6b869132020-03-23 23:48:35 -0700528 Deploy.increment_timer()
529 print_status_flag = has_print_interval_reached()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800530
531
532class CompileSim(Deploy):
533 """
534 Abstraction for building the simulation executable.
535 """
536
537 # Register all builds with the class
538 items = []
539
540 def __init__(self, build_mode, sim_cfg):
541 self.target = "build"
542 self.pass_patterns = []
543 self.fail_patterns = []
544
Srikrishna Iyer8ce80d02020-02-05 10:53:19 -0800545 self.mandatory_cmd_attrs = {
546 # tool srcs
Weicai Yang680f7e22020-02-26 18:10:24 -0800547 "tool_srcs": False,
548 "tool_srcs_dir": False,
Srikrishna Iyer8ce80d02020-02-05 10:53:19 -0800549
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800550 # Flist gen
551 "sv_flist_gen_cmd": False,
552 "sv_flist_gen_dir": False,
553 "sv_flist_gen_opts": False,
554
555 # Build
556 "build_dir": False,
557 "build_cmd": False,
558 "build_opts": False
559 }
560
Weicai Yang31029d22020-03-24 11:14:56 -0700561 self.mandatory_misc_attrs = {
562 "cov_db_dir": False,
563 "build_pass_patterns": False,
564 "build_fail_patterns": False
565 }
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800566
567 # Initialize
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800568 super().__init__(sim_cfg)
569 super().parse_dict(build_mode.__dict__)
570 # Call this method again with the sim_cfg dict passed as the object,
571 # since it may contain additional mandatory attrs.
572 super().parse_dict(sim_cfg.__dict__)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800573 self.build_mode = self.name
Weicai Yang31029d22020-03-24 11:14:56 -0700574 self.pass_patterns = self.build_pass_patterns
575 self.fail_patterns = self.build_fail_patterns
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800576 self.__post_init__()
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800577
578 # Start fail message construction
579 self.fail_msg = "\n**BUILD:** {}<br>\n".format(self.name)
Srikrishna Iyerf578e7c2020-01-29 13:11:58 -0800580 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
581 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800582
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800583 CompileSim.items.append(self)
584
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800585 def dispatch_cmd(self):
586 # Delete previous cov_db_dir if it exists before dispatching new build.
587 if os.path.exists(self.cov_db_dir):
588 os.system("rm -rf " + self.cov_db_dir)
589 super().dispatch_cmd()
590
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800591
Michael Schaffner8ac6c4c2020-03-03 15:00:20 -0800592class CompileOneShot(Deploy):
593 """
594 Abstraction for building the simulation executable.
595 """
596
597 # Register all builds with the class
598 items = []
599
600 def __init__(self, build_mode, sim_cfg):
601 self.target = "build"
602 self.pass_patterns = []
603 self.fail_patterns = []
604
605 self.mandatory_cmd_attrs = {
606 # tool srcs
607 "tool_srcs": False,
608 "tool_srcs_dir": False,
609
Michael Schaffner3d160992020-03-31 18:37:53 -0700610 # Flist gen
611 "sv_flist_gen_cmd": False,
612 "sv_flist_gen_dir": False,
613 "sv_flist_gen_opts": False,
614
Michael Schaffner8ac6c4c2020-03-03 15:00:20 -0800615 # Build
616 "build_dir": False,
617 "build_cmd": False,
618 "build_opts": False,
Michael Schaffner3d160992020-03-31 18:37:53 -0700619 "build_log": False,
Michael Schaffner8ac6c4c2020-03-03 15:00:20 -0800620
621 # Report processing
622 "report_cmd": False,
623 "report_opts": False
624 }
625
626 self.mandatory_misc_attrs = {}
627
628 # Initialize
629 super().__init__(sim_cfg)
630 super().parse_dict(build_mode.__dict__)
631 # Call this method again with the sim_cfg dict passed as the object,
632 # since it may contain additional mandatory attrs.
633 super().parse_dict(sim_cfg.__dict__)
634 self.build_mode = self.name
635 self.__post_init__()
636
637 # Start fail message construction
638 self.fail_msg = "\n**BUILD:** {}<br>\n".format(self.name)
639 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
640 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
641
642 CompileOneShot.items.append(self)
643
644
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800645class RunTest(Deploy):
646 """
647 Abstraction for running tests. This is one per seed for each test.
648 """
649
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800650 # Initial seed values when running tests (if available).
651 seeds = []
Srikrishna Iyer96e54102020-03-12 22:46:50 -0700652 fixed_seed = None
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800653
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800654 # Register all runs with the class
655 items = []
656
657 def __init__(self, index, test, sim_cfg):
658 self.target = "run"
659 self.pass_patterns = []
660 self.fail_patterns = []
661
662 self.mandatory_cmd_attrs = {
Srikrishna Iyer42d032f2020-03-04 23:55:44 -0800663 "proj_root": False,
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800664 "uvm_test": False,
665 "uvm_test_seq": False,
666 "run_opts": False,
Srikrishna Iyerc612bb02020-05-12 00:33:27 -0700667 "sw_test": False,
668 "sw_test_is_prebuilt": False,
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800669 "sw_build_device": False,
670 "sw_build_dir": False,
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800671 "run_dir": False,
672 "run_cmd": False,
673 "run_opts": False
674 }
675
676 self.mandatory_misc_attrs = {
677 "run_dir_name": False,
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800678 "cov_db_test_dir": False,
Weicai Yang31029d22020-03-24 11:14:56 -0700679 "run_pass_patterns": False,
680 "run_fail_patterns": False
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800681 }
682
683 self.index = index
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800684 self.seed = RunTest.get_seed()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800685
686 # Initialize
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800687 super().__init__(sim_cfg)
688 super().parse_dict(test.__dict__)
689 # Call this method again with the sim_cfg dict passed as the object,
690 # since it may contain additional mandatory attrs.
691 super().parse_dict(sim_cfg.__dict__)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800692 self.test = self.name
693 self.renew_odir = True
694 self.build_mode = test.build_mode.name
Weicai Yang31029d22020-03-24 11:14:56 -0700695 self.pass_patterns = self.run_pass_patterns
696 self.fail_patterns = self.run_fail_patterns
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800697 self.__post_init__()
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800698 # For output dir link, use run_dir_name instead.
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800699 self.odir_ln = self.run_dir_name
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800700
701 # Start fail message construction
702 self.fail_msg = "\n**TEST:** {}, ".format(self.name)
703 self.fail_msg += "**SEED:** {}<br>\n".format(self.seed)
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800704 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
705 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800706
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800707 RunTest.items.append(self)
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800708
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800709 def __post_init__(self):
710 super().__post_init__()
711 # Set identifier.
712 self.identifier = self.sim_cfg.name + ":" + self.run_dir_name
713
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800714 def get_status(self):
715 '''Override base class get_status implementation for additional post-status
716 actions.'''
717 super().get_status()
Srikrishna Iyer7702be52020-03-07 01:00:28 -0800718 if self.status not in ["D", "P"]:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800719 # Delete the coverage data if available.
720 if os.path.exists(self.cov_db_test_dir):
721 log.log(VERBOSE, "Deleting coverage data of failing test:\n%s",
722 self.cov_db_test_dir)
Srikrishna Iyer32aae3f2020-03-19 17:34:00 -0700723 os.system("/bin/rm -rf " + self.cov_db_test_dir)
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800724
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800725 @staticmethod
726 def get_seed():
Srikrishna Iyer96e54102020-03-12 22:46:50 -0700727 # If --seeds option is passed, then those custom seeds are consumed
728 # first. If --fixed-seed <val> is also passed, the subsequent tests
729 # (once the custom seeds are consumed) will be run with the fixed seed.
Philipp Wagner9a521782020-02-25 11:49:53 +0000730 if not RunTest.seeds:
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100731 if RunTest.fixed_seed:
732 return RunTest.fixed_seed
Srikrishna Iyera821bbc2020-01-31 00:28:02 -0800733 for i in range(1000):
Philipp Wagner75fd6c72020-02-25 11:47:41 +0000734 seed = random.getrandbits(32)
Srikrishna Iyera821bbc2020-01-31 00:28:02 -0800735 RunTest.seeds.append(seed)
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800736 return RunTest.seeds.pop(0)
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800737
738
739class CovMerge(Deploy):
740 """
741 Abstraction for merging coverage databases. An item of this class is created AFTER
742 the regression is completed.
743 """
744
745 # Register all builds with the class
746 items = []
747
748 def __init__(self, sim_cfg):
749 self.target = "cov_merge"
750 self.pass_patterns = []
751 self.fail_patterns = []
752
753 # Construct local 'special' variable from cov directories that need to
754 # be merged.
755 self.cov_db_dirs = ""
756
757 self.mandatory_cmd_attrs = {
758 "cov_merge_cmd": False,
759 "cov_merge_opts": False
760 }
761
762 self.mandatory_misc_attrs = {
763 "cov_merge_dir": False,
764 "cov_merge_db_dir": False
765 }
766
767 # Initialize
768 super().__init__(sim_cfg)
769 super().parse_dict(sim_cfg.__dict__)
770 self.__post_init__()
771
772 # Override standard output and log patterns.
773 self.odir = self.cov_merge_db_dir
774 self.odir_ln = os.path.basename(os.path.normpath(self.odir))
775
776 # Start fail message construction
777 self.fail_msg = "\n**COV_MERGE:** {}<br>\n".format(self.name)
778 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
779 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
780
781 CovMerge.items.append(self)
782
783 def __post_init__(self):
784 # Add cov db dirs from all the builds that were kicked off.
785 for bld in self.sim_cfg.builds:
786 self.cov_db_dirs += bld.cov_db_dir + " "
787
788 # Recursively search and replace wildcards, ignoring cov_db_dirs for now.
789 # We need to resolve it later based on cov_db_dirs value set below.
790 self.__dict__ = find_and_substitute_wildcards(
791 self.__dict__, self.__dict__, ignored_wildcards=["cov_db_dirs"])
792
793 # Prune previous merged cov directories.
794 prev_cov_db_dirs = self.odir_limiter(odir=self.cov_merge_db_dir)
795
796 # If a merged cov data base exists from a previous run, then consider
797 # that as well for merging, if the --cov-merge-previous command line
798 # switch is passed.
799 if self.sim_cfg.cov_merge_previous:
800 self.cov_db_dirs += prev_cov_db_dirs
801
Srikrishna Iyer39ffebd2020-03-30 11:53:12 -0700802 # Append cov_db_dirs to the list of exports.
803 self.exports["cov_db_dirs"] = "\"{}\"".format(self.cov_db_dirs)
804
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800805 # Call base class __post_init__ to do checks and substitutions
806 super().__post_init__()
807
808
809class CovReport(Deploy):
810 """
811 Abstraction for coverage report generation. An item of this class is created AFTER
812 the regression is completed.
813 """
814
815 # Register all builds with the class
816 items = []
817
818 def __init__(self, sim_cfg):
819 self.target = "cov_report"
820 self.pass_patterns = []
821 self.fail_patterns = []
Weicai Yang680f7e22020-02-26 18:10:24 -0800822 self.cov_total = ""
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800823 self.cov_results = ""
824
825 self.mandatory_cmd_attrs = {
826 "cov_report_cmd": False,
827 "cov_report_opts": False
828 }
829
830 self.mandatory_misc_attrs = {
831 "cov_report_dir": False,
832 "cov_merge_db_dir": False,
Srikrishna Iyer39ffebd2020-03-30 11:53:12 -0700833 "cov_report_txt": False
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800834 }
835
836 # Initialize
837 super().__init__(sim_cfg)
838 super().parse_dict(sim_cfg.__dict__)
839 self.__post_init__()
840
841 # Start fail message construction
842 self.fail_msg = "\n**COV_REPORT:** {}<br>\n".format(self.name)
843 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
844 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
845
846 CovReport.items.append(self)
847
848 def get_status(self):
849 super().get_status()
850 # Once passed, extract the cov results summary from the dashboard.
851 if self.status == "P":
Srikrishna Iyer39ffebd2020-03-30 11:53:12 -0700852 results, self.cov_total, ex_msg = get_cov_summary_table(
853 self.cov_report_txt, self.sim_cfg.tool)
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800854
Srikrishna Iyer39ffebd2020-03-30 11:53:12 -0700855 if not ex_msg:
856 # Succeeded in obtaining the coverage data.
857 colalign = (("center", ) * len(results[0]))
858 self.cov_results = tabulate(results,
859 headers="firstrow",
860 tablefmt="pipe",
861 colalign=colalign)
862 else:
Srikrishna Iyer86f6bce2020-02-27 19:02:04 -0800863 self.fail_msg += ex_msg
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800864 log.error(ex_msg)
865 self.status = "F"
866
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800867 if self.status == "P":
868 # Delete the cov report - not needed.
869 os.system("rm -rf " + self.log)
870
871
872class CovAnalyze(Deploy):
873 """
874 Abstraction for coverage analysis tool.
875 """
876
877 # Register all builds with the class
878 items = []
879
880 def __init__(self, sim_cfg):
881 self.target = "cov_analyze"
882 self.pass_patterns = []
883 self.fail_patterns = []
884
885 self.mandatory_cmd_attrs = {
Srikrishna Iyer39ffebd2020-03-30 11:53:12 -0700886 # tool srcs
887 "tool_srcs": False,
888 "tool_srcs_dir": False,
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800889 "cov_analyze_cmd": False,
890 "cov_analyze_opts": False
891 }
892
893 self.mandatory_misc_attrs = {
894 "cov_analyze_dir": False,
895 "cov_merge_db_dir": False
896 }
897
898 # Initialize
899 super().__init__(sim_cfg)
900 super().parse_dict(sim_cfg.__dict__)
901 self.__post_init__()
902
903 # Start fail message construction
904 self.fail_msg = "\n**COV_ANALYZE:** {}<br>\n".format(self.name)
905 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
906 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
907
908 CovAnalyze.items.append(self)