# Added/changed options
The following options are **added** to the `statistics dump` command:
* `--targets=bool`: Boolean. Dumps the `targets` section.
* `--modules=bool`: Boolean. Dumps the `modules` section.
When both options are given, the field `moduleIdentifiers` will be
dumped for each target in the `targets` section.
The following options are **changed**:
* `--transcript=bool`: Changed to a boolean. Dumps the `transcript`
section.
# Behavior of `statistics dump` with various options
The behavior is **backward compatible**:
- When no options are provided, `statistics dump` dumps all sections.
- When `--summary` is provided, only dumps the summary info.
**New** behavior:
- `--targets=bool`, `--modules=bool`, `--transcript=bool` overrides the
above "default".
For **example**:
- `statistics dump --modules=false` dumps summary + targets +
transcript. No modules.
- `statistics dump --summary --targets=true --transcript=true` dumps
summary + targets (in summary mode) + transcript.
# Added options into public API
In `SBStatisticsOptions`, add:
* `Set/GetIncludeTargets`
* `Set/GetIncludeModules`
* `Set/GetIncludeTranscript`
**Alternative considered**: Thought about adding
`Set/GetIncludeSections(string sections_spec)`, which receives a
comma-separated list of section names to be included ("targets",
"modules", "transcript"). The **benefit** of this approach is that the
API is more future-proof when it comes to possible adding/changing of
section names. **However**, I feel the section names are likely to
remain unchanged for a while - it's not like we plan to make big changes
to the output of `statistics dump` any time soon. The **downsides** of
this approach are: 1\ the readability of the API is worse (requires
reading doc to understand what string can be accepted), 2\ string input
are more prone to human error (e.g. typo "target" instead of expected
"targets").
# Tests
```
bin/llvm-lit -sv ../external/llvm-project/lldb/test/API/commands/statistics/basic/TestStats.py
```
```
./tools/lldb/unittests/Interpreter/InterpreterTests
```
New test cases have been added to verify:
* Different sections are dumped/not dumped when different
`StatisticsOptions` are given through command line (CLI or
`HandleCommand`; see `test_sections_existence_through_command`) or API
(see `test_sections_existence_through_api`).
* The order in which the options are given in command line does not
matter (see `test_order_of_options_do_not_matter`).
---------
Co-authored-by: Roy Shi <royshi@meta.com>
411 lines
17 KiB
C++
411 lines
17 KiB
C++
//===-- Statistics.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/Target/Statistics.h"
|
|
|
|
#include "lldb/Core/Debugger.h"
|
|
#include "lldb/Core/Module.h"
|
|
#include "lldb/Interpreter/CommandInterpreter.h"
|
|
#include "lldb/Symbol/SymbolFile.h"
|
|
#include "lldb/Target/DynamicLoader.h"
|
|
#include "lldb/Target/Process.h"
|
|
#include "lldb/Target/Target.h"
|
|
#include "lldb/Target/UnixSignals.h"
|
|
#include "lldb/Utility/StructuredData.h"
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
using namespace llvm;
|
|
|
|
static void EmplaceSafeString(llvm::json::Object &obj, llvm::StringRef key,
|
|
const std::string &str) {
|
|
if (str.empty())
|
|
return;
|
|
if (LLVM_LIKELY(llvm::json::isUTF8(str)))
|
|
obj.try_emplace(key, str);
|
|
else
|
|
obj.try_emplace(key, llvm::json::fixUTF8(str));
|
|
}
|
|
|
|
json::Value StatsSuccessFail::ToJSON() const {
|
|
return json::Object{{"successes", successes}, {"failures", failures}};
|
|
}
|
|
|
|
static double elapsed(const StatsTimepoint &start, const StatsTimepoint &end) {
|
|
StatsDuration::Duration elapsed =
|
|
end.time_since_epoch() - start.time_since_epoch();
|
|
return elapsed.count();
|
|
}
|
|
|
|
void TargetStats::CollectStats(Target &target) {
|
|
m_module_identifiers.clear();
|
|
for (ModuleSP module_sp : target.GetImages().Modules())
|
|
m_module_identifiers.emplace_back((intptr_t)module_sp.get());
|
|
}
|
|
|
|
json::Value ModuleStats::ToJSON() const {
|
|
json::Object module;
|
|
EmplaceSafeString(module, "path", path);
|
|
EmplaceSafeString(module, "uuid", uuid);
|
|
EmplaceSafeString(module, "triple", triple);
|
|
module.try_emplace("identifier", identifier);
|
|
module.try_emplace("symbolTableParseTime", symtab_parse_time);
|
|
module.try_emplace("symbolTableIndexTime", symtab_index_time);
|
|
module.try_emplace("symbolTableLoadedFromCache", symtab_loaded_from_cache);
|
|
module.try_emplace("symbolTableSavedToCache", symtab_saved_to_cache);
|
|
module.try_emplace("debugInfoParseTime", debug_parse_time);
|
|
module.try_emplace("debugInfoIndexTime", debug_index_time);
|
|
module.try_emplace("debugInfoByteSize", (int64_t)debug_info_size);
|
|
module.try_emplace("debugInfoIndexLoadedFromCache",
|
|
debug_info_index_loaded_from_cache);
|
|
module.try_emplace("debugInfoIndexSavedToCache",
|
|
debug_info_index_saved_to_cache);
|
|
module.try_emplace("debugInfoEnabled", debug_info_enabled);
|
|
module.try_emplace("debugInfoHadVariableErrors",
|
|
debug_info_had_variable_errors);
|
|
module.try_emplace("debugInfoHadIncompleteTypes",
|
|
debug_info_had_incomplete_types);
|
|
module.try_emplace("symbolTableStripped", symtab_stripped);
|
|
if (!symfile_path.empty())
|
|
module.try_emplace("symbolFilePath", symfile_path);
|
|
|
|
if (!symfile_modules.empty()) {
|
|
json::Array symfile_ids;
|
|
for (const auto symfile_id: symfile_modules)
|
|
symfile_ids.emplace_back(symfile_id);
|
|
module.try_emplace("symbolFileModuleIdentifiers", std::move(symfile_ids));
|
|
}
|
|
|
|
if (!type_system_stats.empty()) {
|
|
json::Array type_systems;
|
|
for (const auto &entry : type_system_stats) {
|
|
json::Object obj;
|
|
obj.try_emplace(entry.first().str(), entry.second);
|
|
type_systems.emplace_back(std::move(obj));
|
|
}
|
|
module.try_emplace("typeSystemInfo", std::move(type_systems));
|
|
}
|
|
|
|
return module;
|
|
}
|
|
|
|
llvm::json::Value ConstStringStats::ToJSON() const {
|
|
json::Object obj;
|
|
obj.try_emplace<int64_t>("bytesTotal", stats.GetBytesTotal());
|
|
obj.try_emplace<int64_t>("bytesUsed", stats.GetBytesUsed());
|
|
obj.try_emplace<int64_t>("bytesUnused", stats.GetBytesUnused());
|
|
return obj;
|
|
}
|
|
|
|
json::Value
|
|
TargetStats::ToJSON(Target &target,
|
|
const lldb_private::StatisticsOptions &options) {
|
|
json::Object target_metrics_json;
|
|
ProcessSP process_sp = target.GetProcessSP();
|
|
const bool summary_only = options.GetSummaryOnly();
|
|
const bool include_modules = options.GetIncludeModules();
|
|
if (!summary_only) {
|
|
CollectStats(target);
|
|
|
|
json::Array json_module_uuid_array;
|
|
for (auto module_identifier : m_module_identifiers)
|
|
json_module_uuid_array.emplace_back(module_identifier);
|
|
|
|
target_metrics_json.try_emplace(m_expr_eval.name, m_expr_eval.ToJSON());
|
|
target_metrics_json.try_emplace(m_frame_var.name, m_frame_var.ToJSON());
|
|
if (include_modules)
|
|
target_metrics_json.try_emplace("moduleIdentifiers",
|
|
std::move(json_module_uuid_array));
|
|
|
|
if (m_launch_or_attach_time && m_first_private_stop_time) {
|
|
double elapsed_time =
|
|
elapsed(*m_launch_or_attach_time, *m_first_private_stop_time);
|
|
target_metrics_json.try_emplace("launchOrAttachTime", elapsed_time);
|
|
}
|
|
if (m_launch_or_attach_time && m_first_public_stop_time) {
|
|
double elapsed_time =
|
|
elapsed(*m_launch_or_attach_time, *m_first_public_stop_time);
|
|
target_metrics_json.try_emplace("firstStopTime", elapsed_time);
|
|
}
|
|
target_metrics_json.try_emplace("targetCreateTime",
|
|
m_create_time.get().count());
|
|
|
|
json::Array breakpoints_array;
|
|
double totalBreakpointResolveTime = 0.0;
|
|
// Report both the normal breakpoint list and the internal breakpoint list.
|
|
for (int i = 0; i < 2; ++i) {
|
|
BreakpointList &breakpoints = target.GetBreakpointList(i == 1);
|
|
std::unique_lock<std::recursive_mutex> lock;
|
|
breakpoints.GetListMutex(lock);
|
|
size_t num_breakpoints = breakpoints.GetSize();
|
|
for (size_t i = 0; i < num_breakpoints; i++) {
|
|
Breakpoint *bp = breakpoints.GetBreakpointAtIndex(i).get();
|
|
breakpoints_array.push_back(bp->GetStatistics());
|
|
totalBreakpointResolveTime += bp->GetResolveTime().count();
|
|
}
|
|
}
|
|
target_metrics_json.try_emplace("breakpoints",
|
|
std::move(breakpoints_array));
|
|
target_metrics_json.try_emplace("totalBreakpointResolveTime",
|
|
totalBreakpointResolveTime);
|
|
|
|
if (process_sp) {
|
|
UnixSignalsSP unix_signals_sp = process_sp->GetUnixSignals();
|
|
if (unix_signals_sp)
|
|
target_metrics_json.try_emplace(
|
|
"signals", unix_signals_sp->GetHitCountStatistics());
|
|
}
|
|
}
|
|
|
|
// Counting "totalSharedLibraryEventHitCount" from breakpoints of kind
|
|
// "shared-library-event".
|
|
{
|
|
uint32_t shared_library_event_breakpoint_hit_count = 0;
|
|
// The "shared-library-event" is only found in the internal breakpoint list.
|
|
BreakpointList &breakpoints = target.GetBreakpointList(/* internal */ true);
|
|
std::unique_lock<std::recursive_mutex> lock;
|
|
breakpoints.GetListMutex(lock);
|
|
size_t num_breakpoints = breakpoints.GetSize();
|
|
for (size_t i = 0; i < num_breakpoints; i++) {
|
|
Breakpoint *bp = breakpoints.GetBreakpointAtIndex(i).get();
|
|
if (strcmp(bp->GetBreakpointKind(), "shared-library-event") == 0)
|
|
shared_library_event_breakpoint_hit_count += bp->GetHitCount();
|
|
}
|
|
|
|
target_metrics_json.try_emplace("totalSharedLibraryEventHitCount",
|
|
shared_library_event_breakpoint_hit_count);
|
|
}
|
|
|
|
if (process_sp) {
|
|
uint32_t stop_id = process_sp->GetStopID();
|
|
target_metrics_json.try_emplace("stopCount", stop_id);
|
|
|
|
llvm::StringRef dyld_plugin_name;
|
|
if (process_sp->GetDynamicLoader())
|
|
dyld_plugin_name = process_sp->GetDynamicLoader()->GetPluginName();
|
|
target_metrics_json.try_emplace("dyldPluginName", dyld_plugin_name);
|
|
}
|
|
target_metrics_json.try_emplace("sourceMapDeduceCount",
|
|
m_source_map_deduce_count);
|
|
return target_metrics_json;
|
|
}
|
|
|
|
void TargetStats::SetLaunchOrAttachTime() {
|
|
m_launch_or_attach_time = StatsClock::now();
|
|
m_first_private_stop_time = std::nullopt;
|
|
}
|
|
|
|
void TargetStats::SetFirstPrivateStopTime() {
|
|
// Launching and attaching has many paths depending on if synchronous mode
|
|
// was used or if we are stopping at the entry point or not. Only set the
|
|
// first stop time if it hasn't already been set.
|
|
if (!m_first_private_stop_time)
|
|
m_first_private_stop_time = StatsClock::now();
|
|
}
|
|
|
|
void TargetStats::SetFirstPublicStopTime() {
|
|
// Launching and attaching has many paths depending on if synchronous mode
|
|
// was used or if we are stopping at the entry point or not. Only set the
|
|
// first stop time if it hasn't already been set.
|
|
if (!m_first_public_stop_time)
|
|
m_first_public_stop_time = StatsClock::now();
|
|
}
|
|
|
|
void TargetStats::IncreaseSourceMapDeduceCount() {
|
|
++m_source_map_deduce_count;
|
|
}
|
|
|
|
bool DebuggerStats::g_collecting_stats = false;
|
|
|
|
llvm::json::Value DebuggerStats::ReportStatistics(
|
|
Debugger &debugger, Target *target,
|
|
const lldb_private::StatisticsOptions &options) {
|
|
|
|
const bool summary_only = options.GetSummaryOnly();
|
|
const bool load_all_debug_info = options.GetLoadAllDebugInfo();
|
|
const bool include_targets = options.GetIncludeTargets();
|
|
const bool include_modules = options.GetIncludeModules();
|
|
const bool include_transcript = options.GetIncludeTranscript();
|
|
|
|
json::Array json_targets;
|
|
json::Array json_modules;
|
|
double symtab_parse_time = 0.0;
|
|
double symtab_index_time = 0.0;
|
|
double debug_parse_time = 0.0;
|
|
double debug_index_time = 0.0;
|
|
uint32_t symtabs_loaded = 0;
|
|
uint32_t symtabs_saved = 0;
|
|
uint32_t debug_index_loaded = 0;
|
|
uint32_t debug_index_saved = 0;
|
|
uint64_t debug_info_size = 0;
|
|
|
|
std::vector<ModuleStats> modules;
|
|
std::lock_guard<std::recursive_mutex> guard(
|
|
Module::GetAllocationModuleCollectionMutex());
|
|
const uint64_t num_modules = Module::GetNumberAllocatedModules();
|
|
uint32_t num_debug_info_enabled_modules = 0;
|
|
uint32_t num_modules_has_debug_info = 0;
|
|
uint32_t num_modules_with_variable_errors = 0;
|
|
uint32_t num_modules_with_incomplete_types = 0;
|
|
uint32_t num_stripped_modules = 0;
|
|
for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) {
|
|
Module *module = Module::GetAllocatedModuleAtIndex(image_idx);
|
|
ModuleStats module_stat;
|
|
module_stat.symtab_parse_time = module->GetSymtabParseTime().get().count();
|
|
module_stat.symtab_index_time = module->GetSymtabIndexTime().get().count();
|
|
Symtab *symtab = module->GetSymtab();
|
|
if (symtab) {
|
|
module_stat.symtab_loaded_from_cache = symtab->GetWasLoadedFromCache();
|
|
if (module_stat.symtab_loaded_from_cache)
|
|
++symtabs_loaded;
|
|
module_stat.symtab_saved_to_cache = symtab->GetWasSavedToCache();
|
|
if (module_stat.symtab_saved_to_cache)
|
|
++symtabs_saved;
|
|
}
|
|
SymbolFile *sym_file = module->GetSymbolFile();
|
|
if (sym_file) {
|
|
if (!summary_only) {
|
|
if (sym_file->GetObjectFile() != module->GetObjectFile())
|
|
module_stat.symfile_path =
|
|
sym_file->GetObjectFile()->GetFileSpec().GetPath();
|
|
ModuleList symbol_modules = sym_file->GetDebugInfoModules();
|
|
for (const auto &symbol_module : symbol_modules.Modules())
|
|
module_stat.symfile_modules.push_back((intptr_t)symbol_module.get());
|
|
}
|
|
module_stat.debug_info_index_loaded_from_cache =
|
|
sym_file->GetDebugInfoIndexWasLoadedFromCache();
|
|
if (module_stat.debug_info_index_loaded_from_cache)
|
|
++debug_index_loaded;
|
|
module_stat.debug_info_index_saved_to_cache =
|
|
sym_file->GetDebugInfoIndexWasSavedToCache();
|
|
if (module_stat.debug_info_index_saved_to_cache)
|
|
++debug_index_saved;
|
|
module_stat.debug_index_time = sym_file->GetDebugInfoIndexTime().count();
|
|
module_stat.debug_parse_time = sym_file->GetDebugInfoParseTime().count();
|
|
module_stat.debug_info_size =
|
|
sym_file->GetDebugInfoSize(load_all_debug_info);
|
|
module_stat.symtab_stripped = module->GetObjectFile()->IsStripped();
|
|
if (module_stat.symtab_stripped)
|
|
++num_stripped_modules;
|
|
module_stat.debug_info_enabled = sym_file->GetLoadDebugInfoEnabled() &&
|
|
module_stat.debug_info_size > 0;
|
|
module_stat.debug_info_had_variable_errors =
|
|
sym_file->GetDebugInfoHadFrameVariableErrors();
|
|
if (module_stat.debug_info_enabled)
|
|
++num_debug_info_enabled_modules;
|
|
if (module_stat.debug_info_size > 0)
|
|
++num_modules_has_debug_info;
|
|
if (module_stat.debug_info_had_variable_errors)
|
|
++num_modules_with_variable_errors;
|
|
}
|
|
symtab_parse_time += module_stat.symtab_parse_time;
|
|
symtab_index_time += module_stat.symtab_index_time;
|
|
debug_parse_time += module_stat.debug_parse_time;
|
|
debug_index_time += module_stat.debug_index_time;
|
|
debug_info_size += module_stat.debug_info_size;
|
|
module->ForEachTypeSystem([&](lldb::TypeSystemSP ts) {
|
|
if (auto stats = ts->ReportStatistics())
|
|
module_stat.type_system_stats.insert({ts->GetPluginName(), *stats});
|
|
if (ts->GetHasForcefullyCompletedTypes())
|
|
module_stat.debug_info_had_incomplete_types = true;
|
|
return true;
|
|
});
|
|
if (module_stat.debug_info_had_incomplete_types)
|
|
++num_modules_with_incomplete_types;
|
|
|
|
if (include_modules) {
|
|
module_stat.identifier = (intptr_t)module;
|
|
module_stat.path = module->GetFileSpec().GetPath();
|
|
if (ConstString object_name = module->GetObjectName()) {
|
|
module_stat.path.append(1, '(');
|
|
module_stat.path.append(object_name.GetStringRef().str());
|
|
module_stat.path.append(1, ')');
|
|
}
|
|
module_stat.uuid = module->GetUUID().GetAsString();
|
|
module_stat.triple = module->GetArchitecture().GetTriple().str();
|
|
json_modules.emplace_back(module_stat.ToJSON());
|
|
}
|
|
}
|
|
|
|
json::Object global_stats{
|
|
{"totalSymbolTableParseTime", symtab_parse_time},
|
|
{"totalSymbolTableIndexTime", symtab_index_time},
|
|
{"totalSymbolTablesLoadedFromCache", symtabs_loaded},
|
|
{"totalSymbolTablesSavedToCache", symtabs_saved},
|
|
{"totalDebugInfoParseTime", debug_parse_time},
|
|
{"totalDebugInfoIndexTime", debug_index_time},
|
|
{"totalDebugInfoIndexLoadedFromCache", debug_index_loaded},
|
|
{"totalDebugInfoIndexSavedToCache", debug_index_saved},
|
|
{"totalDebugInfoByteSize", debug_info_size},
|
|
{"totalModuleCount", num_modules},
|
|
{"totalModuleCountHasDebugInfo", num_modules_has_debug_info},
|
|
{"totalModuleCountWithVariableErrors", num_modules_with_variable_errors},
|
|
{"totalModuleCountWithIncompleteTypes",
|
|
num_modules_with_incomplete_types},
|
|
{"totalDebugInfoEnabled", num_debug_info_enabled_modules},
|
|
{"totalSymbolTableStripped", num_stripped_modules},
|
|
};
|
|
|
|
if (include_targets) {
|
|
if (target) {
|
|
json_targets.emplace_back(target->ReportStatistics(options));
|
|
} else {
|
|
for (const auto &target : debugger.GetTargetList().Targets())
|
|
json_targets.emplace_back(target->ReportStatistics(options));
|
|
}
|
|
global_stats.try_emplace("targets", std::move(json_targets));
|
|
}
|
|
|
|
ConstStringStats const_string_stats;
|
|
json::Object json_memory{
|
|
{"strings", const_string_stats.ToJSON()},
|
|
};
|
|
global_stats.try_emplace("memory", std::move(json_memory));
|
|
if (!summary_only) {
|
|
json::Value cmd_stats = debugger.GetCommandInterpreter().GetStatistics();
|
|
global_stats.try_emplace("commands", std::move(cmd_stats));
|
|
}
|
|
|
|
if (include_modules) {
|
|
global_stats.try_emplace("modules", std::move(json_modules));
|
|
}
|
|
|
|
if (include_transcript) {
|
|
// When transcript is available, add it to the to-be-returned statistics.
|
|
//
|
|
// NOTE:
|
|
// When the statistics is polled by an LLDB command:
|
|
// - The transcript in the returned statistics *will NOT* contain the
|
|
// returned statistics itself (otherwise infinite recursion).
|
|
// - The returned statistics *will* be written to the internal transcript
|
|
// buffer. It *will* appear in the next statistcs or transcript poll.
|
|
//
|
|
// For example, let's say the following commands are run in order:
|
|
// - "version"
|
|
// - "statistics dump" <- call it "A"
|
|
// - "statistics dump" <- call it "B"
|
|
// The output of "A" will contain the transcript of "version" and
|
|
// "statistics dump" (A), with the latter having empty output. The output
|
|
// of B will contain the trascnript of "version", "statistics dump" (A),
|
|
// "statistics dump" (B), with A's output populated and B's output empty.
|
|
const StructuredData::Array &transcript =
|
|
debugger.GetCommandInterpreter().GetTranscript();
|
|
if (transcript.GetSize() != 0) {
|
|
std::string buffer;
|
|
llvm::raw_string_ostream ss(buffer);
|
|
json::OStream json_os(ss);
|
|
transcript.Serialize(json_os);
|
|
if (auto json_transcript = llvm::json::parse(ss.str()))
|
|
global_stats.try_emplace("transcript",
|
|
std::move(json_transcript.get()));
|
|
}
|
|
}
|
|
|
|
return std::move(global_stats);
|
|
}
|