https://github.com/royitaqi created https://github.com/llvm/llvm-project/pull/92843
# Changes 1. Add `resolvedCommand` as a new field to (structured) transcript. This field will hold the expanded/executed command (e.g. "breakpoint set ..."). This is not to be confused with the "command" field, which holds the user input (e.g. "br s ..."). 2. When transcript is available, add it to the output of `statistics dump`. 3. A few minor comment and test name changes. # Test ``` bin/llvm-lit -sv ../external/llvm-project/lldb/test/API/commands/statistics/basic/TestStats.py ``` ``` bin/llvm-lit -sv ../external/llvm-project/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py ``` >From 8499f16ad46b3268f35da2bfcbfa02a10aab935a Mon Sep 17 00:00:00 2001 From: Roy Shi <roy...@meta.com> Date: Mon, 20 May 2024 22:30:40 -0400 Subject: [PATCH 1/3] 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 <roy...@meta.com> Date: Mon, 20 May 2024 22:41:57 -0400 Subject: [PATCH 2/3] 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 <roy...@meta.com> Date: Mon, 20 May 2024 22:42:42 -0400 Subject: [PATCH 3/3] 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 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits