This is an automated email from the ASF dual-hosted git repository.

xiaoxiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit 5d86bee5c7102b90a4376e630bd7c3cdf5e8395e
Author: xuxingliang <xuxingli...@xiaomi.com>
AuthorDate: Mon Sep 9 14:04:18 2024 +0800

    tools/gdb: add diagnose commands
    
    Run diagnostic related commands anytime to generate report.
    
    E.g `diag report -o systemreport.json`
    
    Signed-off-by: xuxingliang <xuxingli...@xiaomi.com>
---
 tools/gdb/nuttxgdb/__init__.py | 12 +++----
 tools/gdb/nuttxgdb/diagnose.py | 78 ++++++++++++++++++++++++++++++++++++++++++
 tools/gdb/nuttxgdb/memdump.py  |  8 +++++
 tools/gdb/nuttxgdb/thread.py   | 36 ++++++++++++++-----
 tools/gdb/nuttxgdb/utils.py    | 36 +++++++++++++++++++
 5 files changed, 155 insertions(+), 15 deletions(-)

diff --git a/tools/gdb/nuttxgdb/__init__.py b/tools/gdb/nuttxgdb/__init__.py
index 48fba48c0d..10cffc619b 100644
--- a/tools/gdb/nuttxgdb/__init__.py
+++ b/tools/gdb/nuttxgdb/__init__.py
@@ -21,17 +21,12 @@
 ############################################################################
 
 import importlib
-from os import listdir, path
+from os import path
 
 import gdb
 
 here = path.dirname(path.abspath(__file__))
 
-# Scan dir to get all modules available
-modules = [
-    path.splitext(path.basename(f))[0] for f in listdir(here) if 
f.endswith(".py")
-]
-
 
 def register_commands(event):
     if getattr(register_commands, "registered", False):
@@ -53,6 +48,10 @@ def register_commands(event):
             if isinstance(c, type) and issubclass(c, gdb.Command):
                 c()
 
+    # import utils module
+    utils = importlib.import_module(f"{__package__}.utils")
+    modules = utils.gather_modules(here)
+
     # Register prefix commands firstly
     init_gdb_commands("prefix")
     modules.remove("prefix")
@@ -62,7 +61,6 @@ def register_commands(event):
     for m in modules:
         init_gdb_commands(m)
 
-    utils = importlib.import_module(f"{__package__}.utils")
     utils.check_version()
 
 
diff --git a/tools/gdb/nuttxgdb/diagnose.py b/tools/gdb/nuttxgdb/diagnose.py
new file mode 100644
index 0000000000..d7e081a68f
--- /dev/null
+++ b/tools/gdb/nuttxgdb/diagnose.py
@@ -0,0 +1,78 @@
+############################################################################
+# tools/gdb/nuttx_gdb/diagnose.py
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.  The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+############################################################################
+
+import argparse
+
+import gdb
+
+from . import utils
+
+
+class DiagnosePrefix(gdb.Command):
+    """Diagnostic related commands."""
+
+    def __init__(self):
+        super().__init__("diagnose", gdb.COMMAND_USER, prefix=True)
+
+
+class DiagnoseReport(gdb.Command):
+    """Run diagnostics to generate reports."""
+
+    def __init__(self):
+        super().__init__("diagnose report", gdb.COMMAND_USER)
+
+    def invoke(self, args, from_tty):
+        parser = argparse.ArgumentParser(description=self.__doc__)
+        parser.add_argument(
+            "-o",
+            "--output",
+            type=str,
+            help="report output file name",
+        )
+
+        try:
+            args = parser.parse_args(gdb.string_to_argv(args))
+        except SystemExit:
+            return
+
+        reportfile = (
+            args.output
+            if args.output
+            else gdb.objfiles()[0].filename + ".diagnostics.json"
+        )
+
+        modules = utils.gather_modules()
+        modules.remove("prefix")
+        modules.remove("__init__")
+
+        commands = utils.gather_gdbcommands(modules=modules)
+
+        results = []
+        for clz in commands:
+            if hasattr(clz, "diagnose"):
+                command = clz()
+                gdb.write(f"Run command: {clz.__name__}\n")
+                results.append(command.diagnose())
+
+        gdb.write(f"Write report to {reportfile}\n")
+        with open(reportfile, "w") as f:
+            f.write(utils.jsonify(results, indent=4))
diff --git a/tools/gdb/nuttxgdb/memdump.py b/tools/gdb/nuttxgdb/memdump.py
index 94edd54fa3..4f76ab8552 100644
--- a/tools/gdb/nuttxgdb/memdump.py
+++ b/tools/gdb/nuttxgdb/memdump.py
@@ -762,6 +762,14 @@ class Memleak(gdb.Command):
 
         return {"simple": args.simple, "detail": args.detail}
 
