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