1#!/usr/bin/env python 2# Copyright (C) 2019 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16import argparse 17import logging 18import os 19import sys 20import subprocess 21import tempfile 22 23ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 24ADB_PATH = os.path.join(ROOT_DIR, 'buildtools/android_sdk/platform-tools/adb') 25 26TEMPLATED_PERFETTO_CFG = ''' 27buffers {{ 28 size_kb: 65536 29 fill_policy: RING_BUFFER 30}} 31data_sources {{ 32 config {{ 33 name: "linux.ftrace" 34 target_buffer: 0 35 ftrace_config {{ 36 ftrace_events: "sched_switch" 37 buffer_size_kb: {buffer_size_kb} 38 drain_period_ms: {drain_period_ms} 39 }} 40 }} 41}} 42duration_ms: 15000 43''' 44 45PERFETTO_PER_CPU_BUFFER_SIZE_KB_PARAMS = [ 46 128, 47 256, 48 512, 49 1 * 1024, # 1 MB 50 2 * 1024, # 2 MB 51 4 * 1024, # 4 MB 52] 53 54PERFETTO_DRAIN_RATE_MS_PARAMS = [ 55 100, 56 240, 57 500, 58 1 * 1000, # 1s 59 2 * 1000, # 2s 60 5 * 1000, # 5s 61] 62 63BUSY_THREADS_NUM_THREADS_PARAMS = [ 64 8, 65 32, 66 128, 67] 68 69BUSY_THREADS_DUTY_CYCLE_PARAMS = [ 70 10, 71 100, 72] 73 74BUSY_THREADS_PERIOD_US_PARAMS = [ 75 500, 76 1 * 1000, # 1 ms 77 10 * 1000, # 10 ms 78] 79 80TRACE_PROCESSOR_QUERY = """ 81SELECT 82 a.value as num_sched, 83 b.value as num_overrun 84FROM ( 85 SELECT COUNT(*) as value 86 FROM sched 87) as a, ( 88 SELECT SUM(value) as value 89 FROM stats 90 WHERE name = 'ftrace_cpu_overrun_end' 91) as b 92""" 93 94 95def AdbArgs(*args): 96 cmd = [ADB_PATH] + list([str(x) for x in args]) 97 logging.debug('> adb ' + ' '.join([str(x) for x in args])) 98 return cmd 99 100 101def AdbCall(*args): 102 return subprocess.check_output(AdbArgs(*args)).decode('utf-8').rstrip() 103 104 105def SingleTraceRun(out_dir, prio_name, buffer_size_kb, drain_rate_ms, 106 num_threads, duty_cycle, period_us): 107 busy_threads_args = AdbArgs('shell', '/data/local/tmp/busy_threads', 108 '--threads={}'.format(num_threads), 109 '--duty_cycle={}'.format(duty_cycle), 110 '--period_us={}'.format(period_us)) 111 perfetto_args = AdbArgs('shell', 'perfetto', '--txt', '-c', '-', '-o', '-') 112 113 # Create a file object to read the trace into. 114 with tempfile.NamedTemporaryFile() as trace_file: 115 logging.info( 116 "Starting trace with parameters ({}, {}, {}, {}, {}, {})".format( 117 prio_name, buffer_size_kb, drain_rate_ms, num_threads, duty_cycle, 118 period_us)) 119 120 # Start the busy threads running. 121 busy_threads_handle = subprocess.Popen(busy_threads_args) 122 123 # Start the Perfetto trace. 124 perfetto_handle = subprocess.Popen( 125 perfetto_args, stdin=subprocess.PIPE, stdout=trace_file) 126 127 # Create the config with the parameters 128 config = TEMPLATED_PERFETTO_CFG.format( 129 buffer_size_kb=buffer_size_kb, drain_period_ms=drain_rate_ms) 130 131 # Send the config to the Perfetto binary and wait for response. 132 perfetto_handle.stdin.write(config.encode()) 133 perfetto_handle.stdin.close() 134 perfetto_ret = perfetto_handle.wait() 135 136 # Stop busy threads from running. 137 busy_threads_handle.terminate() 138 139 # Return any errors from Perfetto. 140 if perfetto_ret: 141 raise subprocess.CalledProcessError( 142 cmd=perfetto_args, returncode=perfetto_ret) 143 144 # TODO(lalitm): allow trace processor to take the query file from stdin 145 # to prevent this hack from being required. 146 with tempfile.NamedTemporaryFile() as trace_query_file: 147 trace_query_file.file.write(TRACE_PROCESSOR_QUERY.encode()) 148 trace_query_file.file.flush() 149 150 # Run the trace processor on the config. 151 tp_path = os.path.join(out_dir, 'trace_processor_shell') 152 tp_out = subprocess.check_output( 153 [tp_path, '-q', trace_query_file.name, trace_file.name]) 154 155 # Get the CSV output from trace processor (stripping the header). 156 [num_sched, num_overrun] = str(tp_out).split('\n')[1].split(',') 157 158 # Print the row to stdout. 159 sys.stdout.write('"{}",{},{},{},{},{},{},{}\n'.format( 160 prio_name, buffer_size_kb, drain_rate_ms, num_threads, duty_cycle, 161 period_us, num_sched, num_overrun)) 162 163 164def SinglePriorityRun(out_dir, prio_name): 165 for buffer_size_kb in PERFETTO_PER_CPU_BUFFER_SIZE_KB_PARAMS: 166 for drain_rate_ms in PERFETTO_DRAIN_RATE_MS_PARAMS: 167 for num_threads in BUSY_THREADS_NUM_THREADS_PARAMS: 168 for duty_cycle in BUSY_THREADS_DUTY_CYCLE_PARAMS: 169 for period_us in BUSY_THREADS_PERIOD_US_PARAMS: 170 SingleTraceRun(out_dir, prio_name, buffer_size_kb, drain_rate_ms, 171 num_threads, duty_cycle, period_us) 172 173 174def CycleTracedAndProbes(): 175 AdbCall('shell', 'stop', 'traced') 176 AdbCall('shell', 'stop', 'traced_probes') 177 AdbCall('shell', 'start', 'traced') 178 AdbCall('shell', 'start', 'traced_probes') 179 AdbCall('shell', 'sleep', '5') 180 traced_pid = AdbCall('shell', 'pidof', 'traced') 181 probes_pid = AdbCall('shell', 'pidof', 'traced_probes') 182 assert (traced_pid is not None and probes_pid is not None) 183 return (traced_pid, probes_pid) 184 185 186def Main(): 187 parser = argparse.ArgumentParser() 188 parser.add_argument('linux_out_dir', help='out/android/') 189 parser.add_argument('android_out_dir', help='out/android/') 190 args = parser.parse_args() 191 192 # Root ourselves on the device. 193 logging.info('Waiting for device and rooting ...') 194 AdbCall('wait-for-device') 195 AdbCall('root') 196 AdbCall('wait-for-device') 197 198 # Push busy threads to device 199 busy_threads_path = os.path.join(args.android_out_dir, 'busy_threads') 200 AdbCall('shell', 'rm', '-rf', '/data/local/tmp/perfetto_load_test') 201 AdbCall('shell', 'mkdir', '/data/local/tmp/perfetto_load_test') 202 AdbCall('push', busy_threads_path, '/data/local/tmp/perfetto_load_test/') 203 204 # Stop and start traced and traced_probes 205 (traced_pid, probes_pid) = CycleTracedAndProbes() 206 207 # Print the header for csv. 208 sys.stdout.write('"{}","{}","{}","{}","{}","{}","{}","{}"\n'.format( 209 'prio_name', 'buffer_size_kb', 'drain_rate_ms', 'num_threads', 210 'duty_cycle', 'period_us', 'num_sched', 'num_overrun')) 211 212 # First, do a single run in all configurations without changing prio. 213 SinglePriorityRun(args.linux_out_dir, 'Default') 214 215 # Stop and start traced and traced_probes 216 (traced_pid, probes_pid) = CycleTracedAndProbes() 217 218 # Setup the nice values and check them. 219 AdbCall('shell', 'renice', '-n', '-19', '-p', traced_pid) 220 AdbCall('shell', 'renice', '-n', '-19', '-p', probes_pid) 221 logging.debug(AdbCall('shell', 'cat', '/proc/{}/stat'.format(traced_pid))) 222 logging.debug(AdbCall('shell', 'cat', '/proc/{}/stat'.format(probes_pid))) 223 224 # Do the run. 225 SinglePriorityRun(args.linux_out_dir, '-19 Nice') 226 227 # Stop and start traced and traced_probes 228 (traced_pid, probes_pid) = CycleTracedAndProbes() 229 230 # Then do a run with FIFO scheduling for traced and traced_probes. 231 AdbCall('shell', 'chrt', '-f', '-p', traced_pid, '99') 232 AdbCall('shell', 'chrt', '-f', '-p', probes_pid, '99') 233 logging.debug(AdbCall('shell', 'chrt', '-p', traced_pid)) 234 logging.debug(AdbCall('shell', 'chrt', '-p', probes_pid)) 235 236 # Do the run. 237 SinglePriorityRun(args.linux_out_dir, 'FIFO') 238 239 # Stop and start traced and traced_probes 240 (traced_pid, probes_pid) = CycleTracedAndProbes() 241 242 # Cleanup any pushed files, priorities etc. 243 logging.info("Cleaning up test") 244 AdbCall('shell', 'rm', '-rf', '/data/local/tmp/perfetto_load_test') 245 logging.debug(AdbCall('shell', 'cat', '/proc/{}/stat'.format(traced_pid))) 246 logging.debug(AdbCall('shell', 'cat', '/proc/{}/stat'.format(probes_pid))) 247 AdbCall('unroot') 248 249 return 0 250 251 252if __name__ == '__main__': 253 logging.basicConfig(level=logging.INFO) 254 sys.exit(Main()) 255