Skip to content

Commit 7670609

Browse files
jeffreytan81jeffreytan81
and
jeffreytan81
authored
Add commands frequency to statistics dump (#80375)
Adding command interpreter statistics into "statistics dump" command so that we can track the command usage frequency for telemetry purpose. This is useful to answer questions like what is the most frequently used lldb commands across all our users. --------- Co-authored-by: jeffreytan81 <[email protected]>
1 parent 4b60626 commit 7670609

File tree

10 files changed

+105
-10
lines changed

10 files changed

+105
-10
lines changed

lldb/include/lldb/API/SBCommandInterpreter.h

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include "lldb/API/SBDebugger.h"
1515
#include "lldb/API/SBDefines.h"
16+
#include "lldb/API/SBStructuredData.h"
1617

1718
namespace lldb_private {
1819
class CommandPluginInterfaceImplementation;
@@ -315,6 +316,8 @@ class SBCommandInterpreter {
315316
/// and aliases. If successful, result->GetOutput has the full expansion.
316317
void ResolveCommand(const char *command_line, SBCommandReturnObject &result);
317318

319+
SBStructuredData GetStatistics();
320+
318321
protected:
319322
friend class lldb_private::CommandPluginInterfaceImplementation;
320323

lldb/include/lldb/API/SBStructuredData.h

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ class SBStructuredData {
122122
friend class SBTrace;
123123
friend class lldb_private::python::SWIGBridge;
124124
friend class lldb_private::lua::SWIGBridge;
125+
friend class SBCommandInterpreter;
125126

126127
SBStructuredData(const lldb_private::StructuredDataImpl &impl);
127128

lldb/include/lldb/Interpreter/CommandInterpreter.h

+11
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <mutex>
2929
#include <optional>
3030
#include <stack>
31+
#include <unordered_map>
3132

3233
namespace lldb_private {
3334
class CommandInterpreter;
@@ -641,6 +642,12 @@ class CommandInterpreter : public Broadcaster,
641642
Status PreprocessCommand(std::string &command);
642643
Status PreprocessToken(std::string &token);
643644

645+
void IncreaseCommandUsage(const CommandObject &cmd_obj) {
646+
++m_command_usages[cmd_obj.GetCommandName()];
647+
}
648+
649+
llvm::json::Value GetStatistics();
650+
644651
protected:
645652
friend class Debugger;
646653

@@ -754,6 +761,10 @@ class CommandInterpreter : public Broadcaster,
754761
// If the driver is accepts custom exit codes for the 'quit' command.
755762
bool m_allow_exit_code = false;
756763

764+
/// Command usage statistics.
765+
typedef llvm::StringMap<uint64_t> CommandUsageMap;
766+
CommandUsageMap m_command_usages;
767+
757768
StreamString m_transcript_stream;
758769
};
759770

lldb/source/API/SBCommandInterpreter.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,19 @@ bool SBCommandInterpreter::SetCommandOverrideCallback(
557557
return false;
558558
}
559559

560+
SBStructuredData SBCommandInterpreter::GetStatistics() {
561+
LLDB_INSTRUMENT_VA(this);
562+
563+
SBStructuredData data;
564+
if (!IsValid())
565+
return data;
566+
567+
std::string json_str =
568+
llvm::formatv("{0:2}", m_opaque_ptr->GetStatistics()).str();
569+
data.m_impl_up->SetObjectSP(StructuredData::ParseJSON(json_str));
570+
return data;
571+
}
572+
560573
lldb::SBCommand SBCommandInterpreter::AddMultiwordCommand(const char *name,
561574
const char *help) {
562575
LLDB_INSTRUMENT_VA(this, name, help);

lldb/source/Commands/CommandObjectCommands.cpp

+11-7
Original file line numberDiff line numberDiff line change
@@ -1123,6 +1123,8 @@ class CommandObjectPythonFunction : public CommandObjectRaw {
11231123
CommandReturnObject &result) override {
11241124
ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
11251125

1126+
m_interpreter.IncreaseCommandUsage(*this);
1127+
11261128
Status error;
11271129

11281130
result.SetStatus(eReturnStatusInvalid);
@@ -1644,8 +1646,9 @@ class CommandObjectCommandsScriptAdd : public CommandObjectParsed,
16441646
llvm::Error llvm_error =
16451647
m_container->LoadUserSubcommand(m_cmd_name, new_cmd_sp, m_overwrite);
16461648
if (llvm_error)
1647-
result.AppendErrorWithFormat("cannot add command: %s",
1648-
llvm::toString(std::move(llvm_error)).c_str());
1649+
result.AppendErrorWithFormat(
1650+
"cannot add command: %s",
1651+
llvm::toString(std::move(llvm_error)).c_str());
16491652
}
16501653
}
16511654

@@ -1788,12 +1791,13 @@ class CommandObjectCommandsScriptDelete : public CommandObjectParsed {
17881791
return;
17891792
}
17901793
const char *leaf_cmd = command[num_args - 1].c_str();
1791-
llvm::Error llvm_error = container->RemoveUserSubcommand(leaf_cmd,
1792-
/* multiword not okay */ false);
1794+
llvm::Error llvm_error =
1795+
container->RemoveUserSubcommand(leaf_cmd,
1796+
/* multiword not okay */ false);
17931797
if (llvm_error) {
1794-
result.AppendErrorWithFormat("could not delete command '%s': %s",
1795-
leaf_cmd,
1796-
llvm::toString(std::move(llvm_error)).c_str());
1798+
result.AppendErrorWithFormat(
1799+
"could not delete command '%s': %s", leaf_cmd,
1800+
llvm::toString(std::move(llvm_error)).c_str());
17971801
return;
17981802
}
17991803

lldb/source/Interpreter/CommandInterpreter.cpp

+9-2
Original file line numberDiff line numberDiff line change
@@ -3055,8 +3055,8 @@ void CommandInterpreter::PrintCommandOutput(IOHandler &io_handler,
30553055
}
30563056

30573057
std::lock_guard<std::recursive_mutex> guard(io_handler.GetOutputMutex());
3058-
if (had_output && INTERRUPT_REQUESTED(GetDebugger(),
3059-
"Interrupted dumping command output"))
3058+
if (had_output &&
3059+
INTERRUPT_REQUESTED(GetDebugger(), "Interrupted dumping command output"))
30603060
stream->Printf("\n... Interrupted.\n");
30613061
stream->Flush();
30623062
}
@@ -3547,3 +3547,10 @@ CommandInterpreter::ResolveCommandImpl(std::string &command_line,
35473547

35483548
return cmd_obj;
35493549
}
3550+
3551+
llvm::json::Value CommandInterpreter::GetStatistics() {
3552+
llvm::json::Object stats;
3553+
for (const auto &command_usage : m_command_usages)
3554+
stats.try_emplace(command_usage.getKey(), command_usage.getValue());
3555+
return stats;
3556+
}

lldb/source/Interpreter/CommandObject.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,7 @@ void CommandObjectParsed::Execute(const char *args_string,
748748
Cleanup();
749749
return;
750750
}
751+
m_interpreter.IncreaseCommandUsage(*this);
751752
DoExecute(cmd_args, result);
752753
}
753754
}

lldb/source/Target/Statistics.cpp

+6-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include "lldb/Core/Debugger.h"
1212
#include "lldb/Core/Module.h"
13+
#include "lldb/Interpreter/CommandInterpreter.h"
1314
#include "lldb/Symbol/SymbolFile.h"
1415
#include "lldb/Target/Process.h"
1516
#include "lldb/Target/Target.h"
@@ -291,10 +292,13 @@ llvm::json::Value DebuggerStats::ReportStatistics(Debugger &debugger,
291292
{"strings", const_string_stats.ToJSON()},
292293
};
293294

295+
json::Value cmd_stats = debugger.GetCommandInterpreter().GetStatistics();
296+
294297
json::Object global_stats{
295298
{"targets", std::move(json_targets)},
296299
{"modules", std::move(json_modules)},
297300
{"memory", std::move(json_memory)},
301+
{"commands", std::move(cmd_stats)},
298302
{"totalSymbolTableParseTime", symtab_parse_time},
299303
{"totalSymbolTableIndexTime", symtab_index_time},
300304
{"totalSymbolTablesLoadedFromCache", symtabs_loaded},
@@ -307,7 +311,8 @@ llvm::json::Value DebuggerStats::ReportStatistics(Debugger &debugger,
307311
{"totalModuleCount", num_modules},
308312
{"totalModuleCountHasDebugInfo", num_modules_has_debug_info},
309313
{"totalModuleCountWithVariableErrors", num_modules_with_variable_errors},
310-
{"totalModuleCountWithIncompleteTypes", num_modules_with_incomplete_types},
314+
{"totalModuleCountWithIncompleteTypes",
315+
num_modules_with_incomplete_types},
311316
{"totalDebugInfoEnabled", num_debug_info_enabled_modules},
312317
{"totalSymbolTableStripped", num_stripped_modules},
313318
};

lldb/test/API/commands/statistics/basic/TestStats.py

+24
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ def get_target_stats(self, debug_stats):
7676
return debug_stats["targets"][0]
7777
return None
7878

79+
def get_command_stats(self, debug_stats):
80+
if "commands" in debug_stats:
81+
return debug_stats["commands"]
82+
return None
83+
7984
def test_expressions_frame_var_counts(self):
8085
self.build()
8186
lldbutil.run_to_source_breakpoint(
@@ -355,6 +360,25 @@ def test_modules(self):
355360
self.assertNotEqual(exe_module, None)
356361
self.verify_keys(exe_module, 'module dict for "%s"' % (exe), module_keys)
357362

363+
def test_commands(self):
364+
"""
365+
Test "statistics dump" and the command information.
366+
"""
367+
self.build()
368+
exe = self.getBuildArtifact("a.out")
369+
target = self.createTestTarget(file_path=exe)
370+
371+
interp = self.dbg.GetCommandInterpreter()
372+
result = lldb.SBCommandReturnObject()
373+
interp.HandleCommand("target list", result)
374+
interp.HandleCommand("target list", result)
375+
376+
debug_stats = self.get_stats()
377+
378+
command_stats = self.get_command_stats(debug_stats)
379+
self.assertNotEqual(command_stats, None)
380+
self.assertEqual(command_stats["target list"], 2)
381+
358382
def test_breakpoints(self):
359383
"""Test "statistics dump"
360384

lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py

+26
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ class TestStatsAPI(TestBase):
1111
NO_DEBUG_INFO_TESTCASE = True
1212

1313
def test_stats_api(self):
14+
"""
15+
Test SBTarget::GetStatistics() API.
16+
"""
1417
self.build()
1518
exe = self.getBuildArtifact("a.out")
1619
target = self.dbg.CreateTarget(exe)
@@ -70,3 +73,26 @@ def test_stats_api(self):
7073
True,
7174
'Make sure the "failures" key in in "frameVariable" dictionary"',
7275
)
76+
77+
def test_command_stats_api(self):
78+
"""
79+
Test GetCommandInterpreter::GetStatistics() API.
80+
"""
81+
self.build()
82+
exe = self.getBuildArtifact("a.out")
83+
lldbutil.run_to_name_breakpoint(self, "main")
84+
85+
interp = self.dbg.GetCommandInterpreter()
86+
result = lldb.SBCommandReturnObject()
87+
interp.HandleCommand("bt", result)
88+
89+
stream = lldb.SBStream()
90+
res = interp.GetStatistics().GetAsJSON(stream)
91+
command_stats = json.loads(stream.GetData())
92+
93+
# Verify bt command is correctly parsed into final form.
94+
self.assertEqual(command_stats["thread backtrace"], 1)
95+
# Verify original raw command is not duplicatedly captured.
96+
self.assertNotIn("bt", command_stats)
97+
# Verify bt's regex command is not duplicatedly captured.
98+
self.assertNotIn("_regexp-bt", command_stats)

0 commit comments

Comments
 (0)