xref: /llvm-project/lldb/source/Target/Statistics.cpp (revision 24feaab8380c69d5fa3eb8c21ef2d660913fd4a9)
1 //===-- Statistics.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 
9 #include "lldb/Target/Statistics.h"
10 
11 #include "lldb/Core/Debugger.h"
12 #include "lldb/Core/Module.h"
13 #include "lldb/Interpreter/CommandInterpreter.h"
14 #include "lldb/Symbol/SymbolFile.h"
15 #include "lldb/Target/DynamicLoader.h"
16 #include "lldb/Target/Process.h"
17 #include "lldb/Target/Target.h"
18 #include "lldb/Target/UnixSignals.h"
19 #include "lldb/Utility/StructuredData.h"
20 
21 using namespace lldb;
22 using namespace lldb_private;
23 using namespace llvm;
24 
25 static void EmplaceSafeString(llvm::json::Object &obj, llvm::StringRef key,
26                               const std::string &str) {
27   if (str.empty())
28     return;
29   if (LLVM_LIKELY(llvm::json::isUTF8(str)))
30     obj.try_emplace(key, str);
31   else
32     obj.try_emplace(key, llvm::json::fixUTF8(str));
33 }
34 
35 json::Value StatsSuccessFail::ToJSON() const {
36   return json::Object{{"successes", successes}, {"failures", failures}};
37 }
38 
39 static double elapsed(const StatsTimepoint &start, const StatsTimepoint &end) {
40   StatsDuration::Duration elapsed =
41       end.time_since_epoch() - start.time_since_epoch();
42   return elapsed.count();
43 }
44 
45 void TargetStats::CollectStats(Target &target) {
46   m_module_identifiers.clear();
47   for (ModuleSP module_sp : target.GetImages().Modules())
48     m_module_identifiers.emplace_back((intptr_t)module_sp.get());
49 }
50 
51 json::Value ModuleStats::ToJSON() const {
52   json::Object module;
53   EmplaceSafeString(module, "path", path);
54   EmplaceSafeString(module, "uuid", uuid);
55   EmplaceSafeString(module, "triple", triple);
56   module.try_emplace("identifier", identifier);
57   module.try_emplace("symbolTableParseTime", symtab_parse_time);
58   module.try_emplace("symbolTableIndexTime", symtab_index_time);
59   module.try_emplace("symbolTableLoadedFromCache", symtab_loaded_from_cache);
60   module.try_emplace("symbolTableSavedToCache", symtab_saved_to_cache);
61   module.try_emplace("debugInfoParseTime", debug_parse_time);
62   module.try_emplace("debugInfoIndexTime", debug_index_time);
63   module.try_emplace("debugInfoByteSize", (int64_t)debug_info_size);
64   module.try_emplace("debugInfoIndexLoadedFromCache",
65                      debug_info_index_loaded_from_cache);
66   module.try_emplace("debugInfoIndexSavedToCache",
67                      debug_info_index_saved_to_cache);
68   module.try_emplace("debugInfoEnabled", debug_info_enabled);
69   module.try_emplace("debugInfoHadVariableErrors",
70                      debug_info_had_variable_errors);
71   module.try_emplace("debugInfoHadIncompleteTypes",
72                      debug_info_had_incomplete_types);
73   module.try_emplace("symbolTableStripped", symtab_stripped);
74   if (!symfile_path.empty())
75     module.try_emplace("symbolFilePath", symfile_path);
76 
77   if (!symfile_modules.empty()) {
78     json::Array symfile_ids;
79     for (const auto symfile_id: symfile_modules)
80       symfile_ids.emplace_back(symfile_id);
81     module.try_emplace("symbolFileModuleIdentifiers", std::move(symfile_ids));
82   }
83 
84   if (!type_system_stats.empty()) {
85     json::Array type_systems;
86     for (const auto &entry : type_system_stats) {
87       json::Object obj;
88       obj.try_emplace(entry.first().str(), entry.second);
89       type_systems.emplace_back(std::move(obj));
90     }
91     module.try_emplace("typeSystemInfo", std::move(type_systems));
92   }
93 
94   return module;
95 }
96 
97 llvm::json::Value ConstStringStats::ToJSON() const {
98   json::Object obj;
99   obj.try_emplace<int64_t>("bytesTotal", stats.GetBytesTotal());
100   obj.try_emplace<int64_t>("bytesUsed", stats.GetBytesUsed());
101   obj.try_emplace<int64_t>("bytesUnused", stats.GetBytesUnused());
102   return obj;
103 }
104 
105 json::Value
106 TargetStats::ToJSON(Target &target,
107                     const lldb_private::StatisticsOptions &options) {
108   json::Object target_metrics_json;
109   ProcessSP process_sp = target.GetProcessSP();
110   const bool summary_only = options.GetSummaryOnly();
111   const bool include_modules = options.GetIncludeModules();
112   if (!summary_only) {
113     CollectStats(target);
114 
115     json::Array json_module_uuid_array;
116     for (auto module_identifier : m_module_identifiers)
117       json_module_uuid_array.emplace_back(module_identifier);
118 
119     target_metrics_json.try_emplace(m_expr_eval.name, m_expr_eval.ToJSON());
120     target_metrics_json.try_emplace(m_frame_var.name, m_frame_var.ToJSON());
121     if (include_modules)
122       target_metrics_json.try_emplace("moduleIdentifiers",
123                                       std::move(json_module_uuid_array));
124 
125     if (m_launch_or_attach_time && m_first_private_stop_time) {
126       double elapsed_time =
127           elapsed(*m_launch_or_attach_time, *m_first_private_stop_time);
128       target_metrics_json.try_emplace("launchOrAttachTime", elapsed_time);
129     }
130     if (m_launch_or_attach_time && m_first_public_stop_time) {
131       double elapsed_time =
132           elapsed(*m_launch_or_attach_time, *m_first_public_stop_time);
133       target_metrics_json.try_emplace("firstStopTime", elapsed_time);
134     }
135     target_metrics_json.try_emplace("targetCreateTime",
136                                     m_create_time.get().count());
137 
138     json::Array breakpoints_array;
139     double totalBreakpointResolveTime = 0.0;
140     // Report both the normal breakpoint list and the internal breakpoint list.
141     for (int i = 0; i < 2; ++i) {
142       BreakpointList &breakpoints = target.GetBreakpointList(i == 1);
143       std::unique_lock<std::recursive_mutex> lock;
144       breakpoints.GetListMutex(lock);
145       size_t num_breakpoints = breakpoints.GetSize();
146       for (size_t i = 0; i < num_breakpoints; i++) {
147         Breakpoint *bp = breakpoints.GetBreakpointAtIndex(i).get();
148         breakpoints_array.push_back(bp->GetStatistics());
149         totalBreakpointResolveTime += bp->GetResolveTime().count();
150       }
151     }
152     target_metrics_json.try_emplace("breakpoints",
153                                     std::move(breakpoints_array));
154     target_metrics_json.try_emplace("totalBreakpointResolveTime",
155                                     totalBreakpointResolveTime);
156 
157     if (process_sp) {
158       UnixSignalsSP unix_signals_sp = process_sp->GetUnixSignals();
159       if (unix_signals_sp)
160         target_metrics_json.try_emplace(
161             "signals", unix_signals_sp->GetHitCountStatistics());
162     }
163   }
164 
165   // Counting "totalSharedLibraryEventHitCount" from breakpoints of kind
166   // "shared-library-event".
167   {
168     uint32_t shared_library_event_breakpoint_hit_count = 0;
169     // The "shared-library-event" is only found in the internal breakpoint list.
170     BreakpointList &breakpoints = target.GetBreakpointList(/* internal */ true);
171     std::unique_lock<std::recursive_mutex> lock;
172     breakpoints.GetListMutex(lock);
173     size_t num_breakpoints = breakpoints.GetSize();
174     for (size_t i = 0; i < num_breakpoints; i++) {
175       Breakpoint *bp = breakpoints.GetBreakpointAtIndex(i).get();
176       if (strcmp(bp->GetBreakpointKind(), "shared-library-event") == 0)
177         shared_library_event_breakpoint_hit_count += bp->GetHitCount();
178     }
179 
180     target_metrics_json.try_emplace("totalSharedLibraryEventHitCount",
181                                     shared_library_event_breakpoint_hit_count);
182   }
183 
184   if (process_sp) {
185     uint32_t stop_id = process_sp->GetStopID();
186     target_metrics_json.try_emplace("stopCount", stop_id);
187 
188     llvm::StringRef dyld_plugin_name;
189     if (process_sp->GetDynamicLoader())
190       dyld_plugin_name = process_sp->GetDynamicLoader()->GetPluginName();
191     target_metrics_json.try_emplace("dyldPluginName", dyld_plugin_name);
192   }
193   target_metrics_json.try_emplace("sourceMapDeduceCount",
194                                   m_source_map_deduce_count);
195   target_metrics_json.try_emplace("sourceRealpathAttemptCount",
196                                   m_source_realpath_attempt_count);
197   target_metrics_json.try_emplace("sourceRealpathCompatibleCount",
198                                   m_source_realpath_compatible_count);
199   target_metrics_json.try_emplace("summaryProviderStatistics",
200                                   target.GetSummaryStatisticsCache().ToJSON());
201   return target_metrics_json;
202 }
203 
204 void TargetStats::Reset(Target &target) {
205   m_launch_or_attach_time.reset();
206   m_first_private_stop_time.reset();
207   m_first_public_stop_time.reset();
208   // Report both the normal breakpoint list and the internal breakpoint list.
209   for (int i = 0; i < 2; ++i) {
210     BreakpointList &breakpoints = target.GetBreakpointList(i == 1);
211     std::unique_lock<std::recursive_mutex> lock;
212     breakpoints.GetListMutex(lock);
213     size_t num_breakpoints = breakpoints.GetSize();
214     for (size_t i = 0; i < num_breakpoints; i++) {
215       Breakpoint *bp = breakpoints.GetBreakpointAtIndex(i).get();
216       bp->ResetStatistics();
217     }
218   }
219   target.GetSummaryStatisticsCache().Reset();
220 }
221 
222 void TargetStats::SetLaunchOrAttachTime() {
223   m_launch_or_attach_time = StatsClock::now();
224   m_first_private_stop_time = std::nullopt;
225 }
226 
227 void TargetStats::SetFirstPrivateStopTime() {
228   // Launching and attaching has many paths depending on if synchronous mode
229   // was used or if we are stopping at the entry point or not. Only set the
230   // first stop time if it hasn't already been set.
231   if (!m_first_private_stop_time)
232     m_first_private_stop_time = StatsClock::now();
233 }
234 
235 void TargetStats::SetFirstPublicStopTime() {
236   // Launching and attaching has many paths depending on if synchronous mode
237   // was used or if we are stopping at the entry point or not. Only set the
238   // first stop time if it hasn't already been set.
239   if (!m_first_public_stop_time)
240     m_first_public_stop_time = StatsClock::now();
241 }
242 
243 void TargetStats::IncreaseSourceMapDeduceCount() {
244   ++m_source_map_deduce_count;
245 }
246 
247 void TargetStats::IncreaseSourceRealpathAttemptCount(uint32_t count) {
248   m_source_realpath_attempt_count += count;
249 }
250 
251 void TargetStats::IncreaseSourceRealpathCompatibleCount(uint32_t count) {
252   m_source_realpath_compatible_count += count;
253 }
254 
255 bool DebuggerStats::g_collecting_stats = false;
256 
257 void DebuggerStats::ResetStatistics(Debugger &debugger, Target *target) {
258   std::lock_guard<std::recursive_mutex> guard(
259       Module::GetAllocationModuleCollectionMutex());
260   const uint64_t num_modules = target != nullptr
261                                    ? target->GetImages().GetSize()
262                                    : Module::GetNumberAllocatedModules();
263   for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) {
264     Module *module = target != nullptr
265                          ? target->GetImages().GetModuleAtIndex(image_idx).get()
266                          : Module::GetAllocatedModuleAtIndex(image_idx);
267     if (module == nullptr)
268       continue;
269     module->ResetStatistics();
270   }
271   if (target)
272     target->ResetStatistics();
273   else {
274     for (const auto &target : debugger.GetTargetList().Targets())
275       target->ResetStatistics();
276   }
277 }
278 
279 llvm::json::Value DebuggerStats::ReportStatistics(
280     Debugger &debugger, Target *target,
281     const lldb_private::StatisticsOptions &options) {
282 
283   const bool summary_only = options.GetSummaryOnly();
284   const bool load_all_debug_info = options.GetLoadAllDebugInfo();
285   const bool include_targets = options.GetIncludeTargets();
286   const bool include_modules = options.GetIncludeModules();
287   const bool include_transcript = options.GetIncludeTranscript();
288 
289   json::Array json_targets;
290   json::Array json_modules;
291   double symtab_parse_time = 0.0;
292   double symtab_index_time = 0.0;
293   double debug_parse_time = 0.0;
294   double debug_index_time = 0.0;
295   uint32_t symtabs_loaded = 0;
296   uint32_t symtabs_saved = 0;
297   uint32_t debug_index_loaded = 0;
298   uint32_t debug_index_saved = 0;
299   uint64_t debug_info_size = 0;
300 
301   std::vector<ModuleStats> modules;
302   std::lock_guard<std::recursive_mutex> guard(
303       Module::GetAllocationModuleCollectionMutex());
304   const uint64_t num_modules = target != nullptr
305                                    ? target->GetImages().GetSize()
306                                    : Module::GetNumberAllocatedModules();
307   uint32_t num_debug_info_enabled_modules = 0;
308   uint32_t num_modules_has_debug_info = 0;
309   uint32_t num_modules_with_variable_errors = 0;
310   uint32_t num_modules_with_incomplete_types = 0;
311   uint32_t num_stripped_modules = 0;
312   for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) {
313     Module *module = target != nullptr
314                          ? target->GetImages().GetModuleAtIndex(image_idx).get()
315                          : Module::GetAllocatedModuleAtIndex(image_idx);
316     ModuleStats module_stat;
317     module_stat.symtab_parse_time = module->GetSymtabParseTime().get().count();
318     module_stat.symtab_index_time = module->GetSymtabIndexTime().get().count();
319     Symtab *symtab = module->GetSymtab();
320     if (symtab) {
321       module_stat.symtab_loaded_from_cache = symtab->GetWasLoadedFromCache();
322       if (module_stat.symtab_loaded_from_cache)
323         ++symtabs_loaded;
324       module_stat.symtab_saved_to_cache = symtab->GetWasSavedToCache();
325       if (module_stat.symtab_saved_to_cache)
326         ++symtabs_saved;
327     }
328     SymbolFile *sym_file = module->GetSymbolFile();
329     if (sym_file) {
330       if (!summary_only) {
331         if (sym_file->GetObjectFile() != module->GetObjectFile())
332           module_stat.symfile_path =
333               sym_file->GetObjectFile()->GetFileSpec().GetPath();
334         ModuleList symbol_modules = sym_file->GetDebugInfoModules();
335         for (const auto &symbol_module : symbol_modules.Modules())
336           module_stat.symfile_modules.push_back((intptr_t)symbol_module.get());
337       }
338       module_stat.debug_info_index_loaded_from_cache =
339           sym_file->GetDebugInfoIndexWasLoadedFromCache();
340       if (module_stat.debug_info_index_loaded_from_cache)
341         ++debug_index_loaded;
342       module_stat.debug_info_index_saved_to_cache =
343           sym_file->GetDebugInfoIndexWasSavedToCache();
344       if (module_stat.debug_info_index_saved_to_cache)
345         ++debug_index_saved;
346       module_stat.debug_index_time = sym_file->GetDebugInfoIndexTime().count();
347       module_stat.debug_parse_time = sym_file->GetDebugInfoParseTime().count();
348       module_stat.debug_info_size =
349           sym_file->GetDebugInfoSize(load_all_debug_info);
350       module_stat.symtab_stripped = module->GetObjectFile()->IsStripped();
351       if (module_stat.symtab_stripped)
352         ++num_stripped_modules;
353       module_stat.debug_info_enabled = sym_file->GetLoadDebugInfoEnabled() &&
354                                        module_stat.debug_info_size > 0;
355       module_stat.debug_info_had_variable_errors =
356           sym_file->GetDebugInfoHadFrameVariableErrors();
357       if (module_stat.debug_info_enabled)
358         ++num_debug_info_enabled_modules;
359       if (module_stat.debug_info_size > 0)
360         ++num_modules_has_debug_info;
361       if (module_stat.debug_info_had_variable_errors)
362         ++num_modules_with_variable_errors;
363     }
364     symtab_parse_time += module_stat.symtab_parse_time;
365     symtab_index_time += module_stat.symtab_index_time;
366     debug_parse_time += module_stat.debug_parse_time;
367     debug_index_time += module_stat.debug_index_time;
368     debug_info_size += module_stat.debug_info_size;
369     module->ForEachTypeSystem([&](lldb::TypeSystemSP ts) {
370       if (auto stats = ts->ReportStatistics())
371         module_stat.type_system_stats.insert({ts->GetPluginName(), *stats});
372       if (ts->GetHasForcefullyCompletedTypes())
373         module_stat.debug_info_had_incomplete_types = true;
374       return true;
375     });
376     if (module_stat.debug_info_had_incomplete_types)
377       ++num_modules_with_incomplete_types;
378 
379     if (include_modules) {
380       module_stat.identifier = (intptr_t)module;
381       module_stat.path = module->GetFileSpec().GetPath();
382       if (ConstString object_name = module->GetObjectName()) {
383         module_stat.path.append(1, '(');
384         module_stat.path.append(object_name.GetStringRef().str());
385         module_stat.path.append(1, ')');
386       }
387       module_stat.uuid = module->GetUUID().GetAsString();
388       module_stat.triple = module->GetArchitecture().GetTriple().str();
389       json_modules.emplace_back(module_stat.ToJSON());
390     }
391   }
392 
393   json::Object global_stats{
394       {"totalSymbolTableParseTime", symtab_parse_time},
395       {"totalSymbolTableIndexTime", symtab_index_time},
396       {"totalSymbolTablesLoadedFromCache", symtabs_loaded},
397       {"totalSymbolTablesSavedToCache", symtabs_saved},
398       {"totalDebugInfoParseTime", debug_parse_time},
399       {"totalDebugInfoIndexTime", debug_index_time},
400       {"totalDebugInfoIndexLoadedFromCache", debug_index_loaded},
401       {"totalDebugInfoIndexSavedToCache", debug_index_saved},
402       {"totalDebugInfoByteSize", debug_info_size},
403       {"totalModuleCount", num_modules},
404       {"totalModuleCountHasDebugInfo", num_modules_has_debug_info},
405       {"totalModuleCountWithVariableErrors", num_modules_with_variable_errors},
406       {"totalModuleCountWithIncompleteTypes",
407        num_modules_with_incomplete_types},
408       {"totalDebugInfoEnabled", num_debug_info_enabled_modules},
409       {"totalSymbolTableStripped", num_stripped_modules},
410   };
411 
412   if (include_targets) {
413     if (target) {
414       json_targets.emplace_back(target->ReportStatistics(options));
415     } else {
416       for (const auto &target : debugger.GetTargetList().Targets())
417         json_targets.emplace_back(target->ReportStatistics(options));
418     }
419     global_stats.try_emplace("targets", std::move(json_targets));
420   }
421 
422   ConstStringStats const_string_stats;
423   json::Object json_memory{
424       {"strings", const_string_stats.ToJSON()},
425   };
426   global_stats.try_emplace("memory", std::move(json_memory));
427   if (!summary_only) {
428     json::Value cmd_stats = debugger.GetCommandInterpreter().GetStatistics();
429     global_stats.try_emplace("commands", std::move(cmd_stats));
430   }
431 
432   if (include_modules) {
433     global_stats.try_emplace("modules", std::move(json_modules));
434   }
435 
436   if (include_transcript) {
437     // When transcript is available, add it to the to-be-returned statistics.
438     //
439     // NOTE:
440     // When the statistics is polled by an LLDB command:
441     // - The transcript in the returned statistics *will NOT* contain the
442     //   returned statistics itself (otherwise infinite recursion).
443     // - The returned statistics *will* be written to the internal transcript
444     //   buffer. It *will* appear in the next statistcs or transcript poll.
445     //
446     // For example, let's say the following commands are run in order:
447     // - "version"
448     // - "statistics dump"  <- call it "A"
449     // - "statistics dump"  <- call it "B"
450     // The output of "A" will contain the transcript of "version" and
451     // "statistics dump" (A), with the latter having empty output. The output
452     // of B will contain the trascnript of "version", "statistics dump" (A),
453     // "statistics dump" (B), with A's output populated and B's output empty.
454     const StructuredData::Array &transcript =
455         debugger.GetCommandInterpreter().GetTranscript();
456     if (transcript.GetSize() != 0) {
457       std::string buffer;
458       llvm::raw_string_ostream ss(buffer);
459       json::OStream json_os(ss);
460       transcript.Serialize(json_os);
461       if (auto json_transcript = llvm::json::parse(buffer))
462         global_stats.try_emplace("transcript",
463                                  std::move(json_transcript.get()));
464     }
465   }
466 
467   return std::move(global_stats);
468 }
469 
470 llvm::json::Value SummaryStatistics::ToJSON() const {
471   return json::Object{{
472       {"name", GetName()},
473       {"type", GetSummaryKindName()},
474       {"count", GetSummaryCount()},
475       {"totalTime", GetTotalTime()},
476   }};
477 }
478 
479 json::Value SummaryStatisticsCache::ToJSON() {
480   std::lock_guard<std::mutex> guard(m_map_mutex);
481   json::Array json_summary_stats;
482   for (const auto &summary_stat : m_summary_stats_map)
483     json_summary_stats.emplace_back(summary_stat.second->ToJSON());
484 
485   return json_summary_stats;
486 }
487 
488 void SummaryStatisticsCache::Reset() {
489   for (const auto &summary_stat : m_summary_stats_map)
490     summary_stat.second->Reset();
491 }
492