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 94def AdbArgs(*args): 95 cmd = [ADB_PATH] + list([str(x) for x in args]) 96 logging.debug('> adb ' + ' '.join([str(x) for x in args])) 97 return cmd 98 99def AdbCall(*args): 100 return subprocess.check_output(AdbArgs(*args)).decode('utf-8').rstrip() 101 102def SingleTraceRun(out_dir, 103 prio_name, 104 buffer_size_kb, 105 drain_rate_ms, 106 num_threads, 107 duty_cycle, 108 period_us): 109 busy_threads_args = AdbArgs( 110 'shell', 111 '/data/local/tmp/busy_threads', 112 '--threads={}'.format(num_threads), 113 '--duty_cycle={}'.format(duty_cycle), 114 '--period_us={}'.format(period_us) 115 ) 116 perfetto_args = AdbArgs( 117 'shell', 118 'perfetto', 119 '--txt', 120 '-c', 121 '-', 122 '-o', 123 '-' 124 ) 125 126 # Create a file object to read the trace into. 127 with tempfile.NamedTemporaryFile() as trace_file: 128 logging.info("Starting trace with parameters ({}, {}, {}, {}, {}, {})" 129 .format(prio_name, 130 buffer_size_kb, 131 drain_rate_ms, 132 num_threads, 133 duty_cycle, 134 period_us)) 135 136 # Start the busy threads running. 137 busy_threads_handle = subprocess.Popen(busy_threads_args) 138 139 # Start the Perfetto trace. 140 perfetto_handle = subprocess.Popen( 141 perfetto_args, stdin=subprocess.PIPE, stdout=trace_file) 142 143 # Create the config with the parameters 144 config = TEMPLATED_PERFETTO_CFG.format( 145 buffer_size_kb=buffer_size_kb, drain_period_ms=drain_rate_ms) 146 147 # Send the config to the Perfetto binary and wait for response. 148 perfetto_handle.stdin.write(config.encode()) 149 perfetto_handle.stdin.close() 150 perfetto_ret = perfetto_handle.wait() 151 152 # Stop busy threads from running. 153 busy_threads_handle.terminate() 154 155 # Return any errors from Perfetto. 156 if perfetto_ret: 157 raise subprocess.CalledProcessError( 158 cmd=perfetto_args, returncode=perfetto_ret) 159 160 # TODO(lalitm): allow trace processor to take the query file from stdin 161 # to prevent this hack from being required. 162 with tempfile.NamedTemporaryFile() as trace_query_file: 163 trace_query_file.file.write(TRACE_PROCESSOR_QUERY.encode()) 164 trace_query_file.file.flush() 165 166 # Run the trace processor on the config. 167 tp_path = os.path.join(out_dir, 'trace_processor_shell') 168 tp_out = subprocess.check_output( 169 [tp_path, '-q', trace_query_file.name, trace_file.name]) 170 171 # Get the CSV output from trace processor (stripping the header). 172 [num_sched, num_overrun] = str(tp_out).split('\n')[1].split(',') 173 174 # Print the row to stdout. 175 sys.stdout.write('"{}",{},{},{},{},{},{},{}\n' 176 .format(prio_name, 177 buffer_size_kb, 178 drain_rate_ms, 179 num_threads, 180 duty_cycle, 181 period_us, 182 num_sched, 183 num_overrun)) 184 185def SinglePriorityRun(out_dir, prio_name): 186 for buffer_size_kb in PERFETTO_PER_CPU_BUFFER_SIZE_KB_PARAMS: 187 for drain_rate_ms in PERFETTO_DRAIN_RATE_MS_PARAMS: 188 for num_threads in BUSY_THREADS_NUM_THREADS_PARAMS: 189 for duty_cycle in BUSY_THREADS_DUTY_CYCLE_PARAMS: 190 for period_us in BUSY_THREADS_PERIOD_US_PARAMS: 191 SingleTraceRun( 192 out_dir, 193 prio_name, 194 buffer_size_kb, 195 drain_rate_ms, 196 num_threads, 197 duty_cycle, 198 period_us 199 ) 200 201def CycleTracedAndProbes(): 202 AdbCall('shell', 'stop', 'traced') 203 AdbCall('shell', 'stop', 'traced_probes') 204 AdbCall('shell', 'start', 'traced') 205 AdbCall('shell', 'start', 'traced_probes') 206 AdbCall('shell', 'sleep', '5') 207 traced_pid = AdbCall('shell', 'pidof', 'traced') 208 probes_pid = AdbCall('shell', 'pidof', 'traced_probes') 209 assert(traced_pid is not None and probes_pid is not None) 210 return (traced_pid, probes_pid) 211 212def Main(): 213 parser = argparse.ArgumentParser() 214 parser.add_argument('linux_out_dir', help='out/android/') 215 parser.add_argument('android_out_dir', help='out/android/') 216 args = parser.parse_args() 217 218 # Root ourselves on the device. 219 logging.info('Waiting for device and rooting ...') 220 AdbCall('wait-for-device') 221 AdbCall('root') 222 AdbCall('wait-for-device') 223 224 # Push busy threads to device 225 busy_threads_path = os.path.join(args.android_out_dir, 'busy_threads') 226 AdbCall('shell', 'rm', '-rf', '/data/local/tmp/perfetto_load_test') 227 AdbCall('shell', 'mkdir', '/data/local/tmp/perfetto_load_test') 228 AdbCall('push', busy_threads_path, '/data/local/tmp/perfetto_load_test/') 229 230 # Stop and start traced and traced_probes 231 (traced_pid, probes_pid) = CycleTracedAndProbes() 232 233 # Print the header for csv. 234 sys.stdout.write('"{}","{}","{}","{}","{}","{}","{}","{}"\n' 235 .format('prio_name', 236 'buffer_size_kb', 237 'drain_rate_ms', 238 'num_threads', 239 'duty_cycle', 240 'period_us', 241 'num_sched', 242 'num_overrun')) 243 244 # First, do a single run in all configurations without changing prio. 245 SinglePriorityRun(args.linux_out_dir, 'Default') 246 247 # Stop and start traced and traced_probes 248 (traced_pid, probes_pid) = CycleTracedAndProbes() 249 250 # Setup the nice values and check them. 251 AdbCall('shell', 'renice', '-n', '-19', '-p', traced_pid) 252 AdbCall('shell', 'renice', '-n', '-19', '-p', probes_pid) 253 logging.debug(AdbCall('shell', 'cat', '/proc/{}/stat'.format(traced_pid))) 254 logging.debug(AdbCall('shell', 'cat', '/proc/{}/stat'.format(probes_pid))) 255 256 # Do the run. 257 SinglePriorityRun(args.linux_out_dir, '-19 Nice') 258 259 # Stop and start traced and traced_probes 260 (traced_pid, probes_pid) = CycleTracedAndProbes() 261 262 # Then do a run with FIFO scheduling for traced and traced_probes. 263 AdbCall('shell', 'chrt', '-f', '-p', traced_pid, '99') 264 AdbCall('shell', 'chrt', '-f', '-p', probes_pid, '99') 265 logging.debug(AdbCall('shell', 'chrt', '-p', traced_pid)) 266 logging.debug(AdbCall('shell', 'chrt', '-p', probes_pid)) 267 268 # Do the run. 269 SinglePriorityRun(args.linux_out_dir, 'FIFO') 270 271 # Stop and start traced and traced_probes 272 (traced_pid, probes_pid) = CycleTracedAndProbes() 273 274 # Cleanup any pushed files, priorities etc. 275 logging.info("Cleaning up test") 276 AdbCall('shell', 'rm', '-rf', '/data/local/tmp/perfetto_load_test') 277 logging.debug(AdbCall('shell', 'cat', '/proc/{}/stat'.format(traced_pid))) 278 logging.debug(AdbCall('shell', 'cat', '/proc/{}/stat'.format(probes_pid))) 279 AdbCall('unroot') 280 281 return 0 282 283if __name__ == '__main__': 284 logging.basicConfig(level=logging.INFO) 285 sys.exit(Main()) 286