xref: /aosp_15_r20/system/extras/simpleperf/scripts/ipc.py (revision 288bf5226967eb3dac5cce6c939ccc2a7f2b4fe5)
1*288bf522SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*288bf522SAndroid Build Coastguard Worker#
3*288bf522SAndroid Build Coastguard Worker# Copyright (C) 2023 The Android Open Source Project
4*288bf522SAndroid Build Coastguard Worker#
5*288bf522SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*288bf522SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*288bf522SAndroid Build Coastguard Worker# You may obtain a copy of the License at
8*288bf522SAndroid Build Coastguard Worker#
9*288bf522SAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
10*288bf522SAndroid Build Coastguard Worker#
11*288bf522SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*288bf522SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
13*288bf522SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*288bf522SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
15*288bf522SAndroid Build Coastguard Worker# limitations under the License.
16*288bf522SAndroid Build Coastguard Worker#
17*288bf522SAndroid Build Coastguard Worker
18*288bf522SAndroid Build Coastguard Worker"""ipc.py: Capture the Instructions per Cycle (IPC) of the system during a
19*288bf522SAndroid Build Coastguard Worker           specified duration.
20*288bf522SAndroid Build Coastguard Worker
21*288bf522SAndroid Build Coastguard Worker  Example:
22*288bf522SAndroid Build Coastguard Worker    ./ipc.py
23*288bf522SAndroid Build Coastguard Worker    ./ipc.py 2 20          # Set interval to 2 secs and total duration to 20 secs
24*288bf522SAndroid Build Coastguard Worker    ./ipc.py -p 284 -C 4   # Only profile the PID 284 while running on core 4
25*288bf522SAndroid Build Coastguard Worker    ./ipc.py -c 'sleep 5'  # Only profile the command to run
26*288bf522SAndroid Build Coastguard Worker
27*288bf522SAndroid Build Coastguard Worker  Result looks like:
28*288bf522SAndroid Build Coastguard Worker    K_CYCLES   K_INSTR      IPC
29*288bf522SAndroid Build Coastguard Worker    36840      14138       0.38
30*288bf522SAndroid Build Coastguard Worker    70701      27743       0.39
31*288bf522SAndroid Build Coastguard Worker    104562     41350       0.40
32*288bf522SAndroid Build Coastguard Worker    138264     54916       0.40
33*288bf522SAndroid Build Coastguard Worker"""
34*288bf522SAndroid Build Coastguard Worker
35*288bf522SAndroid Build Coastguard Workerimport io
36*288bf522SAndroid Build Coastguard Workerimport logging
37*288bf522SAndroid Build Coastguard Workerimport subprocess
38*288bf522SAndroid Build Coastguard Workerimport sys
39*288bf522SAndroid Build Coastguard Workerimport time
40*288bf522SAndroid Build Coastguard Worker
41*288bf522SAndroid Build Coastguard Workerfrom simpleperf_utils import (
42*288bf522SAndroid Build Coastguard Worker        AdbHelper, BaseArgumentParser, get_target_binary_path, log_exit)
43*288bf522SAndroid Build Coastguard Worker
44*288bf522SAndroid Build Coastguard Workerdef start_profiling(adb, args, target_args):
45*288bf522SAndroid Build Coastguard Worker    """Start simpleperf process on device."""
46*288bf522SAndroid Build Coastguard Worker    shell_args = ['simpleperf', 'stat', '-e', 'cpu-cycles',
47*288bf522SAndroid Build Coastguard Worker            '-e', 'instructions', '--interval', str(args.interval * 1000),
48*288bf522SAndroid Build Coastguard Worker            '--duration', str(args.duration)]
49*288bf522SAndroid Build Coastguard Worker    shell_args += target_args
50*288bf522SAndroid Build Coastguard Worker    adb_args = [adb.adb_path, 'shell'] + shell_args
51*288bf522SAndroid Build Coastguard Worker    logging.info('run adb cmd: %s' % adb_args)
52*288bf522SAndroid Build Coastguard Worker    return subprocess.Popen(adb_args, stdout=subprocess.PIPE)
53*288bf522SAndroid Build Coastguard Worker
54*288bf522SAndroid Build Coastguard Workerdef capture_stats(adb, args, stat_subproc):
55*288bf522SAndroid Build Coastguard Worker    """Capture IPC profiling stats or stop profiling when user presses Ctrl-C."""
56*288bf522SAndroid Build Coastguard Worker    try:
57*288bf522SAndroid Build Coastguard Worker        print("%-10s %-10s %5s" % ("K_CYCLES", "K_INSTR", "IPC"))
58*288bf522SAndroid Build Coastguard Worker        cpu_cycles = 0
59*288bf522SAndroid Build Coastguard Worker        for line in io.TextIOWrapper(stat_subproc.stdout, encoding="utf-8"):
60*288bf522SAndroid Build Coastguard Worker            if 'cpu-cycles' in line:
61*288bf522SAndroid Build Coastguard Worker                if args.cpu == None:
62*288bf522SAndroid Build Coastguard Worker                    cpu_cycles = int(line.split()[0].replace(",", ""))
63*288bf522SAndroid Build Coastguard Worker                    continue
64*288bf522SAndroid Build Coastguard Worker                columns = line.split()
65*288bf522SAndroid Build Coastguard Worker                if args.cpu == int(columns[0]):
66*288bf522SAndroid Build Coastguard Worker                    cpu_cycles = int(columns[1].replace(",", ""))
67*288bf522SAndroid Build Coastguard Worker            elif 'instructions' in line:
68*288bf522SAndroid Build Coastguard Worker                if cpu_cycles == 0: cpu_cycles = 1 # PMCs are broken, or no events
69*288bf522SAndroid Build Coastguard Worker                ins = -1
70*288bf522SAndroid Build Coastguard Worker                columns = line.split()
71*288bf522SAndroid Build Coastguard Worker                if args.cpu == None:
72*288bf522SAndroid Build Coastguard Worker                    ins = int(columns[0].replace(",", ""))
73*288bf522SAndroid Build Coastguard Worker                elif args.cpu == int(columns[0]):
74*288bf522SAndroid Build Coastguard Worker                    ins = int(columns[1].replace(",", ""))
75*288bf522SAndroid Build Coastguard Worker                if ins >= 0:
76*288bf522SAndroid Build Coastguard Worker                    print("%-10d %-10d %5.2f" %
77*288bf522SAndroid Build Coastguard Worker                            (cpu_cycles / 1000, ins / 1000, ins / cpu_cycles))
78*288bf522SAndroid Build Coastguard Worker
79*288bf522SAndroid Build Coastguard Worker    except KeyboardInterrupt:
80*288bf522SAndroid Build Coastguard Worker        stop_profiling(adb)
81*288bf522SAndroid Build Coastguard Worker        stat_subproc = None
82*288bf522SAndroid Build Coastguard Worker
83*288bf522SAndroid Build Coastguard Workerdef stop_profiling(adb):
84*288bf522SAndroid Build Coastguard Worker    """Stop profiling by sending SIGINT to simpleperf and wait until it exits."""
85*288bf522SAndroid Build Coastguard Worker    has_killed = False
86*288bf522SAndroid Build Coastguard Worker    while True:
87*288bf522SAndroid Build Coastguard Worker        (result, _) = adb.run_and_return_output(['shell', 'pidof', 'simpleperf'])
88*288bf522SAndroid Build Coastguard Worker        if not result:
89*288bf522SAndroid Build Coastguard Worker            break
90*288bf522SAndroid Build Coastguard Worker        if not has_killed:
91*288bf522SAndroid Build Coastguard Worker            has_killed = True
92*288bf522SAndroid Build Coastguard Worker            adb.run_and_return_output(['shell', 'pkill', '-l', '2', 'simpleperf'])
93*288bf522SAndroid Build Coastguard Worker        time.sleep(1)
94*288bf522SAndroid Build Coastguard Worker
95*288bf522SAndroid Build Coastguard Workerdef capture_ipc(args):
96*288bf522SAndroid Build Coastguard Worker    # Initialize adb and verify device
97*288bf522SAndroid Build Coastguard Worker    adb = AdbHelper(enable_switch_to_root=True)
98*288bf522SAndroid Build Coastguard Worker    if not adb.is_device_available():
99*288bf522SAndroid Build Coastguard Worker        log_exit('No Android device is connected via ADB.')
100*288bf522SAndroid Build Coastguard Worker    is_root_device = adb.switch_to_root()
101*288bf522SAndroid Build Coastguard Worker    device_arch = adb.get_device_arch()
102*288bf522SAndroid Build Coastguard Worker
103*288bf522SAndroid Build Coastguard Worker    if args.pid:
104*288bf522SAndroid Build Coastguard Worker       (result, _) = adb.run_and_return_output(['shell', 'ls', '/proc/%s' % args.pid])
105*288bf522SAndroid Build Coastguard Worker       if not result:
106*288bf522SAndroid Build Coastguard Worker           log_exit("Pid '%s' does not exist" % args.pid)
107*288bf522SAndroid Build Coastguard Worker
108*288bf522SAndroid Build Coastguard Worker    target_args = []
109*288bf522SAndroid Build Coastguard Worker    if args.cpu is not None:
110*288bf522SAndroid Build Coastguard Worker        target_args += ['--per-core']
111*288bf522SAndroid Build Coastguard Worker    if args.pid:
112*288bf522SAndroid Build Coastguard Worker        target_args += ['-p', args.pid]
113*288bf522SAndroid Build Coastguard Worker    elif args.command:
114*288bf522SAndroid Build Coastguard Worker        target_args += [args.command]
115*288bf522SAndroid Build Coastguard Worker    else:
116*288bf522SAndroid Build Coastguard Worker        target_args += ['-a']
117*288bf522SAndroid Build Coastguard Worker
118*288bf522SAndroid Build Coastguard Worker    stat_subproc = start_profiling(adb, args, target_args)
119*288bf522SAndroid Build Coastguard Worker    capture_stats(adb, args, stat_subproc)
120*288bf522SAndroid Build Coastguard Worker
121*288bf522SAndroid Build Coastguard Workerdef main():
122*288bf522SAndroid Build Coastguard Worker    parser = BaseArgumentParser(description=__doc__)
123*288bf522SAndroid Build Coastguard Worker    parser.add_argument('-C', '--cpu', type=int, help='Capture IPC only for this CPU core')
124*288bf522SAndroid Build Coastguard Worker    process_group = parser.add_mutually_exclusive_group()
125*288bf522SAndroid Build Coastguard Worker    process_group.add_argument('-p', '--pid', help='Capture IPC only for this PID')
126*288bf522SAndroid Build Coastguard Worker    process_group.add_argument('-c', '--command', help='Capture IPC only for this command')
127*288bf522SAndroid Build Coastguard Worker    parser.add_argument('interval', nargs='?', default=1, type=int, help='sampling interval in seconds')
128*288bf522SAndroid Build Coastguard Worker    parser.add_argument('duration', nargs='?', default=10, type=int, help='sampling duration in seconds')
129*288bf522SAndroid Build Coastguard Worker
130*288bf522SAndroid Build Coastguard Worker    args = parser.parse_args()
131*288bf522SAndroid Build Coastguard Worker    if args.interval > args.duration:
132*288bf522SAndroid Build Coastguard Worker        log_exit("interval cannot be greater than duration")
133*288bf522SAndroid Build Coastguard Worker
134*288bf522SAndroid Build Coastguard Worker    capture_ipc(args)
135*288bf522SAndroid Build Coastguard Worker
136*288bf522SAndroid Build Coastguard Workerif __name__ == '__main__':
137*288bf522SAndroid Build Coastguard Worker    main()
138