• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# Copyright (C) 2017 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17"""test.py: Tests for simpleperf python scripts.
18
19These are smoke tests Using examples to run python scripts.
20For each example, we go through the steps of running each python script.
21Examples are collected from simpleperf/demo, which includes:
22  SimpleperfExamplePureJava
23  SimpleperfExampleWithNative
24  SimpleperfExampleOfKotlin
25
26Tested python scripts include:
27  app_profiler.py
28  report.py
29  annotate.py
30  report_sample.py
31  pprof_proto_generator.py
32  report_html.py
33
34Test using both `adb root` and `adb unroot`.
35
36"""
37
38import os
39import re
40import shutil
41import signal
42import sys
43import tempfile
44import time
45import unittest
46
47from simpleperf_report_lib import ReportLib
48from utils import *
49
50has_google_protobuf = True
51try:
52    import google.protobuf
53except:
54    has_google_protobuf = False
55
56inferno_script = os.path.join(get_script_dir(), "inferno.bat" if is_windows() else "./inferno.sh")
57
58support_trace_offcpu = None
59
60def is_trace_offcpu_supported():
61    global support_trace_offcpu
62    if support_trace_offcpu is None:
63        adb = AdbHelper()
64        adb.check_run_and_return_output(['push',
65                                         'bin/android/%s/simpleperf' % adb.get_device_arch(),
66                                         "/data/local/tmp"])
67        adb.check_run_and_return_output(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf'])
68        output = adb.check_run_and_return_output(['shell', '/data/local/tmp/simpleperf', 'list',
69                                                  '--show-features'])
70        support_trace_offcpu = 'trace-offcpu' in output
71    return support_trace_offcpu
72
73def build_testdata():
74    """ Collect testdata from ../testdata and ../demo. """
75    from_testdata_path = os.path.join('..', 'testdata')
76    from_demo_path = os.path.join('..', 'demo')
77    from_script_testdata_path = 'script_testdata'
78    if (not os.path.isdir(from_testdata_path) or not os.path.isdir(from_demo_path) or
79        not from_script_testdata_path):
80        return
81    copy_testdata_list = ['perf_with_symbols.data', 'perf_with_trace_offcpu.data',
82                          'perf_with_tracepoint_event.data']
83    copy_demo_list = ['SimpleperfExamplePureJava', 'SimpleperfExampleWithNative',
84                      'SimpleperfExampleOfKotlin']
85
86    testdata_path = "testdata"
87    remove(testdata_path)
88    os.mkdir(testdata_path)
89    for testdata in copy_testdata_list:
90        shutil.copy(os.path.join(from_testdata_path, testdata), testdata_path)
91    for demo in copy_demo_list:
92        shutil.copytree(os.path.join(from_demo_path, demo), os.path.join(testdata_path, demo))
93    for f in os.listdir(from_script_testdata_path):
94        shutil.copy(os.path.join(from_script_testdata_path, f), testdata_path)
95
96class TestBase(unittest.TestCase):
97    def run_cmd(self, args, return_output=False):
98        if args[0].endswith('.py'):
99            args = [sys.executable] + args
100        use_shell = args[0].endswith('.bat')
101        try:
102            if not return_output:
103                returncode = subprocess.call(args, shell=use_shell)
104            else:
105                subproc = subprocess.Popen(args, stdout=subprocess.PIPE, shell=use_shell)
106                (output_data, _) = subproc.communicate()
107                returncode = subproc.returncode
108        except:
109            returncode = None
110        self.assertEqual(returncode, 0, msg="failed to run cmd: %s" % args)
111        if return_output:
112            return output_data
113
114
115class TestExampleBase(TestBase):
116    @classmethod
117    def prepare(cls, example_name, package_name, activity_name, abi=None, adb_root=False):
118        cls.adb = AdbHelper(enable_switch_to_root=adb_root)
119        cls.example_path = os.path.join("testdata", example_name)
120        if not os.path.isdir(cls.example_path):
121            log_fatal("can't find " + cls.example_path)
122        for root, _, files in os.walk(cls.example_path):
123            if 'app-profiling.apk' in files:
124                cls.apk_path = os.path.join(root, 'app-profiling.apk')
125                break
126        if not hasattr(cls, 'apk_path'):
127            log_fatal("can't find app-profiling.apk under " + cls.example_path)
128        cls.package_name = package_name
129        cls.activity_name = activity_name
130        cls.abi = "arm64"
131        if abi and abi != "arm64" and abi.find("arm") != -1:
132            cls.abi = "arm"
133        args = ["install", "-r"]
134        if abi:
135            args += ["--abi", abi]
136        args.append(cls.apk_path)
137        cls.adb.check_run(args)
138        cls.adb_root = adb_root
139        cls.compiled = False
140
141    def setUp(self):
142        if self.id().find('TraceOffCpu') != -1 and not is_trace_offcpu_supported():
143            self.skipTest('trace-offcpu is not supported on device')
144
145    @classmethod
146    def tearDownClass(cls):
147        if hasattr(cls, 'test_result') and cls.test_result and not cls.test_result.wasSuccessful():
148            return
149        if hasattr(cls, 'package_name'):
150            cls.adb.check_run(["uninstall", cls.package_name])
151        remove("binary_cache")
152        remove("annotated_files")
153        remove("perf.data")
154        remove("report.txt")
155        remove("pprof.profile")
156
157    def run(self, result=None):
158        self.__class__.test_result = result
159        super(TestBase, self).run(result)
160
161    def run_app_profiler(self, record_arg = "-g -f 1000 --duration 3 -e cpu-cycles:u",
162                         build_binary_cache=True, skip_compile=False, start_activity=True,
163                         native_lib_dir=None, profile_from_launch=False, add_arch=False):
164        args = ["app_profiler.py", "--app", self.package_name, "--apk", self.apk_path,
165                "-r", record_arg, "-o", "perf.data"]
166        if not build_binary_cache:
167            args.append("-nb")
168        if skip_compile or self.__class__.compiled:
169            args.append("-nc")
170        if start_activity:
171            args += ["-a", self.activity_name]
172        if native_lib_dir:
173            args += ["-lib", native_lib_dir]
174        if profile_from_launch:
175            args.append("--profile_from_launch")
176        if add_arch:
177            args += ["--arch", self.abi]
178        if not self.adb_root:
179            args.append("--disable_adb_root")
180        self.run_cmd(args)
181        self.check_exist(file="perf.data")
182        if build_binary_cache:
183            self.check_exist(dir="binary_cache")
184        if not skip_compile:
185            self.__class__.compiled = True
186
187    def check_exist(self, file=None, dir=None):
188        if file:
189            self.assertTrue(os.path.isfile(file), file)
190        if dir:
191            self.assertTrue(os.path.isdir(dir), dir)
192
193    def check_file_under_dir(self, dir, file):
194        self.check_exist(dir=dir)
195        for _, _, files in os.walk(dir):
196            for f in files:
197                if f == file:
198                    return
199        self.fail("Failed to call check_file_under_dir(dir=%s, file=%s)" % (dir, file))
200
201
202    def check_strings_in_file(self, file, strings):
203        self.check_exist(file=file)
204        with open(file, 'r') as fh:
205            self.check_strings_in_content(fh.read(), strings)
206
207    def check_strings_in_content(self, content, strings):
208        for s in strings:
209            self.assertNotEqual(content.find(s), -1, "s: %s, content: %s" % (s, content))
210
211    def check_annotation_summary(self, summary_file, check_entries):
212        """ check_entries is a list of (name, accumulated_period, period).
213            This function checks for each entry, if the line containing [name]
214            has at least required accumulated_period and period.
215        """
216        self.check_exist(file=summary_file)
217        with open(summary_file, 'r') as fh:
218            summary = fh.read()
219        fulfilled = [False for x in check_entries]
220        if not hasattr(self, "summary_check_re"):
221            self.summary_check_re = re.compile(r'accumulated_period:\s*([\d.]+)%.*period:\s*([\d.]+)%')
222        for line in summary.split('\n'):
223            for i in range(len(check_entries)):
224                (name, need_acc_period, need_period) = check_entries[i]
225                if not fulfilled[i] and name in line:
226                    m = self.summary_check_re.search(line)
227                    if m:
228                        acc_period = float(m.group(1))
229                        period = float(m.group(2))
230                        if acc_period >= need_acc_period and period >= need_period:
231                            fulfilled[i] = True
232        self.assertEqual(len(fulfilled), sum([int(x) for x in fulfilled]), fulfilled)
233
234    def check_inferno_report_html(self, check_entries, file="report.html"):
235        self.check_exist(file=file)
236        with open(file, 'r') as fh:
237            data = fh.read()
238        fulfilled = [False for _ in check_entries]
239        for line in data.split('\n'):
240            # each entry is a (function_name, min_percentage) pair.
241            for i, entry in enumerate(check_entries):
242                if fulfilled[i] or line.find(entry[0]) == -1:
243                    continue
244                m = re.search(r'(\d+\.\d+)%', line)
245                if m and float(m.group(1)) >= entry[1]:
246                    fulfilled[i] = True
247                    break
248        self.assertEqual(fulfilled, [True for x in check_entries])
249
250    def common_test_app_profiler(self):
251        self.run_cmd(["app_profiler.py", "-h"])
252        remove("binary_cache")
253        self.run_app_profiler(build_binary_cache=False)
254        self.assertFalse(os.path.isdir("binary_cache"))
255        args = ["binary_cache_builder.py"]
256        if not self.adb_root:
257            args.append("--disable_adb_root")
258        self.run_cmd(args)
259        self.check_exist(dir="binary_cache")
260        remove("binary_cache")
261        self.run_app_profiler(build_binary_cache=True)
262        self.run_app_profiler(skip_compile=True)
263        self.run_app_profiler(start_activity=False)
264
265    def common_test_report(self):
266        self.run_cmd(["report.py", "-h"])
267        self.run_app_profiler(build_binary_cache=False)
268        self.run_cmd(["report.py"])
269        self.run_cmd(["report.py", "-i", "perf.data"])
270        self.run_cmd(["report.py", "-g"])
271        self.run_cmd(["report.py", "--self-kill-for-testing",  "-g", "--gui"])
272
273    def common_test_annotate(self):
274        self.run_cmd(["annotate.py", "-h"])
275        self.run_app_profiler()
276        remove("annotated_files")
277        self.run_cmd(["annotate.py", "-s", self.example_path])
278        self.check_exist(dir="annotated_files")
279
280    def common_test_report_sample(self, check_strings):
281        self.run_cmd(["report_sample.py", "-h"])
282        remove("binary_cache")
283        self.run_app_profiler(build_binary_cache=False)
284        self.run_cmd(["report_sample.py"])
285        output = self.run_cmd(["report_sample.py", "perf.data"], return_output=True)
286        self.check_strings_in_content(output, check_strings)
287        self.run_app_profiler(record_arg="-g -f 1000 --duration 3 -e cpu-cycles:u --no-dump-symbols")
288        output = self.run_cmd(["report_sample.py", "--symfs", "binary_cache"], return_output=True)
289        self.check_strings_in_content(output, check_strings)
290
291    def common_test_pprof_proto_generator(self, check_strings_with_lines,
292                                          check_strings_without_lines):
293        if not has_google_protobuf:
294            log_info('Skip test for pprof_proto_generator because google.protobuf is missing')
295            return
296        self.run_cmd(["pprof_proto_generator.py", "-h"])
297        self.run_app_profiler()
298        self.run_cmd(["pprof_proto_generator.py"])
299        remove("pprof.profile")
300        self.run_cmd(["pprof_proto_generator.py", "-i", "perf.data", "-o", "pprof.profile"])
301        self.check_exist(file="pprof.profile")
302        self.run_cmd(["pprof_proto_generator.py", "--show"])
303        output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"],
304                              return_output=True)
305        self.check_strings_in_content(output, check_strings_with_lines +
306                                              ["has_line_numbers: True"])
307        remove("binary_cache")
308        self.run_cmd(["pprof_proto_generator.py"])
309        output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"],
310                              return_output=True)
311        self.check_strings_in_content(output, check_strings_without_lines +
312                                              ["has_line_numbers: False"])
313
314    def common_test_inferno(self):
315        self.run_cmd([inferno_script, "-h"])
316        remove("perf.data")
317        append_args = [] if self.adb_root else ["--disable_adb_root"]
318        self.run_cmd([inferno_script, "-p", self.package_name, "-t", "3"] + append_args)
319        self.check_exist(file="perf.data")
320        self.run_cmd([inferno_script, "-p", self.package_name, "-f", "1000", "-du", "-t", "1",
321                      "-nc"] + append_args)
322        self.run_cmd([inferno_script, "-p", self.package_name, "-e", "100000 cpu-cycles",
323                      "-t", "1", "-nc"] + append_args)
324        self.run_cmd([inferno_script, "-sc"])
325
326    def common_test_report_html(self):
327        self.run_cmd(['report_html.py', '-h'])
328        self.run_app_profiler(record_arg='-g -f 1000 --duration 3 -e task-clock:u')
329        self.run_cmd(['report_html.py'])
330        self.run_cmd(['report_html.py', '--add_source_code', '--source_dirs', 'testdata'])
331        self.run_cmd(['report_html.py', '--add_disassembly'])
332        # Test with multiple perf.data.
333        shutil.move('perf.data', 'perf2.data')
334        self.run_app_profiler()
335        self.run_cmd(['report_html.py', '-i', 'perf.data', 'perf2.data'])
336        remove('perf2.data')
337
338
339class TestExamplePureJava(TestExampleBase):
340    @classmethod
341    def setUpClass(cls):
342        cls.prepare("SimpleperfExamplePureJava",
343                    "com.example.simpleperf.simpleperfexamplepurejava",
344                    ".MainActivity")
345
346    def test_app_profiler(self):
347        self.common_test_app_profiler()
348
349    def test_app_profiler_profile_from_launch(self):
350        self.run_app_profiler(profile_from_launch=True, add_arch=True, build_binary_cache=False)
351        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
352        self.check_strings_in_file("report.txt",
353            ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()",
354             "__start_thread"])
355
356    def test_app_profiler_multiprocesses(self):
357        self.adb.check_run(['shell', 'am', 'force-stop', self.package_name])
358        self.adb.check_run(['shell', 'am', 'start', '-n',
359                            self.package_name + '/.MultiProcessActivity'])
360        # Wait until both MultiProcessActivity and MultiProcessService set up.
361        time.sleep(3)
362        self.run_app_profiler(skip_compile=True, start_activity=False)
363        self.run_cmd(["report.py", "-o", "report.txt"])
364        self.check_strings_in_file("report.txt", ["BusyService", "BusyThread"])
365
366    def test_app_profiler_with_ctrl_c(self):
367        if is_windows():
368            return
369        self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity'])
370        time.sleep(1)
371        args = [sys.executable, "app_profiler.py", "--app", self.package_name,
372                "-r", "--duration 10000", "-nc", "--disable_adb_root"]
373        subproc = subprocess.Popen(args)
374        time.sleep(3)
375
376        subproc.send_signal(signal.SIGINT)
377        subproc.wait()
378        self.assertEqual(subproc.returncode, 0)
379        self.run_cmd(["report.py"])
380
381    def test_app_profiler_stop_after_app_exit(self):
382        self.adb.check_run(['shell', 'am', 'start', '-n', self.package_name + '/.MainActivity'])
383        time.sleep(1)
384        subproc = subprocess.Popen([sys.executable, 'app_profiler.py', '--app', self.package_name,
385                                    '-r', '--duration 10000', '-nc', '--disable_adb_root'])
386        time.sleep(3)
387        self.adb.check_run(['shell', 'am', 'force-stop', self.package_name])
388        subproc.wait()
389        self.assertEqual(subproc.returncode, 0)
390        self.run_cmd(["report.py"])
391
392    def test_report(self):
393        self.common_test_report()
394        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
395        self.check_strings_in_file("report.txt",
396            ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()",
397             "__start_thread"])
398
399    def test_annotate(self):
400        self.common_test_annotate()
401        self.check_file_under_dir("annotated_files", "MainActivity.java")
402        summary_file = os.path.join("annotated_files", "summary")
403        self.check_annotation_summary(summary_file,
404            [("MainActivity.java", 80, 80),
405             ("run", 80, 0),
406             ("callFunction", 0, 0),
407             ("line 23", 80, 0)])
408
409    def test_report_sample(self):
410        self.common_test_report_sample(
411            ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()",
412             "__start_thread"])
413
414    def test_pprof_proto_generator(self):
415        self.common_test_pprof_proto_generator(
416            check_strings_with_lines=
417                ["com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java",
418                 "run"],
419            check_strings_without_lines=
420                ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()"])
421
422    def test_inferno(self):
423        self.common_test_inferno()
424        self.run_app_profiler()
425        self.run_cmd([inferno_script, "-sc"])
426        self.check_inferno_report_html(
427            [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()', 80)])
428        self.run_cmd([inferno_script, "-sc", "-o", "report2.html"])
429        self.check_inferno_report_html(
430            [('com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()', 80)],
431            "report2.html")
432        remove("report2.html")
433
434    def test_inferno_in_another_dir(self):
435        test_dir = 'inferno_testdir'
436        saved_dir = os.getcwd()
437        remove(test_dir)
438        os.mkdir(test_dir)
439        os.chdir(test_dir)
440        self.run_cmd(['python', os.path.join(saved_dir, 'app_profiler.py'),
441                      '--app', self.package_name, '-r', '-e task-clock:u -g --duration 3'])
442        self.check_exist(file="perf.data")
443        self.run_cmd([inferno_script, "-sc"])
444        os.chdir(saved_dir)
445        remove(test_dir)
446
447    def test_report_html(self):
448        self.common_test_report_html()
449
450
451class TestExamplePureJavaRoot(TestExampleBase):
452    @classmethod
453    def setUpClass(cls):
454        cls.prepare("SimpleperfExamplePureJava",
455                    "com.example.simpleperf.simpleperfexamplepurejava",
456                    ".MainActivity",
457                    adb_root=True)
458
459    def test_app_profiler(self):
460        self.common_test_app_profiler()
461
462
463class TestExamplePureJavaTraceOffCpu(TestExampleBase):
464    @classmethod
465    def setUpClass(cls):
466        cls.prepare("SimpleperfExamplePureJava",
467                    "com.example.simpleperf.simpleperfexamplepurejava",
468                    ".SleepActivity")
469
470    def test_smoke(self):
471        self.run_app_profiler(record_arg="-g -f 1000 --duration 3 -e cpu-cycles:u --trace-offcpu")
472        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
473        self.check_strings_in_file("report.txt",
474            ["com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run()",
475             "long com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction()",
476             "long com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction(long)"
477             ])
478        remove("annotated_files")
479        self.run_cmd(["annotate.py", "-s", self.example_path])
480        self.check_exist(dir="annotated_files")
481        self.check_file_under_dir("annotated_files", "SleepActivity.java")
482        summary_file = os.path.join("annotated_files", "summary")
483        self.check_annotation_summary(summary_file,
484            [("SleepActivity.java", 80, 20),
485             ("run", 80, 0),
486             ("RunFunction", 20, 20),
487             ("SleepFunction", 20, 0),
488             ("line 24", 20, 0),
489             ("line 32", 20, 0)])
490        self.run_cmd([inferno_script, "-sc"])
491        self.check_inferno_report_html(
492            [('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.run() ', 80),
493             ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction()',
494              20),
495             ('com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction(long)',
496              20)])
497
498
499class TestExampleWithNative(TestExampleBase):
500    @classmethod
501    def setUpClass(cls):
502        cls.prepare("SimpleperfExampleWithNative",
503                    "com.example.simpleperf.simpleperfexamplewithnative",
504                    ".MainActivity")
505
506    def test_app_profiler(self):
507        self.common_test_app_profiler()
508        remove("binary_cache")
509        self.run_app_profiler(native_lib_dir=self.example_path)
510
511    def test_app_profiler_profile_from_launch(self):
512        self.run_app_profiler(profile_from_launch=True, add_arch=True, build_binary_cache=False)
513        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
514        self.check_strings_in_file("report.txt",
515            ["BusyLoopThread",
516             "__start_thread"])
517
518    def test_report(self):
519        self.common_test_report()
520        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
521        self.check_strings_in_file("report.txt",
522            ["BusyLoopThread",
523             "__start_thread"])
524
525    def test_annotate(self):
526        self.common_test_annotate()
527        self.check_file_under_dir("annotated_files", "native-lib.cpp")
528        summary_file = os.path.join("annotated_files", "summary")
529        self.check_annotation_summary(summary_file,
530            [("native-lib.cpp", 20, 0),
531             ("BusyLoopThread", 20, 0),
532             ("line 46", 20, 0)])
533
534    def test_report_sample(self):
535        self.common_test_report_sample(
536            ["BusyLoopThread",
537             "__start_thread"])
538
539    def test_pprof_proto_generator(self):
540        self.common_test_pprof_proto_generator(
541            check_strings_with_lines=
542                ["native-lib.cpp",
543                 "BusyLoopThread"],
544            check_strings_without_lines=
545                ["BusyLoopThread"])
546
547    def test_inferno(self):
548        self.common_test_inferno()
549        self.run_app_profiler()
550        self.run_cmd([inferno_script, "-sc"])
551        self.check_inferno_report_html([('BusyLoopThread', 20)])
552
553    def test_report_html(self):
554        self.common_test_report_html()
555
556
557class TestExampleWithNativeRoot(TestExampleBase):
558    @classmethod
559    def setUpClass(cls):
560        cls.prepare("SimpleperfExampleWithNative",
561                    "com.example.simpleperf.simpleperfexamplewithnative",
562                    ".MainActivity",
563                    adb_root=True)
564
565    def test_app_profiler(self):
566        self.common_test_app_profiler()
567        remove("binary_cache")
568        self.run_app_profiler(native_lib_dir=self.example_path)
569
570
571class TestExampleWithNativeTraceOffCpu(TestExampleBase):
572    @classmethod
573    def setUpClass(cls):
574        cls.prepare("SimpleperfExampleWithNative",
575                    "com.example.simpleperf.simpleperfexamplewithnative",
576                    ".SleepActivity")
577
578    def test_smoke(self):
579        self.run_app_profiler(record_arg="-g -f 1000 --duration 3 -e cpu-cycles:u --trace-offcpu")
580        self.run_cmd(["report.py", "-g", "--comms", "SleepThread", "-o", "report.txt"])
581        self.check_strings_in_file("report.txt",
582            ["SleepThread(void*)",
583             "RunFunction()",
584             "SleepFunction(unsigned long long)"])
585        remove("annotated_files")
586        self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "SleepThread"])
587        self.check_exist(dir="annotated_files")
588        self.check_file_under_dir("annotated_files", "native-lib.cpp")
589        summary_file = os.path.join("annotated_files", "summary")
590        self.check_annotation_summary(summary_file,
591            [("native-lib.cpp", 80, 20),
592             ("SleepThread", 80, 0),
593             ("RunFunction", 20, 20),
594             ("SleepFunction", 20, 0),
595             ("line 73", 20, 0),
596             ("line 83", 20, 0)])
597        self.run_cmd([inferno_script, "-sc"])
598        self.check_inferno_report_html([('SleepThread', 80),
599                                        ('RunFunction', 20),
600                                        ('SleepFunction', 20)])
601
602
603class TestExampleWithNativeJniCall(TestExampleBase):
604    @classmethod
605    def setUpClass(cls):
606        cls.prepare("SimpleperfExampleWithNative",
607                    "com.example.simpleperf.simpleperfexamplewithnative",
608                    ".MixActivity")
609
610    def test_smoke(self):
611        self.run_app_profiler()
612        self.run_cmd(["report.py", "-g", "--comms", "BusyThread", "-o", "report.txt"])
613        self.check_strings_in_file("report.txt",
614            ["void com.example.simpleperf.simpleperfexamplewithnative.MixActivity$1.run()",
615             "int com.example.simpleperf.simpleperfexamplewithnative.MixActivity.callFunction(int)",
616             "Java_com_example_simpleperf_simpleperfexamplewithnative_MixActivity_callFunction"])
617        remove("annotated_files")
618        self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "BusyThread"])
619        self.check_exist(dir="annotated_files")
620        self.check_file_under_dir("annotated_files", "native-lib.cpp")
621        self.check_file_under_dir("annotated_files", "MixActivity.java")
622        summary_file = os.path.join("annotated_files", "summary")
623        self.check_annotation_summary(summary_file,
624            [("MixActivity.java", 80, 0),
625             ("run", 80, 0),
626             ("line 26", 20, 0),
627             ("native-lib.cpp", 5, 0),
628             ("line 40", 5, 0)])
629        self.run_cmd([inferno_script, "-sc"])
630
631
632class TestExampleWithNativeForceArm(TestExampleWithNative):
633    @classmethod
634    def setUpClass(cls):
635        cls.prepare("SimpleperfExampleWithNative",
636                    "com.example.simpleperf.simpleperfexamplewithnative",
637                    ".MainActivity",
638                    abi="armeabi-v7a")
639
640
641class TestExampleWithNativeForceArmRoot(TestExampleWithNativeRoot):
642    @classmethod
643    def setUpClass(cls):
644        cls.prepare("SimpleperfExampleWithNative",
645                    "com.example.simpleperf.simpleperfexamplewithnative",
646                    ".MainActivity",
647                    abi="armeabi-v7a",
648                    adb_root=False)
649
650
651class TestExampleWithNativeTraceOffCpuForceArm(TestExampleWithNativeTraceOffCpu):
652    @classmethod
653    def setUpClass(cls):
654        cls.prepare("SimpleperfExampleWithNative",
655                    "com.example.simpleperf.simpleperfexamplewithnative",
656                    ".SleepActivity",
657                    abi="armeabi-v7a")
658
659
660class TestExampleOfKotlin(TestExampleBase):
661    @classmethod
662    def setUpClass(cls):
663        cls.prepare("SimpleperfExampleOfKotlin",
664                    "com.example.simpleperf.simpleperfexampleofkotlin",
665                    ".MainActivity")
666
667    def test_app_profiler(self):
668        self.common_test_app_profiler()
669
670    def test_app_profiler_profile_from_launch(self):
671        self.run_app_profiler(profile_from_launch=True, add_arch=True, build_binary_cache=False)
672        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
673        self.check_strings_in_file("report.txt",
674            ["com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()",
675             "__start_thread"])
676
677    def test_report(self):
678        self.common_test_report()
679        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
680        self.check_strings_in_file("report.txt",
681            ["com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()",
682             "__start_thread"])
683
684    def test_annotate(self):
685        self.common_test_annotate()
686        self.check_file_under_dir("annotated_files", "MainActivity.kt")
687        summary_file = os.path.join("annotated_files", "summary")
688        self.check_annotation_summary(summary_file,
689            [("MainActivity.kt", 80, 80),
690             ("run", 80, 0),
691             ("callFunction", 0, 0),
692             ("line 19", 80, 0),
693             ("line 25", 0, 0)])
694
695    def test_report_sample(self):
696        self.common_test_report_sample(
697            ["com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()",
698             "__start_thread"])
699
700    def test_pprof_proto_generator(self):
701        self.common_test_pprof_proto_generator(
702            check_strings_with_lines=
703                ["com/example/simpleperf/simpleperfexampleofkotlin/MainActivity.kt",
704                 "run"],
705            check_strings_without_lines=
706                ["com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()"])
707
708    def test_inferno(self):
709        self.common_test_inferno()
710        self.run_app_profiler()
711        self.run_cmd([inferno_script, "-sc"])
712        self.check_inferno_report_html(
713            [('com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()',
714              80)])
715
716    def test_report_html(self):
717        self.common_test_report_html()
718
719
720class TestExampleOfKotlinRoot(TestExampleBase):
721    @classmethod
722    def setUpClass(cls):
723        cls.prepare("SimpleperfExampleOfKotlin",
724                    "com.example.simpleperf.simpleperfexampleofkotlin",
725                    ".MainActivity",
726                    adb_root=True)
727
728    def test_app_profiler(self):
729        self.common_test_app_profiler()
730
731
732class TestExampleOfKotlinTraceOffCpu(TestExampleBase):
733    @classmethod
734    def setUpClass(cls):
735        cls.prepare("SimpleperfExampleOfKotlin",
736                    "com.example.simpleperf.simpleperfexampleofkotlin",
737                    ".SleepActivity")
738
739    def test_smoke(self):
740        self.run_app_profiler(record_arg="-g -f 1000 --duration 3 -e cpu-cycles:u --trace-offcpu")
741        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
742        self.check_strings_in_file("report.txt",
743            ["void com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.run()",
744             "long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.RunFunction()",
745             "long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.SleepFunction(long)"
746             ])
747        remove("annotated_files")
748        self.run_cmd(["annotate.py", "-s", self.example_path])
749        self.check_exist(dir="annotated_files")
750        self.check_file_under_dir("annotated_files", "SleepActivity.kt")
751        summary_file = os.path.join("annotated_files", "summary")
752        self.check_annotation_summary(summary_file,
753            [("SleepActivity.kt", 80, 20),
754             ("run", 80, 0),
755             ("RunFunction", 20, 20),
756             ("SleepFunction", 20, 0),
757             ("line 24", 20, 0),
758             ("line 32", 20, 0)])
759        self.run_cmd([inferno_script, "-sc"])
760        self.check_inferno_report_html(
761            [('void com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.run()',
762              80),
763             ('long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.RunFunction()',
764              20),
765             ('long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.SleepFunction(long)',
766              20)])
767
768
769class TestProfilingNativeProgram(TestExampleBase):
770    def test_smoke(self):
771        if not AdbHelper().switch_to_root():
772            log_info('skip TestProfilingNativeProgram on non-rooted devices.')
773            return
774        remove("perf.data")
775        self.run_cmd(["app_profiler.py", "-np", "surfaceflinger",
776                      "-r", "-g -f 1000 --duration 3 -e cpu-cycles:u"])
777        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
778
779
780class TestProfilingCmd(TestExampleBase):
781    def test_smoke(self):
782        remove("perf.data")
783        self.run_cmd(["app_profiler.py", "-cmd", "pm -l", "--disable_adb_root"])
784        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
785
786    def test_set_arch(self):
787        arch = AdbHelper().get_device_arch()
788        remove("perf.data")
789        self.run_cmd(["app_profiler.py", "-cmd", "pm -l", "--arch", arch])
790        self.run_cmd(["report.py", "-g", "-o", "report.txt"])
791
792
793class TestProfilingNativeProgram(TestExampleBase):
794    def test_smoke(self):
795        adb = AdbHelper()
796        if adb.switch_to_root():
797            self.run_cmd(["app_profiler.py", "-np", "surfaceflinger"])
798            self.run_cmd(["report.py", "-g", "-o", "report.txt"])
799            self.run_cmd([inferno_script, "-sc"])
800            self.run_cmd([inferno_script, "-np", "surfaceflinger"])
801
802
803class TestReportLib(unittest.TestCase):
804    def setUp(self):
805        self.report_lib = ReportLib()
806        self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_symbols.data'))
807
808    def tearDown(self):
809        self.report_lib.Close()
810
811    def test_build_id(self):
812        build_id = self.report_lib.GetBuildIdForPath('/data/t2')
813        self.assertEqual(build_id, '0x70f1fe24500fc8b0d9eb477199ca1ca21acca4de')
814
815    def test_symbol(self):
816        found_func2 = False
817        while self.report_lib.GetNextSample():
818            sample = self.report_lib.GetCurrentSample()
819            symbol = self.report_lib.GetSymbolOfCurrentSample()
820            if symbol.symbol_name == 'func2(int, int)':
821                found_func2 = True
822                self.assertEqual(symbol.symbol_addr, 0x4004ed)
823                self.assertEqual(symbol.symbol_len, 0x14)
824        self.assertTrue(found_func2)
825
826    def test_sample(self):
827        found_sample = False
828        while self.report_lib.GetNextSample():
829            sample = self.report_lib.GetCurrentSample()
830            if sample.ip == 0x4004ff and sample.time == 7637889424953:
831                found_sample = True
832                self.assertEqual(sample.pid, 15926)
833                self.assertEqual(sample.tid, 15926)
834                self.assertEqual(sample.thread_comm, 't2')
835                self.assertEqual(sample.cpu, 5)
836                self.assertEqual(sample.period, 694614)
837                event = self.report_lib.GetEventOfCurrentSample()
838                self.assertEqual(event.name, 'cpu-cycles')
839                callchain = self.report_lib.GetCallChainOfCurrentSample()
840                self.assertEqual(callchain.nr, 0)
841        self.assertTrue(found_sample)
842
843    def test_meta_info(self):
844        self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data'))
845        meta_info = self.report_lib.MetaInfo()
846        self.assertTrue("simpleperf_version" in meta_info)
847        self.assertEqual(meta_info["system_wide_collection"], "false")
848        self.assertEqual(meta_info["trace_offcpu"], "true")
849        self.assertEqual(meta_info["event_type_info"], "cpu-cycles,0,0\nsched:sched_switch,2,47")
850        self.assertTrue("product_props" in meta_info)
851
852    def test_event_name_from_meta_info(self):
853        self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_tracepoint_event.data'))
854        event_names = set()
855        while self.report_lib.GetNextSample():
856            event_names.add(self.report_lib.GetEventOfCurrentSample().name)
857        self.assertTrue('sched:sched_switch' in event_names)
858        self.assertTrue('cpu-cycles' in event_names)
859
860    def test_record_cmd(self):
861        self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data'))
862        self.assertEqual(self.report_lib.GetRecordCmd(),
863                         "/data/local/tmp/simpleperf record --trace-offcpu --duration 2 -g ./simpleperf_runtest_run_and_sleep64")
864
865    def test_offcpu(self):
866        self.report_lib.SetRecordFile(os.path.join('testdata', 'perf_with_trace_offcpu.data'))
867        total_period = 0
868        sleep_function_period = 0
869        sleep_function_name = "SleepFunction(unsigned long long)"
870        while self.report_lib.GetNextSample():
871            sample = self.report_lib.GetCurrentSample()
872            total_period += sample.period
873            if self.report_lib.GetSymbolOfCurrentSample().symbol_name == sleep_function_name:
874                sleep_function_period += sample.period
875                continue
876            callchain = self.report_lib.GetCallChainOfCurrentSample()
877            for i in range(callchain.nr):
878                if callchain.entries[i].symbol.symbol_name == sleep_function_name:
879                    sleep_function_period += sample.period
880                    break
881            self.assertEqual(self.report_lib.GetEventOfCurrentSample().name, 'cpu-cycles')
882        sleep_percentage = float(sleep_function_period) / total_period
883        self.assertGreater(sleep_percentage, 0.30)
884
885
886class TestRunSimpleperfOnDevice(TestBase):
887    def test_smoke(self):
888        self.run_cmd(['run_simpleperf_on_device.py', 'list', '--show-features'])
889
890
891class TestTools(unittest.TestCase):
892    def test_addr2nearestline(self):
893        binary_cache_path = 'testdata'
894        test_map = {
895            '/simpleperf_runtest_two_functions_arm64': [
896                {
897                    'func_addr': 0x668,
898                    'addr': 0x668,
899                    'source': 'system/extras/simpleperf/runtest/two_functions.cpp:20',
900                },
901                {
902                    'func_addr': 0x668,
903                    'addr': 0x6a4,
904                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:7
905                                 system/extras/simpleperf/runtest/two_functions.cpp:22""",
906                },
907            ],
908            '/simpleperf_runtest_two_functions_arm': [
909                {
910                    'func_addr': 0x784,
911                    'addr': 0x7b0,
912                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:14
913                                 system/extras/simpleperf/runtest/two_functions.cpp:23""",
914                },
915                {
916                    'func_addr': 0x784,
917                    'addr': 0x7d0,
918                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:15
919                                 system/extras/simpleperf/runtest/two_functions.cpp:23""",
920                }
921            ],
922            '/simpleperf_runtest_two_functions_x86_64': [
923                {
924                    'func_addr': 0x840,
925                    'addr': 0x840,
926                    'source': 'system/extras/simpleperf/runtest/two_functions.cpp:7',
927                },
928                {
929                    'func_addr': 0x920,
930                    'addr': 0x94a,
931                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:7
932                                 system/extras/simpleperf/runtest/two_functions.cpp:22""",
933                }
934            ],
935            '/simpleperf_runtest_two_functions_x86': [
936                {
937                    'func_addr': 0x6d0,
938                    'addr': 0x6da,
939                    'source': 'system/extras/simpleperf/runtest/two_functions.cpp:14',
940                },
941                {
942                    'func_addr': 0x710,
943                    'addr': 0x749,
944                    'source': """system/extras/simpleperf/runtest/two_functions.cpp:8
945                                 system/extras/simpleperf/runtest/two_functions.cpp:22""",
946                }
947            ],
948        }
949        addr2line = Addr2Nearestline(None, binary_cache_path)
950        for dso_path in test_map:
951            test_addrs = test_map[dso_path]
952            for test_addr in test_addrs:
953                addr2line.add_addr(dso_path, test_addr['func_addr'], test_addr['addr'])
954        addr2line.convert_addrs_to_lines()
955        for dso_path in test_map:
956            dso = addr2line.get_dso(dso_path)
957            self.assertTrue(dso is not None)
958            test_addrs = test_map[dso_path]
959            for test_addr in test_addrs:
960                source_str = test_addr['source']
961                expected_source = []
962                for line in source_str.split('\n'):
963                    items = line.split(':')
964                    expected_source.append((items[0].strip(), int(items[1])))
965                actual_source = addr2line.get_addr_source(dso, test_addr['addr'])
966                self.assertTrue(actual_source is not None)
967                self.assertEqual(len(actual_source), len(expected_source))
968                for i in range(len(expected_source)):
969                    actual_file_path, actual_line = actual_source[i]
970                    self.assertEqual(actual_file_path, expected_source[i][0])
971                    self.assertEqual(actual_line, expected_source[i][1])
972
973    def test_objdump(self):
974        binary_cache_path = 'testdata'
975        test_map = {
976            '/simpleperf_runtest_two_functions_arm64': {
977                'start_addr': 0x668,
978                'len': 116,
979                'expected_items': [
980                    ('main():', 0),
981                    ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0),
982                    (' 694:	add	x20, x20, #0x6de', 0x694),
983                ],
984            },
985            '/simpleperf_runtest_two_functions_arm': {
986                'start_addr': 0x784,
987                'len': 80,
988                'expected_items': [
989                    ('main():', 0),
990                    ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0),
991                    ('     7ae:	bne.n	7a6 <main+0x22>', 0x7ae),
992                ],
993            },
994            '/simpleperf_runtest_two_functions_x86_64': {
995                'start_addr': 0x920,
996                'len': 201,
997                'expected_items': [
998                    ('main():', 0),
999                    ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0),
1000                    (' 96e:	mov    %edx,(%rbx,%rax,4)', 0x96e),
1001                ],
1002            },
1003            '/simpleperf_runtest_two_functions_x86': {
1004                'start_addr': 0x710,
1005                'len': 98,
1006                'expected_items': [
1007                    ('main():', 0),
1008                    ('system/extras/simpleperf/runtest/two_functions.cpp:20', 0),
1009                    (' 748:	cmp    $0x5f5e100,%ebp', 0x748),
1010                ],
1011            },
1012        }
1013        objdump = Objdump(None, binary_cache_path)
1014        for dso_path in test_map:
1015            dso_info = test_map[dso_path]
1016            disassemble_code = objdump.disassemble_code(dso_path, dso_info['start_addr'],
1017                                                        dso_info['len'])
1018            self.assertTrue(disassemble_code)
1019            for item in dso_info['expected_items']:
1020                self.assertTrue(item in disassemble_code)
1021
1022
1023def main():
1024    os.chdir(get_script_dir())
1025    build_testdata()
1026    if AdbHelper().get_android_version() < 7:
1027        log_info("Skip tests on Android version < N.")
1028        sys.exit(0)
1029    unittest.main(failfast=True)
1030
1031if __name__ == '__main__':
1032    main()
1033