From fc16149fe4b977276b38bc9d4cfeee1ba7c31bec Mon Sep 17 00:00:00 2001
From: William Brown <firstyear@redhat.com>
Date: Thu, 4 May 2017 12:00:25 +1000
Subject: [PATCH] Ticket 49242 - add gdb script to rpm

Bug Description:  DS is hard to debug, and often has many threads.

Fix Description:  To make this easier for support and users to
work through issues, this gdb script allows automatic elimination
of idle threads, and dumps the inmemory access log.

https://pagure.io/389-ds-base/issue/49242

Author: wibrown

Review by: ???
---
 Makefile.am                      |   2 +
 ldap/admin/src/scripts/ds_gdb.py | 119 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 121 insertions(+)
 create mode 100644 ldap/admin/src/scripts/ds_gdb.py

diff --git a/Makefile.am b/Makefile.am
index 5f3af07..aebccb6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -564,6 +564,7 @@ dist_noinst_DATA = \
 	$(srcdir)/ldap/admin/src/scripts/*.pm \
 	$(srcdir)/ldap/admin/src/scripts/*.py \
 	$(srcdir)/ldap/admin/src/scripts/*.sh \
+	$(srcdir)/ldap/admin/src/scripts/ds-replcheck \
 	$(srcdir)/ldap/admin/src/scripts/migrate-ds.res \
 	$(srcdir)/ldap/ldif/*.in \
 	$(srcdir)/ldap/ldif/*.ldif \
@@ -765,6 +766,7 @@ perl_DATA = ldap/admin/src/scripts/SetupLog.pm \
 	ldap/admin/src/scripts/DSUpdateDialogs.pm
 
 python_DATA = ldap/admin/src/scripts/failedbinds.py \
+	ldap/admin/src/scripts/ds_gdb.py \
 	ldap/admin/src/scripts/logregex.py
 
 property_DATA = ldap/admin/src/scripts/setup-ds.res \
diff --git a/ldap/admin/src/scripts/ds_gdb.py b/ldap/admin/src/scripts/ds_gdb.py
new file mode 100644
index 0000000..c3fe692
--- /dev/null
+++ b/ldap/admin/src/scripts/ds_gdb.py
@@ -0,0 +1,119 @@
+# --- BEGIN COPYRIGHT BLOCK ---
+# Copyright (C) 2017 Red Hat, Inc.
+# All rights reserved.
+#
+# License: GPL (version 3 or any later version).
+# See LICENSE for details.
+# --- END COPYRIGHT BLOCK ---
+
+##################################################
+#
+# Usage: from within gdb call:
+#      source ds_gdb.py
+#
+
+import itertools
+import re
+
+import gdb
+from gdb.FrameDecorator import FrameDecorator
+
+class DSAccessLog (gdb.Command):
+    """Display the Directory Server access log."""
+    def __init__ (self):
+        super (DSAccessLog, self).__init__ ("ds-access-log", gdb.COMMAND_DATA)
+
+    def invoke (self, arg, from_tty):
+        print('===== BEGIN ACCESS LOG =====')
+        gdb.execute('set print elements 0')
+        o = gdb.execute('p loginfo.log_access_buffer.top', to_string=True)
+        for l in o.split('\\n'):
+            print(l)
+        print('===== END ACCESS LOG =====')
+
+class DSBacktrace(gdb.Command):
+    """Display a filtered backtrace"""
+    def __init__ (self):
+        super (DSBacktrace, self).__init__ ("ds-backtrace", gdb.COMMAND_DATA)
+
+    def _parse_thread_state(self, lwpid, tid):
+        # Stash the BT output
+        o = gdb.execute('bt', to_string=True)
+        # Get to the top of the frame stack.
+        gdb.newest_frame()
+        # Now work our way down.
+        backtrace = []
+        cur_frame = gdb.selected_frame()
+        while cur_frame is not None:
+            backtrace.append(cur_frame.name())
+            cur_frame = cur_frame.older()
+        # Dicts can't use lists as keys, so we need to squash this to a string.
+        s_backtrace = ' '.join(backtrace)
+        # Have we seen this trace before?
+        if s_backtrace not in self._stack_maps:
+            # Make it!
+            self._stack_maps[s_backtrace] = []
+        # Add it to the set.
+        self._stack_maps[s_backtrace].append({'gtid': tid, 'lwpid': lwpid, 'bt': o}  )
+
+    def invoke(self, arg, from_tty):
+        print('===== BEGIN ACTIVE THREADS =====')
+
+        # Reset our thread maps.
+        self._stack_maps = {}
+
+        inferiors = gdb.inferiors()
+        for inferior in inferiors:
+            threads = inferior.threads()
+            for thread in threads:
+                (tpid, lwpid, tid) = thread.ptid
+                gtid = thread.num
+                thread.switch()
+                self._parse_thread_state(lwpid, gtid)
+
+        # print (self._stack_maps)
+        for m in self._stack_maps:
+            # Take a copy of the bt
+            o = self._stack_maps[m][0]['bt']
+            # Print every thread and id.
+            for t in self._stack_maps[m]:
+                print("Thread %s (LWP %s))" % (t['gtid'], t['lwpid']))
+            # Print the trace
+            print(o)
+
+        print('===== END ACTIVE THREADS =====')
+
+class DSIdleFilterDecorator(FrameDecorator):
+    def __init__(self, fobj):
+        super(DSIdleFilterDecorator, self).__init__(fobj)
+
+    def function(self):
+        frame = self.inferior_frame()
+        name = str(frame.name())
+
+        if name == 'connection_wait_for_new_work' or name == 'work_q_wait':
+            name = "[IDLE THREAD] " + name
+
+        return name
+
+class DSIdleFilter():
+    def __init__(self):
+        self.name = "DSIdleFilter"
+        self.priority = 100
+        self.enabled = True
+        # Register this frame filter with the global frame_filters
+        # dictionary.
+        gdb.frame_filters[self.name] = self
+
+    def filter(self, frame_iter):
+        # Just return the iterator.
+        if hasattr(itertools, 'imap'):
+            frame_iter = imap(DSIdleFilterDecorator, frame_iter)
+        else:
+            frame_iter = map(DSIdleFilterDecorator, frame_iter)
+        return frame_iter
+
+DSErrorLog ()
+DSBacktrace()
+DSIdleFilter ()
+
-- 
1.8.3.1

