[Lldb-commits] [lldb] Fix a test failure in PR #92843 (PR #94067)

via lldb-commits lldb-commits at lists.llvm.org
Fri May 31 15:32:24 PDT 2024


https://github.com/royitaqi created https://github.com/llvm/llvm-project/pull/94067

Problematic PR: https://github.com/llvm/llvm-project/pull/92843

There is a failure in the added test for Linux (test passes on macOS)

The problem of the said test is that its assertion is unnecessarily detailed. Relaxing the assertion makes the test pass.


>From 8499f16ad46b3268f35da2bfcbfa02a10aab935a Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Mon, 20 May 2024 22:30:40 -0400
Subject: [PATCH 01/12] Add resolvedCommand to transcript, add transcript to
 statistics dump

---
 lldb/include/lldb/API/SBCommandInterpreter.h  |  3 +-
 .../lldb/Interpreter/CommandInterpreter.h     |  5 +--
 .../source/Interpreter/CommandInterpreter.cpp |  6 ++++
 lldb/source/Target/Statistics.cpp             | 31 +++++++++++++++++++
 .../commands/statistics/basic/TestStats.py    | 30 ++++++++++++++++++
 .../interpreter/TestCommandInterpreterAPI.py  | 20 ++++++++----
 6 files changed, 86 insertions(+), 9 deletions(-)

