[Lldb-commits] [lldb] [llvm] [DRAFT][llvm]Added lib/Telemetry (PR #98528)
Vy Nguyen via lldb-commits
lldb-commits at lists.llvm.org
Tue Jul 23 12:44:46 PDT 2024
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 <vyng at 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 <vyng at 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
More information about the lldb-commits
mailing list