[Asterisk-code-review] ast_coredumper: add Asterisk information dump (asterisk[13])

Joshua Colp asteriskteam at digium.com
Thu Mar 26 07:43:51 CDT 2020


Joshua Colp has submitted this change. ( https://gerrit.asterisk.org/c/asterisk/+/13979 )

Change subject: ast_coredumper: add Asterisk information dump
......................................................................

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
---
M contrib/scripts/ast_coredumper
1 file changed, 410 insertions(+), 7 deletions(-)

Approvals:
  Joshua Colp: Looks good to me, approved; Approved for Submit
  George Joseph: Looks good to me, but someone else must approve



diff --git a/contrib/scripts/ast_coredumper b/contrib/scripts/ast_coredumper
index defb629..ee62ab8 100755
--- a/contrib/scripts/ast_coredumper
+++ b/contrib/scripts/ast_coredumper
@@ -383,14 +383,9 @@
 	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 @@
 	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 @@
 
 #@@@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 @@
             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:

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/13979
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: 13
Gerrit-Change-Id: I54f35c19ab69b8f8dc78cc933c3fb7c99cef346b
Gerrit-Change-Number: 13979
Gerrit-PatchSet: 4
Gerrit-Owner: Kevin Harwell <kharwell at digium.com>
Gerrit-Reviewer: Friendly Automation
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Joshua Colp <jcolp at sangoma.com>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20200326/4db605bb/attachment-0001.html>


More information about the asterisk-code-review mailing list