blob: 112816ade4f19aff5f2a5b9c1775e140bf087577 [file]
# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import annotations
import argparse
import pathlib
import unittest
from unittest import mock
from typing_extensions import override
from crossbench.browsers.chromium.version import ChromiumVersion
from crossbench.browsers.settings import Settings
from crossbench.probes import all as all_probes
from crossbench.probes.profiling.context.android import \
generate_simpleperf_command_line
from crossbench.probes.profiling.context.linux import LinuxProfilingContext
from crossbench.probes.profiling.system_profiling import RENDERER_CMD_PATH, \
CallGraphMode, CleanupMode, ProfilingProbe, TargetMode
from tests import test_helper
from tests.crossbench.mock_browser import MockChromeStable, MockFirefox, \
MockSafari
from tests.crossbench.mock_helper import LinuxMockPlatform, MacOsMockPlatform
from tests.crossbench.probes.helper import GenericProbeTestCase
class SystemProfilingProbeTestCase(GenericProbeTestCase):
@override
def setUp(self):
super().setUp()
self.fs.add_real_file(RENDERER_CMD_PATH)
def test_simpleperf_command_line_with_tid(self):
output_path = pathlib.Path("simpleperf.perf.data")
self.assertListEqual(
generate_simpleperf_command_line(
target=TargetMode.RENDERER_MAIN_ONLY,
app_name="com.android.chrome",
renderer_pid=1234,
renderer_main_tid=5678,
call_graph_mode=CallGraphMode.DWARF,
frequency=None,
count=None,
cpus=(),
events=(),
grouped_events=(),
add_counters=(),
output_path=output_path), [
"simpleperf", "record", "-t", "5678", "--call-graph", "dwarf",
"--post-unwind=yes", "-o", output_path
])
def test_simpleperf_command_line_with_pid(self):
output_path = pathlib.Path("simpleperf.perf.data")
self.assertListEqual(
generate_simpleperf_command_line(
target=TargetMode.RENDERER_PROCESS_ONLY,
app_name="com.android.chrome",
renderer_pid=1234,
renderer_main_tid=5678,
call_graph_mode=CallGraphMode.DWARF,
frequency=None,
count=None,
cpus=(),
events=(),
grouped_events=(),
add_counters=(),
output_path=output_path), [
"simpleperf", "record", "-p", "1234", "--call-graph", "dwarf",
"--post-unwind=yes", "-o", output_path
])
def test_simpleperf_command_line_with_app(self):
output_path = pathlib.Path("simpleperf.perf.data")
self.assertListEqual(
generate_simpleperf_command_line(
target=TargetMode.BROWSER_APP_ONLY,
app_name="com.chrome.beta",
renderer_pid=None,
renderer_main_tid=None,
call_graph_mode=CallGraphMode.DWARF,
frequency=None,
count=None,
cpus=(),
events=(),
grouped_events=(),
add_counters=(),
output_path=output_path), [
"simpleperf", "record", "--app", "com.chrome.beta",
"--call-graph", "dwarf", "--post-unwind=yes", "-o", output_path
])
def test_simpleperf_command_line_systemwide(self):
output_path = pathlib.Path("simpleperf.perf.data")
self.assertListEqual(
generate_simpleperf_command_line(
target=TargetMode.SYSTEM_WIDE,
app_name="org.chromium.chrome",
renderer_pid=None,
renderer_main_tid=None,
call_graph_mode=CallGraphMode.DWARF,
frequency=None,
count=None,
cpus=(),
events=(),
grouped_events=(),
add_counters=(),
output_path=output_path), [
"simpleperf", "record", "-a", "--call-graph", "dwarf",
"--post-unwind=yes", "-o", output_path
])
def test_simpleperf_command_line_with_frequency(self):
output_path = pathlib.Path("simpleperf.perf.data")
self.assertListEqual(
generate_simpleperf_command_line(
target=TargetMode.SYSTEM_WIDE,
app_name="org.chromium.chrome",
renderer_pid=None,
renderer_main_tid=None,
call_graph_mode=CallGraphMode.FRAME_POINTER,
frequency=1234,
count=None,
cpus=(),
events=(),
grouped_events=(),
add_counters=(),
output_path=output_path), [
"simpleperf", "record", "-a", "--call-graph", "fp", "-f",
"1234", "-o", output_path
])
def test_simpleperf_command_line_with_count(self):
output_path = pathlib.Path("simpleperf.perf.data")
self.assertListEqual(
generate_simpleperf_command_line(
target=TargetMode.SYSTEM_WIDE,
app_name="org.chromium.chrome",
renderer_pid=None,
renderer_main_tid=None,
call_graph_mode=CallGraphMode.FRAME_POINTER,
frequency=None,
count=5,
cpus=(),
events=(),
grouped_events=(),
add_counters=(),
output_path=output_path), [
"simpleperf", "record", "-a", "--call-graph", "fp", "-c", "5",
"-o", output_path
])
def test_simpleperf_command_line_with_cpu(self):
output_path = pathlib.Path("simpleperf.perf.data")
self.assertListEqual(
generate_simpleperf_command_line(
target=TargetMode.SYSTEM_WIDE,
app_name="org.chromium.chrome",
renderer_pid=None,
renderer_main_tid=None,
call_graph_mode=CallGraphMode.FRAME_POINTER,
frequency=None,
count=None,
cpus=(
0,
1,
2,
),
events=(),
grouped_events=(),
add_counters=(),
output_path=output_path), [
"simpleperf", "record", "-a", "--call-graph", "fp", "--cpu",
"0,1,2", "-o", output_path
])
def test_simpleperf_command_line_with_events(self):
output_path = pathlib.Path("simpleperf.perf.data")
self.assertListEqual(
generate_simpleperf_command_line(
target=TargetMode.SYSTEM_WIDE,
app_name="org.chromium.chrome",
renderer_pid=None,
renderer_main_tid=None,
call_graph_mode=CallGraphMode.NO_CALL_GRAPH,
frequency=1234,
count=5,
cpus=(),
events=(
"cpu-cycles",
"instructions",
),
grouped_events=(),
add_counters=(),
output_path=output_path), [
"simpleperf", "record", "-a", "-f", "1234", "-c", "5", "-e",
"cpu-cycles,instructions", "-o", output_path
])
def test_simpleperf_command_line_with_grouped_events(self):
output_path = pathlib.Path("simpleperf.perf.data")
self.assertListEqual(
generate_simpleperf_command_line(
target=TargetMode.SYSTEM_WIDE,
app_name="org.chromium.chrome",
renderer_pid=None,
renderer_main_tid=None,
call_graph_mode=CallGraphMode.NO_CALL_GRAPH,
frequency=1234,
count=5,
cpus=(),
events=(),
grouped_events=(
"cpu-cycles",
"instructions",
),
add_counters=(),
output_path=output_path), [
"simpleperf", "record", "-a", "-f", "1234", "-c", "5",
"--group", "cpu-cycles,instructions", "-o", output_path
])
def test_simpleperf_command_line_with_add_counters(self):
output_path = pathlib.Path("simpleperf.perf.data")
self.assertListEqual(
generate_simpleperf_command_line(
target=TargetMode.SYSTEM_WIDE,
app_name="org.chromium.chrome",
renderer_pid=None,
renderer_main_tid=None,
call_graph_mode=CallGraphMode.NO_CALL_GRAPH,
frequency=1234,
count=5,
cpus=(),
events=("sched:sched_switch",),
grouped_events=(),
add_counters=(
"cpu-cycles",
"instructions",
),
output_path=output_path), [
"simpleperf", "record", "-a", "-f", "1234", "-c", "5", "-e",
"sched:sched_switch", "--add-counter",
"cpu-cycles,instructions", "--no-inherit", "-o", output_path
])
def test_parse_target_preset(self):
probe = ProfilingProbe()
self.assertEqual(probe.target, TargetMode.AUTO)
probe = ProfilingProbe.parse_str("browser_app_only")
self.assertEqual(probe.target, TargetMode.BROWSER_APP_ONLY)
probe = ProfilingProbe.parse_str("renderer_process_only")
self.assertEqual(probe.target, TargetMode.RENDERER_PROCESS_ONLY)
probe = ProfilingProbe.parse_str("renderer_main_only")
self.assertEqual(probe.target, TargetMode.RENDERER_MAIN_ONLY)
def test_pprof_default_none(self):
probe = ProfilingProbe()
self.assertIsNone(probe._run_pprof)
def test_run_pprof_method(self):
probe = ProfilingProbe(pprof=True)
mock_browser1 = mock.Mock()
self.assertTrue(probe.run_pprof(mock_browser1))
probe = ProfilingProbe(pprof=False)
mock_browser2 = mock.Mock()
self.assertFalse(probe.run_pprof(mock_browser2))
probe = ProfilingProbe(pprof=None)
mock_browser3 = mock.Mock()
mock_browser3.platform.is_linux = False
self.assertFalse(probe.run_pprof(mock_browser3))
mock_browser4 = mock.Mock()
mock_browser4.platform = LinuxMockPlatform(fake_fs=self.fs)
mock_browser4.platform.install_mock_binary("pprof", "/usr/bin/pprof4")
mock_browser4.platform.install_mock_binary("gcert", "/usr/bin/gcert4")
self.assertTrue(probe.run_pprof(mock_browser4))
mock_browser5 = mock.Mock()
mock_browser5.platform = LinuxMockPlatform(fake_fs=self.fs)
mock_browser5.platform.install_mock_binary("gcert", "/usr/bin/gcert5")
self.assertFalse(probe.run_pprof(mock_browser5))
mock_browser6 = mock.Mock()
mock_browser6.platform = LinuxMockPlatform(fake_fs=self.fs)
mock_browser6.platform.install_mock_binary("pprof", "/usr/bin/pprof6")
self.assertFalse(probe.run_pprof(mock_browser6))
def test_resolve_target_mode(self):
probe = ProfilingProbe()
self.assertEqual(probe.target, TargetMode.AUTO)
macos_platform = MacOsMockPlatform()
MockChromeStable.setup_fs(self.fs, macos_platform)
macos_browser = MockChromeStable(
"macos_chrome", settings=Settings(platform=macos_platform))
self.assertEqual(
probe.resolve_target_mode(macos_browser),
TargetMode.RENDERER_PROCESS_ONLY)
linux_platform = LinuxMockPlatform()
MockChromeStable.setup_fs(self.fs, linux_platform)
linux_browser = MockChromeStable(
"linux_chrome", settings=Settings(platform=linux_platform))
self.assertEqual(
probe.resolve_target_mode(linux_browser), TargetMode.BROWSER_APP_ONLY)
# For explicitly set targets, it should always return that target
probe = ProfilingProbe(target=TargetMode.SYSTEM_WIDE)
self.assertEqual(
probe.resolve_target_mode(macos_browser), TargetMode.SYSTEM_WIDE)
self.assertEqual(
probe.resolve_target_mode(linux_browser), TargetMode.SYSTEM_WIDE)
def test_create_non_defaults(self):
probe = ProfilingProbe.parse_dict({
"js": False,
"browser_process": True,
"spare_renderer_process": True,
"v8_interpreted_frames": False,
"pprof": False,
"cleanup": "never",
"target": "renderer_process_only",
"pin_renderer_main_core": 3,
"call_graph_mode": "dwarf",
"frequency": 1200,
"count": 430,
"cpu": [1, 2, 3],
"events": ["instructions", "cache-misses"],
"grouped_events": ["cache-references", "cache-misses"],
"add_counters": ["aa", "bb"],
})
self.assertTrue(probe.key)
self.assertFalse(probe.sample_js)
self.assertTrue(probe.sample_browser_process)
self.assertFalse(probe._run_pprof)
self.assertTrue(probe.cleanup_mode, CleanupMode.NEVER)
self.assertEqual(probe.target, TargetMode.RENDERER_PROCESS_ONLY)
self.assertTrue(probe.start_profiling_after_setup(probe.target))
self.assertEqual(probe.pin_renderer_main_core, 3)
self.assertEqual(probe.call_graph_mode, CallGraphMode.DWARF)
self.assertEqual(probe.frequency, 1200)
self.assertEqual(probe.count, 430)
self.assertEqual(probe.cpu, (1, 2, 3))
self.assertEqual(probe.events, ("instructions", "cache-misses"))
self.assertEqual(probe.grouped_events, ("cache-references", "cache-misses"))
self.assertEqual(probe.add_counters, ("aa", "bb"))
def test_v8_interpreted_frames_default(self):
probe = ProfilingProbe()
self.assertTrue(probe.expose_v8_interpreted_frames)
probe = ProfilingProbe(js=False)
self.assertFalse(probe.expose_v8_interpreted_frames)
probe = ProfilingProbe(js=True, v8_interpreted_frames=False)
self.assertFalse(probe.expose_v8_interpreted_frames)
with self.assertRaisesRegex(AssertionError,
"Cannot expose V8 interpreted frames"):
ProfilingProbe(js=False, v8_interpreted_frames=True)
probe = ProfilingProbe.parse_dict({"js": False})
self.assertFalse(probe.expose_v8_interpreted_frames)
probe = ProfilingProbe.parse_dict({"js": True})
self.assertTrue(probe.expose_v8_interpreted_frames)
def test_create_custom_frequency(self):
probe = ProfilingProbe.parse_dict({"freq": "max"})
self.assertEqual(probe.frequency, "max")
probe = ProfilingProbe.parse_dict({"freq": 333})
self.assertEqual(probe.frequency, 333)
def test_create_invalid_frequency(self):
with self.assertRaisesRegex(argparse.ArgumentTypeError, "frequency"):
_ = ProfilingProbe.parse_dict({"freq": -100})
with self.assertRaisesRegex(argparse.ArgumentTypeError, "frequency"):
_ = ProfilingProbe.parse_dict({"freq": "maaaaxxx"})
def test_spare_renderer(self):
browser_a = self.browsers[0]
browser_b = self.browsers[0]
probe_spare = ProfilingProbe(spare_renderer_process=True)
browser_a.attach_probe(probe_spare)
self.assertNotIn("SpareRendererForSitePerProcess",
browser_b.features.disabled)
probe_no_spare = ProfilingProbe(spare_renderer_process=False)
browser_b.attach_probe(probe_no_spare)
self.assertIn("SpareRendererForSitePerProcess", browser_b.features.disabled)
def test_attach_unsupported(self):
probe = ProfilingProbe()
macos_platform = MacOsMockPlatform()
test_browsers = (MockSafari, MockFirefox, MockChromeStable)
for browser_cls in test_browsers:
browser_cls.setup_fs(self.fs, macos_platform)
name = browser_cls.__name__
browser_cls(
name, settings=Settings(platform=macos_platform)).attach_probe(probe)
linux_platform = LinuxMockPlatform()
for browser_cls in test_browsers:
browser_cls.setup_fs(self.fs, linux_platform)
with self.assertRaises(AssertionError):
MockFirefox(
"firefox",
settings=Settings(platform=linux_platform)).attach_probe(probe)
MockChromeStable(
"chrome",
settings=Settings(platform=linux_platform)).attach_probe(probe)
class LinuxProfilingContextTestCase(GenericProbeTestCase):
@override
def setUp(self):
super().setUp()
self.probe = ProfilingProbe(pprof=None)
self.platform = LinuxMockPlatform(fake_fs=self.fs)
self.run = mock.Mock()
self.run.browser = mock.Mock()
self.run.browser.platform = self.platform
self.run.browser.version = ChromiumVersion((120, 0, 0, 0))
self.run.result_path = pathlib.Path("/tmp/test_result")
self.run.session = mock.Mock()
self.run.session.extra_js_flags = {}
self.run.get_default_probe_result_path = mock.Mock(
return_value=pathlib.Path("/tmp/test_result"))
self.platform.absolute = mock.Mock(
return_value=pathlib.Path("/tmp/test_result"))
def test_auto_infer_pprof_true(self):
self.platform.install_mock_binary("pprof", "/usr/bin/pprof")
self.platform.install_mock_binary("gcert", "/usr/bin/gcert")
context = LinuxProfilingContext(self.probe, self.run)
context.setup_v8_log_path = mock.Mock()
context.setup()
self.assertTrue(context.run_pprof)
def test_auto_infer_pprof_false_missing_pprof(self):
self.platform.install_mock_binary("gcert", "/usr/bin/gcert")
context = LinuxProfilingContext(self.probe, self.run)
context.setup_v8_log_path = mock.Mock()
context.setup()
self.assertFalse(context.run_pprof)
def test_auto_infer_pprof_false_missing_gcert(self):
self.platform.install_mock_binary("pprof", "/usr/bin/pprof")
context = LinuxProfilingContext(self.probe, self.run)
context.setup_v8_log_path = mock.Mock()
context.setup()
self.assertFalse(context.run_pprof)
def test_explicit_pprof_true(self):
self.probe = ProfilingProbe(pprof=True)
context = LinuxProfilingContext(self.probe, self.run)
context.setup_v8_log_path = mock.Mock()
context.setup()
self.assertTrue(context.run_pprof)
class EnumTestCase(unittest.TestCase):
def test_cleanup_mode(self):
self.assertIs(CleanupMode(True), CleanupMode.ALWAYS)
self.assertIs(CleanupMode(False), CleanupMode.NEVER)
self.assertIs(CleanupMode("always"), CleanupMode.ALWAYS)
self.assertIs(CleanupMode("never"), CleanupMode.NEVER)
self.assertIs(CleanupMode("auto"), CleanupMode.AUTO)
def test_target_mode(self):
self.assertIs(
TargetMode("renderer_main_only"), TargetMode.RENDERER_MAIN_ONLY)
self.assertIs(
TargetMode("RENDERER_MAIN_ONLY"), TargetMode.RENDERER_MAIN_ONLY)
def test_call_graph_mode(self):
self.assertIs(CallGraphMode("fp"), CallGraphMode.FRAME_POINTER)
self.assertIs(CallGraphMode("FP"), CallGraphMode.FRAME_POINTER)
# Remove import that's used to avoid circular import issues.
del all_probes
if __name__ == "__main__":
test_helper.run_pytest(__file__)