summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJahdiel Alvarez <jahdiel@google.com>2023-12-12 00:06:51 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2023-12-12 00:06:51 +0000
commitc554c23e26433d85c3d1523763fddcf26e3f67ca (patch)
tree15a825745d5c6b3b6458e8c809af2794e570b502
parent1cfe9faa25a1a3f817bdfb9fa50b81f3e9b33b63 (diff)
parent14d067b38bfc12cab6556196a08d6ea7b56b3ee7 (diff)
downloadextras-c554c23e26433d85c3d1523763fddcf26e3f67ca.tar.gz
Merge "Added script to capture the instructions per cycle (IPC) of the system" into main
-rw-r--r--simpleperf/doc/scripts_reference.md20
-rwxr-xr-xsimpleperf/scripts/ipc.py137
2 files changed, 157 insertions, 0 deletions
diff --git a/simpleperf/doc/scripts_reference.md b/simpleperf/doc/scripts_reference.md
index 31dee02d..fd76ed94 100644
--- a/simpleperf/doc/scripts_reference.md
+++ b/simpleperf/doc/scripts_reference.md
@@ -320,3 +320,23 @@ Then we can read all samples through GetNextSample(). For each sample, we can re
Examples of using `simpleperf_report_lib.py` are in `report_sample.py`, `report_html.py`,
`pprof_proto_generator.py` and `inferno/inferno.py`.
+
+## ipc.py
+`ipc.py`captures the instructions per cycle (IPC) of the system during a specified duration.
+
+Example:
+```sh
+./ipc.py
+./ipc.py 2 20 # Set interval to 2 secs and total duration to 20 secs
+./ipc.py -p 284 -C 4 # Only profile the PID 284 while running on core 4
+./ipc.py -c 'sleep 5' # Only profile the command to run
+```
+
+The results look like:
+```
+K_CYCLES K_INSTR IPC
+36840 14138 0.38
+70701 27743 0.39
+104562 41350 0.40
+138264 54916 0.40
+```
diff --git a/simpleperf/scripts/ipc.py b/simpleperf/scripts/ipc.py
new file mode 100755
index 00000000..9871875e
--- /dev/null
+++ b/simpleperf/scripts/ipc.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 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.
+#
+
+"""ipc.py: Capture the Instructions per Cycle (IPC) of the system during a
+ specified duration.
+
+ Example:
+ ./ipc.py
+ ./ipc.py 2 20 # Set interval to 2 secs and total duration to 20 secs
+ ./ipc.py -p 284 -C 4 # Only profile the PID 284 while running on core 4
+ ./ipc.py -c 'sleep 5' # Only profile the command to run
+
+ Result looks like:
+ K_CYCLES K_INSTR IPC
+ 36840 14138 0.38
+ 70701 27743 0.39
+ 104562 41350 0.40
+ 138264 54916 0.40
+"""
+
+import io
+import logging
+import subprocess
+import sys
+import time
+
+from simpleperf_utils import (
+ AdbHelper, BaseArgumentParser, get_target_binary_path, log_exit)
+
+def start_profiling(adb, args, target_args):
+ """Start simpleperf process on device."""
+ shell_args = ['simpleperf', 'stat', '-e', 'cpu-cycles',
+ '-e', 'instructions', '--interval', str(args.interval * 1000),
+ '--duration', str(args.duration)]
+ shell_args += target_args
+ adb_args = [adb.adb_path, 'shell'] + shell_args
+ logging.info('run adb cmd: %s' % adb_args)
+ return subprocess.Popen(adb_args, stdout=subprocess.PIPE)
+
+def capture_stats(adb, args, stat_subproc):
+ """Capture IPC profiling stats or stop profiling when user presses Ctrl-C."""
+ try:
+ print("%-10s %-10s %5s" % ("K_CYCLES", "K_INSTR", "IPC"))
+ cpu_cycles = 0
+ for line in io.TextIOWrapper(stat_subproc.stdout, encoding="utf-8"):
+ if 'cpu-cycles' in line:
+ if args.cpu == None:
+ cpu_cycles = int(line.split()[0].replace(",", ""))
+ continue
+ columns = line.split()
+ if args.cpu == int(columns[0]):
+ cpu_cycles = int(columns[1].replace(",", ""))
+ elif 'instructions' in line:
+ if cpu_cycles == 0: cpu_cycles = 1 # PMCs are broken, or no events
+ ins = -1
+ columns = line.split()
+ if args.cpu == None:
+ ins = int(columns[0].replace(",", ""))
+ elif args.cpu == int(columns[0]):
+ ins = int(columns[1].replace(",", ""))
+ if ins >= 0:
+ print("%-10d %-10d %5.2f" %
+ (cpu_cycles / 1000, ins / 1000, ins / cpu_cycles))
+
+ except KeyboardInterrupt:
+ stop_profiling(adb)
+ stat_subproc = None
+
+def stop_profiling(adb):
+ """Stop profiling by sending SIGINT to simpleperf and wait until it exits."""
+ has_killed = False
+ while True:
+ (result, _) = adb.run_and_return_output(['shell', 'pidof', 'simpleperf'])
+ if not result:
+ break
+ if not has_killed:
+ has_killed = True
+ adb.run_and_return_output(['shell', 'pkill', '-l', '2', 'simpleperf'])
+ time.sleep(1)
+
+def capture_ipc(args):
+ # Initialize adb and verify device
+ adb = AdbHelper(enable_switch_to_root=True)
+ if not adb.is_device_available():
+ log_exit('No Android device is connected via ADB.')
+ is_root_device = adb.switch_to_root()
+ device_arch = adb.get_device_arch()
+
+ if args.pid:
+ (result, _) = adb.run_and_return_output(['shell', 'ls', '/proc/%s' % args.pid])
+ if not result:
+ log_exit("Pid '%s' does not exist" % args.pid)
+
+ target_args = []
+ if args.cpu is not None:
+ target_args += ['--per-core']
+ if args.pid:
+ target_args += ['-p', args.pid]
+ elif args.command:
+ target_args += [args.command]
+ else:
+ target_args += ['-a']
+
+ stat_subproc = start_profiling(adb, args, target_args)
+ capture_stats(adb, args, stat_subproc)
+
+def main():
+ parser = BaseArgumentParser(description=__doc__)
+ parser.add_argument('-C', '--cpu', type=int, help='Capture IPC only for this CPU core')
+ process_group = parser.add_mutually_exclusive_group()
+ process_group.add_argument('-p', '--pid', help='Capture IPC only for this PID')
+ process_group.add_argument('-c', '--command', help='Capture IPC only for this command')
+ parser.add_argument('interval', nargs='?', default=1, type=int, help='sampling interval in seconds')
+ parser.add_argument('duration', nargs='?', default=10, type=int, help='sampling duration in seconds')
+
+ args = parser.parse_args()
+ if args.interval > args.duration:
+ log_exit("interval cannot be greater than duration")
+
+ capture_ipc(args)
+
+if __name__ == '__main__':
+ main()