llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-lldb Author: Vy Nguyen (oontvoo) <details> <summary>Changes</summary> Details: - This is a subset of PR/98528.( Pavel's suggestion was to split up the patch to make reviewing easier) - This contains only the concrete implementation of the framework to be used but no usages yet. - I plan to send two follow-up patches: + part2 : includes changes in the plugin-manager to set up the plugin stuff. + part3 : includes changes in LLDB/LLDB-DAP to use the framework Note: Please ignore all changes under llvm/.... These will be reverted after the pending LLVM patch is submitted. --- Patch is 32.79 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/119716.diff 9 Files Affected: - (added) lldb/include/lldb/Core/Telemetry.h (+309) - (modified) lldb/include/lldb/lldb-enumerations.h (+2-2) - (modified) lldb/source/Core/CMakeLists.txt (+2) - (added) lldb/source/Core/Telemetry.cpp (+338) - (modified) lldb/test/CMakeLists.txt (+10-10) - (added) llvm/include/llvm/Telemetry/Telemetry.h (+133) - (modified) llvm/lib/CMakeLists.txt (+1) - (added) llvm/lib/Telemetry/CMakeLists.txt (+6) - (added) llvm/lib/Telemetry/Telemetry.cpp (+11) ``````````diff diff --git a/lldb/include/lldb/Core/Telemetry.h b/lldb/include/lldb/Core/Telemetry.h new file mode 100644 index 00000000000000..241d957672b6ca --- /dev/null +++ b/lldb/include/lldb/Core/Telemetry.h @@ -0,0 +1,309 @@ +//===-- Telemetry.h ----------------------------------------------*- C++ +//-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_CORE_TELEMETRY_H +#define LLDB_CORE_TELEMETRY_H + +#include <atomic> +#include <chrono> +#include <ctime> +#include <memory> +#include <optional> +#include <string> +#include <unordered_map> + +#include "lldb/Core/StructuredDataImpl.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Utility/StructuredData.h" +#include "lldb/lldb-forward.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/JSON.h" +#include "llvm/Telemetry/Telemetry.h" + +namespace lldb_private { + +using llvm::telemetry::Destination; +using llvm::telemetry::KindType; +using llvm::telemetry::Serializer; +using llvm::telemetry::TelemetryInfo; + +struct LldbEntryKind : public ::llvm::telemetry::EntryKind { + static const KindType BaseInfo = 0b11000; + static const KindType DebuggerInfo = 0b11001; + static const KindType TargetInfo = 0b11010; + static const KindType ClientInfo = 0b11100; + static const KindType CommandInfo = 0b11101; + static const KindType MiscInfo = 0b11110; +}; + +/// Defines a convenient type for timestamp of various events. +/// This is used by the EventStats below. +using SteadyTimePoint = std::chrono::time_point<std::chrono::steady_clock>; + +/// Various time (and possibly memory) statistics of an event. +struct EventStats { + // REQUIRED: Start time of an event + SteadyTimePoint start; + // OPTIONAL: End time of an event - may be empty if not meaningful. + std::optional<SteadyTimePoint> end; + // TBD: could add some memory stats here too? + + EventStats() = default; + EventStats(SteadyTimePoint start) : start(start) {} + EventStats(SteadyTimePoint start, SteadyTimePoint end) + : start(start), end(end) {} +}; + +/// Describes the exit signal of an event. +struct ExitDescription { + int exit_code; + std::string description; +}; + +struct LldbBaseTelemetryInfo : public TelemetryInfo { + EventStats stats; + + // For dyn_cast, isa, etc operations. + KindType getKind() const override { return LldbEntryKind::BaseInfo; } + + static bool classof(const TelemetryInfo *t) { + if (t == nullptr) + return false; + // Subclasses of this is also acceptable. + return (t->getKind() & LldbEntryKind::BaseInfo) == LldbEntryKind::BaseInfo; + } + + void serialize(Serializer &serializer) const override; +}; + +struct DebuggerTelemetryInfo : public LldbBaseTelemetryInfo { + std::string username; + std::string lldb_git_sha; + std::string lldb_path; + std::string cwd; + + std::optional<ExitDescription> exit_desc; + DebuggerTelemetryInfo() = default; + + // Provide a copy ctor because we may need to make a copy before + // sanitizing the data. + // (The sanitization might differ between different Destination classes). + DebuggerTelemetryInfo(const DebuggerTelemetryInfo &other) { + username = other.username; + lldb_git_sha = other.lldb_git_sha; + lldb_path = other.lldb_path; + cwd = other.cwd; + }; + + KindType getKind() const override { return LldbEntryKind::DebuggerInfo; } + + static bool classof(const TelemetryInfo *T) { + if (T == nullptr) + return false; + return T->getKind() == LldbEntryKind::DebuggerInfo; + } + + void serialize(Serializer &serializer) const override; +}; + +struct TargetTelemetryInfo : public LldbBaseTelemetryInfo { + lldb::ModuleSP exec_mod; + Target *target_ptr; + + // The same as the executable-module's UUID. + std::string target_uuid; + std::string file_format; + + std::string binary_path; + size_t binary_size; + + std::optional<ExitDescription> exit_desc; + TargetTelemetryInfo() = default; + + TargetTelemetryInfo(const TargetTelemetryInfo &other) { + exec_mod = other.exec_mod; + target_uuid = other.target_uuid; + file_format = other.file_format; + binary_path = other.binary_path; + binary_size = other.binary_size; + exit_desc = other.exit_desc; + } + + KindType getKind() const override { return LldbEntryKind::TargetInfo; } + + static bool classof(const TelemetryInfo *T) { + if (T == nullptr) + return false; + return T->getKind() == LldbEntryKind::TargetInfo; + } + + void serialize(Serializer &serializer) const override; +}; + +// Entry from client (eg., SB-API) +struct ClientTelemetryInfo : public LldbBaseTelemetryInfo { + std::string request_name; + std::string error_msg; + + ClientTelemetryInfo() = default; + + ClientTelemetryInfo(const ClientTelemetryInfo &other) { + request_name = other.request_name; + error_msg = other.error_msg; + } + + KindType getKind() const override { return LldbEntryKind::ClientInfo; } + + static bool classof(const TelemetryInfo *T) { + if (T == nullptr) + return false; + return T->getKind() == LldbEntryKind::ClientInfo; + } + + void serialize(Serializer &serializer) const override; +}; + +struct CommandTelemetryInfo : public LldbBaseTelemetryInfo { + Target *target_ptr; + CommandReturnObject *result; + + // If the command is/can be associated with a target entry, + // this field contains that target's UUID. + // <EMPTY> otherwise. + std::string target_uuid; + std::string command_uuid; + + // Eg., "breakpoint set" + std::string command_name; + + // !!NOTE!!: The following fields may be omitted due to PII risk. + // (Configurable via the telemery::Config struct) + std::string original_command; + std::string args; + + std::optional<ExitDescription> exit_desc; + lldb::ReturnStatus ret_status; + + CommandTelemetryInfo() = default; + + CommandTelemetryInfo(const CommandTelemetryInfo &other) { + target_uuid = other.target_uuid; + command_uuid = other.command_uuid; + command_name = other.command_name; + original_command = other.original_command; + args = other.args; + exit_desc = other.exit_desc; + ret_status = other.ret_status; + } + + KindType getKind() const override { return LldbEntryKind::CommandInfo; } + + static bool classof(const TelemetryInfo *T) { + if (T == nullptr) + return false; + return T->getKind() == LldbEntryKind::CommandInfo; + } + + void serialize(Serializer &serializer) const override; +}; + +/// The "catch-all" entry to store a set of custom/non-standard +/// data. +struct MiscTelemetryInfo : public LldbBaseTelemetryInfo { + /// If the event is/can be associated with a target entry, + /// this field contains that target's UUID. + /// <EMPTY> otherwise. + std::string target_uuid; + + /// Set of key-value pairs for any optional (or impl-specific) data + std::map<std::string, std::string> meta_data; + + MiscTelemetryInfo() = default; + + MiscTelemetryInfo(const MiscTelemetryInfo &other) { + target_uuid = other.target_uuid; + meta_data = other.meta_data; + } + + KindType getKind() const override { return LldbEntryKind::MiscInfo; } + + static bool classof(const TelemetryInfo *T) { + if (T == nullptr) + return false; + return T->getKind() == LldbEntryKind::MiscInfo; + } + + void serialize(Serializer &serializer) const override; +}; + +/// The base Telemetry manager instance in LLDB +/// This class declares additional instrumentation points +/// applicable to LLDB. +class TelemetryManager : public llvm::telemetry::Manager { +public: + /// Creates an instance of TelemetryManager. + /// This uses the plugin registry to find an instance: + /// - If a vendor supplies a implementation, it will use it. + /// - If not, it will either return a no-op instance or a basic + /// implementation for testing. + /// + /// See also lldb_private::TelemetryVendor. + static std::unique_ptr<TelemetryManager> + CreateInstance(std::unique_ptr<llvm::telemetry::Config> config, + Debugger *debugger); + + /// To be invoked upon LLDB startup. + virtual void LogStartup(DebuggerTelemetryInfo *entry); + + /// To be invoked upon LLDB exit. + virtual void LogExit(DebuggerTelemetryInfo *entry); + + /// To be invoked upon loading the main executable module. + /// We log in a fire-n-forget fashion so that if the load + /// crashes, we don't lose the entry. + virtual void LogMainExecutableLoadStart(TargetTelemetryInfo *entry); + virtual void LogMainExecutableLoadEnd(TargetTelemetryInfo *entry); + + /// To be invoked upon process exit. + virtual void LogProcessExit(TargetTelemetryInfo *entry); + + /// Invoked for each command + /// We log in a fire-n-forget fashion so that if the command execution + /// crashes, we don't lose the entry. + virtual void LogCommandStart(CommandTelemetryInfo *entry); + virtual void LogCommandEnd(CommandTelemetryInfo *entry); + + /// For client (eg., SB API) to send telemetry entries. + virtual void + LogClientTelemetry(const lldb_private::StructuredDataImpl &entry); + + virtual std::string GetNextUUID() { + return std::to_string(uuid_seed.fetch_add(1)); + } + + llvm::Error dispatch(TelemetryInfo *entry) override; + void addDestination(std::unique_ptr<Destination> destination) override; + +protected: + TelemetryManager(std::unique_ptr<llvm::telemetry::Config> config, + Debugger *debugger); + TelemetryManager() = default; + virtual void CollectMiscBuildInfo(); + +private: + std::atomic<size_t> uuid_seed = 0; + std::unique_ptr<llvm::telemetry::Config> m_config; + Debugger *m_debugger; + const std::string m_session_uuid; + std::vector<std::unique_ptr<Destination>> m_destinations; +}; + +} // namespace lldb_private +#endif // LLDB_CORE_TELEMETRY_H diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h index 938f6e3abe8f2a..8015f42c5ffc8c 100644 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -257,8 +257,8 @@ enum StopReason { }; /// Command Return Status Types. -enum ReturnStatus { - eReturnStatusInvalid, +enum ReturnStatus : int { + eReturnStatusInvalid = 0, eReturnStatusSuccessFinishNoResult, eReturnStatusSuccessFinishResult, eReturnStatusSuccessContinuingNoResult, diff --git a/lldb/source/Core/CMakeLists.txt b/lldb/source/Core/CMakeLists.txt index dbc620b91b1ed1..4a02f7f1fc85e5 100644 --- a/lldb/source/Core/CMakeLists.txt +++ b/lldb/source/Core/CMakeLists.txt @@ -51,6 +51,7 @@ add_lldb_library(lldbCore Section.cpp SourceLocationSpec.cpp SourceManager.cpp + Telemetry.cpp StreamAsynchronousIO.cpp ThreadedCommunication.cpp UserSettingsController.cpp @@ -94,6 +95,7 @@ add_lldb_library(lldbCore Support Demangle TargetParser + Telemetry ) add_dependencies(lldbCore diff --git a/lldb/source/Core/Telemetry.cpp b/lldb/source/Core/Telemetry.cpp new file mode 100644 index 00000000000000..5ddad030ef962e --- /dev/null +++ b/lldb/source/Core/Telemetry.cpp @@ -0,0 +1,338 @@ + +//===-- Telemetry.cpp -----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "lldb/Core/Telemetry.h" + +#include <chrono> +#include <cstdlib> +#include <ctime> +#include <fstream> +#include <memory> +#include <string> +#include <typeinfo> +#include <utility> +#include <vector> + +#include "lldb/API/SBDebugger.h" +#include "lldb/API/SBProcess.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Statistics.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/UUID.h" +#include "lldb/Version/Version.h" +#include "lldb/lldb-enumerations.h" +#include "lldb/lldb-forward.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/LineIterator.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/RandomNumberGenerator.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Telemetry/Telemetry.h" + +namespace lldb_private { + +using ::llvm::Error; +using ::llvm::telemetry::Destination; +using ::llvm::telemetry::TelemetryInfo; + +static size_t ToNanosecOrZero(const std::optional<SteadyTimePoint> &Point) { + if (!Point.has_value()) + return 0; + + return Point.value().time_since_epoch().count(); +} + +void LldbBaseTelemetryInfo::serialize(Serializer &serializer) const { + serializer.writeInt32("EntryKind", getKind()); + serializer.writeString("SessionId", SessionId); +} + +void DebuggerTelemetryInfo::serialize(Serializer &serializer) const { + LldbBaseTelemetryInfo::serialize(serializer); + serializer.writeString("username", username); + serializer.writeString("lldb_path", lldb_path); + serializer.writeString("cwd", cwd); + serializer.writeSizeT("start", stats.start.time_since_epoch().count()); + serializer.writeSizeT("end", ToNanosecOrZero(stats.end)); +} + +void ClientTelemetryInfo::serialize(Serializer &serializer) const { + LldbBaseTelemetryInfo::serialize(serializer); + serializer.writeString("request_name", request_name); + serializer.writeString("error_msg", error_msg); + serializer.writeSizeT("start", stats.start.time_since_epoch().count()); + serializer.writeSizeT("end", ToNanosecOrZero(stats.end)); +} + +void TargetTelemetryInfo::serialize(Serializer &serializer) const { + LldbBaseTelemetryInfo::serialize(serializer); + serializer.writeString("target_uuid", target_uuid); + serializer.writeString("binary_path", binary_path); + serializer.writeSizeT("binary_size", binary_size); +} + +void CommandTelemetryInfo::serialize(Serializer &serializer) const { + LldbBaseTelemetryInfo::serialize(serializer); + serializer.writeString("target_uuid", target_uuid); + serializer.writeString("command_uuid", command_uuid); + serializer.writeString("args", args); + serializer.writeString("original_command", original_command); + serializer.writeSizeT("start", stats.start.time_since_epoch().count()); + serializer.writeSizeT("end", ToNanosecOrZero(stats.end)); + + // If this entry was emitted at the end of the command-execution, + // then calculate the runtime too. + if (stats.end.has_value()) { + serializer.writeSizeT("command_runtime", + (stats.end.value() - stats.start).count()); + if (exit_desc.has_value()) { + serializer.writeInt32("exit_code", exit_desc->exit_code); + serializer.writeString("exit_msg", exit_desc->description); + serializer.writeInt32("return_status", static_cast<int>(ret_status)); + } + } +} + +void MiscTelemetryInfo::serialize(Serializer &serializer) const { + LldbBaseTelemetryInfo::serialize(serializer); + serializer.writeString("target_uuid", target_uuid); + serializer.writeKeyValueMap("meta_data", meta_data); +} + +static std::string MakeUUID(lldb_private::Debugger *debugger) { + std::string ret; + uint8_t random_bytes[16]; + if (auto ec = llvm::getRandomBytes(random_bytes, 16)) { + LLDB_LOG(GetLog(LLDBLog::Object), + "Failed to generate random bytes for UUID: {0}", ec.message()); + // fallback to using timestamp + debugger ID. + ret = std::to_string( + std::chrono::steady_clock::now().time_since_epoch().count()) + + "_" + std::to_string(debugger->GetID()); + } else { + ret = lldb_private::UUID(random_bytes).GetAsString(); + } + + return ret; +} + +TelemetryManager::TelemetryManager( + std::unique_ptr<llvm::telemetry::Config> config, + lldb_private::Debugger *debugger) + : m_config(std::move(config)), m_debugger(debugger), + m_session_uuid(MakeUUID(debugger)) {} + +std::unique_ptr<TelemetryManager> TelemetryManager::CreateInstance( + std::unique_ptr<llvm::telemetry::Config> config, + lldb_private::Debugger *debugger) { + + TelemetryManager *ins = new TelemetryManager(std::move(config), debugger); + + return std::unique_ptr<TelemetryManager>(ins); +} + +llvm::Error TelemetryManager::dispatch(TelemetryInfo *entry) { + entry->SessionId = m_session_uuid; + + for (auto &destination : m_destinations) { + llvm::Error err = destination->receiveEntry(entry); + if (err) { + return std::move(err); + } + } + return Error::success(); +} + +void TelemetryManager::addDestination( + std::unique_ptr<Destination> destination) { + m_destinations.push_back(std::move(destination)); +} + +void TelemetryManager::LogStartup(DebuggerTelemetryInfo *entry) { + UserIDResolver &resolver = lldb_private::HostInfo::GetUserIDResolver(); + std::optional<llvm::StringRef> opt_username = + resolver.GetUserName(lldb_private::HostInfo::GetUserID()); + if (opt_username) + entry->username = *opt_username; + + entry->lldb_git_sha = + lldb_private::GetVersion(); // TODO: find the real git sha? + + llvm::SmallString<64> cwd; + if (!llvm::sys::fs::current_path(cwd)) { + entry->cwd = cwd.c_str(); + } else { + MiscTelemetryInfo misc_info; + misc_info.meta_data["internal_errors"] = "Cannot determine CWD"; + if (auto er = dispatch(&misc_info)) { + LLDB_LOG(GetLog(LLDBLog::Object), + "Failed to dispatch misc-info from startup"); + } + } + + if (auto er = dispatch(entry)) { + LLDB_LOG(GetLog(LLDBLog::Object), "Failed to dispatch entry from startup"); + } + + // Optional part + CollectMiscBuildInfo(); +} + +void TelemetryManager::LogExit(DebuggerTelemetryInfo *entry) { + if (auto *selected_target = + m_debugger->GetSelectedExecutionContext().GetTargetPtr()) { + if (!selected_target->IsDummyTarget()) { + const lldb::ProcessSP proc = selected_target->GetProcessSP(); + if (proc == nullptr) { + // no process has been launched yet. + entry->exit_desc = {-1, "no process launched."}; + } else { + entry->exit_desc = {proc->GetExitStatus(), ""}; + if (const char *description = proc->GetExitDescription()) + entry->exit_desc->description = std::string(description); + } + } + } + dispatch(entry); +} + +void TelemetryManager::LogProcessExit(TargetTelemetryInfo *entry) { + entry->target_uuid = + entry->target_ptr && !entry->target_ptr->IsDummyTarget() + ? entry->target_ptr->GetExecutableModule()->GetUUID().GetAsString() + : ""; + + dispatch(entry); +} + +void TelemetryManager::CollectMiscBuildInfo() { + // collecting use-case specific data +} + +void TelemetryManager::LogMainExecutableLoadStart(TargetTelemetryInfo *entry) { + entry->binary_path = + entry->exec_mod->GetFileSpec().GetPathAsConstString().GetCString(); + entry->file_format = entry->exec_mod->GetArchitecture().GetArchitectureName(); + entry->target_uuid = entry->exec_mod->GetUUID().GetAsString(); + if (auto err = llvm::sys::fs::file_size( + entry->exec_mod->GetFileSpec().GetPath(), entry->binary_size)) { + // If there was error obtaining it, just reset the size to 0. + // Maybe log the error too? + entry->binary_size = 0; + } + dispatch(entry); +} + +void TelemetryManager::LogMainExecutableLoadEnd(TargetTelemetryInfo *entry) { + lldb::ModuleSP exec_mod = entry->exec_mod; + entry->binary_path = + exec_mod->GetFileSpec().GetPathAsConstString().GetCString(); + entry->file_format = exec_mod->GetArchitecture().GetArchitectureName(); + entry->target_uuid = exec_mod->GetUUID().GetAsString(); + entry->binary_size = exec_mod->GetObjectFile()->GetByteSize(); + + dispatch(entry); + + // Collect some more info, might be useful? + MiscTelemetryInfo misc_info; + misc_info.target_uuid = exec_mod->GetUUID().GetAsString(); + misc_info.m... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/119716 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits