summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJahdiel Alvarez <jahdiel@google.com>2023-12-06 16:00:01 -0800
committerJahdiel Alvarez <jahdiel@google.com>2023-12-11 15:03:04 -0800
commit14d067b38bfc12cab6556196a08d6ea7b56b3ee7 (patch)
tree19c6850eb01650bd9cab52c51513c1f6dd95ba80
parente674d291c41b295a52f80ee064b639903da71073 (diff)
downloadextras-14d067b38bfc12cab6556196a08d6ea7b56b3ee7.tar.gz
Added script to capture the instructions per cycle (IPC) of the system
One can capture the overall IPC of the system as provided by the hardware PMCs. Also, one can filter by PID, CPU core, and command. Test: ./ipc.py Bug: 315213493 Change-Id: I9135d52b34da826fcd90209301063d72bd7ce7cd
-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()