diff options
author | Yabin Cui <yabinc@google.com> | 2021-11-02 16:04:12 -0700 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2021-11-02 16:09:13 -0700 |
commit | ed343476daa008d1e2e3ca85daf348bec893ffd9 (patch) | |
tree | 5fff40dae5c9f4a808bb115ad79904e9a1352621 | |
parent | ce9d6cf7f0ce92101b7c17766b15d5715ae766ed (diff) | |
download | extras-ed343476daa008d1e2e3ca85daf348bec893ffd9.tar.gz |
simpleperf: format summary of annotate.py.
1. Use Texttable to format output in summary file.
2. Use 'Total/Self' in summary file, which is similar to report command
output.
3. Add --raw-period option to show raw periods instead percentages.
Bug: 204455134
Test: run scripts/test/test.py
Change-Id: I147995abc74db7eea6c62f21a0920f5a355e7b9f
-rwxr-xr-x | simpleperf/scripts/annotate.py | 111 | ||||
-rw-r--r-- | simpleperf/scripts/test/annotate_test.py | 12 | ||||
-rw-r--r-- | simpleperf/scripts/test/app_test.py | 8 | ||||
-rw-r--r-- | simpleperf/scripts/test/cpp_app_test.py | 6 | ||||
-rw-r--r-- | simpleperf/scripts/test/java_app_test.py | 2 | ||||
-rw-r--r-- | simpleperf/scripts/test/kotlin_app_test.py | 2 | ||||
-rw-r--r-- | simpleperf/scripts/test/test_utils.py | 13 |
7 files changed, 100 insertions, 54 deletions
diff --git a/simpleperf/scripts/annotate.py b/simpleperf/scripts/annotate.py index 27d67a8f..92978a4a 100755 --- a/simpleperf/scripts/annotate.py +++ b/simpleperf/scripts/annotate.py @@ -22,6 +22,8 @@ import logging import os import os.path import shutil +from texttable import Texttable +from typing import Dict, Union from simpleperf_report_lib import ReportLib from simpleperf_utils import ( @@ -305,7 +307,7 @@ class SourceFileAnnotator(object): if is_sample_used: self.period += sample.period - def _add_dso_period(self, dso_name, period, used_dso_dict): + def _add_dso_period(self, dso_name: str, period: Period, used_dso_dict: Dict[str, bool]): if dso_name not in used_dso_dict: used_dso_dict[dso_name] = True dso_period = self.dso_periods.get(dso_name) @@ -337,44 +339,72 @@ class SourceFileAnnotator(object): summary = os.path.join(self.config['annotate_dest_dir'], 'summary') with open(summary, 'w') as f: f.write('total period: %d\n\n' % self.period) - dso_periods = sorted(self.dso_periods.values(), - key=lambda x: x.period.acc_period, reverse=True) - for dso_period in dso_periods: - f.write('dso %s: %s\n' % (dso_period.dso_name, - self._get_percentage_str(dso_period.period))) - f.write('\n') + self._write_dso_summary(f) + self._write_file_summary(f) file_periods = sorted(self.file_periods.values(), key=lambda x: x.period.acc_period, reverse=True) for file_period in file_periods: - f.write('file %s: %s\n' % (file_period.file, - self._get_percentage_str(file_period.period))) - for file_period in file_periods: - f.write('\n\n%s: %s\n' % (file_period.file, - self._get_percentage_str(file_period.period))) - values = [] - for func_name in file_period.function_dict.keys(): - func_start_line, period = file_period.function_dict[func_name] - values.append((func_name, func_start_line, period)) - values = sorted(values, key=lambda x: x[2].acc_period, reverse=True) - for value in values: - f.write('\tfunction (%s): line %d, %s\n' % ( - value[0], value[1], self._get_percentage_str(value[2]))) - f.write('\n') - for line in sorted(file_period.line_dict.keys()): - f.write('\tline %d: %s\n' % ( - line, self._get_percentage_str(file_period.line_dict[line]))) - - def _get_percentage_str(self, period, short=False): - s = 'acc_p: %f%%, p: %f%%' if short else 'accumulated_period: %f%%, period: %f%%' - return s % self._get_percentage(period) - - def _get_percentage(self, period): - if self.period == 0: - return (0, 0) - acc_p = 100.0 * period.acc_period / self.period - p = 100.0 * period.period / self.period - return (acc_p, p) + self._write_function_line_summary(f, file_period) + + def _write_dso_summary(self, summary_fh): + dso_periods = sorted(self.dso_periods.values(), + key=lambda x: x.period.acc_period, reverse=True) + table = Texttable(max_width=self.config['summary_width']) + table.set_cols_align(['l', 'l', 'l']) + table.add_row(['Total', 'Self', 'DSO']) + for dso_period in dso_periods: + total_str = self._get_period_str(dso_period.period.acc_period) + self_str = self._get_period_str(dso_period.period.period) + table.add_row([total_str, self_str, dso_period.dso_name]) + print(table.draw(), file=summary_fh) + print(file=summary_fh) + + def _write_file_summary(self, summary_fh): + file_periods = sorted(self.file_periods.values(), + key=lambda x: x.period.acc_period, reverse=True) + table = Texttable(max_width=self.config['summary_width']) + table.set_cols_align(['l', 'l', 'l']) + table.add_row(['Total', 'Self', 'Source File']) + for file_period in file_periods: + total_str = self._get_period_str(file_period.period.acc_period) + self_str = self._get_period_str(file_period.period.period) + table.add_row([total_str, self_str, file_period.file]) + print(table.draw(), file=summary_fh) + print(file=summary_fh) + + def _write_function_line_summary(self, summary_fh, file_period: FilePeriod): + table = Texttable(max_width=self.config['summary_width']) + table.set_cols_align(['l', 'l', 'l']) + table.add_row(['Total', 'Self', 'Function/Line in ' + file_period.file]) + values = [] + for func_name in file_period.function_dict.keys(): + func_start_line, period = file_period.function_dict[func_name] + values.append((func_name, func_start_line, period)) + values.sort(key=lambda x: x[2].acc_period, reverse=True) + for func_name, func_start_line, period in values: + total_str = self._get_period_str(period.acc_period) + self_str = self._get_period_str(period.period) + name = func_name + ' (line %d)' % func_start_line + table.add_row([total_str, self_str, name]) + for line in sorted(file_period.line_dict.keys()): + period = file_period.line_dict[line] + total_str = self._get_period_str(period.acc_period) + self_str = self._get_period_str(period.period) + name = 'line %d' % line + table.add_row([total_str, self_str, name]) + + print(table.draw(), file=summary_fh) + print(file=summary_fh) + + def _get_period_str(self, period: Union[Period, int]) -> str: + if isinstance(period, Period): + return 'Total %s, Self %s' % ( + self._get_period_str(period.acc_period), + self._get_period_str(period.period)) + if self.config['raw_period'] or self.period == 0: + return str(period) + return '%.2f%%' % (100.0 * period / self.period) def _annotate_files(self): """Annotate Source files: add acc_period/period for each source file. @@ -411,14 +441,14 @@ class SourceFileAnnotator(object): annotates = {} for line in file_period.line_dict.keys(): - annotates[line] = self._get_percentage_str(file_period.line_dict[line], True) + annotates[line] = self._get_period_str(file_period.line_dict[line]) for func_name in file_period.function_dict.keys(): func_start_line, period = file_period.function_dict[func_name] if func_start_line == -1: continue line = func_start_line - 1 if is_java else func_start_line - annotates[line] = '[func] ' + self._get_percentage_str(period, True) - annotates[1] = '[file] ' + self._get_percentage_str(file_period.period, True) + annotates[line] = '[func] ' + self._get_period_str(period) + annotates[1] = '[file] ' + self._get_period_str(file_period.period) max_annotate_cols = 0 for key in annotates: @@ -462,6 +492,9 @@ def main(): parser.add_argument('--dso', nargs='+', action='append', help=""" Use samples only in selected binaries.""") parser.add_argument('--ndk_path', type=extant_dir, help='Set the path of a ndk release.') + parser.add_argument('--raw-period', action='store_true', + help='show raw period instead of percentage') + parser.add_argument('--summary-width', type=int, default=80, help='max width of summary file') args = parser.parse_args() config = {} @@ -474,6 +507,8 @@ def main(): config['tid_filters'] = flatten_arg_list(args.tid) config['dso_filters'] = flatten_arg_list(args.dso) config['ndk_path'] = args.ndk_path + config['raw_period'] = args.raw_period + config['summary_width'] = args.summary_width annotator = SourceFileAnnotator(config) annotator.annotate() diff --git a/simpleperf/scripts/test/annotate_test.py b/simpleperf/scripts/test/annotate_test.py index ca216cbc..87e9b853 100644 --- a/simpleperf/scripts/test/annotate_test.py +++ b/simpleperf/scripts/test/annotate_test.py @@ -15,6 +15,7 @@ # limitations under the License. from pathlib import Path +import re from binary_cache_builder import BinaryCacheBuilder from . test_utils import TestBase, TestHelper @@ -30,15 +31,16 @@ class TestAnnotate(TestBase): # Generate annotated files. source_dir = TestHelper.testdata_dir - self.run_cmd(['annotate.py', '-i', testdata_file, '-s', str(source_dir)]) + self.run_cmd(['annotate.py', '-i', testdata_file, '-s', + str(source_dir), '--summary-width', '1000']) # Check annotated files. annotate_dir = Path('annotated_files') summary_file = annotate_dir / 'summary' check_items = [ - 'two_functions.cpp: accumulated_period: 100.000000%, period: 100.000000%', - 'function (main): line 20, accumulated_period: 100.000000%, period: 0.000000%', - 'line 16: accumulated_period: 50.058937%, period: 50.058937%', + re.compile(r'100.00% \| 100.00% \| .+two_functions.cpp'), + '100.00% | 0.00% | main (line 20)', + '50.06% | 50.06% | line 16', ] self.check_strings_in_file(summary_file, check_items) @@ -46,5 +48,5 @@ class TestAnnotate(TestBase): self.assertEqual(len(source_files), 1) source_file = source_files[0] self.assertEqual(source_file.name, 'two_functions.cpp') - check_items = ['/* acc_p: 49.941063%, p: 49.941063% */ *p = i;'] + check_items = ['/* Total 50.06%, Self 50.06% */ *p = i;'] self.check_strings_in_file(source_file, check_items) diff --git a/simpleperf/scripts/test/app_test.py b/simpleperf/scripts/test/app_test.py index 146c1403..c5db86b8 100644 --- a/simpleperf/scripts/test/app_test.py +++ b/simpleperf/scripts/test/app_test.py @@ -21,6 +21,7 @@ import re import shutil import subprocess import time +from typing import List, Tuple from simpleperf_utils import remove from . test_utils import TestBase, TestHelper, AdbHelper, INFERNO_SCRIPT @@ -107,7 +108,8 @@ class TestExampleBase(TestBase): return self.fail("Failed to call check_file_under_dir(dir=%s, file=%s)" % (dirname, filename)) - def check_annotation_summary(self, summary_file, check_entries): + def check_annotation_summary( + self, summary_file: str, check_entries: List[Tuple[str, float, float]]): """ check_entries is a list of (name, accumulated_period, period). This function checks for each entry, if the line containing [name] has at least required accumulated_period and period. @@ -116,7 +118,7 @@ class TestExampleBase(TestBase): with open(summary_file, 'r') as fh: summary = fh.read() fulfilled = [False for x in check_entries] - summary_check_re = re.compile(r'accumulated_period:\s*([\d.]+)%.*period:\s*([\d.]+)%') + summary_check_re = re.compile(r'^\|\s*([\d.]+)%\s*\|\s*([\d.]+)%\s*\|') for line in summary.split('\n'): for i, (name, need_acc_period, need_period) in enumerate(check_entries): if not fulfilled[i] and name in line: @@ -170,7 +172,7 @@ class TestExampleBase(TestBase): def common_test_annotate(self): self.run_cmd(["annotate.py", "-h"]) remove("annotated_files") - self.run_cmd(["annotate.py", "-s", self.example_path]) + self.run_cmd(["annotate.py", "-s", self.example_path, '--summary-width', '1000']) self.check_exist(dirname="annotated_files") def common_test_report_sample(self, check_strings): diff --git a/simpleperf/scripts/test/cpp_app_test.py b/simpleperf/scripts/test/cpp_app_test.py index fb4d7077..f57868ca 100644 --- a/simpleperf/scripts/test/cpp_app_test.py +++ b/simpleperf/scripts/test/cpp_app_test.py @@ -107,7 +107,8 @@ class TestExampleCppTraceOffCpu(TestExampleBase): 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.run_cmd(["annotate.py", "-s", self.example_path, "--comm", + "SleepThread", '--summary-width', '1000']) self.check_exist(dirname="annotated_files") self.check_file_under_dir("annotated_files", "native-lib.cpp") summary_file = os.path.join("annotated_files", "summary") @@ -140,7 +141,8 @@ class TestExampleCppJniCall(TestExampleBase): "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.run_cmd(["annotate.py", "-s", self.example_path, "--comm", + "BusyThread", '--summary-width', '1000']) self.check_exist(dirname="annotated_files") self.check_file_under_dir("annotated_files", "native-lib.cpp") summary_file = os.path.join("annotated_files", "summary") diff --git a/simpleperf/scripts/test/java_app_test.py b/simpleperf/scripts/test/java_app_test.py index f3c89136..e334036a 100644 --- a/simpleperf/scripts/test/java_app_test.py +++ b/simpleperf/scripts/test/java_app_test.py @@ -197,7 +197,7 @@ class TestExamplePureJavaTraceOffCpu(TestExampleBase): "com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction" ]) remove("annotated_files") - self.run_cmd(["annotate.py", "-s", self.example_path]) + self.run_cmd(["annotate.py", "-s", self.example_path, '--summary-width', '1000']) self.check_exist(dirname="annotated_files") if self.use_compiled_java_code: self.check_file_under_dir("annotated_files", "SleepActivity.java") diff --git a/simpleperf/scripts/test/kotlin_app_test.py b/simpleperf/scripts/test/kotlin_app_test.py index 9055453e..cddfd82d 100644 --- a/simpleperf/scripts/test/kotlin_app_test.py +++ b/simpleperf/scripts/test/kotlin_app_test.py @@ -116,7 +116,7 @@ class TestExampleOfKotlinTraceOffCpu(TestExampleBase): ]) if self.use_compiled_java_code: remove("annotated_files") - self.run_cmd(["annotate.py", "-s", self.example_path]) + self.run_cmd(["annotate.py", "-s", self.example_path, '--summary-width', '1000']) self.check_exist(dirname="annotated_files") self.check_file_under_dir("annotated_files", "SleepActivity.kt") summary_file = os.path.join("annotated_files", "summary") diff --git a/simpleperf/scripts/test/test_utils.py b/simpleperf/scripts/test/test_utils.py index 77d2a3b2..422f04de 100644 --- a/simpleperf/scripts/test/test_utils.py +++ b/simpleperf/scripts/test/test_utils.py @@ -26,7 +26,7 @@ import shutil import sys import subprocess import time -from typing import List, Optional, Tuple +from typing import List, Optional, Tuple, Union import unittest from simpleperf_utils import remove, get_script_dir, AdbHelper, is_windows, bytes_to_str @@ -189,7 +189,7 @@ class TestBase(unittest.TestCase): return output_data return '' - def check_strings_in_file(self, filename, strings): + def check_strings_in_file(self, filename, strings: List[Union[str, re.Pattern]]): self.check_exist(filename=filename) with open(filename, 'r') as fh: self.check_strings_in_content(fh.read(), strings) @@ -200,8 +200,13 @@ class TestBase(unittest.TestCase): if dirname: self.assertTrue(os.path.isdir(dirname), dirname) - def check_strings_in_content(self, content, strings): - fulfilled = [content.find(s) != -1 for s in strings] + def check_strings_in_content(self, content: str, strings: List[Union[str, re.Pattern]]): + fulfilled = [] + for s in strings: + if isinstance(s, re.Pattern): + fulfilled.append(s.search(content)) + else: + fulfilled.append(s in content) self.check_fulfilled_entries(fulfilled, strings) def check_fulfilled_entries(self, fulfilled, entries): |