| //===-- 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 "lldb/Core/Debugger.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "lldb/Utility/UUID.h" | |
| #include "lldb/lldb-enumerations.h" | |
| #include "lldb/lldb-forward.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include "llvm/Support/Error.h" | |
| #include "llvm/Support/Format.h" | |
| #include "llvm/Support/RandomNumberGenerator.h" | |
| #include "llvm/Telemetry/Telemetry.h" | |
| #include <chrono> | |
| #include <cstdlib> | |
| #include <ctime> | |
| #include <memory> | |
| #include <string> | |
| #include <utility> | |
| namespace lldb_private { | |
| namespace telemetry { | |
| using namespace llvm::telemetry; | |
| static uint64_t ToNanosec(const SteadyTimePoint Point) { | |
| return std::chrono::nanoseconds(Point.time_since_epoch()).count(); | |
| } | |
| // Generate a unique string. This should be unique across different runs. | |
| // We build such string by combining three parts: | |
| // <16 random bytes>_<timestamp> | |
| // This reduces the chances of getting the same UUID, even when the same | |
| // user runs the two copies of binary at the same time. | |
| static std::string MakeUUID() { | |
| auto timestmap = std::chrono::steady_clock::now().time_since_epoch().count(); | |
| UUID uuid = UUID::Generate(); | |
| return llvm::formatv("{0}_{1}", uuid.GetAsString(), timestmap); | |
| } | |
| void LLDBBaseTelemetryInfo::serialize(Serializer &serializer) const { | |
| serializer.write("entry_kind", getKind()); | |
| serializer.write("session_id", SessionId); | |
| serializer.write("start_time", ToNanosec(start_time)); | |
| if (end_time.has_value()) | |
| serializer.write("end_time", ToNanosec(end_time.value())); | |
| } | |
| void ClientInfo::serialize(Serializer &serializer) const { | |
| LLDBBaseTelemetryInfo::serialize(serializer); | |
| serializer.write("client_data", client_data); | |
| serializer.write("client_name", client_name); | |
| if (error_msg.has_value()) | |
| serializer.write("error_msg", error_msg.value()); | |
| } | |
| void CommandInfo::serialize(Serializer &serializer) const { | |
| LLDBBaseTelemetryInfo::serialize(serializer); | |
| serializer.write("target_uuid", target_uuid.GetAsString()); | |
| serializer.write("command_id", command_id); | |
| serializer.write("command_name", command_name); | |
| if (original_command.has_value()) | |
| serializer.write("original_command", original_command.value()); | |
| if (args.has_value()) | |
| serializer.write("args", args.value()); | |
| if (ret_status.has_value()) | |
| serializer.write("ret_status", ret_status.value()); | |
| if (error_data.has_value()) | |
| serializer.write("error_data", error_data.value()); | |
| } | |
| std::atomic<uint64_t> CommandInfo::g_command_id_seed = 1; | |
| uint64_t CommandInfo::GetNextID() { return g_command_id_seed.fetch_add(1); } | |
| void DebuggerInfo::serialize(Serializer &serializer) const { | |
| LLDBBaseTelemetryInfo::serialize(serializer); | |
| serializer.write("lldb_version", lldb_version); | |
| serializer.write("is_exit_entry", is_exit_entry); | |
| } | |
| void ExecutableModuleInfo::serialize(Serializer &serializer) const { | |
| LLDBBaseTelemetryInfo::serialize(serializer); | |
| serializer.write("uuid", uuid.GetAsString()); | |
| serializer.write("pid", pid); | |
| serializer.write("triple", triple); | |
| serializer.write("is_start_entry", is_start_entry); | |
| } | |
| void ProcessExitInfo::serialize(Serializer &serializer) const { | |
| LLDBBaseTelemetryInfo::serialize(serializer); | |
| serializer.write("module_uuid", module_uuid.GetAsString()); | |
| serializer.write("pid", pid); | |
| serializer.write("is_start_entry", is_start_entry); | |
| if (exit_desc.has_value()) { | |
| serializer.write("exit_code", exit_desc->exit_code); | |
| serializer.write("exit_desc", exit_desc->description); | |
| } | |
| } | |
| TelemetryManager::TelemetryManager(std::unique_ptr<LLDBConfig> config) | |
| : m_config(std::move(config)), m_id(MakeUUID()) {} | |
| llvm::Error TelemetryManager::preDispatch(TelemetryInfo *entry) { | |
| // Assign the manager_id, and debugger_id, if available, to this entry. | |
| LLDBBaseTelemetryInfo *lldb_entry = llvm::cast<LLDBBaseTelemetryInfo>(entry); | |
| lldb_entry->SessionId = m_id; | |
| if (Debugger *debugger = lldb_entry->debugger) | |
| lldb_entry->debugger_id = debugger->GetID(); | |
| return llvm::Error::success(); | |
| } | |
| // Helper for extracting time field from a Dictionary. | |
| static std::optional<std::chrono::nanoseconds> | |
| GetAsNanosec(StructuredData::Dictionary *dict, llvm::StringRef key) { | |
| auto value = dict->GetValueForKey(key); | |
| if (!value->IsValid()) { | |
| LLDB_LOG(GetLog(LLDBLog::Object), | |
| "Cannot determine {0} from client-telemetry entry", key); | |
| return std::nullopt; | |
| } | |
| return std::chrono::nanoseconds(value->GetUnsignedIntegerValue(0)); | |
| } | |
| void TelemetryManager::DispatchClientTelemetry( | |
| const lldb_private::StructuredDataImpl &entry, Debugger *debugger) { | |
| if (!m_config->enable_client_telemetry) | |
| return; | |
| ClientInfo client_info; | |
| client_info.debugger = debugger; | |
| if (entry.GetObjectSP()->GetType() != lldb::eStructuredDataTypeDictionary) { | |
| LLDB_LOG(GetLog(LLDBLog::Object), "Expected Dictionary type but got {0}.", | |
| entry.GetObjectSP()->GetType()); | |
| return; | |
| } | |
| auto *dict = entry.GetObjectSP()->GetAsDictionary(); | |
| llvm::StringRef client_name; | |
| if (dict->GetValueForKeyAsString("client_name", client_name)) | |
| client_info.client_name = client_name.str(); | |
| else | |
| LLDB_LOG(GetLog(LLDBLog::Object), | |
| "Cannot determine client_name from client-telemetry entry"); | |
| llvm::StringRef client_data; | |
| if (dict->GetValueForKeyAsString("client_data", client_data)) | |
| client_info.client_data = client_data.str(); | |
| else | |
| LLDB_LOG(GetLog(LLDBLog::Object), | |
| "Cannot determine client_data from client-telemetry entry"); | |
| if (auto maybe_start_time = GetAsNanosec(dict, "start_time")) | |
| client_info.start_time += *maybe_start_time; | |
| if (auto maybe_end_time = GetAsNanosec(dict, "end_time")) { | |
| SteadyTimePoint epoch; | |
| client_info.end_time = epoch + *maybe_end_time; | |
| } | |
| llvm::StringRef error_msg; | |
| if (dict->GetValueForKeyAsString("error", error_msg)) | |
| client_info.error_msg = error_msg.str(); | |
| if (llvm::Error er = dispatch(&client_info)) | |
| LLDB_LOG_ERROR(GetLog(LLDBLog::Object), std::move(er), | |
| "Failed to dispatch client telemetry: {0}"); | |
| } | |
| class NoOpTelemetryManager : public TelemetryManager { | |
| public: | |
| llvm::Error preDispatch(llvm::telemetry::TelemetryInfo *entry) override { | |
| // Does nothing. | |
| return llvm::Error::success(); | |
| } | |
| explicit NoOpTelemetryManager() | |
| : TelemetryManager(std::make_unique<LLDBConfig>( | |
| /*EnableTelemetry=*/false, /*DetailedCommand=*/false, | |
| /*ClientTelemery=*/false)) {} | |
| virtual llvm::StringRef GetInstanceName() const override { | |
| return "NoOpTelemetryManager"; | |
| } | |
| void DispatchClientTelemetry(const lldb_private::StructuredDataImpl &entry, | |
| Debugger *debugger) override { | |
| // Does nothing. | |
| } | |
| llvm::Error dispatch(llvm::telemetry::TelemetryInfo *entry) override { | |
| // Does nothing. | |
| return llvm::Error::success(); | |
| } | |
| static NoOpTelemetryManager *GetInstance() { | |
| static std::unique_ptr<NoOpTelemetryManager> g_ins = | |
| std::make_unique<NoOpTelemetryManager>(); | |
| return g_ins.get(); | |
| } | |
| }; | |
| std::unique_ptr<TelemetryManager> TelemetryManager::g_instance = nullptr; | |
| TelemetryManager *TelemetryManager::GetInstance() { | |
| // If Telemetry is disabled or if there is no default instance, then use the | |
| // NoOp manager. We use a dummy instance to avoid having to do nullchecks in | |
| // various places. | |
| if (!Config::BuildTimeEnableTelemetry || !g_instance) | |
| return NoOpTelemetryManager::GetInstance(); | |
| return g_instance.get(); | |
| } | |
| void TelemetryManager::SetInstance(std::unique_ptr<TelemetryManager> manager) { | |
| if (Config::BuildTimeEnableTelemetry) | |
| g_instance = std::move(manager); | |
| } | |
| } // namespace telemetry | |
| } // namespace lldb_private |