diff --git a/lldb/include/lldb/API/SBCommandInterpreter.h b/lldb/include/lldb/API/SBCommandInterpreter.h
index 8ac36344b3a79..8eb4a71cb7f88 100644
--- a/lldb/include/lldb/API/SBCommandInterpreter.h
+++ b/lldb/include/lldb/API/SBCommandInterpreter.h
@@ -320,7 +320,8 @@ class SBCommandInterpreter {
 
   /// Returns a list of handled commands, output and error. Each element in
   /// the list is a dictionary with the following keys/values:
-  /// - "command" (string): The command that was executed.
+  /// - "command" (string): The command that was given by the user.
+  /// - "resolvedCommand" (string): The expanded command that was executed.
   /// - "output" (string): The output of the command. Empty ("") if no output.
   /// - "error" (string): The error of the command. Empty ("") if no error.
   /// - "seconds" (float): The time it took to execute the command.
diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h
index ccc30cf4f1a82..7f420daca450a 100644
--- a/lldb/include/lldb/Interpreter/CommandInterpreter.h
+++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h
@@ -580,7 +580,7 @@ class CommandInterpreter : public Broadcaster,
   void SetEchoCommentCommands(bool enable);
 
   bool GetRepeatPreviousCommand() const;
-  
+
   bool GetRequireCommandOverwrite() const;
 
   const CommandObject::CommandMap &GetUserCommands() const {
@@ -776,7 +776,8 @@ class CommandInterpreter : public Broadcaster,
 
   /// Contains a list of handled commands and their details. Each element in
   /// the list is a dictionary with the following keys/values:
-  /// - "command" (string): The command that was executed.
+  /// - "command" (string): The command that was given by the user.
+  /// - "resolvedCommand" (string): The expanded command that was executed.
   /// - "output" (string): The output of the command. Empty ("") if no output.
   /// - "error" (string): The error of the command. Empty ("") if no error.
   /// - "seconds" (float): The time it took to execute the command.
diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp
index 811726e30af4d..04820bd7d39f6 100644
--- a/lldb/source/Interpreter/CommandInterpreter.cpp
+++ b/lldb/source/Interpreter/CommandInterpreter.cpp
@@ -2011,6 +2011,12 @@ bool CommandInterpreter::HandleCommand(const char *command_line,
               wants_raw_input ? "True" : "False");
   }
 
+  // To test whether or not transcript should be saved, `transcript_item` is
+  // used instead of `GetSaveTrasncript()`. This is because the latter will
+  // fail when the command is "settings set interpreter.save-transcript true".
+  if (transcript_item)
+    transcript_item->AddStringItem("resolvedCommand", command_string);
+
   // Phase 2.
   // Take care of things like setting up the history command & calling the
   // appropriate Execute method on the CommandObject, with the appropriate
diff --git a/lldb/source/Target/Statistics.cpp b/lldb/source/Target/Statistics.cpp
index 7f866ae0ef324..fd31377abd06b 100644
--- a/lldb/source/Target/Statistics.cpp
+++ b/lldb/source/Target/Statistics.cpp
@@ -16,6 +16,7 @@
 #include "lldb/Target/Process.h"
 #include "lldb/Target/Target.h"
 #include "lldb/Target/UnixSignals.h"
+#include "lldb/Utility/StructuredData.h"
 
 using namespace lldb;
 using namespace lldb_private;
@@ -362,6 +363,36 @@ llvm::json::Value DebuggerStats::ReportStatistics(
     global_stats.try_emplace("modules", std::move(json_modules));
     global_stats.try_emplace("memory", std::move(json_memory));
     global_stats.try_emplace("commands", std::move(cmd_stats));
+
+    // When transcript is available, add it to the to-be-returned statistics.
+    //
+    // NOTE:
+    // When the statistics is polled by an LLDB command:
+    // - The transcript in the returned statistics *will NOT* contain the
+    //   returned statistics itself.
+    // - The returned statistics *will* be written to the internal transcript
+    //   buffer as the output of the said LLDB command. It *will* appear in
+    //   the next statistcs or transcript poll.
+    //
+    // For example, let's say the following commands are run in order:
+    // - "version"
+    // - "statistics dump"  <- call it "A"
+    // - "statistics dump"  <- call it "B"
+    // The output of "A" will contain the transcript of "version" and
+    // "statistics dump" (A), with the latter having empty output. The output
+    // of B will contain the trascnript of "version", "statistics dump" (A),
+    // "statistics dump" (B), with A's output populated and B's output empty.
+    const StructuredData::Array &transcript =
+        debugger.GetCommandInterpreter().GetTranscript();
+    if (transcript.GetSize() != 0) {
+      std::string buffer;
+      llvm::raw_string_ostream ss(buffer);
+      json::OStream json_os(ss);
+      transcript.Serialize(json_os);
+      if (auto json_transcript = llvm::json::parse(ss.str()))
+        global_stats.try_emplace("transcript",
+                                 std::move(json_transcript.get()));
+    }
   }
 
   return std::move(global_stats);
diff --git a/lldb/test/API/commands/statistics/basic/TestStats.py b/lldb/test/API/commands/statistics/basic/TestStats.py
index fb6fc07d2d443..a536ee2c9bbdc 100644
--- a/lldb/test/API/commands/statistics/basic/TestStats.py
+++ b/lldb/test/API/commands/statistics/basic/TestStats.py
@@ -623,3 +623,33 @@ def test_had_frame_variable_errors(self):
         # Verify that the top level statistic that aggregates the number of
         # modules with debugInfoHadVariableErrors is greater than zero
         self.assertGreater(stats["totalModuleCountWithVariableErrors"], 0)
+
+def test_transcript(self):
+        """
+        Test "statistics dump" and the transcript information.
+        """
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+        target = self.createTestTarget(file_path=exe)
+        self.runCmd("settings set target.save-transcript true")
+        self.runCmd("version")
+
+        # Verify the output of a first "statistics dump"
+        debug_stats = self.get_stats()
+        self.assertIn("transcript", debug_stats)
+        transcript = debug_stats["transcript"]
+        self.assertEqual(len(transcript), 2)
+        self.assertEqual(transcript[0]["command"], "version")
+        self.assertEqual(transcript[1]["command"], "statistics dump")
+        self.assertEqual(transcript[1]["output"], "")
+
+        # Verify the output of a second "statistics dump"
+        debug_stats = self.get_stats()
+        self.assertIn("transcript", debug_stats)
+        transcript = debug_stats["transcript"]
+        self.assertEqual(len(transcript), 3)
+        self.assertEqual(transcript[0]["command"], "version")
+        self.assertEqual(transcript[1]["command"], "statistics dump")
+        self.assertNotEqual(transcript[1]["output"], "")
+        self.assertEqual(transcript[2]["command"], "statistics dump")
+        self.assertEqual(transcript[2]["output"], "")
diff --git a/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py b/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
index 95643eef0d344..73eef27095b6d 100644
--- a/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
+++ b/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
@@ -104,7 +104,7 @@ def getTranscriptAsPythonObject(self, ci):
 
         return json.loads(stream.GetData())
 
-    def test_structured_transcript(self):
+    def test_get_transcript(self):
         """Test structured transcript generation and retrieval."""
         ci = self.buildAndCreateTarget()
 
@@ -118,7 +118,7 @@ def test_structured_transcript(self):
         res = lldb.SBCommandReturnObject()
         ci.HandleCommand("version", res)
         ci.HandleCommand("an-unknown-command", res)
-        ci.HandleCommand("breakpoint set -f main.c -l %d" % self.line, res)
+        ci.HandleCommand("br set -f main.c -l %d" % self.line, res)
         ci.HandleCommand("r", res)
         ci.HandleCommand("p a", res)
         ci.HandleCommand("statistics dump", res)
@@ -130,6 +130,7 @@ def test_structured_transcript(self):
         # All commands should have expected fields.
         for command in transcript:
             self.assertIn("command", command)
+            self.assertIn("resolvedCommand", command)
             self.assertIn("output", command)
             self.assertIn("error", command)
             self.assertIn("seconds", command)
@@ -146,6 +147,7 @@ def test_structured_transcript(self):
 
         # (lldb) version
         self.assertEqual(transcript[0]["command"], "version")
+        self.assertEqual(transcript[0]["resolvedCommand"], "version")
         self.assertIn("lldb version", transcript[0]["output"])
         self.assertEqual(transcript[0]["error"], "")
 
@@ -153,18 +155,21 @@ def test_structured_transcript(self):
         self.assertEqual(transcript[1],
             {
                 "command": "an-unknown-command",
+                "resolvedCommand": "an-unknown-command",
                 "output": "",
                 "error": "error: 'an-unknown-command' is not a valid command.\n",
             })
 
-        # (lldb) breakpoint set -f main.c -l <line>
-        self.assertEqual(transcript[2]["command"], "breakpoint set -f main.c -l %d" % self.line)
+        # (lldb) br set -f main.c -l <line>
+        self.assertEqual(transcript[2]["command"], "br set -f main.c -l %d" % self.line)
+        self.assertEqual(transcript[2]["resolvedCommand"], "breakpoint set -f main.c -l %d" % self.line)
         # Breakpoint 1: where = a.out`main + 29 at main.c:5:3, address = 0x0000000100000f7d
         self.assertIn("Breakpoint 1: where = a.out`main ", transcript[2]["output"])
         self.assertEqual(transcript[2]["error"], "")
 
         # (lldb) r
         self.assertEqual(transcript[3]["command"], "r")
+        self.assertEqual(transcript[3]["resolvedCommand"], "process launch -X true --")
         # Process 25494 launched: '<path>/TestCommandInterpreterAPI.test_structured_transcript/a.out' (x86_64)
         self.assertIn("Process", transcript[3]["output"])
         self.assertIn("launched", transcript[3]["output"])
@@ -174,11 +179,15 @@ def test_structured_transcript(self):
         self.assertEqual(transcript[4],
             {
                 "command": "p a",
+                "resolvedCommand": "dwim-print -- a",
                 "output": "(int) 123\n",
                 "error": "",
             })
 
         # (lldb) statistics dump
+        self.assertEqual(transcript[5]["command"], "statistics dump")
+        self.assertEqual(transcript[5]["resolvedCommand"], "statistics dump")
+        self.assertEqual(transcript[5]["error"], "")
         statistics_dump = json.loads(transcript[5]["output"])
         # Dump result should be valid JSON
         self.assertTrue(statistics_dump is not json.JSONDecodeError)
@@ -194,7 +203,6 @@ def test_save_transcript_setting_default(self):
 
         # The setting's default value should be "false"
         self.runCmd("settings show interpreter.save-transcript", "interpreter.save-transcript (boolean) = false\n")
-        # self.assertEqual(res.GetOutput(), )
 
     def test_save_transcript_setting_off(self):
         ci = self.buildAndCreateTarget()
@@ -220,7 +228,7 @@ def test_save_transcript_setting_on(self):
         self.assertEqual(len(transcript), 1)
         self.assertEqual(transcript[0]["command"], "version")
 
-    def test_save_transcript_returns_copy(self):
+    def test_get_transcript_returns_copy(self):
         """
         Test that the returned structured data is *at least* a shallow copy.
 

>From 1cac4fa898051537b6cc9c8472dd368436c3a511 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Mon, 20 May 2024 22:41:57 -0400
Subject: [PATCH 02/12] Small fixes

---
 lldb/include/lldb/Interpreter/CommandInterpreter.h            | 2 +-
 .../API/python_api/interpreter/TestCommandInterpreterAPI.py   | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h
index 7f420daca450a..6e21af7180f79 100644
--- a/lldb/include/lldb/Interpreter/CommandInterpreter.h
+++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h
@@ -580,7 +580,7 @@ class CommandInterpreter : public Broadcaster,
   void SetEchoCommentCommands(bool enable);
 
   bool GetRepeatPreviousCommand() const;
-
+  
   bool GetRequireCommandOverwrite() const;
 
   const CommandObject::CommandMap &GetUserCommands() const {
diff --git a/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py b/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
index 73eef27095b6d..1541bcdddd1a4 100644
--- a/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
+++ b/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
@@ -118,7 +118,7 @@ def test_get_transcript(self):
         res = lldb.SBCommandReturnObject()
         ci.HandleCommand("version", res)
         ci.HandleCommand("an-unknown-command", res)
-        ci.HandleCommand("br set -f main.c -l %d" % self.line, res)
+        ci.HandleCommand("br s -f main.c -l %d" % self.line, res)
         ci.HandleCommand("r", res)
         ci.HandleCommand("p a", res)
         ci.HandleCommand("statistics dump", res)
@@ -161,7 +161,7 @@ def test_get_transcript(self):
             })
 
         # (lldb) br set -f main.c -l <line>
-        self.assertEqual(transcript[2]["command"], "br set -f main.c -l %d" % self.line)
+        self.assertEqual(transcript[2]["command"], "br s -f main.c -l %d" % self.line)
         self.assertEqual(transcript[2]["resolvedCommand"], "breakpoint set -f main.c -l %d" % self.line)
         # Breakpoint 1: where = a.out`main + 29 at main.c:5:3, address = 0x0000000100000f7d
         self.assertIn("Breakpoint 1: where = a.out`main ", transcript[2]["output"])

>From 67a1197a15682731f37890734a5c8674896e0428 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Mon, 20 May 2024 22:42:42 -0400
Subject: [PATCH 03/12] More small fixes

---
 .../API/python_api/interpreter/TestCommandInterpreterAPI.py     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py b/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
index 1541bcdddd1a4..157594b4aefa1 100644
--- a/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
+++ b/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
@@ -160,7 +160,7 @@ def test_get_transcript(self):
                 "error": "error: 'an-unknown-command' is not a valid command.\n",
             })
 
-        # (lldb) br set -f main.c -l <line>
+        # (lldb) br s -f main.c -l <line>
         self.assertEqual(transcript[2]["command"], "br s -f main.c -l %d" % self.line)
         self.assertEqual(transcript[2]["resolvedCommand"], "breakpoint set -f main.c -l %d" % self.line)
         # Breakpoint 1: where = a.out`main + 29 at main.c:5:3, address = 0x0000000100000f7d

>From eadf71a28c7445b727e5ea783ffb4d070b5c8b3b Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Thu, 23 May 2024 22:26:54 -0400
Subject: [PATCH 04/12] Add timestamp into transcript, rename duration field,
 fix format for python files

---
 .../source/Interpreter/CommandInterpreter.cpp |  9 ++-
 .../commands/statistics/basic/TestStats.py    | 57 +++++++++---------
 .../interpreter/TestCommandInterpreterAPI.py  | 60 ++++++++++++++-----
 3 files changed, 83 insertions(+), 43 deletions(-)

diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp
index 04820bd7d39f6..97d581d21784f 100644
--- a/lldb/source/Interpreter/CommandInterpreter.cpp
+++ b/lldb/source/Interpreter/CommandInterpreter.cpp
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include <chrono>
 #include <cstdlib>
 #include <limits>
 #include <memory>
@@ -1909,6 +1910,11 @@ bool CommandInterpreter::HandleCommand(const char *command_line,
 
     transcript_item = std::make_shared<StructuredData::Dictionary>();
     transcript_item->AddStringItem("command", command_line);
+    transcript_item->AddIntegerItem(
+        "timestampInEpochSeconds",
+        std::chrono::duration_cast<std::chrono::seconds>(
+            std::chrono::system_clock::now().time_since_epoch())
+            .count());
     m_transcript.AddItem(transcript_item);
   }
 
@@ -2078,7 +2084,8 @@ bool CommandInterpreter::HandleCommand(const char *command_line,
 
     transcript_item->AddStringItem("output", result.GetOutputData());
     transcript_item->AddStringItem("error", result.GetErrorData());
-    transcript_item->AddFloatItem("seconds", execute_time.get().count());
+    transcript_item->AddFloatItem("durationInSeconds",
+                                  execute_time.get().count());
   }
 
   return result.Succeeded();
diff --git a/lldb/test/API/commands/statistics/basic/TestStats.py b/lldb/test/API/commands/statistics/basic/TestStats.py
index a536ee2c9bbdc..abf610d78dd9f 100644
--- a/lldb/test/API/commands/statistics/basic/TestStats.py
+++ b/lldb/test/API/commands/statistics/basic/TestStats.py
@@ -624,32 +624,33 @@ def test_had_frame_variable_errors(self):
         # modules with debugInfoHadVariableErrors is greater than zero
         self.assertGreater(stats["totalModuleCountWithVariableErrors"], 0)
 
-def test_transcript(self):
-        """
-        Test "statistics dump" and the transcript information.
-        """
-        self.build()
-        exe = self.getBuildArtifact("a.out")
-        target = self.createTestTarget(file_path=exe)
-        self.runCmd("settings set target.save-transcript true")
-        self.runCmd("version")
 
-        # Verify the output of a first "statistics dump"
-        debug_stats = self.get_stats()
-        self.assertIn("transcript", debug_stats)
-        transcript = debug_stats["transcript"]
-        self.assertEqual(len(transcript), 2)
-        self.assertEqual(transcript[0]["command"], "version")
-        self.assertEqual(transcript[1]["command"], "statistics dump")
-        self.assertEqual(transcript[1]["output"], "")
-
-        # Verify the output of a second "statistics dump"
-        debug_stats = self.get_stats()
-        self.assertIn("transcript", debug_stats)
-        transcript = debug_stats["transcript"]
-        self.assertEqual(len(transcript), 3)
-        self.assertEqual(transcript[0]["command"], "version")
-        self.assertEqual(transcript[1]["command"], "statistics dump")
-        self.assertNotEqual(transcript[1]["output"], "")
-        self.assertEqual(transcript[2]["command"], "statistics dump")
-        self.assertEqual(transcript[2]["output"], "")
+def test_transcript(self):
+    """
+    Test "statistics dump" and the transcript information.
+    """
+    self.build()
+    exe = self.getBuildArtifact("a.out")
+    target = self.createTestTarget(file_path=exe)
+    self.runCmd("settings set target.save-transcript true")
+    self.runCmd("version")
+
+    # Verify the output of a first "statistics dump"
+    debug_stats = self.get_stats()
+    self.assertIn("transcript", debug_stats)
+    transcript = debug_stats["transcript"]
+    self.assertEqual(len(transcript), 2)
+    self.assertEqual(transcript[0]["command"], "version")
+    self.assertEqual(transcript[1]["command"], "statistics dump")
+    self.assertEqual(transcript[1]["output"], "")
+
+    # Verify the output of a second "statistics dump"
+    debug_stats = self.get_stats()
+    self.assertIn("transcript", debug_stats)
+    transcript = debug_stats["transcript"]
+    self.assertEqual(len(transcript), 3)
+    self.assertEqual(transcript[0]["command"], "version")
+    self.assertEqual(transcript[1]["command"], "statistics dump")
+    self.assertNotEqual(transcript[1]["output"], "")
+    self.assertEqual(transcript[2]["command"], "statistics dump")
+    self.assertEqual(transcript[2]["output"], "")
diff --git a/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py b/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
index 157594b4aefa1..9ac56266a6eba 100644
--- a/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
+++ b/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
@@ -133,17 +133,19 @@ def test_get_transcript(self):
             self.assertIn("resolvedCommand", command)
             self.assertIn("output", command)
             self.assertIn("error", command)
-            self.assertIn("seconds", command)
+            self.assertIn("durationInSeconds", command)
+            self.assertIn("timestampInEpochSeconds", command)
 
         # The following validates individual commands in the transcript.
         #
         # Notes:
         # 1. Some of the asserts rely on the exact output format of the
         #    commands. Hopefully we are not changing them any time soon.
-        # 2. We are removing the "seconds" field from each command, so that
-        #    some of the validations below can be easier / more readable.
+        # 2. We are removing the time-related fields from each command, so
+        #    that some of the validations below can be easier / more readable.
         for command in transcript:
-            del(command["seconds"])
+            del command["durationInSeconds"]
+            del command["timestampInEpochSeconds"]
 
         # (lldb) version
         self.assertEqual(transcript[0]["command"], "version")
@@ -152,17 +154,22 @@ def test_get_transcript(self):
         self.assertEqual(transcript[0]["error"], "")
 
         # (lldb) an-unknown-command
-        self.assertEqual(transcript[1],
+        self.assertEqual(
+            transcript[1],
             {
                 "command": "an-unknown-command",
                 "resolvedCommand": "an-unknown-command",
                 "output": "",
                 "error": "error: 'an-unknown-command' is not a valid command.\n",
-            })
+            },
+        )
 
         # (lldb) br s -f main.c -l <line>
         self.assertEqual(transcript[2]["command"], "br s -f main.c -l %d" % self.line)
-        self.assertEqual(transcript[2]["resolvedCommand"], "breakpoint set -f main.c -l %d" % self.line)
+        self.assertEqual(
+            transcript[2]["resolvedCommand"],
+            "breakpoint set -f main.c -l %d" % self.line,
+        )
         # Breakpoint 1: where = a.out`main + 29 at main.c:5:3, address = 0x0000000100000f7d
         self.assertIn("Breakpoint 1: where = a.out`main ", transcript[2]["output"])
         self.assertEqual(transcript[2]["error"], "")
@@ -176,13 +183,15 @@ def test_get_transcript(self):
         self.assertEqual(transcript[3]["error"], "")
 
         # (lldb) p a
-        self.assertEqual(transcript[4],
+        self.assertEqual(
+            transcript[4],
             {
                 "command": "p a",
                 "resolvedCommand": "dwim-print -- a",
                 "output": "(int) 123\n",
                 "error": "",
-            })
+            },
+        )
 
         # (lldb) statistics dump
         self.assertEqual(transcript[5]["command"], "statistics dump")
@@ -202,7 +211,10 @@ def test_save_transcript_setting_default(self):
         res = lldb.SBCommandReturnObject()
 
         # The setting's default value should be "false"
-        self.runCmd("settings show interpreter.save-transcript", "interpreter.save-transcript (boolean) = false\n")
+        self.runCmd(
+            "settings show interpreter.save-transcript",
+            "interpreter.save-transcript (boolean) = false\n",
+        )
 
     def test_save_transcript_setting_off(self):
         ci = self.buildAndCreateTarget()
@@ -247,17 +259,37 @@ def test_get_transcript_returns_copy(self):
         structured_data_1 = ci.GetTranscript()
         self.assertTrue(structured_data_1.IsValid())
         self.assertEqual(structured_data_1.GetSize(), 1)
-        self.assertEqual(structured_data_1.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")
+        self.assertEqual(
+            structured_data_1.GetItemAtIndex(0)
+            .GetValueForKey("command")
+            .GetStringValue(100),
+            "version",
+        )
 
         # Run some more commands and get the transcript as structured data again
         self.runCmd("help")
         structured_data_2 = ci.GetTranscript()
         self.assertTrue(structured_data_2.IsValid())
         self.assertEqual(structured_data_2.GetSize(), 2)
-        self.assertEqual(structured_data_2.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")
-        self.assertEqual(structured_data_2.GetItemAtIndex(1).GetValueForKey("command").GetStringValue(100), "help")
+        self.assertEqual(
+            structured_data_2.GetItemAtIndex(0)
+            .GetValueForKey("command")
+            .GetStringValue(100),
+            "version",
+        )
+        self.assertEqual(
+            structured_data_2.GetItemAtIndex(1)
+            .GetValueForKey("command")
+            .GetStringValue(100),
+            "help",
+        )
 
         # Now, the first structured data should remain unchanged
         self.assertTrue(structured_data_1.IsValid())
         self.assertEqual(structured_data_1.GetSize(), 1)
-        self.assertEqual(structured_data_1.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")
+        self.assertEqual(
+            structured_data_1.GetItemAtIndex(0)
+            .GetValueForKey("command")
+            .GetStringValue(100),
+            "version",
+        )

>From d2389e3d722a71313186eb99b91043c3d9b672cf Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Thu, 23 May 2024 23:05:07 -0400
Subject: [PATCH 05/12] Fix test and comment

---
 lldb/include/lldb/API/SBCommandInterpreter.h  |  4 +-
 .../lldb/Interpreter/CommandInterpreter.h     |  6 +-
 .../commands/statistics/basic/TestStats.py    | 64 ++++++++++---------
 3 files changed, 42 insertions(+), 32 deletions(-)

diff --git a/lldb/include/lldb/API/SBCommandInterpreter.h b/lldb/include/lldb/API/SBCommandInterpreter.h
index 8eb4a71cb7f88..a2d28b2d17968 100644
--- a/lldb/include/lldb/API/SBCommandInterpreter.h
+++ b/lldb/include/lldb/API/SBCommandInterpreter.h
@@ -324,7 +324,9 @@ class SBCommandInterpreter {
   /// - "resolvedCommand" (string): The expanded command that was executed.
   /// - "output" (string): The output of the command. Empty ("") if no output.
   /// - "error" (string): The error of the command. Empty ("") if no error.
-  /// - "seconds" (float): The time it took to execute the command.
+  /// - "durationInSeconds" (float): The time it took to execute the command.
+  /// - "timestampInEpochSeconds" (int): The timestamp when the command is
+  ///   executed.
   SBStructuredData GetTranscript();
 
 protected:
diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h
index 6e21af7180f79..e59948a333bfd 100644
--- a/lldb/include/lldb/Interpreter/CommandInterpreter.h
+++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h
@@ -580,7 +580,7 @@ class CommandInterpreter : public Broadcaster,
   void SetEchoCommentCommands(bool enable);
 
   bool GetRepeatPreviousCommand() const;
-  
+
   bool GetRequireCommandOverwrite() const;
 
   const CommandObject::CommandMap &GetUserCommands() const {
@@ -780,7 +780,9 @@ class CommandInterpreter : public Broadcaster,
   /// - "resolvedCommand" (string): The expanded command that was executed.
   /// - "output" (string): The output of the command. Empty ("") if no output.
   /// - "error" (string): The error of the command. Empty ("") if no error.
-  /// - "seconds" (float): The time it took to execute the command.
+  /// - "durationInSeconds" (float): The time it took to execute the command.
+  /// - "timestampInEpochSeconds" (int): The timestamp when the command is
+  ///   executed.
   ///
   /// Turn on settings `interpreter.save-transcript` for LLDB to populate
   /// this list. Otherwise this list is empty.
diff --git a/lldb/test/API/commands/statistics/basic/TestStats.py b/lldb/test/API/commands/statistics/basic/TestStats.py
index abf610d78dd9f..4b0d3256f0c7e 100644
--- a/lldb/test/API/commands/statistics/basic/TestStats.py
+++ b/lldb/test/API/commands/statistics/basic/TestStats.py
@@ -624,33 +624,39 @@ def test_had_frame_variable_errors(self):
         # modules with debugInfoHadVariableErrors is greater than zero
         self.assertGreater(stats["totalModuleCountWithVariableErrors"], 0)
 
+    def test_transcript(self):
+        """
+        Test "statistics dump" and the transcript information.
+        """
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+        target = self.createTestTarget(file_path=exe)
+        self.runCmd("settings set interpreter.save-transcript true")
+        self.runCmd("version")
 
-def test_transcript(self):
-    """
-    Test "statistics dump" and the transcript information.
-    """
-    self.build()
-    exe = self.getBuildArtifact("a.out")
-    target = self.createTestTarget(file_path=exe)
-    self.runCmd("settings set target.save-transcript true")
-    self.runCmd("version")
-
-    # Verify the output of a first "statistics dump"
-    debug_stats = self.get_stats()
-    self.assertIn("transcript", debug_stats)
-    transcript = debug_stats["transcript"]
-    self.assertEqual(len(transcript), 2)
-    self.assertEqual(transcript[0]["command"], "version")
-    self.assertEqual(transcript[1]["command"], "statistics dump")
-    self.assertEqual(transcript[1]["output"], "")
-
-    # Verify the output of a second "statistics dump"
-    debug_stats = self.get_stats()
-    self.assertIn("transcript", debug_stats)
-    transcript = debug_stats["transcript"]
-    self.assertEqual(len(transcript), 3)
-    self.assertEqual(transcript[0]["command"], "version")
-    self.assertEqual(transcript[1]["command"], "statistics dump")
-    self.assertNotEqual(transcript[1]["output"], "")
-    self.assertEqual(transcript[2]["command"], "statistics dump")
-    self.assertEqual(transcript[2]["output"], "")
+        # Verify the output of a first "statistics dump"
+        debug_stats = self.get_stats()
+        self.assertIn("transcript", debug_stats)
+        transcript = debug_stats["transcript"]
+        print("DEBUG1")
+        print(transcript)
+        self.assertEqual(len(transcript), 2)
+        self.assertEqual(transcript[0]["resolvedCommand"], "version")
+        self.assertEqual(transcript[1]["resolvedCommand"], "statistics dump")
+        # The first "statistics dump" in the transcript should have no output
+        self.assertNotIn("output", transcript[1])
+
+        # Verify the output of a second "statistics dump"
+        debug_stats = self.get_stats()
+        self.assertIn("transcript", debug_stats)
+        transcript = debug_stats["transcript"]
+        print("DEBUG2")
+        print(transcript)
+        self.assertEqual(len(transcript), 3)
+        self.assertEqual(transcript[0]["resolvedCommand"], "version")
+        self.assertEqual(transcript[1]["resolvedCommand"], "statistics dump")
+        # The first "statistics dump" in the transcript should have output now
+        self.assertIn("output", transcript[1])
+        self.assertEqual(transcript[2]["resolvedCommand"], "statistics dump")
+        # The second "statistics dump" in the transcript should have no output
+        self.assertNotIn("output", transcript[2])

>From a2ea0ef10eeba6f1c2be187b85a5f7bc2dc41fa6 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Thu, 23 May 2024 23:12:24 -0400
Subject: [PATCH 06/12] Fix more format

---
 .../lldb/Interpreter/CommandInterpreter.h     |  2 +-
 .../interpreter/TestCommandInterpreterAPI.py  | 45 ++++---------------
 2 files changed, 10 insertions(+), 37 deletions(-)

diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h
index e59948a333bfd..3b9f920f12b8d 100644
--- a/lldb/include/lldb/Interpreter/CommandInterpreter.h
+++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h
@@ -580,7 +580,7 @@ class CommandInterpreter : public Broadcaster,
   void SetEchoCommentCommands(bool enable);
 
   bool GetRepeatPreviousCommand() const;
-
+  
   bool GetRequireCommandOverwrite() const;
 
   const CommandObject::CommandMap &GetUserCommands() const {
diff --git a/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py b/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
index 9ac56266a6eba..17793e7ab00a9 100644
--- a/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
+++ b/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
@@ -154,15 +154,13 @@ def test_get_transcript(self):
         self.assertEqual(transcript[0]["error"], "")
 
         # (lldb) an-unknown-command
-        self.assertEqual(
-            transcript[1],
+        self.assertEqual(transcript[1],
             {
                 "command": "an-unknown-command",
                 "resolvedCommand": "an-unknown-command",
                 "output": "",
                 "error": "error: 'an-unknown-command' is not a valid command.\n",
-            },
-        )
+            })
 
         # (lldb) br s -f main.c -l <line>
         self.assertEqual(transcript[2]["command"], "br s -f main.c -l %d" % self.line)
@@ -183,15 +181,13 @@ def test_get_transcript(self):
         self.assertEqual(transcript[3]["error"], "")
 
         # (lldb) p a
-        self.assertEqual(
-            transcript[4],
+        self.assertEqual(transcript[4],
             {
                 "command": "p a",
                 "resolvedCommand": "dwim-print -- a",
                 "output": "(int) 123\n",
                 "error": "",
-            },
-        )
+            })
 
         # (lldb) statistics dump
         self.assertEqual(transcript[5]["command"], "statistics dump")
@@ -211,10 +207,7 @@ def test_save_transcript_setting_default(self):
         res = lldb.SBCommandReturnObject()
 
         # The setting's default value should be "false"
-        self.runCmd(
-            "settings show interpreter.save-transcript",
-            "interpreter.save-transcript (boolean) = false\n",
-        )
+        self.runCmd("settings show interpreter.save-transcript", "interpreter.save-transcript (boolean) = false\n")
 
     def test_save_transcript_setting_off(self):
         ci = self.buildAndCreateTarget()
@@ -259,37 +252,17 @@ def test_get_transcript_returns_copy(self):
         structured_data_1 = ci.GetTranscript()
         self.assertTrue(structured_data_1.IsValid())
         self.assertEqual(structured_data_1.GetSize(), 1)
-        self.assertEqual(
-            structured_data_1.GetItemAtIndex(0)
-            .GetValueForKey("command")
-            .GetStringValue(100),
-            "version",
-        )
+        self.assertEqual(structured_data_1.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")
 
         # Run some more commands and get the transcript as structured data again
         self.runCmd("help")
         structured_data_2 = ci.GetTranscript()
         self.assertTrue(structured_data_2.IsValid())
         self.assertEqual(structured_data_2.GetSize(), 2)
-        self.assertEqual(
-            structured_data_2.GetItemAtIndex(0)
-            .GetValueForKey("command")
-            .GetStringValue(100),
-            "version",
-        )
-        self.assertEqual(
-            structured_data_2.GetItemAtIndex(1)
-            .GetValueForKey("command")
-            .GetStringValue(100),
-            "help",
-        )
+        self.assertEqual(structured_data_2.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")
+        self.assertEqual(structured_data_2.GetItemAtIndex(1).GetValueForKey("command").GetStringValue(100), "help")
 
         # Now, the first structured data should remain unchanged
         self.assertTrue(structured_data_1.IsValid())
         self.assertEqual(structured_data_1.GetSize(), 1)
-        self.assertEqual(
-            structured_data_1.GetItemAtIndex(0)
-            .GetValueForKey("command")
-            .GetStringValue(100),
-            "version",
-        )
+        self.assertEqual(structured_data_1.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")

>From 83df90b8ee2375bb78acf314b92b8de349fc9699 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Thu, 23 May 2024 23:22:58 -0400
Subject: [PATCH 07/12] Remove debug prints

---
 lldb/test/API/commands/statistics/basic/TestStats.py | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/lldb/test/API/commands/statistics/basic/TestStats.py b/lldb/test/API/commands/statistics/basic/TestStats.py
index 4b0d3256f0c7e..844aa7d0e9a68 100644
--- a/lldb/test/API/commands/statistics/basic/TestStats.py
+++ b/lldb/test/API/commands/statistics/basic/TestStats.py
@@ -638,8 +638,6 @@ def test_transcript(self):
         debug_stats = self.get_stats()
         self.assertIn("transcript", debug_stats)
         transcript = debug_stats["transcript"]
-        print("DEBUG1")
-        print(transcript)
         self.assertEqual(len(transcript), 2)
         self.assertEqual(transcript[0]["resolvedCommand"], "version")
         self.assertEqual(transcript[1]["resolvedCommand"], "statistics dump")
@@ -650,8 +648,6 @@ def test_transcript(self):
         debug_stats = self.get_stats()
         self.assertIn("transcript", debug_stats)
         transcript = debug_stats["transcript"]
-        print("DEBUG2")
-        print(transcript)
         self.assertEqual(len(transcript), 3)
         self.assertEqual(transcript[0]["resolvedCommand"], "version")
         self.assertEqual(transcript[1]["resolvedCommand"], "statistics dump")

>From 685771c5fc2da0e581294cb014f60ab426e2ca61 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Thu, 30 May 2024 10:36:58 -0700
Subject: [PATCH 08/12] Split "resolvedCommand" into "commandName" and
 "commandArguments"

---
 lldb/include/lldb/API/SBCommandInterpreter.h  |  6 ++++-
 .../lldb/Interpreter/CommandInterpreter.h     |  3 ++-
 .../source/Interpreter/CommandInterpreter.cpp | 14 ++++++-----
 .../interpreter/TestCommandInterpreterAPI.py  | 23 +++++++++++--------
 4 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/lldb/include/lldb/API/SBCommandInterpreter.h b/lldb/include/lldb/API/SBCommandInterpreter.h
index a2d28b2d17968..639309aa32bfc 100644
--- a/lldb/include/lldb/API/SBCommandInterpreter.h
+++ b/lldb/include/lldb/API/SBCommandInterpreter.h
@@ -321,12 +321,16 @@ class SBCommandInterpreter {
   /// Returns a list of handled commands, output and error. Each element in
   /// the list is a dictionary with the following keys/values:
   /// - "command" (string): The command that was given by the user.
-  /// - "resolvedCommand" (string): The expanded command that was executed.
+  /// - "commandName" (string): The name of the executed command.
+  /// - "commandArguments" (string): The arguments of the executed command.
   /// - "output" (string): The output of the command. Empty ("") if no output.
   /// - "error" (string): The error of the command. Empty ("") if no error.
   /// - "durationInSeconds" (float): The time it took to execute the command.
   /// - "timestampInEpochSeconds" (int): The timestamp when the command is
   ///   executed.
+  ///
+  /// Turn on settings `interpreter.save-transcript` for LLDB to populate
+  /// this list. Otherwise this list is empty.
   SBStructuredData GetTranscript();
 
 protected:
diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h
index 3b9f920f12b8d..8863523b2e31f 100644
--- a/lldb/include/lldb/Interpreter/CommandInterpreter.h
+++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h
@@ -777,7 +777,8 @@ class CommandInterpreter : public Broadcaster,
   /// Contains a list of handled commands and their details. Each element in
   /// the list is a dictionary with the following keys/values:
   /// - "command" (string): The command that was given by the user.
-  /// - "resolvedCommand" (string): The expanded command that was executed.
+  /// - "commandName" (string): The name of the executed command.
+  /// - "commandArguments" (string): The arguments of the executed command.
   /// - "output" (string): The output of the command. Empty ("") if no output.
   /// - "error" (string): The error of the command. Empty ("") if no error.
   /// - "durationInSeconds" (float): The time it took to execute the command.
diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp
index 97d581d21784f..f7a088bfe9551 100644
--- a/lldb/source/Interpreter/CommandInterpreter.cpp
+++ b/lldb/source/Interpreter/CommandInterpreter.cpp
@@ -2017,12 +2017,6 @@ bool CommandInterpreter::HandleCommand(const char *command_line,
               wants_raw_input ? "True" : "False");
   }
 
-  // To test whether or not transcript should be saved, `transcript_item` is
-  // used instead of `GetSaveTrasncript()`. This is because the latter will
-  // fail when the command is "settings set interpreter.save-transcript true".
-  if (transcript_item)
-    transcript_item->AddStringItem("resolvedCommand", command_string);
-
   // Phase 2.
   // Take care of things like setting up the history command & calling the
   // appropriate Execute method on the CommandObject, with the appropriate
@@ -2068,6 +2062,14 @@ bool CommandInterpreter::HandleCommand(const char *command_line,
         log, "HandleCommand, command line after removing command name(s): '%s'",
         remainder.c_str());
 
+    // To test whether or not transcript should be saved, `transcript_item` is
+    // used instead of `GetSaveTrasncript()`. This is because the latter will
+    // fail when the command is "settings set interpreter.save-transcript true".
+    if (transcript_item) {
+      transcript_item->AddStringItem("commandName", cmd_obj->GetCommandName());
+      transcript_item->AddStringItem("commandArguments", remainder);
+    }
+
     ElapsedTime elapsed(execute_time);
     cmd_obj->Execute(remainder.c_str(), result);
   }
diff --git a/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py b/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
index 17793e7ab00a9..b8a8f2b108b19 100644
--- a/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
+++ b/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
@@ -130,7 +130,8 @@ def test_get_transcript(self):
         # All commands should have expected fields.
         for command in transcript:
             self.assertIn("command", command)
-            self.assertIn("resolvedCommand", command)
+            # Unresolved commands don't have "commandName"/"commandArguments".
+            # We will validate these fields below, instead of here.
             self.assertIn("output", command)
             self.assertIn("error", command)
             self.assertIn("durationInSeconds", command)
@@ -149,7 +150,8 @@ def test_get_transcript(self):
 
         # (lldb) version
         self.assertEqual(transcript[0]["command"], "version")
-        self.assertEqual(transcript[0]["resolvedCommand"], "version")
+        self.assertEqual(transcript[0]["commandName"], "version")
+        self.assertEqual(transcript[0]["commandArguments"], "")
         self.assertIn("lldb version", transcript[0]["output"])
         self.assertEqual(transcript[0]["error"], "")
 
@@ -157,24 +159,23 @@ def test_get_transcript(self):
         self.assertEqual(transcript[1],
             {
                 "command": "an-unknown-command",
-                "resolvedCommand": "an-unknown-command",
+                # Unresolved commands don't have "commandName"/"commandArguments"
                 "output": "",
                 "error": "error: 'an-unknown-command' is not a valid command.\n",
             })
 
         # (lldb) br s -f main.c -l <line>
         self.assertEqual(transcript[2]["command"], "br s -f main.c -l %d" % self.line)
-        self.assertEqual(
-            transcript[2]["resolvedCommand"],
-            "breakpoint set -f main.c -l %d" % self.line,
-        )
+        self.assertEqual(transcript[2]["commandName"], "breakpoint set")
+        self.assertEqual(transcript[2]["commandArguments"], "-f main.c -l %d" % self.line)
         # Breakpoint 1: where = a.out`main + 29 at main.c:5:3, address = 0x0000000100000f7d
         self.assertIn("Breakpoint 1: where = a.out`main ", transcript[2]["output"])
         self.assertEqual(transcript[2]["error"], "")
 
         # (lldb) r
         self.assertEqual(transcript[3]["command"], "r")
-        self.assertEqual(transcript[3]["resolvedCommand"], "process launch -X true --")
+        self.assertEqual(transcript[3]["commandName"], "process launch")
+        self.assertEqual(transcript[3]["commandArguments"], "-X true --")
         # Process 25494 launched: '<path>/TestCommandInterpreterAPI.test_structured_transcript/a.out' (x86_64)
         self.assertIn("Process", transcript[3]["output"])
         self.assertIn("launched", transcript[3]["output"])
@@ -184,14 +185,16 @@ def test_get_transcript(self):
         self.assertEqual(transcript[4],
             {
                 "command": "p a",
-                "resolvedCommand": "dwim-print -- a",
+                "commandName": "dwim-print",
+                "commandArguments": "-- a",
                 "output": "(int) 123\n",
                 "error": "",
             })
 
         # (lldb) statistics dump
         self.assertEqual(transcript[5]["command"], "statistics dump")
-        self.assertEqual(transcript[5]["resolvedCommand"], "statistics dump")
+        self.assertEqual(transcript[5]["commandName"], "statistics dump")
+        self.assertEqual(transcript[5]["commandArguments"], "")
         self.assertEqual(transcript[5]["error"], "")
         statistics_dump = json.loads(transcript[5]["output"])
         # Dump result should be valid JSON

>From 2132829a8d9973d6212ba8cee815fe2273ffa3f6 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Thu, 30 May 2024 13:23:22 -0700
Subject: [PATCH 09/12] Add argument "statistics dump --transcript". Default
 off.

---
 lldb/include/lldb/Target/Statistics.h         |  1 +
 lldb/source/Commands/CommandObjectStats.cpp   |  3 ++
 lldb/source/Commands/Options.td               |  2 ++
 lldb/source/Target/Statistics.cpp             |  8 +++--
 .../commands/statistics/basic/TestStats.py    | 30 ++++++++++++++-----
 5 files changed, 33 insertions(+), 11 deletions(-)

diff --git a/lldb/include/lldb/Target/Statistics.h b/lldb/include/lldb/Target/Statistics.h
index c4f17b503a1f9..c04d529290fff 100644
--- a/lldb/include/lldb/Target/Statistics.h
+++ b/lldb/include/lldb/Target/Statistics.h
@@ -133,6 +133,7 @@ struct ConstStringStats {
 struct StatisticsOptions {
   bool summary_only = false;
   bool load_all_debug_info = false;
+  bool include_transcript = false;
 };
 
 /// A class that represents statistics for a since lldb_private::Target.
diff --git a/lldb/source/Commands/CommandObjectStats.cpp b/lldb/source/Commands/CommandObjectStats.cpp
index a92bb5d1165ee..1935b0fdfadfb 100644
--- a/lldb/source/Commands/CommandObjectStats.cpp
+++ b/lldb/source/Commands/CommandObjectStats.cpp
@@ -81,6 +81,9 @@ class CommandObjectStatsDump : public CommandObjectParsed {
       case 'f':
         m_stats_options.load_all_debug_info = true;
         break;
+      case 't':
+        m_stats_options.include_transcript = true;
+        break;
       default:
         llvm_unreachable("Unimplemented option");
       }
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index 62bbfdc117834..08e3f53dd6d57 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1425,4 +1425,6 @@ let Command = "statistics dump" in {
     Desc<"Dump the total possible debug info statistics. "
     "Force loading all the debug information if not yet loaded, and collect "
     "statistics with those.">;
+  def statistics_dump_transcript: Option<"transcript", "t">, Group<1>,
+    Desc<"Include transcript.">;
 }
diff --git a/lldb/source/Target/Statistics.cpp b/lldb/source/Target/Statistics.cpp
index fd31377abd06b..be0848573f812 100644
--- a/lldb/source/Target/Statistics.cpp
+++ b/lldb/source/Target/Statistics.cpp
@@ -226,6 +226,7 @@ llvm::json::Value DebuggerStats::ReportStatistics(
 
   const bool summary_only = options.summary_only;
   const bool load_all_debug_info = options.load_all_debug_info;
+  const bool include_transcript = options.include_transcript;
 
   json::Array json_targets;
   json::Array json_modules;
@@ -363,16 +364,17 @@ llvm::json::Value DebuggerStats::ReportStatistics(
     global_stats.try_emplace("modules", std::move(json_modules));
     global_stats.try_emplace("memory", std::move(json_memory));
     global_stats.try_emplace("commands", std::move(cmd_stats));
+  }
 
+  if (include_transcript) {
     // When transcript is available, add it to the to-be-returned statistics.
     //
     // NOTE:
     // When the statistics is polled by an LLDB command:
     // - The transcript in the returned statistics *will NOT* contain the
-    //   returned statistics itself.
+    //   returned statistics itself (otherwise infinite recursion).
     // - The returned statistics *will* be written to the internal transcript
-    //   buffer as the output of the said LLDB command. It *will* appear in
-    //   the next statistcs or transcript poll.
+    //   buffer. It *will* appear in the next statistcs or transcript poll.
     //
     // For example, let's say the following commands are run in order:
     // - "version"
diff --git a/lldb/test/API/commands/statistics/basic/TestStats.py b/lldb/test/API/commands/statistics/basic/TestStats.py
index 844aa7d0e9a68..40f6b0babff89 100644
--- a/lldb/test/API/commands/statistics/basic/TestStats.py
+++ b/lldb/test/API/commands/statistics/basic/TestStats.py
@@ -624,7 +624,7 @@ def test_had_frame_variable_errors(self):
         # modules with debugInfoHadVariableErrors is greater than zero
         self.assertGreater(stats["totalModuleCountWithVariableErrors"], 0)
 
-    def test_transcript(self):
+    def test_transcript_happy_path(self):
         """
         Test "statistics dump" and the transcript information.
         """
@@ -635,24 +635,38 @@ def test_transcript(self):
         self.runCmd("version")
 
         # Verify the output of a first "statistics dump"
-        debug_stats = self.get_stats()
+        debug_stats = self.get_stats("--transcript")
         self.assertIn("transcript", debug_stats)
         transcript = debug_stats["transcript"]
         self.assertEqual(len(transcript), 2)
-        self.assertEqual(transcript[0]["resolvedCommand"], "version")
-        self.assertEqual(transcript[1]["resolvedCommand"], "statistics dump")
+        self.assertEqual(transcript[0]["commandName"], "version")
+        self.assertEqual(transcript[1]["commandName"], "statistics dump")
         # The first "statistics dump" in the transcript should have no output
         self.assertNotIn("output", transcript[1])
 
         # Verify the output of a second "statistics dump"
-        debug_stats = self.get_stats()
+        debug_stats = self.get_stats("--transcript")
         self.assertIn("transcript", debug_stats)
         transcript = debug_stats["transcript"]
         self.assertEqual(len(transcript), 3)
-        self.assertEqual(transcript[0]["resolvedCommand"], "version")
-        self.assertEqual(transcript[1]["resolvedCommand"], "statistics dump")
+        self.assertEqual(transcript[0]["commandName"], "version")
+        self.assertEqual(transcript[1]["commandName"], "statistics dump")
         # The first "statistics dump" in the transcript should have output now
         self.assertIn("output", transcript[1])
-        self.assertEqual(transcript[2]["resolvedCommand"], "statistics dump")
+        self.assertEqual(transcript[2]["commandName"], "statistics dump")
         # The second "statistics dump" in the transcript should have no output
         self.assertNotIn("output", transcript[2])
+
+    def test_transcript_should_not_exist_when_not_asked_for(self):
+        """
+        Test "statistics dump" and the transcript information.
+        """
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+        target = self.createTestTarget(file_path=exe)
+        self.runCmd("settings set interpreter.save-transcript true")
+        self.runCmd("version")
+
+        # Verify the output of a first "statistics dump"
+        debug_stats = self.get_stats() # Not with "--transcript"
+        self.assertNotIn("transcript", debug_stats)

>From 47a670c1ef44ea4bb42263ff1f682b9511722c7b Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Fri, 31 May 2024 09:32:54 -0700
Subject: [PATCH 10/12] Update description for argument --transcript

---
 lldb/source/Commands/Options.td | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index 08e3f53dd6d57..194902abdce49 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1426,5 +1426,7 @@ let Command = "statistics dump" in {
     "Force loading all the debug information if not yet loaded, and collect "
     "statistics with those.">;
   def statistics_dump_transcript: Option<"transcript", "t">, Group<1>,
-    Desc<"Include transcript.">;
+    Desc<"If the setting interpreter.save-transcript is enabled and this "
+    "option is specified, include a JSON array with all commands the user and/"
+    "or scripts executed during a debug session.">;
 }

>From 2adec1671267645c08d155388e40dec521234766 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Fri, 31 May 2024 11:29:56 -0700
Subject: [PATCH 11/12] Fix python format

---
 lldb/test/API/commands/statistics/basic/TestStats.py          | 2 +-
 .../API/python_api/interpreter/TestCommandInterpreterAPI.py   | 4 +++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/lldb/test/API/commands/statistics/basic/TestStats.py b/lldb/test/API/commands/statistics/basic/TestStats.py
index 40f6b0babff89..ebe6166eb45e5 100644
--- a/lldb/test/API/commands/statistics/basic/TestStats.py
+++ b/lldb/test/API/commands/statistics/basic/TestStats.py
@@ -668,5 +668,5 @@ def test_transcript_should_not_exist_when_not_asked_for(self):
         self.runCmd("version")
 
         # Verify the output of a first "statistics dump"
-        debug_stats = self.get_stats() # Not with "--transcript"
+        debug_stats = self.get_stats()  # Not with "--transcript"
         self.assertNotIn("transcript", debug_stats)
diff --git a/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py b/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
index b8a8f2b108b19..6814ccec16c48 100644
--- a/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
+++ b/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
@@ -167,7 +167,9 @@ def test_get_transcript(self):
         # (lldb) br s -f main.c -l <line>
         self.assertEqual(transcript[2]["command"], "br s -f main.c -l %d" % self.line)
         self.assertEqual(transcript[2]["commandName"], "breakpoint set")
-        self.assertEqual(transcript[2]["commandArguments"], "-f main.c -l %d" % self.line)
+        self.assertEqual(
+            transcript[2]["commandArguments"], "-f main.c -l %d" % self.line
+        )
         # Breakpoint 1: where = a.out`main + 29 at main.c:5:3, address = 0x0000000100000f7d
         self.assertIn("Breakpoint 1: where = a.out`main ", transcript[2]["output"])
         self.assertEqual(transcript[2]["error"], "")

>From ddd361b5d16d1ccdc80b2424024de49311ac7e06 Mon Sep 17 00:00:00 2001
From: Roy Shi <royshi at meta.com>
Date: Fri, 31 May 2024 15:25:38 -0700
Subject: [PATCH 12/12] Fix build break

---
 .../API/python_api/interpreter/TestCommandInterpreterAPI.py    | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py b/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
index 6814ccec16c48..e4b7eee91c46a 100644
--- a/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
+++ b/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
@@ -177,7 +177,8 @@ def test_get_transcript(self):
         # (lldb) r
         self.assertEqual(transcript[3]["command"], "r")
         self.assertEqual(transcript[3]["commandName"], "process launch")
-        self.assertEqual(transcript[3]["commandArguments"], "-X true --")
+        # Not checking `commandArguments`, because it can be different on different platforms/configurations.
+        # On macOS, it's "-X true --". On Linux, it's "-c/bin/bash --".
         # Process 25494 launched: '<path>/TestCommandInterpreterAPI.test_structured_transcript/a.out' (x86_64)
         self.assertIn("Process", transcript[3]["output"])
         self.assertIn("launched", transcript[3]["output"])



More information about the lldb-commits mailing list