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 @@ -22,6 +22,7 @@ #include "lldb/Breakpoint/Stoppoint.h" #include "lldb/Breakpoint/StoppointHitCounter.h" #include "lldb/Core/SearchFilter.h" +#include "lldb/Utility/ElapsedTime.h" #include "lldb/Utility/Event.h" #include "lldb/Utility/StringList.h" #include "lldb/Utility/StructuredData.h" @@ -576,6 +577,12 @@ static lldb::BreakpointSP CopyFromBreakpoint(lldb::TargetSP new_target, const Breakpoint &bp_to_copy_from); + /// Get statistics associated with this breakpoint in JSON format. + llvm::json::Value GetStatistics(const StatsDumpOptions &options); + + /// Get the time it took to resolve all locations in this breakpoint. + ElapsedTime::Duration GetResolveTime() const { return m_resolve_time; } + protected: friend class Target; // Protected Methods @@ -653,6 +660,8 @@ BreakpointName::Permissions m_permissions; + ElapsedTime::Duration 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 StatsDumpOptions &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 @@ -18,10 +18,11 @@ #include "lldb/Target/PathMappingList.h" #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/ConstString.h" +#include "lldb/Utility/ElapsedTime.h" #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/Status.h" -#include "lldb/Utility/XcodeSDK.h" #include "lldb/Utility/UUID.h" +#include "lldb/Utility/XcodeSDK.h" #include "lldb/lldb-defines.h" #include "lldb/lldb-enumerations.h" #include "lldb/lldb-forward.h" @@ -933,6 +934,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 + ElapsedTime::Duration &GetSymtabParseTime() { return m_symtab_parse_time; } + ElapsedTime::Duration &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 +969,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. + ElapsedTime::Duration 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. + ElapsedTime::Duration 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 @@ -19,6 +19,7 @@ #include "lldb/Symbol/Type.h" #include "lldb/Symbol/TypeList.h" #include "lldb/Symbol/TypeSystem.h" +#include "lldb/Utility/ElapsedTime.h" #include "lldb/Utility/XcodeSDK.h" #include "lldb/lldb-private.h" #include "llvm/ADT/DenseSet.h" @@ -299,6 +300,35 @@ 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 ElapsedTime::Duration GetDebugInfoParseTime() { + return ElapsedTime::Duration(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 ElapsedTime::Duration GetDebugInfoIndexTime() { + return ElapsedTime::Duration(0.0); + } + protected: void AssertModuleLock(); virtual uint32_t CalculateNumCompileUnits() = 0; 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/Statistics.h b/lldb/include/lldb/Target/Statistics.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/Target/Statistics.h @@ -0,0 +1,95 @@ +//===-- Statistics.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_STATISTICS_H +#define LLDB_TARGET_STATISTICS_H + +#include +#include + +#include "lldb/Utility/ElapsedTime.h" +#include "lldb/Utility/Stream.h" +#include "lldb/lldb-forward.h" +#include "llvm/Support/JSON.h" + +namespace lldb_private { + +struct SuccessFailStats { + void Notify(bool success); + + llvm::json::Value ToJSON() const; + + uint32_t successes = 0; + uint32_t failures = 0; +}; + +struct StatsDumpOptions { + 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 ModuleStats { + 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 TargetStats { +public: + llvm::json::Value ToJSON(Target &target, const StatsDumpOptions &options); + + void CollectModuleStats(Target &target); + + void SetLaunchOrAttachTime(); + + void SetFirstPrivateStopTime(); + + void SetFirstPublicStopTime(); + + ElapsedTime::Duration &GetCreateTime() { return create_time; } + + void NotifyExprEval(bool success) { return m_expr_eval.Notify(success); } + void NotifyFrameVar(bool success) { return m_frame_var.Notify(success); } + void SetCollectingStats(bool v) { m_collecting_stats = v; } + bool GetCollectingStats() { return m_collecting_stats; } + +protected: + // Collecting stats can be set to true to collect stats that are expensive + // to collect. By default all stats that are cheap to collect are enabled. + // This settings is here to maintain compatibility with "statistics enable" + // and "statistics disable". + bool m_collecting_stats = false; + std::vector m_modules; + ElapsedTime::Duration create_time{0.0}; + llvm::Optional launch_or_attach_time; + llvm::Optional first_private_stop_time; + llvm::Optional first_public_stop_time; + SuccessFailStats m_expr_eval; + SuccessFailStats m_frame_var; +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_STATISTICS_H 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 @@ -28,6 +28,7 @@ #include "lldb/Target/ExecutionContextScope.h" #include "lldb/Target/PathMappingList.h" #include "lldb/Target/SectionLoadHistory.h" +#include "lldb/Target/Statistics.h" #include "lldb/Target/ThreadSpec.h" #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/Broadcaster.h" @@ -1451,23 +1452,26 @@ // Utilities for `statistics` command. private: - std::vector m_stats_storage; - bool m_collecting_stats = false; + // Target metrics storage. + TargetStats m_stats; public: - void SetCollectingStats(bool v) { m_collecting_stats = v; } - - bool GetCollectingStats() { return m_collecting_stats; } - - void IncrementStats(lldb_private::StatisticKind key) { - if (!GetCollectingStats()) - return; - lldbassert(key < lldb_private::StatisticKind::StatisticMax && - "invalid statistics!"); - m_stats_storage[key] += 1; - } + /// 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 ReportStatistics(const StatsDumpOptions &options); - std::vector GetStatistics() { return m_stats_storage; } + TargetStats &GetStatistics() { return m_stats; } private: /// Construct with optional file and arch. diff --git a/lldb/include/lldb/Utility/ElapsedTime.h b/lldb/include/lldb/Utility/ElapsedTime.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/Utility/ElapsedTime.h @@ -0,0 +1,56 @@ +//===-- ElapsedTime.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_UTILITY_ELAPSEDTIME_H +#define LLDB_UTILITY_ELAPSEDTIME_H + +#include +namespace lldb_private { + +/// 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 "ElapsedTime::Duration 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. + ElapsedTime::Duration &m_elapsed_time; + +public: + ElapsedTime(ElapsedTime::Duration &opt_time) : m_elapsed_time(opt_time) { + m_start_time = Clock::now(); + } + ~ElapsedTime() { + Duration elapsed = Clock::now() - m_start_time; + m_elapsed_time += elapsed; + } +}; + +} // namespace lldb_private + +#endif // LLDB_UTILITY_ELAPSEDTIME_H 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 StatsDumpOptions; 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/API/SBTarget.cpp b/lldb/source/API/SBTarget.cpp --- a/lldb/source/API/SBTarget.cpp +++ b/lldb/source/API/SBTarget.cpp @@ -217,17 +217,11 @@ TargetSP target_sp(GetSP()); if (!target_sp) return LLDB_RECORD_RESULT(data); - - auto stats_up = std::make_unique(); - int i = 0; - for (auto &Entry : target_sp->GetStatistics()) { - std::string Desc = lldb_private::GetStatDescription( - static_cast(i)); - stats_up->AddIntegerItem(Desc, Entry); - i += 1; - } - - data.m_impl_up->SetObjectSP(std::move(stats_up)); + std::string json_text; + llvm::raw_string_ostream stream(json_text); + llvm::json::Value json = target_sp->ReportStatistics(StatsDumpOptions()); + stream << json; + data.m_impl_up->SetObjectSP(StructuredData::ParseJSON(stream.str())); return LLDB_RECORD_RESULT(data); } @@ -237,7 +231,7 @@ TargetSP target_sp(GetSP()); if (!target_sp) return; - return target_sp->SetCollectingStats(v); + return target_sp->GetStatistics().SetCollectingStats(v); } bool SBTarget::GetCollectingStats() { @@ -246,7 +240,7 @@ TargetSP target_sp(GetSP()); if (!target_sp) return false; - return target_sp->GetCollectingStats(); + return target_sp->GetStatistics().GetCollectingStats(); } SBProcess SBTarget::LoadCore(const char *core_file) { 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::GetStatistics(const StatsDumpOptions &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.count() > 0.0) + bp.try_emplace("resolveTime", m_resolve_time.count()); + 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 StatsDumpOptions &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/CommandObjectExpression.cpp b/lldb/source/Commands/CommandObjectExpression.cpp --- a/lldb/source/Commands/CommandObjectExpression.cpp +++ b/lldb/source/Commands/CommandObjectExpression.cpp @@ -660,12 +660,12 @@ history.AppendString(fixed_command); } // Increment statistics to record this expression evaluation success. - target.IncrementStats(StatisticKind::ExpressionSuccessful); + target.GetStatistics().NotifyExprEval(true); return true; } // Increment statistics to record this expression evaluation failure. - target.IncrementStats(StatisticKind::ExpressionFailure); + target.GetStatistics().NotifyExprEval(false); result.SetStatus(eReturnStatusFailed); return false; } diff --git a/lldb/source/Commands/CommandObjectFrame.cpp b/lldb/source/Commands/CommandObjectFrame.cpp --- a/lldb/source/Commands/CommandObjectFrame.cpp +++ b/lldb/source/Commands/CommandObjectFrame.cpp @@ -708,11 +708,7 @@ // Increment statistics. bool res = result.Succeeded(); - Target &target = GetSelectedOrDummyTarget(); - if (res) - target.IncrementStats(StatisticKind::FrameVarSuccess); - else - target.IncrementStats(StatisticKind::FrameVarFailure); + GetSelectedOrDummyTarget().GetStatistics().NotifyFrameVar(res); return res; } diff --git a/lldb/source/Commands/CommandObjectStats.cpp b/lldb/source/Commands/CommandObjectStats.cpp --- a/lldb/source/Commands/CommandObjectStats.cpp +++ b/lldb/source/Commands/CommandObjectStats.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "CommandObjectStats.h" +#include "lldb/Host/OptionParser.h" #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Target/Target.h" @@ -26,12 +27,12 @@ bool DoExecute(Args &command, CommandReturnObject &result) override { Target &target = GetSelectedOrDummyTarget(); - if (target.GetCollectingStats()) { + if (target.GetStatistics().GetCollectingStats()) { result.AppendError("statistics already enabled"); return false; } - target.SetCollectingStats(true); + target.GetStatistics().SetCollectingStats(true); result.SetStatus(eReturnStatusSuccessFinishResult); return true; } @@ -50,42 +51,84 @@ bool DoExecute(Args &command, CommandReturnObject &result) override { Target &target = GetSelectedOrDummyTarget(); - if (!target.GetCollectingStats()) { + if (!target.GetStatistics().GetCollectingStats()) { result.AppendError("need to enable statistics before disabling them"); return false; } - target.SetCollectingStats(false); + target.GetStatistics().SetCollectingStats(false); result.SetStatus(eReturnStatusSuccessFinishResult); return true; } }; +#define LLDB_OPTIONS_statistics_dump +#include "CommandOptions.inc" + class CommandObjectStatsDump : 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 = StatsDumpOptions(); + } + + llvm::ArrayRef GetDefinitions() override { + return llvm::makeArrayRef(g_statistics_dump_options); + } + + StatsDumpOptions m_metric_options; + }; + CommandObjectStatsDump(CommandInterpreter &interpreter) - : CommandObjectParsed(interpreter, "dump", "Dump statistics results", - nullptr, eCommandProcessMustBePaused) {} + : CommandObjectParsed( + interpreter, "statistics dump", "Dump metrics in JSON format", + "statistics dump []", eCommandRequiresTarget), + m_options() {} ~CommandObjectStatsDump() override = default; + Options *GetOptions() override { return &m_options; } + protected: bool DoExecute(Args &command, CommandReturnObject &result) override { - Target &target = GetSelectedOrDummyTarget(); - - uint32_t i = 0; - for (auto &stat : target.GetStatistics()) { - result.AppendMessageWithFormat( - "%s : %u\n", - lldb_private::GetStatDescription( - static_cast(i)) - .c_str(), - stat); - i += 1; - } + Target &target = m_exe_ctx.GetTargetRef(); + result.AppendMessageWithFormatv( + "{0:2}", target.ReportStatistics(m_options.m_metric_options)); result.SetStatus(eReturnStatusSuccessFinishResult); return true; } + + CommandOptions m_options; }; CommandObjectStats::CommandObjectStats(CommandInterpreter &interpreter) 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 = "statistics dump" in { + def statistics_dump_modules: Option<"modules", "m">, Group<1>, + Desc<"Include metrics for each module in the target.">; + def statistics_dump_breakpoints: Option<"breakpoints", "b">, Group<1>, + Desc<"Include metrics for each breakpoint in the target.">; + def statistics_dump_locations: Option<"locations", "l">, Group<1>, + Desc<"Include metrics for each breakpoint location in each breakpoint.">; + def statistics_dump_settings: Option<"settings", "s">, Group<1>, + Desc<"Include important target settings in metrics.">; + def statistics_dump_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 @@ -12,6 +12,7 @@ #include "Plugins/SymbolFile/DWARF/DIERef.h" #include "Plugins/SymbolFile/DWARF/DWARFDIE.h" #include "Plugins/SymbolFile/DWARF/DWARFFormValue.h" +#include "lldb/Utility/ElapsedTime.h" class DWARFDeclContext; class DWARFDIE; @@ -62,8 +63,11 @@ virtual void Dump(Stream &s) = 0; + ElapsedTime::Duration GetIndexTime() { return m_index_time; } + protected: Module &m_module; + ElapsedTime::Duration 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 @@ -25,6 +25,7 @@ #include "lldb/Symbol/SymbolContext.h" #include "lldb/Symbol/SymbolFile.h" #include "lldb/Utility/ConstString.h" +#include "lldb/Utility/ElapsedTime.h" #include "lldb/Utility/Flags.h" #include "lldb/Utility/RangeMap.h" #include "lldb/lldb-private.h" @@ -318,6 +319,16 @@ /// Same as GetLanguage() but reports all C++ versions as C++ (no version). static lldb::LanguageType GetLanguageFamily(DWARFUnit &unit); + uint64_t GetDebugInfoSize() override; + lldb_private::ElapsedTime::Duration GetDebugInfoParseTime() override { + return m_parse_time; + } + lldb_private::ElapsedTime::Duration GetDebugInfoIndexTime() override; + + lldb_private::ElapsedTime::Duration &GetDebugInfoParseTimeRef() { + return m_parse_time; + } + protected: typedef llvm::DenseMap DIEToTypePtr; @@ -519,6 +530,7 @@ llvm::DenseMap m_type_unit_support_files; std::vector m_lldb_cu_to_dwarf_unit; + lldb_private::ElapsedTime::Duration 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,19 @@ 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; +} + +ElapsedTime::Duration SymbolFileDWARF::GetDebugInfoIndexTime() { + if (m_index) + return m_index->GetIndexTime(); + return ElapsedTime::Duration(); +} 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 @@ -10,6 +10,7 @@ #define LLDB_SOURCE_PLUGINS_SYMBOLFILE_DWARF_SYMBOLFILEDWARFDEBUGMAP_H #include "lldb/Symbol/SymbolFile.h" +#include "lldb/Utility/ElapsedTime.h" #include "lldb/Utility/RangeMap.h" #include "llvm/Support/Chrono.h" #include @@ -142,6 +143,10 @@ // PluginInterface protocol lldb_private::ConstString GetPluginName() override; + uint64_t GetDebugInfoSize() override; + lldb_private::ElapsedTime::Duration GetDebugInfoParseTime() override; + lldb_private::ElapsedTime::Duration 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; +} + +ElapsedTime::Duration SymbolFileDWARFDebugMap::GetDebugInfoParseTime() { + ElapsedTime::Duration elapsed(0.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) + elapsed += symfile->GetDebugInfoParseTime(); + } + } + return false; // Keep iterating + }); + return elapsed; +} + +ElapsedTime::Duration SymbolFileDWARFDebugMap::GetDebugInfoIndexTime() { + ElapsedTime::Duration elapsed(0.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) + elapsed += symfile->GetDebugInfoIndexTime(); + } + } + return false; // Keep iterating + }); + return elapsed; +} 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 @@ -38,6 +38,7 @@ StackFrameList.cpp StackFrameRecognizer.cpp StackID.cpp + Statistics.cpp StopInfo.cpp StructuredDataPlugin.cpp SystemRuntime.cpp 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().GetStatistics().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().GetStatistics().SetFirstPrivateStopTime(); + if (!m_mod_id.IsLastResumeForUserExpression()) m_mod_id.SetStopEventForLastNaturalStopID(event_sp); m_memory_cache.Clear(); diff --git a/lldb/source/Target/Statistics.cpp b/lldb/source/Target/Statistics.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Target/Statistics.cpp @@ -0,0 +1,203 @@ +//===-- 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/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 SuccessFailStats::Notify(bool success) { + if (success) + ++successes; + else + ++failures; +} + +json::Value SuccessFailStats::ToJSON() const { + return json::Value(json::Object{ + {"successes", successes}, + {"failures", failures}, + }); +} + +void TargetStats::CollectModuleStats(Target &target) { + m_modules.clear(); + for (ModuleSP module_sp : target.GetImages().Modules()) { + ModuleStats module; + module.path = module_sp->GetFileSpec().GetPath(); + module.uuid = module_sp->GetUUID().GetAsString(); + module.triple = module_sp->GetArchitecture().GetTriple().str(); + SymbolFile *sym_file = module_sp->GetSymbolFile(); + if (sym_file) { + module.debug_index_time = sym_file->GetDebugInfoIndexTime().count(); + module.debug_parse_time = sym_file->GetDebugInfoParseTime().count(); + } + module.symtab_parse_time = module_sp->GetSymtabParseTime().count(); + module.symtab_names_time = module_sp->GetSymtabNamesTime().count(); + module.debug_info_size = module_sp->GetDebugInfoSize(); + m_modules.push_back(std::move(module)); + } +} + +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 TargetStats::ToJSON(Target &target, + const StatsDumpOptions &options) { + CollectModuleStats(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 : m_modules) { + modules.push_back(module.ToJSON()); + debug_info_size += module.debug_info_size; + debug_parse_time += module.debug_parse_time; + debug_index_time += module.debug_index_time; + symtab_parse_time += module.symtab_parse_time; + symtab_names_time += module.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->GetStatistics(options)); + totalBreakpointResolveTime += bp->GetResolveTime().count(); + } + + // 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}, + {"expressionEvaluation", m_expr_eval.ToJSON()}, + {"frameVariable", m_frame_var.ToJSON()}, + }; + 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.count()); + + return target_metrics_json; +} + +void TargetStats::SetLaunchOrAttachTime() { + launch_or_attach_time = std::chrono::steady_clock::now(); + first_private_stop_time = llvm::None; +} + +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 (!first_private_stop_time.hasValue()) + first_private_stop_time = std::chrono::steady_clock::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 (!first_public_stop_time.hasValue()) + first_public_stop_time = std::chrono::steady_clock::now(); +} + +json::Value ModuleStats::ToJSON() const { + json::Object module; + EmplaceSafeString(module, "path", path); + EmplaceSafeString(module, "uuid", uuid); + EmplaceSafeString(module, "triple", triple); + module.try_emplace("debugInfoParseTime", debug_parse_time); + module.try_emplace("debugInfoIndexTime", debug_index_time); + module.try_emplace("symbolTableParseTime", symtab_parse_time); + module.try_emplace("symbolTableIndexTime", symtab_names_time); + module.try_emplace("debugInfoSize", (int64_t)debug_info_size); + return module; +} 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 @@ -95,14 +95,10 @@ m_watchpoint_list(), m_process_sp(), m_search_filter_sp(), m_image_search_paths(ImageSearchPathsChanged, this), m_source_manager_up(), m_stop_hooks(), m_stop_hook_next_id(0), - m_latest_stop_hook_id(0), - m_valid(true), m_suppress_stop_hooks(false), + m_latest_stop_hook_id(0), m_valid(true), m_suppress_stop_hooks(false), m_is_dummy_target(is_dummy_target), m_frame_recognizer_manager_up( - std::make_unique()), - m_stats_storage(static_cast(StatisticKind::StatisticMax)) - -{ + std::make_unique()) { SetEventName(eBroadcastBitBreakpointChanged, "breakpoint-changed"); SetEventName(eBroadcastBitModulesLoaded, "modules-loaded"); SetEventName(eBroadcastBitModulesUnloaded, "modules-unloaded"); @@ -1400,6 +1396,7 @@ ClearModules(false); if (executable_sp) { + ElapsedTime elapsed(m_stats.GetCreateTime()); LLDB_SCOPED_TIMERF("Target::SetExecutableModule (executable = '%s')", executable_sp->GetFileSpec().GetPath().c_str()); @@ -2908,6 +2905,7 @@ void Target::ClearAllLoadedSections() { m_section_load_history.Clear(); } Status Target::Launch(ProcessLaunchInfo &launch_info, Stream *stream) { + m_stats.SetLaunchOrAttachTime(); Status error; Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_TARGET)); @@ -3119,6 +3117,7 @@ } Status Target::Attach(ProcessAttachInfo &attach_info, Stream *stream) { + m_stats.SetLaunchOrAttachTime(); auto state = eStateInvalid; auto process_sp = GetProcessSP(); if (process_sp) { @@ -4464,3 +4463,9 @@ else return m_mutex; } + +/// Get metrics associated with this target in JSON format. +llvm::json::Value Target::ReportStatistics(const StatsDumpOptions &options) { + m_stats.CollectModuleStats(*this); + return m_stats.ToJSON(*this, options); +} diff --git a/lldb/test/API/commands/statistics/basic/TestStats.py b/lldb/test/API/commands/statistics/basic/TestStats.py --- a/lldb/test/API/commands/statistics/basic/TestStats.py +++ b/lldb/test/API/commands/statistics/basic/TestStats.py @@ -1,4 +1,5 @@ import lldb +import json from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil @@ -7,22 +8,85 @@ mydir = TestBase.compute_mydir(__file__) - def test(self): + def setUp(self): + TestBase.setUp(self) self.build() - lldbutil.run_to_source_breakpoint(self, "// break here", lldb.SBFileSpec("main.c")) - self.expect("statistics disable", substrs=['need to enable statistics before disabling'], error=True) + NO_DEBUG_INFO_TESTCASE = True + + def test_enable_disable(self): + """ + Test "statistics disable" and "statistics enable". These don't do + anything anymore for cheap to gather statistics. In the future if + statistics are expensive to gather, we can enable the feature inside + of LLDB and test that enabling and disabling stops expesive information + from being gathered. + """ + target = self.createTestTarget() - # 'expression' should change the statistics. + self.expect("statistics disable", substrs=['need to enable statistics before disabling'], error=True) self.expect("statistics enable") self.expect("statistics enable", substrs=['already enabled'], error=True) - self.expect("expr patatino", substrs=['27']) self.expect("statistics disable") - self.expect("statistics dump", substrs=['expr evaluation successes : 1\n', - 'expr evaluation failures : 0\n']) + self.expect("statistics disable", substrs=['need to enable statistics before disabling'], error=True) - self.expect("statistics enable") - # Doesn't parse. + def verify_key_in_dict(self, key, d, description): + self.assertEqual(key in d, True, + 'make sure key "%s" is in dictionary %s' % (key, description)) + + def verify_key_not_in_dict(self, key, d, description): + self.assertEqual(key in d, False, + 'make sure key "%s" is in dictionary %s' % (key, description)) + + def verify_keys(self, dict, description, keys_exist, keys_missing=None): + """ + Verify that all keys in "keys_exist" list are top level items in + "dict", and that all keys in "keys_missing" do not exist as top + level items in "dict". + """ + if keys_exist: + for key in keys_exist: + self.verify_key_in_dict(key, dict, description) + if keys_missing: + for key in keys_missing: + self.verify_key_not_in_dict(key, dict, description) + + def verify_success_fail_count(self, stats, key, num_successes, num_fails): + self.verify_key_in_dict(key, stats, 'stats["%s"]' % (key)) + success_fail_dict = stats[key] + self.assertEqual(success_fail_dict['successes'], num_successes, + 'make sure success count') + self.assertEqual(success_fail_dict['failures'], num_fails, + 'make sure success count') + + def get_stats(self, options=None, log_path=None): + """ + Get the output of the "statistics dump" with optional extra options + and return the JSON as a python dictionary. + """ + if log_path is not None: + f = open(log_path, 'w') + else: + f = None + return_obj = lldb.SBCommandReturnObject() + command = "statistics dump " + if options is not None: + command += options + if f is not None: + f.write('(lldb) %s' % (command)) + self.ci.HandleCommand(command, return_obj, False) + metrics_json = return_obj.GetOutput() + if f: + f.write(metrics_json) + return json.loads(metrics_json) + + def test_expressions_frame_var_counts(self): + lldbutil.run_to_source_breakpoint(self, "// break here", + lldb.SBFileSpec("main.c")) + + self.expect("expr patatino", substrs=['27']) + stats = self.get_stats() + self.verify_success_fail_count(stats, 'expressionEvaluation', 1, 0) self.expect("expr doesnt_exist", error=True, substrs=["undeclared identifier 'doesnt_exist'"]) # Doesn't successfully execute. @@ -30,17 +94,367 @@ # Interpret an integer as an array with 3 elements is also a failure. self.expect("expr -Z 3 -- 1", error=True, substrs=["expression cannot be used with --element-count"]) - self.expect("statistics disable") # We should have gotten 3 new failures and the previous success. - self.expect("statistics dump", substrs=['expr evaluation successes : 1\n', - 'expr evaluation failures : 3\n']) - - # 'frame var' with disabled statistics shouldn't change stats. - self.expect("frame var", substrs=['27']) + stats = self.get_stats() + self.verify_success_fail_count(stats, 'expressionEvaluation', 1, 3) self.expect("statistics enable") # 'frame var' with enabled statistics will change stats. self.expect("frame var", substrs=['27']) - self.expect("statistics disable") - self.expect("statistics dump", substrs=['frame var successes : 1\n', - 'frame var failures : 0\n']) + stats = self.get_stats() + self.verify_success_fail_count(stats, 'frameVariable', 1, 0) + + def find_module_in_metrics(self, path, stats): + modules = stats['modules'] + for module in modules: + if module['path'] == path: + return module + return None + + def test_default_no_run(self): + """Test "statistics dump" without running the target. + + When we don't run the target, we expect to not see any 'firstStopTime' + or 'launchOrAttachTime' top level keys that measure the launch or + attach of the target. + + Output expected to be something like: + + (lldb) statistics dump + { + "targetCreateTime": 0.26566899599999999, + "totalBreakpointResolveTime": 0.0031409419999999999, + "totalDebugInfoIndexTime": 0, + "totalDebugInfoParseTime": 0, + "totalDebugInfoSize": 2193, + "totalSymbolTableIndexTime": 0.056834488000000009, + "totalSymbolTableParseTime": 0.093979421999999979 + } + + """ + target = self.createTestTarget() + stats = self.get_stats(log_path="/tmp/test_default_no_run.txt") + keys_exist = [ + 'expressionEvaluation', + 'frameVariable', + 'targetCreateTime', + 'totalBreakpointResolveTime', + 'totalDebugInfoIndexTime', + 'totalDebugInfoParseTime', + 'totalDebugInfoSize', + 'totalSymbolTableIndexTime', + 'totalSymbolTableParseTime', + ] + keys_missing = [ + 'firstStopTime', + 'launchOrAttachTime', + 'breakpoints', + 'modules', + 'settings' + ] + self.verify_keys(stats, '"stats"', keys_exist, keys_missing) + self.assertGreater(stats['targetCreateTime'], 0.0) + self.assertEqual(stats['totalBreakpointResolveTime'], 0.0) + self.assertGreater(stats['totalDebugInfoSize'], 0) + self.assertGreater(stats['totalSymbolTableIndexTime'], 0.0) + self.assertGreater(stats['totalSymbolTableParseTime'], 0.0) + + def test_default_with_run(self): + """Test "statistics dump" when running the target to a breakpoint. + + When we run the target, we expect to see 'launchOrAttachTime' and + 'firstStopTime' top level keys. + + Output expected to be something like: + + (lldb) statistics dump + { + "targetCreateTime": 0.26566899599999999, + "totalBreakpointResolveTime": 0.0031409419999999999, + "totalDebugInfoIndexTime": 0, + "totalDebugInfoParseTime": 0, + "totalDebugInfoSize": 2193, + "totalSymbolTableIndexTime": 0.056834488000000009, + "totalSymbolTableParseTime": 0.093979421999999979 + } + + """ + target = self.createTestTarget() + lldbutil.run_to_source_breakpoint(self, "// break here", + lldb.SBFileSpec("main.c")) + stats = self.get_stats(log_path="/tmp/test_default_no_run.txt") + keys_exist = [ + 'expressionEvaluation', + 'firstStopTime', + 'frameVariable', + 'launchOrAttachTime', + 'targetCreateTime', + 'totalBreakpointResolveTime', + 'totalDebugInfoIndexTime', + 'totalDebugInfoParseTime', + 'totalDebugInfoSize', + 'totalSymbolTableIndexTime', + 'totalSymbolTableParseTime', + ] + keys_missing = [ + 'breakpoints', + 'modules', + 'settings' + ] + self.verify_keys(stats, '"stats"', keys_exist, keys_missing) + self.assertGreater(stats['firstStopTime'], 0.0) + self.assertGreater(stats['launchOrAttachTime'], 0.0) + self.assertGreater(stats['targetCreateTime'], 0.0) + self.assertGreater(stats['totalBreakpointResolveTime'], 0.0) + self.assertGreater(stats['totalDebugInfoSize'], 0) + self.assertGreater(stats['totalSymbolTableIndexTime'], 0.0) + self.assertGreater(stats['totalSymbolTableParseTime'], 0.0) + + def test_modules(self): + """Test "statistics dump --modules" + + Output expected to be something like: + + (lldb) statistics dump --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": "/.../TestStats.test_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": "/.../TestStats.test_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") + target = self.createTestTarget(file_path=exe) + stats = self.get_stats(options="--modules", + log_path="/tmp/test_default_no_run.txt") + keys_exist = [ + 'targetCreateTime', + 'totalBreakpointResolveTime', + 'totalDebugInfoIndexTime', + 'totalDebugInfoParseTime', + 'totalDebugInfoSize', + 'totalSymbolTableIndexTime', + 'totalSymbolTableParseTime', + 'modules', + ] + keys_missing = [ + 'breakpoints', + 'settings' + ] + self.verify_keys(stats, '"stats"', keys_exist, keys_missing) + exe_module = self.find_module_in_metrics(exe, stats) + module_keys = [ + 'debugInfoIndexTime', + 'debugInfoParseTime', + 'debugInfoSize', + 'path', + 'symbolTableIndexTime', + 'symbolTableParseTime', + 'triple', + 'uuid', + ] + self.assertNotEqual(exe_module, None) + self.verify_keys(exe_module, 'module dict for "%s"' % (exe), + module_keys) + + def test_breakpoints(self): + """Test "statistics dump --breakpoints" + + Output expected to be something like: + + (lldb) statistics dump --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 + }, + ] + } + + """ + target = self.createTestTarget() + self.runCmd("b main.cpp:7") + self.runCmd("b a_function") + stats = self.get_stats(options="--breakpoints") + keys_exist = [ + 'targetCreateTime', + 'totalBreakpointResolveTime', + 'totalDebugInfoIndexTime', + 'totalDebugInfoParseTime', + 'totalDebugInfoSize', + 'totalSymbolTableIndexTime', + 'totalSymbolTableParseTime', + 'breakpoints', + ] + keys_missing = [ + 'modules', + 'settings' + ] + self.verify_keys(stats, '"stats"', keys_exist, keys_missing) + self.assertGreater(stats['totalBreakpointResolveTime'], 0.0) + self.assertGreater(stats['totalDebugInfoParseTime'], 0.0) + breakpoints = stats['breakpoints'] + bp_keys_exist = [ + 'details', + 'id', + 'resolveTime' + ] + bk_keys_missing = [ + 'locations' + ] + for breakpoint in breakpoints: + self.verify_keys(breakpoint, 'stats["breakpoints"]', + bp_keys_exist, bk_keys_missing) + + def test_locations(self): + """Test "statistics dump --locations" + + Output expected to be something like: + + (lldb) statistics dump --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 + }, + ] + } + + """ + target = self.createTestTarget() + self.runCmd("b main.cpp:7") + self.runCmd("b a_function") + stats = self.get_stats(options="--locations") + keys_exist = [ + 'targetCreateTime', + 'totalBreakpointResolveTime', + 'totalDebugInfoIndexTime', + 'totalDebugInfoParseTime', + 'totalDebugInfoSize', + 'totalSymbolTableIndexTime', + 'totalSymbolTableParseTime', + 'breakpoints', + ] + keys_missing = [ + 'modules', + 'settings' + ] + self.verify_keys(stats, '"stats"', keys_exist, keys_missing) + self.assertGreater(stats['totalBreakpointResolveTime'], 0.0) + self.assertGreater(stats['totalDebugInfoParseTime'], 0.0) + breakpoints = stats['breakpoints'] + bp_keys_exist = [ + 'details', + 'id', + 'resolveTime', + 'locations' + ] + for breakpoint in breakpoints: + self.verify_keys(breakpoint, 'stats["breakpoints"]', + bp_keys_exist, None) + + def test_settings(self): + """Test "statistics dump --settings" + + Output expected to be something like: + + (lldb) statistics dump --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/TestStats.test_settings/a.out", + "target.clang-module-search-paths": [], + "target.debug-file-search-paths": [], + "target.exec-search-paths": [ + "/.../commands/target/metrics/TestStats.test_settings" + ], + "target.inline-breakpoint-strategy": "always", + "target.preload-symbols": true, + "target.run-args": [], + "target.skip-prologue": true, + "target.source-map": [] + }, + } + + """ + target = self.createTestTarget() + stats = self.get_stats(options="--settings") + keys_exist = [ + 'targetCreateTime', + 'totalBreakpointResolveTime', + 'totalDebugInfoIndexTime', + 'totalDebugInfoParseTime', + 'totalDebugInfoSize', + 'totalSymbolTableIndexTime', + 'totalSymbolTableParseTime', + 'settings' + ] + keys_missing = [ + 'breakpoints', + 'modules', + ] + self.verify_keys(stats, '"stats"', keys_exist, keys_missing) + settings = stats['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_keys(settings, 'stats["settings"]', settings_keys_exist) diff --git a/lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py b/lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py --- a/lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py +++ b/lldb/test/API/functionalities/stats_api/TestStatisticsAPI.py @@ -10,6 +10,8 @@ class TestStatsAPI(TestBase): mydir = TestBase.compute_mydir(__file__) + NO_DEBUG_INFO_TESTCASE = True + def test_stats_api(self): self.build() exe = self.getBuildArtifact("a.out") @@ -26,9 +28,18 @@ stats = target.GetStatistics() stream = lldb.SBStream() res = stats.GetAsJSON(stream) - stats_json = sorted(json.loads(stream.GetData())) - self.assertEqual(len(stats_json), 4) - self.assertIn("Number of expr evaluation failures", stats_json) - self.assertIn("Number of expr evaluation successes", stats_json) - self.assertIn("Number of frame var failures", stats_json) - self.assertIn("Number of frame var successes", stats_json) + stats_json = json.loads(stream.GetData()) + self.assertEqual('expressionEvaluation' in stats_json, True, + 'Make sure the "expressionEvaluation" key in in target.GetStatistics()') + self.assertEqual('frameVariable' in stats_json, True, + 'Make sure the "frameVariable" key in in target.GetStatistics()') + expressionEvaluation = stats_json['expressionEvaluation'] + self.assertEqual('successes' in expressionEvaluation, True, + 'Make sure the "successes" key in in "expressionEvaluation" dictionary"') + self.assertEqual('failures' in expressionEvaluation, True, + 'Make sure the "failures" key in in "expressionEvaluation" dictionary"') + frameVariable = stats_json['frameVariable'] + self.assertEqual('successes' in frameVariable, True, + 'Make sure the "successes" key in in "frameVariable" dictionary"') + self.assertEqual('failures' in frameVariable, True, + 'Make sure the "failures" key in in "frameVariable" dictionary"')