https://github.com/jeffreytan81 created https://github.com/llvm/llvm-project/pull/80375
None >From 59e1499ec0afebb533c4952f079278341b957241 Mon Sep 17 00:00:00 2001 From: jeffreytan81 <jeffrey...@fb.com> Date: Thu, 1 Feb 2024 18:07:51 -0800 Subject: [PATCH] Add commands frequency to statistics dump --- lldb/include/lldb/API/SBCommandInterpreter.h | 9 ++++--- lldb/include/lldb/API/SBStructuredData.h | 1 + .../lldb/Interpreter/CommandInterpreter.h | 15 +++++++++-- lldb/source/API/SBCommandInterpreter.cpp | 15 ++++++++++- .../source/Commands/CommandObjectCommands.cpp | 10 ++++--- .../source/Interpreter/CommandInterpreter.cpp | 9 ++++++- lldb/source/Interpreter/CommandObject.cpp | 1 + lldb/source/Target/Statistics.cpp | 4 +++ .../commands/statistics/basic/TestStats.py | 24 +++++++++++++++++ .../stats_api/TestStatisticsAPI.py | 26 +++++++++++++++++++ 10 files changed, 103 insertions(+), 11 deletions(-) diff --git a/lldb/include/lldb/API/SBCommandInterpreter.h b/lldb/include/lldb/API/SBCommandInterpreter.h index b7f5b3bf3396e..b4629a4c2f5a8 100644 --- a/lldb/include/lldb/API/SBCommandInterpreter.h +++ b/lldb/include/lldb/API/SBCommandInterpreter.h @@ -13,6 +13,7 @@ #include "lldb/API/SBDebugger.h" #include "lldb/API/SBDefines.h" +#include "lldb/API/SBStructuredData.h" namespace lldb_private { class CommandPluginInterfaceImplementation; @@ -246,13 +247,13 @@ class SBCommandInterpreter { lldb::SBStringList &matches, lldb::SBStringList &descriptions); - /// Returns whether an interrupt flag was raised either by the SBDebugger - + /// Returns whether an interrupt flag was raised either by the SBDebugger - /// when the function is not running on the RunCommandInterpreter thread, or /// by SBCommandInterpreter::InterruptCommand if it is. If your code is doing - /// interruptible work, check this API periodically, and interrupt if it + /// interruptible work, check this API periodically, and interrupt if it /// returns true. bool WasInterrupted() const; - + /// Interrupts the command currently executing in the RunCommandInterpreter /// thread. /// @@ -315,6 +316,8 @@ class SBCommandInterpreter { /// and aliases. If successful, result->GetOutput has the full expansion. void ResolveCommand(const char *command_line, SBCommandReturnObject &result); + SBStructuredData GetStatistics(); + protected: friend class lldb_private::CommandPluginInterfaceImplementation; diff --git a/lldb/include/lldb/API/SBStructuredData.h b/lldb/include/lldb/API/SBStructuredData.h index 35d321eaa7b89..fc6e1ec95c7b8 100644 --- a/lldb/include/lldb/API/SBStructuredData.h +++ b/lldb/include/lldb/API/SBStructuredData.h @@ -122,6 +122,7 @@ class SBStructuredData { friend class SBTrace; friend class lldb_private::python::SWIGBridge; friend class lldb_private::lua::SWIGBridge; + friend class SBCommandInterpreter; SBStructuredData(const lldb_private::StructuredDataImpl &impl); diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h index 747188a15312f..c46cf0409bab6 100644 --- a/lldb/include/lldb/Interpreter/CommandInterpreter.h +++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h @@ -28,6 +28,7 @@ #include <mutex> #include <optional> #include <stack> +#include <unordered_map> namespace lldb_private { class CommandInterpreter; @@ -240,7 +241,7 @@ class CommandInterpreter : public Broadcaster, eCommandTypesAllThem = 0xFFFF //< all commands }; - // The CommandAlias and CommandInterpreter both have a hand in + // The CommandAlias and CommandInterpreter both have a hand in // substituting for alias commands. They work by writing special tokens // in the template form of the Alias command, and then detecting them when the // command is executed. These are the special tokens: @@ -575,7 +576,7 @@ class CommandInterpreter : public Broadcaster, void SetEchoCommentCommands(bool enable); bool GetRepeatPreviousCommand() const; - + bool GetRequireCommandOverwrite() const; const CommandObject::CommandMap &GetUserCommands() const { @@ -641,6 +642,12 @@ 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().str()]; + } + + llvm::json::Value GetStatistics(); + protected: friend class Debugger; @@ -754,6 +761,10 @@ class CommandInterpreter : public Broadcaster, // If the driver is accepts custom exit codes for the 'quit' command. bool m_allow_exit_code = false; + /// Command usage statistics. + typedef std::unordered_map<std::string, uint64_t> CommandUsageMap; + CommandUsageMap m_command_usages; + StreamString m_transcript_stream; }; diff --git a/lldb/source/API/SBCommandInterpreter.cpp b/lldb/source/API/SBCommandInterpreter.cpp index c3cbb00145ed3..a8906686d1a11 100644 --- a/lldb/source/API/SBCommandInterpreter.cpp +++ b/lldb/source/API/SBCommandInterpreter.cpp @@ -150,7 +150,7 @@ bool SBCommandInterpreter::WasInterrupted() const { bool SBCommandInterpreter::InterruptCommand() { LLDB_INSTRUMENT_VA(this); - + return (IsValid() ? m_opaque_ptr->InterruptCommand() : false); } @@ -557,6 +557,19 @@ bool SBCommandInterpreter::SetCommandOverrideCallback( return false; } +SBStructuredData SBCommandInterpreter::GetStatistics() { + LLDB_INSTRUMENT_VA(this); + + SBStructuredData data; + if (!IsValid()) + return data; + + std::string json_str = + llvm::formatv("{0:2}", m_opaque_ptr->GetStatistics()).str(); + data.m_impl_up->SetObjectSP(StructuredData::ParseJSON(json_str)); + return data; +} + lldb::SBCommand SBCommandInterpreter::AddMultiwordCommand(const char *name, const char *help) { LLDB_INSTRUMENT_VA(this, name, help); diff --git a/lldb/source/Commands/CommandObjectCommands.cpp b/lldb/source/Commands/CommandObjectCommands.cpp index 5b9af4a3e1b88..c81f56b6f91bc 100644 --- a/lldb/source/Commands/CommandObjectCommands.cpp +++ b/lldb/source/Commands/CommandObjectCommands.cpp @@ -1123,6 +1123,8 @@ class CommandObjectPythonFunction : public CommandObjectRaw { CommandReturnObject &result) override { ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter(); + m_interpreter.IncreaseCommandUsage(*this); + Status error; result.SetStatus(eReturnStatusInvalid); @@ -1577,7 +1579,7 @@ class CommandObjectCommandsScriptAdd : public CommandObjectParsed, case eLazyBoolNo: m_overwrite = false; } - + Status path_error; m_container = GetCommandInterpreter().VerifyUserMultiwordCmdPath( command, true, path_error); @@ -1631,7 +1633,7 @@ class CommandObjectCommandsScriptAdd : public CommandObjectParsed, m_interpreter, m_cmd_name, cmd_obj_sp, m_synchronicity, m_completion_type)); } - + // Assume we're going to succeed... result.SetStatus(eReturnStatusSuccessFinishNoResult); if (!m_container) { @@ -1644,7 +1646,7 @@ class CommandObjectCommandsScriptAdd : public CommandObjectParsed, llvm::Error llvm_error = m_container->LoadUserSubcommand(m_cmd_name, new_cmd_sp, m_overwrite); if (llvm_error) - result.AppendErrorWithFormat("cannot add command: %s", + result.AppendErrorWithFormat("cannot add command: %s", llvm::toString(std::move(llvm_error)).c_str()); } } @@ -1792,7 +1794,7 @@ class CommandObjectCommandsScriptDelete : public CommandObjectParsed { /* multiword not okay */ false); if (llvm_error) { result.AppendErrorWithFormat("could not delete command '%s': %s", - leaf_cmd, + leaf_cmd, llvm::toString(std::move(llvm_error)).c_str()); return; } diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index 00651df48b622..86fbd48888a24 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -3055,7 +3055,7 @@ void CommandInterpreter::PrintCommandOutput(IOHandler &io_handler, } std::lock_guard<std::recursive_mutex> guard(io_handler.GetOutputMutex()); - if (had_output && INTERRUPT_REQUESTED(GetDebugger(), + if (had_output && INTERRUPT_REQUESTED(GetDebugger(), "Interrupted dumping command output")) stream->Printf("\n... Interrupted.\n"); stream->Flush(); @@ -3547,3 +3547,10 @@ CommandInterpreter::ResolveCommandImpl(std::string &command_line, return cmd_obj; } + +llvm::json::Value CommandInterpreter::GetStatistics() { + llvm::json::Object stats; + for (const auto& command_usage : m_command_usages) + stats.try_emplace(command_usage.first, command_usage.second); + return stats; +} diff --git a/lldb/source/Interpreter/CommandObject.cpp b/lldb/source/Interpreter/CommandObject.cpp index 1ff9774a0da49..6324c7e701ed5 100644 --- a/lldb/source/Interpreter/CommandObject.cpp +++ b/lldb/source/Interpreter/CommandObject.cpp @@ -748,6 +748,7 @@ void CommandObjectParsed::Execute(const char *args_string, Cleanup(); return; } + m_interpreter.IncreaseCommandUsage(*this); DoExecute(cmd_args, result); } } diff --git a/lldb/source/Target/Statistics.cpp b/lldb/source/Target/Statistics.cpp index c739ac7058cae..7cdee3414d8a3 100644 --- a/lldb/source/Target/Statistics.cpp +++ b/lldb/source/Target/Statistics.cpp @@ -10,6 +10,7 @@ #include "lldb/Core/Debugger.h" #include "lldb/Core/Module.h" +#include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Symbol/SymbolFile.h" #include "lldb/Target/Process.h" #include "lldb/Target/Target.h" @@ -291,10 +292,13 @@ llvm::json::Value DebuggerStats::ReportStatistics(Debugger &debugger, {"strings", const_string_stats.ToJSON()}, }; + json::Value cmd_stats = debugger.GetCommandInterpreter().GetStatistics(); + json::Object global_stats{ {"targets", std::move(json_targets)}, {"modules", std::move(json_modules)}, {"memory", std::move(json_memory)}, + {"commands", std::move(cmd_stats)}, {"totalSymbolTableParseTime", symtab_parse_time}, {"totalSymbolTableIndexTime", symtab_index_time}, {"totalSymbolTablesLoadedFromCache", symtabs_loaded}, diff --git a/lldb/test/API/commands/statistics/basic/TestStats.py b/lldb/test/API/commands/statistics/basic/TestStats.py index 6fb0cc6c3be73..4ea5c6a74b317 100644 --- a/lldb/test/API/commands/statistics/basic/TestStats.py +++ b/lldb/test/API/commands/statistics/basic/TestStats.py @@ -76,6 +76,11 @@ def get_target_stats(self, debug_stats): return debug_stats["targets"][0] return None + def get_command_stats(self, debug_stats): + if "commands" in debug_stats: + return debug_stats["commands"] + return None + def test_expressions_frame_var_counts(self): self.build() lldbutil.run_to_source_breakpoint( @@ -355,6 +360,25 @@ def test_modules(self): self.assertNotEqual(exe_module, None) self.verify_keys(exe_module, 'module dict for "%s"' % (exe), module_keys) + def test_commands(self): + """ + Test "statistics dump" and the command information. + """ + self.build() + exe = self.getBuildArtifact("a.out") + target = self.createTestTarget(file_path=exe) + + interp = self.dbg.GetCommandInterpreter() + result = lldb.SBCommandReturnObject() + interp.HandleCommand('target list', result) + interp.HandleCommand('target list', result) + + debug_stats = self.get_stats() + + command_stats = self.get_command_stats(debug_stats) + self.assertNotEqual(command_stats, None) + self.assertEqual(command_stats["target list"], 2) + 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 33490c9c8f501..b6c972cbcf989 100644 --- a/lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py +++ b/lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py @@ -11,6 +11,9 @@ class TestStatsAPI(TestBase): NO_DEBUG_INFO_TESTCASE = True def test_stats_api(self): + """ + Test SBTarget::GetStatistics() API. + """ self.build() exe = self.getBuildArtifact("a.out") target = self.dbg.CreateTarget(exe) @@ -70,3 +73,26 @@ def test_stats_api(self): True, 'Make sure the "failures" key in in "frameVariable" dictionary"', ) + + def test_command_stats_api(self): + """ + Test GetCommandInterpreter::GetStatistics() API. + """ + self.build() + exe = self.getBuildArtifact("a.out") + lldbutil.run_to_name_breakpoint(self, 'main') + + interp = self.dbg.GetCommandInterpreter() + result = lldb.SBCommandReturnObject() + interp.HandleCommand('bt', result) + + stream = lldb.SBStream() + res = interp.GetStatistics().GetAsJSON(stream) + command_stats = json.loads(stream.GetData()) + + # Verify bt command is correctly parsed into final form. + self.assertEqual(command_stats["thread backtrace"], 1) + # Verify original raw command is not duplicatedly captured. + self.assertNotIn('bt', command_stats) + # Verify bt's regex command is not duplicatedly captured. + self.assertNotIn('_regexp-bt', command_stats) _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits