Lakshman Annadorai | 3c8f88e | 2022-02-16 16:24:51 -0800 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # |
| 3 | # Copyright (C) 2022 The Android Open Source Project |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | # |
| 17 | """Reports disk I/O usage by UID/Package, process, and file level breakdowns.""" |
| 18 | |
| 19 | from datetime import datetime |
| 20 | from collections import namedtuple |
| 21 | |
| 22 | import androidFsParser |
| 23 | import argparse |
| 24 | import collections |
| 25 | import os |
| 26 | import psutil |
| 27 | import re |
| 28 | import signal |
| 29 | import subprocess |
| 30 | import sys |
| 31 | import threading |
| 32 | import time |
| 33 | import uidProcessMapper |
| 34 | |
| 35 | # ex) lrwxrwxrwx 1 root root 16 1970-01-06 13:22 userdata -> /dev/block/sda14 |
| 36 | RE_LS_BLOCK_DEVICE = r"\S+\s[0-9]+\s\S+\s\S+\s+[0-9]+\s[0-9\-]+\s[0-9]+\:[0-9]+\suserdata\s\-\>\s\/dev\/block\/(\S+)" |
| 37 | |
| 38 | # ex) 1002 246373245 418936352 1818624 0 0 0 0 0 0 0 |
| 39 | RE_UID_IO_STATS_LINE = r"([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)" |
| 40 | |
| 41 | # ex) 253 5 dm-5 3117 0 354656 3324 0 0 0 0 0 2696 3324 0 0 0 0 |
| 42 | RE_DISK_STATS_LINE = r"\s+([0-9]+)\s+([0-9]+)\s([a-z\-0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s([0-9]+)" |
| 43 | |
| 44 | ADB_CMD = "adb" |
| 45 | |
| 46 | TEMP_TRACE_FILE = "temp_trace_file.txt" |
| 47 | CARWATCHDOG_DUMP = "carwatchdog_dump.txt" |
| 48 | OUTPUT_FILE = "ioblame_out.txt" |
| 49 | |
| 50 | WATCHDOG_BUFFER_SECS = 600 |
| 51 | |
| 52 | DID_RECEIVE_SIGINT = False |
| 53 | |
| 54 | |
| 55 | def signal_handler(sig, frame): |
| 56 | global DID_RECEIVE_SIGINT |
| 57 | DID_RECEIVE_SIGINT = True |
| 58 | print("Received signal interrupt") |
| 59 | |
| 60 | |
| 61 | def init_arguments(): |
| 62 | parser = argparse.ArgumentParser( |
Jaegeuk Kim | d4ac37c | 2022-08-19 17:22:04 -0700 | [diff] [blame] | 63 | description="Collect and process f2fs traces") |
Lakshman Annadorai | 3c8f88e | 2022-02-16 16:24:51 -0800 | [diff] [blame] | 64 | parser.add_argument( |
| 65 | "-s", |
| 66 | "--serial", |
| 67 | dest="serial", |
| 68 | action="store", |
| 69 | help="Android device serial number") |
| 70 | parser.add_argument( |
| 71 | "-r", |
| 72 | "--trace_reads", |
| 73 | default=False, |
| 74 | action="store_true", |
| 75 | dest="traceReads", |
Jaegeuk Kim | d4ac37c | 2022-08-19 17:22:04 -0700 | [diff] [blame] | 76 | help="Trace f2fs_dataread_start") |
Lakshman Annadorai | 3c8f88e | 2022-02-16 16:24:51 -0800 | [diff] [blame] | 77 | parser.add_argument( |
| 78 | "-w", |
| 79 | "--trace_writes", |
| 80 | default=False, |
| 81 | action="store_true", |
| 82 | dest="traceWrites", |
Jaegeuk Kim | d4ac37c | 2022-08-19 17:22:04 -0700 | [diff] [blame] | 83 | help="Trace f2fs_datawrite_start") |
Lakshman Annadorai | 3c8f88e | 2022-02-16 16:24:51 -0800 | [diff] [blame] | 84 | parser.add_argument( |
| 85 | "-d", |
| 86 | "--trace_duration", |
| 87 | type=int, |
| 88 | default=3600, |
| 89 | dest="traceDuration", |
| 90 | help="Total trace duration in seconds") |
| 91 | parser.add_argument( |
| 92 | "-i", |
| 93 | "--sampling_interval", |
| 94 | type=int, |
| 95 | default=300, |
| 96 | dest="samplingInterval", |
| 97 | help="Sampling interval in seconds for CarWatchdog collection (applicable only on" |
| 98 | " automotive form-factor") |
| 99 | parser.add_argument( |
| 100 | "-o", |
| 101 | "--output_directory", |
| 102 | type=dir_path, |
| 103 | default=os.getcwd(), |
| 104 | dest="outputDir", |
| 105 | help="Output directory") |
| 106 | |
| 107 | return parser.parse_args() |
| 108 | |
| 109 | |
| 110 | def verify_arguments(args): |
| 111 | if args.serial is not None: |
| 112 | global ADB_CMD |
| 113 | ADB_CMD = "%s %s" % ("adb -s", args.serial) |
| 114 | if not args.traceReads and not args.traceWrites: |
| 115 | raise argparse.ArgumentTypeError( |
| 116 | "Must provide at least one of the --trace_reads or --trace_writes options" |
| 117 | ) |
| 118 | |
| 119 | |
| 120 | def dir_path(path): |
| 121 | if os.path.isdir(path): |
| 122 | return path |
| 123 | else: |
| 124 | raise argparse.ArgumentTypeError( |
| 125 | "{} is not a valid directory path".format(path)) |
| 126 | |
| 127 | |
| 128 | def run_adb_cmd(cmd): |
| 129 | r = subprocess.check_output(ADB_CMD + " " + cmd, shell=True) |
| 130 | return r.decode("utf-8") |
| 131 | |
| 132 | |
| 133 | def run_adb_shell_cmd(cmd): |
| 134 | return run_adb_cmd("shell " + cmd) |
| 135 | |
| 136 | |
| 137 | def run_adb_shell_cmd_strip_output(cmd): |
| 138 | return run_adb_cmd("shell " + cmd).strip() |
| 139 | |
| 140 | |
| 141 | def run_adb_shell_cmd_ignore_err(cmd): |
| 142 | try: |
| 143 | r = subprocess.run( |
| 144 | ADB_CMD + " shell " + cmd, shell=True, capture_output=True) |
| 145 | return r.stdout.decode("utf-8") |
| 146 | except Exception: |
| 147 | return "" |
| 148 | |
| 149 | |
| 150 | def run_shell_cmd(cmd): |
| 151 | return subprocess.check_output(cmd, shell=True) |
| 152 | |
| 153 | |
| 154 | def run_bg_adb_shell_cmd(cmd): |
| 155 | return subprocess.Popen(ADB_CMD + " shell " + cmd, shell=True) |
| 156 | |
| 157 | |
| 158 | def run_bg_shell_cmd(cmd): |
| 159 | return subprocess.Popen(cmd, shell=True) |
| 160 | |
| 161 | |
| 162 | def get_block_dev(): |
| 163 | model = run_adb_shell_cmd_strip_output( |
| 164 | "'getprop ro.product.name' | sed \'s/[ \\t\\r\\n]*$//\'") |
| 165 | print("Found %s Device" % model) |
| 166 | |
| 167 | if "emu" in model: |
| 168 | return "vda" |
| 169 | |
| 170 | result = run_adb_shell_cmd_strip_output( |
| 171 | "'ls -la /dev/block/bootdevice/by-name | grep userdata'") |
| 172 | |
| 173 | match = re.compile(RE_LS_BLOCK_DEVICE).match(result) |
| 174 | if not match: |
| 175 | print("Unknown Device {} -- trying Pixel config".format(model)) |
| 176 | return "sda" |
| 177 | |
| 178 | return match.group(1) |
| 179 | |
| 180 | |
| 181 | def prep_to_do_something(): |
| 182 | run_adb_shell_cmd("'echo 3 > /proc/sys/vm/drop_caches'") |
| 183 | time.sleep(1) |
| 184 | |
| 185 | |
| 186 | def setup_tracepoints(shouldTraceReads, shouldTraceWrites): |
| 187 | # This is a good point to check if the Android FS tracepoints are enabled in the |
| 188 | # kernel or not |
| 189 | isTraceEnabled = run_adb_shell_cmd( |
Jaegeuk Kim | d4ac37c | 2022-08-19 17:22:04 -0700 | [diff] [blame] | 190 | "'if [ -d /sys/kernel/tracing/events/f2fs ]; then echo 0; else echo 1; fi'" |
Lakshman Annadorai | 3c8f88e | 2022-02-16 16:24:51 -0800 | [diff] [blame] | 191 | ) |
| 192 | |
| 193 | if isTraceEnabled == 0: |
| 194 | raise RuntimeError("Android FS tracing is not enabled") |
| 195 | |
| 196 | run_adb_shell_cmd("'echo 0 > /sys/kernel/tracing/tracing_on;\ |
| 197 | echo 0 > /sys/kernel/tracing/trace;\ |
| 198 | echo 0 > /sys/kernel/tracing/events/ext4/enable;\ |
| 199 | echo 0 > /sys/kernel/tracing/events/block/enable'") |
| 200 | |
| 201 | if shouldTraceReads: |
| 202 | run_adb_shell_cmd( |
Jaegeuk Kim | d4ac37c | 2022-08-19 17:22:04 -0700 | [diff] [blame] | 203 | "'echo 1 > /sys/kernel/tracing/events/f2fs/f2fs_dataread_start/enable'" |
Lakshman Annadorai | 3c8f88e | 2022-02-16 16:24:51 -0800 | [diff] [blame] | 204 | ) |
| 205 | |
| 206 | if shouldTraceWrites: |
| 207 | run_adb_shell_cmd( |
Jaegeuk Kim | d4ac37c | 2022-08-19 17:22:04 -0700 | [diff] [blame] | 208 | "'echo 1 > /sys/kernel/tracing/events/f2fs/f2fs_datawrite_start/enable'" |
Lakshman Annadorai | 3c8f88e | 2022-02-16 16:24:51 -0800 | [diff] [blame] | 209 | ) |
| 210 | |
| 211 | run_adb_shell_cmd("'echo 1 > /sys/kernel/tracing/tracing_on'") |
| 212 | |
| 213 | |
| 214 | def clear_tracing(shouldTraceReads, shouldTraceWrites): |
| 215 | if shouldTraceReads: |
| 216 | run_adb_shell_cmd( |
Jaegeuk Kim | d4ac37c | 2022-08-19 17:22:04 -0700 | [diff] [blame] | 217 | "'echo 0 > /sys/kernel/tracing/events/f2fs/f2fs_dataread_start/enable'" |
Lakshman Annadorai | 3c8f88e | 2022-02-16 16:24:51 -0800 | [diff] [blame] | 218 | ) |
| 219 | |
| 220 | if shouldTraceWrites: |
| 221 | run_adb_shell_cmd( |
Jaegeuk Kim | d4ac37c | 2022-08-19 17:22:04 -0700 | [diff] [blame] | 222 | "'echo 0 > /sys/kernel/tracing/events/f2fs/f2fs_datawrite_start/enable'" |
Lakshman Annadorai | 3c8f88e | 2022-02-16 16:24:51 -0800 | [diff] [blame] | 223 | ) |
| 224 | |
| 225 | run_adb_shell_cmd("'echo 0 > /sys/kernel/tracing/tracing_on'") |
| 226 | |
| 227 | |
| 228 | def start_streaming_trace(traceFile): |
| 229 | return run_bg_adb_shell_cmd( |
Jaegeuk Kim | d4ac37c | 2022-08-19 17:22:04 -0700 | [diff] [blame] | 230 | "'cat /sys/kernel/tracing/trace_pipe | grep -e f2fs_data -e f2fs_writepages'\ |
Lakshman Annadorai | 3c8f88e | 2022-02-16 16:24:51 -0800 | [diff] [blame] | 231 | > {}".format(traceFile)) |
| 232 | |
| 233 | |
| 234 | def stop_streaming_trace(sub_proc): |
| 235 | process = psutil.Process(sub_proc.pid) |
| 236 | for child_proc in process.children(recursive=True): |
| 237 | child_proc.kill() |
| 238 | process.kill() |
| 239 | |
| 240 | |
| 241 | class carwatchdog_collection(threading.Thread): |
| 242 | |
| 243 | def __init__(self, traceDuration, samplingInterval): |
| 244 | threading.Thread.__init__(self) |
| 245 | self.traceDuration = traceDuration |
| 246 | self.samplingInterval = samplingInterval |
| 247 | |
| 248 | def run(self): |
| 249 | isBootCompleted = 0 |
| 250 | |
| 251 | while isBootCompleted == 0: |
| 252 | isBootCompleted = run_adb_shell_cmd_strip_output( |
| 253 | "'getprop sys.boot_completed'") |
| 254 | time.sleep(1) |
| 255 | |
| 256 | # Clean up previous state. |
| 257 | run_adb_shell_cmd( |
| 258 | "'dumpsys android.automotive.watchdog.ICarWatchdog/default\ |
| 259 | --stop_perf &>/dev/null'") |
| 260 | |
| 261 | run_adb_shell_cmd( |
| 262 | "'dumpsys android.automotive.watchdog.ICarWatchdog/default \ |
| 263 | --start_perf --max_duration {} --interval {}'".format( |
| 264 | self.traceDuration + WATCHDOG_BUFFER_SECS, self.samplingInterval)) |
| 265 | |
| 266 | |
| 267 | def stop_carwatchdog_collection(outputDir): |
| 268 | run_adb_shell_cmd("'dumpsys android.automotive.watchdog.ICarWatchdog/default" |
| 269 | " --stop_perf' > {}/{}".format(outputDir, CARWATCHDOG_DUMP)) |
| 270 | |
| 271 | |
| 272 | def do_something(outpuDir, traceDuration, samplingInterval, uidProcessMapperObj): |
| 273 | buildChars = run_adb_shell_cmd_strip_output( |
| 274 | "'getprop ro.build.characteristics'") |
| 275 | |
| 276 | carwatchdog_collection_thread = None |
| 277 | if "automotive" in buildChars: |
| 278 | carwatchdog_collection_thread = carwatchdog_collection( |
| 279 | traceDuration, samplingInterval) |
| 280 | carwatchdog_collection_thread.start() |
| 281 | |
| 282 | for i in range(1, traceDuration): |
| 283 | if DID_RECEIVE_SIGINT: |
| 284 | break |
| 285 | now = time.process_time() |
| 286 | read_uid_process_mapping(uidProcessMapperObj) |
| 287 | taken = time.process_time() - now |
| 288 | if (taken < 1): |
| 289 | time.sleep(1 - taken) |
| 290 | |
| 291 | read_uid_package_mapping(uidProcessMapperObj) |
| 292 | |
| 293 | if "automotive" in buildChars: |
| 294 | carwatchdog_collection_thread.join() |
| 295 | stop_carwatchdog_collection(outpuDir) |
| 296 | |
| 297 | |
| 298 | def read_uid_process_mapping(uidProcessMapperObj): |
| 299 | procStatusDump = run_adb_shell_cmd_ignore_err( |
| 300 | "'cat /proc/*/status /proc/*/task/*/status 2> /dev/null'") |
| 301 | |
| 302 | uidProcessMapperObj.parse_proc_status_dump(procStatusDump) |
| 303 | |
| 304 | |
| 305 | def read_uid_package_mapping(uidProcessMapperObj): |
| 306 | packageMappingDump = run_adb_shell_cmd_ignore_err( |
| 307 | "'pm list packages -a -U | sort | uniq'") |
| 308 | |
| 309 | uidProcessMapperObj.parse_uid_package_dump(packageMappingDump) |
| 310 | |
| 311 | |
| 312 | # Parser for "/proc/diskstats". |
| 313 | class DiskStats: |
| 314 | |
| 315 | def __init__(self, readIos, readSectors, writeIos, writeSectors): |
| 316 | self.readIos = readIos |
| 317 | self.readSectors = readSectors |
| 318 | self.writeIos = writeIos |
| 319 | self.writeSectors = writeSectors |
| 320 | |
| 321 | def delta(self, other): |
| 322 | return DiskStats(self.readIos - other.readIos, |
| 323 | self.readSectors - other.readSectors, |
| 324 | self.writeIos - other.writeIos, |
| 325 | self.writeSectors - other.writeSectors) |
| 326 | |
| 327 | def dump(self, shouldDumpReads, shouldDumpWrites, outputFile): |
| 328 | if self.readIos is None or self.readIos is None or self.readIos is None\ |
| 329 | or self.readIos is None: |
| 330 | outputFile.write("Missing disk stats") |
| 331 | return |
| 332 | |
| 333 | if (shouldDumpReads): |
| 334 | outputFile.write("Total dev block reads: {} KB, IOs: {}\n".format( |
| 335 | self.readSectors / 2, self.readIos)) |
| 336 | |
| 337 | if (shouldDumpWrites): |
| 338 | outputFile.write("Total dev block writes: {} KB, IOs: {}\n".format( |
| 339 | self.writeSectors / 2, self.writeIos)) |
| 340 | |
| 341 | |
| 342 | def get_disk_stats(blockDev): |
| 343 | line = run_adb_shell_cmd( |
| 344 | "'cat /proc/diskstats' | fgrep -w {}".format(blockDev)) |
| 345 | matcher = re.compile(RE_DISK_STATS_LINE) |
| 346 | match = matcher.match(line) |
| 347 | |
| 348 | if not match: |
| 349 | return None |
| 350 | |
| 351 | readIos = int(match.group(4)) |
| 352 | readSectors = int(match.group(6)) |
| 353 | writeIos = int(match.group(8)) |
| 354 | writeSectors = int(match.group(10)) |
| 355 | |
| 356 | return DiskStats(readIos, readSectors, writeIos, writeSectors) |
| 357 | |
| 358 | |
| 359 | IoBytes = namedtuple("IoBytes", "rdBytes wrBytes") |
| 360 | |
| 361 | |
| 362 | # Parser for "/proc/uid_io/stats". |
| 363 | class UidIoStats: |
| 364 | |
| 365 | def __init__(self): |
| 366 | self.uidIoStatsReMatcher = re.compile(RE_UID_IO_STATS_LINE) |
| 367 | self.ioBytesByUid = {} # Key: UID, Value: IoBytes |
| 368 | self.totalIoBytes = IoBytes(rdBytes=0, wrBytes=0) |
| 369 | |
| 370 | def parse(self, dump): |
| 371 | totalRdBytes = 0 |
| 372 | totalWrBytes = 0 |
| 373 | for line in dump.split("\n"): |
| 374 | (uid, ioBytes) = self.parse_uid_io_bytes(line) |
| 375 | self.ioBytesByUid[uid] = ioBytes |
| 376 | totalRdBytes += ioBytes.rdBytes |
| 377 | totalWrBytes += ioBytes.wrBytes |
| 378 | |
| 379 | self.totalIoBytes = IoBytes(rdBytes=totalRdBytes, wrBytes=totalWrBytes) |
| 380 | |
| 381 | def parse_uid_io_bytes(self, line): |
| 382 | match = self.uidIoStatsReMatcher.match(line) |
| 383 | if not match: |
| 384 | return None |
| 385 | return (int(match.group(1)), |
| 386 | IoBytes( |
| 387 | rdBytes=(int(match.group(4)) + int(match.group(8))), |
| 388 | wrBytes=(int(match.group(5)) + int(match.group(9))))) |
| 389 | |
| 390 | def delta(self, other): |
| 391 | deltaStats = UidIoStats() |
| 392 | deltaStats.totalIoBytes = IoBytes( |
| 393 | rdBytes=self.totalIoBytes.rdBytes - other.totalIoBytes.rdBytes, |
| 394 | wrBytes=self.totalIoBytes.wrBytes - other.totalIoBytes.wrBytes) |
| 395 | |
| 396 | for uid, ioBytes in self.ioBytesByUid.items(): |
| 397 | if uid not in other.ioBytesByUid: |
| 398 | deltaStats.ioBytesByUid[uid] = ioBytes |
| 399 | continue |
| 400 | otherIoBytes = other.ioBytesByUid[uid] |
| 401 | rdBytes = ioBytes.rdBytes - otherIoBytes.rdBytes if ioBytes.rdBytes > otherIoBytes.rdBytes\ |
| 402 | else 0 |
| 403 | wrBytes = ioBytes.wrBytes - otherIoBytes.wrBytes if ioBytes.wrBytes > otherIoBytes.wrBytes\ |
| 404 | else 0 |
| 405 | deltaStats.ioBytesByUid[uid] = IoBytes(rdBytes=rdBytes, wrBytes=wrBytes) |
| 406 | return deltaStats |
| 407 | |
| 408 | def dumpTotal(self, mode, outputFile): |
| 409 | totalBytes = self.totalIoBytes.wrBytes if mode == "write" else self.totalIoBytes.rdBytes |
| 410 | outputFile.write("Total system-wide {} KB: {}\n".format( |
| 411 | mode, to_kib(totalBytes))) |
| 412 | |
| 413 | def dump(self, uidProcessMapperObj, mode, func, outputFile): |
| 414 | sortedEntries = collections.OrderedDict( |
| 415 | sorted( |
| 416 | self.ioBytesByUid.items(), |
| 417 | key=lambda item: item[1].wrBytes |
| 418 | if mode == "write" else item[1].rdBytes, |
| 419 | reverse=True)) |
| 420 | totalEntries = len(sortedEntries) |
| 421 | for i in range(totalEntries): |
| 422 | uid, ioBytes = sortedEntries.popitem(last=False) |
| 423 | totalBytes = ioBytes.wrBytes if mode == "write" else ioBytes.rdBytes |
| 424 | if totalBytes < androidFsParser.MIN_PID_BYTES: |
| 425 | continue |
| 426 | uidInfo = uidProcessMapperObj.get_uid_info(uid) |
| 427 | outputFile.write("{}, Total {} KB: {}\n".format(uidInfo.to_string(), mode, |
| 428 | to_kib(totalBytes))) |
| 429 | func(uid) |
| 430 | outputFile.write("\n" + ("=" * 100) + "\n") |
| 431 | if i < totalEntries - 1: |
| 432 | outputFile.write("\n") |
| 433 | |
| 434 | |
| 435 | def get_uid_io_stats(): |
| 436 | uidIoStatsDump = run_adb_shell_cmd_strip_output("'cat /proc/uid_io/stats'") |
| 437 | uidIoStats = UidIoStats() |
| 438 | uidIoStats.parse(uidIoStatsDump) |
| 439 | return uidIoStats |
| 440 | |
| 441 | |
| 442 | def to_kib(bytes): |
| 443 | return bytes / 1024 |
| 444 | |
| 445 | |
| 446 | def main(argv): |
| 447 | signal.signal(signal.SIGINT, signal_handler) |
| 448 | |
| 449 | args = init_arguments() |
| 450 | verify_arguments(args) |
| 451 | |
| 452 | run_adb_cmd("root") |
| 453 | buildDesc = run_adb_shell_cmd_strip_output("'getprop ro.build.description'") |
| 454 | blockDev = get_block_dev() |
| 455 | |
| 456 | prep_to_do_something() |
| 457 | setup_tracepoints(args.traceReads, args.traceWrites) |
| 458 | diskStatsBefore = get_disk_stats(blockDev) |
| 459 | uidIoStatsBefore = get_uid_io_stats() |
| 460 | |
| 461 | traceFile = "{}/{}".format(args.outputDir, TEMP_TRACE_FILE) |
| 462 | |
| 463 | startDateTime = datetime.now() |
| 464 | proc = start_streaming_trace(traceFile) |
| 465 | print("Started trace streaming") |
| 466 | |
| 467 | uidProcessMapperObj = uidProcessMapper.UidProcessMapper() |
| 468 | do_something(args.outputDir, args.traceDuration, args.samplingInterval, |
| 469 | uidProcessMapperObj) |
| 470 | |
| 471 | stop_streaming_trace(proc) |
| 472 | endDateTime = datetime.now() |
| 473 | print("Stopped trace streaming") |
| 474 | |
| 475 | clear_tracing(args.traceReads, args.traceWrites) |
| 476 | |
| 477 | diskStatsAfter = get_disk_stats(blockDev) |
| 478 | uidIoStatsAfter = get_uid_io_stats() |
| 479 | diskStatsDelta = diskStatsAfter.delta(diskStatsBefore) |
| 480 | uidIoStatsDelta = uidIoStatsAfter.delta(uidIoStatsBefore) |
| 481 | |
| 482 | print("Completed device side collection") |
| 483 | |
| 484 | writeParser = androidFsParser.AndroidFsParser(androidFsParser.RE_WRITE_START, |
| 485 | uidProcessMapperObj) |
| 486 | readParser = androidFsParser.AndroidFsParser(androidFsParser.RE_READ_START, |
| 487 | uidProcessMapperObj) |
| 488 | with open(traceFile) as file: |
| 489 | for line in file: |
| 490 | if args.traceWrites and writeParser.parse(line): |
| 491 | continue |
| 492 | if args.traceReads: |
| 493 | readParser.parse(line) |
| 494 | |
| 495 | outputFile = open("{}/{}".format(args.outputDir, OUTPUT_FILE), "w") |
| 496 | outputFile.write("Collection datetime: {}, Total duration: {}\n".format( |
| 497 | endDateTime, endDateTime - startDateTime)) |
| 498 | outputFile.write("Build description: {}\n".format(buildDesc)) |
| 499 | outputFile.write( |
| 500 | "Minimum KB per process or UID: {}, Small file KB: {}\n\n".format( |
| 501 | to_kib(androidFsParser.MIN_PID_BYTES), |
| 502 | to_kib(androidFsParser.SMALL_FILE_BYTES))) |
| 503 | |
| 504 | diskStatsDelta.dump(args.traceReads, args.traceWrites, outputFile) |
| 505 | |
| 506 | if args.traceWrites: |
| 507 | uidIoStatsDelta.dumpTotal("write", outputFile) |
| 508 | writeParser.dumpTotal(outputFile) |
| 509 | uidIoStatsDelta.dump(uidProcessMapperObj, "write", |
| 510 | lambda uid: writeParser.dump(uid, outputFile), |
| 511 | outputFile) |
| 512 | |
| 513 | if args.traceWrites and args.traceReads: |
| 514 | outputFile.write("\n\n\n") |
| 515 | |
| 516 | if args.traceReads: |
| 517 | uidIoStatsDelta.dumpTotal("read", outputFile) |
| 518 | readParser.dumpTotal(outputFile) |
| 519 | uidIoStatsDelta.dump(uidProcessMapperObj, "read", |
| 520 | lambda uid: readParser.dump(uid, outputFile), |
| 521 | outputFile) |
| 522 | |
| 523 | outputFile.close() |
| 524 | run_shell_cmd("rm {}/{}".format(args.outputDir, TEMP_TRACE_FILE)) |
| 525 | |
| 526 | |
| 527 | if __name__ == "__main__": |
| 528 | main(sys.argv) |