Author: Vy Nguyen
Date: 2025-04-26T02:19:29+02:00
New Revision: a74e4ac9d52029f9c9fe80654acd9fdd34d2a4cd

URL: 
https://github.com/llvm/llvm-project/commit/a74e4ac9d52029f9c9fe80654acd9fdd34d2a4cd
DIFF: 
https://github.com/llvm/llvm-project/commit/a74e4ac9d52029f9c9fe80654acd9fdd34d2a4cd.diff

LOG: [LLDB][Telemetry] Collect telemetry from client when allowed. (#129728)

This patch is slightly different from other impl in that we dispatch
client-telemetry via a different helper method. This is to make it
easier for vendor to opt-out (simply by overriding the method to do
nothing). There is also a configuration option to disallow collecting
client telemetry.

---------

Co-authored-by: Pavel Labath <pa...@labath.sk>

Added: 
    

Modified: 
    lldb/include/lldb/API/SBDebugger.h
    lldb/include/lldb/Core/Debugger.h
    lldb/include/lldb/Core/Telemetry.h
    lldb/source/API/SBDebugger.cpp
    lldb/source/Core/Debugger.cpp
    lldb/source/Core/Telemetry.cpp
    lldb/tools/lldb-dap/DAP.cpp
    lldb/tools/lldb-dap/LLDBUtils.h
    lldb/unittests/Core/TelemetryTest.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/API/SBDebugger.h 
b/lldb/include/lldb/API/SBDebugger.h
index 3ece2a2e3a9f2..192fbee9c0c6d 100644
--- a/lldb/include/lldb/API/SBDebugger.h
+++ b/lldb/include/lldb/API/SBDebugger.h
@@ -13,6 +13,7 @@
 
 #include "lldb/API/SBDefines.h"
 #include "lldb/API/SBPlatform.h"
+#include "lldb/API/SBStructuredData.h"
 
 namespace lldb_private {
 class CommandPluginInterfaceImplementation;
@@ -250,6 +251,13 @@ class LLDB_API SBDebugger {
 
   lldb::SBTarget GetDummyTarget();
 
+#ifndef SWIG
+  // Dispatch telemery from client to server if client-telemetry is enabled
+  // (by vendor), otherwise the data is ignored.
+  // Invoking this from python client (with SWIG) is not supported.
+  void DispatchClientTelemetry(const lldb::SBStructuredData &data);
+#endif
+
   // 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 0595125b1813d..c9e5310cded1a 100644
--- a/lldb/include/lldb/Core/Debugger.h
+++ b/lldb/include/lldb/Core/Debugger.h
@@ -20,6 +20,8 @@
 #include "lldb/Core/IOHandler.h"
 #include "lldb/Core/SourceManager.h"
 #include "lldb/Core/Statusline.h"
+#include "lldb/Core/StructuredDataImpl.h"
+#include "lldb/Core/Telemetry.h"
 #include "lldb/Core/UserSettingsController.h"
 #include "lldb/Host/HostThread.h"
 #include "lldb/Host/StreamFile.h"
@@ -32,6 +34,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"
@@ -124,6 +127,8 @@ class Debugger : public 
std::enable_shared_from_this<Debugger>,
 
   void Clear();
 
+  void DispatchClientTelemetry(const lldb_private::StructuredDataImpl &entry);
+
   bool GetAsyncExecution();
 
   void SetAsyncExecution(bool async);

diff  --git a/lldb/include/lldb/Core/Telemetry.h 
b/lldb/include/lldb/Core/Telemetry.h
index fa01e2e4af90f..7889cda40e75f 100644
--- a/lldb/include/lldb/Core/Telemetry.h
+++ b/lldb/include/lldb/Core/Telemetry.h
@@ -39,9 +39,16 @@ struct LLDBConfig : public ::llvm::telemetry::Config {
   // the vendor while creating the Manager.
   const bool detailed_command_telemetry;
 
-  explicit LLDBConfig(bool enable_telemetry, bool detailed_command_telemetry)
+  // If true, we will collect telemetry from LLDB's clients (eg., lldb-dap) via
+  // the SB interface. Must also be enabled by the vendor while creating the
+  // manager.
+  const bool enable_client_telemetry;
+
+  explicit LLDBConfig(bool enable_telemetry, bool detailed_command_telemetry,
+                      bool enable_client_telemetry)
       : ::llvm::telemetry::Config(enable_telemetry),
-        detailed_command_telemetry(detailed_command_telemetry) {}
+        detailed_command_telemetry(detailed_command_telemetry),
+        enable_client_telemetry(enable_client_telemetry) {}
 };
 
 // We expect each (direct) subclass of LLDBTelemetryInfo to
@@ -56,6 +63,7 @@ struct LLDBConfig : public ::llvm::telemetry::Config {
 struct LLDBEntryKind : public ::llvm::telemetry::EntryKind {
   // clang-format off
   static const llvm::telemetry::KindType BaseInfo        = 0b11000000;
+  static const llvm::telemetry::KindType ClientInfo      = 0b11100000;
   static const llvm::telemetry::KindType CommandInfo     = 0b11010000;
   static const llvm::telemetry::KindType DebuggerInfo    = 0b11001000;
   static const llvm::telemetry::KindType ExecModuleInfo  = 0b11000100;
@@ -89,6 +97,14 @@ struct LLDBBaseTelemetryInfo : public 
llvm::telemetry::TelemetryInfo {
   void serialize(llvm::telemetry::Serializer &serializer) const override;
 };
 
+struct ClientInfo : public LLDBBaseTelemetryInfo {
+  std::string client_name;
+  std::string client_data;
+  std::optional<std::string> error_msg;
+
+  void serialize(llvm::telemetry::Serializer &serializer) const override;
+};
+
 struct CommandInfo : public LLDBBaseTelemetryInfo {
   /// If the command is/can be associated with a target entry this field
   /// contains that target's UUID. <EMPTY> otherwise.
@@ -217,6 +233,9 @@ class TelemetryManager : public llvm::telemetry::Manager {
 
   const LLDBConfig *GetConfig() { return m_config.get(); }
 
+  virtual void
+  DispatchClientTelemetry(const lldb_private::StructuredDataImpl &entry,
+                          Debugger *debugger);
   virtual llvm::StringRef GetInstanceName() const = 0;
 
   static TelemetryManager *GetInstance();

diff  --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp
index 2cfcdc78c8111..603e306497841 100644
--- a/lldb/source/API/SBDebugger.cpp
+++ b/lldb/source/API/SBDebugger.cpp
@@ -926,6 +926,17 @@ SBTarget SBDebugger::GetDummyTarget() {
   return sb_target;
 }
 
+void SBDebugger::DispatchClientTelemetry(const lldb::SBStructuredData &entry) {
+  LLDB_INSTRUMENT_VA(this);
+  if (m_opaque_sp) {
+    m_opaque_sp->DispatchClientTelemetry(*entry.m_impl_up);
+  } else {
+    Log *log = GetLog(LLDBLog::API);
+    LLDB_LOGF(log,
+              "Could not send telemetry from SBDebugger - debugger was null.");
+  }
+}
+
 bool SBDebugger::DeleteTarget(lldb::SBTarget &target) {
   LLDB_INSTRUMENT_VA(this, target);
 

diff  --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index 1a0723a2f3b3f..25bb42bad152c 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -841,6 +841,12 @@ DebuggerSP 
Debugger::CreateInstance(lldb::LogOutputCallback log_callback,
   return debugger_sp;
 }
 
+void Debugger::DispatchClientTelemetry(
+    const lldb_private::StructuredDataImpl &entry) {
+  lldb_private::telemetry::TelemetryManager::GetInstance()
+      ->DispatchClientTelemetry(entry, this);
+}
+
 void Debugger::HandleDestroyCallback() {
   const lldb::user_id_t user_id = GetID();
   // Invoke and remove all the callbacks in an FIFO order. Callbacks which are

diff  --git a/lldb/source/Core/Telemetry.cpp b/lldb/source/Core/Telemetry.cpp
index 8db29889e0846..a819d5366cedc 100644
--- a/lldb/source/Core/Telemetry.cpp
+++ b/lldb/source/Core/Telemetry.cpp
@@ -53,6 +53,14 @@ void LLDBBaseTelemetryInfo::serialize(Serializer 
&serializer) const {
     serializer.write("end_time", ToNanosec(end_time.value()));
 }
 
+void ClientInfo::serialize(Serializer &serializer) const {
+  LLDBBaseTelemetryInfo::serialize(serializer);
+  serializer.write("client_data", client_data);
+  serializer.write("client_name", client_name);
+  if (error_msg.has_value())
+    serializer.write("error_msg", error_msg.value());
+}
+
 void CommandInfo::serialize(Serializer &serializer) const {
   LLDBBaseTelemetryInfo::serialize(serializer);
 
@@ -112,6 +120,63 @@ llvm::Error TelemetryManager::preDispatch(TelemetryInfo 
*entry) {
   return llvm::Error::success();
 }
 
+void TelemetryManager::DispatchClientTelemetry(
+    const lldb_private::StructuredDataImpl &entry, Debugger *debugger) {
+  if (!m_config->enable_client_telemetry)
+    return;
+
+  ClientInfo client_info;
+  client_info.debugger = debugger;
+  if (entry.GetObjectSP()->GetType() != lldb::eStructuredDataTypeDictionary) {
+    LLDB_LOG(GetLog(LLDBLog::Object), "Expected Dictionary type but got {0}.",
+             entry.GetObjectSP()->GetType());
+    return;
+  }
+
+  auto *dict = entry.GetObjectSP()->GetAsDictionary();
+
+  llvm::StringRef client_name;
+  if (dict->GetValueForKeyAsString("client_name", client_name))
+    client_info.client_name = client_name.str();
+  else
+    LLDB_LOG(GetLog(LLDBLog::Object),
+             "Cannot determine client_name from client-telemetry entry");
+
+  llvm::StringRef client_data;
+  if (dict->GetValueForKeyAsString("client_data", client_data))
+    client_info.client_data = client_data.str();
+  else
+    LLDB_LOG(GetLog(LLDBLog::Object),
+             "Cannot determine client_data from client-telemetry entry");
+
+  int64_t start_time;
+  if (dict->GetValueForKeyAsInteger("start_time", start_time)) {
+    client_info.start_time +=
+        std::chrono::nanoseconds(static_cast<size_t>(start_time));
+  } else {
+    LLDB_LOG(GetLog(LLDBLog::Object),
+             "Cannot determine start-time from client-telemetry entry");
+  }
+
+  int64_t end_time;
+  if (dict->GetValueForKeyAsInteger("end_time", end_time)) {
+    SteadyTimePoint epoch;
+    client_info.end_time =
+        epoch + std::chrono::nanoseconds(static_cast<size_t>(end_time));
+  } else {
+    LLDB_LOG(GetLog(LLDBLog::Object),
+             "Cannot determine end-time from client-telemetry entry");
+  }
+
+  llvm::StringRef error_msg;
+  if (dict->GetValueForKeyAsString("error", error_msg))
+    client_info.error_msg = error_msg.str();
+
+  if (llvm::Error er = dispatch(&client_info))
+    LLDB_LOG_ERROR(GetLog(LLDBLog::Object), std::move(er),
+                   "Failed to dispatch client telemetry");
+}
+
 class NoOpTelemetryManager : public TelemetryManager {
 public:
   llvm::Error preDispatch(llvm::telemetry::TelemetryInfo *entry) override {
@@ -121,12 +186,18 @@ class NoOpTelemetryManager : public TelemetryManager {
 
   explicit NoOpTelemetryManager()
       : TelemetryManager(std::make_unique<LLDBConfig>(
-            /*EnableTelemetry*/ false, /*DetailedCommand*/ false)) {}
+            /*EnableTelemetry=*/false, /*DetailedCommand=*/false,
+            /*ClientTelemery=*/false)) {}
 
   virtual llvm::StringRef GetInstanceName() const override {
     return "NoOpTelemetryManager";
   }
 
+  void DispatchClientTelemetry(const lldb_private::StructuredDataImpl &entry,
+                               Debugger *debugger) override {
+    // Does nothing.
+  }
+
   llvm::Error dispatch(llvm::telemetry::TelemetryInfo *entry) override {
     // Does nothing.
     return llvm::Error::success();

diff  --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index e76430b75e80e..55d49667b6398 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -700,6 +700,8 @@ void DAP::SetTarget(const lldb::SBTarget target) {
 }
 
 bool DAP::HandleObject(const Message &M) {
+  TelemetryDispatcher dispatcher(&debugger);
+  dispatcher.Set("client_name", transport.GetClientName().str());
   if (const auto *req = std::get_if<Request>(&M)) {
     {
       std::lock_guard<std::mutex> guard(m_active_request_mutex);
@@ -716,11 +718,15 @@ bool DAP::HandleObject(const Message &M) {
     });
 
     auto handler_pos = request_handlers.find(req->command);
+    dispatcher.Set("client_data",
+                   llvm::Twine("request_command:", req->command).str());
     if (handler_pos != request_handlers.end()) {
       handler_pos->second->Run(*req);
       return true; // Success
     }
 
+    dispatcher.Set("error",
+                   llvm::Twine("unhandled-command:" + req->command).str());
     DAP_LOG(log, "({0}) error: unhandled command '{1}'",
             transport.GetClientName(), req->command);
     return false; // Fail
@@ -744,6 +750,8 @@ bool DAP::HandleObject(const Message &M) {
     // Result should be given, use null if not.
     if (resp->success) {
       (*response_handler)(resp->body);
+      dispatcher.Set("client_data",
+                     llvm::Twine("response_command:", resp->command).str());
     } else {
       llvm::StringRef message = "Unknown error, response failed";
       if (resp->message) {
@@ -764,6 +772,7 @@ bool DAP::HandleObject(const Message &M) {
                            }),
                        *resp->message);
       }
+      dispatcher.Set("error", message.str());
 
       (*response_handler)(llvm::createStringError(
           std::error_code(-1, std::generic_category()), message));
@@ -772,6 +781,7 @@ bool DAP::HandleObject(const Message &M) {
     return true;
   }
 
+  dispatcher.Set("error", "Unsupported protocol message");
   DAP_LOG(log, "Unsupported protocol message");
 
   return false;

diff  --git a/lldb/tools/lldb-dap/LLDBUtils.h b/lldb/tools/lldb-dap/LLDBUtils.h
index 7ce242ec887ae..610ebce83566c 100644
--- a/lldb/tools/lldb-dap/LLDBUtils.h
+++ b/lldb/tools/lldb-dap/LLDBUtils.h
@@ -17,7 +17,9 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/JSON.h"
+#include "llvm/Support/ScopedPrinter.h"
 #include "llvm/Support/raw_ostream.h"
+#include <chrono>
 #include <string>
 
 namespace lldb_dap {
@@ -159,6 +161,43 @@ uint32_t GetLLDBFrameID(uint64_t dap_frame_id);
 lldb::SBEnvironment
 GetEnvironmentFromArguments(const llvm::json::Object &arguments);
 
+/// Helper for sending telemetry to lldb server, if client-telemetry is 
enabled.
+class TelemetryDispatcher {
+public:
+  TelemetryDispatcher(lldb::SBDebugger *debugger) {
+    m_telemetry_json = llvm::json::Object();
+    m_telemetry_json.try_emplace(
+        "start_time",
+        std::chrono::steady_clock::now().time_since_epoch().count());
+    this->debugger = debugger;
+  }
+
+  void Set(std::string key, std::string value) {
+    m_telemetry_json.try_emplace(key, value);
+  }
+
+  void Set(std::string key, int64_t value) {
+    m_telemetry_json.try_emplace(key, value);
+  }
+
+  ~TelemetryDispatcher() {
+    m_telemetry_json.try_emplace(
+        "end_time",
+        std::chrono::steady_clock::now().time_since_epoch().count());
+
+    lldb::SBStructuredData telemetry_entry;
+    llvm::json::Value val(std::move(m_telemetry_json));
+
+    std::string string_rep = llvm::to_string(val);
+    telemetry_entry.SetFromJSON(string_rep.c_str());
+    debugger->DispatchClientTelemetry(telemetry_entry);
+  }
+
+private:
+  llvm::json::Object m_telemetry_json;
+  lldb::SBDebugger *debugger;
+};
+
 /// Get the stop-disassembly-display settings
 ///
 /// \param[in] debugger
@@ -168,6 +207,7 @@ GetEnvironmentFromArguments(const llvm::json::Object 
&arguments);
 ///     The value of the stop-disassembly-display setting
 lldb::StopDisassemblyType GetStopDisassemblyDisplay(lldb::SBDebugger 
&debugger);
 
+
 /// Take ownership of the stored error.
 llvm::Error ToError(const lldb::SBError &error);
 

diff  --git a/lldb/unittests/Core/TelemetryTest.cpp 
b/lldb/unittests/Core/TelemetryTest.cpp
index 910149d865c13..8d69b9a6cdc76 100644
--- a/lldb/unittests/Core/TelemetryTest.cpp
+++ b/lldb/unittests/Core/TelemetryTest.cpp
@@ -53,7 +53,8 @@ class FakePlugin : public telemetry::TelemetryManager {
 public:
   FakePlugin()
       : telemetry::TelemetryManager(std::make_unique<telemetry::LLDBConfig>(
-            /*enable_telemetry=*/true, /*detailed_command_telemetry=*/true)) {}
+            /*enable_telemetry=*/true, /*detailed_command_telemetry=*/true,
+            /*enable_client_telemetry=*/true)) {}
 
   // TelemetryManager interface
   llvm::Error preDispatch(llvm::telemetry::TelemetryInfo *entry) override {


        
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to