summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2021-10-19 15:21:58 -0700
committerYabin Cui <yabinc@google.com>2021-10-20 09:48:28 -0700
commitcf9ee002871e8e7db9e283c6c564d069838bc56b (patch)
tree41e018ad0d5e15f2b5b54cbbb974e78d4192b6ef
parent423b78acfa1755605275f0e8d21092acceb44635 (diff)
downloadextras-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.py92
-rw-r--r--simpleperf/scripts/test/app_test.py15
-rw-r--r--simpleperf/scripts/test/cpp_app_test.py76
-rw-r--r--simpleperf/scripts/test/test_utils.py10
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)