diff --git a/lldb/include/lldb/Breakpoint/Breakpoint.h b/lldb/include/lldb/Breakpoint/Breakpoint.h --- a/lldb/include/lldb/Breakpoint/Breakpoint.h +++ b/lldb/include/lldb/Breakpoint/Breakpoint.h @@ -9,6 +9,7 @@ #ifndef LLDB_BREAKPOINT_BREAKPOINT_H #define LLDB_BREAKPOINT_BREAKPOINT_H +#include #include #include #include @@ -576,6 +577,12 @@ static lldb::BreakpointSP CopyFromBreakpoint(lldb::TargetSP new_target, const Breakpoint &bp_to_copy_from); + /// Get metrics associated with this breakpoint in JSON format. + llvm::json::Value GetMetrics(const MetricDumpOptions &options); + + /// Get the time it took to resolve all locations in this breakpoint. + double GetResolveTime() const { return m_resolve_time; } + protected: friend class Target; // Protected Methods @@ -653,6 +660,8 @@ BreakpointName::Permissions m_permissions; + double m_resolve_time = 0.0; + void SendBreakpointChangedEvent(lldb::BreakpointEventType eventKind); void SendBreakpointChangedEvent(BreakpointEventData *data); diff --git a/lldb/include/lldb/Breakpoint/BreakpointLocation.h b/lldb/include/lldb/Breakpoint/BreakpointLocation.h --- a/lldb/include/lldb/Breakpoint/BreakpointLocation.h +++ b/lldb/include/lldb/Breakpoint/BreakpointLocation.h @@ -18,6 +18,8 @@ #include "lldb/Utility/UserID.h" #include "lldb/lldb-private.h" +#include "llvm/Support/JSON.h" + namespace lldb_private { /// \class BreakpointLocation BreakpointLocation.h @@ -230,7 +232,7 @@ /// \b true if the target should stop at this breakpoint and \b /// false not. bool InvokeCallback(StoppointCallbackContext *context); - + /// Report whether the callback for this location is synchronous or not. /// /// \return @@ -279,6 +281,9 @@ /// Returns the breakpoint location ID. lldb::break_id_t GetID() const { return m_loc_id; } + /// Returns the breakpoint location metrics as JSON. + llvm::json::Value GetMetrics(const MetricDumpOptions &options); + protected: friend class BreakpointSite; friend class BreakpointLocationList; diff --git a/lldb/include/lldb/Core/FileSpecList.h b/lldb/include/lldb/Core/FileSpecList.h --- a/lldb/include/lldb/Core/FileSpecList.h +++ b/lldb/include/lldb/Core/FileSpecList.h @@ -11,7 +11,7 @@ #if defined(__cplusplus) #include "lldb/Utility/FileSpec.h" - +#include "llvm/Support/JSON.h" #include #include @@ -197,6 +197,8 @@ const_iterator begin() const { return m_files.begin(); } const_iterator end() const { return m_files.end(); } + llvm::json::Value ToJSON() const; + protected: collection m_files; ///< A collection of FileSpec objects. }; diff --git a/lldb/include/lldb/Core/Module.h b/lldb/include/lldb/Core/Module.h --- a/lldb/include/lldb/Core/Module.h +++ b/lldb/include/lldb/Core/Module.h @@ -933,6 +933,15 @@ bool m_match_name_after_lookup = false; }; + /// Get the symbol table parse time metric. + /// + /// The value is returned as a reference to allow it to be updated by the + /// ElapsedTime + double &GetSymtabParseTime() { return m_symtab_parse_time; } + double &GetSymtabNamesTime() { return m_symtab_names_time; } + + uint64_t GetDebugInfoSize(); + protected: // Member Variables mutable std::recursive_mutex m_mutex; ///< A mutex to keep this object happy @@ -959,6 +968,14 @@ ///by \a m_file. uint64_t m_object_offset = 0; llvm::sys::TimePoint<> m_object_mod_time; + /// We store a symbol table parse time duration here because we might have + /// an object file and a symbol file which both have symbol tables. The parse + /// time for the symbol tables can be aggregated here. + double m_symtab_parse_time = 0.0; + /// We store a symbol named index time duration here because we might have + /// an object file and a symbol file which both have symbol tables. The parse + /// time for the symbol tables can be aggregated here. + double m_symtab_names_time = 0.0; /// DataBuffer containing the module image, if it was provided at /// construction time. Otherwise the data will be retrieved by mapping diff --git a/lldb/include/lldb/Core/Section.h b/lldb/include/lldb/Core/Section.h --- a/lldb/include/lldb/Core/Section.h +++ b/lldb/include/lldb/Core/Section.h @@ -89,6 +89,12 @@ void Clear() { m_sections.clear(); } + /// Get the debug information size from all sections that contain debug + /// information. Symbol tables are not considered part of the debug + /// information for this call, just known sections that contain debug + /// information. + uint64_t GetDebugInfoSize() const; + protected: collection m_sections; }; @@ -181,6 +187,13 @@ void SetIsThreadSpecific(bool b) { m_thread_specific = b; } + /// Returns true if this section contains debug information. Symbol tables + /// are not considered debug information since some symbols might contain + /// debug information (STABS, COFF) but not all symbols do, so to keep this + /// fast and simple only sections that contains only debug information should + /// return true. + bool ContainsOnlyDebugInfo() const; + /// Get the permissions as OR'ed bits from lldb::Permissions uint32_t GetPermissions() const; diff --git a/lldb/include/lldb/Symbol/SymbolContext.h b/lldb/include/lldb/Symbol/SymbolContext.h --- a/lldb/include/lldb/Symbol/SymbolContext.h +++ b/lldb/include/lldb/Symbol/SymbolContext.h @@ -18,6 +18,7 @@ #include "lldb/Symbol/LineEntry.h" #include "lldb/Utility/Iterable.h" #include "lldb/lldb-private.h" +#include "llvm/Support/JSON.h" namespace lldb_private { @@ -313,6 +314,8 @@ SymbolContext &next_frame_sc, Address &inlined_frame_addr) const; + llvm::json::Value ToJSON() const; + // Member variables lldb::TargetSP target_sp; ///< The Target for a given query lldb::ModuleSP module_sp; ///< The Module for a given query diff --git a/lldb/include/lldb/Symbol/SymbolFile.h b/lldb/include/lldb/Symbol/SymbolFile.h --- a/lldb/include/lldb/Symbol/SymbolFile.h +++ b/lldb/include/lldb/Symbol/SymbolFile.h @@ -299,6 +299,31 @@ virtual void Dump(Stream &s); + /// Metrics gathering functions + + /// Return the size in bytes of all debug information in the symbol file. + /// + /// If the debug information is contained in sections of an ObjectFile, then + /// this call should add the size of all sections that contain debug + /// information. Symbols the symbol tables are not considered debug + /// information for this call to make it easy and quick for this number to be + /// calculated. If the symbol file is all debug information, the size of the + /// entire file should be returned. + virtual uint64_t GetDebugInfoSize() = 0; + + /// Return the time taken to parse the debug information. + /// + /// \returns llvm::None if no information has been parsed or if there is + /// no computational cost to parsing the debug information. + virtual double GetDebugInfoParseTime() { return 0.0; } + + /// Return the time it took to index the debug information in the object + /// file. + /// + /// \returns llvm::None if the file doesn't need to be indexed or if it + /// hasn't been indexed yet, or a valid duration if it has. + virtual double GetDebugInfoIndexTime() { return 0.0; } + protected: void AssertModuleLock(); virtual uint32_t CalculateNumCompileUnits() = 0; diff --git a/lldb/include/lldb/Target/Metrics.h b/lldb/include/lldb/Target/Metrics.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/Target/Metrics.h @@ -0,0 +1,74 @@ +//===-- Metrics.h -----------------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_TARGET_METRICS_H +#define LLDB_TARGET_METRICS_H + +#include +#include + +#include "lldb/Utility/Stream.h" +#include "lldb/lldb-forward.h" + +#include "llvm/Support/JSON.h" + +namespace lldb_private { + +struct MetricDumpOptions { + bool dump_modules = false; + bool dump_breakpoints = false; + bool dump_breakpoint_locations = false; + bool dump_settings = false; + + /// Set all dump options to true. + void SetAll() { + dump_modules = true; + dump_breakpoints = true; + dump_breakpoint_locations = true; + dump_settings = true; + } +}; + +struct ModuleMetrics { + llvm::json::Value ToJSON() const; + + std::string path; + std::string uuid; + std::string triple; + uint64_t debug_info_size = 0; + double debug_parse_time = 0.0; + double debug_index_time = 0.0; + double symtab_parse_time = 0.0; + double symtab_names_time = 0.0; +}; + +class TargetMetrics { +public: + llvm::json::Value ToJSON(Target &target, const MetricDumpOptions &options); + + void CollectModuleMetrics(Target &target); + + void SetLaunchOrAttachTime(); + + void SetFirstPrivateStopTime(); + + void SetFirstPublicStopTime(); + + double &GetCreateTime() { return create_time; } + +protected: + std::vector m_module_metrics; + double create_time; + llvm::Optional launch_or_attach_time; + llvm::Optional first_private_stop_time; + llvm::Optional first_public_stop_time; +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_METRICS_H diff --git a/lldb/include/lldb/Target/PathMappingList.h b/lldb/include/lldb/Target/PathMappingList.h --- a/lldb/include/lldb/Target/PathMappingList.h +++ b/lldb/include/lldb/Target/PathMappingList.h @@ -9,10 +9,11 @@ #ifndef LLDB_TARGET_PATHMAPPINGLIST_H #define LLDB_TARGET_PATHMAPPINGLIST_H -#include -#include #include "lldb/Utility/ConstString.h" #include "lldb/Utility/Status.h" +#include "llvm/Support/JSON.h" +#include +#include namespace lldb_private { @@ -108,6 +109,11 @@ uint32_t GetModificationID() const { return m_mod_id; } + /// Return the path mappings as a JSON array. + /// + /// The array contains arrays of path mapping paths as strings. + llvm::json::Value ToJSON() const; + protected: typedef std::pair pair; typedef std::vector collection; diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -240,10 +240,11 @@ ~ProcessModID() = default; - void BumpStopID() { - m_stop_id++; + uint32_t BumpStopID() { + const uint32_t prev_stop_id = m_stop_id++; if (!IsLastResumeForUserExpression()) m_last_natural_stop_id++; + return prev_stop_id; } void BumpMemoryID() { m_memory_id++; } diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h --- a/lldb/include/lldb/Target/Target.h +++ b/lldb/include/lldb/Target/Target.h @@ -26,6 +26,7 @@ #include "lldb/Host/ProcessLaunchInfo.h" #include "lldb/Symbol/TypeSystem.h" #include "lldb/Target/ExecutionContextScope.h" +#include "lldb/Target/Metrics.h" #include "lldb/Target/PathMappingList.h" #include "lldb/Target/SectionLoadHistory.h" #include "lldb/Target/ThreadSpec.h" @@ -1178,6 +1179,30 @@ /// if none can be found. llvm::Expected GetEntryPointAddress(); + // Utilities for the `target metrics` command +private: + // Target metrics storage. + TargetMetrics m_metrics; + +public: + /// Get metrics associated with this target in JSON format. + /// + /// Target metrics help measure timings and information that is contained in + /// a target. These are designed to help measure performance of a debug + /// session as well as represent the current state of the target, like + /// information on the currently modules, currently set breakpoints and more. + /// + /// \param [in] modules + /// If true, include an array of metrics for each module loaded in the + /// target. + // + /// \return + /// Returns a JSON value that contains all target metrics. + llvm::json::Value GatherMetrics(const MetricDumpOptions &options); + + /// Accessor for the target metrics object. + TargetMetrics &GetMetrics() { return m_metrics; } + // Target Stop Hooks class StopHook : public UserID { public: diff --git a/lldb/include/lldb/Utility/Timer.h b/lldb/include/lldb/Utility/Timer.h --- a/lldb/include/lldb/Utility/Timer.h +++ b/lldb/include/lldb/Utility/Timer.h @@ -83,6 +83,45 @@ llvm::SignpostEmitter &GetSignposts(); +/// A class that measures elapsed time. +/// +/// This class is designed to help gather timing metrics within LLDB where +/// objects have optional Duration variables that get updated with elapsed +/// times. This helps LLDB measure metrics for many things that are then +/// reported in LLDB commands. +/// +/// Objects that need to measure elapsed times should have a variable with of +/// type "double m_time_xxx;" which can then be used in the constructor of this +/// class inside a scope that wants to measure something: +/// +/// ElapsedTime elapsed(m_time_xxx); +/// // Do some work +/// +/// This class will update the m_time_xxx variable with the elapsed time when +/// the object goes out of scope. The "m_time_xxx" variable will be incremented +/// when the class goes out of scope. This allows a variable to measure +/// something that might happen in stages at different times, like resolving a +/// breakpoint each time a new shared library is loaded. +class ElapsedTime { +public: + using Clock = std::chrono::high_resolution_clock; + using Duration = std::chrono::duration; + using Timepoint = std::chrono::time_point; + /// Set to the start time when the object is created. + Timepoint m_start_time; + /// The elapsed time in seconds to update when this object goes out of scope. + double &m_elapsed_time; + +public: + ElapsedTime(double &opt_time) : m_elapsed_time(opt_time) { + m_start_time = Clock::now(); + } + ~ElapsedTime() { + Duration elapsed = Clock::now() - m_start_time; + m_elapsed_time += elapsed.count(); + } +}; + } // namespace lldb_private // Use a format string because LLVM_PRETTY_FUNCTION might not be a string diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h --- a/lldb/include/lldb/lldb-forward.h +++ b/lldb/include/lldb/lldb-forward.h @@ -118,6 +118,7 @@ class MemoryHistory; class MemoryRegionInfo; class MemoryRegionInfos; +struct MetricDumpOptions; class Module; class ModuleList; class ModuleSpec; diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -766,6 +766,20 @@ """Return absolute path to an artifact in the test's build directory.""" return os.path.join(self.getBuildDir(), name) + def getShlibBuildArtifact(self, DYLIB_NAME): + """Return absolute path to a shared library artifact given the library + name as it is known from the Makefile in DYLIB_NAME. + + Each platform has different prefixes and extensions for shared + librairies when they get built by the Makefile. This function + allows tests to specify the DYLIB_NAME from the Makefile and it + will return the correct path for the built shared library in the + build artifacts directory + """ + ctx = self.platformContext + dylibName = ctx.shlib_prefix + DYLIB_NAME + '.' + ctx.shlib_extension + return self.getBuildArtifact(dylibName) + def getSourcePath(self, name): """Return absolute path to a file in the test's source directory.""" return os.path.join(self.getSourceDir(), name) diff --git a/lldb/source/Breakpoint/Breakpoint.cpp b/lldb/source/Breakpoint/Breakpoint.cpp --- a/lldb/source/Breakpoint/Breakpoint.cpp +++ b/lldb/source/Breakpoint/Breakpoint.cpp @@ -19,16 +19,17 @@ #include "lldb/Core/ModuleList.h" #include "lldb/Core/SearchFilter.h" #include "lldb/Core/Section.h" -#include "lldb/Target/SectionLoadList.h" #include "lldb/Symbol/CompileUnit.h" #include "lldb/Symbol/Function.h" #include "lldb/Symbol/Symbol.h" #include "lldb/Symbol/SymbolContext.h" +#include "lldb/Target/SectionLoadList.h" #include "lldb/Target/Target.h" #include "lldb/Target/ThreadSpec.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/Stream.h" #include "lldb/Utility/StreamString.h" +#include "lldb/Utility/Timer.h" #include @@ -77,6 +78,44 @@ return bp; } +json::Value Breakpoint::GetMetrics(const MetricDumpOptions &options) { + json::Object bp; + bp.try_emplace("id", GetID()); + if (!m_name_list.empty()) { + json::Array names; + for (const auto &name : m_name_list) + names.emplace_back(name); + bp.try_emplace("names", std::move(names)); + } + if (options.dump_breakpoint_locations) { + json::Array locations; + const size_t num_locs = m_locations.GetSize(); + for (size_t i = 0; i < num_locs; ++i) { + BreakpointLocationSP loc_sp = GetLocationAtIndex(i); + if (!loc_sp) + continue; + locations.emplace_back(loc_sp->GetMetrics(options)); + } + bp.try_emplace("locations", std::move(locations)); + } + + if (m_resolve_time > 0.0) + bp.try_emplace("resolveTime", m_resolve_time); + StructuredData::ObjectSP bp_data_sp = SerializeToStructuredData(); + if (bp_data_sp) { + std::string buffer; + llvm::raw_string_ostream ss(buffer); + json::OStream json_os(ss); + bp_data_sp->Serialize(json_os); + if (llvm::Expected expected_value = + llvm::json::parse(ss.str())) + bp.try_emplace("details", std::move(*expected_value)); + else + llvm::consumeError(expected_value.takeError()); + } + return json::Value(std::move(bp)); +} + // Serialization StructuredData::ObjectSP Breakpoint::SerializeToStructuredData() { // Serialize the resolver: @@ -439,22 +478,24 @@ const BreakpointOptions &Breakpoint::GetOptions() const { return m_options; } void Breakpoint::ResolveBreakpoint() { - if (m_resolver_sp) + if (m_resolver_sp) { + ElapsedTime elapsed(m_resolve_time); m_resolver_sp->ResolveBreakpoint(*m_filter_sp); + } } void Breakpoint::ResolveBreakpointInModules( ModuleList &module_list, BreakpointLocationCollection &new_locations) { + ElapsedTime elapsed(m_resolve_time); m_locations.StartRecordingNewLocations(new_locations); - m_resolver_sp->ResolveBreakpointInModules(*m_filter_sp, module_list); - m_locations.StopRecordingNewLocations(); } void Breakpoint::ResolveBreakpointInModules(ModuleList &module_list, bool send_event) { if (m_resolver_sp) { + ElapsedTime elapsed(m_resolve_time); // If this is not an internal breakpoint, set up to record the new // locations, then dispatch an event with the new locations. if (!IsInternal() && send_event) { @@ -522,12 +563,12 @@ locations_with_no_section.Add(break_loc_sp); continue; } - + if (!break_loc_sp->IsEnabled()) continue; - + SectionSP section_sp(section_addr.GetSection()); - + // If we don't have a Section, that means this location is a raw // address that we haven't resolved to a section yet. So we'll have to // look in all the new modules to resolve this location. Otherwise, if @@ -544,9 +585,9 @@ } } } - + size_t num_to_delete = locations_with_no_section.GetSize(); - + for (size_t i = 0; i < num_to_delete; i++) m_locations.RemoveLocation(locations_with_no_section.GetByIndex(i)); diff --git a/lldb/source/Breakpoint/BreakpointLocation.cpp b/lldb/source/Breakpoint/BreakpointLocation.cpp --- a/lldb/source/Breakpoint/BreakpointLocation.cpp +++ b/lldb/source/Breakpoint/BreakpointLocation.cpp @@ -667,3 +667,33 @@ m_is_indirect = swap_from->m_is_indirect; m_user_expression_sp.reset(); } + +llvm::json::Value +BreakpointLocation::GetMetrics(const MetricDumpOptions &options) { + + llvm::json::Object loc_json; + + const bool is_resolved = IsResolved(); + const bool is_hardware = is_resolved && m_bp_site_sp->IsHardware(); + loc_json.try_emplace("id", GetID()); + loc_json.try_emplace("resolved", is_resolved); + loc_json.try_emplace("hardward", is_hardware); + loc_json.try_emplace("reexported", IsReExported()); + loc_json.try_emplace("indirect", IsIndirect()); + loc_json.try_emplace("enabled", IsEnabled()); + + if (m_address.IsSectionOffset()) { + SymbolContext sc; + m_address.CalculateSymbolContext(&sc); + loc_json.try_emplace("symbolContext", sc.ToJSON()); + } + + const addr_t load_addr = GetLoadAddress(); + if (load_addr != LLDB_INVALID_ADDRESS) + loc_json.try_emplace("loadAddress", (int64_t)load_addr); + const addr_t file_addr = m_address.GetFileAddress(); + if (file_addr != LLDB_INVALID_ADDRESS) + loc_json.try_emplace("fileAddress", (int64_t)file_addr); + loc_json.try_emplace("hitCount", (int64_t)GetHitCount()); + return llvm::json::Value(std::move(loc_json)); +} diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp --- a/lldb/source/Commands/CommandObjectTarget.cpp +++ b/lldb/source/Commands/CommandObjectTarget.cpp @@ -4990,6 +4990,77 @@ ~CommandObjectMultiwordTargetStopHooks() override = default; }; +#pragma mark CommandObjectTargetMetrics + +#define LLDB_OPTIONS_target_metrics +#include "CommandOptions.inc" + +class CommandObjectTargetMetrics : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options() { OptionParsingStarting(nullptr); } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'a': + m_metric_options.SetAll(); + break; + case 'b': + m_metric_options.dump_breakpoints = true; + break; + case 'l': + m_metric_options.dump_breakpoint_locations = true; + break; + case 'm': + m_metric_options.dump_modules = true; + break; + case 's': + m_metric_options.dump_settings = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_metric_options = MetricDumpOptions(); + } + + llvm::ArrayRef GetDefinitions() override { + return llvm::makeArrayRef(g_target_metrics_options); + } + + MetricDumpOptions m_metric_options; + }; + + CommandObjectTargetMetrics(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "target metrics", + "Dump target metrics in JSON format", nullptr, + eCommandRequiresTarget), + m_options() {} + + ~CommandObjectTargetMetrics() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = m_exe_ctx.GetTargetRef(); + result.AppendMessageWithFormatv( + "{0:2}", target.GatherMetrics(m_options.m_metric_options)); + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + + CommandOptions m_options; +}; + #pragma mark CommandObjectMultiwordTarget // CommandObjectMultiwordTarget @@ -5005,6 +5076,8 @@ CommandObjectSP(new CommandObjectTargetDelete(interpreter))); LoadSubCommand("list", CommandObjectSP(new CommandObjectTargetList(interpreter))); + LoadSubCommand("metrics", + CommandObjectSP(new CommandObjectTargetMetrics(interpreter))); LoadSubCommand("select", CommandObjectSP(new CommandObjectTargetSelect(interpreter))); LoadSubCommand("show-launch-environment", diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -1290,3 +1290,16 @@ def trace_schema_verbose : Option<"verbose", "v">, Group<1>, Desc<"Show verbose trace schema logging for debugging the plug-in.">; } + +let Command = "target metrics" in { + def target_metrics_modules: Option<"modules", "m">, Group<1>, + Desc<"Include metrics for each module in the target.">; + def target_metrics_breakpoints: Option<"breakpoints", "b">, Group<1>, + Desc<"Include metrics for each breakpoint in the target.">; + def target_metrics_locations: Option<"locations", "l">, Group<1>, + Desc<"Include metrics for each breakpoint location in each breakpoint.">; + def target_metrics_settings: Option<"settings", "s">, Group<1>, + Desc<"Include important target settings in metrics.">; + def target_metrics_all: Option<"all", "a">, Group<1>, + Desc<"Include all metrics in the target.">; +} diff --git a/lldb/source/Core/FileSpecList.cpp b/lldb/source/Core/FileSpecList.cpp --- a/lldb/source/Core/FileSpecList.cpp +++ b/lldb/source/Core/FileSpecList.cpp @@ -119,3 +119,10 @@ FileSpecList &matches) { return 0; } + +llvm::json::Value FileSpecList::ToJSON() const { + llvm::json::Array files; + for (const auto &file : m_files) + files.emplace_back(file.GetPath()); + return llvm::json::Value(std::move(files)); +} diff --git a/lldb/source/Core/Module.cpp b/lldb/source/Core/Module.cpp --- a/lldb/source/Core/Module.cpp +++ b/lldb/source/Core/Module.cpp @@ -1659,3 +1659,10 @@ return false; } + +uint64_t Module::GetDebugInfoSize() { + SymbolFile *symbols = GetSymbolFile(); + if (symbols) + return symbols->GetDebugInfoSize(); + return 0; +} diff --git a/lldb/source/Core/Section.cpp b/lldb/source/Core/Section.cpp --- a/lldb/source/Core/Section.cpp +++ b/lldb/source/Core/Section.cpp @@ -151,6 +151,75 @@ return "unknown"; } +bool Section::ContainsOnlyDebugInfo() const { + switch (m_type) { + case eSectionTypeInvalid: + case eSectionTypeCode: + case eSectionTypeContainer: + case eSectionTypeData: + case eSectionTypeDataCString: + case eSectionTypeDataCStringPointers: + case eSectionTypeDataSymbolAddress: + case eSectionTypeData4: + case eSectionTypeData8: + case eSectionTypeData16: + case eSectionTypeDataPointers: + case eSectionTypeZeroFill: + case eSectionTypeDataObjCMessageRefs: + case eSectionTypeDataObjCCFStrings: + case eSectionTypeELFSymbolTable: + case eSectionTypeELFDynamicSymbols: + case eSectionTypeELFRelocationEntries: + case eSectionTypeELFDynamicLinkInfo: + case eSectionTypeEHFrame: + case eSectionTypeARMexidx: + case eSectionTypeARMextab: + case eSectionTypeCompactUnwind: + case eSectionTypeGoSymtab: + case eSectionTypeAbsoluteAddress: + case eSectionTypeOther: + return false; + + case eSectionTypeDebug: + case eSectionTypeDWARFDebugAbbrev: + case eSectionTypeDWARFDebugAbbrevDwo: + case eSectionTypeDWARFDebugAddr: + case eSectionTypeDWARFDebugAranges: + case eSectionTypeDWARFDebugCuIndex: + case eSectionTypeDWARFDebugTuIndex: + case eSectionTypeDWARFDebugFrame: + case eSectionTypeDWARFDebugInfo: + case eSectionTypeDWARFDebugInfoDwo: + case eSectionTypeDWARFDebugLine: + case eSectionTypeDWARFDebugLineStr: + case eSectionTypeDWARFDebugLoc: + case eSectionTypeDWARFDebugLocDwo: + case eSectionTypeDWARFDebugLocLists: + case eSectionTypeDWARFDebugLocListsDwo: + case eSectionTypeDWARFDebugMacInfo: + case eSectionTypeDWARFDebugMacro: + case eSectionTypeDWARFDebugPubNames: + case eSectionTypeDWARFDebugPubTypes: + case eSectionTypeDWARFDebugRanges: + case eSectionTypeDWARFDebugRngLists: + case eSectionTypeDWARFDebugRngListsDwo: + case eSectionTypeDWARFDebugStr: + case eSectionTypeDWARFDebugStrDwo: + case eSectionTypeDWARFDebugStrOffsets: + case eSectionTypeDWARFDebugStrOffsetsDwo: + case eSectionTypeDWARFDebugTypes: + case eSectionTypeDWARFDebugTypesDwo: + case eSectionTypeDWARFDebugNames: + case eSectionTypeDWARFAppleNames: + case eSectionTypeDWARFAppleTypes: + case eSectionTypeDWARFAppleNamespaces: + case eSectionTypeDWARFAppleObjC: + case eSectionTypeDWARFGNUDebugAltLink: + return true; + } + return false; +} + Section::Section(const ModuleSP &module_sp, ObjectFile *obj_file, user_id_t sect_id, ConstString name, SectionType sect_type, addr_t file_addr, addr_t byte_size, @@ -599,3 +668,15 @@ } return count; } + +uint64_t SectionList::GetDebugInfoSize() const { + uint64_t debug_info_size = 0; + for (const auto §ion : m_sections) { + const SectionList &sub_sections = section->GetChildren(); + if (sub_sections.GetSize() > 0) + debug_info_size += sub_sections.GetDebugInfoSize(); + else if (section->ContainsOnlyDebugInfo()) + debug_info_size += section->GetFileSize(); + } + return debug_info_size; +} diff --git a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp --- a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp +++ b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp @@ -2707,9 +2707,6 @@ if (!module_sp) return nullptr; - Progress progress(llvm::formatv("Parsing symbol table for {0}", - m_file.GetFilename().AsCString(""))); - // We always want to use the main object file so we (hopefully) only have one // cached copy of our symtab, dynamic sections, etc. ObjectFile *module_obj_file = module_sp->GetObjectFile(); @@ -2717,6 +2714,10 @@ return module_obj_file->GetSymtab(); if (m_symtab_up == nullptr) { + Progress progress( + llvm::formatv("Parsing symbol table for {0}", + m_file.GetFilename().AsCString(""))); + ElapsedTime elapsed(module_sp->GetSymtabParseTime()); SectionList *section_list = module_sp->GetSectionList(); if (!section_list) return nullptr; diff --git a/lldb/source/Plugins/ObjectFile/JIT/ObjectFileJIT.cpp b/lldb/source/Plugins/ObjectFile/JIT/ObjectFileJIT.cpp --- a/lldb/source/Plugins/ObjectFile/JIT/ObjectFileJIT.cpp +++ b/lldb/source/Plugins/ObjectFile/JIT/ObjectFileJIT.cpp @@ -120,6 +120,7 @@ if (module_sp) { std::lock_guard guard(module_sp->GetMutex()); if (m_symtab_up == nullptr) { + ElapsedTime elapsed(module_sp->GetSymtabParseTime()); m_symtab_up = std::make_unique(this); std::lock_guard symtab_guard( m_symtab_up->GetMutex()); diff --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp --- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp +++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp @@ -1307,6 +1307,10 @@ if (module_sp) { std::lock_guard guard(module_sp->GetMutex()); if (m_symtab_up == nullptr) { + // Aggregate the symbol table parse time into the module in case we have + // one object file for the module and one for the debug information, both + // of which can have symbol tables that might get parsed. + ElapsedTime elapsed(module_sp->GetSymtabParseTime()); m_symtab_up = std::make_unique(this); std::lock_guard symtab_guard( m_symtab_up->GetMutex()); @@ -2229,7 +2233,6 @@ ModuleSP module_sp(GetModule()); if (!module_sp) return 0; - Progress progress(llvm::formatv("Parsing symbol table for {0}", m_file.GetFilename().AsCString(""))); @@ -2479,8 +2482,8 @@ // We shouldn't have exports data from both the LC_DYLD_INFO command // AND the LC_DYLD_EXPORTS_TRIE command in the same binary: - lldbassert(!((dyld_info.export_size > 0) - && (exports_trie_load_command.datasize > 0))); + lldbassert(!((dyld_info.export_size > 0) && + (exports_trie_load_command.datasize > 0))); if (dyld_info.export_size > 0) { dyld_trie_data.SetData(m_data, dyld_info.export_off, dyld_info.export_size); diff --git a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp --- a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp +++ b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp @@ -600,6 +600,7 @@ if (module_sp) { std::lock_guard guard(module_sp->GetMutex()); if (m_symtab_up == nullptr) { + ElapsedTime elapsed(module_sp->GetSymtabParseTime()); SectionList *sect_list = GetSectionList(); m_symtab_up = std::make_unique(this); std::lock_guard guard(m_symtab_up->GetMutex()); diff --git a/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.h b/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.h --- a/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.h +++ b/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.h @@ -55,6 +55,8 @@ uint32_t CalculateAbilities() override; + uint64_t GetDebugInfoSize() override; + void InitializeObject() override {} // Compile Unit function calls diff --git a/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp b/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp --- a/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp +++ b/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp @@ -188,6 +188,11 @@ return CompileUnits | Functions | LineTables; } +uint64_t SymbolFileBreakpad::GetDebugInfoSize() { + // Breakpad files are all debug info. + return m_objfile_sp->GetByteSize(); +} + uint32_t SymbolFileBreakpad::CalculateNumCompileUnits() { ParseCUData(); return m_cu_data->GetSize(); diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.h b/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.h --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.h +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.h @@ -62,8 +62,11 @@ virtual void Dump(Stream &s) = 0; + double GetIndexTime() { return m_index_time; } + protected: Module &m_module; + double m_index_time = 0.0; /// Helper function implementing common logic for processing function dies. If /// the function given by "ref" matches search criteria given by diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp @@ -49,6 +49,7 @@ if (m_first_die) return; // Already parsed + ElapsedTime elapsed(m_dwarf.GetDebugInfoParseTimeRef()); LLDB_SCOPED_TIMERF("%8.8x: DWARFUnit::ExtractUnitDIENoDwoIfNeeded()", GetOffset()); @@ -195,7 +196,7 @@ // held R/W and m_die_array must be empty. void DWARFUnit::ExtractDIEsRWLocked() { llvm::sys::ScopedWriter first_die_lock(m_first_die_mutex); - + ElapsedTime elapsed(m_dwarf.GetDebugInfoParseTimeRef()); LLDB_SCOPED_TIMERF("%8.8x: DWARFUnit::ExtractDIEsIfNeeded()", GetOffset()); // Set the offset to that of the first DIE and calculate the start of the diff --git a/lldb/source/Plugins/SymbolFile/DWARF/ManualDWARFIndex.cpp b/lldb/source/Plugins/SymbolFile/DWARF/ManualDWARFIndex.cpp --- a/lldb/source/Plugins/SymbolFile/DWARF/ManualDWARFIndex.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/ManualDWARFIndex.cpp @@ -30,6 +30,7 @@ SymbolFileDWARF &main_dwarf = *m_dwarf; m_dwarf = nullptr; + ElapsedTime elapsed(m_index_time); LLDB_SCOPED_TIMERF("%p", static_cast(&main_dwarf)); DWARFDebugInfo &main_info = main_dwarf.DebugInfo(); diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h --- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h +++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h @@ -318,6 +318,12 @@ /// Same as GetLanguage() but reports all C++ versions as C++ (no version). static lldb::LanguageType GetLanguageFamily(DWARFUnit &unit); + uint64_t GetDebugInfoSize() override; + double GetDebugInfoParseTime() override; + double GetDebugInfoIndexTime() override; + + double &GetDebugInfoParseTimeRef() { return m_parse_time; } + protected: typedef llvm::DenseMap DIEToTypePtr; @@ -519,6 +525,7 @@ llvm::DenseMap m_type_unit_support_files; std::vector m_lldb_cu_to_dwarf_unit; + double m_parse_time = 0.0; }; #endif // LLDB_SOURCE_PLUGINS_SYMBOLFILE_DWARF_SYMBOLFILEDWARF_H diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp --- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp @@ -965,6 +965,7 @@ if (offset == DW_INVALID_OFFSET) return false; + ElapsedTime elapsed(m_parse_time); llvm::DWARFDebugLine::Prologue prologue; if (!ParseLLVMLineTablePrologue(m_context, prologue, offset, dwarf_cu.GetOffset())) @@ -1013,6 +1014,7 @@ "SymbolFileDWARF::GetTypeUnitSupportFiles failed to parse " "the line table prologue"); }; + ElapsedTime elapsed(m_parse_time); llvm::Error error = prologue.parse(data, &line_table_offset, report, ctx); if (error) { report(std::move(error)); @@ -1099,6 +1101,7 @@ if (offset == DW_INVALID_OFFSET) return false; + ElapsedTime elapsed(m_parse_time); llvm::DWARFDebugLine line; const llvm::DWARFDebugLine::LineTable *line_table = ParseLLVMLineTable(m_context, line, offset, dwarf_cu->GetOffset()); @@ -1147,6 +1150,7 @@ if (iter != m_debug_macros_map.end()) return iter->second; + ElapsedTime elapsed(m_parse_time); const DWARFDataExtractor &debug_macro_data = m_context.getOrLoadMacroData(); if (debug_macro_data.GetByteSize() == 0) return DebugMacrosSP(); @@ -3937,3 +3941,21 @@ lang = DW_LANG_C_plus_plus; return LanguageTypeFromDWARF(lang); } + +uint64_t SymbolFileDWARF::GetDebugInfoSize() { + ModuleSP module_sp(m_objfile_sp->GetModule()); + if (!module_sp) + return 0; + const SectionList *section_list = module_sp->GetSectionList(); + if (section_list) + return section_list->GetDebugInfoSize(); + return 0; +} + +double SymbolFileDWARF::GetDebugInfoParseTime() { return m_parse_time; } + +double SymbolFileDWARF::GetDebugInfoIndexTime() { + if (m_index) + return m_index->GetIndexTime(); + return 0.0; +} diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h --- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h +++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h @@ -142,6 +142,10 @@ // PluginInterface protocol lldb_private::ConstString GetPluginName() override; + uint64_t GetDebugInfoSize() override; + double GetDebugInfoParseTime() override; + double GetDebugInfoIndexTime() override; + protected: enum { kHaveInitializedOSOs = (1 << 0), kNumFlags }; diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp --- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp @@ -1441,3 +1441,54 @@ } return num_line_entries_added; } + +uint64_t SymbolFileDWARFDebugMap::GetDebugInfoSize() { + uint64_t debug_info_size = 0; + ForEachSymbolFile([&](SymbolFileDWARF *oso_dwarf) -> bool { + ObjectFile *oso_objfile = oso_dwarf->GetObjectFile(); + if (!oso_objfile) + return false; // Keep iterating + ModuleSP module_sp = oso_objfile->GetModule(); + if (!module_sp) + return false; // Keep iterating + SectionList *section_list = module_sp->GetSectionList(); + if (section_list) + debug_info_size += section_list->GetDebugInfoSize(); + return false; // Keep iterating + }); + return debug_info_size; +} + +double SymbolFileDWARFDebugMap::GetDebugInfoParseTime() { + double time = 0; + ForEachSymbolFile([&](SymbolFileDWARF *oso_dwarf) -> bool { + ObjectFile *oso_objfile = oso_dwarf->GetObjectFile(); + if (oso_objfile) { + ModuleSP module_sp = oso_objfile->GetModule(); + if (module_sp) { + SymbolFile *symfile = module_sp->GetSymbolFile(); + if (symfile) + time += symfile->GetDebugInfoParseTime(); + } + } + return false; // Keep iterating + }); + return time; +} + +double SymbolFileDWARFDebugMap::GetDebugInfoIndexTime() { + double index_time = 0; + ForEachSymbolFile([&](SymbolFileDWARF *oso_dwarf) -> bool { + ObjectFile *oso_objfile = oso_dwarf->GetObjectFile(); + if (oso_objfile) { + ModuleSP module_sp = oso_objfile->GetModule(); + if (module_sp) { + SymbolFile *symfile = module_sp->GetSymbolFile(); + if (symfile) + index_time += symfile->GetDebugInfoIndexTime(); + } + } + return false; // Keep iterating + }); + return index_time; +} diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h --- a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h +++ b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h @@ -73,6 +73,8 @@ uint32_t CalculateAbilities() override; + uint64_t GetDebugInfoSize() override; + void InitializeObject() override; // Compile Unit function calls diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp --- a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp +++ b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp @@ -296,6 +296,11 @@ return abilities; } +uint64_t SymbolFileNativePDB::GetDebugInfoSize() { + // PDB files are a separate file that contains all debug info. + return m_index->pdb().getFileSize(); +} + void SymbolFileNativePDB::InitializeObject() { m_obj_load_address = m_objfile_sp->GetModule() ->GetObjectFile() diff --git a/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.h b/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.h --- a/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.h +++ b/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.h @@ -55,6 +55,8 @@ uint32_t CalculateAbilities() override; + uint64_t GetDebugInfoSize() override; + void InitializeObject() override; // Compile Unit function calls diff --git a/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp b/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp --- a/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp +++ b/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp @@ -2062,3 +2062,8 @@ return 0; } + +uint64_t SymbolFilePDB::GetDebugInfoSize() { + // TODO: implement this + return 0; +} diff --git a/lldb/source/Plugins/SymbolFile/Symtab/SymbolFileSymtab.h b/lldb/source/Plugins/SymbolFile/Symtab/SymbolFileSymtab.h --- a/lldb/source/Plugins/SymbolFile/Symtab/SymbolFileSymtab.h +++ b/lldb/source/Plugins/SymbolFile/Symtab/SymbolFileSymtab.h @@ -45,6 +45,8 @@ uint32_t CalculateAbilities() override; + uint64_t GetDebugInfoSize() override; + // Compile Unit function calls lldb::LanguageType ParseLanguage(lldb_private::CompileUnit &comp_unit) override; diff --git a/lldb/source/Plugins/SymbolFile/Symtab/SymbolFileSymtab.cpp b/lldb/source/Plugins/SymbolFile/Symtab/SymbolFileSymtab.cpp --- a/lldb/source/Plugins/SymbolFile/Symtab/SymbolFileSymtab.cpp +++ b/lldb/source/Plugins/SymbolFile/Symtab/SymbolFileSymtab.cpp @@ -104,6 +104,13 @@ return abilities; } +uint64_t SymbolFileSymtab::GetDebugInfoSize() { + // This call isn't expected to look at symbols and figure out which ones are + // debug info or not, it is just to report sections full of debug info, so + // we should return 0 here. + return 0; +} + uint32_t SymbolFileSymtab::CalculateNumCompileUnits() { // If we don't have any source file symbols we will just have one compile // unit for the entire object file diff --git a/lldb/source/Symbol/SymbolContext.cpp b/lldb/source/Symbol/SymbolContext.cpp --- a/lldb/source/Symbol/SymbolContext.cpp +++ b/lldb/source/Symbol/SymbolContext.cpp @@ -916,6 +916,26 @@ return nullptr; // no error; we just didn't find anything } +llvm::json::Value SymbolContext::ToJSON() const { + llvm::json::Object json; + if (module_sp) + json.try_emplace("module", module_sp->GetUUID().GetAsString()); + if (comp_unit) + json.try_emplace("compUnit", comp_unit->GetPrimaryFile().GetPath()); + if (function && function->GetName()) + json.try_emplace("function", function->GetName().AsCString()); + if (line_entry.file) + json.try_emplace("sourceFile", line_entry.file.GetPath()); + if (line_entry.file != line_entry.original_file && line_entry.original_file) + json.try_emplace("origSourceFile", line_entry.original_file.GetPath()); + json.try_emplace("sourceLine", (int64_t)line_entry.line); + if (line_entry.column) + json.try_emplace("sourceColumn", (int64_t)line_entry.column); + if (symbol) + json.try_emplace("symbol", symbol->GetName().AsCString("")); + return llvm::json::Value(std::move(json)); +} + // // SymbolContextSpecifier // diff --git a/lldb/source/Symbol/Symtab.cpp b/lldb/source/Symbol/Symtab.cpp --- a/lldb/source/Symbol/Symtab.cpp +++ b/lldb/source/Symbol/Symtab.cpp @@ -265,6 +265,7 @@ // Protected function, no need to lock mutex... if (!m_name_indexes_computed) { m_name_indexes_computed = true; + ElapsedTime elapsed(m_objfile->GetModule()->GetSymtabNamesTime()); LLDB_SCOPED_TIMER(); // Collect all loaded language plugins. diff --git a/lldb/source/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt --- a/lldb/source/Target/CMakeLists.txt +++ b/lldb/source/Target/CMakeLists.txt @@ -19,6 +19,7 @@ Memory.cpp MemoryHistory.cpp MemoryRegionInfo.cpp + Metrics.cpp ModuleCache.cpp OperatingSystem.cpp PathMappingList.cpp diff --git a/lldb/source/Target/Metrics.cpp b/lldb/source/Target/Metrics.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Target/Metrics.cpp @@ -0,0 +1,187 @@ +//===-- Metrics.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/Metrics.h" + +#include "lldb/Core/Module.h" +#include "lldb/Symbol/SymbolFile.h" +#include "lldb/Target/Target.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)); +} + +void TargetMetrics::CollectModuleMetrics(Target &target) { + m_module_metrics.clear(); + for (ModuleSP module_sp : target.GetImages().Modules()) { + ModuleMetrics module_metrics; + module_metrics.path = module_sp->GetFileSpec().GetPath(); + module_metrics.uuid = module_sp->GetUUID().GetAsString(); + module_metrics.triple = module_sp->GetArchitecture().GetTriple().str(); + SymbolFile *sym_file = module_sp->GetSymbolFile(); + if (sym_file) { + module_metrics.debug_index_time = sym_file->GetDebugInfoIndexTime(); + module_metrics.debug_parse_time = sym_file->GetDebugInfoParseTime(); + } + module_metrics.symtab_parse_time = module_sp->GetSymtabParseTime(); + module_metrics.symtab_names_time = module_sp->GetSymtabNamesTime(); + module_metrics.debug_info_size = module_sp->GetDebugInfoSize(); + m_module_metrics.push_back(std::move(module_metrics)); + } +} + +static double elapsed(const std::chrono::steady_clock::time_point &start, + const std::chrono::steady_clock::time_point &end) { + std::chrono::duration elapsed = + end.time_since_epoch() - start.time_since_epoch(); + return elapsed.count(); +} + +json::Value TargetMetrics::ToJSON(Target &target, + const MetricDumpOptions &options) { + CollectModuleMetrics(target); + + uint64_t debug_info_size = 0; + double debug_parse_time = 0.0; + double debug_index_time = 0.0; + double symtab_parse_time = 0.0; + double symtab_names_time = 0.0; + + json::Array modules; + for (const auto &module_metrics : m_module_metrics) { + modules.push_back(module_metrics.ToJSON()); + debug_info_size += module_metrics.debug_info_size; + debug_parse_time += module_metrics.debug_parse_time; + debug_index_time += module_metrics.debug_index_time; + symtab_parse_time += module_metrics.symtab_parse_time; + symtab_names_time += module_metrics.symtab_names_time; + } + + json::Array breakpoints_array; + std::unique_lock lock; + target.GetBreakpointList().GetListMutex(lock); + + const BreakpointList &breakpoints = target.GetBreakpointList(); + size_t num_breakpoints = breakpoints.GetSize(); + double totalBreakpointResolveTime = 0; + for (size_t i = 0; i < num_breakpoints; i++) { + Breakpoint *bp = breakpoints.GetBreakpointAtIndex(i).get(); + if (options.dump_breakpoints || options.dump_breakpoint_locations) + breakpoints_array.push_back(bp->GetMetrics(options)); + totalBreakpointResolveTime += bp->GetResolveTime(); + } + + // Include settings that can change how executables, sources and breakpoints + // can be resolved. + json::Object settings; + if (options.dump_settings) { + settings.try_emplace("target.arg0", target.GetArg0()); + Args target_args; + json::Array run_args; + target.GetRunArguments(target_args); + for (const auto &arg : target_args.entries()) + run_args.emplace_back(arg.c_str()); + settings.try_emplace("target.run-args", std::move(run_args)); + settings.try_emplace("target.source-map", + target.GetSourcePathMap().ToJSON()); + settings.try_emplace("target.exec-search-paths", + target.GetExecutableSearchPaths().ToJSON()); + settings.try_emplace("target.debug-file-search-paths", + target.GetDebugFileSearchPaths().ToJSON()); + settings.try_emplace("target.clang-module-search-paths", + target.GetClangModuleSearchPaths().ToJSON()); + settings.try_emplace("target.preload-symbols", target.GetPreloadSymbols()); + const char *strategy = nullptr; + switch (target.GetInlineStrategy()) { + case eInlineBreakpointsNever: + strategy = "never"; + break; + case eInlineBreakpointsHeaders: + strategy = "headers"; + break; + case eInlineBreakpointsAlways: + strategy = "always"; + break; + } + settings.try_emplace("target.inline-breakpoint-strategy", strategy); + settings.try_emplace("target.skip-prologue", target.GetSkipPrologue()); + } + + json::Object target_metrics_json{ + {"totalDebugInfoSize", (int64_t)debug_info_size}, + {"totalDebugInfoParseTime", debug_parse_time}, + {"totalDebugInfoIndexTime", debug_index_time}, + {"totalSymbolTableParseTime", symtab_parse_time}, + {"totalSymbolTableIndexTime", symtab_names_time}, + {"totalBreakpointResolveTime", totalBreakpointResolveTime}, + }; + if (options.dump_breakpoints || options.dump_breakpoint_locations) + target_metrics_json.try_emplace("breakpoints", + std::move(breakpoints_array)); + if (options.dump_modules) + target_metrics_json.try_emplace("modules", std::move(modules)); + if (options.dump_settings) + target_metrics_json.try_emplace("settings", std::move(settings)); + if (launch_or_attach_time.hasValue() && first_private_stop_time.hasValue()) { + double elapsed_time = + elapsed(*launch_or_attach_time, *first_private_stop_time); + target_metrics_json.try_emplace("launchOrAttachTime", elapsed_time); + } + if (launch_or_attach_time.hasValue() && first_public_stop_time.hasValue()) { + double elapsed_time = + elapsed(*launch_or_attach_time, *first_public_stop_time); + target_metrics_json.try_emplace("firstStopTime", elapsed_time); + } + target_metrics_json.try_emplace("targetCreateTime", create_time); + + return json::Value(std::move(target_metrics_json)); +} + +void TargetMetrics::SetLaunchOrAttachTime() { + launch_or_attach_time = std::chrono::steady_clock::now(); + first_private_stop_time = llvm::None; +} + +void TargetMetrics::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 (!first_private_stop_time.hasValue()) + first_private_stop_time = std::chrono::steady_clock::now(); +} + +void TargetMetrics::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 (!first_public_stop_time.hasValue()) + first_public_stop_time = std::chrono::steady_clock::now(); +} + +json::Value ModuleMetrics::ToJSON() const { + json::Object module_metrics; + EmplaceSafeString(module_metrics, "path", path); + EmplaceSafeString(module_metrics, "uuid", uuid); + EmplaceSafeString(module_metrics, "triple", triple); + module_metrics.try_emplace("debugInfoParseTime", debug_parse_time); + module_metrics.try_emplace("debugInfoIndexTime", debug_index_time); + module_metrics.try_emplace("symbolTableParseTime", symtab_parse_time); + module_metrics.try_emplace("symbolTableIndexTime", symtab_names_time); + module_metrics.try_emplace("debugInfoSize", (int64_t)debug_info_size); + return module_metrics; +} diff --git a/lldb/source/Target/PathMappingList.cpp b/lldb/source/Target/PathMappingList.cpp --- a/lldb/source/Target/PathMappingList.cpp +++ b/lldb/source/Target/PathMappingList.cpp @@ -297,3 +297,14 @@ } return UINT32_MAX; } + +llvm::json::Value PathMappingList::ToJSON() const { + llvm::json::Array mappings; + for (const auto &pair : m_pairs) { + llvm::json::Array mapping; + mapping.emplace_back(pair.first.GetCString()); + mapping.emplace_back(pair.second.GetCString()); + mappings.emplace_back(std::move(mapping)); + } + return llvm::json::Value(std::move(mappings)); +} diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -1297,6 +1297,17 @@ } void Process::SetPublicState(StateType new_state, bool restarted) { + const bool new_state_is_stopped = StateIsStoppedState(new_state, false); + if (new_state_is_stopped) { + // This will only set the time if the public stop time has no value, so + // it is ok to call this multiple times. With a public stop we can't look + // at the stop ID because many private stops might have happened, so we + // can't check for a stop ID of zero. This allows the "statistics" command + // to dump the time it takes to reach somewhere in your code, like a + // breakpoint you set. + GetTarget().GetMetrics().SetFirstPublicStopTime(); + } + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_STATE | LIBLLDB_LOG_PROCESS)); LLDB_LOGF(log, "Process::SetPublicState (state = %s, restarted = %i)", @@ -1315,7 +1326,6 @@ m_public_run_lock.SetStopped(); } else { const bool old_state_is_stopped = StateIsStoppedState(old_state, false); - const bool new_state_is_stopped = StateIsStoppedState(new_state, false); if ((old_state_is_stopped != new_state_is_stopped)) { if (new_state_is_stopped && !restarted) { LLDB_LOGF(log, "Process::SetPublicState (%s) -- unlocking run lock", @@ -1446,7 +1456,9 @@ // before we get here. m_thread_list.DidStop(); - m_mod_id.BumpStopID(); + if (m_mod_id.BumpStopID() == 0) + GetTarget().GetMetrics().SetFirstPrivateStopTime(); + if (!m_mod_id.IsLastResumeForUserExpression()) m_mod_id.SetStopEventForLastNaturalStopID(event_sp); m_memory_cache.Clear(); diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -1400,6 +1400,7 @@ ClearModules(false); if (executable_sp) { + ElapsedTime elapsed(m_metrics.GetCreateTime()); LLDB_SCOPED_TIMERF("Target::SetExecutableModule (executable = '%s')", executable_sp->GetFileSpec().GetPath().c_str()); @@ -2908,6 +2909,7 @@ void Target::ClearAllLoadedSections() { m_section_load_history.Clear(); } Status Target::Launch(ProcessLaunchInfo &launch_info, Stream *stream) { + m_metrics.SetLaunchOrAttachTime(); Status error; Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_TARGET)); @@ -3119,6 +3121,7 @@ } Status Target::Attach(ProcessAttachInfo &attach_info, Stream *stream) { + m_metrics.SetLaunchOrAttachTime(); auto state = eStateInvalid; auto process_sp = GetProcessSP(); if (process_sp) { @@ -4464,3 +4467,9 @@ else return m_mutex; } + +/// Get metrics associated with this target in JSON format. +llvm::json::Value Target::GatherMetrics(const MetricDumpOptions &options) { + m_metrics.CollectModuleMetrics(*this); + return m_metrics.ToJSON(*this, options); +} diff --git a/lldb/test/API/commands/target/metrics/Makefile b/lldb/test/API/commands/target/metrics/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/target/metrics/Makefile @@ -0,0 +1,10 @@ +LD_EXTRAS := -L. -lload_a +CXX_SOURCES := main.cpp + +a.out: libload_a + +include Makefile.rules + +libload_a: + $(MAKE) -f $(MAKEFILE_RULES) \ + DYLIB_ONLY=YES DYLIB_NAME=load_a DYLIB_CXX_SOURCES=a.cpp diff --git a/lldb/test/API/commands/target/metrics/TestTargetMetrics.py b/lldb/test/API/commands/target/metrics/TestTargetMetrics.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/target/metrics/TestTargetMetrics.py @@ -0,0 +1,363 @@ +""" +Test that loading of dependents works correctly for all the potential +combinations. +""" + + +import lldb +import json +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +@skipIfWindows # Windows deals differently with shared libs. +class TargetDependentsTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + NO_DEBUG_INFO_TESTCASE = True + + def setUp(self): + TestBase.setUp(self) + self.build() + + def has_exactly_one_image(self, matching, msg=""): + self.expect( + "image list", + "image list should contain at least one image", + substrs=['[ 0]']) + should_match = not matching + self.expect( + "image list", msg, matching=should_match, substrs=['[ 1]']) + + + def verify_key_presence(self, dict, description, keys_exist, keys_missing=None): + if keys_exist: + for key in keys_exist: + self.assertTrue(key in dict, + 'Make sure "%s" key exists in %s dictionary' % (key, description)) + if keys_missing: + for key in keys_missing: + self.assertTrue(key not in dict, + 'Make sure "%s" key does not exists in %s dictionary' % (key, description)) + + def find_module_in_metrics(self, path, metrics): + modules = metrics['modules'] + for module in modules: + if module['path'] == path: + return module + return None + + def test_metrics_default(self): + """Test default behavior of "target metrics" command. + + Output expected to be something like: + + (lldb) target metrics + { + "targetCreateTime": 0.26566899599999999, + "totalBreakpointResolveTime": 0.0031409419999999999, + "totalDebugInfoIndexTime": 0, + "totalDebugInfoParseTime": 0, + "totalDebugInfoSize": 2193, + "totalSymbolTableIndexTime": 0.056834488000000009, + "totalSymbolTableParseTime": 0.093979421999999979 + } + + """ + exe = self.getBuildArtifact("a.out") + self.runCmd("target create " + exe, CURRENT_EXECUTABLE_SET) + return_obj = lldb.SBCommandReturnObject() + self.ci.HandleCommand("target metrics", return_obj, False) + metrics_json = return_obj.GetOutput() + metrics = json.loads(metrics_json) + keys_exist = [ + 'targetCreateTime', + 'totalBreakpointResolveTime', + 'totalDebugInfoIndexTime', + 'totalDebugInfoParseTime', + 'totalDebugInfoSize', + 'totalSymbolTableIndexTime', + 'totalSymbolTableParseTime', + ] + keys_missing = [ + 'breakpoints', + 'modules', + 'settings' + ] + self.verify_key_presence(metrics, '"metrics"', keys_exist, keys_missing) + self.assertTrue(metrics['targetCreateTime'] > 0.0) + self.assertTrue(metrics['totalBreakpointResolveTime'] == 0.0) + self.assertTrue(metrics['totalDebugInfoSize'] > 0) + self.assertTrue(metrics['totalSymbolTableIndexTime'] > 0.0) + self.assertTrue(metrics['totalSymbolTableParseTime'] > 0.0) + + def test_metrics_modules(self): + """Test behavior of "target metrics --modules" command. + + Output expected to be something like: + + (lldb) target metrics --modules + { + "targetCreateTime": 0.26566899599999999, + "totalBreakpointResolveTime": 0.0031409419999999999, + "totalDebugInfoIndexTime": 0, + "totalDebugInfoParseTime": 0.00037288300000000001, + "totalDebugInfoSize": 2193, + "totalSymbolTableIndexTime": 0.056834488000000009, + "totalSymbolTableParseTime": 0.093979421999999979, + "modules": [ + { + "debugInfoIndexTime": 0, + "debugInfoParseTime": 0, + "debugInfoSize": 1139, + "path": "/Users/gclayton/Documents/src/lldb/main/Debug/lldb-test-build.noindex/commands/target/metrics/TestTargetMetrics.test_metrics_modules/a.out", + "symbolTableIndexTime": 2.3816e-05, + "symbolTableParseTime": 0.000163747, + "triple": "x86_64-apple-macosx11.0.0", + "uuid": "10531B95-4DF4-3FFE-9D51-549DD17435E2" + }, + { + "debugInfoIndexTime": 0, + "debugInfoParseTime": 0, + "debugInfoSize": 1054, + "path": "/Users/gclayton/Documents/src/lldb/main/Debug/lldb-test-build.noindex/commands/target/metrics/TestTargetMetrics.test_metrics_modules/libload_a.dylib", + "symbolTableIndexTime": 2.7852999999999999e-05, + "symbolTableParseTime": 0.000110246, + "triple": "x86_64-apple-macosx11.0.0", + "uuid": "F0974CDE-309A-3837-9593-385ABDC93803" + }, + ] + } + + """ + exe = self.getBuildArtifact("a.out") + self.runCmd("target create " + exe, CURRENT_EXECUTABLE_SET) + return_obj = lldb.SBCommandReturnObject() + self.ci.HandleCommand("target metrics --modules", return_obj, False) + output = return_obj.GetOutput() + metrics = json.loads(output) + keys_exist = [ + 'targetCreateTime', + 'totalBreakpointResolveTime', + 'totalDebugInfoIndexTime', + 'totalDebugInfoParseTime', + 'totalDebugInfoSize', + 'totalSymbolTableIndexTime', + 'totalSymbolTableParseTime', + 'modules', + ] + keys_missing = [ + 'breakpoints', + 'settings' + ] + self.verify_key_presence(metrics, '"metrics"', keys_exist, keys_missing) + exe_module = self.find_module_in_metrics(exe, metrics) + module_keys = [ + 'debugInfoIndexTime', + 'debugInfoParseTime', + 'debugInfoSize', + 'path', + 'symbolTableIndexTime', + 'symbolTableParseTime', + 'triple', + 'uuid', + ] + self.assertTrue(exe_module != None) + self.verify_key_presence(exe_module, 'module dict for "%s"' % (exe), + module_keys) + shlib_path = self.getShlibBuildArtifact('load_a') + shlib_module = self.find_module_in_metrics(shlib_path, metrics) + self.assertTrue(shlib_module != None) + self.verify_key_presence(shlib_module, 'module dict for "%s"' % (exe), + module_keys) + + def test_metrics_breakpoints(self): + """Test behavior of "target metrics --breakpoints" command. + + Output expected to be something like: + + (lldb) target metrics --breakpoints + { + "targetCreateTime": 0.26566899599999999, + "totalBreakpointResolveTime": 0.0031409419999999999, + "totalDebugInfoIndexTime": 0, + "totalDebugInfoParseTime": 0.00037288300000000001, + "totalDebugInfoSize": 2193, + "totalSymbolTableIndexTime": 0.056834488000000009, + "totalSymbolTableParseTime": 0.093979421999999979 + "breakpoints": [ + { + "details": {...}. + "id": 1, + "resolveTime": 0.0029114369999999998 + }, + ] + } + + """ + exe = self.getBuildArtifact("a.out") + self.runCmd("target create " + exe, CURRENT_EXECUTABLE_SET) + self.runCmd("b main.cpp:7") + self.runCmd("b a_function") + return_obj = lldb.SBCommandReturnObject() + self.ci.HandleCommand("target metrics --breakpoints", return_obj, False) + metrics_json = return_obj.GetOutput() + metrics = json.loads(metrics_json) + keys_exist = [ + 'targetCreateTime', + 'totalBreakpointResolveTime', + 'totalDebugInfoIndexTime', + 'totalDebugInfoParseTime', + 'totalDebugInfoSize', + 'totalSymbolTableIndexTime', + 'totalSymbolTableParseTime', + 'breakpoints', + ] + keys_missing = [ + 'modules', + 'settings' + ] + self.verify_key_presence(metrics, '"metrics"', keys_exist, keys_missing) + self.assertTrue(metrics['totalBreakpointResolveTime'] > 0.0) + self.assertTrue(metrics['totalDebugInfoParseTime'] > 0.0) + breakpoints = metrics['breakpoints'] + bp_keys_exist = [ + 'details', + 'id', + 'resolveTime' + ] + bk_keys_missing = [ + 'locations' + ] + for breakpoint in breakpoints: + self.verify_key_presence(breakpoint, 'metrics["breakpoints"]', + bp_keys_exist, bk_keys_missing) + + def test_metrics_locations(self): + """Test behavior of "target metrics --locations" command. + + Output expected to be something like: + + (lldb) target metrics --locations + { + "targetCreateTime": 0.26566899599999999, + "totalBreakpointResolveTime": 0.0031409419999999999, + "totalDebugInfoIndexTime": 0, + "totalDebugInfoParseTime": 0.00037288300000000001, + "totalDebugInfoSize": 2193, + "totalSymbolTableIndexTime": 0.056834488000000009, + "totalSymbolTableParseTime": 0.093979421999999979 + "breakpoints": [ + { + "details": {...}. + "id": 1, + "locations": [...], + "resolveTime": 0.0029114369999999998 + }, + ] + } + + """ + exe = self.getBuildArtifact("a.out") + self.runCmd("target create " + exe, CURRENT_EXECUTABLE_SET) + self.runCmd("b main.cpp:7") + self.runCmd("b a_function") + return_obj = lldb.SBCommandReturnObject() + self.ci.HandleCommand("target metrics --locations", return_obj, False) + metrics_json = return_obj.GetOutput() + metrics = json.loads(metrics_json) + keys_exist = [ + 'targetCreateTime', + 'totalBreakpointResolveTime', + 'totalDebugInfoIndexTime', + 'totalDebugInfoParseTime', + 'totalDebugInfoSize', + 'totalSymbolTableIndexTime', + 'totalSymbolTableParseTime', + 'breakpoints', + ] + keys_missing = [ + 'modules', + 'settings' + ] + self.verify_key_presence(metrics, '"metrics"', keys_exist, keys_missing) + self.assertTrue(metrics['totalBreakpointResolveTime'] > 0.0) + self.assertTrue(metrics['totalDebugInfoParseTime'] > 0.0) + breakpoints = metrics['breakpoints'] + bp_keys_exist = [ + 'details', + 'id', + 'resolveTime', + 'locations' + ] + for breakpoint in breakpoints: + self.verify_key_presence(breakpoint, 'metrics["breakpoints"]', + bp_keys_exist, None) + + def test_metrics_settings(self): + """Test behavior of "target metrics --settings" command. + + Output expected to be something like: + + (lldb) target metrics --settings + { + "targetCreateTime": 0.26566899599999999, + "totalBreakpointResolveTime": 0.0031409419999999999, + "totalDebugInfoIndexTime": 0, + "totalDebugInfoParseTime": 0.00037288300000000001, + "totalDebugInfoSize": 2193, + "totalSymbolTableIndexTime": 0.056834488000000009, + "totalSymbolTableParseTime": 0.093979421999999979 + "settings": { + "target.arg0": "/.../commands/target/metrics/TestTargetMetrics.test_metrics_settings/a.out", + "target.clang-module-search-paths": [], + "target.debug-file-search-paths": [], + "target.exec-search-paths": [ + "/.../commands/target/metrics/TestTargetMetrics.test_metrics_settings" + ], + "target.inline-breakpoint-strategy": "always", + "target.preload-symbols": true, + "target.run-args": [], + "target.skip-prologue": true, + "target.source-map": [] + }, + } + + """ + exe = self.getBuildArtifact("a.out") + self.runCmd("target create " + exe, CURRENT_EXECUTABLE_SET) + return_obj = lldb.SBCommandReturnObject() + self.ci.HandleCommand("target metrics --settings", return_obj, False) + metrics_json = return_obj.GetOutput() + f = open('/tmp/a', 'w') + f.write(metrics_json) + metrics = json.loads(metrics_json) + keys_exist = [ + 'targetCreateTime', + 'totalBreakpointResolveTime', + 'totalDebugInfoIndexTime', + 'totalDebugInfoParseTime', + 'totalDebugInfoSize', + 'totalSymbolTableIndexTime', + 'totalSymbolTableParseTime', + 'settings' + ] + keys_missing = [ + 'breakpoints', + 'modules', + ] + self.verify_key_presence(metrics, '"metrics"', keys_exist, keys_missing) + settings = metrics['settings'] + settings_keys_exist = [ + "target.arg0", + "target.clang-module-search-paths", + "target.debug-file-search-paths", + "target.exec-search-paths", + "target.inline-breakpoint-strategy", + "target.preload-symbols", + "target.run-args", + "target.skip-prologue", + "target.source-map", + ] + self.verify_key_presence(settings, 'metrics["settings"]', settings_keys_exist) diff --git a/lldb/test/API/commands/target/metrics/a.cpp b/lldb/test/API/commands/target/metrics/a.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/target/metrics/a.cpp @@ -0,0 +1 @@ +int a_function() { return 500; } diff --git a/lldb/test/API/commands/target/metrics/main.cpp b/lldb/test/API/commands/target/metrics/main.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/target/metrics/main.cpp @@ -0,0 +1,4 @@ +extern int a_function(); +extern int b_function(); + +int main(int argc, char const *argv[]) { return a_function(); }