[Lldb-commits] [lldb] [lldb] Track execution time of commands in statistics (PR #200817)

Raphael Isemann via lldb-commits lldb-commits at lists.llvm.org
Mon Jun 1 06:34:47 PDT 2026


https://github.com/Teemperor created https://github.com/llvm/llvm-project/pull/200817

LLDB tracks how often certain commands are invoked. This patch adds to this functionality how long it takes in total for all these commands to be invoked.

>From 7fcc8c8d115f36280b987aa4ab0c33fa60296827 Mon Sep 17 00:00:00 2001
From: Raphael Isemann <rise at apple.com>
Date: Mon, 1 Jun 2026 14:21:24 +0100
Subject: [PATCH] [lldb] Track execution time of commands in statistics

LLDB tracks how often certain commands are invoked. This patch adds
to this functionality how long it takes in total for all these commands
to be invoked.
---
 .../lldb/Interpreter/CommandInterpreter.h     | 21 ++++++++++---
 .../source/Commands/CommandObjectCommands.cpp | 30 +++++++++++--------
 .../source/Interpreter/CommandInterpreter.cpp |  8 +++--
 lldb/source/Interpreter/CommandObject.cpp     |  8 +++--
 .../commands/statistics/basic/TestStats.py    | 13 +++++++-
 .../stats_api/TestStatisticsAPI.py            |  3 +-
 6 files changed, 60 insertions(+), 23 deletions(-)

diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h
index 6fd5b5285c27d..7e77d7adb223a 100644
--- a/lldb/include/lldb/Interpreter/CommandInterpreter.h
+++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h
@@ -683,8 +683,9 @@ class CommandInterpreter : public Broadcaster,
   Status PreprocessCommand(std::string &command);
   Status PreprocessToken(std::string &token);
 
-  void IncreaseCommandUsage(const CommandObject &cmd_obj) {
-    ++m_command_usages[cmd_obj.GetCommandName()];
+  void IncreaseCommandUsage(const CommandObject &cmd_obj,
+                            const StatsDuration &duration) {
+    m_command_stats[cmd_obj.GetCommandName()].addInvocation(duration);
   }
 
   void SetPrintCallback(CommandReturnObjectCallback callback);
@@ -815,8 +816,20 @@ class CommandInterpreter : public Broadcaster,
   bool m_allow_exit_code = false;
 
   /// Command usage statistics.
-  typedef llvm::StringMap<uint64_t> CommandUsageMap;
-  CommandUsageMap m_command_usages;
+  struct CommandStats {
+    /// How often this command was invoked.
+    uint64_t invocations = 0;
+    /// Total wall time for all invocations.
+    StatsDuration totalDuration;
+
+    void addInvocation(const StatsDuration &duration) {
+      invocations += 1;
+      totalDuration += duration.get();
+    }
+  };
+
+  typedef llvm::StringMap<CommandStats> CommandStatsMap;
+  CommandStatsMap m_command_stats;
 
   /// Turn on settings `interpreter.save-transcript` for LLDB to populate
   /// this stream. Otherwise this stream is empty.
diff --git a/lldb/source/Commands/CommandObjectCommands.cpp b/lldb/source/Commands/CommandObjectCommands.cpp
index 84e661ec01f53..5f80167edeb58 100644
--- a/lldb/source/Commands/CommandObjectCommands.cpp
+++ b/lldb/source/Commands/CommandObjectCommands.cpp
@@ -1089,25 +1089,29 @@ class CommandObjectPythonFunction : public CommandObjectRaw {
                  CommandReturnObject &result) override {
     ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
 
-    m_interpreter.IncreaseCommandUsage(*this);
-
     Status error;
 
     result.SetStatus(eReturnStatusInvalid);
 
-    if (!scripter || !scripter->RunScriptBasedCommand(
-                         m_function_name.c_str(), raw_command_line, m_synchro,
-                         result, error, m_exe_ctx)) {
-      result.AppendError(error.AsCString());
-    } else {
-      // Don't change the status if the command already set it...
-      if (result.GetStatus() == eReturnStatusInvalid) {
-        if (result.GetOutputString().empty())
-          result.SetStatus(eReturnStatusSuccessFinishNoResult);
-        else
-          result.SetStatus(eReturnStatusSuccessFinishResult);
+    StatsDuration duration;
+    {
+      ElapsedTime measure(duration);
+      if (!scripter || !scripter->RunScriptBasedCommand(
+                           m_function_name.c_str(), raw_command_line, m_synchro,
+                           result, error, m_exe_ctx)) {
+        result.AppendError(error.AsCString());
+      } else {
+        // Don't change the status if the command already set it...
+        if (result.GetStatus() == eReturnStatusInvalid) {
+          if (result.GetOutputString().empty())
+            result.SetStatus(eReturnStatusSuccessFinishNoResult);
+          else
+            result.SetStatus(eReturnStatusSuccessFinishResult);
+        }
       }
     }
+
+    m_interpreter.IncreaseCommandUsage(*this, duration);
   }
 
 private:
diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp
index 0c5456c2c3b57..20e29eee45162 100644
--- a/lldb/source/Interpreter/CommandInterpreter.cpp
+++ b/lldb/source/Interpreter/CommandInterpreter.cpp
@@ -3911,8 +3911,12 @@ CommandInterpreter::ResolveCommandImpl(std::string &command_line,
 
 llvm::json::Value CommandInterpreter::GetStatistics() {
   llvm::json::Object stats;
-  for (const auto &command_usage : m_command_usages)
-    stats.try_emplace(command_usage.getKey(), command_usage.getValue());
+  for (const auto &command_usage : m_command_stats) {
+    const auto &cmd = command_usage.second;
+    llvm::json::Object cmdObj{{"invocations", cmd.invocations},
+                              {"duration", cmd.totalDuration.get().count()}};
+    stats.try_emplace(command_usage.first(), std::move(cmdObj));
+  }
   return stats;
 }
 
diff --git a/lldb/source/Interpreter/CommandObject.cpp b/lldb/source/Interpreter/CommandObject.cpp
index 75abf49e77207..702c2e8da43eb 100644
--- a/lldb/source/Interpreter/CommandObject.cpp
+++ b/lldb/source/Interpreter/CommandObject.cpp
@@ -856,9 +856,13 @@ void CommandObjectParsed::Execute(const char *args_string,
           Cleanup();
           return;
         }
-        m_interpreter.IncreaseCommandUsage(*this);
         DoExecuteStatusCheck check(result);
-        DoExecute(cmd_args, result);
+        StatsDuration duration;
+        {
+          ElapsedTime measure(duration);
+          DoExecute(cmd_args, result);
+        }
+        m_interpreter.IncreaseCommandUsage(*this, duration);
       }
     }
 
diff --git a/lldb/test/API/commands/statistics/basic/TestStats.py b/lldb/test/API/commands/statistics/basic/TestStats.py
index a32b8feecc5cf..67a484e7fabb3 100644
--- a/lldb/test/API/commands/statistics/basic/TestStats.py
+++ b/lldb/test/API/commands/statistics/basic/TestStats.py
@@ -437,7 +437,18 @@ def test_commands(self):
 
         command_stats = self.get_command_stats(debug_stats)
         self.assertNotEqual(command_stats, None)
-        self.assertEqual(command_stats["target list"], 2)
+        self.assertEqual(command_stats["target list"]["invocations"], 2)
+
+        # Duration should always be positive.
+        old_duration = command_stats["target list"]["duration"]
+        self.assertGreater(command_stats["target list"]["duration"], 0)
+
+        # Check that duration increases with each command.
+        interp.HandleCommand("target list", result)
+        debug_stats = self.get_stats()
+        command_stats = self.get_command_stats(debug_stats)
+        self.assertGreater(command_stats["target list"]["duration"], old_duration)
+
 
     def test_breakpoints(self):
         """Test "statistics dump"
diff --git a/lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py b/lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py
index 5f09e8fe07c0b..ff98bd94ddadd 100644
--- a/lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py
+++ b/lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py
@@ -120,7 +120,8 @@ def test_command_stats_api(self):
         command_stats = json.loads(stream.GetData())
 
         # Verify bt command is correctly parsed into final form.
-        self.assertEqual(command_stats["thread backtrace"], 1)
+        self.assertEqual(command_stats["thread backtrace"]["invocations"], 1)
+        self.assertGreater(command_stats["thread backtrace"]["duration"], 0)
         # Verify original raw command is not duplicatedly captured.
         self.assertNotIn("bt", command_stats)
         # Verify bt's regex command is not duplicatedly captured.



More information about the lldb-commits mailing list