Jacob Lalonde b66dfa7273
[LLDB] Add load core time to target metrics (#161581)
This patch adds a load core time, right now we don't have much insight
into the performance of load core, especially for large coredumps. To
start collecting information on this I've added some minor
instrumentation code to measure the two call sites of `LoadCore`.

I've also added a test to validate the new metric is output in
statistics dump
2025-10-01 21:14:23 +00:00

188 lines
7.1 KiB
Python

# Test the SBAPI for GetStatistics()
import json
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class TestStatsAPI(TestBase):
NO_DEBUG_INFO_TESTCASE = True
def test_stats_api(self):
"""
Test SBTarget::GetStatistics() API.
"""
self.build()
exe = self.getBuildArtifact("a.out")
# Launch a process and break
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
self, "break here", lldb.SBFileSpec("main.c")
)
# Test enabling/disabling stats
self.assertFalse(target.GetCollectingStats())
target.SetCollectingStats(True)
self.assertTrue(target.GetCollectingStats())
target.SetCollectingStats(False)
self.assertFalse(target.GetCollectingStats())
# Test the function to get the statistics in JSON'ish.
stats = target.GetStatistics()
stream = lldb.SBStream()
res = stats.GetAsJSON(stream)
debug_stats = json.loads(stream.GetData())
self.assertIn(
"targets",
debug_stats,
'Make sure the "targets" key in in target.GetStatistics()',
)
self.assertIn(
"modules",
debug_stats,
'Make sure the "modules" key in in target.GetStatistics()',
)
stats_json = debug_stats["targets"][0]
self.assertIn(
"expressionEvaluation",
stats_json,
'Make sure the "expressionEvaluation" key in in target.GetStatistics()["targets"][0]',
)
self.assertIn(
"frameVariable",
stats_json,
'Make sure the "frameVariable" key in in target.GetStatistics()["targets"][0]',
)
self.assertNotIn(
"loadCoreTime",
stats_json,
"LoadCoreTime should not be present in a live, non-coredump target",
)
expressionEvaluation = stats_json["expressionEvaluation"]
self.assertIn(
"successes",
expressionEvaluation,
'Make sure the "successes" key in in "expressionEvaluation" dictionary"',
)
self.assertIn(
"failures",
expressionEvaluation,
'Make sure the "failures" key in in "expressionEvaluation" dictionary"',
)
frameVariable = stats_json["frameVariable"]
self.assertIn(
"successes",
frameVariable,
'Make sure the "successes" key in in "frameVariable" dictionary"',
)
self.assertIn(
"failures",
frameVariable,
'Make sure the "failures" key in in "frameVariable" dictionary"',
)
# Test statistics summary.
stats_options = lldb.SBStatisticsOptions()
stats_options.SetSummaryOnly(True)
stats_summary = target.GetStatistics(stats_options)
stream_summary = lldb.SBStream()
stats_summary.GetAsJSON(stream_summary)
debug_stats_summary = json.loads(stream_summary.GetData())
self.assertNotIn("modules", debug_stats_summary)
self.assertNotIn("commands", debug_stats_summary)
# Summary values should be the same as in full statistics.
# The exceptions to this are:
# - The parse time on Mac OS X is not deterministic.
# - Memory usage may grow over time due to the use of ConstString.
for key, value in debug_stats_summary.items():
self.assertIn(key, debug_stats)
if key != "memory" and key != "targets" and not key.endswith("Time"):
self.assertEqual(debug_stats[key], value)
def test_command_stats_api(self):
"""
Test GetCommandInterpreter::GetStatistics() API.
"""
self.build()
exe = self.getBuildArtifact("a.out")
lldbutil.run_to_name_breakpoint(self, "main")
interp = self.dbg.GetCommandInterpreter()
result = lldb.SBCommandReturnObject()
interp.HandleCommand("bt", result)
stream = lldb.SBStream()
res = interp.GetStatistics().GetAsJSON(stream)
command_stats = json.loads(stream.GetData())
# Verify bt command is correctly parsed into final form.
self.assertEqual(command_stats["thread backtrace"], 1)
# Verify original raw command is not duplicatedly captured.
self.assertNotIn("bt", command_stats)
# Verify bt's regex command is not duplicatedly captured.
self.assertNotIn("_regexp-bt", command_stats)
@add_test_categories(["dwo"])
def test_command_stats_force(self):
"""
Test reporting all pssible debug info stats by force loading all debug
info. For example, dwo files
"""
src_dir = self.getSourceDir()
dwo_yaml_path = os.path.join(src_dir, "main-main.dwo.yaml")
exe_yaml_path = os.path.join(src_dir, "main.yaml")
dwo_path = self.getBuildArtifact("main-main.dwo")
exe_path = self.getBuildArtifact("main")
self.yaml2obj(dwo_yaml_path, dwo_path)
self.yaml2obj(exe_yaml_path, exe_path)
# Turn on symbols on-demand loading
self.runCmd("settings set symbols.load-on-demand true")
# We need the current working directory to be set to the build directory
os.chdir(self.getBuildDir())
# Create a target with the object file we just created from YAML
target = self.dbg.CreateTarget(exe_path)
self.assertTrue(target, VALID_TARGET)
# Get statistics
stats_options = lldb.SBStatisticsOptions()
stats = target.GetStatistics(stats_options)
stream = lldb.SBStream()
stats.GetAsJSON(stream)
debug_stats = json.loads(stream.GetData())
self.assertEqual(debug_stats["totalDebugInfoByteSize"], 193)
# Get statistics with force loading
stats_options.SetReportAllAvailableDebugInfo(True)
stats_force = target.GetStatistics(stats_options)
stream_force = lldb.SBStream()
stats_force.GetAsJSON(stream_force)
debug_stats_force = json.loads(stream_force.GetData())
self.assertEqual(debug_stats_force["totalDebugInfoByteSize"], 445)
def test_core_load_time(self):
"""
Test to see if the coredump path is included in statistics dump.
"""
yaml_file = "arm64-minidump-build-ids.yaml"
src_dir = self.getSourceDir()
minidump_path = self.getBuildArtifact(os.path.basename(yaml_file) + ".dmp")
self.yaml2obj(os.path.join(src_dir, yaml_file), minidump_path)
target = self.dbg.CreateTarget(None)
process = target.LoadCore(minidump_path)
self.assertTrue(process.IsValid())
stats_options = lldb.SBStatisticsOptions()
stats = target.GetStatistics(stats_options)
stream = lldb.SBStream()
stats.GetAsJSON(stream)
debug_stats = json.loads(stream.GetData())
self.assertTrue("targets" in debug_stats)
target_info = debug_stats["targets"][0]
self.assertTrue("loadCoreTime" in target_info)
self.assertTrue(float(target_info["loadCoreTime"]) > 0.0)