-
Notifications
You must be signed in to change notification settings - Fork 15.3k
[LLDB][Telemetry]Define telemetry::CommandInfo #129354
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
5a992af
ff1ce49
d7a554d
4e9d65e
22d15d4
291e923
2d7d713
f875c85
f025bdb
b859e2d
5655d4c
017be75
9d6997b
714d803
481e141
ec598c6
46225e8
a5c46a1
ac03cb0
00bf711
031ee14
9a0d4cb
f55ea59
ebbda8d
c26b651
42e7a62
1d79c13
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -19,6 +19,7 @@ | |||||
| #include "llvm/ADT/StringRef.h" | ||||||
| #include "llvm/Support/JSON.h" | ||||||
| #include "llvm/Telemetry/Telemetry.h" | ||||||
| #include <atomic> | ||||||
| #include <chrono> | ||||||
| #include <ctime> | ||||||
| #include <memory> | ||||||
|
|
@@ -28,6 +29,17 @@ | |||||
| namespace lldb_private { | ||||||
| namespace telemetry { | ||||||
|
|
||||||
| struct LLDBConfig : public ::llvm::telemetry::Config { | ||||||
| // If true, we will collect full details about a debug command (eg., args and | ||||||
| // original command). Note: This may contain PII, hence can only be enabled by | ||||||
| // the vendor while creating the Manager. | ||||||
| const bool m_detailed_command_telemetry; | ||||||
|
|
||||||
| explicit LLDBConfig(bool enable_telemetry, bool detailed_command_telemetry) | ||||||
| : ::llvm::telemetry::Config(enable_telemetry), | ||||||
| m_detailed_command_telemetry(detailed_command_telemetry) {} | ||||||
| }; | ||||||
|
|
||||||
| // We expect each (direct) subclass of LLDBTelemetryInfo to | ||||||
| // have an LLDBEntryKind in the form 0b11xxxxxxxx | ||||||
| // Specifically: | ||||||
|
|
@@ -37,6 +49,7 @@ namespace telemetry { | |||||
| // must have their LLDBEntryKind in the similar form (ie., share common prefix) | ||||||
| struct LLDBEntryKind : public ::llvm::telemetry::EntryKind { | ||||||
| static const llvm::telemetry::KindType BaseInfo = 0b11000000; | ||||||
| static const llvm::telemetry::KindType CommandInfo = 0b11010000; | ||||||
| static const llvm::telemetry::KindType DebuggerInfo = 0b11000100; | ||||||
| }; | ||||||
|
|
||||||
|
|
@@ -66,6 +79,42 @@ struct LLDBBaseTelemetryInfo : public llvm::telemetry::TelemetryInfo { | |||||
| void serialize(llvm::telemetry::Serializer &serializer) const override; | ||||||
| }; | ||||||
|
|
||||||
| struct CommandInfo : public LLDBBaseTelemetryInfo { | ||||||
|
|
||||||
| // If the command is/can be associated with a target entry this field contains | ||||||
| // that target's UUID. <EMPTY> otherwise. | ||||||
|
||||||
| std::string target_uuid; | ||||||
oontvoo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| // A unique ID for a command so the manager can match the start entry with | ||||||
| // its end entry. These values only need to be unique within the same session. | ||||||
| // Necessary because we'd send off an entry right before a command's execution | ||||||
| // and another right after. This is to avoid losing telemetry if the command | ||||||
| // does not execute successfully. | ||||||
| int command_id; | ||||||
oontvoo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| // Eg., "breakpoint set" | ||||||
| std::string command_name; | ||||||
| // !!NOTE!! These two fields are not collected by default due to PII risks. | ||||||
|
||||||
| // !!NOTE!! These two fields are not collected by default due to PII risks. | |
| // These two fields are not collected by default due to PII risks. |
When comments apply to multiple lines, you can use Doxygen groups:
/// Comment spanning multiple things
/// @{
thing one;
thing two;
/// @}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see, the m_detailed_command_telemetry boolean controls a subset of the command, which means my earlier suggestion doesn't work. I can't think of a way to make this more "generic" without breaking up the event, which you probably don't want to do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right - that was the knob for controlling how much data is collected upstream.
(The previous design was to have those DispatchFoo() helper method which could be overriden by vendors to collect more or less data, as needed. There's pros and cons to each ...)
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be worth wrapping these in std::optional to make it more obvious that these fields are optional?
(I thought I left this comment in an earlier comment, but I can't find it so maybe I forgot to submit it)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
oontvoo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
oontvoo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,7 +46,9 @@ | |
| #include "Commands/CommandObjectWatchpoint.h" | ||
|
|
||
| #include "lldb/Core/Debugger.h" | ||
| #include "lldb/Core/Module.h" | ||
| #include "lldb/Core/PluginManager.h" | ||
| #include "lldb/Core/Telemetry.h" | ||
| #include "lldb/Host/StreamFile.h" | ||
| #include "lldb/Utility/ErrorMessages.h" | ||
| #include "lldb/Utility/LLDBLog.h" | ||
|
|
@@ -88,6 +90,7 @@ | |
| #include "llvm/Support/Path.h" | ||
| #include "llvm/Support/PrettyStackTrace.h" | ||
| #include "llvm/Support/ScopedPrinter.h" | ||
| #include "llvm/Telemetry/Telemetry.h" | ||
|
|
||
| #if defined(__APPLE__) | ||
| #include <TargetConditionals.h> | ||
|
|
@@ -1883,9 +1886,49 @@ bool CommandInterpreter::HandleCommand(const char *command_line, | |
| LazyBool lazy_add_to_history, | ||
| CommandReturnObject &result, | ||
| bool force_repeat_command) { | ||
| lldb_private::telemetry::ScopedDispatcher< | ||
| lldb_private::telemetry::CommandInfo> | ||
| helper(&m_debugger); | ||
| lldb_private::telemetry::TelemetryManager *ins = | ||
| lldb_private::telemetry::TelemetryManager::GetInstance(); | ||
oontvoo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const int command_id = ins->MakeNextCommandId(); | ||
|
|
||
| std::string command_string(command_line); | ||
| std::string original_command_string(command_string); | ||
| std::string real_original_command_string(command_string); | ||
| std::string parsed_command_args; | ||
| CommandObject *cmd_obj = nullptr; | ||
|
Comment on lines
+1899
to
+1900
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really dislike that the RAII object forces us to declare variables early like it's C99. Do we need the RAII helper here? Are there early returns we need to account for, or can we just fill in the object at the end of the function?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, unfortunately, there are a bunch of early returns in this function (which is also very long).
|
||
|
|
||
| helper.DispatchNow([&](lldb_private::telemetry::CommandInfo *info) { | ||
| info->command_id = command_id; | ||
| if (Target *target = GetExecutionContext().GetTargetPtr()) { | ||
| // If we have a target attached to this command, then get the UUID. | ||
| info->target_uuid = | ||
| target->GetExecutableModule() != nullptr | ||
| ? target->GetExecutableModule()->GetUUID().GetAsString() | ||
| : ""; | ||
| } | ||
| if (ins->GetConfig()->m_detailed_command_telemetry) | ||
| info->original_command = original_command_string; | ||
| // The rest (eg., command_name, args, etc) hasn't been parsed yet; | ||
| // Those will be collected by the on-exit-callback. | ||
| }); | ||
|
|
||
| helper.DispatchOnExit([&](lldb_private::telemetry::CommandInfo *info) { | ||
| // TODO: this is logging the time the command-handler finishes. | ||
| // But we may want a finer-grain durations too? | ||
| // (ie., the execute_time recorded below?) | ||
| info->command_id = command_id; | ||
| llvm::StringRef command_name = | ||
| cmd_obj ? cmd_obj->GetCommandName() : "<not found>"; | ||
| info->command_name = command_name.str(); | ||
| info->ret_status = result.GetStatus(); | ||
| if (std::string error_str = result.GetErrorString(); !error_str.empty()) | ||
| info->error_data = std::move(error_str); | ||
|
|
||
| if (ins->GetConfig()->m_detailed_command_telemetry) | ||
| info->args = parsed_command_args; | ||
| }); | ||
|
|
||
| Log *log = GetLog(LLDBLog::Commands); | ||
| LLDB_LOGF(log, "Processing command: %s", command_line); | ||
|
|
@@ -1991,7 +2034,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 | ||
|
|
@@ -2053,37 +2096,36 @@ 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 `GetSaveTranscript()`. 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->SetOriginalCommandString(real_original_command_string); | ||
| // Set the indent to the position of the command in the command line. | ||
| pos = real_original_command_string.rfind(remainder); | ||
| pos = real_original_command_string.rfind(parsed_command_args); | ||
| std::optional<uint16_t> indent; | ||
| if (pos != std::string::npos) | ||
| indent = pos; | ||
| result.SetDiagnosticIndent(indent); | ||
| cmd_obj->Execute(remainder.c_str(), result); | ||
| cmd_obj->Execute(parsed_command_args.c_str(), result); | ||
| } | ||
|
|
||
| LLDB_LOGF(log, "HandleCommand, command %s", | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of the Configuration being the one deciding what events we emit. That's easy to audit. Can we make this a bit more generic and maybe have a set of
telemetry::KindTypethat are enabled? We can have a default configuration that enables everything by default upstream (for testing) and then vendors have to override this downstream with the events they're interested in. Originally I imagined doing this in the emitter, but I think the config is a better place to do that. WDYT?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for controlling how much data we collect for a given command - not for which type of events we collect.
But we can also add that if you want. Although you could already do taht by overriding the
::dispatch()to simply return/ignore the kind of entries you dont want to record. At that point, the data had been collected but it's not "leaving" LLDB yet - so still fine? (Or do you want something stricter? eg., not collect the data at all?)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack, I think this is fine as is. Given that this is a
struct, please drop them_prefix.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done