xref: /freebsd/contrib/llvm-project/lldb/source/Core/Telemetry.cpp (revision 700637cbb5e582861067a11aaca4d053546871d2)
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-exception
6 //
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 
27 namespace lldb_private {
28 namespace telemetry {
29 
30 using namespace llvm::telemetry;
31 
ToNanosec(const SteadyTimePoint Point)32 static 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 same
40 // user runs the two copies of binary at the same time.
MakeUUID()41 static 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 
serialize(Serializer & serializer) const47 void 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 
serialize(Serializer & serializer) const55 void 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 
serialize(Serializer & serializer) const63 void 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 
79 std::atomic<uint64_t> CommandInfo::g_command_id_seed = 1;
GetNextID()80 uint64_t CommandInfo::GetNextID() { return g_command_id_seed.fetch_add(1); }
81 
serialize(Serializer & serializer) const82 void 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 
serialize(Serializer & serializer) const89 void 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 
serialize(Serializer & serializer) const98 void 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 
TelemetryManager(std::unique_ptr<LLDBConfig> config)110 TelemetryManager::TelemetryManager(std::unique_ptr<LLDBConfig> config)
111     : m_config(std::move(config)), m_id(MakeUUID()) {}
112 
preDispatch(TelemetryInfo * entry)113 llvm::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.
123 static std::optional<std::chrono::nanoseconds>
GetAsNanosec(StructuredData::Dictionary * dict,llvm::StringRef key)124 GetAsNanosec(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 
DispatchClientTelemetry(const lldb_private::StructuredDataImpl & entry,Debugger * debugger)135 void 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   else
154     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   else
161     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 
181 class NoOpTelemetryManager : public TelemetryManager {
182 public:
preDispatch(llvm::telemetry::TelemetryInfo * entry)183   llvm::Error preDispatch(llvm::telemetry::TelemetryInfo *entry) override {
184     // Does nothing.
185     return llvm::Error::success();
186   }
187 
NoOpTelemetryManager()188   explicit NoOpTelemetryManager()
189       : TelemetryManager(std::make_unique<LLDBConfig>(
190             /*EnableTelemetry=*/false, /*DetailedCommand=*/false,
191             /*ClientTelemery=*/false)) {}
192 
GetInstanceName() const193   virtual llvm::StringRef GetInstanceName() const override {
194     return "NoOpTelemetryManager";
195   }
196 
DispatchClientTelemetry(const lldb_private::StructuredDataImpl & entry,Debugger * debugger)197   void DispatchClientTelemetry(const lldb_private::StructuredDataImpl &entry,
198                                Debugger *debugger) override {
199     // Does nothing.
200   }
201 
dispatch(llvm::telemetry::TelemetryInfo * entry)202   llvm::Error dispatch(llvm::telemetry::TelemetryInfo *entry) override {
203     // Does nothing.
204     return llvm::Error::success();
205   }
206 
GetInstance()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 
214 std::unique_ptr<TelemetryManager> TelemetryManager::g_instance = nullptr;
GetInstance()215 TelemetryManager *TelemetryManager::GetInstance() {
216   // If Telemetry is disabled or if there is no default instance, then use the
217   // NoOp manager. We use a dummy instance to avoid having to do nullchecks in
218   // various places.
219   if (!Config::BuildTimeEnableTelemetry || !g_instance)
220     return NoOpTelemetryManager::GetInstance();
221   return g_instance.get();
222 }
223 
SetInstance(std::unique_ptr<TelemetryManager> manager)224 void TelemetryManager::SetInstance(std::unique_ptr<TelemetryManager> manager) {
225   if (Config::BuildTimeEnableTelemetry)
226     g_instance = std::move(manager);
227 }
228 
229 } // namespace telemetry
230 } // namespace lldb_private
231