Skip to content

Commit e8a518b

Browse files
oontvooJDevlieghere
authored andcommitted
[LLDB][Telemetry]Define telemetry::CommandInfo (llvm#129354)
and collect telemetry about a command's execution. *NOTE: Please consider this PR a DRAFT ( Waiting on PR/127696 to be submitted. ) --------- Co-authored-by: Jonas Devlieghere <[email protected]>
1 parent 6f3d05b commit e8a518b

File tree

4 files changed

+200
-50
lines changed

4 files changed

+200
-50
lines changed

lldb/include/lldb/Core/Telemetry.h

Lines changed: 97 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
#include "lldb/Interpreter/CommandReturnObject.h"
1414
#include "lldb/Utility/LLDBLog.h"
1515
#include "lldb/Utility/StructuredData.h"
16+
#include "lldb/Utility/UUID.h"
1617
#include "lldb/lldb-forward.h"
1718
#include "llvm/ADT/FunctionExtras.h"
1819
#include "llvm/ADT/StringExtras.h"
1920
#include "llvm/ADT/StringRef.h"
2021
#include "llvm/Support/JSON.h"
2122
#include "llvm/Telemetry/Telemetry.h"
23+
#include <atomic>
2224
#include <chrono>
2325
#include <ctime>
2426
#include <memory>
@@ -28,6 +30,17 @@
2830
namespace lldb_private {
2931
namespace telemetry {
3032

33+
struct LLDBConfig : public ::llvm::telemetry::Config {
34+
// If true, we will collect full details about a debug command (eg., args and
35+
// original command). Note: This may contain PII, hence can only be enabled by
36+
// the vendor while creating the Manager.
37+
const bool detailed_command_telemetry;
38+
39+
explicit LLDBConfig(bool enable_telemetry, bool detailed_command_telemetry)
40+
: ::llvm::telemetry::Config(enable_telemetry),
41+
detailed_command_telemetry(detailed_command_telemetry) {}
42+
};
43+
3144
// We expect each (direct) subclass of LLDBTelemetryInfo to
3245
// have an LLDBEntryKind in the form 0b11xxxxxxxx
3346
// Specifically:
@@ -37,6 +50,7 @@ namespace telemetry {
3750
// must have their LLDBEntryKind in the similar form (ie., share common prefix)
3851
struct LLDBEntryKind : public ::llvm::telemetry::EntryKind {
3952
static const llvm::telemetry::KindType BaseInfo = 0b11000000;
53+
static const llvm::telemetry::KindType CommandInfo = 0b11010000;
4054
static const llvm::telemetry::KindType DebuggerInfo = 0b11000100;
4155
};
4256

@@ -66,6 +80,52 @@ struct LLDBBaseTelemetryInfo : public llvm::telemetry::TelemetryInfo {
6680
void serialize(llvm::telemetry::Serializer &serializer) const override;
6781
};
6882

83+
struct CommandInfo : public LLDBBaseTelemetryInfo {
84+
/// If the command is/can be associated with a target entry this field
85+
/// contains that target's UUID. <EMPTY> otherwise.
86+
UUID target_uuid;
87+
/// A unique ID for a command so the manager can match the start entry with
88+
/// its end entry. These values only need to be unique within the same
89+
/// session. Necessary because we'd send off an entry right before a command's
90+
/// execution and another right after. This is to avoid losing telemetry if
91+
/// the command does not execute successfully.
92+
uint64_t command_id;
93+
/// The command name(eg., "breakpoint set")
94+
std::string command_name;
95+
/// These two fields are not collected by default due to PII risks.
96+
/// Vendor may allow them by setting the
97+
/// LLDBConfig::detailed_command_telemetry.
98+
/// @{
99+
std::optional<std::string> original_command;
100+
std::optional<std::string> args;
101+
/// @}
102+
/// Return status of a command and any error description in case of error.
103+
std::optional<lldb::ReturnStatus> ret_status;
104+
std::optional<std::string> error_data;
105+
106+
CommandInfo() = default;
107+
108+
llvm::telemetry::KindType getKind() const override {
109+
return LLDBEntryKind::CommandInfo;
110+
}
111+
112+
static bool classof(const llvm::telemetry::TelemetryInfo *T) {
113+
return (T->getKind() & LLDBEntryKind::CommandInfo) ==
114+
LLDBEntryKind::CommandInfo;
115+
}
116+
117+
void serialize(llvm::telemetry::Serializer &serializer) const override;
118+
119+
static uint64_t GetNextId();
120+
121+
private:
122+
// We assign each command (in the same session) a unique id so that their
123+
// "start" and "end" entries can be matched up.
124+
// These values don't need to be unique across runs (because they are
125+
// secondary-key), hence a simple counter is sufficent.
126+
static std::atomic<uint64_t> g_command_id_seed;
127+
};
128+
69129
struct DebuggerInfo : public LLDBBaseTelemetryInfo {
70130
std::string lldb_version;
71131

@@ -93,64 +153,76 @@ class TelemetryManager : public llvm::telemetry::Manager {
93153
public:
94154
llvm::Error preDispatch(llvm::telemetry::TelemetryInfo *entry) override;
95155

96-
const llvm::telemetry::Config *GetConfig();
156+
const LLDBConfig *GetConfig() { return m_config.get(); }
97157

98158
virtual llvm::StringRef GetInstanceName() const = 0;
99159

100160
static TelemetryManager *GetInstance();
101161

102-
static TelemetryManager *GetInstanceIfEnabled();
103-
104162
protected:
105-
TelemetryManager(std::unique_ptr<llvm::telemetry::Config> config);
163+
TelemetryManager(std::unique_ptr<LLDBConfig> config);
106164

107165
static void SetInstance(std::unique_ptr<TelemetryManager> manger);
108166

109167
private:
110-
std::unique_ptr<llvm::telemetry::Config> m_config;
168+
std::unique_ptr<LLDBConfig> m_config;
111169
// Each instance of a TelemetryManager is assigned a unique ID.
112170
const std::string m_id;
113-
114171
static std::unique_ptr<TelemetryManager> g_instance;
115172
};
116173

117174
/// Helper RAII class for collecting telemetry.
118175
template <typename Info> struct ScopedDispatcher {
119176
// The debugger pointer is optional because we may not have a debugger yet.
120177
// In that case, caller must set the debugger later.
121-
ScopedDispatcher(llvm::unique_function<void(Info *info)> callback,
178+
ScopedDispatcher(Debugger *debugger = nullptr) {
179+
// Start the timer.
180+
m_start_time = std::chrono::steady_clock::now();
181+
this->debugger = debugger;
182+
}
183+
ScopedDispatcher(llvm::unique_function<void(Info *info)> final_callback,
122184
Debugger *debugger = nullptr)
123-
: m_callback(std::move(callback)) {
185+
: m_final_callback(std::move(final_callback)) {
124186
// Start the timer.
125187
m_start_time = std::chrono::steady_clock::now();
126-
m_info.debugger = debugger;
188+
this->debugger = debugger;
127189
}
128190

129-
void SetDebugger(Debugger *debugger) { m_info.debugger = debugger; }
191+
void SetDebugger(Debugger *debugger) { this->debugger = debugger; }
130192

131-
~ScopedDispatcher() {
132-
// If Telemetry is disabled (either at buildtime or runtime),
133-
// then don't do anything.
134-
TelemetryManager *manager = TelemetryManager::GetInstanceIfEnabled();
135-
if (!manager)
136-
return;
193+
void DispatchOnExit(llvm::unique_function<void(Info *info)> final_callback) {
194+
// We probably should not be overriding previously set cb.
195+
assert(!m_final_callback);
196+
m_final_callback = std::move(final_callback);
197+
}
137198

138-
m_info.start_time = m_start_time;
139-
// Populate common fields that we can only set now.
140-
m_info.end_time = std::chrono::steady_clock::now();
141-
// The callback will set the remaining fields.
142-
m_callback(&m_info);
199+
void DispatchNow(llvm::unique_function<void(Info *info)> populate_fields_cb) {
200+
TelemetryManager *manager = TelemetryManager::GetInstance();
201+
if (!manager->GetConfig()->EnableTelemetry)
202+
return;
203+
Info info;
204+
// Populate the common fields we know about.
205+
info.start_time = m_start_time;
206+
info.end_time = std::chrono::steady_clock::now();
207+
info.debugger = debugger;
208+
// The callback will set the rest.
209+
populate_fields_cb(&info);
143210
// And then we dispatch.
144-
if (llvm::Error er = manager->dispatch(&m_info)) {
211+
if (llvm::Error er = manager->dispatch(&info)) {
145212
LLDB_LOG_ERROR(GetLog(LLDBLog::Object), std::move(er),
146-
"Failed to dispatch entry of type: {0}", m_info.getKind());
213+
"Failed to dispatch entry of type: {0}", info.getKind());
147214
}
148215
}
149216

217+
~ScopedDispatcher() {
218+
if (m_final_callback)
219+
DispatchNow(std::move(m_final_callback));
220+
}
221+
150222
private:
151223
SteadyTimePoint m_start_time;
152-
llvm::unique_function<void(Info *info)> m_callback;
153-
Info m_info;
224+
llvm::unique_function<void(Info *info)> m_final_callback;
225+
Debugger *debugger;
154226
};
155227

156228
} // namespace telemetry

lldb/source/Core/Telemetry.cpp

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,33 @@ void LLDBBaseTelemetryInfo::serialize(Serializer &serializer) const {
6060
serializer.write("end_time", ToNanosec(end_time.value()));
6161
}
6262

63+
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+
6379
void DebuggerInfo::serialize(Serializer &serializer) const {
6480
LLDBBaseTelemetryInfo::serialize(serializer);
6581

6682
serializer.write("lldb_version", lldb_version);
6783
serializer.write("is_exit_entry", is_exit_entry);
6884
}
6985

70-
TelemetryManager::TelemetryManager(std::unique_ptr<Config> config)
86+
std::atomic<uint64_t> CommandInfo::g_command_id_seed = 0;
87+
uint64_t CommandInfo::GetNextId() { return g_command_id_seed.fetch_add(1); }
88+
89+
TelemetryManager::TelemetryManager(std::unique_ptr<LLDBConfig> config)
7190
: m_config(std::move(config)), m_id(MakeUUID()) {}
7291

7392
llvm::Error TelemetryManager::preDispatch(TelemetryInfo *entry) {
@@ -79,23 +98,41 @@ llvm::Error TelemetryManager::preDispatch(TelemetryInfo *entry) {
7998
return llvm::Error::success();
8099
}
81100

82-
const Config *TelemetryManager::GetConfig() { return m_config.get(); }
101+
class NoOpTelemetryManager : public TelemetryManager {
102+
public:
103+
llvm::Error preDispatch(llvm::telemetry::TelemetryInfo *entry) override {
104+
// Does nothing.
105+
return llvm::Error::success();
106+
}
83107

84-
std::unique_ptr<TelemetryManager> TelemetryManager::g_instance = nullptr;
85-
TelemetryManager *TelemetryManager::GetInstance() {
86-
if (!Config::BuildTimeEnableTelemetry)
87-
return nullptr;
88-
return g_instance.get();
89-
}
108+
explicit NoOpTelemetryManager()
109+
: TelemetryManager(std::make_unique<LLDBConfig>(
110+
/*EnableTelemetry*/ false, /*DetailedCommand*/ false)) {}
111+
112+
virtual llvm::StringRef GetInstanceName() const override {
113+
return "NoOpTelemetryManager";
114+
}
115+
116+
llvm::Error dispatch(llvm::telemetry::TelemetryInfo *entry) override {
117+
// Does nothing.
118+
return llvm::Error::success();
119+
}
90120

91-
TelemetryManager *TelemetryManager::GetInstanceIfEnabled() {
92-
// Telemetry may be enabled at build time but disabled at runtime.
93-
if (TelemetryManager *ins = TelemetryManager::GetInstance()) {
94-
if (ins->GetConfig()->EnableTelemetry)
95-
return ins;
121+
static NoOpTelemetryManager *GetInstance() {
122+
static std::unique_ptr<NoOpTelemetryManager> g_ins =
123+
std::make_unique<NoOpTelemetryManager>();
124+
return g_ins.get();
96125
}
126+
};
97127

98-
return nullptr;
128+
std::unique_ptr<TelemetryManager> TelemetryManager::g_instance = nullptr;
129+
TelemetryManager *TelemetryManager::GetInstance() {
130+
// If Telemetry is disabled or if there is no default instance, then use the
131+
// NoOp manager. We use a dummy instance to avoid having to do nullchecks in
132+
// various places.
133+
if (!Config::BuildTimeEnableTelemetry || !g_instance)
134+
return NoOpTelemetryManager::GetInstance();
135+
return g_instance.get();
99136
}
100137

101138
void TelemetryManager::SetInstance(std::unique_ptr<TelemetryManager> manager) {

0 commit comments

Comments
 (0)