231 lines · cpp
1//===-- Telemetry.cpp -----------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8#include "lldb/Core/Telemetry.h"9#include "lldb/Core/Debugger.h"10#include "lldb/Utility/LLDBLog.h"11#include "lldb/Utility/Log.h"12#include "lldb/Utility/UUID.h"13#include "lldb/lldb-enumerations.h"14#include "lldb/lldb-forward.h"15#include "llvm/ADT/StringRef.h"16#include "llvm/Support/Error.h"17#include "llvm/Support/Format.h"18#include "llvm/Support/RandomNumberGenerator.h"19#include "llvm/Telemetry/Telemetry.h"20#include <chrono>21#include <cstdlib>22#include <ctime>23#include <memory>24#include <string>25#include <utility>26 27namespace lldb_private {28namespace telemetry {29 30using namespace llvm::telemetry;31 32static uint64_t ToNanosec(const SteadyTimePoint Point) {33 return std::chrono::nanoseconds(Point.time_since_epoch()).count();34}35 36// Generate a unique string. This should be unique across different runs.37// We build such string by combining three parts:38// <16 random bytes>_<timestamp>39// This reduces the chances of getting the same UUID, even when the same40// user runs the two copies of binary at the same time.41static std::string MakeUUID() {42 auto timestmap = std::chrono::steady_clock::now().time_since_epoch().count();43 UUID uuid = UUID::Generate();44 return llvm::formatv("{0}_{1}", uuid.GetAsString(), timestmap);45}46 47void LLDBBaseTelemetryInfo::serialize(Serializer &serializer) const {48 serializer.write("entry_kind", getKind());49 serializer.write("session_id", SessionId);50 serializer.write("start_time", ToNanosec(start_time));51 if (end_time.has_value())52 serializer.write("end_time", ToNanosec(end_time.value()));53}54 55void ClientInfo::serialize(Serializer &serializer) const {56 LLDBBaseTelemetryInfo::serialize(serializer);57 serializer.write("client_data", client_data);58 serializer.write("client_name", client_name);59 if (error_msg.has_value())60 serializer.write("error_msg", error_msg.value());61}62 63void CommandInfo::serialize(Serializer &serializer) const {64 LLDBBaseTelemetryInfo::serialize(serializer);65 66 serializer.write("target_uuid", target_uuid.GetAsString());67 serializer.write("command_id", command_id);68 serializer.write("command_name", command_name);69 if (original_command.has_value())70 serializer.write("original_command", original_command.value());71 if (args.has_value())72 serializer.write("args", args.value());73 if (ret_status.has_value())74 serializer.write("ret_status", ret_status.value());75 if (error_data.has_value())76 serializer.write("error_data", error_data.value());77}78 79std::atomic<uint64_t> CommandInfo::g_command_id_seed = 1;80uint64_t CommandInfo::GetNextID() { return g_command_id_seed.fetch_add(1); }81 82void DebuggerInfo::serialize(Serializer &serializer) const {83 LLDBBaseTelemetryInfo::serialize(serializer);84 85 serializer.write("lldb_version", lldb_version);86 serializer.write("is_exit_entry", is_exit_entry);87}88 89void ExecutableModuleInfo::serialize(Serializer &serializer) const {90 LLDBBaseTelemetryInfo::serialize(serializer);91 92 serializer.write("uuid", uuid.GetAsString());93 serializer.write("pid", pid);94 serializer.write("triple", triple);95 serializer.write("is_start_entry", is_start_entry);96}97 98void ProcessExitInfo::serialize(Serializer &serializer) const {99 LLDBBaseTelemetryInfo::serialize(serializer);100 101 serializer.write("module_uuid", module_uuid.GetAsString());102 serializer.write("pid", pid);103 serializer.write("is_start_entry", is_start_entry);104 if (exit_desc.has_value()) {105 serializer.write("exit_code", exit_desc->exit_code);106 serializer.write("exit_desc", exit_desc->description);107 }108}109 110TelemetryManager::TelemetryManager(std::unique_ptr<LLDBConfig> config)111 : m_config(std::move(config)), m_id(MakeUUID()) {}112 113llvm::Error TelemetryManager::preDispatch(TelemetryInfo *entry) {114 // Assign the manager_id, and debugger_id, if available, to this entry.115 LLDBBaseTelemetryInfo *lldb_entry = llvm::cast<LLDBBaseTelemetryInfo>(entry);116 lldb_entry->SessionId = m_id;117 if (Debugger *debugger = lldb_entry->debugger)118 lldb_entry->debugger_id = debugger->GetID();119 return llvm::Error::success();120}121 122// Helper for extracting time field from a Dictionary.123static std::optional<std::chrono::nanoseconds>124GetAsNanosec(StructuredData::Dictionary *dict, llvm::StringRef key) {125 auto value = dict->GetValueForKey(key);126 if (!value->IsValid()) {127 LLDB_LOG(GetLog(LLDBLog::Object),128 "Cannot determine {0} from client-telemetry entry", key);129 return std::nullopt;130 }131 132 return std::chrono::nanoseconds(value->GetUnsignedIntegerValue(0));133}134 135void TelemetryManager::DispatchClientTelemetry(136 const lldb_private::StructuredDataImpl &entry, Debugger *debugger) {137 if (!m_config->enable_client_telemetry)138 return;139 140 ClientInfo client_info;141 client_info.debugger = debugger;142 if (entry.GetObjectSP()->GetType() != lldb::eStructuredDataTypeDictionary) {143 LLDB_LOG(GetLog(LLDBLog::Object), "Expected Dictionary type but got {0}.",144 entry.GetObjectSP()->GetType());145 return;146 }147 148 auto *dict = entry.GetObjectSP()->GetAsDictionary();149 150 llvm::StringRef client_name;151 if (dict->GetValueForKeyAsString("client_name", client_name))152 client_info.client_name = client_name.str();153 else154 LLDB_LOG(GetLog(LLDBLog::Object),155 "Cannot determine client_name from client-telemetry entry");156 157 llvm::StringRef client_data;158 if (dict->GetValueForKeyAsString("client_data", client_data))159 client_info.client_data = client_data.str();160 else161 LLDB_LOG(GetLog(LLDBLog::Object),162 "Cannot determine client_data from client-telemetry entry");163 164 if (auto maybe_start_time = GetAsNanosec(dict, "start_time"))165 client_info.start_time += *maybe_start_time;166 167 if (auto maybe_end_time = GetAsNanosec(dict, "end_time")) {168 SteadyTimePoint epoch;169 client_info.end_time = epoch + *maybe_end_time;170 }171 172 llvm::StringRef error_msg;173 if (dict->GetValueForKeyAsString("error", error_msg))174 client_info.error_msg = error_msg.str();175 176 if (llvm::Error er = dispatch(&client_info))177 LLDB_LOG_ERROR(GetLog(LLDBLog::Object), std::move(er),178 "Failed to dispatch client telemetry");179}180 181class NoOpTelemetryManager : public TelemetryManager {182public:183 llvm::Error preDispatch(llvm::telemetry::TelemetryInfo *entry) override {184 // Does nothing.185 return llvm::Error::success();186 }187 188 explicit NoOpTelemetryManager()189 : TelemetryManager(std::make_unique<LLDBConfig>(190 /*EnableTelemetry=*/false, /*DetailedCommand=*/false,191 /*ClientTelemery=*/false)) {}192 193 virtual llvm::StringRef GetInstanceName() const override {194 return "NoOpTelemetryManager";195 }196 197 void DispatchClientTelemetry(const lldb_private::StructuredDataImpl &entry,198 Debugger *debugger) override {199 // Does nothing.200 }201 202 llvm::Error dispatch(llvm::telemetry::TelemetryInfo *entry) override {203 // Does nothing.204 return llvm::Error::success();205 }206 207 static NoOpTelemetryManager *GetInstance() {208 static std::unique_ptr<NoOpTelemetryManager> g_ins =209 std::make_unique<NoOpTelemetryManager>();210 return g_ins.get();211 }212};213 214std::unique_ptr<TelemetryManager> TelemetryManager::g_instance = nullptr;215TelemetryManager *TelemetryManager::GetInstance() {216 // If Telemetry is disabled or if there is no default instance, then use the217 // NoOp manager. We use a dummy instance to avoid having to do nullchecks in218 // various places.219 if (!Config::BuildTimeEnableTelemetry || !g_instance)220 return NoOpTelemetryManager::GetInstance();221 return g_instance.get();222}223 224void TelemetryManager::SetInstance(std::unique_ptr<TelemetryManager> manager) {225 if (Config::BuildTimeEnableTelemetry)226 g_instance = std::move(manager);227}228 229} // namespace telemetry230} // namespace lldb_private231