summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2021-11-02 16:04:12 -0700
committerYabin Cui <yabinc@google.com>2021-11-02 16:09:13 -0700
commited343476daa008d1e2e3ca85daf348bec893ffd9 (patch)
tree5fff40dae5c9f4a808bb115ad79904e9a1352621
parentce9d6cf7f0ce92101b7c17766b15d5715ae766ed (diff)
downloadextras-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-xsimpleperf/scripts/annotate.py111
-rw-r--r--simpleperf/scripts/test/annotate_test.py12
-rw-r--r--simpleperf/scripts/test/app_test.py8
-rw-r--r--simpleperf/scripts/test/cpp_app_test.py6
-rw-r--r--simpleperf/scripts/test/java_app_test.py2
-rw-r--r--simpleperf/scripts/test/kotlin_app_test.py2
-rw-r--r--simpleperf/scripts/test/test_utils.py13
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):