diff options
author | Yabin Cui <yabinc@google.com> | 2021-10-19 15:21:58 -0700 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2021-10-20 09:48:28 -0700 |
commit | cf9ee002871e8e7db9e283c6c564d069838bc56b (patch) | |
tree | 41e018ad0d5e15f2b5b54cbbb974e78d4192b6ef | |
parent | 423b78acfa1755605275f0e8d21092acceb44635 (diff) | |
download | extras-cf9ee002871e8e7db9e283c6c564d069838bc56b.tar.gz |
simpleperf: update test to use ExampleCpp.
1. Replace ExampleWithNative with ExampleCpp in tests.
2. Fix TestExampleCpp.test_annotate, which is broken because
llvm-symbolizer may output empty function name. So fix Addr2NearestLine
code parsing llvm-symbolizer output.
3. Some cpp tests have reason to fail on selected Android versions on
x86. So add code to skip them.
Bug: 203246914
Test: run scripts/test/test.py -p TestExampleCpp* on Android N-S
Change-Id: Ia39a7e9068ac58d3912473219bd19b98e72595bc
-rw-r--r-- | simpleperf/scripts/simpleperf_utils.py | 92 | ||||
-rw-r--r-- | simpleperf/scripts/test/app_test.py | 15 | ||||
-rw-r--r-- | simpleperf/scripts/test/cpp_app_test.py | 76 | ||||
-rw-r--r-- | simpleperf/scripts/test/test_utils.py | 10 |
4 files changed, 120 insertions, 73 deletions
diff --git a/simpleperf/scripts/simpleperf_utils.py b/simpleperf/scripts/simpleperf_utils.py index 0f416142..0519b5fa 100644 --- a/simpleperf/scripts/simpleperf_utils.py +++ b/simpleperf/scripts/simpleperf_utils.py @@ -322,8 +322,8 @@ class AdbHelper(object): return logging.info('unroot adb') self.run(['unroot']) - self.run(['wait-for-device']) time.sleep(1) + self.run(['wait-for-device']) def switch_to_root(self) -> bool: if not self.enable_switch_to_root: @@ -610,38 +610,7 @@ class Addr2Nearestline(object): stdoutdata = bytes_to_str(stdoutdata) except OSError: return - addr_map: Dict[int, List[Tuple[int]]] = {} - cur_line_list: Optional[List[Tuple[int]]] = None - need_function_name = self.with_function_name - cur_function_name: Optional[str] = None - for line in stdoutdata.strip().split('\n'): - line = line.strip() - if not line: - continue - if line[:2] == '0x': - # a new address - cur_line_list = addr_map[int(line, 16)] = [] - elif need_function_name: - cur_function_name = line.strip() - need_function_name = False - else: - need_function_name = self.with_function_name - if cur_line_list is None: - continue - file_path, line_number = self._parse_source_location(line) - if not file_path or not line_number: - # An addr can have a list of (file, line), when the addr belongs to an inlined - # function. Sometimes only part of the list has ? mark. In this case, we think - # the line info is valid if the first line doesn't have ? mark. - if not cur_line_list: - cur_line_list = None - continue - file_id = dso.get_file_id(file_path) - if self.with_function_name: - func_id = dso.get_func_id(cur_function_name) - cur_line_list.append((file_id, line_number, func_id)) - else: - cur_line_list.append((file_id, line_number)) + addr_map = self._parse_line_output(stdoutdata, dso) # 3. Fill line info in dso.addrs. for addr in dso.addrs: @@ -665,7 +634,62 @@ class Addr2Nearestline(object): args.append('--functions=none') return args - def _parse_source_location(self, line: str) -> Tuple[Optional[str], Optional[int]]: + def _parse_line_output(self, output: str, dso: Addr2Nearestline.Dso) -> Dict[int, + List[Tuple[int]]]: + """ + The output is a list of lines. + address1 + function_name1 (the function name can be empty) + source_location1 + function_name2 + source_location2 + ... + (end with empty line) + """ + + addr_map: Dict[int, List[Tuple[int]]] = {} + lines = output.strip().split('\n') + i = 0 + while i < len(lines): + address = self._parse_line_output_address(lines[i]) + i += 1 + if address is None: + continue + info = [] + while i < len(lines): + if self.with_function_name: + if i + 1 == len(lines): + break + function_name = lines[i].strip() + if not function_name and (':' not in lines[i+1]): + # no more frames + break + i += 1 + file_path, line_number = self._parse_line_output_source_location(lines[i]) + i += 1 + if not file_path or not line_number: + # An addr can have a list of (file, line), when the addr belongs to an inlined + # function. Sometimes only part of the list has ? mark. In this case, we think + # the line info is valid if the first line doesn't have ? mark. + if not info: + break + continue + file_id = dso.get_file_id(file_path) + if self.with_function_name: + func_id = dso.get_func_id(function_name) + info.append((file_id, line_number, func_id)) + else: + info.append((file_id, line_number)) + if info: + addr_map[address] = info + return addr_map + + def _parse_line_output_address(self, output: str) -> Optional[int]: + if output.startswith('0x'): + return int(output, 16) + return None + + def _parse_line_output_source_location(self, line: str) -> Tuple[Optional[str], Optional[int]]: file_path, line_number = None, None # Handle lines in format filename:line:column, like "runtest/two_functions.cpp:14:25". # Filename may contain ':' like "C:\Users\...\file". diff --git a/simpleperf/scripts/test/app_test.py b/simpleperf/scripts/test/app_test.py index e0aa7025..146c1403 100644 --- a/simpleperf/scripts/test/app_test.py +++ b/simpleperf/scripts/test/app_test.py @@ -14,10 +14,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import glob import os +from pathlib import Path import re import shutil import subprocess +import time from simpleperf_utils import remove from . test_utils import TestBase, TestHelper, AdbHelper, INFERNO_SCRIPT @@ -30,12 +33,12 @@ class TestExampleBase(TestBase): cls.example_path = TestHelper.testdata_path(example_name) if not os.path.isdir(cls.example_path): log_fatal("can't find " + cls.example_path) - for root, _, files in os.walk(cls.example_path): - if 'app-profiling.apk' in files: - cls.apk_path = os.path.join(root, 'app-profiling.apk') - break - if not hasattr(cls, 'apk_path'): - log_fatal("can't find app-profiling.apk under " + cls.example_path) + apk_files = list(Path(cls.example_path).glob('**/app-profiling.apk')) + if not apk_files: + apk_files = list(Path(cls.example_path).glob('**/app-debug.apk')) + if not apk_files: + log_fatal("can't find apk under " + cls.example_path) + cls.apk_path = apk_files[0] cls.package_name = package_name cls.activity_name = activity_name args = ["install", "-r"] diff --git a/simpleperf/scripts/test/cpp_app_test.py b/simpleperf/scripts/test/cpp_app_test.py index 8565cb3a..fb4d7077 100644 --- a/simpleperf/scripts/test/cpp_app_test.py +++ b/simpleperf/scripts/test/cpp_app_test.py @@ -21,12 +21,10 @@ from . app_test import TestExampleBase from . test_utils import INFERNO_SCRIPT, TestHelper -class TestExampleWithNative(TestExampleBase): +class TestExampleCpp(TestExampleBase): @classmethod def setUpClass(cls): - cls.prepare("SimpleperfExampleWithNative", - "com.example.simpleperf.simpleperfexamplewithnative", - ".MainActivity") + cls.prepare("SimpleperfExampleCpp", "simpleperf.example.cpp", ".MainActivity") def test_app_profiler(self): self.common_test_app_profiler() @@ -48,7 +46,7 @@ class TestExampleWithNative(TestExampleBase): self.check_annotation_summary(summary_file, [ ("native-lib.cpp", 20, 0), ("BusyLoopThread", 20, 0), - ("line 46", 20, 0)]) + ("line 43", 20, 0)]) def test_report_sample(self): self.common_test_report_sample( @@ -77,11 +75,11 @@ class TestExampleWithNative(TestExampleBase): '--add_disassembly', '--binary_filter', "libnative-lib.so"]) -class TestExampleWithNativeRoot(TestExampleBase): +class TestExampleCppRoot(TestExampleBase): @classmethod def setUpClass(cls): - cls.prepare("SimpleperfExampleWithNative", - "com.example.simpleperf.simpleperfexamplewithnative", + cls.prepare("SimpleperfExampleCpp", + "simpleperf.example.cpp", ".MainActivity", adb_root=True) @@ -89,12 +87,10 @@ class TestExampleWithNativeRoot(TestExampleBase): self.common_test_app_profiler() -class TestExampleWithNativeTraceOffCpu(TestExampleBase): +class TestExampleCppTraceOffCpu(TestExampleBase): @classmethod def setUpClass(cls): - cls.prepare("SimpleperfExampleWithNative", - "com.example.simpleperf.simpleperfexamplewithnative", - ".SleepActivity") + cls.prepare("SimpleperfExampleCpp", "simpleperf.example.cpp", ".SleepActivity") def test_smoke(self): self.run_app_profiler(record_arg="-g -f 1000 --duration 10 -e cpu-cycles:u --trace-offcpu") @@ -103,6 +99,13 @@ class TestExampleWithNativeTraceOffCpu(TestExampleBase): "SleepThread(void*)", "RunFunction()", "SleepFunction(unsigned long long)"]) + if (self.adb.get_device_arch() in ['x86', 'x86_64'] and + TestHelper.get_kernel_version() < (4, 19)): + # Skip on x86 and kernel < 4.19, which doesn't have patch + # "perf/x86: Store user space frame-pointer value on a sample" and may fail to unwind + # system call. + TestHelper.log('Skip annotation test on x86 for kernel < 4.19.') + return remove("annotated_files") self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "SleepThread"]) self.check_exist(dirname="annotated_files") @@ -113,68 +116,77 @@ class TestExampleWithNativeTraceOffCpu(TestExampleBase): ("SleepThread", 80, 0), ("RunFunction", 20, 20), ("SleepFunction", 20, 0), - ("line 73", 20, 0), - ("line 83", 20, 0)]) + ("line 70", 20, 0), + ("line 80", 20, 0)]) self.run_cmd([INFERNO_SCRIPT, "-sc"]) self.check_inferno_report_html([('SleepThread', 80), ('RunFunction', 20), ('SleepFunction', 20)]) -class TestExampleWithNativeJniCall(TestExampleBase): +class TestExampleCppJniCall(TestExampleBase): @classmethod def setUpClass(cls): - cls.prepare("SimpleperfExampleWithNative", - "com.example.simpleperf.simpleperfexamplewithnative", - ".MixActivity") + cls.prepare("SimpleperfExampleCpp", "simpleperf.example.cpp", ".MixActivity") def test_smoke(self): + if self.adb.get_android_version() == 8: + TestHelper.log( + "Android O needs wrap.sh to use compiled java code. But cpp example doesn't use wrap.sh.") + return self.run_app_profiler() self.run_cmd(["report.py", "-g", "--comms", "BusyThread", "-o", "report.txt"]) self.check_strings_in_file("report.txt", [ - "com.example.simpleperf.simpleperfexamplewithnative.MixActivity$1.run", - "Java_com_example_simpleperf_simpleperfexamplewithnative_MixActivity_callFunction"]) + "simpleperf.example.cpp.MixActivity$1.run", + "Java_simpleperf_example_cpp_MixActivity_callFunction"]) remove("annotated_files") self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "BusyThread"]) self.check_exist(dirname="annotated_files") self.check_file_under_dir("annotated_files", "native-lib.cpp") summary_file = os.path.join("annotated_files", "summary") - self.check_annotation_summary(summary_file, [("native-lib.cpp", 5, 0), ("line 40", 5, 0)]) + self.check_annotation_summary(summary_file, [("native-lib.cpp", 5, 0), ("line 37", 5, 0)]) if self.use_compiled_java_code: self.check_file_under_dir("annotated_files", "MixActivity.java") self.check_annotation_summary(summary_file, [ ("MixActivity.java", 80, 0), ("run", 80, 0), - ("line 26", 20, 0), + ("line 27", 20, 0), ("native-lib.cpp", 5, 0), - ("line 40", 5, 0)]) + ("line 37", 5, 0)]) self.run_cmd([INFERNO_SCRIPT, "-sc"]) -class TestExampleWithNativeForce32Bit(TestExampleWithNative): +class TestExampleCppForce32Bit(TestExampleCpp): @classmethod def setUpClass(cls): - cls.prepare("SimpleperfExampleWithNative", - "com.example.simpleperf.simpleperfexamplewithnative", + cls.prepare("SimpleperfExampleCpp", + "simpleperf.example.cpp", ".MainActivity", abi=TestHelper.get_32bit_abi()) -class TestExampleWithNativeRootForce32Bit(TestExampleWithNativeRoot): +class TestExampleCppRootForce32Bit(TestExampleCppRoot): @classmethod def setUpClass(cls): - cls.prepare("SimpleperfExampleWithNative", - "com.example.simpleperf.simpleperfexamplewithnative", + cls.prepare("SimpleperfExampleCpp", + "simpleperf.example.cpp", ".MainActivity", abi=TestHelper.get_32bit_abi(), adb_root=False) -class TestExampleWithNativeTraceOffCpuForce32Bit(TestExampleWithNativeTraceOffCpu): +class TestExampleCppTraceOffCpuForce32Bit(TestExampleCppTraceOffCpu): @classmethod def setUpClass(cls): - cls.prepare("SimpleperfExampleWithNative", - "com.example.simpleperf.simpleperfexamplewithnative", + cls.prepare("SimpleperfExampleCpp", + "simpleperf.example.cpp", ".SleepActivity", abi=TestHelper.get_32bit_abi()) + + def test_smoke(self): + if (self.adb.get_device_arch() in ['x86', 'x86_64'] and + self.adb.get_android_version() in [10, 11]): + TestHelper.log("Skip test on x86. Because simpleperf can't unwind 32bit vdso.") + return + super().test_smoke() diff --git a/simpleperf/scripts/test/test_utils.py b/simpleperf/scripts/test/test_utils.py index 04911091..77d2a3b2 100644 --- a/simpleperf/scripts/test/test_utils.py +++ b/simpleperf/scripts/test/test_utils.py @@ -21,11 +21,12 @@ import logging from multiprocessing.connection import Connection import os from pathlib import Path +import re import shutil import sys import subprocess import time -from typing import List, Optional +from typing import List, Optional, Tuple import unittest from simpleperf_utils import remove, get_script_dir, AdbHelper, is_windows, bytes_to_str @@ -108,6 +109,13 @@ class TestHelper: return cls.adb.get_property('ro.product.cpu.abilist32').strip().split(',')[0] @classmethod + def get_kernel_version(cls) -> Tuple[int]: + output = cls.adb.check_run_and_return_output(['shell', 'uname', '-r']) + m = re.search(r'^(\d+)\.(\d+)', output) + assert m + return (int(m.group(1)), int(m.group(2))) + + @classmethod def write_progress(cls, progress: str): if cls.progress_conn: cls.progress_conn.send(progress) |