https://github.com/oontvoo updated https://github.com/llvm/llvm-project/pull/98528
>From 2fa1fa227e6ff93f8904d0f9d56432401d673ed7 Mon Sep 17 00:00:00 2001 From: Vy Nguyen <v...@google.com> Date: Wed, 10 Jul 2024 15:27:38 -0400 Subject: [PATCH 1/2] [llvm]Added lib/Telemetry - Provide a base API for llvm Telemetry - Provide some concrete implementation for it in lldb/Telemetry --- lldb/include/lldb/API/SBDebugger.h | 4 + lldb/include/lldb/Core/Debugger.h | 8 + lldb/include/lldb/Core/Telemetry.h | 153 +++++ lldb/include/lldb/Target/Process.h | 3 + lldb/source/API/SBDebugger.cpp | 9 + lldb/source/Core/CMakeLists.txt | 2 + lldb/source/Core/Debugger.cpp | 31 +- lldb/source/Core/Telemetry.cpp | 606 ++++++++++++++++++ .../source/Interpreter/CommandInterpreter.cpp | 44 +- lldb/source/Target/Process.cpp | 7 +- lldb/source/Target/Target.cpp | 15 +- lldb/tools/lldb-dap/DAP.cpp | 6 +- llvm/include/llvm/Telemetry/Telemetry.h | 99 +++ llvm/lib/CMakeLists.txt | 1 + llvm/lib/Telemetry/CMakeLists.txt | 6 + llvm/lib/Telemetry/Telemetry.cpp | 32 + 16 files changed, 1011 insertions(+), 15 deletions(-) create mode 100644 lldb/include/lldb/Core/Telemetry.h create mode 100644 lldb/source/Core/Telemetry.cpp create mode 100644 llvm/include/llvm/Telemetry/Telemetry.h create mode 100644 llvm/lib/Telemetry/CMakeLists.txt create mode 100644 llvm/lib/Telemetry/Telemetry.cpp diff --git a/lldb/include/lldb/API/SBDebugger.h b/lldb/include/lldb/API/SBDebugger.h index 84ea9c0f772e1..de09995679ad9 100644 --- a/lldb/include/lldb/API/SBDebugger.h +++ b/lldb/include/lldb/API/SBDebugger.h @@ -9,10 +9,12 @@ #ifndef LLDB_API_SBDEBUGGER_H #define LLDB_API_SBDEBUGGER_H +#include <chrono> #include <cstdio> #include "lldb/API/SBDefines.h" #include "lldb/API/SBPlatform.h" +#include "lldb/API/SBStructuredData.h" namespace lldb_private { class CommandPluginInterfaceImplementation; @@ -245,6 +247,8 @@ class LLDB_API SBDebugger { lldb::SBTarget GetDummyTarget(); + void SendTelemetry(SBStructuredData *entry); + // Return true if target is deleted from the target list of the debugger. bool DeleteTarget(lldb::SBTarget &target); diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h index a72c2596cc2c5..13a444e438fec 100644 --- a/lldb/include/lldb/Core/Debugger.h +++ b/lldb/include/lldb/Core/Debugger.h @@ -19,6 +19,7 @@ #include "lldb/Core/FormatEntity.h" #include "lldb/Core/IOHandler.h" #include "lldb/Core/SourceManager.h" +#include "lldb/Core/Telemetry.h" #include "lldb/Core/UserSettingsController.h" #include "lldb/Host/HostThread.h" #include "lldb/Host/StreamFile.h" @@ -31,6 +32,7 @@ #include "lldb/Utility/Diagnostics.h" #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/Status.h" +#include "lldb/Utility/StructuredData.h" #include "lldb/Utility/UserID.h" #include "lldb/lldb-defines.h" #include "lldb/lldb-enumerations.h" @@ -137,6 +139,10 @@ class Debugger : public std::enable_shared_from_this<Debugger>, lldb::StreamFileSP GetErrorStreamSP() { return m_error_stream_sp; } + std::shared_ptr<LldbTelemeter> GetTelemeter() { return m_telemeter; } + + void SendClientTelemetry(lldb_private::StructuredData::Object *entry); + File &GetInputFile() { return *m_input_file_sp; } File &GetOutputFile() { return m_output_stream_sp->GetFile(); } @@ -754,6 +760,7 @@ class Debugger : public std::enable_shared_from_this<Debugger>, uint32_t m_interrupt_requested = 0; ///< Tracks interrupt requests std::mutex m_interrupt_mutex; + std::shared_ptr<LldbTelemeter> m_telemeter; // Events for m_sync_broadcaster enum { eBroadcastBitEventThreadIsListening = (1 << 0), @@ -766,6 +773,7 @@ class Debugger : public std::enable_shared_from_this<Debugger>, Debugger(const Debugger &) = delete; const Debugger &operator=(const Debugger &) = delete; + TelemetryEventStats stats; }; } // namespace lldb_private diff --git a/lldb/include/lldb/Core/Telemetry.h b/lldb/include/lldb/Core/Telemetry.h new file mode 100644 index 0000000000000..15cc3139764ad --- /dev/null +++ b/lldb/include/lldb/Core/Telemetry.h @@ -0,0 +1,153 @@ +#ifndef LLDB_CORE_TELEMETRY_H +#define LLDB_CORE_TELEMETRY_H + +#include <chrono> +#include <ctime> +#include <memory> +#include <optional> +#include <string> +#include <unordered_map> + +#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/Telemetry/Telemetry.h" + +using namespace llvm::telemetry; + +namespace lldb_private { + +struct DebuggerTelemetryInfo : public ::llvm::telemetry::TelemetryInfo { + std::string username; + std::string lldb_git_sha; + std::string lldb_path; + std::string cwd; + + std::string ToString() const override; +}; + +struct TargetTelemetryInfo : public ::llvm::telemetry::TelemetryInfo { + // All entries emitted for the same SBTarget will have the same + // target_uuid. + std::string target_uuid; + std::string file_format; + + std::string binary_path; + size_t binary_size; + + std::string ToString() const override; +}; + +// Entry from client (eg., SB-API) +struct ClientTelemetryInfo : public ::llvm::telemetry::TelemetryInfo { + std::string request_name; + std::string error_msg; + std::string ToString() const override; +}; + +struct CommandTelemetryInfo : public ::llvm::telemetry::TelemetryInfo { + // 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 TelemetryConfig struct) + std::string original_command; + std::string args; + + std::string ToString() const override; +}; + +// The "catch-all" entry to store a set of custom/non-standard +// data. +struct MiscTelemetryInfo : public ::llvm::telemetry::TelemetryInfo { + // 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::unordered_map<std::string, std::string> meta_data; + + std::string ToString() const override; +}; + +class LldbTelemeter : public llvm::telemetry::Telemeter { +public: + static std::shared_ptr<LldbTelemeter> CreateInstance(Debugger *); + + virtual ~LldbTelemeter() = default; + + // void LogStartup(llvm::StringRef lldb_path, + // TelemetryInfo *entry) override; + // void LogExit(llvm::StringRef lldb_path, TelemetryInfo *entry) + // override; + + // Invoked upon process exit + virtual void LogProcessExit(int status, llvm::StringRef exit_string, + TelemetryEventStats stats, + Target *target_ptr) = 0; + + // 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(lldb::ModuleSP exec_mod, + TelemetryEventStats stats) = 0; + virtual void LogMainExecutableLoadEnd(lldb::ModuleSP exec_mod, + TelemetryEventStats stats) = 0; + + // 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(llvm::StringRef uuid, + llvm::StringRef original_command, + TelemetryEventStats stats, + Target *target_ptr) = 0; + virtual void LogCommandEnd(llvm::StringRef uuid, llvm::StringRef command_name, + llvm::StringRef command_args, + TelemetryEventStats stats, Target *target_ptr, + CommandReturnObject *result) = 0; + + virtual std::string GetNextUUID() = 0; + + // For client (eg., SB API) to send telemetry entries. + virtual void + LogClientTelemetry(lldb_private::StructuredData::Object *entry) = 0; +}; + +// Logger configs: LLDB users can also supply their own configs via: +// $HOME/.lldb_telemetry_config +// +// We can propose simple syntax: <field_name><colon><value> +// Eg., +// enable_telemetry:true +// destination:stdout +// destination:stderr +// destination:/path/to/some/file +// +// The allowed field_name values are: +// * enable_telemetry +// If the fields are specified more than once, the last line will take +// precedence If enable_logging is set to false, no logging will occur. +// * destination. +// This is allowed to be specified multiple times - it will add to the +// default (ie, specified by vendor) list of destinations. +// The value can be either: +// + one of the two magic values "stdout" or "stderr". +// + a path to a local file +// !!NOTE!!: We decided to use a separate file instead of the existing settings +// file because that file is parsed too late in the process and by the +// there might have been lots of telemetry-entries that need to be +// sent already. +// This approach avoid losing log entries if LLDB crashes during init. +TelemetryConfig *GetTelemetryConfig(); + +} // namespace lldb_private +#endif // LLDB_CORE_TELEMETRY_H diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index ceaf547ebddaf..c35ff397a203f 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -28,6 +28,7 @@ #include "lldb/Core/LoadedModuleInfoList.h" #include "lldb/Core/PluginInterface.h" #include "lldb/Core/SourceManager.h" +#include "lldb/Core/Telemetry.h" #include "lldb/Core/ThreadSafeValue.h" #include "lldb/Core/ThreadedCommunication.h" #include "lldb/Core/UserSettingsController.h" @@ -3285,6 +3286,8 @@ void PruneThreadPlans(); Process(const Process &) = delete; const Process &operator=(const Process &) = delete; + + TelemetryEventStats m_event_stats; }; /// RAII guard that should be acquired when an utility function is called within diff --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp index fb035a36e7d74..3629fcc2574af 100644 --- a/lldb/source/API/SBDebugger.cpp +++ b/lldb/source/API/SBDebugger.cpp @@ -34,6 +34,7 @@ #include "lldb/API/SBTypeNameSpecifier.h" #include "lldb/API/SBTypeSummary.h" #include "lldb/API/SBTypeSynthetic.h" +#include <iostream> #include "lldb/Core/Debugger.h" #include "lldb/Core/DebuggerEvents.h" @@ -968,6 +969,14 @@ SBTarget SBDebugger::GetDummyTarget() { return sb_target; } +void SBDebugger::SendTelemetry(SBStructuredData *entry) { + if (lldb_private::Debugger *debugger = this->get()) { + debugger->SendClientTelemetry(entry->m_impl_up->GetObjectSP().get()); + } else { + std::cerr << " --- cannot send telemetry entry - debugger is null\n"; + } +} + bool SBDebugger::DeleteTarget(lldb::SBTarget &target) { LLDB_INSTRUMENT_VA(this, target); diff --git a/lldb/source/Core/CMakeLists.txt b/lldb/source/Core/CMakeLists.txt index dbc620b91b1ed..d7f8297fa555c 100644 --- a/lldb/source/Core/CMakeLists.txt +++ b/lldb/source/Core/CMakeLists.txt @@ -52,6 +52,7 @@ add_lldb_library(lldbCore SourceLocationSpec.cpp SourceManager.cpp StreamAsynchronousIO.cpp + Telemetry.cpp ThreadedCommunication.cpp UserSettingsController.cpp Value.cpp @@ -94,6 +95,7 @@ add_lldb_library(lldbCore Support Demangle TargetParser + Telemetry ) add_dependencies(lldbCore diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp index 309e01e456580..2947d2d537767 100644 --- a/lldb/source/Core/Debugger.cpp +++ b/lldb/source/Core/Debugger.cpp @@ -17,6 +17,7 @@ #include "lldb/Core/PluginManager.h" #include "lldb/Core/Progress.h" #include "lldb/Core/StreamAsynchronousIO.h" +#include "lldb/Core/Telemetry.h" #include "lldb/DataFormatters/DataVisualization.h" #include "lldb/Expression/REPL.h" #include "lldb/Host/File.h" @@ -53,6 +54,8 @@ #include "lldb/Utility/Stream.h" #include "lldb/Utility/StreamString.h" #include "lldb/lldb-enumerations.h" +#include "llvm/Telemetry/Telemetry.h" +#include <chrono> #if defined(_WIN32) #include "lldb/Host/windows/PosixApi.h" @@ -733,12 +736,20 @@ void Debugger::InstanceInitialize() { DebuggerSP Debugger::CreateInstance(lldb::LogOutputCallback log_callback, void *baton) { + SteadyTimePoint start_time = std::chrono::steady_clock::now(); DebuggerSP debugger_sp(new Debugger(log_callback, baton)); + debugger_sp->stats.m_start = start_time; if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { std::lock_guard<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr); g_debugger_list_ptr->push_back(debugger_sp); } debugger_sp->InstanceInitialize(); + TelemetryEventStats init_stats(start_time, std::chrono::steady_clock::now()); + llvm::telemetry::TelemetryInfo entry; + entry.stats = {start_time, std::chrono::steady_clock::now()}; + debugger_sp->m_telemeter->LogStartup( + HostInfo::GetProgramFileSpec().GetPathAsConstString().GetCString(), + &entry); return debugger_sp; } @@ -860,7 +871,8 @@ Debugger::Debugger(lldb::LogOutputCallback log_callback, void *baton) m_sync_broadcaster(nullptr, "lldb.debugger.sync"), m_broadcaster(m_broadcaster_manager_sp, GetStaticBroadcasterClass().str()), - m_forward_listener_sp(), m_clear_once() { + m_forward_listener_sp(), m_clear_once(), + m_telemeter(LldbTelemeter::CreateInstance(this)) { // Initialize the debugger properties as early as possible as other parts of // LLDB will start querying them during construction. m_collection_sp->Initialize(g_debugger_properties); @@ -952,6 +964,18 @@ void Debugger::Clear() { // static void Debugger::Destroy(lldb::DebuggerSP &debugger_sp); // static void Debugger::Terminate(); llvm::call_once(m_clear_once, [this]() { + // Log the "quit" event. + // Note: this session_stats include the time since LLDB starts till quit + // (now). + // TBD: we could also record stats for *just* the quit action, if needed? + // (ie., how long it takes to run all these cleanup functions?) + llvm::telemetry::TelemetryInfo entry; + entry.stats = {/*start_session*/ stats.m_start, + /*end_session*/ std::chrono::steady_clock::now()}; + m_telemeter->LogExit( + HostInfo::GetProgramFileSpec().GetPathAsConstString().GetCString(), + &entry); + ClearIOHandlers(); StopIOHandlerThread(); StopEventHandlerThread(); @@ -2231,6 +2255,11 @@ Status Debugger::RunREPL(LanguageType language, const char *repl_options) { return err; } +void Debugger::SendClientTelemetry( + lldb_private::StructuredData::Object *entry) { + m_telemeter->LogClientTelemetry(entry); +} + llvm::ThreadPoolInterface &Debugger::GetThreadPool() { assert(g_thread_pool && "Debugger::GetThreadPool called before Debugger::Initialize"); diff --git a/lldb/source/Core/Telemetry.cpp b/lldb/source/Core/Telemetry.cpp new file mode 100644 index 0000000000000..6a6f67bd8d034 --- /dev/null +++ b/lldb/source/Core/Telemetry.cpp @@ -0,0 +1,606 @@ + +//===-- 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 <stdbool.h> +#include <sys/auxv.h> + +#include <memory> + +#include <chrono> +#include <cstdlib> +#include <ctime> +#include <fstream> +#include <iostream> +#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/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/FileSystem.h" +#include "llvm/Support/LineIterator.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/RandomNumberGenerator.h" +#include "llvm/Telemetry/Telemetry.h" + +// Vendor specific. +void ApplyVendorSpecificConfigs(llvm::telemetry::TelemetryConfig *config) + __attribute__((weak)); +std::shared_ptr<llvm::telemetry::TelemetryInfo> +SanitizeSensitiveFields(const llvm::telemetry::TelemetryInfo *entry) + __attribute__((weak)); +std::shared_ptr<lldb_private::LldbTelemeter> +CreateVendorSpecificTelemeter(llvm::telemetry::TelemetryConfig *config) + __attribute__((weak)); + +namespace lldb_private { + +static std::string GetDuration(const TelemetryEventStats &stats) { + if (stats.m_end.has_value()) + return std::to_string((stats.m_end.value() - stats.m_start).count()) + + "(nanosec)"; + return "<NONE>"; +} + +std::string DebuggerTelemetryInfo::ToString() const { + std::string duration_desc = + (exit_description.has_value() ? " lldb session duration: " + : " lldb startup duration: ") + + std::to_string((stats.m_end.value() - stats.m_start).count()) + + "(nanosec)\n"; + + return TelemetryInfo::ToString() + "\n" + ("[DebuggerTelemetryInfo]\n") + + (" username: " + username + "\n") + + (" lldb_git_sha: " + lldb_git_sha + "\n") + + (" lldb_path: " + lldb_path + "\n") + (" cwd: " + cwd + "\n") + + duration_desc + "\n"; +} + +std::string ClientTelemetryInfo::ToString() const { + return TelemetryInfo::ToString() + "\n" + ("[DapRequestInfoEntry]\n") + + (" request_name: " + request_name + "\n") + + (" request_duration: " + GetDuration(stats) + "(nanosec)\n") + + (" error_msg: " + error_msg + "\n"); +} + +std::string TargetTelemetryInfo::ToString() const { + std::string exit_or_load_desc; + if (exit_description.has_value()) { + // If this entry was emitted for an exit + exit_or_load_desc = " process_duration: " + GetDuration(stats) + + " exit: " + exit_description->ToString() + "\n"; + } else { + // This was emitted for a load event. + // See if it was the start-load or end-load entry + if (stats.m_end.has_value()) { + exit_or_load_desc = + " startup_init_duration: " + GetDuration(stats) + "\n"; + } else { + exit_or_load_desc = " startup_init_start\n"; + } + } + return TelemetryInfo::ToString() + "\n" + ("[TargetTelemetryInfo]\n") + + (" target_uuid: " + target_uuid + "\n") + + (" file_format: " + file_format + "\n") + + (" binary_path: " + binary_path + "\n") + + (" binary_size: " + std::to_string(binary_size) + "\n") + + exit_or_load_desc; +} + +static std::string StatusToString(CommandReturnObject *result) { + std::string msg; + switch (result->GetStatus()) { + case lldb::eReturnStatusInvalid: + msg = "invalid"; + break; + case lldb::eReturnStatusSuccessFinishNoResult: + msg = "success_finish_no_result"; + break; + case lldb::eReturnStatusSuccessFinishResult: + msg = "success_finish_result"; + break; + case lldb::eReturnStatusSuccessContinuingNoResult: + msg = "success_continuing_no_result"; + break; + case lldb::eReturnStatusSuccessContinuingResult: + msg = "success_continuing_result"; + break; + case lldb::eReturnStatusStarted: + msg = "started"; + break; + case lldb::eReturnStatusFailed: + msg = "failed"; + break; + case lldb::eReturnStatusQuit: + msg = "quit"; + break; + } + if (llvm::StringRef error_data = result->GetErrorData(); + !error_data.empty()) { + msg += " Error msg: " + error_data.str(); + } + return msg; +} + +std::string CommandTelemetryInfo::ToString() const { + // Whether this entry was emitted at the start or at the end of the + // command-execution. + if (stats.m_end.has_value()) { + return TelemetryInfo::ToString() + "\n" + + ("[CommandTelemetryInfo] - END\n") + + (" target_uuid: " + target_uuid + "\n") + + (" command_uuid: " + command_uuid + "\n") + + (" command_name: " + command_name + "\n") + + (" args: " + args + "\n") + + (" command_runtime: " + GetDuration(stats) + "\n") + + (exit_description.has_value() ? exit_description->ToString() + : "no exit-description") + + "\n"; + } else { + return TelemetryInfo::ToString() + "\n" + + ("[CommandTelemetryInfo] - START\n") + + (" target_uuid: " + target_uuid + "\n") + + (" command_uuid: " + command_uuid + "\n") + + (" original_command: " + original_command + "\n"); + } +} + +std::string MiscTelemetryInfo::ToString() const { + std::string ret = + TelemetryInfo::ToString() + "\n" + ("[MiscTelemetryInfo]\n") + + (" target_uuid: " + target_uuid + "\n") + (" meta_data:\n"); + + for (const auto &kv : meta_data) { + ret += (" " + kv.first + ": " + kv.second + "\n"); + } + return ret; +} + +class StreamTelemetryDestination : public TelemetryDestination { +public: + StreamTelemetryDestination(std::ostream &os, std::string desc) + : os(os), desc(desc) {} + llvm::Error EmitEntry(const TelemetryInfo *entry) override { + if (SanitizeSensitiveFields) + os << SanitizeSensitiveFields(entry)->ToString() << "\n"; + else + os << entry->ToString() << "\n"; + os.flush(); + return llvm::ErrorSuccess(); + } + + std::string name() const override { return desc; } + +private: + std::ostream &os; + const std::string desc; +}; + +// No-op logger to use when users disable telemetry +class NoOpTelemeter : public LldbTelemeter { +public: + static std::shared_ptr<LldbTelemeter> CreateInstance(Debugger *debugger) { + static std::shared_ptr<LldbTelemeter> ins(new NoOpTelemeter(debugger)); + return ins; + } + + NoOpTelemeter(Debugger *debugger) {} + void LogStartup(llvm::StringRef tool_path, TelemetryInfo *entry) override {} + void LogExit(llvm::StringRef tool_path, TelemetryInfo *entry) override {} + void LogProcessExit(int status, llvm::StringRef exit_string, + TelemetryEventStats stats, Target *target_ptr) override {} + void LogMainExecutableLoadStart(lldb::ModuleSP exec_mod, + TelemetryEventStats stats) override {} + void LogMainExecutableLoadEnd(lldb::ModuleSP exec_mod, + TelemetryEventStats stats) override {} + + void LogCommandStart(llvm::StringRef uuid, llvm::StringRef original_command, + TelemetryEventStats stats, Target *target_ptr) override { + } + void LogCommandEnd(llvm::StringRef uuid, llvm::StringRef command_name, + llvm::StringRef command_args, TelemetryEventStats stats, + Target *target_ptr, CommandReturnObject *result) override { + } + + void + LogClientTelemetry(lldb_private::StructuredData::Object *entry) override {} + + void AddDestination(TelemetryDestination *destination) override {} + std::string GetNextUUID() override { return ""; } +}; + +class BasicTelemeter : public LldbTelemeter { +public: + static std::shared_ptr<BasicTelemeter> CreateInstance(Debugger *); + + virtual ~BasicTelemeter() = default; + + void LogStartup(llvm::StringRef lldb_path, TelemetryInfo *entry) override; + void LogExit(llvm::StringRef lldb_path, TelemetryInfo *entry) override; + + void LogProcessExit(int status, llvm::StringRef exit_string, + TelemetryEventStats stats, Target *target_ptr) override; + void LogMainExecutableLoadStart(lldb::ModuleSP exec_mod, + TelemetryEventStats stats) override; + void LogMainExecutableLoadEnd(lldb::ModuleSP exec_mod, + TelemetryEventStats stats) override; + + void LogCommandStart(llvm::StringRef uuid, llvm::StringRef original_command, + TelemetryEventStats stats, Target *target_ptr) override; + void LogCommandEnd(llvm::StringRef uuid, llvm::StringRef command_name, + llvm::StringRef command_args, TelemetryEventStats stats, + Target *target_ptr, CommandReturnObject *result) override; + + void LogClientTelemetry(lldb_private::StructuredData::Object *entry) override; + + void AddDestination(TelemetryDestination *destination) override { + m_destinations.push_back(destination); + } + + std::string GetNextUUID() override { + return std::to_string(uuid_seed.fetch_add(1)); + } + +protected: + BasicTelemeter(Debugger *debugger); + + void CollectMiscBuildInfo(); + +private: + template <typename EntrySubType> EntrySubType MakeBaseEntry() { + EntrySubType entry; + entry.session_uuid = m_session_uuid; + entry.counter = counter.fetch_add(1); + return entry; + } + + void EmitToDestinations(const TelemetryInfo *entry); + + Debugger *m_debugger; + const std::string m_session_uuid; + std::string startup_lldb_path; + + // counting number of entries. + std::atomic<size_t> counter = 0; + + std::vector<TelemetryDestination *> m_destinations; + + std::atomic<size_t> uuid_seed = 0; +}; + +static std::string MakeUUID(lldb_private::Debugger *debugger) { + std::string ret; + uint8_t random_bytes[16]; + if (auto ec = llvm::getRandomBytes(random_bytes, 16)) { + std::cerr << "entropy source failure: " + 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; +} + +BasicTelemeter::BasicTelemeter(lldb_private::Debugger *debugger) + : m_debugger(debugger), m_session_uuid(MakeUUID(debugger)) {} + +std::shared_ptr<BasicTelemeter> +BasicTelemeter::CreateInstance(lldb_private::Debugger *debugger) { + auto *config = GetTelemetryConfig(); + + BasicTelemeter *ins = new BasicTelemeter(debugger); + for (const std ::string &dest : config->additional_destinations) { + if (dest == "stdout") { + ins->AddDestination(new StreamTelemetryDestination(std::cout, "stdout")); + } else if (dest == "stderr") { + ins->AddDestination(new StreamTelemetryDestination(std::cerr, "stderr")); + } else { + // TODO: handle file paths + } + } + + return std::shared_ptr<BasicTelemeter>(ins); +} + +void BasicTelemeter::EmitToDestinations(const TelemetryInfo *entry) { + // TODO: can do this in a separate thread (need to own the ptrs!). + for (auto destination : m_destinations) { + auto err = destination->EmitEntry(entry); + if (err) { + std::cerr << "error emitting to destination: " << destination->name() + << "\n"; + } + } +} + +void BasicTelemeter::LogStartup(llvm::StringRef lldb_path, + TelemetryInfo *entry) { + startup_lldb_path = lldb_path.str(); + lldb_private::DebuggerTelemetryInfo startup_info = + MakeBaseEntry<lldb_private::DebuggerTelemetryInfo>(); + + auto &resolver = lldb_private::HostInfo::GetUserIDResolver(); + auto opt_username = resolver.GetUserName(lldb_private::HostInfo::GetUserID()); + if (opt_username) + startup_info.username = *opt_username; + + startup_info.lldb_git_sha = lldb_private::GetVersion(); // TODO: fix this + startup_info.lldb_path = startup_lldb_path; + startup_info.stats = entry->stats; + + llvm::SmallString<64> cwd; + if (!llvm::sys::fs::current_path(cwd)) { + startup_info.cwd = cwd.c_str(); + } else { + MiscTelemetryInfo misc_info = MakeBaseEntry<MiscTelemetryInfo>(); + misc_info.meta_data["internal_errors"] = "Cannot determine CWD"; + EmitToDestinations(&misc_info); + } + + std::cout << "emitting startup info\n"; + EmitToDestinations(&startup_info); + + // Optional part + CollectMiscBuildInfo(); +} + +void BasicTelemeter::LogExit(llvm::StringRef lldb_path, TelemetryInfo *entry) { + std::cout << "debugger exiting at " << lldb_path.str() << "\n"; + // we should be shutting down the same instance that we started?! + // llvm::Assert(startup_lldb_path == lldb_path.str()); + + lldb_private::DebuggerTelemetryInfo exit_info = + MakeBaseEntry<lldb_private::DebuggerTelemetryInfo>(); + exit_info.stats = entry->stats; + exit_info.lldb_path = startup_lldb_path; + 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. + exit_info.exit_description = {-1, "no process launched."}; + } else { + exit_info.exit_description = {proc->GetExitStatus(), ""}; + if (const char *description = proc->GetExitDescription()) + exit_info.exit_description->description = std::string(description); + } + } + } + EmitToDestinations(&exit_info); +} + +void BasicTelemeter::LogProcessExit(int status, llvm::StringRef exit_string, + TelemetryEventStats stats, + Target *target_ptr) { + lldb_private::TargetTelemetryInfo exit_info = + MakeBaseEntry<lldb_private::TargetTelemetryInfo>(); + exit_info.stats = stats; + exit_info.target_uuid = + target_ptr && !target_ptr->IsDummyTarget() + ? target_ptr->GetExecutableModule()->GetUUID().GetAsString() + : ""; + exit_info.exit_description = {status, exit_string.str()}; + + std::cout << "emitting process exit ...\n"; + EmitToDestinations(&exit_info); +} + +void BasicTelemeter::CollectMiscBuildInfo() { + // collecting use-case specific data +} + +void BasicTelemeter::LogMainExecutableLoadStart(lldb::ModuleSP exec_mod, + TelemetryEventStats stats) { + TargetTelemetryInfo target_info = MakeBaseEntry<TargetTelemetryInfo>(); + target_info.stats = std::move(stats); + target_info.binary_path = + exec_mod->GetFileSpec().GetPathAsConstString().GetCString(); + target_info.file_format = exec_mod->GetArchitecture().GetArchitectureName(); + target_info.target_uuid = exec_mod->GetUUID().GetAsString(); + if (auto err = llvm::sys::fs::file_size(exec_mod->GetFileSpec().GetPath(), + target_info.binary_size)) { + // If there was error obtaining it, just reset the size to 0. + // Maybe log the error too? + target_info.binary_size = 0; + } + EmitToDestinations(&target_info); +} + +void BasicTelemeter::LogMainExecutableLoadEnd(lldb::ModuleSP exec_mod, + TelemetryEventStats stats) { + TargetTelemetryInfo target_info = MakeBaseEntry<TargetTelemetryInfo>(); + target_info.stats = std::move(stats); + target_info.binary_path = + exec_mod->GetFileSpec().GetPathAsConstString().GetCString(); + target_info.file_format = exec_mod->GetArchitecture().GetArchitectureName(); + target_info.target_uuid = exec_mod->GetUUID().GetAsString(); + if (auto err = llvm::sys::fs::file_size(exec_mod->GetFileSpec().GetPath(), + target_info.binary_size)) { + // If there was error obtaining it, just reset the size to 0. + // Maybe log the error too? + target_info.binary_size = 0; + } + EmitToDestinations(&target_info); + + // Collect some more info, might be useful? + MiscTelemetryInfo misc_info = MakeBaseEntry<MiscTelemetryInfo>(); + misc_info.target_uuid = exec_mod->GetUUID().GetAsString(); + misc_info.meta_data["symtab_index_time"] = + std::to_string(exec_mod->GetSymtabIndexTime().get().count()); + misc_info.meta_data["symtab_parse_time"] = + std::to_string(exec_mod->GetSymtabParseTime().get().count()); + EmitToDestinations(&misc_info); +} + +void BasicTelemeter::LogClientTelemetry( + lldb_private::StructuredData::Object *entry) { + ClientTelemetryInfo client_info = MakeBaseEntry<ClientTelemetryInfo>(); + auto *dictionary = entry->GetAsDictionary(); + llvm::StringRef request_name; + if (!dictionary->GetValueForKeyAsString("request_name", request_name, "")) { + MiscTelemetryInfo misc_info = MakeBaseEntry<MiscTelemetryInfo>(); + misc_info.meta_data["internal_errors"] = + "Cannot determine request name from client entry"; + // TODO: Dump the errornous entry to stderr too? + EmitToDestinations(&misc_info); + return; + } + client_info.request_name = request_name.str(); + + size_t start_time; + size_t end_time; + if (!dictionary->GetValueForKeyAsInteger("start_time", start_time) || + !dictionary->GetValueForKeyAsInteger("end_time", end_time)) { + MiscTelemetryInfo misc_info = MakeBaseEntry<MiscTelemetryInfo>(); + misc_info.meta_data["internal_errors"] = + "Cannot determine start/end time from client entry"; + EmitToDestinations(&misc_info); + return; + } + + EmitToDestinations(&client_info); +} + +void BasicTelemeter::LogCommandStart(llvm::StringRef uuid, + llvm::StringRef original_command, + TelemetryEventStats stats, + Target *target_ptr) { + + lldb_private::CommandTelemetryInfo command_info = + MakeBaseEntry<lldb_private::CommandTelemetryInfo>(); + + // If we have a target attached to this command, then get the UUID. + command_info.target_uuid = ""; + if (target_ptr && target_ptr->GetExecutableModule() != nullptr) { + command_info.target_uuid = + target_ptr->GetExecutableModule()->GetUUID().GetAsString(); + } + command_info.command_uuid = uuid.str(); + command_info.original_command = original_command.str(); + command_info.stats = std::move(stats); + + EmitToDestinations(&command_info); +} + +void BasicTelemeter::LogCommandEnd(llvm::StringRef uuid, + llvm::StringRef command_name, + llvm::StringRef command_args, + TelemetryEventStats stats, + Target *target_ptr, + CommandReturnObject *result) { + + lldb_private::CommandTelemetryInfo command_info = + MakeBaseEntry<lldb_private::CommandTelemetryInfo>(); + + // If we have a target attached to this command, then get the UUID. + command_info.target_uuid = ""; + if (target_ptr && target_ptr->GetExecutableModule() != nullptr) { + command_info.target_uuid = + target_ptr->GetExecutableModule()->GetUUID().GetAsString(); + } + command_info.command_uuid = uuid.str(); + command_info.command_name = command_name.str(); + command_info.args = command_args.str(); + command_info.stats = std::move(stats); + command_info.exit_description = {result->Succeeded() ? 0 : -1, + StatusToString(result)}; + EmitToDestinations(&command_info); +} + +llvm::StringRef parse_value(llvm::StringRef str, llvm::StringRef label) { + return str.substr(label.size()).trim(); +} + +bool parse_field(llvm::StringRef str, llvm::StringRef label) { + if (parse_value(str, label) == "true") + return true; + return false; +} + +llvm::telemetry::TelemetryConfig *GetTelemetryConfig() { + static llvm::telemetry::TelemetryConfig *config = []() { + bool enable_telemetry = false; + std::vector<std::string> additional_destinations; + + // Look in the $HOME/.lldb_telemetry_config file to populate the struct + llvm::SmallString<64> init_file; + FileSystem::Instance().GetHomeDirectory(init_file); + llvm::sys::path::append(init_file, ".lldb_telemetry_config"); + FileSystem::Instance().Resolve(init_file); + if (llvm::sys::fs::exists(init_file)) { + auto contents = llvm::MemoryBuffer::getFile(init_file, /*IsText*/ true); + if (contents) { + llvm::line_iterator iter = + llvm::line_iterator(contents->get()->getMemBufferRef()); + for (; !iter.is_at_eof(); ++iter) { + if (iter->starts_with("enable_telemetry:")) { + enable_telemetry = parse_field(*iter, "enable_telemetry:"); + } else if (iter->starts_with("destination:")) { + llvm::StringRef dest = parse_value(*iter, "destination:"); + if (dest == "stdout") { + additional_destinations.push_back("stdout"); + } else if (dest == "stderr") { + additional_destinations.push_back("stderr"); + } else { + additional_destinations.push_back(dest.str()); + } + } + } + } else { + std::cerr << "Error reading config file at " << init_file.c_str() + << "\n"; + } + } + + auto *ret = new llvm::telemetry::TelemetryConfig{enable_telemetry, + additional_destinations}; + if (ApplyVendorSpecificConfigs) + ApplyVendorSpecificConfigs(ret); + + return ret; + }(); + return config; +} + +std::shared_ptr<LldbTelemeter> +LldbTelemeter::CreateInstance(lldb_private::Debugger *debugger) { + auto *config = GetTelemetryConfig(); + if (!config->enable_telemetry) { + return NoOpTelemeter::CreateInstance(debugger); + } + + if (CreateVendorSpecificTelemeter) + return CreateVendorSpecificTelemeter(config); + return BasicTelemeter::CreateInstance(debugger); +} +} // namespace lldb_private diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index fc07168b6c0ac..e60f9e7d3838d 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -12,6 +12,7 @@ #include <memory> #include <optional> #include <string> +#include <typeinfo> #include <vector> #include "Commands/CommandObjectApropos.h" @@ -56,7 +57,10 @@ #include "lldb/Utility/StructuredData.h" #include "lldb/Utility/Timer.h" +#include "lldb/Core/Telemetry.h" #include "lldb/Host/Config.h" +#include "lldb/Interpreter/CommandObject.h" + #if LLDB_ENABLE_LIBEDIT #include "lldb/Host/Editline.h" #endif @@ -1848,8 +1852,28 @@ bool CommandInterpreter::HandleCommand(const char *command_line, LazyBool lazy_add_to_history, CommandReturnObject &result, bool force_repeat_command) { + TelemetryEventStats command_stats(std::chrono::steady_clock::now()); + auto telemeter = GetDebugger().GetTelemeter(); + // Generate a UUID for this command so the logger can match + // the start/end entries correctly. + const std::string command_uuid = telemeter->GetNextUUID(); + + telemeter->LogCommandStart(command_uuid, command_line, command_stats, + GetExecutionContext().GetTargetPtr()); + std::string command_string(command_line); std::string original_command_string(command_line); + std::string parsed_command_args; + CommandObject *cmd_obj = nullptr; + + auto log_on_exit = llvm::make_scope_exit([&]() { + command_stats.m_end = std::chrono::steady_clock::now(); + llvm::StringRef command_name = + cmd_obj ? cmd_obj->GetCommandName() : "<not found>"; + telemeter->LogCommandEnd(command_uuid, command_name, parsed_command_args, + command_stats, + GetExecutionContext().GetTargetPtr(), &result); + }); Log *log = GetLog(LLDBLog::Commands); llvm::PrettyStackTraceFormat stack_trace("HandleCommand(command = \"%s\")", @@ -1887,11 +1911,10 @@ bool CommandInterpreter::HandleCommand(const char *command_line, bool empty_command = false; bool comment_command = false; - if (command_string.empty()) + if (command_string.empty()) { empty_command = true; - else { + } else { const char *k_space_characters = "\t\n\v\f\r "; - size_t non_space = command_string.find_first_not_of(k_space_characters); // Check for empty line or comment line (lines whose first non-space // character is the comment character for this interpreter) @@ -1954,7 +1977,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 @@ -2015,30 +2038,29 @@ 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 `GetSaveTrasncript()`. 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->Execute(remainder.c_str(), result); + cmd_obj->Execute(parsed_command_args.c_str(), result); } LLDB_LOGF(log, "HandleCommand, command %s", diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index dc7f6c9e86a47..ededeb9e8b28f 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include <atomic> +#include <chrono> #include <memory> #include <mutex> #include <optional> @@ -477,7 +478,8 @@ Process::Process(lldb::TargetSP target_sp, ListenerSP listener_sp, m_clear_thread_plans_on_stop(false), m_force_next_event_delivery(false), m_last_broadcast_state(eStateInvalid), m_destroy_in_process(false), m_can_interpret_function_calls(false), m_run_thread_plan_lock(), - m_can_jit(eCanJITDontKnow) { + m_can_jit(eCanJITDontKnow), + m_event_stats(std::chrono::steady_clock::now()) { CheckInWithManager(); Log *log = GetLog(LLDBLog::Object); @@ -1085,6 +1087,7 @@ bool Process::SetExitStatus(int status, llvm::StringRef exit_string) { // Use a mutex to protect setting the exit status. std::lock_guard<std::mutex> guard(m_exit_status_mutex); + m_event_stats.m_end = std::chrono::steady_clock::now(); Log *log(GetLog(LLDBLog::State | LLDBLog::Process)); LLDB_LOG(log, "(plugin = {0} status = {1} ({1:x8}), description=\"{2}\")", GetPluginName(), status, exit_string); @@ -1098,6 +1101,8 @@ bool Process::SetExitStatus(int status, llvm::StringRef exit_string) { GetPluginName()); return false; } + GetTarget().GetDebugger().GetTelemeter()->LogProcessExit( + status, exit_string, m_event_stats, &GetTarget()); m_exit_status = status; if (!exit_string.empty()) diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index ec0da8a1378a8..60bd544bd2a92 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -24,6 +24,7 @@ #include "lldb/Core/Section.h" #include "lldb/Core/SourceManager.h" #include "lldb/Core/StructuredDataImpl.h" +#include "lldb/Core/Telemetry.h" #include "lldb/Core/ValueObject.h" #include "lldb/Core/ValueObjectConstResult.h" #include "lldb/Expression/DiagnosticManager.h" @@ -67,6 +68,7 @@ #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/SetVector.h" +#include <chrono> #include <memory> #include <mutex> #include <optional> @@ -1470,11 +1472,22 @@ void Target::DidExec() { void Target::SetExecutableModule(ModuleSP &executable_sp, LoadDependentFiles load_dependent_files) { + Log *log = GetLog(LLDBLog::Target); ClearModules(false); - if (executable_sp) { + TelemetryEventStats load_executable_stats(std::chrono::steady_clock::now()); + m_debugger.GetTelemeter()->LogMainExecutableLoadStart( + executable_sp, load_executable_stats); + + auto log_on_exit = llvm::make_scope_exit([&]() { + load_executable_stats.m_end = std::chrono::steady_clock::now(); + m_debugger.GetTelemeter()->LogMainExecutableLoadEnd( + executable_sp, load_executable_stats); + }); + ElapsedTime elapsed(m_stats.GetCreateTime()); + LLDB_SCOPED_TIMERF("Target::SetExecutableModule (executable = '%s')", executable_sp->GetFileSpec().GetPath().c_str()); diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 0196aed819f2b..aa43d845ccfbe 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -6,6 +6,11 @@ // //===----------------------------------------------------------------------===// +#include "DAP.h" +#include "LLDBUtils.h" +#include "lldb/API/SBStructuredData.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/FormatVariadic.h" #include <chrono> #include <cstdarg> #include <fstream> @@ -14,7 +19,6 @@ #include "DAP.h" #include "LLDBUtils.h" -#include "lldb/API/SBCommandInterpreter.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/FormatVariadic.h" diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h new file mode 100644 index 0000000000000..e34b228b219c1 --- /dev/null +++ b/llvm/include/llvm/Telemetry/Telemetry.h @@ -0,0 +1,99 @@ +#ifndef LVM_TELEMETRY_TELEMETRY_H +#define LVM_TELEMETRY_TELEMETRY_H + +#include <chrono> +#include <ctime> +#include <memory> +#include <optional> +#include <string> + +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/JSON.h" + +namespace llvm { +namespace telemetry { + +using SteadyTimePoint = std::chrono::time_point<std::chrono::steady_clock>; + +struct TelemetryConfig { + // If true, telemetry will be enabled. + bool enable_telemetry; + + // Additional destinations to send the logged entries. + // Could be stdout, stderr, or some local paths. + // Note: these are destinations are __in addition to__ whatever the default + // destination(s) are, as implemented by vendors. + std::vector<std::string> additional_destinations; +}; + +struct TelemetryEventStats { + // REQUIRED: Start time of event + SteadyTimePoint m_start; + // OPTIONAL: End time of event - may be empty if not meaningful. + std::optional<SteadyTimePoint> m_end; + // TBD: could add some memory stats here too? + + TelemetryEventStats() = default; + TelemetryEventStats(SteadyTimePoint start) : m_start(start) {} + TelemetryEventStats(SteadyTimePoint start, SteadyTimePoint end) + : m_start(start), m_end(end) {} + + std::string ToString() const; +}; + +struct ExitDescription { + int exit_code; + std::string description; + + std::string ToString() const; +}; + +// The base class contains the basic set of data. +// Downstream implementations can add more fields as needed. +struct TelemetryInfo { + // A "session" corresponds to every time the tool starts. + // All entries emitted for the same session will have + // the same session_uuid + std::string session_uuid; + + TelemetryEventStats stats; + + std::optional<ExitDescription> exit_description; + + // Counting number of entries. + // (For each set of entries with the same session_uuid, this value should + // be unique for each entry) + size_t counter; + + TelemetryInfo() = default; + ~TelemetryInfo() = default; + virtual std::string ToString() const; +}; + +// Where/how to send the telemetry entries. +class TelemetryDestination { +public: + virtual ~TelemetryDestination() = default; + virtual Error EmitEntry(const TelemetryInfo *entry) = 0; + virtual std::string name() const = 0; +}; + +class Telemeter { +public: + virtual ~Telemeter() = default; + + // Invoked upon tool startup + virtual void LogStartup(llvm::StringRef tool_path, TelemetryInfo *entry) = 0; + + // Invoked upon tool exit. + virtual void LogExit(llvm::StringRef tool_path, TelemetryInfo *entry) = 0; + + virtual void AddDestination(TelemetryDestination *destination) = 0; +}; + +} // namespace telemetry +} // namespace llvm + +#endif // LVM_TELEMETRY_TELEMETRY_H diff --git a/llvm/lib/CMakeLists.txt b/llvm/lib/CMakeLists.txt index 638c3bd6f90f5..1d2fb32922648 100644 --- a/llvm/lib/CMakeLists.txt +++ b/llvm/lib/CMakeLists.txt @@ -41,6 +41,7 @@ add_subdirectory(ProfileData) add_subdirectory(Passes) add_subdirectory(TargetParser) add_subdirectory(TextAPI) +add_subdirectory(Telemetry) add_subdirectory(ToolDrivers) add_subdirectory(XRay) if (LLVM_INCLUDE_TESTS) diff --git a/llvm/lib/Telemetry/CMakeLists.txt b/llvm/lib/Telemetry/CMakeLists.txt new file mode 100644 index 0000000000000..8208bdadb05e9 --- /dev/null +++ b/llvm/lib/Telemetry/CMakeLists.txt @@ -0,0 +1,6 @@ +add_llvm_component_library(LLVMTelemetry + Telemetry.cpp + + ADDITIONAL_HEADER_DIRS + "${LLVM_MAIN_INCLUDE_DIR}/llvm/Telemetry" +) diff --git a/llvm/lib/Telemetry/Telemetry.cpp b/llvm/lib/Telemetry/Telemetry.cpp new file mode 100644 index 0000000000000..f7100685ee2d2 --- /dev/null +++ b/llvm/lib/Telemetry/Telemetry.cpp @@ -0,0 +1,32 @@ +#include "llvm/Telemetry/Telemetry.h" + +namespace llvm { +namespace telemetry { + +std::string TelemetryEventStats::ToString() const { + std::string result; + llvm::raw_string_ostream os(result); + os << "start_timestamp: " << m_start.time_since_epoch().count() + << ", end_timestamp: " + << (m_end.has_value() ? std::to_string(m_end->time_since_epoch().count()) + : "<NONE>"); + return result; +} + +std::string ExitDescription::ToString() const { + return "exit_code: " + std::to_string(exit_code) + + ", description: " + description + "\n"; +} + +std::string TelemetryInfo::ToString() const { + return "[TelemetryInfo]\n" + (" session_uuid:" + session_uuid + "\n") + + (" stats: " + stats.ToString() + "\n") + + (" exit_description: " + + (exit_description.has_value() ? exit_description->ToString() + : "<NONE>") + + "\n") + + (" counter: " + std::to_string(counter) + "\n"); +} + +} // namespace telemetry +} // namespace llvm \ No newline at end of file >From 09ac6e101e1988f650a72fcd5cce715defee1690 Mon Sep 17 00:00:00 2001 From: Vy Nguyen <v...@google.com> Date: Wed, 10 Jul 2024 15:27:38 -0400 Subject: [PATCH 2/2] [llvm]Added lib/Telemetry - Provide a base API for llvm Telemetry - Provide some concrete implementation for it in lldb/Telemetry --- lldb/include/lldb/API/SBDebugger.h | 4 + lldb/include/lldb/Core/Debugger.h | 8 + lldb/include/lldb/Core/Telemetry.h | 153 +++++ lldb/include/lldb/Target/Process.h | 3 + lldb/source/API/SBDebugger.cpp | 9 + lldb/source/Core/CMakeLists.txt | 2 + lldb/source/Core/Debugger.cpp | 31 +- lldb/source/Core/Telemetry.cpp | 618 ++++++++++++++++++ .../source/Interpreter/CommandInterpreter.cpp | 44 +- lldb/source/Target/Process.cpp | 7 +- lldb/source/Target/Target.cpp | 15 +- lldb/tools/lldb-dap/DAP.cpp | 6 +- llvm/include/llvm/Telemetry/Telemetry.h | 99 +++ llvm/lib/CMakeLists.txt | 1 + llvm/lib/Telemetry/CMakeLists.txt | 6 + llvm/lib/Telemetry/Telemetry.cpp | 32 + 16 files changed, 1023 insertions(+), 15 deletions(-) create mode 100644 lldb/include/lldb/Core/Telemetry.h create mode 100644 lldb/source/Core/Telemetry.cpp create mode 100644 llvm/include/llvm/Telemetry/Telemetry.h create mode 100644 llvm/lib/Telemetry/CMakeLists.txt create mode 100644 llvm/lib/Telemetry/Telemetry.cpp diff --git a/lldb/include/lldb/API/SBDebugger.h b/lldb/include/lldb/API/SBDebugger.h index 84ea9c0f772e1..de09995679ad9 100644 --- a/lldb/include/lldb/API/SBDebugger.h +++ b/lldb/include/lldb/API/SBDebugger.h @@ -9,10 +9,12 @@ #ifndef LLDB_API_SBDEBUGGER_H #define LLDB_API_SBDEBUGGER_H +#include <chrono> #include <cstdio> #include "lldb/API/SBDefines.h" #include "lldb/API/SBPlatform.h" +#include "lldb/API/SBStructuredData.h" namespace lldb_private { class CommandPluginInterfaceImplementation; @@ -245,6 +247,8 @@ class LLDB_API SBDebugger { lldb::SBTarget GetDummyTarget(); + void SendTelemetry(SBStructuredData *entry); + // Return true if target is deleted from the target list of the debugger. bool DeleteTarget(lldb::SBTarget &target); diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h index a72c2596cc2c5..13a444e438fec 100644 --- a/lldb/include/lldb/Core/Debugger.h +++ b/lldb/include/lldb/Core/Debugger.h @@ -19,6 +19,7 @@ #include "lldb/Core/FormatEntity.h" #include "lldb/Core/IOHandler.h" #include "lldb/Core/SourceManager.h" +#include "lldb/Core/Telemetry.h" #include "lldb/Core/UserSettingsController.h" #include "lldb/Host/HostThread.h" #include "lldb/Host/StreamFile.h" @@ -31,6 +32,7 @@ #include "lldb/Utility/Diagnostics.h" #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/Status.h" +#include "lldb/Utility/StructuredData.h" #include "lldb/Utility/UserID.h" #include "lldb/lldb-defines.h" #include "lldb/lldb-enumerations.h" @@ -137,6 +139,10 @@ class Debugger : public std::enable_shared_from_this<Debugger>, lldb::StreamFileSP GetErrorStreamSP() { return m_error_stream_sp; } + std::shared_ptr<LldbTelemeter> GetTelemeter() { return m_telemeter; } + + void SendClientTelemetry(lldb_private::StructuredData::Object *entry); + File &GetInputFile() { return *m_input_file_sp; } File &GetOutputFile() { return m_output_stream_sp->GetFile(); } @@ -754,6 +760,7 @@ class Debugger : public std::enable_shared_from_this<Debugger>, uint32_t m_interrupt_requested = 0; ///< Tracks interrupt requests std::mutex m_interrupt_mutex; + std::shared_ptr<LldbTelemeter> m_telemeter; // Events for m_sync_broadcaster enum { eBroadcastBitEventThreadIsListening = (1 << 0), @@ -766,6 +773,7 @@ class Debugger : public std::enable_shared_from_this<Debugger>, Debugger(const Debugger &) = delete; const Debugger &operator=(const Debugger &) = delete; + TelemetryEventStats stats; }; } // namespace lldb_private diff --git a/lldb/include/lldb/Core/Telemetry.h b/lldb/include/lldb/Core/Telemetry.h new file mode 100644 index 0000000000000..15cc3139764ad --- /dev/null +++ b/lldb/include/lldb/Core/Telemetry.h @@ -0,0 +1,153 @@ +#ifndef LLDB_CORE_TELEMETRY_H +#define LLDB_CORE_TELEMETRY_H + +#include <chrono> +#include <ctime> +#include <memory> +#include <optional> +#include <string> +#include <unordered_map> + +#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/Telemetry/Telemetry.h" + +using namespace llvm::telemetry; + +namespace lldb_private { + +struct DebuggerTelemetryInfo : public ::llvm::telemetry::TelemetryInfo { + std::string username; + std::string lldb_git_sha; + std::string lldb_path; + std::string cwd; + + std::string ToString() const override; +}; + +struct TargetTelemetryInfo : public ::llvm::telemetry::TelemetryInfo { + // All entries emitted for the same SBTarget will have the same + // target_uuid. + std::string target_uuid; + std::string file_format; + + std::string binary_path; + size_t binary_size; + + std::string ToString() const override; +}; + +// Entry from client (eg., SB-API) +struct ClientTelemetryInfo : public ::llvm::telemetry::TelemetryInfo { + std::string request_name; + std::string error_msg; + std::string ToString() const override; +}; + +struct CommandTelemetryInfo : public ::llvm::telemetry::TelemetryInfo { + // 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 TelemetryConfig struct) + std::string original_command; + std::string args; + + std::string ToString() const override; +}; + +// The "catch-all" entry to store a set of custom/non-standard +// data. +struct MiscTelemetryInfo : public ::llvm::telemetry::TelemetryInfo { + // 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::unordered_map<std::string, std::string> meta_data; + + std::string ToString() const override; +}; + +class LldbTelemeter : public llvm::telemetry::Telemeter { +public: + static std::shared_ptr<LldbTelemeter> CreateInstance(Debugger *); + + virtual ~LldbTelemeter() = default; + + // void LogStartup(llvm::StringRef lldb_path, + // TelemetryInfo *entry) override; + // void LogExit(llvm::StringRef lldb_path, TelemetryInfo *entry) + // override; + + // Invoked upon process exit + virtual void LogProcessExit(int status, llvm::StringRef exit_string, + TelemetryEventStats stats, + Target *target_ptr) = 0; + + // 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(lldb::ModuleSP exec_mod, + TelemetryEventStats stats) = 0; + virtual void LogMainExecutableLoadEnd(lldb::ModuleSP exec_mod, + TelemetryEventStats stats) = 0; + + // 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(llvm::StringRef uuid, + llvm::StringRef original_command, + TelemetryEventStats stats, + Target *target_ptr) = 0; + virtual void LogCommandEnd(llvm::StringRef uuid, llvm::StringRef command_name, + llvm::StringRef command_args, + TelemetryEventStats stats, Target *target_ptr, + CommandReturnObject *result) = 0; + + virtual std::string GetNextUUID() = 0; + + // For client (eg., SB API) to send telemetry entries. + virtual void + LogClientTelemetry(lldb_private::StructuredData::Object *entry) = 0; +}; + +// Logger configs: LLDB users can also supply their own configs via: +// $HOME/.lldb_telemetry_config +// +// We can propose simple syntax: <field_name><colon><value> +// Eg., +// enable_telemetry:true +// destination:stdout +// destination:stderr +// destination:/path/to/some/file +// +// The allowed field_name values are: +// * enable_telemetry +// If the fields are specified more than once, the last line will take +// precedence If enable_logging is set to false, no logging will occur. +// * destination. +// This is allowed to be specified multiple times - it will add to the +// default (ie, specified by vendor) list of destinations. +// The value can be either: +// + one of the two magic values "stdout" or "stderr". +// + a path to a local file +// !!NOTE!!: We decided to use a separate file instead of the existing settings +// file because that file is parsed too late in the process and by the +// there might have been lots of telemetry-entries that need to be +// sent already. +// This approach avoid losing log entries if LLDB crashes during init. +TelemetryConfig *GetTelemetryConfig(); + +} // namespace lldb_private +#endif // LLDB_CORE_TELEMETRY_H diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index ceaf547ebddaf..c35ff397a203f 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -28,6 +28,7 @@ #include "lldb/Core/LoadedModuleInfoList.h" #include "lldb/Core/PluginInterface.h" #include "lldb/Core/SourceManager.h" +#include "lldb/Core/Telemetry.h" #include "lldb/Core/ThreadSafeValue.h" #include "lldb/Core/ThreadedCommunication.h" #include "lldb/Core/UserSettingsController.h" @@ -3285,6 +3286,8 @@ void PruneThreadPlans(); Process(const Process &) = delete; const Process &operator=(const Process &) = delete; + + TelemetryEventStats m_event_stats; }; /// RAII guard that should be acquired when an utility function is called within diff --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp index fb035a36e7d74..3629fcc2574af 100644 --- a/lldb/source/API/SBDebugger.cpp +++ b/lldb/source/API/SBDebugger.cpp @@ -34,6 +34,7 @@ #include "lldb/API/SBTypeNameSpecifier.h" #include "lldb/API/SBTypeSummary.h" #include "lldb/API/SBTypeSynthetic.h" +#include <iostream> #include "lldb/Core/Debugger.h" #include "lldb/Core/DebuggerEvents.h" @@ -968,6 +969,14 @@ SBTarget SBDebugger::GetDummyTarget() { return sb_target; } +void SBDebugger::SendTelemetry(SBStructuredData *entry) { + if (lldb_private::Debugger *debugger = this->get()) { + debugger->SendClientTelemetry(entry->m_impl_up->GetObjectSP().get()); + } else { + std::cerr << " --- cannot send telemetry entry - debugger is null\n"; + } +} + bool SBDebugger::DeleteTarget(lldb::SBTarget &target) { LLDB_INSTRUMENT_VA(this, target); diff --git a/lldb/source/Core/CMakeLists.txt b/lldb/source/Core/CMakeLists.txt index dbc620b91b1ed..d7f8297fa555c 100644 --- a/lldb/source/Core/CMakeLists.txt +++ b/lldb/source/Core/CMakeLists.txt @@ -52,6 +52,7 @@ add_lldb_library(lldbCore SourceLocationSpec.cpp SourceManager.cpp StreamAsynchronousIO.cpp + Telemetry.cpp ThreadedCommunication.cpp UserSettingsController.cpp Value.cpp @@ -94,6 +95,7 @@ add_lldb_library(lldbCore Support Demangle TargetParser + Telemetry ) add_dependencies(lldbCore diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp index 309e01e456580..2947d2d537767 100644 --- a/lldb/source/Core/Debugger.cpp +++ b/lldb/source/Core/Debugger.cpp @@ -17,6 +17,7 @@ #include "lldb/Core/PluginManager.h" #include "lldb/Core/Progress.h" #include "lldb/Core/StreamAsynchronousIO.h" +#include "lldb/Core/Telemetry.h" #include "lldb/DataFormatters/DataVisualization.h" #include "lldb/Expression/REPL.h" #include "lldb/Host/File.h" @@ -53,6 +54,8 @@ #include "lldb/Utility/Stream.h" #include "lldb/Utility/StreamString.h" #include "lldb/lldb-enumerations.h" +#include "llvm/Telemetry/Telemetry.h" +#include <chrono> #if defined(_WIN32) #include "lldb/Host/windows/PosixApi.h" @@ -733,12 +736,20 @@ void Debugger::InstanceInitialize() { DebuggerSP Debugger::CreateInstance(lldb::LogOutputCallback log_callback, void *baton) { + SteadyTimePoint start_time = std::chrono::steady_clock::now(); DebuggerSP debugger_sp(new Debugger(log_callback, baton)); + debugger_sp->stats.m_start = start_time; if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { std::lock_guard<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr); g_debugger_list_ptr->push_back(debugger_sp); } debugger_sp->InstanceInitialize(); + TelemetryEventStats init_stats(start_time, std::chrono::steady_clock::now()); + llvm::telemetry::TelemetryInfo entry; + entry.stats = {start_time, std::chrono::steady_clock::now()}; + debugger_sp->m_telemeter->LogStartup( + HostInfo::GetProgramFileSpec().GetPathAsConstString().GetCString(), + &entry); return debugger_sp; } @@ -860,7 +871,8 @@ Debugger::Debugger(lldb::LogOutputCallback log_callback, void *baton) m_sync_broadcaster(nullptr, "lldb.debugger.sync"), m_broadcaster(m_broadcaster_manager_sp, GetStaticBroadcasterClass().str()), - m_forward_listener_sp(), m_clear_once() { + m_forward_listener_sp(), m_clear_once(), + m_telemeter(LldbTelemeter::CreateInstance(this)) { // Initialize the debugger properties as early as possible as other parts of // LLDB will start querying them during construction. m_collection_sp->Initialize(g_debugger_properties); @@ -952,6 +964,18 @@ void Debugger::Clear() { // static void Debugger::Destroy(lldb::DebuggerSP &debugger_sp); // static void Debugger::Terminate(); llvm::call_once(m_clear_once, [this]() { + // Log the "quit" event. + // Note: this session_stats include the time since LLDB starts till quit + // (now). + // TBD: we could also record stats for *just* the quit action, if needed? + // (ie., how long it takes to run all these cleanup functions?) + llvm::telemetry::TelemetryInfo entry; + entry.stats = {/*start_session*/ stats.m_start, + /*end_session*/ std::chrono::steady_clock::now()}; + m_telemeter->LogExit( + HostInfo::GetProgramFileSpec().GetPathAsConstString().GetCString(), + &entry); + ClearIOHandlers(); StopIOHandlerThread(); StopEventHandlerThread(); @@ -2231,6 +2255,11 @@ Status Debugger::RunREPL(LanguageType language, const char *repl_options) { return err; } +void Debugger::SendClientTelemetry( + lldb_private::StructuredData::Object *entry) { + m_telemeter->LogClientTelemetry(entry); +} + llvm::ThreadPoolInterface &Debugger::GetThreadPool() { assert(g_thread_pool && "Debugger::GetThreadPool called before Debugger::Initialize"); diff --git a/lldb/source/Core/Telemetry.cpp b/lldb/source/Core/Telemetry.cpp new file mode 100644 index 0000000000000..b8ea75906a919 --- /dev/null +++ b/lldb/source/Core/Telemetry.cpp @@ -0,0 +1,618 @@ + +//===-- 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 <stdbool.h> +#include <sys/auxv.h> + +#include <memory> + +#include <chrono> +#include <cstdlib> +#include <ctime> +#include <fstream> +#include <iostream> +#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/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/FileSystem.h" +#include "llvm/Support/LineIterator.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/RandomNumberGenerator.h" +#include "llvm/Telemetry/Telemetry.h" + +#ifdef HAS_VENDOR_TELEMETRY_PLUGINS +// TODO: could make this path a build-variable rather than hard-coded +#include "lldb/Core/VendorTelemetryPlugin.h" + +// This must include definitions for these functions +extern void ApplyVendorSpecificConfigs( + llvm::telemetry::TelemetryConfig *config) /* __attribute__((weak))*/; +extern std::shared_ptr<llvm::telemetry::TelemetryInfo> SanitizeSensitiveFields( + const llvm::telemetry::TelemetryInfo *entry) /*__attribute__((weak))*/; +extern std::shared_ptr<lldb_private::LldbTelemeter> +CreateVendorSpecificTelemeter( + llvm::telemetry::TelemetryConfig *config) /*__attribute__((weak))*/; +#endif + +namespace lldb_private { + +static std::string GetDuration(const TelemetryEventStats &stats) { + if (stats.m_end.has_value()) + return std::to_string((stats.m_end.value() - stats.m_start).count()) + + "(nanosec)"; + return "<NONE>"; +} + +std::string DebuggerTelemetryInfo::ToString() const { + std::string duration_desc = + (exit_description.has_value() ? " lldb session duration: " + : " lldb startup duration: ") + + std::to_string((stats.m_end.value() - stats.m_start).count()) + + "(nanosec)\n"; + + return TelemetryInfo::ToString() + "\n" + ("[DebuggerTelemetryInfo]\n") + + (" username: " + username + "\n") + + (" lldb_git_sha: " + lldb_git_sha + "\n") + + (" lldb_path: " + lldb_path + "\n") + (" cwd: " + cwd + "\n") + + duration_desc + "\n"; +} + +std::string ClientTelemetryInfo::ToString() const { + return TelemetryInfo::ToString() + "\n" + ("[DapRequestInfoEntry]\n") + + (" request_name: " + request_name + "\n") + + (" request_duration: " + GetDuration(stats) + "(nanosec)\n") + + (" error_msg: " + error_msg + "\n"); +} + +std::string TargetTelemetryInfo::ToString() const { + std::string exit_or_load_desc; + if (exit_description.has_value()) { + // If this entry was emitted for an exit + exit_or_load_desc = " process_duration: " + GetDuration(stats) + + " exit: " + exit_description->ToString() + "\n"; + } else { + // This was emitted for a load event. + // See if it was the start-load or end-load entry + if (stats.m_end.has_value()) { + exit_or_load_desc = + " startup_init_duration: " + GetDuration(stats) + "\n"; + } else { + exit_or_load_desc = " startup_init_start\n"; + } + } + return TelemetryInfo::ToString() + "\n" + ("[TargetTelemetryInfo]\n") + + (" target_uuid: " + target_uuid + "\n") + + (" file_format: " + file_format + "\n") + + (" binary_path: " + binary_path + "\n") + + (" binary_size: " + std::to_string(binary_size) + "\n") + + exit_or_load_desc; +} + +static std::string StatusToString(CommandReturnObject *result) { + std::string msg; + switch (result->GetStatus()) { + case lldb::eReturnStatusInvalid: + msg = "invalid"; + break; + case lldb::eReturnStatusSuccessFinishNoResult: + msg = "success_finish_no_result"; + break; + case lldb::eReturnStatusSuccessFinishResult: + msg = "success_finish_result"; + break; + case lldb::eReturnStatusSuccessContinuingNoResult: + msg = "success_continuing_no_result"; + break; + case lldb::eReturnStatusSuccessContinuingResult: + msg = "success_continuing_result"; + break; + case lldb::eReturnStatusStarted: + msg = "started"; + break; + case lldb::eReturnStatusFailed: + msg = "failed"; + break; + case lldb::eReturnStatusQuit: + msg = "quit"; + break; + } + if (llvm::StringRef error_data = result->GetErrorData(); + !error_data.empty()) { + msg += " Error msg: " + error_data.str(); + } + return msg; +} + +std::string CommandTelemetryInfo::ToString() const { + // Whether this entry was emitted at the start or at the end of the + // command-execution. + if (stats.m_end.has_value()) { + return TelemetryInfo::ToString() + "\n" + + ("[CommandTelemetryInfo] - END\n") + + (" target_uuid: " + target_uuid + "\n") + + (" command_uuid: " + command_uuid + "\n") + + (" command_name: " + command_name + "\n") + + (" args: " + args + "\n") + + (" command_runtime: " + GetDuration(stats) + "\n") + + (exit_description.has_value() ? exit_description->ToString() + : "no exit-description") + + "\n"; + } else { + return TelemetryInfo::ToString() + "\n" + + ("[CommandTelemetryInfo] - START\n") + + (" target_uuid: " + target_uuid + "\n") + + (" command_uuid: " + command_uuid + "\n") + + (" original_command: " + original_command + "\n"); + } +} + +std::string MiscTelemetryInfo::ToString() const { + std::string ret = + TelemetryInfo::ToString() + "\n" + ("[MiscTelemetryInfo]\n") + + (" target_uuid: " + target_uuid + "\n") + (" meta_data:\n"); + + for (const auto &kv : meta_data) { + ret += (" " + kv.first + ": " + kv.second + "\n"); + } + return ret; +} + +class StreamTelemetryDestination : public TelemetryDestination { +public: + StreamTelemetryDestination(std::ostream &os, std::string desc) + : os(os), desc(desc) {} + llvm::Error EmitEntry(const TelemetryInfo *entry) override { + // Unless there exists a custom (vendor-defined) data-cleanup + // for printing, upstream Telemetry should not leak anything other than the + // basic. +#ifdef HAS_TELEMETRY_FIELDS_PRINTER + os << SanitizeSensitiveFields(entry)->ToString() << "\n"; +#else + os << "session_uuid: " << entry->session_uuid + << "<the rest is omitted due to PII risk>\n"; +#endif + os.flush(); + return llvm::ErrorSuccess(); + } + + std::string name() const override { return desc; } + +private: + std::ostream &os; + const std::string desc; +}; + +// No-op logger to use when users disable telemetry +class NoOpTelemeter : public LldbTelemeter { +public: + static std::shared_ptr<LldbTelemeter> CreateInstance(Debugger *debugger) { + static std::shared_ptr<LldbTelemeter> ins(new NoOpTelemeter(debugger)); + return ins; + } + + NoOpTelemeter(Debugger *debugger) {} + void LogStartup(llvm::StringRef tool_path, TelemetryInfo *entry) override {} + void LogExit(llvm::StringRef tool_path, TelemetryInfo *entry) override {} + void LogProcessExit(int status, llvm::StringRef exit_string, + TelemetryEventStats stats, Target *target_ptr) override {} + void LogMainExecutableLoadStart(lldb::ModuleSP exec_mod, + TelemetryEventStats stats) override {} + void LogMainExecutableLoadEnd(lldb::ModuleSP exec_mod, + TelemetryEventStats stats) override {} + + void LogCommandStart(llvm::StringRef uuid, llvm::StringRef original_command, + TelemetryEventStats stats, Target *target_ptr) override { + } + void LogCommandEnd(llvm::StringRef uuid, llvm::StringRef command_name, + llvm::StringRef command_args, TelemetryEventStats stats, + Target *target_ptr, CommandReturnObject *result) override { + } + + void + LogClientTelemetry(lldb_private::StructuredData::Object *entry) override {} + + void AddDestination(TelemetryDestination *destination) override {} + std::string GetNextUUID() override { return ""; } +}; + +class BasicTelemeter : public LldbTelemeter { +public: + static std::shared_ptr<BasicTelemeter> CreateInstance(Debugger *); + + virtual ~BasicTelemeter() = default; + + void LogStartup(llvm::StringRef lldb_path, TelemetryInfo *entry) override; + void LogExit(llvm::StringRef lldb_path, TelemetryInfo *entry) override; + + void LogProcessExit(int status, llvm::StringRef exit_string, + TelemetryEventStats stats, Target *target_ptr) override; + void LogMainExecutableLoadStart(lldb::ModuleSP exec_mod, + TelemetryEventStats stats) override; + void LogMainExecutableLoadEnd(lldb::ModuleSP exec_mod, + TelemetryEventStats stats) override; + + void LogCommandStart(llvm::StringRef uuid, llvm::StringRef original_command, + TelemetryEventStats stats, Target *target_ptr) override; + void LogCommandEnd(llvm::StringRef uuid, llvm::StringRef command_name, + llvm::StringRef command_args, TelemetryEventStats stats, + Target *target_ptr, CommandReturnObject *result) override; + + void LogClientTelemetry(lldb_private::StructuredData::Object *entry) override; + + void AddDestination(TelemetryDestination *destination) override { + m_destinations.push_back(destination); + } + + std::string GetNextUUID() override { + return std::to_string(uuid_seed.fetch_add(1)); + } + +protected: + BasicTelemeter(Debugger *debugger); + + void CollectMiscBuildInfo(); + +private: + template <typename EntrySubType> EntrySubType MakeBaseEntry() { + EntrySubType entry; + entry.session_uuid = m_session_uuid; + entry.counter = counter.fetch_add(1); + return entry; + } + + void EmitToDestinations(const TelemetryInfo *entry); + + Debugger *m_debugger; + const std::string m_session_uuid; + std::string startup_lldb_path; + + // counting number of entries. + std::atomic<size_t> counter = 0; + + std::vector<TelemetryDestination *> m_destinations; + + std::atomic<size_t> uuid_seed = 0; +}; + +static std::string MakeUUID(lldb_private::Debugger *debugger) { + std::string ret; + uint8_t random_bytes[16]; + if (auto ec = llvm::getRandomBytes(random_bytes, 16)) { + std::cerr << "entropy source failure: " + 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; +} + +BasicTelemeter::BasicTelemeter(lldb_private::Debugger *debugger) + : m_debugger(debugger), m_session_uuid(MakeUUID(debugger)) {} + +std::shared_ptr<BasicTelemeter> +BasicTelemeter::CreateInstance(lldb_private::Debugger *debugger) { + auto *config = GetTelemetryConfig(); + + BasicTelemeter *ins = new BasicTelemeter(debugger); + for (const std ::string &dest : config->additional_destinations) { + if (dest == "stdout") { + ins->AddDestination(new StreamTelemetryDestination(std::cout, "stdout")); + } else if (dest == "stderr") { + ins->AddDestination(new StreamTelemetryDestination(std::cerr, "stderr")); + } else { + // TODO: handle file paths + } + } + + return std::shared_ptr<BasicTelemeter>(ins); +} + +void BasicTelemeter::EmitToDestinations(const TelemetryInfo *entry) { + // TODO: can do this in a separate thread (need to own the ptrs!). + for (auto destination : m_destinations) { + auto err = destination->EmitEntry(entry); + if (err) { + std::cerr << "error emitting to destination: " << destination->name() + << "\n"; + } + } +} + +void BasicTelemeter::LogStartup(llvm::StringRef lldb_path, + TelemetryInfo *entry) { + startup_lldb_path = lldb_path.str(); + lldb_private::DebuggerTelemetryInfo startup_info = + MakeBaseEntry<lldb_private::DebuggerTelemetryInfo>(); + + auto &resolver = lldb_private::HostInfo::GetUserIDResolver(); + auto opt_username = resolver.GetUserName(lldb_private::HostInfo::GetUserID()); + if (opt_username) + startup_info.username = *opt_username; + + startup_info.lldb_git_sha = lldb_private::GetVersion(); // TODO: fix this + startup_info.lldb_path = startup_lldb_path; + startup_info.stats = entry->stats; + + llvm::SmallString<64> cwd; + if (!llvm::sys::fs::current_path(cwd)) { + startup_info.cwd = cwd.c_str(); + } else { + MiscTelemetryInfo misc_info = MakeBaseEntry<MiscTelemetryInfo>(); + misc_info.meta_data["internal_errors"] = "Cannot determine CWD"; + EmitToDestinations(&misc_info); + } + + std::cout << "emitting startup info\n"; + EmitToDestinations(&startup_info); + + // Optional part + CollectMiscBuildInfo(); +} + +void BasicTelemeter::LogExit(llvm::StringRef lldb_path, TelemetryInfo *entry) { + std::cout << "debugger exiting at " << lldb_path.str() << "\n"; + // we should be shutting down the same instance that we started?! + // llvm::Assert(startup_lldb_path == lldb_path.str()); + + lldb_private::DebuggerTelemetryInfo exit_info = + MakeBaseEntry<lldb_private::DebuggerTelemetryInfo>(); + exit_info.stats = entry->stats; + exit_info.lldb_path = startup_lldb_path; + 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. + exit_info.exit_description = {-1, "no process launched."}; + } else { + exit_info.exit_description = {proc->GetExitStatus(), ""}; + if (const char *description = proc->GetExitDescription()) + exit_info.exit_description->description = std::string(description); + } + } + } + EmitToDestinations(&exit_info); +} + +void BasicTelemeter::LogProcessExit(int status, llvm::StringRef exit_string, + TelemetryEventStats stats, + Target *target_ptr) { + lldb_private::TargetTelemetryInfo exit_info = + MakeBaseEntry<lldb_private::TargetTelemetryInfo>(); + exit_info.stats = stats; + exit_info.target_uuid = + target_ptr && !target_ptr->IsDummyTarget() + ? target_ptr->GetExecutableModule()->GetUUID().GetAsString() + : ""; + exit_info.exit_description = {status, exit_string.str()}; + + std::cout << "emitting process exit ...\n"; + EmitToDestinations(&exit_info); +} + +void BasicTelemeter::CollectMiscBuildInfo() { + // collecting use-case specific data +} + +void BasicTelemeter::LogMainExecutableLoadStart(lldb::ModuleSP exec_mod, + TelemetryEventStats stats) { + TargetTelemetryInfo target_info = MakeBaseEntry<TargetTelemetryInfo>(); + target_info.stats = std::move(stats); + target_info.binary_path = + exec_mod->GetFileSpec().GetPathAsConstString().GetCString(); + target_info.file_format = exec_mod->GetArchitecture().GetArchitectureName(); + target_info.target_uuid = exec_mod->GetUUID().GetAsString(); + if (auto err = llvm::sys::fs::file_size(exec_mod->GetFileSpec().GetPath(), + target_info.binary_size)) { + // If there was error obtaining it, just reset the size to 0. + // Maybe log the error too? + target_info.binary_size = 0; + } + EmitToDestinations(&target_info); +} + +void BasicTelemeter::LogMainExecutableLoadEnd(lldb::ModuleSP exec_mod, + TelemetryEventStats stats) { + TargetTelemetryInfo target_info = MakeBaseEntry<TargetTelemetryInfo>(); + target_info.stats = std::move(stats); + target_info.binary_path = + exec_mod->GetFileSpec().GetPathAsConstString().GetCString(); + target_info.file_format = exec_mod->GetArchitecture().GetArchitectureName(); + target_info.target_uuid = exec_mod->GetUUID().GetAsString(); + if (auto err = llvm::sys::fs::file_size(exec_mod->GetFileSpec().GetPath(), + target_info.binary_size)) { + // If there was error obtaining it, just reset the size to 0. + // Maybe log the error too? + target_info.binary_size = 0; + } + EmitToDestinations(&target_info); + + // Collect some more info, might be useful? + MiscTelemetryInfo misc_info = MakeBaseEntry<MiscTelemetryInfo>(); + misc_info.target_uuid = exec_mod->GetUUID().GetAsString(); + misc_info.meta_data["symtab_index_time"] = + std::to_string(exec_mod->GetSymtabIndexTime().get().count()); + misc_info.meta_data["symtab_parse_time"] = + std::to_string(exec_mod->GetSymtabParseTime().get().count()); + EmitToDestinations(&misc_info); +} + +void BasicTelemeter::LogClientTelemetry( + lldb_private::StructuredData::Object *entry) { + ClientTelemetryInfo client_info = MakeBaseEntry<ClientTelemetryInfo>(); + auto *dictionary = entry->GetAsDictionary(); + llvm::StringRef request_name; + if (!dictionary->GetValueForKeyAsString("request_name", request_name, "")) { + MiscTelemetryInfo misc_info = MakeBaseEntry<MiscTelemetryInfo>(); + misc_info.meta_data["internal_errors"] = + "Cannot determine request name from client entry"; + // TODO: Dump the errornous entry to stderr too? + EmitToDestinations(&misc_info); + return; + } + client_info.request_name = request_name.str(); + + size_t start_time; + size_t end_time; + if (!dictionary->GetValueForKeyAsInteger("start_time", start_time) || + !dictionary->GetValueForKeyAsInteger("end_time", end_time)) { + MiscTelemetryInfo misc_info = MakeBaseEntry<MiscTelemetryInfo>(); + misc_info.meta_data["internal_errors"] = + "Cannot determine start/end time from client entry"; + EmitToDestinations(&misc_info); + return; + } + + EmitToDestinations(&client_info); +} + +void BasicTelemeter::LogCommandStart(llvm::StringRef uuid, + llvm::StringRef original_command, + TelemetryEventStats stats, + Target *target_ptr) { + + lldb_private::CommandTelemetryInfo command_info = + MakeBaseEntry<lldb_private::CommandTelemetryInfo>(); + + // If we have a target attached to this command, then get the UUID. + command_info.target_uuid = ""; + if (target_ptr && target_ptr->GetExecutableModule() != nullptr) { + command_info.target_uuid = + target_ptr->GetExecutableModule()->GetUUID().GetAsString(); + } + command_info.command_uuid = uuid.str(); + command_info.original_command = original_command.str(); + command_info.stats = std::move(stats); + + EmitToDestinations(&command_info); +} + +void BasicTelemeter::LogCommandEnd(llvm::StringRef uuid, + llvm::StringRef command_name, + llvm::StringRef command_args, + TelemetryEventStats stats, + Target *target_ptr, + CommandReturnObject *result) { + + lldb_private::CommandTelemetryInfo command_info = + MakeBaseEntry<lldb_private::CommandTelemetryInfo>(); + + // If we have a target attached to this command, then get the UUID. + command_info.target_uuid = ""; + if (target_ptr && target_ptr->GetExecutableModule() != nullptr) { + command_info.target_uuid = + target_ptr->GetExecutableModule()->GetUUID().GetAsString(); + } + command_info.command_uuid = uuid.str(); + command_info.command_name = command_name.str(); + command_info.args = command_args.str(); + command_info.stats = std::move(stats); + command_info.exit_description = {result->Succeeded() ? 0 : -1, + StatusToString(result)}; + EmitToDestinations(&command_info); +} + +llvm::StringRef parse_value(llvm::StringRef str, llvm::StringRef label) { + return str.substr(label.size()).trim(); +} + +bool parse_field(llvm::StringRef str, llvm::StringRef label) { + if (parse_value(str, label) == "true") + return true; + return false; +} + +llvm::telemetry::TelemetryConfig *MakeTelemetryConfig() { + bool enable_telemetry = false; + std::vector<std::string> additional_destinations; + + // Look in the $HOME/.lldb_telemetry_config file to populate the struct + llvm::SmallString<64> init_file; + FileSystem::Instance().GetHomeDirectory(init_file); + llvm::sys::path::append(init_file, ".lldb_telemetry_config"); + FileSystem::Instance().Resolve(init_file); + if (llvm::sys::fs::exists(init_file)) { + auto contents = llvm::MemoryBuffer::getFile(init_file, /*IsText*/ true); + if (contents) { + llvm::line_iterator iter = + llvm::line_iterator(contents->get()->getMemBufferRef()); + for (; !iter.is_at_eof(); ++iter) { + if (iter->starts_with("enable_telemetry:")) { + enable_telemetry = parse_field(*iter, "enable_telemetry:"); + } else if (iter->starts_with("destination:")) { + llvm::StringRef dest = parse_value(*iter, "destination:"); + if (dest == "stdout") { + additional_destinations.push_back("stdout"); + } else if (dest == "stderr") { + additional_destinations.push_back("stderr"); + } else { + additional_destinations.push_back(dest.str()); + } + } + } + } else { + std::cerr << "Error reading config file at " << init_file.c_str() << "\n"; + } + } + + auto *ret = new llvm::telemetry::TelemetryConfig{enable_telemetry, + additional_destinations}; +#ifdef HAS_VENDOR_TELEMETRY_CONFIG + ApplyVendorSpecificConfigs(ret); +#endif + return ret; +} + +llvm::telemetry::TelemetryConfig *GetTelemetryConfig() { + static llvm::telemetry::TelemetryConfig *config = MakeTelemetryConfig(); + return config; +} + +std::shared_ptr<LldbTelemeter> +LldbTelemeter::CreateInstance(lldb_private::Debugger *debugger) { + auto *config = GetTelemetryConfig(); + if (!config->enable_telemetry) { + return NoOpTelemeter::CreateInstance(debugger); + } + +#ifdef HAS_VENDOR_TELEMETER + return CreateVendorSpecificTelemeter(config); +#else + return BasicTelemeter::CreateInstance(debugger); +#endif +} +} // namespace lldb_private diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index fc07168b6c0ac..e60f9e7d3838d 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -12,6 +12,7 @@ #include <memory> #include <optional> #include <string> +#include <typeinfo> #include <vector> #include "Commands/CommandObjectApropos.h" @@ -56,7 +57,10 @@ #include "lldb/Utility/StructuredData.h" #include "lldb/Utility/Timer.h" +#include "lldb/Core/Telemetry.h" #include "lldb/Host/Config.h" +#include "lldb/Interpreter/CommandObject.h" + #if LLDB_ENABLE_LIBEDIT #include "lldb/Host/Editline.h" #endif @@ -1848,8 +1852,28 @@ bool CommandInterpreter::HandleCommand(const char *command_line, LazyBool lazy_add_to_history, CommandReturnObject &result, bool force_repeat_command) { + TelemetryEventStats command_stats(std::chrono::steady_clock::now()); + auto telemeter = GetDebugger().GetTelemeter(); + // Generate a UUID for this command so the logger can match + // the start/end entries correctly. + const std::string command_uuid = telemeter->GetNextUUID(); + + telemeter->LogCommandStart(command_uuid, command_line, command_stats, + GetExecutionContext().GetTargetPtr()); + std::string command_string(command_line); std::string original_command_string(command_line); + std::string parsed_command_args; + CommandObject *cmd_obj = nullptr; + + auto log_on_exit = llvm::make_scope_exit([&]() { + command_stats.m_end = std::chrono::steady_clock::now(); + llvm::StringRef command_name = + cmd_obj ? cmd_obj->GetCommandName() : "<not found>"; + telemeter->LogCommandEnd(command_uuid, command_name, parsed_command_args, + command_stats, + GetExecutionContext().GetTargetPtr(), &result); + }); Log *log = GetLog(LLDBLog::Commands); llvm::PrettyStackTraceFormat stack_trace("HandleCommand(command = \"%s\")", @@ -1887,11 +1911,10 @@ bool CommandInterpreter::HandleCommand(const char *command_line, bool empty_command = false; bool comment_command = false; - if (command_string.empty()) + if (command_string.empty()) { empty_command = true; - else { + } else { const char *k_space_characters = "\t\n\v\f\r "; - size_t non_space = command_string.find_first_not_of(k_space_characters); // Check for empty line or comment line (lines whose first non-space // character is the comment character for this interpreter) @@ -1954,7 +1977,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 @@ -2015,30 +2038,29 @@ 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 `GetSaveTrasncript()`. 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->Execute(remainder.c_str(), result); + cmd_obj->Execute(parsed_command_args.c_str(), result); } LLDB_LOGF(log, "HandleCommand, command %s", diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index dc7f6c9e86a47..ededeb9e8b28f 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include <atomic> +#include <chrono> #include <memory> #include <mutex> #include <optional> @@ -477,7 +478,8 @@ Process::Process(lldb::TargetSP target_sp, ListenerSP listener_sp, m_clear_thread_plans_on_stop(false), m_force_next_event_delivery(false), m_last_broadcast_state(eStateInvalid), m_destroy_in_process(false), m_can_interpret_function_calls(false), m_run_thread_plan_lock(), - m_can_jit(eCanJITDontKnow) { + m_can_jit(eCanJITDontKnow), + m_event_stats(std::chrono::steady_clock::now()) { CheckInWithManager(); Log *log = GetLog(LLDBLog::Object); @@ -1085,6 +1087,7 @@ bool Process::SetExitStatus(int status, llvm::StringRef exit_string) { // Use a mutex to protect setting the exit status. std::lock_guard<std::mutex> guard(m_exit_status_mutex); + m_event_stats.m_end = std::chrono::steady_clock::now(); Log *log(GetLog(LLDBLog::State | LLDBLog::Process)); LLDB_LOG(log, "(plugin = {0} status = {1} ({1:x8}), description=\"{2}\")", GetPluginName(), status, exit_string); @@ -1098,6 +1101,8 @@ bool Process::SetExitStatus(int status, llvm::StringRef exit_string) { GetPluginName()); return false; } + GetTarget().GetDebugger().GetTelemeter()->LogProcessExit( + status, exit_string, m_event_stats, &GetTarget()); m_exit_status = status; if (!exit_string.empty()) diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index ec0da8a1378a8..60bd544bd2a92 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -24,6 +24,7 @@ #include "lldb/Core/Section.h" #include "lldb/Core/SourceManager.h" #include "lldb/Core/StructuredDataImpl.h" +#include "lldb/Core/Telemetry.h" #include "lldb/Core/ValueObject.h" #include "lldb/Core/ValueObjectConstResult.h" #include "lldb/Expression/DiagnosticManager.h" @@ -67,6 +68,7 @@ #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/SetVector.h" +#include <chrono> #include <memory> #include <mutex> #include <optional> @@ -1470,11 +1472,22 @@ void Target::DidExec() { void Target::SetExecutableModule(ModuleSP &executable_sp, LoadDependentFiles load_dependent_files) { + Log *log = GetLog(LLDBLog::Target); ClearModules(false); - if (executable_sp) { + TelemetryEventStats load_executable_stats(std::chrono::steady_clock::now()); + m_debugger.GetTelemeter()->LogMainExecutableLoadStart( + executable_sp, load_executable_stats); + + auto log_on_exit = llvm::make_scope_exit([&]() { + load_executable_stats.m_end = std::chrono::steady_clock::now(); + m_debugger.GetTelemeter()->LogMainExecutableLoadEnd( + executable_sp, load_executable_stats); + }); + ElapsedTime elapsed(m_stats.GetCreateTime()); + LLDB_SCOPED_TIMERF("Target::SetExecutableModule (executable = '%s')", executable_sp->GetFileSpec().GetPath().c_str()); diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 0196aed819f2b..aa43d845ccfbe 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -6,6 +6,11 @@ // //===----------------------------------------------------------------------===// +#include "DAP.h" +#include "LLDBUtils.h" +#include "lldb/API/SBStructuredData.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/FormatVariadic.h" #include <chrono> #include <cstdarg> #include <fstream> @@ -14,7 +19,6 @@ #include "DAP.h" #include "LLDBUtils.h" -#include "lldb/API/SBCommandInterpreter.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/FormatVariadic.h" diff --git a/llvm/include/llvm/Telemetry/Telemetry.h b/llvm/include/llvm/Telemetry/Telemetry.h new file mode 100644 index 0000000000000..e34b228b219c1 --- /dev/null +++ b/llvm/include/llvm/Telemetry/Telemetry.h @@ -0,0 +1,99 @@ +#ifndef LVM_TELEMETRY_TELEMETRY_H +#define LVM_TELEMETRY_TELEMETRY_H + +#include <chrono> +#include <ctime> +#include <memory> +#include <optional> +#include <string> + +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/JSON.h" + +namespace llvm { +namespace telemetry { + +using SteadyTimePoint = std::chrono::time_point<std::chrono::steady_clock>; + +struct TelemetryConfig { + // If true, telemetry will be enabled. + bool enable_telemetry; + + // Additional destinations to send the logged entries. + // Could be stdout, stderr, or some local paths. + // Note: these are destinations are __in addition to__ whatever the default + // destination(s) are, as implemented by vendors. + std::vector<std::string> additional_destinations; +}; + +struct TelemetryEventStats { + // REQUIRED: Start time of event + SteadyTimePoint m_start; + // OPTIONAL: End time of event - may be empty if not meaningful. + std::optional<SteadyTimePoint> m_end; + // TBD: could add some memory stats here too? + + TelemetryEventStats() = default; + TelemetryEventStats(SteadyTimePoint start) : m_start(start) {} + TelemetryEventStats(SteadyTimePoint start, SteadyTimePoint end) + : m_start(start), m_end(end) {} + + std::string ToString() const; +}; + +struct ExitDescription { + int exit_code; + std::string description; + + std::string ToString() const; +}; + +// The base class contains the basic set of data. +// Downstream implementations can add more fields as needed. +struct TelemetryInfo { + // A "session" corresponds to every time the tool starts. + // All entries emitted for the same session will have + // the same session_uuid + std::string session_uuid; + + TelemetryEventStats stats; + + std::optional<ExitDescription> exit_description; + + // Counting number of entries. + // (For each set of entries with the same session_uuid, this value should + // be unique for each entry) + size_t counter; + + TelemetryInfo() = default; + ~TelemetryInfo() = default; + virtual std::string ToString() const; +}; + +// Where/how to send the telemetry entries. +class TelemetryDestination { +public: + virtual ~TelemetryDestination() = default; + virtual Error EmitEntry(const TelemetryInfo *entry) = 0; + virtual std::string name() const = 0; +}; + +class Telemeter { +public: + virtual ~Telemeter() = default; + + // Invoked upon tool startup + virtual void LogStartup(llvm::StringRef tool_path, TelemetryInfo *entry) = 0; + + // Invoked upon tool exit. + virtual void LogExit(llvm::StringRef tool_path, TelemetryInfo *entry) = 0; + + virtual void AddDestination(TelemetryDestination *destination) = 0; +}; + +} // namespace telemetry +} // namespace llvm + +#endif // LVM_TELEMETRY_TELEMETRY_H diff --git a/llvm/lib/CMakeLists.txt b/llvm/lib/CMakeLists.txt index 638c3bd6f90f5..1d2fb32922648 100644 --- a/llvm/lib/CMakeLists.txt +++ b/llvm/lib/CMakeLists.txt @@ -41,6 +41,7 @@ add_subdirectory(ProfileData) add_subdirectory(Passes) add_subdirectory(TargetParser) add_subdirectory(TextAPI) +add_subdirectory(Telemetry) add_subdirectory(ToolDrivers) add_subdirectory(XRay) if (LLVM_INCLUDE_TESTS) diff --git a/llvm/lib/Telemetry/CMakeLists.txt b/llvm/lib/Telemetry/CMakeLists.txt new file mode 100644 index 0000000000000..8208bdadb05e9 --- /dev/null +++ b/llvm/lib/Telemetry/CMakeLists.txt @@ -0,0 +1,6 @@ +add_llvm_component_library(LLVMTelemetry + Telemetry.cpp + + ADDITIONAL_HEADER_DIRS + "${LLVM_MAIN_INCLUDE_DIR}/llvm/Telemetry" +) diff --git a/llvm/lib/Telemetry/Telemetry.cpp b/llvm/lib/Telemetry/Telemetry.cpp new file mode 100644 index 0000000000000..f7100685ee2d2 --- /dev/null +++ b/llvm/lib/Telemetry/Telemetry.cpp @@ -0,0 +1,32 @@ +#include "llvm/Telemetry/Telemetry.h" + +namespace llvm { +namespace telemetry { + +std::string TelemetryEventStats::ToString() const { + std::string result; + llvm::raw_string_ostream os(result); + os << "start_timestamp: " << m_start.time_since_epoch().count() + << ", end_timestamp: " + << (m_end.has_value() ? std::to_string(m_end->time_since_epoch().count()) + : "<NONE>"); + return result; +} + +std::string ExitDescription::ToString() const { + return "exit_code: " + std::to_string(exit_code) + + ", description: " + description + "\n"; +} + +std::string TelemetryInfo::ToString() const { + return "[TelemetryInfo]\n" + (" session_uuid:" + session_uuid + "\n") + + (" stats: " + stats.ToString() + "\n") + + (" exit_description: " + + (exit_description.has_value() ? exit_description->ToString() + : "<NONE>") + + "\n") + + (" counter: " + std::to_string(counter) + "\n"); +} + +} // namespace telemetry +} // namespace llvm \ No newline at end of file _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits