summaryrefslogtreecommitdiff
path: root/simpleperf
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2024-01-29 14:19:13 -0800
committerYabin Cui <yabinc@google.com>2024-01-29 15:04:59 -0800
commit73d797327bde7b64a3bafcfbc2f545aa461dba7d (patch)
tree0fd04dae8c6848339ce0b4831d9bdd50288d32ba /simpleperf
parent5ce0102ccd44e70b6d10e6f23d9b2e924081b4f1 (diff)
downloadextras-73d797327bde7b64a3bafcfbc2f545aa461dba7d.tar.gz
simpleperf: scripts: Add sample_filter.py
It is used to generate sample filter files. A filter file can be passed via --filter-file in report scripts. For example, when a recording file is too large to report, we can split it into several reporting files using filter files: ./sample_filter.py -i perf.data --split-time-range 2 -o sample_filter ./gecko_profile_generator.py -i perf.data --filter-file sample_filter_part1 \ | gzip >profile-part1.json.gz ./gecko_profile_generator.py -i perf.data --filter-file sample_filter_part2 \ | gzip >profile-part2.json.gz Bug: 322509778 Test: run test.py --only-host-test Change-Id: Icea816154d8e21463776a0ce9c407917307d98e9
Diffstat (limited to 'simpleperf')
-rw-r--r--simpleperf/doc/sample_filter.md3
-rw-r--r--simpleperf/doc/scripts_reference.md15
-rwxr-xr-xsimpleperf/scripts/sample_filter.py130
-rwxr-xr-xsimpleperf/scripts/test/do_test.py2
-rw-r--r--simpleperf/scripts/test/sample_filter_test.py42
5 files changed, 191 insertions, 1 deletions
diff --git a/simpleperf/doc/sample_filter.md b/simpleperf/doc/sample_filter.md
index 3755e760..421c9758 100644
--- a/simpleperf/doc/sample_filter.md
+++ b/simpleperf/doc/sample_filter.md
@@ -7,7 +7,8 @@ ranges. To filter samples, we can pass filter options to the report commands or
## filter file format
To filter samples based on time ranges, simpleperf accepts a filter file when reporting. The filter
-file is in text format, containing a list of lines. Each line is a filter command.
+file is in text format, containing a list of lines. Each line is a filter command. The filter file
+can be generated by `sample_filter.py`, and passed to report scripts via `--filter-file`.
```
filter_command1 command_args
diff --git a/simpleperf/doc/scripts_reference.md b/simpleperf/doc/scripts_reference.md
index fd76ed94..d118ed2c 100644
--- a/simpleperf/doc/scripts_reference.md
+++ b/simpleperf/doc/scripts_reference.md
@@ -340,3 +340,18 @@ K_CYCLES K_INSTR IPC
104562 41350 0.40
138264 54916 0.40
```
+
+## sample_filter.py
+
+`sample_filter.py` generates sample filter files as documented in [sample_filter.md](https://android.googlesource.com/platform/system/extras/+/refs/heads/main/simpleperf/doc/sample_filter.md).
+A filter file can be passed in `--filter-file` when running report scripts.
+
+For example, it can be used to split a large recording file into several report files.
+
+```sh
+$ sample_filter.py -i perf.data --split-time-range 2 -o sample_filter
+$ gecko_profile_generator.py -i perf.data --filter-file sample_filter_part1 \
+ | gzip >profile-part1.json.gz
+$ gecko_profile_generator.py -i perf.data --filter-file sample_filter_part2 \
+ | gzip >profile-part2.json.gz
+```
diff --git a/simpleperf/scripts/sample_filter.py b/simpleperf/scripts/sample_filter.py
new file mode 100755
index 00000000..c3617629
--- /dev/null
+++ b/simpleperf/scripts/sample_filter.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""sample_filter.py: generate sample filter files, which can be passed in the
+ --filter-file option when reporting.
+
+Example:
+ ./sample_filter.py -i perf.data --split-time-range 2 -o sample_filter
+ ./gecko_profile_generator.py -i perf.data --filter-file sample_filter_part1 \
+ | gzip >profile-part1.json.gz
+ ./gecko_profile_generator.py -i perf.data --filter-file sample_filter_part2 \
+ | gzip >profile-part2.json.gz
+"""
+
+import logging
+from simpleperf_report_lib import ReportLib
+from simpleperf_utils import BaseArgumentParser
+from typing import Tuple
+
+
+class RecordFileReader:
+ def __init__(self, record_file: str):
+ self.record_file = record_file
+
+ def get_time_range(self) -> Tuple[int, int]:
+ """ Return a tuple of (min_timestamp, max_timestamp). """
+ min_timestamp = 0
+ max_timestamp = 0
+ lib = ReportLib()
+ lib.SetRecordFile(self.record_file)
+ while True:
+ sample = lib.GetNextSample()
+ if not sample:
+ break
+ if not min_timestamp or sample.time < min_timestamp:
+ min_timestamp = sample.time
+ if not max_timestamp or sample.time > max_timestamp:
+ max_timestamp = sample.time
+ lib.Close()
+ return (min_timestamp, max_timestamp)
+
+
+def show_time_range(record_file: str) -> None:
+ reader = RecordFileReader(record_file)
+ time_range = reader.get_time_range()
+ print('time range of samples is %.3f s' % ((time_range[1] - time_range[0]) / 1e9))
+
+
+def filter_samples(
+ record_file: str, split_time_range: int, exclude_first_seconds: int,
+ exclude_last_seconds: int, output_file_prefix: str) -> None:
+ reader = RecordFileReader(record_file)
+ min_timestamp, max_timestamp = reader.get_time_range()
+ comment = 'total time range: %d seconds' % ((max_timestamp - min_timestamp) // 1e9)
+ if exclude_first_seconds:
+ min_timestamp += int(exclude_first_seconds * 1e9)
+ comment += ', exclude first %d seconds' % exclude_first_seconds
+ if exclude_last_seconds:
+ max_timestamp -= int(exclude_last_seconds * 1e9)
+ comment += ', exclude last %d seconds' % exclude_last_seconds
+ if min_timestamp > max_timestamp:
+ logging.error('All samples are filtered out')
+ return
+ if not split_time_range:
+ output_file = output_file_prefix
+ with open(output_file, 'w') as fh:
+ fh.write('// %s\n' % comment)
+ fh.write('GLOBAL_BEGIN %d\n' % min_timestamp)
+ fh.write('GLOBAL_END %d\n' % max_timestamp)
+ print('Generate sample filter file: %s' % output_file)
+ else:
+ step = (max_timestamp - min_timestamp) // split_time_range
+ cur_timestamp = min_timestamp
+ for i in range(split_time_range):
+ output_file = output_file_prefix + '_part%s' % (i + 1)
+ with open(output_file, 'w') as fh:
+ time_range_comment = 'current range: %d to %d seconds' % (
+ (cur_timestamp - min_timestamp) // 1e9,
+ (cur_timestamp + step - min_timestamp) // 1e9)
+ fh.write('// %s, %s\n' % (comment, time_range_comment))
+ fh.write('GLOBAL_BEGIN %d\n' % cur_timestamp)
+ if i == split_time_range - 1:
+ cur_timestamp = max_timestamp
+ else:
+ cur_timestamp += step
+ fh.write('GLOBAL_END %d\n' % (cur_timestamp + 1))
+ cur_timestamp += 1
+ print('Generate sample filter file: %s' % output_file)
+
+
+def main():
+ parser = BaseArgumentParser(description=__doc__)
+ parser.add_argument('-i', '--record-file', nargs='?', default='perf.data',
+ help='Default is perf.data.')
+ parser.add_argument('--show-time-range', action='store_true', help='show time range of samples')
+ parser.add_argument('--split-time-range', type=int,
+ help='split time ranges of samples into several parts')
+ parser.add_argument('--exclude-first-seconds', type=int,
+ help='exclude samples recorded in the first seconds')
+ parser.add_argument('--exclude-last-seconds', type=int,
+ help='exclude samples recorded in the last seconds')
+ parser.add_argument(
+ '-o', '--output-file-prefix', default='sample_filter',
+ help='prefix for the generated sample filter files')
+ args = parser.parse_args()
+
+ if args.show_time_range:
+ show_time_range(args.record_file)
+
+ if args.split_time_range or args.exclude_first_seconds or args.exclude_last_seconds:
+ filter_samples(args.record_file, args.split_time_range, args.exclude_first_seconds,
+ args.exclude_last_seconds, args.output_file_prefix)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/simpleperf/scripts/test/do_test.py b/simpleperf/scripts/test/do_test.py
index 83168b03..d341c20b 100755
--- a/simpleperf/scripts/test/do_test.py
+++ b/simpleperf/scripts/test/do_test.py
@@ -59,6 +59,7 @@ from . report_html_test import *
from . report_lib_test import *
from . report_sample_test import *
from . run_simpleperf_on_device_test import *
+from . sample_filter_test import *
from . stackcollapse_test import *
from . tools_test import *
from . test_utils import TestHelper
@@ -135,6 +136,7 @@ def get_test_type(test: str) -> Optional[str]:
'TestReportHtml',
'TestReportLib',
'TestReportSample',
+ 'TestSampleFilter',
'TestStackCollapse',
'TestTools',
'TestGeckoProfileGenerator'):
diff --git a/simpleperf/scripts/test/sample_filter_test.py b/simpleperf/scripts/test/sample_filter_test.py
new file mode 100644
index 00000000..46d51d08
--- /dev/null
+++ b/simpleperf/scripts/test/sample_filter_test.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+import os
+from pathlib import Path
+import re
+import tempfile
+from typing import List, Optional, Set
+
+from . test_utils import TestBase, TestHelper
+
+
+class TestSampleFilter(TestBase):
+ def test_show_time_range(self):
+ testdata_file = TestHelper.testdata_path('perf_display_bitmaps.data')
+ output = self.run_cmd(['sample_filter.py', '-i', testdata_file,
+ '--show-time-range'], return_output=True)
+ self.assertIn('0.134 s', output)
+
+ def test_split_time_range(self):
+ testdata_file = TestHelper.testdata_path('perf_display_bitmaps.data')
+ self.run_cmd(['sample_filter.py', '-i', testdata_file, '--split-time-range', '2'])
+ part1_data = Path('sample_filter_part1').read_text()
+ self.assertIn('GLOBAL_BEGIN 684943449406175', part1_data)
+ self.assertIn('GLOBAL_END 684943516618526', part1_data)
+ part2_data = Path('sample_filter_part2').read_text()
+ self.assertIn('GLOBAL_BEGIN 684943516618526', part2_data)
+ self.assertIn('GLOBAL_END 684943583830876', part2_data)