xref: /openbmc/openbmc/poky/meta/lib/oeqa/utils/dump.py (revision 520786cc)
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