From cd0516638c348b6fea5961f99e81492e6aab43a0 Mon Sep 17 00:00:00 2001 From: William Brown 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 | 4 ++ ldap/admin/src/scripts/ns-slapd-gdb.py | 119 +++++++++++++++++++++++++++++++++ rpm/389-ds-base.spec.in | 2 + 3 files changed, 125 insertions(+) create mode 100644 ldap/admin/src/scripts/ns-slapd-gdb.py diff --git a/Makefile.am b/Makefile.am index 5f3af07..6f9c1c4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -267,6 +267,7 @@ mibdir = $(datadir)@mibdir@ updatedir = $(datadir)@updatedir@ pkgconfigdir = $(libdir)/pkgconfig serverincdir = $(includedir)/@serverincdir@ +gdbautoloaddir = $(prefixdir)/share/gdb/auto-load$(sbindir) defaultuser=@defaultuser@ defaultgroup=@defaultgroup@ @@ -564,6 +565,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 \ @@ -767,6 +769,8 @@ perl_DATA = ldap/admin/src/scripts/SetupLog.pm \ python_DATA = ldap/admin/src/scripts/failedbinds.py \ ldap/admin/src/scripts/logregex.py +gdbautoload_DATA = ldap/admin/src/scripts/ns-slapd-gdb.py + property_DATA = ldap/admin/src/scripts/setup-ds.res \ ldap/admin/src/scripts/migrate-ds.res diff --git a/ldap/admin/src/scripts/ns-slapd-gdb.py b/ldap/admin/src/scripts/ns-slapd-gdb.py new file mode 100644 index 0000000..0df32e1 --- /dev/null +++ b/ldap/admin/src/scripts/ns-slapd-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 + +DSAccessLog() +DSBacktrace() +DSIdleFilter() + diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in index d334757..905d70a 100644 --- a/rpm/389-ds-base.spec.in +++ b/rpm/389-ds-base.spec.in @@ -288,6 +288,7 @@ make setup.py %install rm -rf $RPM_BUILD_ROOT +mkdir -p %{buildroot}%{_datadir}/gdb/auto-load%{_sbindir} make DESTDIR="$RPM_BUILD_ROOT" install # Copy in our docs from doxygen. @@ -435,6 +436,7 @@ fi %config(noreplace)%{_sysconfdir}/sysconfig/%{pkgname} %config(noreplace)%{_sysconfdir}/sysconfig/%{pkgname}.systemd %{_datadir}/%{pkgname} +%{_datadir}/gdb/auto-load/* %{_unitdir} %{_bindir}/* # We have to seperate this from being a glob to ensure the caps are applied. -- 1.8.3.1