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/Target/Statistics.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(); + + /// Get the time it took to resolve all locations in this breakpoint. + StatsDuration GetResolveTime() const { return m_resolve_time; } + protected: friend class Target; // Protected Methods @@ -653,6 +660,8 @@ BreakpointName::Permissions m_permissions; + StatsDuration m_resolve_time{0.0}; + void SendBreakpointChangedEvent(lldb::BreakpointEventType eventKind); void SendBreakpointChangedEvent(BreakpointEventData *data); 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 @@ -439,12 +439,15 @@ 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); @@ -470,6 +473,7 @@ } else delete new_locations_event; } else { + ElapsedTime elapsed(m_resolve_time); m_resolver_sp->ResolveBreakpointInModules(*m_filter_sp, module_list); } } @@ -1086,3 +1090,34 @@ return bp_loc_sp; } + +json::Value Breakpoint::GetStatistics() { + json::Object bp; + bp.try_emplace("id", GetID()); + bp.try_emplace("resolveTime", m_resolve_time.count()); + bp.try_emplace("numLocations", (int64_t)GetNumLocations()); + bp.try_emplace("numResolvedLocations", (int64_t)GetNumResolvedLocations()); + bp.try_emplace("internal", IsInternal()); + if (!m_kind_description.empty()) + bp.try_emplace("kindDescription", m_kind_description); + // Put the full structured data for reproducing this breakpoint in a key/value + // pair named "details". This allows the breakpoint's details to be visible + // in the stats in case we need to reproduce a breakpoint that has long + // resolve times + 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 (auto expected_value = llvm::json::parse(ss.str())) { + bp.try_emplace("details", std::move(*expected_value)); + } else { + std::string details_error = toString(expected_value.takeError()); + json::Object details; + details.try_emplace("error", details_error); + bp.try_emplace("details", std::move(details)); + } + } + return json::Value(std::move(bp)); +} diff --git a/lldb/source/Target/Statistics.cpp b/lldb/source/Target/Statistics.cpp --- a/lldb/source/Target/Statistics.cpp +++ b/lldb/source/Target/Statistics.cpp @@ -80,6 +80,25 @@ } target_metrics_json.try_emplace("targetCreateTime", m_create_time.count()); + json::Array breakpoints_array; + double totalBreakpointResolveTime = 0.0; + // Rport both the normal breakpoint list and the internal breakpoint list. + for (int i = 0; i < 2; ++i) { + BreakpointList &breakpoints = target.GetBreakpointList(i == 1); + std::unique_lock lock; + breakpoints.GetListMutex(lock); + size_t num_breakpoints = breakpoints.GetSize(); + for (size_t i = 0; i < num_breakpoints; i++) { + Breakpoint *bp = breakpoints.GetBreakpointAtIndex(i).get(); + breakpoints_array.push_back(bp->GetStatistics()); + totalBreakpointResolveTime += bp->GetResolveTime().count(); + } + } + + target_metrics_json.try_emplace("breakpoints", std::move(breakpoints_array)); + target_metrics_json.try_emplace("totalBreakpointResolveTime", + totalBreakpointResolveTime); + return target_metrics_json; } 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 @@ -283,3 +283,85 @@ ] self.assertNotEqual(exe_module, None) self.verify_keys(exe_module, 'module dict for "%s"' % (exe), module_keys) + + def test_breakpoints(self): + """Test "statistics dump" + + Output expected to be something like: + + { + "modules" : [...], + "targets" : [ + { + "firstStopTime": 0.34164492800000001, + "launchOrAttachTime": 0.31969605400000001, + "moduleIdentifiers": [...], + "targetCreateTime": 0.0040863039999999998 + "expressionEvaluation": { + "failures": 0, + "successes": 0 + }, + "frameVariable": { + "failures": 0, + "successes": 0 + }, + "breakpoints": [ + { + "details": {...}, + "id": 1, + "resolveTime": 2.65438675 + }, + { + "details": {...}, + "id": 2, + "resolveTime": 4.3632581669999997 + } + ] + } + ], + "totalDebugInfoByteSize": 182522234, + "totalDebugInfoIndexTime": 2.33343, + "totalDebugInfoParseTime": 8.2121400240000071, + "totalSymbolTableParseTime": 0.123, + "totalSymbolTableIndexTime": 0.234, + "totalBreakpointResolveTime": 7.0176449170000001 + } + + """ + target = self.createTestTarget() + self.runCmd("b main.cpp:7") + self.runCmd("b a_function") + debug_stats = self.get_stats() + debug_stat_keys = [ + 'modules', + 'targets', + 'totalSymbolTableParseTime', + 'totalSymbolTableIndexTime', + 'totalDebugInfoParseTime', + 'totalDebugInfoIndexTime', + 'totalDebugInfoByteSize', + ] + self.verify_keys(debug_stats, '"debug_stats"', debug_stat_keys, None) + target_stats = debug_stats['targets'][0] + keys_exist = [ + 'breakpoints', + 'expressionEvaluation', + 'frameVariable', + 'targetCreateTime', + 'moduleIdentifiers', + 'totalBreakpointResolveTime', + ] + self.verify_keys(target_stats, '"stats"', keys_exist, None) + self.assertGreater(target_stats['totalBreakpointResolveTime'], 0.0) + breakpoints = target_stats['breakpoints'] + bp_keys_exist = [ + 'details', + 'id', + 'internal', + 'numLocations', + 'numResolvedLocations', + 'resolveTime' + ] + for breakpoint in breakpoints: + self.verify_keys(breakpoint, 'target_stats["breakpoints"]', + bp_keys_exist, None)