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