[Lldb-commits] [lldb] A few updates around "transcript" (PR #92843)
via lldb-commits
lldb-commits at lists.llvm.org
Mon May 20 19:46:02 PDT 2024
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 <royshi at 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 <royshi at 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 <royshi at 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
More information about the lldb-commits
mailing list