Author: Vy Nguyen Date: 2025-03-07T05:59:05-05:00 New Revision: de9cee1c97882dc69a20ac688d20a84f831b62c6
URL: https://github.com/llvm/llvm-project/commit/de9cee1c97882dc69a20ac688d20a84f831b62c6 DIFF: https://github.com/llvm/llvm-project/commit/de9cee1c97882dc69a20ac688d20a84f831b62c6.diff LOG: [LLDB][Telemetry]Define telemetry::CommandInfo (#129354) and collect telemetry about a command's execution. *NOTE: Please consider this PR a DRAFT ( Waiting on PR/127696 to be submitted. ) --------- Co-authored-by: Jonas Devlieghere <jo...@devlieghere.com> Added: Modified: lldb/include/lldb/Core/Telemetry.h lldb/source/Core/Telemetry.cpp lldb/source/Interpreter/CommandInterpreter.cpp lldb/unittests/Core/TelemetryTest.cpp Removed: ################################################################################ diff --git a/lldb/include/lldb/Core/Telemetry.h b/lldb/include/lldb/Core/Telemetry.h index 7d8716f1659b5..56259e4a673bf 100644 --- a/lldb/include/lldb/Core/Telemetry.h +++ b/lldb/include/lldb/Core/Telemetry.h @@ -13,12 +13,14 @@ #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/StructuredData.h" +#include "lldb/Utility/UUID.h" #include "lldb/lldb-forward.h" #include "llvm/ADT/FunctionExtras.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/JSON.h" #include "llvm/Telemetry/Telemetry.h" +#include <atomic> #include <chrono> #include <ctime> #include <memory> @@ -28,6 +30,17 @@ namespace lldb_private { namespace telemetry { +struct LLDBConfig : public ::llvm::telemetry::Config { + // If true, we will collect full details about a debug command (eg., args and + // original command). Note: This may contain PII, hence can only be enabled by + // the vendor while creating the Manager. + const bool detailed_command_telemetry; + + explicit LLDBConfig(bool enable_telemetry, bool detailed_command_telemetry) + : ::llvm::telemetry::Config(enable_telemetry), + detailed_command_telemetry(detailed_command_telemetry) {} +}; + // We expect each (direct) subclass of LLDBTelemetryInfo to // have an LLDBEntryKind in the form 0b11xxxxxxxx // Specifically: @@ -37,6 +50,7 @@ namespace telemetry { // must have their LLDBEntryKind in the similar form (ie., share common prefix) struct LLDBEntryKind : public ::llvm::telemetry::EntryKind { static const llvm::telemetry::KindType BaseInfo = 0b11000000; + static const llvm::telemetry::KindType CommandInfo = 0b11010000; static const llvm::telemetry::KindType DebuggerInfo = 0b11000100; }; @@ -66,6 +80,52 @@ struct LLDBBaseTelemetryInfo : public llvm::telemetry::TelemetryInfo { void serialize(llvm::telemetry::Serializer &serializer) const override; }; +struct CommandInfo : public LLDBBaseTelemetryInfo { + /// If the command is/can be associated with a target entry this field + /// contains that target's UUID. <EMPTY> otherwise. + UUID target_uuid; + /// A unique ID for a command so the manager can match the start entry with + /// its end entry. These values only need to be unique within the same + /// session. Necessary because we'd send off an entry right before a command's + /// execution and another right after. This is to avoid losing telemetry if + /// the command does not execute successfully. + uint64_t command_id; + /// The command name(eg., "breakpoint set") + std::string command_name; + /// These two fields are not collected by default due to PII risks. + /// Vendor may allow them by setting the + /// LLDBConfig::detailed_command_telemetry. + /// @{ + std::optional<std::string> original_command; + std::optional<std::string> args; + /// @} + /// Return status of a command and any error description in case of error. + std::optional<lldb::ReturnStatus> ret_status; + std::optional<std::string> error_data; + + CommandInfo() = default; + + llvm::telemetry::KindType getKind() const override { + return LLDBEntryKind::CommandInfo; + } + + static bool classof(const llvm::telemetry::TelemetryInfo *T) { + return (T->getKind() & LLDBEntryKind::CommandInfo) == + LLDBEntryKind::CommandInfo; + } + + void serialize(llvm::telemetry::Serializer &serializer) const override; + + static uint64_t GetNextId(); + +private: + // We assign each command (in the same session) a unique id so that their + // "start" and "end" entries can be matched up. + // These values don't need to be unique across runs (because they are + // secondary-key), hence a simple counter is sufficent. + static std::atomic<uint64_t> g_command_id_seed; +}; + struct DebuggerInfo : public LLDBBaseTelemetryInfo { std::string lldb_version; @@ -93,24 +153,21 @@ class TelemetryManager : public llvm::telemetry::Manager { public: llvm::Error preDispatch(llvm::telemetry::TelemetryInfo *entry) override; - const llvm::telemetry::Config *GetConfig(); + const LLDBConfig *GetConfig() { return m_config.get(); } virtual llvm::StringRef GetInstanceName() const = 0; static TelemetryManager *GetInstance(); - static TelemetryManager *GetInstanceIfEnabled(); - protected: - TelemetryManager(std::unique_ptr<llvm::telemetry::Config> config); + TelemetryManager(std::unique_ptr<LLDBConfig> config); static void SetInstance(std::unique_ptr<TelemetryManager> manger); private: - std::unique_ptr<llvm::telemetry::Config> m_config; + std::unique_ptr<LLDBConfig> m_config; // Each instance of a TelemetryManager is assigned a unique ID. const std::string m_id; - static std::unique_ptr<TelemetryManager> g_instance; }; @@ -118,39 +175,54 @@ class TelemetryManager : public llvm::telemetry::Manager { template <typename Info> struct ScopedDispatcher { // The debugger pointer is optional because we may not have a debugger yet. // In that case, caller must set the debugger later. - ScopedDispatcher(llvm::unique_function<void(Info *info)> callback, + ScopedDispatcher(Debugger *debugger = nullptr) { + // Start the timer. + m_start_time = std::chrono::steady_clock::now(); + this->debugger = debugger; + } + ScopedDispatcher(llvm::unique_function<void(Info *info)> final_callback, Debugger *debugger = nullptr) - : m_callback(std::move(callback)) { + : m_final_callback(std::move(final_callback)) { // Start the timer. m_start_time = std::chrono::steady_clock::now(); - m_info.debugger = debugger; + this->debugger = debugger; } - void SetDebugger(Debugger *debugger) { m_info.debugger = debugger; } + void SetDebugger(Debugger *debugger) { this->debugger = debugger; } - ~ScopedDispatcher() { - // If Telemetry is disabled (either at buildtime or runtime), - // then don't do anything. - TelemetryManager *manager = TelemetryManager::GetInstanceIfEnabled(); - if (!manager) - return; + void DispatchOnExit(llvm::unique_function<void(Info *info)> final_callback) { + // We probably should not be overriding previously set cb. + assert(!m_final_callback); + m_final_callback = std::move(final_callback); + } - m_info.start_time = m_start_time; - // Populate common fields that we can only set now. - m_info.end_time = std::chrono::steady_clock::now(); - // The callback will set the remaining fields. - m_callback(&m_info); + void DispatchNow(llvm::unique_function<void(Info *info)> populate_fields_cb) { + TelemetryManager *manager = TelemetryManager::GetInstance(); + if (!manager->GetConfig()->EnableTelemetry) + return; + Info info; + // Populate the common fields we know about. + info.start_time = m_start_time; + info.end_time = std::chrono::steady_clock::now(); + info.debugger = debugger; + // The callback will set the rest. + populate_fields_cb(&info); // And then we dispatch. - if (llvm::Error er = manager->dispatch(&m_info)) { + if (llvm::Error er = manager->dispatch(&info)) { LLDB_LOG_ERROR(GetLog(LLDBLog::Object), std::move(er), - "Failed to dispatch entry of type: {0}", m_info.getKind()); + "Failed to dispatch entry of type: {0}", info.getKind()); } } + ~ScopedDispatcher() { + if (m_final_callback) + DispatchNow(std::move(m_final_callback)); + } + private: SteadyTimePoint m_start_time; - llvm::unique_function<void(Info *info)> m_callback; - Info m_info; + llvm::unique_function<void(Info *info)> m_final_callback; + Debugger *debugger; }; } // namespace telemetry diff --git a/lldb/source/Core/Telemetry.cpp b/lldb/source/Core/Telemetry.cpp index 367e94d6566de..be929a644706e 100644 --- a/lldb/source/Core/Telemetry.cpp +++ b/lldb/source/Core/Telemetry.cpp @@ -60,6 +60,22 @@ void LLDBBaseTelemetryInfo::serialize(Serializer &serializer) const { serializer.write("end_time", ToNanosec(end_time.value())); } +void CommandInfo::serialize(Serializer &serializer) const { + LLDBBaseTelemetryInfo::serialize(serializer); + + serializer.write("target_uuid", target_uuid.GetAsString()); + serializer.write("command_id", command_id); + serializer.write("command_name", command_name); + if (original_command.has_value()) + serializer.write("original_command", original_command.value()); + if (args.has_value()) + serializer.write("args", args.value()); + if (ret_status.has_value()) + serializer.write("ret_status", ret_status.value()); + if (error_data.has_value()) + serializer.write("error_data", error_data.value()); +} + void DebuggerInfo::serialize(Serializer &serializer) const { LLDBBaseTelemetryInfo::serialize(serializer); @@ -67,7 +83,10 @@ void DebuggerInfo::serialize(Serializer &serializer) const { serializer.write("is_exit_entry", is_exit_entry); } -TelemetryManager::TelemetryManager(std::unique_ptr<Config> config) +std::atomic<uint64_t> CommandInfo::g_command_id_seed = 0; +uint64_t CommandInfo::GetNextId() { return g_command_id_seed.fetch_add(1); } + +TelemetryManager::TelemetryManager(std::unique_ptr<LLDBConfig> config) : m_config(std::move(config)), m_id(MakeUUID()) {} llvm::Error TelemetryManager::preDispatch(TelemetryInfo *entry) { @@ -79,23 +98,41 @@ llvm::Error TelemetryManager::preDispatch(TelemetryInfo *entry) { return llvm::Error::success(); } -const Config *TelemetryManager::GetConfig() { return m_config.get(); } +class NoOpTelemetryManager : public TelemetryManager { +public: + llvm::Error preDispatch(llvm::telemetry::TelemetryInfo *entry) override { + // Does nothing. + return llvm::Error::success(); + } -std::unique_ptr<TelemetryManager> TelemetryManager::g_instance = nullptr; -TelemetryManager *TelemetryManager::GetInstance() { - if (!Config::BuildTimeEnableTelemetry) - return nullptr; - return g_instance.get(); -} + explicit NoOpTelemetryManager() + : TelemetryManager(std::make_unique<LLDBConfig>( + /*EnableTelemetry*/ false, /*DetailedCommand*/ false)) {} + + virtual llvm::StringRef GetInstanceName() const override { + return "NoOpTelemetryManager"; + } + + llvm::Error dispatch(llvm::telemetry::TelemetryInfo *entry) override { + // Does nothing. + return llvm::Error::success(); + } -TelemetryManager *TelemetryManager::GetInstanceIfEnabled() { - // Telemetry may be enabled at build time but disabled at runtime. - if (TelemetryManager *ins = TelemetryManager::GetInstance()) { - if (ins->GetConfig()->EnableTelemetry) - return ins; + static NoOpTelemetryManager *GetInstance() { + static std::unique_ptr<NoOpTelemetryManager> g_ins = + std::make_unique<NoOpTelemetryManager>(); + return g_ins.get(); } +}; - return nullptr; +std::unique_ptr<TelemetryManager> TelemetryManager::g_instance = nullptr; +TelemetryManager *TelemetryManager::GetInstance() { + // If Telemetry is disabled or if there is no default instance, then use the + // NoOp manager. We use a dummy instance to avoid having to do nullchecks in + // various places. + if (!Config::BuildTimeEnableTelemetry || !g_instance) + return NoOpTelemetryManager::GetInstance(); + return g_instance.get(); } void TelemetryManager::SetInstance(std::unique_ptr<TelemetryManager> manager) { diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index c363f20081f9e..24cf755d1f740 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -46,7 +46,9 @@ #include "Commands/CommandObjectWatchpoint.h" #include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" #include "lldb/Core/PluginManager.h" +#include "lldb/Core/Telemetry.h" #include "lldb/Host/StreamFile.h" #include "lldb/Utility/ErrorMessages.h" #include "lldb/Utility/LLDBLog.h" @@ -88,6 +90,7 @@ #include "llvm/Support/Path.h" #include "llvm/Support/PrettyStackTrace.h" #include "llvm/Support/ScopedPrinter.h" +#include "llvm/Telemetry/Telemetry.h" #if defined(__APPLE__) #include <TargetConditionals.h> @@ -1883,9 +1886,48 @@ bool CommandInterpreter::HandleCommand(const char *command_line, LazyBool lazy_add_to_history, CommandReturnObject &result, bool force_repeat_command) { + telemetry::ScopedDispatcher<telemetry::CommandInfo> helper(&m_debugger); + const bool detailed_command_telemetry = + telemetry::TelemetryManager::GetInstance() + ->GetConfig() + ->detailed_command_telemetry; + const int command_id = telemetry::CommandInfo::GetNextId(); + std::string command_string(command_line); std::string original_command_string(command_string); std::string real_original_command_string(command_string); + std::string parsed_command_args; + CommandObject *cmd_obj = nullptr; + + helper.DispatchNow([&](lldb_private::telemetry::CommandInfo *info) { + info->command_id = command_id; + if (Target *target = GetExecutionContext().GetTargetPtr()) { + // If we have a target attached to this command, then get the UUID. + info->target_uuid = target->GetExecutableModule() != nullptr + ? target->GetExecutableModule()->GetUUID() + : UUID(); + } + if (detailed_command_telemetry) + info->original_command = original_command_string; + // The rest (eg., command_name, args, etc) hasn't been parsed yet; + // Those will be collected by the on-exit-callback. + }); + + helper.DispatchOnExit([&](lldb_private::telemetry::CommandInfo *info) { + // TODO: this is logging the time the command-handler finishes. + // But we may want a finer-grain durations too? + // (ie., the execute_time recorded below?) + info->command_id = command_id; + llvm::StringRef command_name = + cmd_obj ? cmd_obj->GetCommandName() : "<not found>"; + info->command_name = command_name.str(); + info->ret_status = result.GetStatus(); + if (std::string error_str = result.GetErrorString(); !error_str.empty()) + info->error_data = std::move(error_str); + + if (detailed_command_telemetry) + info->args = parsed_command_args; + }); Log *log = GetLog(LLDBLog::Commands); LLDB_LOGF(log, "Processing command: %s", command_line); @@ -1991,7 +2033,7 @@ bool CommandInterpreter::HandleCommand(const char *command_line, // From 1 above, we can determine whether the Execute function wants raw // input or not. - CommandObject *cmd_obj = ResolveCommandImpl(command_string, result); + cmd_obj = ResolveCommandImpl(command_string, result); // We have to preprocess the whole command string for Raw commands, since we // don't know the structure of the command. For parsed commands, we only @@ -2053,37 +2095,36 @@ bool CommandInterpreter::HandleCommand(const char *command_line, if (add_to_history) m_command_history.AppendString(original_command_string); - std::string remainder; const std::size_t actual_cmd_name_len = cmd_obj->GetCommandName().size(); if (actual_cmd_name_len < command_string.length()) - remainder = command_string.substr(actual_cmd_name_len); + parsed_command_args = command_string.substr(actual_cmd_name_len); // Remove any initial spaces - size_t pos = remainder.find_first_not_of(k_white_space); + size_t pos = parsed_command_args.find_first_not_of(k_white_space); if (pos != 0 && pos != std::string::npos) - remainder.erase(0, pos); + parsed_command_args.erase(0, pos); LLDB_LOGF( log, "HandleCommand, command line after removing command name(s): '%s'", - remainder.c_str()); + parsed_command_args.c_str()); // To test whether or not transcript should be saved, `transcript_item` is // used instead of `GetSaveTranscript()`. 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); + transcript_item->AddStringItem("commandArguments", parsed_command_args); } ElapsedTime elapsed(execute_time); cmd_obj->SetOriginalCommandString(real_original_command_string); // Set the indent to the position of the command in the command line. - pos = real_original_command_string.rfind(remainder); + pos = real_original_command_string.rfind(parsed_command_args); std::optional<uint16_t> indent; if (pos != std::string::npos) indent = pos; result.SetDiagnosticIndent(indent); - cmd_obj->Execute(remainder.c_str(), result); + cmd_obj->Execute(parsed_command_args.c_str(), result); } LLDB_LOGF(log, "HandleCommand, command %s", diff --git a/lldb/unittests/Core/TelemetryTest.cpp b/lldb/unittests/Core/TelemetryTest.cpp index 7fc2b3d0264e3..72db8c17f09ea 100644 --- a/lldb/unittests/Core/TelemetryTest.cpp +++ b/lldb/unittests/Core/TelemetryTest.cpp @@ -52,8 +52,8 @@ class TestDestination : public llvm::telemetry::Destination { class FakePlugin : public telemetry::TelemetryManager { public: FakePlugin() - : telemetry::TelemetryManager( - std::make_unique<llvm::telemetry::Config>(true)) {} + : telemetry::TelemetryManager(std::make_unique<telemetry::LLDBConfig>( + /*enable_telemetry=*/true, /*detailed_command_telemetry=*/true)) {} // TelemetryManager interface llvm::Error preDispatch(llvm::telemetry::TelemetryInfo *entry) override { _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits