From a088b0ec7653f12e60d01959bc71ea4f7fd206f0 Mon Sep 17 00:00:00 2001 From: Vy Nguyen Date: Mon, 3 Mar 2025 10:07:54 -0500 Subject: [PATCH] [LLDB][Telemetry]Define DebuggerTelemetryInfo and related methods (#127696) This type of entry is used to collect data about the debugger startup/exit Also introduced a helper ScopedDispatcher --------- Co-authored-by: Jonas Devlieghere Co-authored-by: Pavel Labath --- lldb/include/lldb/Core/Telemetry.h | 81 ++++++++++++++++++++++++++- lldb/source/Core/Debugger.cpp | 17 ++++++ lldb/source/Core/Telemetry.cpp | 63 +++++++++++++++------ lldb/unittests/Core/TelemetryTest.cpp | 60 +++++++++++++++++--- 4 files changed, 195 insertions(+), 26 deletions(-) diff --git a/lldb/include/lldb/Core/Telemetry.h b/lldb/include/lldb/Core/Telemetry.h index 3bf38dac04c3d..7d8716f1659b5 100644 --- a/lldb/include/lldb/Core/Telemetry.h +++ b/lldb/include/lldb/Core/Telemetry.h @@ -11,8 +11,10 @@ #include "lldb/Core/StructuredDataImpl.h" #include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/StructuredData.h" #include "lldb/lldb-forward.h" +#include "llvm/ADT/FunctionExtras.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/JSON.h" @@ -22,13 +24,20 @@ #include #include #include -#include namespace lldb_private { namespace telemetry { +// We expect each (direct) subclass of LLDBTelemetryInfo to +// have an LLDBEntryKind in the form 0b11xxxxxxxx +// Specifically: +// - Length: 8 bits +// - First two bits (MSB) must be 11 - the common prefix +// If any of the subclass has descendents, those descendents +// must have their LLDBEntryKind in the similar form (ie., share common prefix) struct LLDBEntryKind : public ::llvm::telemetry::EntryKind { - static const llvm::telemetry::KindType BaseInfo = 0b11000; + static const llvm::telemetry::KindType BaseInfo = 0b11000000; + static const llvm::telemetry::KindType DebuggerInfo = 0b11000100; }; /// Defines a convenient type for timestamp of various events. @@ -41,6 +50,7 @@ struct LLDBBaseTelemetryInfo : public llvm::telemetry::TelemetryInfo { std::optional end_time; // TBD: could add some memory stats here too? + lldb::user_id_t debugger_id = LLDB_INVALID_UID; Debugger *debugger; // For dyn_cast, isa, etc operations. @@ -56,6 +66,26 @@ struct LLDBBaseTelemetryInfo : public llvm::telemetry::TelemetryInfo { void serialize(llvm::telemetry::Serializer &serializer) const override; }; +struct DebuggerInfo : public LLDBBaseTelemetryInfo { + std::string lldb_version; + + bool is_exit_entry = false; + + DebuggerInfo() = default; + + llvm::telemetry::KindType getKind() const override { + return LLDBEntryKind::DebuggerInfo; + } + + static bool classof(const llvm::telemetry::TelemetryInfo *T) { + // Subclasses of this is also acceptable + return (T->getKind() & LLDBEntryKind::DebuggerInfo) == + LLDBEntryKind::DebuggerInfo; + } + + void serialize(llvm::telemetry::Serializer &serializer) const override; +}; + /// The base Telemetry manager instance in LLDB. /// This class declares additional instrumentation points /// applicable to LLDB. @@ -63,9 +93,14 @@ class TelemetryManager : public llvm::telemetry::Manager { public: llvm::Error preDispatch(llvm::telemetry::TelemetryInfo *entry) override; + const llvm::telemetry::Config *GetConfig(); + virtual llvm::StringRef GetInstanceName() const = 0; + static TelemetryManager *GetInstance(); + static TelemetryManager *GetInstanceIfEnabled(); + protected: TelemetryManager(std::unique_ptr config); @@ -73,9 +108,51 @@ class TelemetryManager : public llvm::telemetry::Manager { private: std::unique_ptr m_config; + // Each instance of a TelemetryManager is assigned a unique ID. + const std::string m_id; + static std::unique_ptr g_instance; }; +/// Helper RAII class for collecting telemetry. +template struct ScopedDispatcher { + // The debugger pointer is optional because we may not have a debugger yet. + // In that case, caller must set the debugger later. + ScopedDispatcher(llvm::unique_function callback, + Debugger *debugger = nullptr) + : m_callback(std::move(callback)) { + // Start the timer. + m_start_time = std::chrono::steady_clock::now(); + m_info.debugger = debugger; + } + + void SetDebugger(Debugger *debugger) { m_info.debugger = debugger; } + + ~ScopedDispatcher() { + // If Telemetry is disabled (either at buildtime or runtime), + // then don't do anything. + TelemetryManager *manager = TelemetryManager::GetInstanceIfEnabled(); + if (!manager) + return; + + m_info.start_time = m_start_time; + // Populate common fields that we can only set now. + m_info.end_time = std::chrono::steady_clock::now(); + // The callback will set the remaining fields. + m_callback(&m_info); + // And then we dispatch. + if (llvm::Error er = manager->dispatch(&m_info)) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Object), std::move(er), + "Failed to dispatch entry of type: {0}", m_info.getKind()); + } + } + +private: + SteadyTimePoint m_start_time; + llvm::unique_function m_callback; + Info m_info; +}; + } // namespace telemetry } // namespace lldb_private #endif // LLDB_CORE_TELEMETRY_H diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp index 585138535203d..68d1bd30cc5b0 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" @@ -52,6 +53,7 @@ #include "lldb/Utility/State.h" #include "lldb/Utility/Stream.h" #include "lldb/Utility/StreamString.h" +#include "lldb/Version/Version.h" #include "lldb/lldb-enumerations.h" #if defined(_WIN32) @@ -62,6 +64,7 @@ #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/iterator.h" +#include "llvm/Config/llvm-config.h" #include "llvm/Support/DynamicLibrary.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Process.h" @@ -69,6 +72,7 @@ #include "llvm/Support/Threading.h" #include "llvm/Support/raw_ostream.h" +#include #include #include #include @@ -761,7 +765,14 @@ void Debugger::InstanceInitialize() { DebuggerSP Debugger::CreateInstance(lldb::LogOutputCallback log_callback, void *baton) { + lldb_private::telemetry::ScopedDispatcher< + lldb_private::telemetry::DebuggerInfo> + helper([](lldb_private::telemetry::DebuggerInfo *entry) { + entry->lldb_version = lldb_private::GetVersion(); + }); DebuggerSP debugger_sp(new Debugger(log_callback, baton)); + helper.SetDebugger(debugger_sp.get()); + if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { std::lock_guard guard(*g_debugger_list_mutex_ptr); g_debugger_list_ptr->push_back(debugger_sp); @@ -987,6 +998,12 @@ void Debugger::Clear() { // static void Debugger::Destroy(lldb::DebuggerSP &debugger_sp); // static void Debugger::Terminate(); llvm::call_once(m_clear_once, [this]() { + telemetry::ScopedDispatcher helper( + [this](lldb_private::telemetry::DebuggerInfo *info) { + assert(this == info->debugger); + info->is_exit_entry = true; + }, + this); ClearIOHandlers(); StopIOHandlerThread(); StopEventHandlerThread(); diff --git a/lldb/source/Core/Telemetry.cpp b/lldb/source/Core/Telemetry.cpp index fef63e3696e70..367e94d6566de 100644 --- a/lldb/source/Core/Telemetry.cpp +++ b/lldb/source/Core/Telemetry.cpp @@ -7,8 +7,10 @@ //===----------------------------------------------------------------------===// #include "lldb/Core/Telemetry.h" #include "lldb/Core/Debugger.h" +#include "lldb/Core/Telemetry.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/UUID.h" +#include "lldb/Version/Version.h" #include "lldb/lldb-enumerations.h" #include "lldb/lldb-forward.h" #include "llvm/ADT/StringRef.h" @@ -30,6 +32,26 @@ static uint64_t ToNanosec(const SteadyTimePoint Point) { return std::chrono::nanoseconds(Point.time_since_epoch()).count(); } +// Generate a unique string. This should be unique across different runs. +// We build such string by combining three parts: +// <16 random bytes>_ +// This reduces the chances of getting the same UUID, even when the same +// user runs the two copies of binary at the same time. +static std::string MakeUUID() { + uint8_t random_bytes[16]; + std::string randomString = "_"; + if (auto ec = llvm::getRandomBytes(random_bytes, 16)) { + LLDB_LOG(GetLog(LLDBLog::Object), + "Failed to generate random bytes for UUID: {0}", ec.message()); + } else { + randomString = UUID(random_bytes).GetAsString(); + } + + return llvm::formatv( + "{0}_{1}", randomString, + std::chrono::steady_clock::now().time_since_epoch().count()); +} + void LLDBBaseTelemetryInfo::serialize(Serializer &serializer) const { serializer.write("entry_kind", getKind()); serializer.write("session_id", SessionId); @@ -38,29 +60,27 @@ void LLDBBaseTelemetryInfo::serialize(Serializer &serializer) const { serializer.write("end_time", ToNanosec(end_time.value())); } -[[maybe_unused]] static std::string MakeUUID(Debugger *debugger) { - uint8_t random_bytes[16]; - if (auto ec = llvm::getRandomBytes(random_bytes, 16)) { - LLDB_LOG(GetLog(LLDBLog::Object), - "Failed to generate random bytes for UUID: {0}", ec.message()); - // Fallback to using timestamp + debugger ID. - return llvm::formatv( - "{0}_{1}", std::chrono::steady_clock::now().time_since_epoch().count(), - debugger->GetID()); - } - return UUID(random_bytes).GetAsString(); +void DebuggerInfo::serialize(Serializer &serializer) const { + LLDBBaseTelemetryInfo::serialize(serializer); + + serializer.write("lldb_version", lldb_version); + serializer.write("is_exit_entry", is_exit_entry); } TelemetryManager::TelemetryManager(std::unique_ptr config) - : m_config(std::move(config)) {} + : m_config(std::move(config)), m_id(MakeUUID()) {} llvm::Error TelemetryManager::preDispatch(TelemetryInfo *entry) { - // Do nothing for now. - // In up-coming patch, this would be where the manager - // attach the session_uuid to the entry. + // Assign the manager_id, and debugger_id, if available, to this entry. + LLDBBaseTelemetryInfo *lldb_entry = llvm::cast(entry); + lldb_entry->SessionId = m_id; + if (Debugger *debugger = lldb_entry->debugger) + lldb_entry->debugger_id = debugger->GetID(); return llvm::Error::success(); } +const Config *TelemetryManager::GetConfig() { return m_config.get(); } + std::unique_ptr TelemetryManager::g_instance = nullptr; TelemetryManager *TelemetryManager::GetInstance() { if (!Config::BuildTimeEnableTelemetry) @@ -68,8 +88,19 @@ TelemetryManager *TelemetryManager::GetInstance() { return g_instance.get(); } +TelemetryManager *TelemetryManager::GetInstanceIfEnabled() { + // Telemetry may be enabled at build time but disabled at runtime. + if (TelemetryManager *ins = TelemetryManager::GetInstance()) { + if (ins->GetConfig()->EnableTelemetry) + return ins; + } + + return nullptr; +} + void TelemetryManager::SetInstance(std::unique_ptr manager) { - g_instance = std::move(manager); + if (Config::BuildTimeEnableTelemetry) + g_instance = std::move(manager); } } // namespace telemetry diff --git a/lldb/unittests/Core/TelemetryTest.cpp b/lldb/unittests/Core/TelemetryTest.cpp index 22fade58660b5..0e9f329110872 100644 --- a/lldb/unittests/Core/TelemetryTest.cpp +++ b/lldb/unittests/Core/TelemetryTest.cpp @@ -18,25 +18,34 @@ namespace lldb_private { -struct FakeTelemetryInfo : public llvm::telemetry::TelemetryInfo { +struct FakeTelemetryInfo : public telemetry::LLDBBaseTelemetryInfo { std::string msg; + int num; + + ::llvm::telemetry::KindType getKind() const override { return 0b11111111; } }; class TestDestination : public llvm::telemetry::Destination { public: - TestDestination(std::vector *entries) + TestDestination( + std::vector> *entries) : received_entries(entries) {} llvm::Error receiveEntry(const llvm::telemetry::TelemetryInfo *entry) override { - received_entries->push_back(entry); + // Save a copy of the entry for later verification (because the original + // entry might have gone out of scope by the time verification is done. + if (auto *fake_entry = llvm::dyn_cast(entry)) + received_entries->push_back( + std::make_unique(*fake_entry)); return llvm::Error::success(); } llvm::StringLiteral name() const override { return "TestDestination"; } private: - std::vector *received_entries; + std::vector> + *received_entries; }; class FakePlugin : public telemetry::TelemetryManager { @@ -66,6 +75,8 @@ class FakePlugin : public telemetry::TelemetryManager { } // namespace lldb_private +using namespace lldb_private::telemetry; + #if LLVM_ENABLE_TELEMETRY #define TELEMETRY_TEST(suite, test) TEST(suite, test) #else @@ -80,18 +91,51 @@ TELEMETRY_TEST(TelemetryTest, PluginTest) { auto *ins = lldb_private::telemetry::TelemetryManager::GetInstance(); ASSERT_NE(ins, nullptr); - std::vector expected_entries; + std::vector> + received_entries; ins->addDestination( - std::make_unique(&expected_entries)); + std::make_unique(&received_entries)); lldb_private::FakeTelemetryInfo entry; entry.msg = ""; ASSERT_THAT_ERROR(ins->dispatch(&entry), ::llvm::Succeeded()); - ASSERT_EQ(1U, expected_entries.size()); + ASSERT_EQ(1U, received_entries.size()); EXPECT_EQ("In FakePlugin", - llvm::dyn_cast(expected_entries[0]) + llvm::dyn_cast(received_entries[0]) ->msg); ASSERT_EQ("FakeTelemetryPlugin", ins->GetInstanceName()); } + +TELEMETRY_TEST(TelemetryTest, ScopedDispatcherTest) { + lldb_private::FakePlugin::Initialize(); + auto *ins = TelemetryManager::GetInstance(); + ASSERT_NE(ins, nullptr); + std::vector> + received_entries; + ins->addDestination( + std::make_unique(&received_entries)); + + { + ScopedDispatcher helper( + [](lldb_private::FakeTelemetryInfo *info) { info->num = 0; }); + } + + { + ScopedDispatcher helper( + [](lldb_private::FakeTelemetryInfo *info) { info->num = 1; }); + } + + { + ScopedDispatcher helper( + [](lldb_private::FakeTelemetryInfo *info) { info->num = 2; }); + } + + EXPECT_EQ(3U, received_entries.size()); + for (int i = 0; i < 3; ++i) { + EXPECT_EQ( + i, llvm::dyn_cast(received_entries[i]) + ->num); + } +}