+    def diagnose(self, *args, **kwargs):
+        output = gdb.execute("memleak", to_string=True)
+        return {
+            "title": "Memory Leak Report",
+            "command": "memleak",
+            "details": output,
+        }
+
     def invoke(self, args, from_tty):
         if sizeof_size_t == 4:
             align = 11
diff --git a/tools/gdb/nuttxgdb/thread.py b/tools/gdb/nuttxgdb/thread.py
index 07a89a91ea..0fb7829a67 100644
--- a/tools/gdb/nuttxgdb/thread.py
+++ b/tools/gdb/nuttxgdb/thread.py
@@ -616,6 +616,8 @@ class Ps(gdb.Command):
 
 
 class DeadLock(gdb.Command):
+    """Detect and report if threads have deadlock."""
+
     def __init__(self):
         super().__init__("deadlock", gdb.COMMAND_USER)
 
@@ -638,9 +640,12 @@ class DeadLock(gdb.Command):
         self.holders.append(holder)
         return self.has_deadlock(holder)
 
-    def invoke(self, args, from_tty):
+    def collect(self, tcbs):
+        """Collect the deadlock information"""
+
         detected = []
-        for tcb in utils.get_tcbs():
+        collected = []
+        for tcb in tcbs:
             self.holders = []  # Holders for this tcb
             pid = tcb["pid"]
             if pid in detected or not self.has_deadlock(tcb["pid"]):
@@ -649,13 +654,28 @@ class DeadLock(gdb.Command):
             # Deadlock detected
             detected.append(pid)
             detected.extend(self.holders)
-            gdb.write(f'Thread {pid} "{utils.get_task_name(tcb)}" has 
deadlocked!\n')
+            collected.append((pid, self.holders))
 
-            gdb.write(f"  holders: {pid}->")
-            gdb.write("->".join(str(pid) for pid in self.holders))
-            gdb.write("\n")
+        return collected
+
+    def diagnose(self, *args, **kwargs):
+        collected = self.collect(utils.get_tcbs())
 
-        if not detected:
+        return {
+            "title": "Deadlock Report",
+            "summary": f"{'No' if not collected else len(collected)} 
deadlocks",
+            "command": "deadlock",
+            "deadlocks": {int(pid): [i for i in h] for pid, h in collected},
+        }
+
+    def invoke(self, args, from_tty):
+        collected = self.collect(utils.get_tcbs())
+        if not collected:
             gdb.write("No deadlock detected.")
+            return
 
-        gdb.write("\n")
+        for pid, holders in collected:
+            gdb.write(f'Thread {pid} "{utils.get_task_name(pid)}" has 
deadlocked!\n')
+            gdb.write(f"  holders: {pid}->")
+            gdb.write("->".join(str(pid) for pid in holders))
+            gdb.write("\n")
diff --git a/tools/gdb/nuttxgdb/utils.py b/tools/gdb/nuttxgdb/utils.py
index c84912bb07..f425a354a4 100644
--- a/tools/gdb/nuttxgdb/utils.py
+++ b/tools/gdb/nuttxgdb/utils.py
@@ -20,6 +20,9 @@
 #
 ############################################################################
 
+import importlib
+import json
+import os
 import re
 import shlex
 from typing import List, Tuple, Union
@@ -600,6 +603,39 @@ def check_version():
     suppress_cli_notifications(state)
 
 
+def gather_modules(dir=None) -> List[str]:
+    dir = os.path.normpath(dir) if dir else os.path.dirname(__file__)
+    return [
+        os.path.splitext(os.path.basename(f))[0]
+        for f in os.listdir(dir)
+        if f.endswith(".py")
+    ]
+
+
+def gather_gdbcommands(modules=None, path=None) -> List[gdb.Command]:
+    modules = modules or gather_modules(path)
+    commands = []
+    for m in modules:
+        module = importlib.import_module(f"{__package__}.{m}")
+        for c in module.__dict__.values():
+            if isinstance(c, type) and issubclass(c, gdb.Command):
+                commands.append(c)
+    return commands
+
+
+def jsonify(obj, indent=None):
+    if not obj:
+        return "{}"
+
+    def dumper(obj):
+        try:
+            return str(obj) if isinstance(obj, gdb.Value) else obj.toJSON()
+        except Exception:
+            return obj.__dict__
+
+    return json.dumps(obj, default=dumper, indent=indent)
+
+
 class Hexdump(gdb.Command):
     """hexdump address/symbol <size>"""
 

Reply via email to