Skip to content

[LLDB][Telemetry] Collect telemetry from client when allowed. #129728

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 29 commits into
base: main
Choose a base branch
from

Conversation

oontvoo
Copy link
Member

@oontvoo oontvoo commented Mar 4, 2025

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.

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.
@llvmbot
Copy link
Member

llvmbot commented Mar 4, 2025

@llvm/pr-subscribers-lldb

Author: Vy Nguyen (oontvoo)

Changes

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.


Full diff: https://github.com/llvm/llvm-project/pull/129728.diff

9 Files Affected:

  • (modified) lldb/include/lldb/API/SBDebugger.h (+3)
  • (modified) lldb/include/lldb/Core/Debugger.h (+5)
  • (modified) lldb/include/lldb/Core/Telemetry.h (+64-25)
  • (modified) lldb/source/API/SBDebugger.cpp (+11)
  • (modified) lldb/source/Core/Debugger.cpp (+6)
  • (modified) lldb/source/Core/Telemetry.cpp (+86-13)
  • (modified) lldb/tools/lldb-dap/DAP.cpp (+4-1)
  • (modified) lldb/tools/lldb-dap/LLDBUtils.h (+34)
  • (modified) lldb/unittests/Core/TelemetryTest.cpp (+1-1)
diff --git a/lldb/include/lldb/API/SBDebugger.h b/lldb/include/lldb/API/SBDebugger.h
index e0819f1684f8b..28f92f2095951 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;
@@ -249,6 +250,8 @@ class LLDB_API SBDebugger {
 
   lldb::SBTarget GetDummyTarget();
 
+  void DispatchClientTelemetry(const lldb::SBStructuredData &data);
+
   // 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 6ebc6147800e1..e40666d5ceec7 100644
--- a/lldb/include/lldb/Core/Debugger.h
+++ b/lldb/include/lldb/Core/Debugger.h
@@ -19,6 +19,8 @@
 #include "lldb/Core/FormatEntity.h"
 #include "lldb/Core/IOHandler.h"
 #include "lldb/Core/SourceManager.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"
@@ -31,6 +33,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"
@@ -127,6 +130,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 7d8716f1659b5..cad4a4a6c9048 100644
--- a/lldb/include/lldb/Core/Telemetry.h
+++ b/lldb/include/lldb/Core/Telemetry.h
@@ -19,6 +19,7 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/JSON.h"
 #include "llvm/Telemetry/Telemetry.h"
+#include <atomic>
 #include <chrono>
 #include <ctime>
 #include <memory>
@@ -28,6 +29,23 @@
 namespace lldb_private {
 namespace telemetry {
 
+struct LLDBConfig : public ::llvm::telemetry::Config {
+  // If true, we will collect full details about a debug command (eg., args and
+  // original command). Note: This may contain PII, hence can only be enabled by
+  // the vendor while creating the Manager.
+  const bool m_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 m_enable_client_telemetry;
+
+  explicit LLDBConfig(bool enable_telemetry, bool detailed_command_telemetry,
+                      bool enable_client_telemetry)
+      : ::llvm::telemetry::Config(enable_telemetry),
+        m_detailed_command_telemetry(detailed_command_telemetry),
+        m_enable_client_telemetry(enable_client_telemetry) {}
+};
+
 // We expect each (direct) subclass of LLDBTelemetryInfo to
 // have an LLDBEntryKind in the form 0b11xxxxxxxx
 // Specifically:
@@ -37,6 +55,7 @@ namespace telemetry {
 // must have their LLDBEntryKind in the similar form (ie., share common prefix)
 struct LLDBEntryKind : public ::llvm::telemetry::EntryKind {
   static const llvm::telemetry::KindType BaseInfo = 0b11000000;
+  static const llvm::telemetry::KindType ClientInfo = 0b11100000;
   static const llvm::telemetry::KindType DebuggerInfo = 0b11000100;
 };
 
@@ -86,6 +105,11 @@ struct DebuggerInfo : public LLDBBaseTelemetryInfo {
   void serialize(llvm::telemetry::Serializer &serializer) const override;
 };
 
+struct ClientInfo : public LLDBBaseTelemetryInfo {
+  std::string request_name;
+  std::optional<std::string> error_msg;
+};
+
 /// The base Telemetry manager instance in LLDB.
 /// This class declares additional instrumentation points
 /// applicable to LLDB.
@@ -93,24 +117,24 @@ class TelemetryManager : public llvm::telemetry::Manager {
 public:
   llvm::Error preDispatch(llvm::telemetry::TelemetryInfo *entry) override;
 
-  const llvm::telemetry::Config *GetConfig();
+  const LLDBConfig *GetConfig() { return m_config.get(); }
+
+  void DispatchClientTelemery(const lldb_private::StructuredDataImpl &entry,
+                              Debugger *debugger);
 
   virtual llvm::StringRef GetInstanceName() const = 0;
 
   static TelemetryManager *GetInstance();
 
-  static TelemetryManager *GetInstanceIfEnabled();
-
 protected:
-  TelemetryManager(std::unique_ptr<llvm::telemetry::Config> config);
+  TelemetryManager(std::unique_ptr<LLDBConfig> config);
 
   static void SetInstance(std::unique_ptr<TelemetryManager> manger);
 
 private:
-  std::unique_ptr<llvm::telemetry::Config> m_config;
+  std::unique_ptr<LLDBConfig> m_config;
   // Each instance of a TelemetryManager is assigned a unique ID.
   const std::string m_id;
-
   static std::unique_ptr<TelemetryManager> g_instance;
 };
 
@@ -118,39 +142,54 @@ class TelemetryManager : public llvm::telemetry::Manager {
 template <typename Info> struct ScopedDispatcher {
   // The debugger pointer is optional because we may not have a debugger yet.
   // In that case, caller must set the debugger later.
-  ScopedDispatcher(llvm::unique_function<void(Info *info)> callback,
+  ScopedDispatcher(Debugger *debugger = nullptr) {
+    // Start the timer.
+    m_start_time = std::chrono::steady_clock::now();
+    this->debugger = debugger;
+  }
+  ScopedDispatcher(llvm::unique_function<void(Info *info)> final_callback,
                    Debugger *debugger = nullptr)
-      : m_callback(std::move(callback)) {
+      : m_final_callback(std::move(final_callback)) {
     // Start the timer.
     m_start_time = std::chrono::steady_clock::now();
-    m_info.debugger = debugger;
+    this->debugger = debugger;
   }
 
-  void SetDebugger(Debugger *debugger) { m_info.debugger = debugger; }
+  void SetDebugger(Debugger *debugger) { this->debugger = debugger; }
 
-  ~ScopedDispatcher() {
-    // If Telemetry is disabled (either at buildtime or runtime),
-    // then don't do anything.
-    TelemetryManager *manager = TelemetryManager::GetInstanceIfEnabled();
-    if (!manager)
-      return;
+  void DispatchOnExit(llvm::unique_function<void(Info *info)> final_callback) {
+    // We probably should not be overriding previously set cb.
+    assert(!m_final_callback);
+    m_final_callback = std::move(final_callback);
+  }
 
-    m_info.start_time = m_start_time;
-    // Populate common fields that we can only set now.
-    m_info.end_time = std::chrono::steady_clock::now();
-    // The callback will set the remaining fields.
-    m_callback(&m_info);
+  void DispatchNow(llvm::unique_function<void(Info *info)> populate_fields_cb) {
+    TelemetryManager *manager = TelemetryManager::GetInstance();
+    if (!manager->GetConfig()->EnableTelemetry)
+      return;
+    Info info;
+    // Populate the common fields we know aboutl
+    info.start_time = m_start_time;
+    info.end_time = std::chrono::steady_clock::now();
+    info.debugger = debugger;
+    // The callback will set the rest.
+    populate_fields_cb(&info);
     // And then we dispatch.
-    if (llvm::Error er = manager->dispatch(&m_info)) {
+    if (llvm::Error er = manager->dispatch(&info)) {
       LLDB_LOG_ERROR(GetLog(LLDBLog::Object), std::move(er),
-                     "Failed to dispatch entry of type: {0}", m_info.getKind());
+                     "Failed to dispatch entry of type: {0}", info.getKind());
     }
   }
 
+  ~ScopedDispatcher() {
+    if (m_final_callback)
+      DispatchNow(std::move(m_final_callback));
+  }
+
 private:
   SteadyTimePoint m_start_time;
-  llvm::unique_function<void(Info *info)> m_callback;
-  Info m_info;
+  llvm::unique_function<void(Info *info)> m_final_callback;
+  Debugger *debugger;
 };
 
 } // namespace telemetry
diff --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp
index e646b09e05852..7cd2beae94189 100644
--- a/lldb/source/API/SBDebugger.cpp
+++ b/lldb/source/API/SBDebugger.cpp
@@ -965,6 +965,17 @@ SBTarget SBDebugger::GetDummyTarget() {
   return sb_target;
 }
 
+void SBDebugger::DispatchClientTelemetry(const lldb::SBStructuredData &entry) {
+  LLDB_INSTRUMENT_VA(this);
+  if (lldb_private::Debugger *debugger = this->get()) {
+    debugger->DispatchClientTelemetry(*(entry.m_impl_up.get()));
+  } 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 dbe3d72e5efa4..5a04eb876a815 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -781,6 +781,12 @@ DebuggerSP Debugger::CreateInstance(lldb::LogOutputCallback log_callback,
   return debugger_sp;
 }
 
+void Debugger::DispatchClientTelemetry(
+    const lldb_private::StructuredDataImpl &entry) {
+  lldb_private::telemetry::TelemeryManager::GetInstance()
+      ->DispatchClientTelemetry(entry);
+}
+
 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 367e94d6566de..3edb27e535da9 100644
--- a/lldb/source/Core/Telemetry.cpp
+++ b/lldb/source/Core/Telemetry.cpp
@@ -59,6 +59,12 @@ void LLDBBaseTelemetryInfo::serialize(Serializer &serializer) const {
   if (end_time.has_value())
     serializer.write("end_time", ToNanosec(end_time.value()));
 }
+void ClientInfo::serialize(Serializer &serializer) const {
+  LLDBBaseTelemetryInfo::serialize(serializer);
+  serializer.write("request_name", request_name);
+  if (error_msg.has_value())
+    serializer.write("error_msg", error_msg.value());
+}
 
 void DebuggerInfo::serialize(Serializer &serializer) const {
   LLDBBaseTelemetryInfo::serialize(serializer);
@@ -67,7 +73,7 @@ void DebuggerInfo::serialize(Serializer &serializer) const {
   serializer.write("is_exit_entry", is_exit_entry);
 }
 
-TelemetryManager::TelemetryManager(std::unique_ptr<Config> config)
+TelemetryManager::TelemetryManager(std::unique_ptr<LLDBConfig> config)
     : m_config(std::move(config)), m_id(MakeUUID()) {}
 
 llvm::Error TelemetryManager::preDispatch(TelemetryInfo *entry) {
@@ -79,23 +85,90 @@ llvm::Error TelemetryManager::preDispatch(TelemetryInfo *entry) {
   return llvm::Error::success();
 }
 
-const Config *TelemetryManager::GetConfig() { return m_config.get(); }
+void TelemetryManager::DispatchClientTelemetry(
+    const lldb_private::StructuredDataImpl &entry, Debugger *debugger) {
+  if (!m_config->m_enable_client_telemetry)
+    return;
 
-std::unique_ptr<TelemetryManager> TelemetryManager::g_instance = nullptr;
-TelemetryManager *TelemetryManager::GetInstance() {
-  if (!Config::BuildTimeEnableTelemetry)
-    return nullptr;
-  return g_instance.get();
+  ClientInfo client_info;
+  client_info.debugger = debugger;
+
+  std::optional<llvm::StringRef> request_name = entry.getString("request_name");
+  if (!request_name.has_value())
+    LLDB_LOG(GetLog(LLDBLog::Object),
+             "Cannot determine request name from client-telemetry entry");
+  else
+    client_info.request_name = request_name->str();
+
+  std::optional<int64_t> start_time = entry.getInteger("start_time");
+  std::optional<int64_t> end_time = entry.getInteger("end_time");
+  SteadyTimePoint epoch;
+  if (!start_time.has_value()) {
+    LLDB_LOG(GetLog(LLDBLog::Object),
+             "Cannot determine start-time from client-telemetry entry");
+    client_info.start_time = 0;
+  } else {
+    client_info.start_time =
+        epoch + std::chrono::nanoseconds(static_cast<size_t>(*start_time));
+  }
+
+  if (!end_time.has_value()) {
+    LLDB_LOG(GetLog(LLDBLog::Object),
+             "Cannot determine end-time from client-telemetry entry");
+  } else {
+    client_info.end_time =
+        epoch + std::chrono::nanoseconds(static_cast<size_t>(*end_time));
+  }
+
+  std::optional<llvm::StringRef> error_msg = entry.getString("error");
+  if (error_msg.has_value())
+    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");
 }
 
-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;
+class NoOpTelemetryManager : public TelemetryManager {
+public:
+  llvm::Error preDispatch(llvm::telemetry::TelemetryInfo *entry) override {
+    // Does nothing.
+    return llvm::Error::success();
+  }
+
+  explicit NoOpTelemetryManager()
+      : TelemetryManager(std::make_unique<LLDBConfig>(
+            /*EnableTelemetry*/ false, /*DetailedCommand*/ false)) {}
+
+  llvm::StringRef GetInstanceName() const override {
+    return "NoOpTelemetryManager";
+  }
+
+  llvm::Error dispatch(llvm::telemetry::TelemetryInfo *entry) override {
+    // Does nothing.
+    return llvm::Error::success();
   }
 
-  return nullptr;
+  void DispatchClientTelemetry(const lldb_private::StructuredDataImpl &entry,
+                               Debugger *debugger) override {
+    // Does nothing.
+  }
+
+  static NoOpTelemetryManager *GetInstance() {
+    static std::unique_ptr<NoOpTelemetryManager> g_ins =
+        std::make_unique<NoOpTelemetryManager>();
+    return g_ins.get();
+  }
+};
+
+std::unique_ptr<TelemetryManager> TelemetryManager::g_instance = nullptr;
+TelemetryManager *TelemetryManager::GetInstance() {
+  // If Telemetry is disabled or if there is no default instance, then use the
+  // NoOp manager. We use a dummy instance to avoid having to do nullchecks in
+  // various places.
+  if (!Config::BuildTimeEnableTelemetry || !g_instance)
+    return NoOpTelemetryManager::GetInstance();
+  return g_instance.get();
 }
 
 void TelemetryManager::SetInstance(std::unique_ptr<TelemetryManager> manager) {
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 53c514b790f38..257d6cc09dcc6 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -754,16 +754,19 @@ PacketStatus DAP::GetNextObject(llvm::json::Object &object) {
 }
 
 bool DAP::HandleObject(const llvm::json::Object &object) {
+  TelemetryDispatcher dispatcher;
+
   const auto packet_type = GetString(object, "type");
   if (packet_type == "request") {
     const auto command = GetString(object, "command");
-
+    dispatcher.set("request_name", command);
     auto new_handler_pos = request_handlers.find(command);
     if (new_handler_pos != request_handlers.end()) {
       (*new_handler_pos->second)(object);
       return true; // Success
     }
 
+    dispatcher.set("error", llvm::Twine("unhandled-command:" + command).str());
     if (log)
       *log << "error: unhandled command \"" << command.data() << "\""
            << std::endl;
diff --git a/lldb/tools/lldb-dap/LLDBUtils.h b/lldb/tools/lldb-dap/LLDBUtils.h
index a9e13bb3678da..a52f8637a9bb4 100644
--- a/lldb/tools/lldb-dap/LLDBUtils.h
+++ b/lldb/tools/lldb-dap/LLDBUtils.h
@@ -16,6 +16,7 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/JSON.h"
 #include "llvm/Support/raw_ostream.h"
+#include <chrono>
 #include <string>
 
 namespace lldb_dap {
@@ -154,6 +155,39 @@ uint32_t GetLLDBFrameID(uint64_t dap_frame_id);
 lldb::SBEnvironment
 GetEnvironmentFromArguments(const llvm::json::Object &arguments);
 
+class TelemetryDispatcher {
+public:
+  TelemetryDispatcher(SBDebugger *debugger) {
+    m_telemetry_array =
+        ({"start_time",
+          std::chrono::steady_clock::now().time_since_epoch().count()});
+    this->debugger = debugger;
+  }
+
+  void Set(std::string key, std::string value) {
+    m_telemetry_array.push_back(llvm::json::Value{key, value})
+  }
+
+  void Set(std::string key, int64_t value) {
+    m_telemetry_array.push_back(llvm::json::Value{key, value})
+  }
+
+  ~TelemetryDispatcher() {
+    m_telemetry_array.push_back(
+        {"end_time",
+         std::chrono::steady_clock::now().time_since_epoch().count()});
+    lldb::SBStructuredData telemetry_entry;
+    llvm::json::Value val(std::move(telemetry_array));
+    std::string string_rep = lldb_dap::JSONToString(val);
+    telemetry_entry.SetFromJSON(string_rep.c_str());
+    debugger->DispatchClientTelemetry(telemetry_entry);
+  }
+
+private:
+  llvm::json::Array m_telemetry_array;
+  SBDebugger *debugger;
+};
+
 } // namespace lldb_dap
 
 #endif
diff --git a/lldb/unittests/Core/TelemetryTest.cpp b/lldb/unittests/Core/TelemetryTest.cpp
index 0e9f329110872..065a57114181e 100644
--- a/lldb/unittests/Core/TelemetryTest.cpp
+++ b/lldb/unittests/Core/TelemetryTest.cpp
@@ -52,7 +52,7 @@ class FakePlugin : public telemetry::TelemetryManager {
 public:
   FakePlugin()
       : telemetry::TelemetryManager(
-            std::make_unique<llvm::telemetry::Config>(true)) {}
+            std::make_unique<telemetry::LLDBConfig>(true, true)) {}
 
   // TelemetryManager interface
   llvm::Error preDispatch(llvm::telemetry::TelemetryInfo *entry) override {

// 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 m_enable_client_telemetry;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to chime in but I'm just new to this project and I'm wonder why you effectively need a flag. If the client needs to send manually a telemetry event, isn't this redudant?

@oontvoo
Copy link
Member Author

oontvoo commented Mar 4, 2025 via email

Copy link

github-actions bot commented Mar 18, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@oontvoo
Copy link
Member Author

oontvoo commented Mar 24, 2025

@labath @JDevlieghere ping? 🔔 Thanks!

Copy link
Collaborator

@labath labath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really sure what to think about this. You call it "client telemetry", which sounds generic, but the implementation (mainly, the "request" field) sounds very specific to lldb-dap (not every lldb "client" needs to have a "request", or even if it has, the term "request" may mean something very different for it. I understand why you did that, but it kinda goes against the principle that (lib)lldb shouldn't tie itself to any specific user/client.

Maybe that's okay, maybe not, but I don't feel comfortable approving on my own.

Comment on lines 166 to 168
if (dict->GetValueForKeyAsString("error", error_msg)) {
client_info.error_msg = error_msg.str();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 193 to 198
static std::string JSONToString(const llvm::json::Value &json) {
std::string data;
llvm::raw_string_ostream os(data);
os << json;
return data;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete, use llvm::to_string instead.

oontvoo and others added 7 commits March 25, 2025 11:06
Co-authored-by: Pavel Labath <pavel@labath.sk>
Co-authored-by: Pavel Labath <pavel@labath.sk>
Co-authored-by: Pavel Labath <pavel@labath.sk>
Co-authored-by: Pavel Labath <pavel@labath.sk>
@oontvoo
Copy link
Member Author

oontvoo commented Mar 25, 2025

I'm not really sure what to think about this. You call it "client telemetry", which sounds generic, but the implementation (mainly, the "request" field) sounds very specific to lldb-dap (not every lldb "client" needs to have a "request", or even if it has, the term "request" may mean something very different for it.

How about renaming it to "client_data"? (We'd record both the request's command and the response's command)

I understand why you did that, but it kinda goes against the principle that (lib)lldb shouldn't tie itself to any specific user/client.

Maybe that's okay, maybe not, but I don't feel comfortable approving on my own.

@labath
Copy link
Collaborator

labath commented Mar 26, 2025

I'm not really sure what to think about this. You call it "client telemetry", which sounds generic, but the implementation (mainly, the "request" field) sounds very specific to lldb-dap (not every lldb "client" needs to have a "request", or even if it has, the term "request" may mean something very different for it.

How about renaming it to "client_data"? (We'd record both the request's command and the response's command)

Yes, I think that's better. Basically, we're saying "You can give me an opaque string, and I'll forward it. I don't care what it is, it's up to you to figure out what to do with it." It might be a good idea to include something like "client_name" field so you can differentiate (assuming people don't lie) messages coming from lldb-dap from other lldb "clients".

So, if you're fine with a stringly-typed API like this, then I think I'm okay as well. I would like @JDevlieghere to have a look at it as well though.

@oontvoo
Copy link
Member Author

oontvoo commented Mar 26, 2025

It might be a good idea to include something like "client_name" field so you can differentiate (assuming people don't lie) messages coming from lldb-dap from other lldb "clients".

done

// Disable client-telemetry for SWIG.
// This prevent arbitrary python client (pretty printers, whatnot) from sending
// telemetry without vendors knowing.
#ifndef SWIG
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@labath @JDevlieghere Would this be enough to address concerns about "random"/unknown client code sending telemetry to LLDB-server?

Copy link
Member

@JDevlieghere JDevlieghere Mar 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the ifndef should go in the header and guard the whole function so it's not exposed from SWIG. That said, downstream I will replace it with an #if 0 or just remove this method altogether because I don't want anyone external to LLDB to call this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I've defined a ENABLE_CLIENT_TELEMETRY macro at the top of SBDebugger and use the ifdef with it.
(It's currently hard-coded to ! SWIG , might be overkill to make that a build option? )

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, is there any other comment/feedback for this patch? Thanks!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a feeling this is recreating a small version of the #ifdef TELEMETRY hell we've escaped only recently. I can definitely understand the desire/need to restrict access to this API to the world outside (lib)lldb. However, does that have to be achieved by deleting the function declaration? Could it be enough to have the declaration unconditionally, but put the implementation behind some build-time flag (or even a runtime flag that's controlled by the telemetry plugin -- and have the downstream implementation never set it)? (this question is mainly for Jonas. I'm aware you didn't ask for support for this to be added upstream, but I think Vy's attempt to do that demonstrates the kind of problems you're setting yourself up for if you choose that strategy. I think it be better to figure out a different solution, as I think you won't be the only one with that concern.)

Vy: note that this question is independent of the question whether to expose the function to python. ifdef SWIG prevents SWIG from seeing a function (so it doesn't generate bindings for it). The function will always be present in the binary. This is why SWIG guard doesn't have to be applied to function bodies as the swig never reads those. I'm pretty sure this is what Jonas meant when he suggested moving the ifdef to the header -- search for other instances of #ifndef SWIG

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, ok - now that you mentioned it - I agree we should avoid the #ifdef thing on function decl (missing function def and whatnot).

Can we consider going back to my previous impl, which put the ifdef in the function's definition so that the function either does nothing (when client-telemetry is disabled) or actually dispatches the data to server?
But yes, we can modify the ifdef there - rather than conditioning on SWIG, we just make it an explicit ENABLE_CLIENT_TELEMETRY.
Why wasn't that sufficient?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Soo, my reason for suggesting ifndef SWIG is because that hides the API from python (specifically from python data formatters). While I think it might be reasonable to provide data formatters a way to report their health metrics, I don't think that's a problem you're trying to solve here. I'm also aware that it's an imperfect solution as it makes no difference between "python code running in lldb" and "python code driving lldb". I think the "outside vs. inside" distinction is what we'd really want to capture here since in theory one should be able rewrite lldb-dap in python and have everything still work, but I don't know how to forbid one without the other.

Bottom line: I'm not insisting on the ifndef SWIG, I'm just trying to make sure there's a reasonable story for who can call this API. If you can make your use case work without that, even better.

@oontvoo
Copy link
Member Author

oontvoo commented Apr 25, 2025

Thanks all, for the feedback! I'll merge this EOD today, if there's no objection! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants