ast_coredumper: add Asterisk information dump
This patch makes it so ast_coredumper now outputs the following information to a *-info.txt file when processing a core file: asterisk version and "built by" string BUILD_OPTS system start, and last reloaded date/time taskprocessor list equivalent of "bridge show all" equivalent of "core show channels verbose" Also a slight modification was made when trying to obtain the pid(s) of a running Asterisk. If it fails to retrieve any it now reports an error. Change-Id: I54f35c19ab69b8f8dc78cc933c3fb7c99cef346b
This commit is contained in:
parent
ff0e685eea
commit
ebddff3453
|
@ -383,14 +383,9 @@ if $running || $RUNNING ; then
|
|||
unset pid
|
||||
|
||||
# Simplest case first...
|
||||
pids=$(pgrep -f "$asterisk_bin")
|
||||
pids=$(pgrep -f "$asterisk_bin" || : )
|
||||
pidcount=$(echo $pids | wc -w)
|
||||
|
||||
if [ $pidcount -eq 0 ] ; then
|
||||
>&2 echo "Asterisk is not running"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Single process, great.
|
||||
if [ $pidcount -eq 1 ] ; then
|
||||
pid=$pids
|
||||
|
@ -552,7 +547,7 @@ if $delete_coredumps_after ; then
|
|||
fi
|
||||
|
||||
if $delete_results_after ; then
|
||||
rm -rf "${cf//:/-}"-{brief,full,thread1,locks}.txt
|
||||
rm -rf "${cf//:/-}"-{brief,full,thread1,locks,info}.txt
|
||||
fi
|
||||
done
|
||||
|
||||
|
@ -568,12 +563,400 @@ exit
|
|||
|
||||
#@@@SCRIPTSTART@@@
|
||||
python
|
||||
|
||||
import datetime
|
||||
|
||||
|
||||
def timeval_to_datetime(value):
|
||||
"""Convert a timeval struct to a python datetime object
|
||||
|
||||
Args:
|
||||
value: A gdb Value representing a C timeval
|
||||
|
||||
Return:
|
||||
A python datetime object
|
||||
"""
|
||||
|
||||
sec = int(value['tv_sec'])
|
||||
usec = int(value['tv_usec'])
|
||||
|
||||
return datetime.datetime.fromtimestamp(sec + usec / float(1000000))
|
||||
|
||||
|
||||
def s_strip(value):
|
||||
"""Convert the given value to a string, and strip any leading/trailing
|
||||
spaces and/or quotes.
|
||||
|
||||
Args:
|
||||
name: The gdb Value to convert and strip
|
||||
|
||||
Return:
|
||||
The stripped value as a string
|
||||
"""
|
||||
|
||||
if value == None:
|
||||
return "None"
|
||||
|
||||
try:
|
||||
if 'char *' not in str(value.type) and 'char [' not in str(value.type):
|
||||
# Use the string method for everything but string pointers (only
|
||||
# points to first letter) and non-string values in general
|
||||
return value.string().strip('" ') or "<None>"
|
||||
except:
|
||||
pass
|
||||
|
||||
return str(value).strip('" ') or "<None>"
|
||||
|
||||
|
||||
def get(name):
|
||||
"""Retrieve a named variable's value as a string using GDB.
|
||||
|
||||
Args:
|
||||
name: The name of the variable to look up
|
||||
|
||||
Return:
|
||||
The variable's value as a string
|
||||
"""
|
||||
|
||||
return s_strip(gdb.parse_and_eval(name))
|
||||
|
||||
|
||||
def get_container_hash_objects(name, type, on_object=None):
|
||||
"""Retrieve a list of objects from an ao2_container_hash.
|
||||
|
||||
Expected on_object signature:
|
||||
|
||||
res, stop = on_object(GDB Value)
|
||||
|
||||
The given callback, on_object, is called for each object found in the
|
||||
container. The callback is passed a dereferenced GDB Value object and
|
||||
expects an object to be returned, which is then appended to a list of
|
||||
objects to be returned by this function. Iteration can be stopped by
|
||||
returning "True" for the second return value.
|
||||
|
||||
If on_object is not specified then the dereferenced GDB value is instead
|
||||
added directly to the returned list.
|
||||
|
||||
Args:
|
||||
name: The name of the ao2_container
|
||||
type: The type of objects stored in the container
|
||||
on_object: Optional function called on each object found
|
||||
|
||||
Return:
|
||||
A list of container objects
|
||||
"""
|
||||
|
||||
objs = []
|
||||
|
||||
try:
|
||||
|
||||
container = gdb.parse_and_eval(name).cast(
|
||||
gdb.lookup_type('struct ao2_container_hash').pointer())
|
||||
|
||||
# Loop over every bucket searching for hash bucket nodes
|
||||
for n in range(container['n_buckets']):
|
||||
node = container['buckets'][n]['list']['last']
|
||||
while node:
|
||||
# Each node holds the needed object
|
||||
obj = node.dereference()['common']['obj'].cast(
|
||||
gdb.lookup_type(type).pointer()).dereference()
|
||||
|
||||
res, stop = on_object(obj) if on_object else (obj, False)
|
||||
|
||||
if res:
|
||||
objs.append(res)
|
||||
|
||||
if stop:
|
||||
return objs
|
||||
|
||||
node = node.dereference()['links']['last']
|
||||
except Exception as e:
|
||||
print("{0} - {1}".format(name, e))
|
||||
pass
|
||||
|
||||
return objs
|
||||
|
||||
|
||||
def get_container_rbtree_objects(name, type, on_object=None):
|
||||
"""Retrieve a list of objects from an ao2_container_rbtree.
|
||||
|
||||
Expected on_object signature:
|
||||
|
||||
res, stop = on_object(GDB Value)
|
||||
|
||||
The given callback, on_object, is called for each object found in the
|
||||
container. The callback is passed a dereferenced GDB Value object and
|
||||
expects an object to be returned, which is then appended to a list of
|
||||
objects to be returned by this function. Iteration can be stopped by
|
||||
returning "True" for the second return value.
|
||||
|
||||
If on_object is not specified then the dereferenced GDB value is instead
|
||||
added directly to the returned list.
|
||||
|
||||
Args:
|
||||
name: The name of the ao2_container
|
||||
type: The type of objects stored in the container
|
||||
on_object: Optional function called on each object found
|
||||
|
||||
Return:
|
||||
A list of container objects
|
||||
"""
|
||||
|
||||
objs = []
|
||||
|
||||
def handle_node(node):
|
||||
|
||||
if not node:
|
||||
return True
|
||||
|
||||
# Each node holds the needed object
|
||||
obj = node.dereference()['common']['obj'].cast(
|
||||
gdb.lookup_type(type).pointer()).dereference()
|
||||
|
||||
res, stop = on_object(obj) if on_object else (obj, False)
|
||||
|
||||
if res:
|
||||
objs.append(res)
|
||||
|
||||
return not stop and (handle_node(node['left']) and
|
||||
handle_node(node['right']))
|
||||
|
||||
try:
|
||||
container = gdb.parse_and_eval(name).cast(
|
||||
gdb.lookup_type('struct ao2_container_rbtree').pointer())
|
||||
|
||||
handle_node(container['root'])
|
||||
except Exception as e:
|
||||
print("{0} - {1}".format(name, e))
|
||||
pass
|
||||
|
||||
return objs
|
||||
|
||||
|
||||
def build_info():
|
||||
|
||||
try:
|
||||
return ("Asterisk {0} built by {1} @ {2} on a {3} running {4} on {5}"
|
||||
.format(get("asterisk_version"),
|
||||
get("ast_build_user"),
|
||||
get("ast_build_hostname"),
|
||||
get("ast_build_machine"),
|
||||
get("ast_build_os"),
|
||||
get("ast_build_date")))
|
||||
except:
|
||||
return "Unable to retrieve build info"
|
||||
|
||||
|
||||
def build_opts():
|
||||
|
||||
try:
|
||||
return get("asterisk_build_opts")
|
||||
except:
|
||||
return "Unable to retrieve build options"
|
||||
|
||||
|
||||
def uptime():
|
||||
|
||||
try:
|
||||
started = timeval_to_datetime(gdb.parse_and_eval("ast_startuptime"))
|
||||
loaded = timeval_to_datetime(gdb.parse_and_eval("ast_lastreloadtime"))
|
||||
|
||||
return ("System started: {0}\n"
|
||||
"Last reload: {1}".format(started, loaded))
|
||||
except:
|
||||
return "Unable to retrieve uptime"
|
||||
|
||||
|
||||
class TaskProcessor(object):
|
||||
|
||||
template = ("{name:70} {processed:>10} {in_queue:>10} {max_depth:>10} "
|
||||
"{low_water:>10} {high_water:>10}")
|
||||
|
||||
header = {'name': 'Processor', 'processed': 'Processed',
|
||||
'in_queue': 'In Queue', 'max_depth': 'Max Depth',
|
||||
'low_water': 'Low water', 'high_water': 'High water'}
|
||||
|
||||
@staticmethod
|
||||
def objects():
|
||||
|
||||
try:
|
||||
objs = get_container_hash_objects('tps_singletons',
|
||||
'struct ast_taskprocessor', TaskProcessor.from_value)
|
||||
|
||||
objs.sort(key=lambda x: x.name.lower())
|
||||
except Exception as e:
|
||||
return []
|
||||
|
||||
return objs
|
||||
|
||||
@staticmethod
|
||||
def from_value(value):
|
||||
|
||||
return TaskProcessor(
|
||||
value['name'],
|
||||
value['stats']['_tasks_processed_count'],
|
||||
value['tps_queue_size'],
|
||||
value['stats']['max_qsize'],
|
||||
value['tps_queue_low'],
|
||||
value['tps_queue_high']), False
|
||||
|
||||
def __init__(self, name, processed, in_queue, max_depth,
|
||||
low_water, high_water):
|
||||
|
||||
self.name = s_strip(name)
|
||||
self.processed = int(processed)
|
||||
self.in_queue = int(in_queue)
|
||||
self.max_depth = int(max_depth)
|
||||
self.low_water = int(low_water)
|
||||
self.high_water = int(high_water)
|
||||
|
||||
|
||||
class Channel(object):
|
||||
|
||||
template = ("{name:30} {context:>20} {exten:>20} {priority:>10} {state:>25} "
|
||||
"{app:>20} {data:>30} {caller_id:>15} {created:>30} "
|
||||
"{account_code:>15} {peer_account:>15} {bridge_id:>38}")
|
||||
|
||||
header = {'name': 'Channel', 'context': 'Context', 'exten': 'Extension',
|
||||
'priority': 'Priority', 'state': "State", 'app': 'Application',
|
||||
'data': 'Data', 'caller_id': 'CallerID', 'created': 'Created',
|
||||
'account_code': 'Accountcode', 'peer_account': 'PeerAccount',
|
||||
'bridge_id': 'BridgeID'}
|
||||
|
||||
@staticmethod
|
||||
def objects():
|
||||
|
||||
try:
|
||||
objs = get_container_hash_objects('channels',
|
||||
'struct ast_channel', Channel.from_value)
|
||||
|
||||
objs.sort(key=lambda x: x.name.lower())
|
||||
except:
|
||||
return []
|
||||
|
||||
return objs
|
||||
|
||||
@staticmethod
|
||||
def from_value(value):
|
||||
|
||||
bridge_id = None
|
||||
if value['bridge']:
|
||||
bridge_id = value['bridge']['uniqueid']
|
||||
|
||||
return Channel(
|
||||
value['name'],
|
||||
value['context'],
|
||||
value['exten'],
|
||||
value['priority'],
|
||||
value['state'],
|
||||
value['appl'],
|
||||
value['data'],
|
||||
value['caller']['id']['number']['str'],
|
||||
timeval_to_datetime(value['creationtime']),
|
||||
value['accountcode'],
|
||||
value['peeraccount'],
|
||||
bridge_id), False
|
||||
|
||||
@staticmethod
|
||||
def summary():
|
||||
|
||||
try:
|
||||
return ("{0} active channels\n"
|
||||
"{1} active calls\n"
|
||||
"{2} calls processed".format(
|
||||
int(gdb.parse_and_eval(
|
||||
'channels').dereference()['elements']),
|
||||
get("countcalls"),
|
||||
get("totalcalls")))
|
||||
except:
|
||||
return "Unable to retrieve channel summary"
|
||||
|
||||
def __init__(self, name, context=None, exten=None, priority=None,
|
||||
state=None, app=None, data=None, caller_id=None,
|
||||
created=None, account_code=None, peer_account=None,
|
||||
bridge_id=None):
|
||||
|
||||
self.name = s_strip(name)
|
||||
self.context = s_strip(context)
|
||||
self.exten = s_strip(exten)
|
||||
self.priority = int(priority)
|
||||
self.state = s_strip(state)
|
||||
self.app = s_strip(app)
|
||||
self.data = s_strip(data)
|
||||
self.caller_id = s_strip(caller_id)
|
||||
self.created = s_strip(created)
|
||||
self.account_code = s_strip(account_code)
|
||||
self.peer_account = s_strip(peer_account)
|
||||
self.bridge_id = s_strip(bridge_id)
|
||||
|
||||
|
||||
class Bridge(object):
|
||||
|
||||
template = ("{uniqueid:38} {num_channels:>15} {subclass:>10} {tech:>20} "
|
||||
"{created:>30}")
|
||||
|
||||
header = {'uniqueid': 'Bridge-ID', 'num_channels': 'Chans',
|
||||
'subclass': 'Type', 'tech': 'Technology', 'created': 'Created'}
|
||||
|
||||
@staticmethod
|
||||
def objects():
|
||||
|
||||
try:
|
||||
objs = get_container_rbtree_objects('bridges',
|
||||
'struct ast_bridge', Bridge.from_value)
|
||||
|
||||
objs.sort(key=lambda x: x.uniqueid.lower())
|
||||
except:
|
||||
return []
|
||||
|
||||
return objs
|
||||
|
||||
@staticmethod
|
||||
def from_value(value):
|
||||
|
||||
return Bridge(
|
||||
value['uniqueid'],
|
||||
value['num_channels'],
|
||||
timeval_to_datetime(value['creationtime']),
|
||||
value['v_table']['name'],
|
||||
value['technology']['name']), False
|
||||
|
||||
|
||||
def __init__(self, uniqueid, num_channels=None, created=None, subclass=None,
|
||||
tech=None):
|
||||
|
||||
self.uniqueid = s_strip(uniqueid)
|
||||
self.num_channels = int(num_channels)
|
||||
self.created = s_strip(created)
|
||||
self.subclass = s_strip(subclass)
|
||||
self.tech = s_strip(tech)
|
||||
|
||||
|
||||
class DumpAsteriskCommand(gdb.Command):
|
||||
|
||||
def __init__(self):
|
||||
super(DumpAsteriskCommand, self).__init__ ("dump-asterisk",
|
||||
gdb.COMMAND_OBSCURE, gdb.COMPLETE_COMMAND)
|
||||
|
||||
def print_table(self, type):
|
||||
|
||||
plural = "{0}s".format(type.__name__)
|
||||
|
||||
objs = type.objects()
|
||||
|
||||
if not len(objs):
|
||||
print("{0} not found\n".format(plural))
|
||||
return
|
||||
|
||||
print("{0} ({1}):\n".format(plural, len(objs)))
|
||||
|
||||
print(type.template.format(**type.header))
|
||||
|
||||
for obj in objs:
|
||||
print(type.template.format(**vars(obj)))
|
||||
|
||||
print("\n")
|
||||
|
||||
def invoke(self, arg, from_tty):
|
||||
try:
|
||||
gdb.execute("interrupt", from_tty)
|
||||
|
@ -619,6 +1002,26 @@ class DumpAsteriskCommand(gdb.Command):
|
|||
gdb.execute("show_locks", from_tty)
|
||||
except:
|
||||
pass
|
||||
|
||||
print("!@!@!@! info.txt !@!@!@!\n")
|
||||
|
||||
gdb.execute('set print addr off')
|
||||
|
||||
try:
|
||||
print("{0}\n".format(build_info()))
|
||||
print("{0}\n".format(uptime()))
|
||||
print("Build options = {0}\n".format(build_opts()))
|
||||
|
||||
self.print_table(TaskProcessor)
|
||||
self.print_table(Bridge)
|
||||
self.print_table(Channel)
|
||||
|
||||
print(Channel.summary())
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
gdb.execute('set print addr on')
|
||||
|
||||
try:
|
||||
gdb.execute("continue", from_tty)
|
||||
except:
|
||||
|
|
Loading…
Reference in New Issue