1*4882a593Smuzhiyun# 2*4882a593Smuzhiyun# SPDX-License-Identifier: MIT 3*4882a593Smuzhiyun# 4*4882a593Smuzhiyun 5*4882a593Smuzhiyunimport os 6*4882a593Smuzhiyunimport sys 7*4882a593Smuzhiyunimport json 8*4882a593Smuzhiyunimport errno 9*4882a593Smuzhiyunimport datetime 10*4882a593Smuzhiyunimport itertools 11*4882a593Smuzhiyunfrom .commands import runCmd 12*4882a593Smuzhiyun 13*4882a593Smuzhiyunclass BaseDumper(object): 14*4882a593Smuzhiyun """ Base class to dump commands from host/target """ 15*4882a593Smuzhiyun 16*4882a593Smuzhiyun def __init__(self, cmds, parent_dir): 17*4882a593Smuzhiyun self.cmds = [] 18*4882a593Smuzhiyun # Some testing doesn't inherit testimage, so it is needed 19*4882a593Smuzhiyun # to set some defaults. 20*4882a593Smuzhiyun self.parent_dir = parent_dir 21*4882a593Smuzhiyun self.dump_dir = parent_dir 22*4882a593Smuzhiyun dft_cmds = """ top -bn1 23*4882a593Smuzhiyun iostat -x -z -N -d -p ALL 20 2 24*4882a593Smuzhiyun ps -ef 25*4882a593Smuzhiyun free 26*4882a593Smuzhiyun df 27*4882a593Smuzhiyun memstat 28*4882a593Smuzhiyun dmesg 29*4882a593Smuzhiyun ip -s link 30*4882a593Smuzhiyun netstat -an""" 31*4882a593Smuzhiyun if not cmds: 32*4882a593Smuzhiyun cmds = dft_cmds 33*4882a593Smuzhiyun for cmd in cmds.split('\n'): 34*4882a593Smuzhiyun cmd = cmd.lstrip() 35*4882a593Smuzhiyun if not cmd or cmd[0] == '#': 36*4882a593Smuzhiyun continue 37*4882a593Smuzhiyun self.cmds.append(cmd) 38*4882a593Smuzhiyun 39*4882a593Smuzhiyun def create_dir(self, dir_suffix): 40*4882a593Smuzhiyun dump_subdir = ("%s_%s" % ( 41*4882a593Smuzhiyun datetime.datetime.now().strftime('%Y%m%d%H%M'), 42*4882a593Smuzhiyun dir_suffix)) 43*4882a593Smuzhiyun dump_dir = os.path.join(self.parent_dir, dump_subdir) 44*4882a593Smuzhiyun try: 45*4882a593Smuzhiyun os.makedirs(dump_dir) 46*4882a593Smuzhiyun except OSError as err: 47*4882a593Smuzhiyun if err.errno != errno.EEXIST: 48*4882a593Smuzhiyun raise err 49*4882a593Smuzhiyun self.dump_dir = dump_dir 50*4882a593Smuzhiyun 51*4882a593Smuzhiyun def _construct_filename(self, command): 52*4882a593Smuzhiyun if isinstance(self, HostDumper): 53*4882a593Smuzhiyun prefix = "host" 54*4882a593Smuzhiyun elif isinstance(self, TargetDumper): 55*4882a593Smuzhiyun prefix = "target" 56*4882a593Smuzhiyun elif isinstance(self, MonitorDumper): 57*4882a593Smuzhiyun prefix = "qmp" 58*4882a593Smuzhiyun else: 59*4882a593Smuzhiyun prefix = "unknown" 60*4882a593Smuzhiyun for i in itertools.count(): 61*4882a593Smuzhiyun filename = "%s_%02d_%s" % (prefix, i, command) 62*4882a593Smuzhiyun fullname = os.path.join(self.dump_dir, filename) 63*4882a593Smuzhiyun if not os.path.exists(fullname): 64*4882a593Smuzhiyun break 65*4882a593Smuzhiyun return fullname 66*4882a593Smuzhiyun 67*4882a593Smuzhiyun def _write_dump(self, command, output): 68*4882a593Smuzhiyun fullname = self._construct_filename(command) 69*4882a593Smuzhiyun os.makedirs(os.path.dirname(fullname), exist_ok=True) 70*4882a593Smuzhiyun if isinstance(self, MonitorDumper): 71*4882a593Smuzhiyun with open(fullname, 'w') as json_file: 72*4882a593Smuzhiyun json.dump(output, json_file, indent=4) 73*4882a593Smuzhiyun else: 74*4882a593Smuzhiyun with open(fullname, 'w') as dump_file: 75*4882a593Smuzhiyun dump_file.write(output) 76*4882a593Smuzhiyun 77*4882a593Smuzhiyunclass HostDumper(BaseDumper): 78*4882a593Smuzhiyun """ Class to get dumps from the host running the tests """ 79*4882a593Smuzhiyun 80*4882a593Smuzhiyun def __init__(self, cmds, parent_dir): 81*4882a593Smuzhiyun super(HostDumper, self).__init__(cmds, parent_dir) 82*4882a593Smuzhiyun 83*4882a593Smuzhiyun def dump_host(self, dump_dir=""): 84*4882a593Smuzhiyun if dump_dir: 85*4882a593Smuzhiyun self.dump_dir = dump_dir 86*4882a593Smuzhiyun env = os.environ.copy() 87*4882a593Smuzhiyun env['PATH'] = '/usr/sbin:/sbin:/usr/bin:/bin' 88*4882a593Smuzhiyun env['COLUMNS'] = '9999' 89*4882a593Smuzhiyun for cmd in self.cmds: 90*4882a593Smuzhiyun result = runCmd(cmd, ignore_status=True, env=env) 91*4882a593Smuzhiyun self._write_dump(cmd.split()[0], result.output) 92*4882a593Smuzhiyun 93*4882a593Smuzhiyunclass TargetDumper(BaseDumper): 94*4882a593Smuzhiyun """ Class to get dumps from target, it only works with QemuRunner. 95*4882a593Smuzhiyun Will give up permanently after 5 errors from running commands over 96*4882a593Smuzhiyun serial console. This helps to end testing when target is really dead, hanging 97*4882a593Smuzhiyun or unresponsive. 98*4882a593Smuzhiyun """ 99*4882a593Smuzhiyun 100*4882a593Smuzhiyun def __init__(self, cmds, parent_dir, runner): 101*4882a593Smuzhiyun super(TargetDumper, self).__init__(cmds, parent_dir) 102*4882a593Smuzhiyun self.runner = runner 103*4882a593Smuzhiyun self.errors = 0 104*4882a593Smuzhiyun 105*4882a593Smuzhiyun def dump_target(self, dump_dir=""): 106*4882a593Smuzhiyun if self.errors >= 5: 107*4882a593Smuzhiyun print("Too many errors when dumping data from target, assuming it is dead! Will not dump data anymore!") 108*4882a593Smuzhiyun return 109*4882a593Smuzhiyun if dump_dir: 110*4882a593Smuzhiyun self.dump_dir = dump_dir 111*4882a593Smuzhiyun for cmd in self.cmds: 112*4882a593Smuzhiyun # We can continue with the testing if serial commands fail 113*4882a593Smuzhiyun try: 114*4882a593Smuzhiyun (status, output) = self.runner.run_serial(cmd) 115*4882a593Smuzhiyun if status == 0: 116*4882a593Smuzhiyun self.errors = self.errors + 1 117*4882a593Smuzhiyun self._write_dump(cmd.split()[0], output) 118*4882a593Smuzhiyun except: 119*4882a593Smuzhiyun self.errors = self.errors + 1 120*4882a593Smuzhiyun print("Tried to dump info from target but " 121*4882a593Smuzhiyun "serial console failed") 122*4882a593Smuzhiyun print("Failed CMD: %s" % (cmd)) 123*4882a593Smuzhiyun 124*4882a593Smuzhiyunclass MonitorDumper(BaseDumper): 125*4882a593Smuzhiyun """ Class to get dumps via the Qemu Monitor, it only works with QemuRunner 126*4882a593Smuzhiyun Will stop completely if there are more than 5 errors when dumping monitor data. 127*4882a593Smuzhiyun This helps to end testing when target is really dead, hanging or unresponsive. 128*4882a593Smuzhiyun """ 129*4882a593Smuzhiyun 130*4882a593Smuzhiyun def __init__(self, cmds, parent_dir, runner): 131*4882a593Smuzhiyun super(MonitorDumper, self).__init__(cmds, parent_dir) 132*4882a593Smuzhiyun self.runner = runner 133*4882a593Smuzhiyun self.errors = 0 134*4882a593Smuzhiyun 135*4882a593Smuzhiyun def dump_monitor(self, dump_dir=""): 136*4882a593Smuzhiyun if self.runner is None: 137*4882a593Smuzhiyun return 138*4882a593Smuzhiyun if dump_dir: 139*4882a593Smuzhiyun self.dump_dir = dump_dir 140*4882a593Smuzhiyun if self.errors >= 5: 141*4882a593Smuzhiyun print("Too many errors when dumping data from qemu monitor, assuming it is dead! Will not dump data anymore!") 142*4882a593Smuzhiyun return 143*4882a593Smuzhiyun for cmd in self.cmds: 144*4882a593Smuzhiyun cmd_name = cmd.split()[0] 145*4882a593Smuzhiyun try: 146*4882a593Smuzhiyun if len(cmd.split()) > 1: 147*4882a593Smuzhiyun cmd_args = cmd.split()[1] 148*4882a593Smuzhiyun if "%s" in cmd_args: 149*4882a593Smuzhiyun filename = self._construct_filename(cmd_name) 150*4882a593Smuzhiyun cmd_data = json.loads(cmd_args % (filename)) 151*4882a593Smuzhiyun output = self.runner.run_monitor(cmd_name, cmd_data) 152*4882a593Smuzhiyun else: 153*4882a593Smuzhiyun output = self.runner.run_monitor(cmd_name) 154*4882a593Smuzhiyun self._write_dump(cmd_name, output) 155*4882a593Smuzhiyun except Exception as e: 156*4882a593Smuzhiyun self.errors = self.errors + 1 157*4882a593Smuzhiyun print("Failed to dump QMP CMD: %s with\nException: %s" % (cmd_name, e)) 158