1#!/usr/bin/python3 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""" 18Tool to parse CarWatchdog's performance stats dump. 19 20To build the parser script run: 21 m perf_stats_parser 22 23To parse a carwatchdog dump text file run: 24 perf_stats_parser -f <cw-dump>.txt -o cw_proto_out.pb 25 26To read a carwatchdog proto file as a json run: 27 pers_stats_parser -r <cw-proto-out>.pb -j 28""" 29import argparse 30import json 31import os 32import re 33import sys 34 35from parser import performancestats_pb2 36from parser import deviceperformancestats_pb2 37from datetime import datetime 38 39BOOT_TIME_REPORT_HEADER = "Boot-time performance report:" 40CUSTOM_COLLECTION_REPORT_HEADER = "Custom performance data report:" 41TOP_N_CPU_TIME_HEADER = "Top N CPU Times:" 42 43DUMP_DATETIME_FORMAT = "%a %b %d %H:%M:%S %Y %Z" 44DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S" 45 46STATS_COLLECTION_PATTERN = "Collection (\d+): <(.+)>" 47PACKAGE_CPU_STATS_PATTERN = "(\d+), (.+), (\d+), (\d+).(\d+)%(, (\d+))?" 48PROCESS_CPU_STATS_PATTERN = "\s+(.+), (\d+), (\d+).(\d+)%(, (\d+))?" 49TOTAL_CPU_TIME_PATTERN = "Total CPU time \\(ms\\): (\d+)" 50TOTAL_IDLE_CPU_TIME_PATTERN = "Total idle CPU time \\(ms\\)/percent: (\d+) / .+" 51CPU_IO_WAIT_TIME_PATTERN = "CPU I/O wait time \\(ms\\)/percent: (\d+) / .+" 52CONTEXT_SWITCHES_PATTERN = "Number of context switches: (\d+)" 53IO_BLOCKED_PROCESSES_PATTERN = "Number of I/O blocked processes/percent: (\d+) / .+" 54 55 56class BuildInformation: 57 def __init__(self): 58 self.fingerprint = None 59 self.brand = None 60 self.product = None 61 self.device = None 62 self.version_release = None 63 self.id = None 64 self.version_incremental = None 65 self.type = None 66 self.tags = None 67 self.sdk = None 68 self.platform_minor = None 69 self.codename = None 70 71 def __repr__(self): 72 return "BuildInformation (fingerprint={}, brand={}, product={}, device={}, " \ 73 "version_release={}, id={}, version_incremental={}, type={}, tags={}, " \ 74 "sdk={}, platform_minor={}, codename={})"\ 75 .format(self.fingerprint, self.brand, self.product, self.device, self.version_release, 76 self.id, self.version_incremental, self.type, self.tags, self.sdk, 77 self.platform_minor, self.codename) 78 79 80class ProcessCpuStats: 81 def __init__(self, command, cpu_time_ms, package_cpu_time_percent, cpu_cycles): 82 self.command = command 83 self.cpu_time_ms = cpu_time_ms 84 self.package_cpu_time_percent = package_cpu_time_percent 85 self.cpu_cycles = cpu_cycles 86 87 def __repr__(self): 88 return "ProcessCpuStats (command={}, CPU time={}ms, percent of " \ 89 "package's CPU time={}%, CPU cycles={})"\ 90 .format(self.command, self.cpu_time_ms, self.package_cpu_time_percent, 91 self.cpu_cycles) 92 93 94class PackageCpuStats: 95 def __init__(self, user_id, package_name, cpu_time_ms, 96 total_cpu_time_percent, cpu_cycles): 97 self.user_id = user_id 98 self.package_name = package_name 99 self.cpu_time_ms = cpu_time_ms 100 self.total_cpu_time_percent = total_cpu_time_percent 101 self.cpu_cycles = cpu_cycles 102 self.process_cpu_stats = [] 103 104 def to_dict(self): 105 return { 106 "user_id": self.user_id, 107 "package_name": self.package_name, 108 "cpu_time_ms": self.cpu_time_ms, 109 "total_cpu_time_percent": self.total_cpu_time_percent, 110 "cpu_cycles": self.cpu_cycles, 111 "process_cpu_stats": [vars(p) for p in self.process_cpu_stats] 112 } 113 114 def __repr__(self): 115 process_cpu_stats_str = "[])" 116 if len(self.process_cpu_stats) > 0: 117 process_list_str = "\n ".join(list(map(repr, self.process_cpu_stats))) 118 process_cpu_stats_str = "\n {}\n )".format(process_list_str) 119 return "PackageCpuStats (user id={}, package name={}, CPU time={}ms, " \ 120 "percent of total CPU time={}%, CPU cycles={}, process CPU stats={}" \ 121 .format(self.user_id, self.package_name, self.cpu_time_ms, 122 self.total_cpu_time_percent, self.cpu_cycles, process_cpu_stats_str) 123 124 125class StatsCollection: 126 def __init__(self): 127 self.id = -1 128 self.date = None 129 self.total_cpu_time_ms = 0 130 self.idle_cpu_time_ms = 0 131 self.io_wait_time_ms = 0 132 self.context_switches = 0 133 self.io_blocked_processes = 0 134 self.package_cpu_stats = [] 135 136 def is_empty(self): 137 val = self.total_cpu_time_ms + self.idle_cpu_time_ms + self.io_wait_time_ms + \ 138 self.context_switches + self.io_blocked_processes 139 return self.id == -1 and not self.date and val == 0 and len(self.package_cpu_stats) == 0 140 141 def to_dict(self): 142 return { 143 "id": self.id, 144 "date": self.date.strftime(DATETIME_FORMAT) if self.date else "", 145 "total_cpu_time_ms": self.total_cpu_time_ms, 146 "idle_cpu_time_ms": self.idle_cpu_time_ms, 147 "io_wait_time_ms": self.io_wait_time_ms, 148 "context_switches": self.context_switches, 149 "io_blocked_processes": self.io_blocked_processes, 150 "packages_cpu_stats": [p.to_dict() for p in self.package_cpu_stats] 151 } 152 153 def __repr__(self): 154 date = self.date.strftime(DATETIME_FORMAT) if self.date else "" 155 pcs_str = "\n ".join(list(map(repr, self.package_cpu_stats))) 156 return "StatsCollection (id={}, date={}, total CPU time={}ms, " \ 157 "idle CPU time={}ms, I/O wait time={}ms, total context switches={}, " \ 158 "total I/O blocked processes={}, package CPU stats=\n {}\n )" \ 159 .format(self.id, date, self.total_cpu_time_ms, self.idle_cpu_time_ms, 160 self.io_wait_time_ms, self.context_switches, 161 self.io_blocked_processes, pcs_str) 162 163 164class SystemEventStats: 165 def __init__(self): 166 self.collections = [] 167 168 def add(self, collection): 169 self.collections.append(collection) 170 171 def is_empty(self): 172 return not any(map(lambda c: not c.is_empty(), self.collections)) 173 174 def to_list(self): 175 return [c.to_dict() for c in self.collections] 176 177 def __repr__(self): 178 collections_str = "\n ".join(list(map(repr, self.collections))) 179 return "SystemEventStats (\n" \ 180 " {}\n)".format(collections_str) 181 182 183class PerformanceStats: 184 def __init__(self): 185 self.boot_time_stats = None 186 self.user_switch_stats = [] 187 self.custom_collection_stats = None 188 189 def has_boot_time(self): 190 return self.boot_time_stats and not self.boot_time_stats.is_empty() 191 192 def has_custom_collection(self): 193 return self.custom_collection_stats \ 194 and not self.custom_collection_stats.is_empty() 195 196 def is_empty(self): 197 return not self.has_boot_time() and not self.has_custom_collection() \ 198 and not any(map(lambda u: not u.is_empty(), self.user_switch_stats)) 199 200 def to_dict(self): 201 return { 202 "boot_time_stats": self.boot_time_stats.to_list() if self.boot_time_stats else None, 203 "user_switch_stats": [u.to_list() for u in self.user_switch_stats], 204 "custom_collection_stats": self.custom_collection_stats.to_list() if self.custom_collection_stats else None, 205 } 206 207 def __repr__(self): 208 return "PerformanceStats (\n" \ 209 "boot-time stats={}\n" \ 210 "\nuser-switch stats={}\n" \ 211 "\ncustom-collection stats={}\n)" \ 212 .format(self.boot_time_stats, self.user_switch_stats, 213 self.custom_collection_stats) 214 215class DevicePerformanceStats: 216 def __init__(self): 217 self.build_info = None 218 self.perf_stats = [] 219 220 def to_dict(self): 221 return { 222 "build_info": vars(self.build_info), 223 "perf_stats": [s.to_dict() for s in self.perf_stats] 224 } 225 226 def __repr__(self): 227 return "DevicePerformanceStats (\n" \ 228 "build_info={}\n" \ 229 "\nperf_stats={}\n)"\ 230 .format(self.build_info, self.perf_stats) 231 232def parse_build_info(build_info_file): 233 build_info = BuildInformation() 234 235 def get_value(line): 236 if ':' not in line: 237 return "" 238 return line.split(':')[1].strip() 239 240 with open(build_info_file, 'r') as f: 241 for line in f.readlines(): 242 value = get_value(line) 243 if line.startswith("fingerprint"): 244 build_info.fingerprint = value 245 elif line.startswith("brand"): 246 build_info.brand = value 247 elif line.startswith("product"): 248 build_info.product = value 249 elif line.startswith("device"): 250 build_info.device = value 251 elif line.startswith("version.release"): 252 build_info.version_release = value 253 elif line.startswith("id"): 254 build_info.id = value 255 elif line.startswith("version.incremental"): 256 build_info.version_incremental = value 257 elif line.startswith("type"): 258 build_info.type = value 259 elif line.startswith("tags"): 260 build_info.tags = value 261 elif line.startswith("sdk"): 262 build_info.sdk = value 263 elif line.startswith("platform minor version"): 264 build_info.platform_minor = value 265 elif line.startswith("codename"): 266 build_info.codename = value 267 268 return build_info 269 270def parse_cpu_times(lines, idx): 271 package_cpu_stats = [] 272 package_cpu_stat = None 273 274 while not (line := lines[idx].rstrip()).startswith("Top N") \ 275 and not re.match(STATS_COLLECTION_PATTERN, line) \ 276 and not line.startswith('-' * 50): 277 if match := re.match(PACKAGE_CPU_STATS_PATTERN, line): 278 user_id = int(match.group(1)) 279 package_name = match.group(2) 280 cpu_time_ms = int(match.group(3)) 281 total_cpu_time_percent = float("{}.{}".format(match.group(4), 282 match.group(5))) 283 cpu_cycles = int(match.group(7)) if match.group(7) is not None else -1 284 285 package_cpu_stat = PackageCpuStats(user_id, package_name, 286 cpu_time_ms, 287 total_cpu_time_percent, 288 cpu_cycles) 289 package_cpu_stats.append(package_cpu_stat) 290 elif match := re.match(PROCESS_CPU_STATS_PATTERN, line): 291 command = match.group(1) 292 cpu_time_ms = int(match.group(2)) 293 package_cpu_time_percent = float("{}.{}".format(match.group(3), 294 match.group(4))) 295 cpu_cycles = int(match.group(6)) if match.group(6) is not None else -1 296 if package_cpu_stat: 297 package_cpu_stat.process_cpu_stats.append( 298 ProcessCpuStats(command, cpu_time_ms, package_cpu_time_percent, cpu_cycles)) 299 else: 300 print("No package CPU stats parsed for process:", command, file=sys.stderr) 301 302 idx += 1 303 304 return package_cpu_stats, idx 305 306 307def parse_collection(lines, idx, match): 308 collection = StatsCollection() 309 collection.id = int(match.group(1)) 310 collection.date = datetime.strptime(match.group(2), DUMP_DATETIME_FORMAT) 311 312 while not re.match(STATS_COLLECTION_PATTERN, 313 (line := lines[idx].strip())) and not line.startswith('-' * 50): 314 if match := re.match(TOTAL_CPU_TIME_PATTERN, line): 315 collection.total_cpu_time_ms = int(match.group(1)) 316 elif match := re.match(TOTAL_IDLE_CPU_TIME_PATTERN, line): 317 collection.idle_cpu_time_ms = int(match.group(1)) 318 elif match := re.match(CPU_IO_WAIT_TIME_PATTERN, line): 319 collection.io_wait_time_ms = int(match.group(1)) 320 elif match := re.match(CONTEXT_SWITCHES_PATTERN, line): 321 collection.context_switches = int(match.group(1)) 322 elif match := re.match(IO_BLOCKED_PROCESSES_PATTERN, line): 323 collection.io_blocked_processes = int(match.group(1)) 324 elif line == TOP_N_CPU_TIME_HEADER: 325 idx += 1 # Skip subsection header 326 package_cpu_stats, idx = parse_cpu_times(lines, idx) 327 collection.package_cpu_stats = package_cpu_stats 328 continue 329 330 idx += 1 331 332 return collection, idx 333 334 335def parse_stats_collections(lines, idx): 336 system_event_stats = SystemEventStats() 337 while not (line := lines[idx].strip()).startswith('-' * 50): 338 if match := re.match(STATS_COLLECTION_PATTERN, line): 339 idx += 1 # Skip the collection header 340 collection, idx = parse_collection(lines, idx, match) 341 if not collection.is_empty(): 342 system_event_stats.add(collection) 343 else: 344 idx += 1 345 return system_event_stats, idx 346 347 348def parse_dump(dump): 349 lines = dump.split("\n") 350 performance_stats = PerformanceStats() 351 idx = 0 352 while idx < len(lines): 353 line = lines[idx].strip() 354 if line == BOOT_TIME_REPORT_HEADER: 355 boot_time_stats, idx = parse_stats_collections(lines, idx) 356 if not boot_time_stats.is_empty(): 357 performance_stats.boot_time_stats = boot_time_stats 358 if line == CUSTOM_COLLECTION_REPORT_HEADER: 359 idx += 2 # Skip the dashed-line after the custom collection header 360 custom_collection_stats, idx = parse_stats_collections(lines, idx) 361 if not custom_collection_stats.is_empty(): 362 performance_stats.custom_collection_stats = custom_collection_stats 363 else: 364 idx += 1 365 366 return performance_stats 367 368 369def create_date_pb(date): 370 date_pb = performancestats_pb2.Date() 371 date_pb.year = date.year 372 date_pb.month = date.month 373 date_pb.day = date.day 374 return date_pb 375 376 377def create_timeofday_pb(date): 378 timeofday_pb = performancestats_pb2.TimeOfDay() 379 timeofday_pb.hours = date.hour 380 timeofday_pb.minutes = date.minute 381 timeofday_pb.seconds = date.second 382 return timeofday_pb 383 384def add_system_event_pb(system_event_stats, system_event_pb): 385 for collection in system_event_stats.collections: 386 stats_collection_pb = system_event_pb.collections.add() 387 stats_collection_pb.id = collection.id 388 stats_collection_pb.date.CopyFrom(create_date_pb(collection.date)) 389 stats_collection_pb.time.CopyFrom(create_timeofday_pb(collection.date)) 390 stats_collection_pb.total_cpu_time_ms = collection.total_cpu_time_ms 391 stats_collection_pb.idle_cpu_time_ms = collection.idle_cpu_time_ms 392 stats_collection_pb.io_wait_time_ms = collection.io_wait_time_ms 393 stats_collection_pb.context_switches = collection.context_switches 394 stats_collection_pb.io_blocked_processes = collection.io_blocked_processes 395 396 for package_cpu_stats in collection.package_cpu_stats: 397 package_cpu_stats_pb = stats_collection_pb.package_cpu_stats.add() 398 package_cpu_stats_pb.user_id = package_cpu_stats.user_id 399 package_cpu_stats_pb.package_name = package_cpu_stats.package_name 400 package_cpu_stats_pb.cpu_time_ms = package_cpu_stats.cpu_time_ms 401 package_cpu_stats_pb.total_cpu_time_percent = package_cpu_stats.total_cpu_time_percent 402 package_cpu_stats_pb.cpu_cycles = package_cpu_stats.cpu_cycles 403 404 for process_cpu_stats in package_cpu_stats.process_cpu_stats: 405 process_cpu_stats_pb = package_cpu_stats_pb.process_cpu_stats.add() 406 process_cpu_stats_pb.command = process_cpu_stats.command 407 process_cpu_stats_pb.cpu_time_ms = process_cpu_stats.cpu_time_ms 408 process_cpu_stats_pb.package_cpu_time_percent = process_cpu_stats.package_cpu_time_percent 409 process_cpu_stats_pb.cpu_cycles = process_cpu_stats.cpu_cycles 410 411def get_system_event(system_event_pb): 412 system_event_stats = SystemEventStats() 413 for stats_collection_pb in system_event_pb.collections: 414 stats_collection = StatsCollection() 415 stats_collection.id = stats_collection_pb.id 416 date_pb = stats_collection_pb.date 417 time_pb = stats_collection_pb.time 418 stats_collection.date = datetime(date_pb.year, date_pb.month, date_pb.day, 419 time_pb.hours, time_pb.minutes, time_pb.seconds) 420 stats_collection.total_cpu_time_ms = stats_collection_pb.total_cpu_time_ms 421 stats_collection.idle_cpu_time_ms = stats_collection_pb.idle_cpu_time_ms 422 stats_collection.io_wait_time_ms = stats_collection_pb.io_wait_time_ms 423 stats_collection.context_switches = stats_collection_pb.context_switches 424 stats_collection.io_blocked_processes = stats_collection_pb.io_blocked_processes 425 426 for package_cpu_stats_pb in stats_collection_pb.package_cpu_stats: 427 package_cpu_stats = \ 428 PackageCpuStats(package_cpu_stats_pb.user_id, 429 package_cpu_stats_pb.package_name, 430 package_cpu_stats_pb.cpu_time_ms, 431 round(package_cpu_stats_pb.total_cpu_time_percent, 2), 432 package_cpu_stats_pb.cpu_cycles) 433 434 for process_cpu_stats_pb in package_cpu_stats_pb.process_cpu_stats: 435 process_cpu_stats = \ 436 ProcessCpuStats(process_cpu_stats_pb.command, 437 process_cpu_stats_pb.cpu_time_ms, 438 round(process_cpu_stats_pb.package_cpu_time_percent, 439 2), 440 process_cpu_stats_pb.cpu_cycles) 441 442 package_cpu_stats.process_cpu_stats.append(process_cpu_stats) 443 stats_collection.package_cpu_stats.append(package_cpu_stats) 444 system_event_stats.add(stats_collection) 445 446 return system_event_stats 447 448def get_perf_stats(perf_stats_pb): 449 perf_stats = PerformanceStats() 450 perf_stats.boot_time_stats = get_system_event(perf_stats_pb.boot_time_stats) 451 perf_stats.custom_collection_stats = get_system_event(perf_stats_pb.custom_collection_stats) 452 return perf_stats 453 454def get_build_info(build_info_pb): 455 build_info = BuildInformation() 456 build_info.fingerprint = build_info_pb.fingerprint 457 build_info.brand = build_info_pb.brand 458 build_info.product = build_info_pb.product 459 build_info.device = build_info_pb.device 460 build_info.version_release = build_info_pb.version_release 461 build_info.id = build_info_pb.id 462 build_info.version_incremental = build_info_pb.version_incremental 463 build_info.type = build_info_pb.type 464 build_info.tags = build_info_pb.tags 465 build_info.sdk = build_info_pb.sdk 466 build_info.platform_minor = build_info_pb.platform_minor 467 build_info.codename = build_info_pb.codename 468 return build_info 469 470def write_pb(perf_stats, out_file, build_info=None, out_build_file=None): 471 if perf_stats.is_empty(): 472 print("Cannot write proto since performance stats are empty") 473 return False 474 475 perf_stats_pb = performancestats_pb2.PerformanceStats() 476 477 # Boot time proto 478 if perf_stats.has_boot_time(): 479 boot_time_stats_pb = performancestats_pb2.SystemEventStats() 480 add_system_event_pb(perf_stats.boot_time_stats, boot_time_stats_pb) 481 perf_stats_pb.boot_time_stats.CopyFrom(boot_time_stats_pb) 482 483 # TODO(b/256654082): Add user switch events to proto 484 485 # Custom collection proto 486 if perf_stats.has_custom_collection(): 487 custom_collection_stats_pb = performancestats_pb2.SystemEventStats() 488 add_system_event_pb(perf_stats.custom_collection_stats, 489 custom_collection_stats_pb) 490 perf_stats_pb.custom_collection_stats.CopyFrom(custom_collection_stats_pb) 491 492 # Write pb binary to disk 493 if out_file: 494 with open(out_file, "wb") as f: 495 f.write(perf_stats_pb.SerializeToString()) 496 497 if build_info is not None: 498 build_info_pb = deviceperformancestats_pb2.BuildInformation() 499 build_info_pb.fingerprint = build_info.fingerprint 500 build_info_pb.brand = build_info.brand 501 build_info_pb.product = build_info.product 502 build_info_pb.device = build_info.device 503 build_info_pb.version_release = build_info.version_release 504 build_info_pb.id = build_info.id 505 build_info_pb.version_incremental = build_info.version_incremental 506 build_info_pb.type = build_info.type 507 build_info_pb.tags = build_info.tags 508 build_info_pb.sdk = build_info.sdk 509 build_info_pb.platform_minor = build_info.platform_minor 510 build_info_pb.codename = build_info.codename 511 512 device_run_perf_stats_pb = deviceperformancestats_pb2.DevicePerformanceStats() 513 device_run_perf_stats_pb.build_info.CopyFrom(build_info_pb) 514 device_run_perf_stats_pb.perf_stats.add().CopyFrom(perf_stats_pb) 515 516 with open(out_build_file, "wb") as f: 517 f.write(device_run_perf_stats_pb.SerializeToString()) 518 519 return True 520 521 522def read_pb(pb_file, is_device_run=False): 523 perf_stats_pb = deviceperformancestats_pb2.DevicePerformanceStats() if \ 524 is_device_run else performancestats_pb2.PerformanceStats() 525 526 with open(pb_file, "rb") as f: 527 try: 528 perf_stats_pb.ParseFromString(f.read()) 529 perf_stats_pb.DiscardUnknownFields() 530 except UnicodeDecodeError: 531 proto_type = "DevicePerformanceStats" if is_device_run else "PerformanceStats" 532 print(f"Error: Proto in {pb_file} probably is not '{proto_type}'") 533 return None 534 535 if not perf_stats_pb: 536 print(f"Error: Proto stored in {pb_file} has incorrect format.") 537 return None 538 539 if not is_device_run: 540 return get_perf_stats(perf_stats_pb) 541 542 device_run_perf_stats = DevicePerformanceStats() 543 device_run_perf_stats.build_info = get_build_info(perf_stats_pb.build_info) 544 545 for perf_stat in perf_stats_pb.perf_stats: 546 device_run_perf_stats.perf_stats.append(get_perf_stats(perf_stat)) 547 548 return device_run_perf_stats 549 550def init_arguments(): 551 parser = argparse.ArgumentParser(description="Parses CarWatchdog's dump.") 552 parser.add_argument("-f", "--file", dest="file", 553 default="dump.txt", 554 help="File with the CarWatchdog dump") 555 parser.add_argument("-o", "--out", dest="out", 556 help="protobuf binary with parsed performance stats") 557 parser.add_argument("-b", "--build", dest="build", 558 help="File with Android device build information") 559 parser.add_argument("-d", "--device-out", dest="device_out", 560 default="device_perf_stats.pb", 561 help="protobuf binary with build information") 562 parser.add_argument("-p", "--print", dest="print", action="store_true", 563 help="prints the parsed performance data to the console " 564 "when out proto defined") 565 parser.add_argument("-r", "--read", dest="read_proto", 566 help="Protobuf binary to be printed in console. If this " 567 "flag is set no other process is executed.") 568 parser.add_argument("-D", "--device-run", dest="device_run", 569 action="store_true", 570 help="Specifies that the proto to be read is a " 571 "DevicePerformanceStats proto. (Only checked if " 572 "-r is set)") 573 parser.add_argument("-j", "--json", dest="json", 574 action="store_true", 575 help="Generate a JSON file from the protobuf binary read.") 576 577 return parser.parse_args() 578 579if __name__ == "__main__": 580 args = init_arguments() 581 582 if args.read_proto: 583 if not os.path.isfile(args.read_proto): 584 print("Error: Proto binary '%s' does not exist" % args.read_proto) 585 sys.exit(1) 586 performance_stats = read_pb(args.read_proto, args.device_run) 587 if performance_stats is None: 588 print(f"Error: Could not read '{args.read_proto}'") 589 sys.exit(1) 590 if args.json: 591 print(json.dumps(performance_stats.to_dict())) 592 else: 593 print("Reading performance stats proto:") 594 print(performance_stats) 595 sys.exit() 596 597 if not os.path.isfile(args.file): 598 print("Error: File '%s' does not exist" % args.file) 599 sys.exit(1) 600 601 with open(args.file, 'r', encoding="UTF-8", errors="ignore") as f: 602 performance_stats = parse_dump(f.read()) 603 604 build_info = None 605 if args.build: 606 build_info = parse_build_info(args.build) 607 print(build_info) 608 609 if performance_stats.is_empty(): 610 print("Error: No performance stats were parsed. Make sure dump file contains carwatchdog's " 611 "dump text.") 612 sys.exit(1) 613 614 if (args.out or args.build) and write_pb(performance_stats, args.out, 615 build_info, args.device_out): 616 out_file = args.out if args.out else args.device_out 617 print("Output protobuf binary in:", out_file) 618 619 if args.print or not (args.out or args.build): 620 if args.json: 621 print(json.dumps(performance_stats.to_dict())) 622 sys.exit() 623 print(performance_stats) 624