blob: 0a4df52337c2d4ea216344de512eef3541db26c7 [file] [log] [blame]
Federico Ressi21a10d32020-01-31 07:43:30 +01001#!/usr/bin/env python3
Sean Dague97fcc7b2014-06-16 17:24:14 -04002#
3# Copyright 2014 Hewlett-Packard Development Company, L.P.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
Eyale7361772016-04-05 16:18:56 +030017
Sean Dague97fcc7b2014-06-16 17:24:14 -040018"""Dump the state of the world for post mortem."""
19
John L. Villalovos09949e02017-02-06 13:46:32 -080020from __future__ import print_function
21
Sean Dague97fcc7b2014-06-16 17:24:14 -040022import argparse
23import datetime
Chris Dent57d79672016-02-23 15:38:43 +000024from distutils import spawn
Sean Dague737e9422015-05-12 19:51:39 -040025import fnmatch
Federico Ressi21a10d32020-01-31 07:43:30 +010026import io
Sean Dague97fcc7b2014-06-16 17:24:14 -040027import os
Jens Harbottce396d32019-09-05 08:51:33 +000028import shutil
Ian Wienand99440f92015-07-01 06:14:01 +100029import subprocess
Sean Dague97fcc7b2014-06-16 17:24:14 -040030import sys
31
32
Ihar Hrachyshkaef219bf2016-02-11 13:54:48 +010033GMR_PROCESSES = (
34 'nova-compute',
35 'neutron-dhcp-agent',
36 'neutron-l3-agent',
37 'neutron-linuxbridge-agent',
38 'neutron-metadata-agent',
39 'neutron-openvswitch-agent',
Eric Harneyd8682db2016-10-14 14:36:29 -040040 'cinder-volume',
Ihar Hrachyshkaef219bf2016-02-11 13:54:48 +010041)
42
43
Sean Dague97fcc7b2014-06-16 17:24:14 -040044def get_options():
45 parser = argparse.ArgumentParser(
46 description='Dump world state for debugging')
47 parser.add_argument('-d', '--dir',
48 default='.',
49 help='Output directory for worlddump')
Sean Dagueac9313e2015-07-27 13:33:30 -040050 parser.add_argument('-n', '--name',
51 default='',
52 help='Additional name to tag into file')
Sean Dague97fcc7b2014-06-16 17:24:14 -040053 return parser.parse_args()
54
55
Sean Dagueac9313e2015-07-27 13:33:30 -040056def filename(dirname, name=""):
Sean Dague97fcc7b2014-06-16 17:24:14 -040057 now = datetime.datetime.utcnow()
Sean Dagueac9313e2015-07-27 13:33:30 -040058 fmt = "worlddump-%Y-%m-%d-%H%M%S"
59 if name:
60 fmt += "-" + name
61 fmt += ".txt"
62 return os.path.join(dirname, now.strftime(fmt))
Sean Dague97fcc7b2014-06-16 17:24:14 -040063
64
65def warn(msg):
Eyale7361772016-04-05 16:18:56 +030066 print("WARN: %s" % msg)
Sean Dague97fcc7b2014-06-16 17:24:14 -040067
68
Sean Dague60a14052015-05-11 14:53:39 -040069def _dump_cmd(cmd):
Eyale7361772016-04-05 16:18:56 +030070 print(cmd)
71 print("-" * len(cmd))
72 print()
Ian Wienand99440f92015-07-01 06:14:01 +100073 try:
74 subprocess.check_call(cmd, shell=True)
Eyale7361772016-04-05 16:18:56 +030075 print()
Ihar Hrachyshka7976aac2016-03-03 15:30:49 +010076 except subprocess.CalledProcessError as e:
Eyale7361772016-04-05 16:18:56 +030077 print("*** Failed to run '%(cmd)s': %(err)s" % {'cmd': cmd, 'err': e})
Sean Dague60a14052015-05-11 14:53:39 -040078
79
Chris Dent57d79672016-02-23 15:38:43 +000080def _find_cmd(cmd):
81 if not spawn.find_executable(cmd):
Eyale7361772016-04-05 16:18:56 +030082 print("*** %s not found: skipping" % cmd)
Chris Dent57d79672016-02-23 15:38:43 +000083 return False
84 return True
85
86
Sean Dague60a14052015-05-11 14:53:39 -040087def _header(name):
Eyale7361772016-04-05 16:18:56 +030088 print()
89 print(name)
90 print("=" * len(name))
91 print()
Sean Dague60a14052015-05-11 14:53:39 -040092
93
fumihiko kakuma578459f2016-04-07 08:15:45 +090094def _bridge_list():
yan.haifeng6ba17f72016-04-29 15:59:56 +080095 process = subprocess.Popen(['sudo', 'ovs-vsctl', 'list-br'],
96 stdout=subprocess.PIPE)
fumihiko kakuma578459f2016-04-07 08:15:45 +090097 stdout, _ = process.communicate()
98 return stdout.split()
99
100
fumihiko kakuma60994012016-03-08 20:55:01 +0900101# This method gets a max openflow version supported by openvswitch.
102# For example 'ovs-ofctl --version' displays the following:
103#
104# ovs-ofctl (Open vSwitch) 2.0.2
105# Compiled Dec 9 2015 14:08:08
106# OpenFlow versions 0x1:0x4
107#
fumihiko kakuma2bd25682016-04-05 10:33:50 +0900108# The above shows that openvswitch supports from OpenFlow10 to OpenFlow13.
fumihiko kakuma60994012016-03-08 20:55:01 +0900109# This method gets max version searching 'OpenFlow versions 0x1:0x'.
110# And return a version value converted to an integer type.
111def _get_ofp_version():
Federico Ressi21a10d32020-01-31 07:43:30 +0100112 process = subprocess.Popen(['ovs-ofctl', '--version'],
113 stdout=subprocess.PIPE)
fumihiko kakuma60994012016-03-08 20:55:01 +0900114 stdout, _ = process.communicate()
Federico Ressi21a10d32020-01-31 07:43:30 +0100115 find_str = b'OpenFlow versions 0x1:0x'
fumihiko kakuma60994012016-03-08 20:55:01 +0900116 offset = stdout.find(find_str)
117 return int(stdout[offset + len(find_str):-1]) - 1
118
119
Sean Dague97fcc7b2014-06-16 17:24:14 -0400120def disk_space():
121 # the df output
Sean Dague60a14052015-05-11 14:53:39 -0400122 _header("File System Summary")
123
Sean Dague97fcc7b2014-06-16 17:24:14 -0400124 dfraw = os.popen("df -Ph").read()
125 df = [s.split() for s in dfraw.splitlines()]
126 for fs in df:
127 try:
128 if int(fs[4][:-1]) > 95:
129 warn("Device %s (%s) is %s full, might be an issue" % (
130 fs[0], fs[5], fs[4]))
131 except ValueError:
132 # if it doesn't look like an int, that's fine
133 pass
134
Eyale7361772016-04-05 16:18:56 +0300135 print(dfraw)
Sean Dague97fcc7b2014-06-16 17:24:14 -0400136
137
Sean Dague2da606d2015-08-06 10:02:43 -0400138def ebtables_dump():
Sean Dague5c5e0862015-11-09 14:08:15 -0500139 tables = ['filter', 'nat', 'broute']
Sean Dague2da606d2015-08-06 10:02:43 -0400140 _header("EB Tables Dump")
Chris Dent57d79672016-02-23 15:38:43 +0000141 if not _find_cmd('ebtables'):
142 return
Sean Dague5c5e0862015-11-09 14:08:15 -0500143 for table in tables:
144 _dump_cmd("sudo ebtables -t %s -L" % table)
Sean Dague2da606d2015-08-06 10:02:43 -0400145
146
Sean Dague168b7c22015-05-07 08:57:28 -0400147def iptables_dump():
148 tables = ['filter', 'nat', 'mangle']
Sean Dague60a14052015-05-11 14:53:39 -0400149 _header("IP Tables Dump")
150
Sean Dague168b7c22015-05-07 08:57:28 -0400151 for table in tables:
Sean Dague60a14052015-05-11 14:53:39 -0400152 _dump_cmd("sudo iptables --line-numbers -L -nv -t %s" % table)
153
154
Ihar Hrachyshka72c34ee2016-01-30 16:18:01 +0100155def _netns_list():
156 process = subprocess.Popen(['ip', 'netns'], stdout=subprocess.PIPE)
157 stdout, _ = process.communicate()
John L. Villalovosc6e69392017-02-06 14:24:42 -0800158 # NOTE(jlvillal): Sometimes 'ip netns list' can return output like:
159 # qrouter-0805fd7d-c493-4fa6-82ca-1c6c9b23cd9e (id: 1)
160 # qdhcp-bb2cc6ae-2ae8-474f-adda-a94059b872b5 (id: 0)
161 output = [x.split()[0] for x in stdout.splitlines()]
162 return output
Ihar Hrachyshka72c34ee2016-01-30 16:18:01 +0100163
164
Sean Dague60a14052015-05-11 14:53:39 -0400165def network_dump():
166 _header("Network Dump")
167
Nate Johnston56946cf2018-11-12 11:17:07 -0500168 _dump_cmd("bridge link")
Nate Johnston56946cf2018-11-12 11:17:07 -0500169 _dump_cmd("ip link show type bridge")
Hunt Xuc3963552018-01-08 16:11:33 +0800170 ip_cmds = ["neigh", "addr", "link", "route"]
Ihar Hrachyshka72c34ee2016-01-30 16:18:01 +0100171 for cmd in ip_cmds + ['netns']:
172 _dump_cmd("ip %s" % cmd)
173 for netns_ in _netns_list():
174 for cmd in ip_cmds:
LuyaoZhong8d4ae4f2020-02-19 08:16:03 +0000175 args = {'netns': bytes.decode(netns_), 'cmd': cmd}
Ihar Hrachyshka72c34ee2016-01-30 16:18:01 +0100176 _dump_cmd('sudo ip netns exec %(netns)s ip %(cmd)s' % args)
Sean Dague168b7c22015-05-07 08:57:28 -0400177
178
Ihar Hrachyshkac1b7cb12016-02-11 13:50:46 +0100179def ovs_dump():
180 _header("Open vSwitch Dump")
181
Chris Dent57d79672016-02-23 15:38:43 +0000182 # NOTE(cdent): If we're not using neutron + ovs these commands
183 # will not be present so
184 if not _find_cmd('ovs-vsctl'):
185 return
186
fumihiko kakuma578459f2016-04-07 08:15:45 +0900187 bridges = _bridge_list()
fumihiko kakuma60994012016-03-08 20:55:01 +0900188 ofctl_cmds = ('show', 'dump-ports-desc', 'dump-ports', 'dump-flows')
189 ofp_max = _get_ofp_version()
190 vers = 'OpenFlow10'
fumihiko kakuma578459f2016-04-07 08:15:45 +0900191 for i in range(1, ofp_max + 1):
fumihiko kakuma60994012016-03-08 20:55:01 +0900192 vers += ',OpenFlow1' + str(i)
Ihar Hrachyshkac1b7cb12016-02-11 13:50:46 +0100193 _dump_cmd("sudo ovs-vsctl show")
fumihiko kakuma60994012016-03-08 20:55:01 +0900194 for ofctl_cmd in ofctl_cmds:
195 for bridge in bridges:
LuyaoZhong8d4ae4f2020-02-19 08:16:03 +0000196 args = {'vers': vers, 'cmd': ofctl_cmd, 'bridge': bytes.decode(bridge)}
fumihiko kakuma60994012016-03-08 20:55:01 +0900197 _dump_cmd("sudo ovs-ofctl --protocols=%(vers)s %(cmd)s %(bridge)s" % args)
Ihar Hrachyshkac1b7cb12016-02-11 13:50:46 +0100198
199
Sean Dague97fcc7b2014-06-16 17:24:14 -0400200def process_list():
Sean Dague60a14052015-05-11 14:53:39 -0400201 _header("Process Listing")
202 _dump_cmd("ps axo "
203 "user,ppid,pid,pcpu,pmem,vsz,rss,tty,stat,start,time,args")
Sean Dague97fcc7b2014-06-16 17:24:14 -0400204
205
Sean Dague737e9422015-05-12 19:51:39 -0400206def compute_consoles():
207 _header("Compute consoles")
Federico Ressi21a10d32020-01-31 07:43:30 +0100208 for root, _, filenames in os.walk('/opt/stack'):
Sean Dague737e9422015-05-12 19:51:39 -0400209 for filename in fnmatch.filter(filenames, 'console.log'):
210 fullpath = os.path.join(root, filename)
211 _dump_cmd("sudo cat %s" % fullpath)
212
213
Ihar Hrachyshkaef219bf2016-02-11 13:54:48 +0100214def guru_meditation_reports():
215 for service in GMR_PROCESSES:
216 _header("%s Guru Meditation Report" % service)
Ian Wienand3a9df1d2015-07-01 06:18:47 +1000217
Ihar Hrachyshkaef219bf2016-02-11 13:54:48 +0100218 try:
219 subprocess.check_call(['pgrep', '-f', service])
220 except subprocess.CalledProcessError:
221 print("Skipping as %s does not appear to be running" % service)
222 continue
Ian Wienand3a9df1d2015-07-01 06:18:47 +1000223
Ihar Hrachyshkaef219bf2016-02-11 13:54:48 +0100224 _dump_cmd("killall -e -USR2 %s" % service)
225 print("guru meditation report in %s log" % service)
Joe Gordon2ebe9932015-06-07 16:57:34 +0900226
227
Ian Wienandbfcc7602017-03-29 11:52:06 +1100228def var_core():
229 if os.path.exists('/var/core'):
230 _header("/var/core dumps")
231 # NOTE(ianw) : see DEBUG_LIBVIRT_COREDUMPS. We could think
232 # about getting backtraces out of these. There are other
233 # tools out there that can do that sort of thing though.
234 _dump_cmd("ls -ltrah /var/core")
235
Federico Ressi21a10d32020-01-31 07:43:30 +0100236
237def disable_stdio_buffering():
238 # re-open STDOUT as binary, then wrap it in a
239 # TextIOWrapper, and write through everything.
240 binary_stdout = io.open(sys.stdout.fileno(), 'wb', 0)
241 sys.stdout = io.TextIOWrapper(binary_stdout, write_through=True)
242
243
Sean Dague97fcc7b2014-06-16 17:24:14 -0400244def main():
245 opts = get_options()
Sean Dagueac9313e2015-07-27 13:33:30 -0400246 fname = filename(opts.dir, opts.name)
Eyale7361772016-04-05 16:18:56 +0300247 print("World dumping... see %s for details" % fname)
Federico Ressi21a10d32020-01-31 07:43:30 +0100248
249 disable_stdio_buffering()
250
251 with io.open(fname, 'w') as f:
Sean Dague97fcc7b2014-06-16 17:24:14 -0400252 os.dup2(f.fileno(), sys.stdout.fileno())
253 disk_space()
254 process_list()
Sean Dague60a14052015-05-11 14:53:39 -0400255 network_dump()
Ihar Hrachyshkac1b7cb12016-02-11 13:50:46 +0100256 ovs_dump()
Sean Dague168b7c22015-05-07 08:57:28 -0400257 iptables_dump()
Sean Dague2da606d2015-08-06 10:02:43 -0400258 ebtables_dump()
Sean Dague737e9422015-05-12 19:51:39 -0400259 compute_consoles()
Ihar Hrachyshkaef219bf2016-02-11 13:54:48 +0100260 guru_meditation_reports()
Ian Wienandbfcc7602017-03-29 11:52:06 +1100261 var_core()
Jens Harbottce396d32019-09-05 08:51:33 +0000262 # Singular name for ease of log retrieval
263 copyname = os.path.join(opts.dir, 'worlddump')
264 if opts.name:
265 copyname += '-' + opts.name
266 copyname += '-latest.txt'
267 # We make a full copy to deal with jobs that may or may not
268 # gzip logs breaking symlinks.
269 shutil.copyfile(fname, copyname)
Sean Dague97fcc7b2014-06-16 17:24:14 -0400270
271
272if __name__ == '__main__':
273 try:
274 sys.exit(main())
275 except KeyboardInterrupt:
276 sys.exit(1)