1from xnu import ( 2 kern, 3 ArgumentError, 4 unsigned, 5 lldb_command, 6 header, 7 GetEnumValue, 8 GetEnumValues, 9 GetEnumName, 10 GetThreadName, 11 GetProcStartAbsTimeForTask, 12 GetRecentTimestamp, 13 GetProcNameForTask, 14 FindTasksByName, 15 IterateQueue, 16) 17 18 19def validate_args(opts, valid_flags): 20 valid_flags = set(valid_flags) 21 for k in opts.keys(): 22 if k[1:] not in valid_flags: 23 raise ArgumentError("-{} not supported in subcommand".format(k)) 24 25 26@lldb_command("recount", "AF:MT", fancy=True) 27def Recount(cmd_args=None, cmd_options={}, O=None): # noqa: E741 28 """Inspect counters maintained by the Recount subsystem on various resource 29 aggregators, like tasks or threads. 30 31 recount task [-TM] <task_t> [...] | -F <task_name> 32 recount thread [-M] <thread_t> [...] 33 recount coalition [-M] <coalition_t> [...] 34 recount processor [-ATM] [<processor_t-or-cpu-id>] [...] 35 36 Options: 37 -T : break out active threads for a task or processor 38 -M : show times in the Mach timebase 39 -A : show all processors 40 41 Diagnostic macros: 42 recount diagnose task <task_t> 43 - Ensure resource accounting consistency in a task. 44 recount triage 45 - Print out statistics useful for general panic triage. 46 47 """ 48 if cmd_args is None or len(cmd_args) == 0: 49 raise ArgumentError("subcommand required") 50 51 if cmd_args[0] == "coalition": 52 validate_args(cmd_options, ["M"]) 53 RecountCoalition(cmd_args[1:], cmd_options=cmd_options, O=O) 54 elif cmd_args[0] == "task": 55 validate_args(cmd_options, ["F", "M", "T"]) 56 RecountTask(cmd_args[1:], cmd_options=cmd_options, O=O) 57 elif cmd_args[0] == "thread": 58 validate_args(cmd_options, ["M"]) 59 RecountThread(cmd_args[1:], cmd_options=cmd_options, O=O) 60 elif cmd_args[0] == "processor": 61 validate_args(cmd_options, ["A", "M", "T"]) 62 RecountProcessor(cmd_args[1:], cmd_options=cmd_options, O=O) 63 elif cmd_args[0] == "diagnose": 64 RecountDiagnose(cmd_args[1:], cmd_options=cmd_options, O=O) 65 elif cmd_args[0] == "triage": 66 validate_args(cmd_options, []) 67 RecountTriage(cmd_options=cmd_options, O=O) 68 else: 69 raise ArgumentError("{}: invalid subcommand".format(cmd_args[0])) 70 71 72def scale_suffix(val, unit=""): 73 si_units = [ 74 (1e21, "Z"), 75 (1e18, "E"), 76 (1e15, "P"), 77 (1e12, "T"), 78 (1e9, "B"), 79 (1e6, "M"), 80 (1e3, "k"), 81 (1, " "), 82 (1e-3, "m"), 83 (1e-6, "u"), 84 (1e-9, "n"), 85 ] 86 scale, sfx = (1, "") 87 for si_scale, si_sfx in si_units: 88 if val >= si_scale: 89 scale, sfx = (si_scale, si_sfx) 90 break 91 return "{:>7.3f}{:<1s}{}".format(val / scale, sfx, unit) 92 93 94class RecountSum(object): 95 """ 96 Accumulate usage counters. 97 """ 98 99 def __init__(self, mach_times=False): 100 self._mach_times = mach_times 101 self._levels = RecountPlan.levels() 102 self._times_mach = [0] * len(self._levels) 103 self._instructions = [0] * len(self._levels) 104 self._cycles = [0] * len(self._levels) 105 self._energy_nj = 0 106 self._valid_count = 0 107 108 def add_usage(self, usage): 109 for _, level in self._levels: 110 metrics = usage.ru_metrics[level] 111 self._times_mach[level] += unsigned(metrics.rm_time_mach) 112 if hasattr(metrics, "rm_cycles"): 113 self._instructions[level] += unsigned(metrics.rm_instructions) 114 self._cycles[level] += unsigned(metrics.rm_cycles) 115 if unsigned(metrics.rm_cycles) != 0: 116 self._valid_count += 1 117 if hasattr(usage, "ru_energy_nj"): 118 self._energy_nj += unsigned(usage.ru_energy_nj) 119 120 def user_sys_times(self): 121 user_level = GetEnumValue("recount_level_t", "RCT_LVL_USER") 122 user_time = self._times_mach[user_level] 123 return (user_time, sum(self._times_mach) - user_time) 124 125 def div_valid(self, numer, denom): 126 if self._valid_count == 0 or denom == 0: 127 return 0 128 return numer / denom 129 130 def _convert_time(self, time): 131 if self._mach_times: 132 return time 133 return kern.GetNanotimeFromAbstime(time) / 1e9 134 135 def time(self): 136 time = sum(self._times_mach) 137 if self._mach_times: 138 return time 139 return kern.GetNanotimeFromAbstime(time) 140 141 def fmt_args(self): 142 level_args = [ 143 [ 144 level_name, 145 self._convert_time(self._times_mach[level]), 146 scale_suffix(self._cycles[level]), 147 self.div_valid( 148 self._cycles[level], 149 kern.GetNanotimeFromAbstime(self._times_mach[level]), 150 ), 151 scale_suffix(self._instructions[level]), 152 self.div_valid(self._cycles[level], self._instructions[level]), 153 "-", 154 "-", 155 ] 156 for (level_name, level) in RecountPlan.levels() 157 ] 158 159 total_time_ns = kern.GetNanotimeFromAbstime(sum(self._times_mach)) 160 total_cycles = sum(self._cycles) 161 total_insns = sum(self._instructions) 162 power_w = self._energy_nj / total_time_ns if total_time_ns != 0 else 0 163 level_args.append( 164 [ 165 "*", 166 total_time_ns / 1e9, 167 scale_suffix(total_cycles), 168 self.div_valid(total_cycles, total_time_ns), 169 scale_suffix(total_insns), 170 self.div_valid(total_cycles, total_insns), 171 scale_suffix(self._energy_nj / 1e9, "J"), 172 scale_suffix(power_w, "W"), 173 ] 174 ) 175 return level_args 176 177 def fmt_basic_args(self): 178 return [ 179 [ 180 level_name, 181 self._convert_time(self._times_mach[level]), 182 self._cycles[level], 183 self._instructions[level], 184 "-", 185 ] 186 for (level_name, level) in RecountPlan.levels() 187 ] 188 189 190class RecountPlan(object): 191 """ 192 Format tracks and usage according to a plan. 193 """ 194 195 def __init__(self, name, mach_times=False): 196 self._mach_times = mach_times 197 self._group_names = [] 198 self._group_column = None 199 200 plan = kern.GetGlobalVariable("recount_" + name + "_plan") 201 topo = plan.rpl_topo 202 if topo == GetEnumValue("recount_topo_t", "RCT_TOPO_CPU"): 203 self._group_column = "cpu" 204 self._group_count = unsigned(kern.globals.real_ncpus) 205 self._group_names = ["cpu-{}".format(i) for i in range(self._group_count)] 206 elif topo == GetEnumValue("recount_topo_t", "RCT_TOPO_CPU_KIND"): 207 if kern.arch.startswith("arm64"): 208 self._group_column = "cpu-kind" 209 cluster_mask = int(kern.globals.topology_info.cluster_types) 210 self._group_count = bin(cluster_mask).count("1") 211 self._group_names = [ 212 GetEnumName("recount_cpu_kind_t", i)[8:][:4] 213 for i in range(self._group_count) 214 ] 215 else: 216 self._group_count = 1 217 elif topo == GetEnumValue("recount_topo_t", "RCT_TOPO_SYSTEM"): 218 self._group_count = 1 219 else: 220 raise RuntimeError("{}: Unexpected recount topography", topo) 221 222 def time_fmt(self): 223 return "{:>12d}" if self._mach_times else "{:>12.05f}" 224 225 def _usage_fmt(self): 226 prefix = "{n}{{:>6s}} {t} ".format( 227 t=self.time_fmt(), n="{:>8s} " if self._group_column else "" 228 ) 229 return prefix + "{:>8s} {:>7.3g} {:>8s} {:>5.03f} {:>9s} {:>9s}" 230 231 def usages(self, usages): 232 for i in range(self._group_count): 233 yield usages[i] 234 235 def track_usages(self, tracks): 236 for i in range(self._group_count): 237 yield tracks[i].rt_usage 238 239 def usage_header(self): 240 fmt = "{:>6s} {:>12s} {:>8s} {:>7s} {:>8s} {:>5s} {:>9s} {:>9s}".format( # noqa: E501 241 "level", 242 "time", 243 "cycles", 244 "GHz", 245 "insns", 246 "CPI", 247 "energy", 248 "power", 249 ) 250 if self._group_column: 251 fmt = "{:>8s} ".format(self._group_column) + fmt 252 return fmt 253 254 def levels(): 255 names = ["kernel", "user"] 256 levels = list( 257 zip( 258 names, 259 GetEnumValues( 260 "recount_level_t", ["RCT_LVL_" + name.upper() for name in names] 261 ), 262 ) 263 ) 264 try: 265 levels.append(("secure", GetEnumValue("recount_level_t", "RCT_LVL_SECURE"))) 266 except KeyError: 267 # RCT_LVL_SECURE is not defined on this system. 268 pass 269 return levels 270 271 def format_usage(self, usage, name=None, sum=None, O=None): 272 rows = [] 273 274 levels = RecountPlan.levels() 275 total_time = 0 276 total_time_ns = 0 277 total_cycles = 0 278 total_insns = 0 279 for level_name, level in levels: 280 metrics = usage.ru_metrics[level] 281 time = unsigned(metrics.rm_time_mach) 282 time_ns = kern.GetNanotimeFromAbstime(time) 283 total_time_ns += time_ns 284 if not self._mach_times: 285 time = time_ns / 1e9 286 total_time += time 287 if hasattr(metrics, "rm_cycles"): 288 cycles = unsigned(metrics.rm_cycles) 289 total_cycles += cycles 290 freq = cycles / time_ns if time_ns != 0 else 0 291 insns = unsigned(metrics.rm_instructions) 292 total_insns += insns 293 cpi = cycles / insns if insns != 0 else 0 294 else: 295 cycles = 0 296 freq = 0 297 insns = 0 298 cpi = 0 299 rows.append( 300 [ 301 level_name, 302 time, 303 scale_suffix(cycles), 304 freq, 305 scale_suffix(insns), 306 cpi, 307 "-", 308 "-", 309 ] 310 ) 311 312 if hasattr(usage, "ru_energy_nj"): 313 energy_nj = unsigned(usage.ru_energy_nj) 314 if total_time_ns != 0: 315 power_w = energy_nj / total_time_ns 316 else: 317 power_w = 0 318 else: 319 energy_nj = 0 320 power_w = 0 321 if total_insns != 0: 322 total_freq = total_cycles / total_time_ns if total_time_ns != 0 else 0 323 total_cpi = total_cycles / total_insns 324 else: 325 total_freq = 0 326 total_cpi = 0 327 328 rows.append( 329 [ 330 "*", 331 total_time, 332 scale_suffix(total_cycles), 333 total_freq, 334 scale_suffix(total_insns), 335 total_cpi, 336 scale_suffix(energy_nj / 1e9, "J"), 337 scale_suffix(power_w, "W"), 338 ] 339 ) 340 341 if sum: 342 sum.add_usage(usage) 343 344 if self._group_column: 345 for row in rows: 346 row.insert(0, name) 347 348 return [O.format(self._usage_fmt(), *row) for row in rows] 349 350 def format_sum(self, sum, O=None): 351 lines = [] 352 for line in sum.fmt_args(): 353 lines.append(O.format(self._usage_fmt(), "*", *line)) 354 return lines 355 356 def format_usages(self, usages, O=None): # noqa: E741 357 sum = RecountSum(self._mach_times) if self._group_count > 1 else None 358 str = "" 359 for i, usage in enumerate(self.usages(usages)): 360 name = self._group_names[i] if i < len(self._group_names) else None 361 lines = self.format_usage(usage, name=name, sum=sum, O=O) 362 str += "\n".join(lines) + "\n" 363 if sum: 364 str += "\n".join(self.format_sum(sum, O=O)) 365 return str 366 367 def format_tracks(self, tracks, O=None): # noqa: E741 368 sum = RecountSum(self._mach_times) if self._group_count > 1 else None 369 str = "" 370 for i, usage in enumerate(self.track_usages(tracks)): 371 name = self._group_names[i] if i < len(self._group_names) else None 372 lines = self.format_usage(usage, name=name, sum=sum, O=O) 373 str += "\n".join(lines) + "\n" 374 if sum: 375 str += "\n".join(self.format_sum(sum, O=O)) 376 return str 377 378 def sum_usages(self, usages, sum=None): 379 if sum is None: 380 sum = RecountSum(mach_times=self._mach_times) 381 for usage in self.usages(usages): 382 sum.add_usage(usage) 383 return sum 384 385 def sum_tracks(self, tracks, sum=None): 386 if sum is None: 387 sum = RecountSum(mach_times=self._mach_times) 388 for usage in self.track_usages(tracks): 389 sum.add_usage(usage) 390 return sum 391 392 393def GetTaskTerminatedUserSysTime(task): 394 plan = RecountPlan("task_terminated") 395 sum = RecountSum() 396 for usage in plan.usages(task.tk_recount.rtk_terminated): 397 sum.add_usage(usage) 398 return sum.user_sys_times() 399 400 401def GetThreadUserSysTime(thread): 402 plan = RecountPlan("thread") 403 sum = RecountSum() 404 for usage in plan.track_usages(thread.th_recount.rth_lifetime): 405 sum.add_usage(usage) 406 return sum.user_sys_times() 407 408 409def print_threads(plan, thread_ptrs, indent=False, O=None): # noqa: E741 410 for thread_ptr in thread_ptrs: 411 thread = kern.GetValueFromAddress(thread_ptr, "thread_t") 412 print( 413 "{}thread 0x{:x} 0x{:x} {}".format( 414 " " if indent else "", 415 unsigned(thread.thread_id), 416 unsigned(thread), 417 GetThreadName(thread), 418 ) 419 ) 420 with O.table(plan.usage_header(), indent=indent): 421 print(plan.format_tracks(thread.th_recount.rth_lifetime, O=O)) 422 423 424def RecountThread(thread_ptrs, cmd_options={}, indent=False, O=None): # noqa: E741 425 plan = RecountPlan("thread", mach_times="-M" in cmd_options) 426 print_threads(plan, thread_ptrs, indent=indent, O=O) 427 428 429def get_task_age_ns(task): 430 start_abs = GetProcStartAbsTimeForTask(task) 431 if start_abs is not None: 432 return kern.GetNanotimeFromAbstime(GetRecentTimestamp() - start_abs) 433 return None 434 435 436def print_task_description(task): 437 task_name = GetProcNameForTask(task) 438 task_age_ns = get_task_age_ns(task) 439 if task_age_ns is not None: 440 duration_desc = "{:.3f}s".format(task_age_ns / 1e9) 441 else: 442 duration_desc = "-s" 443 print("task 0x{:x} {} ({} old)".format(unsigned(task), task_name, duration_desc)) 444 return task_name 445 446 447def RecountTask(task_ptrs, cmd_options={}, O=None): # noqa: E741 448 if "-F" in cmd_options: 449 tasks = FindTasksByName(cmd_options["-F"]) 450 else: 451 tasks = [kern.GetValueFromAddress(t, "task_t") for t in task_ptrs] 452 mach_times = "-M" in cmd_options 453 plan = RecountPlan("task", mach_times=mach_times) 454 terminated_plan = RecountPlan("task_terminated", mach_times=mach_times) 455 active_threads = "-T" in cmd_options 456 if active_threads: 457 thread_plan = RecountPlan("thread", mach_times=mach_times) 458 for task in tasks: 459 task_name = print_task_description(task) 460 with O.table(plan.usage_header()): 461 print(plan.format_tracks(task.tk_recount.rtk_lifetime, O=O)) 462 if active_threads: 463 threads = [ 464 unsigned(t) 465 for t in IterateQueue(task.threads, "thread *", "task_threads") 466 ] 467 print_threads(thread_plan, threads, indent=True, O=O) 468 print("task (terminated threads) 0x{:x} {}".format(unsigned(task), task_name)) 469 with O.table(terminated_plan.usage_header()): 470 print(terminated_plan.format_usages(task.tk_recount.rtk_terminated, O=O)) 471 472 473def RecountCoalition(coal_ptrs, cmd_options={}, O=None): # noqa: E741 474 plan = RecountPlan("coalition", mach_times="-M" in cmd_options) 475 coals = [kern.GetValueFromAddress(c, "coalition_t") for c in coal_ptrs] 476 for coal in coals: 477 print("coalition 0x{:x} {}".format(unsigned(coal), unsigned(coal.id))) 478 with O.table(plan.usage_header()): 479 print(plan.format_usages(coal.r.co_recount.rco_exited, O=O)) 480 481 482def get_processor(ptr_or_id): 483 ptr_or_id = unsigned(ptr_or_id) 484 if ptr_or_id < 1024: 485 processor_list = kern.GetGlobalVariable("processor_list") 486 current_processor = processor_list 487 while unsigned(current_processor) > 0: 488 if unsigned(current_processor.cpu_id) == ptr_or_id: 489 return current_processor 490 current_processor = current_processor.processor_list 491 raise ArgumentError("no processor found with CPU ID {}".format(ptr_or_id)) 492 else: 493 return kern.GetValueFromAddress(ptr_or_id, "processor_t") 494 495 496def get_all_processors(): 497 processors = [] 498 processor_list = kern.GetGlobalVariable("processor_list") 499 current_processor = processor_list 500 while unsigned(current_processor) > 0: 501 processors.append(current_processor) 502 current_processor = current_processor.processor_list 503 return sorted(processors, key=lambda p: p.cpu_id) 504 505 506def RecountProcessor(pr_ptrs_or_ids, cmd_options={}, O=None): # noqa: E741 507 mach_times = "-M" in cmd_options 508 plan = RecountPlan("processor", mach_times=mach_times) 509 if "-A" in cmd_options: 510 prs = get_all_processors() 511 else: 512 prs = [get_processor(p) for p in pr_ptrs_or_ids] 513 active_threads = "-T" in cmd_options 514 if active_threads: 515 thread_plan = RecountPlan("thread", mach_times=mach_times) 516 hdr_prefix = "{:>18s} {:>4s} {:>4s} ".format( 517 "processor", 518 "cpu", 519 "kind", 520 ) 521 header_fmt = " {:>12s} {:>12s} {:>8s}" 522 hdr_suffix = header_fmt.format("idle-time", "total-time", "idle-pct") 523 null_suffix = header_fmt.format("-", "-", "-") 524 levels = RecountPlan.levels() 525 with O.table(hdr_prefix + plan.usage_header() + hdr_suffix): 526 for pr in prs: 527 usage = pr.pr_recount.rpr_active.rt_usage 528 idle_time = pr.pr_recount.rpr_idle_time_mach 529 times = [usage.ru_metrics[i].rm_time_mach for (_, i) in levels] 530 total_time = sum(times) + idle_time 531 if not mach_times: 532 idle_time = kern.GetNanotimeFromAbstime(idle_time) / 1e9 533 total_time = kern.GetNanotimeFromAbstime(total_time) / 1e9 534 pset = pr.processor_set 535 cluster_kind = "SMP" 536 if unsigned(pset.pset_cluster_type) != 0: 537 cluster_kind = GetEnumName( 538 "pset_cluster_type_t", pset.pset_cluster_type, "PSET_AMP_" 539 ) 540 prefix = "{:<#018x} {:>4d} {:>4s} ".format( 541 unsigned(pr), pr.cpu_id, cluster_kind 542 ) 543 suffix = ( 544 " " 545 + plan.time_fmt().format(idle_time) 546 + " " 547 + plan.time_fmt().format(total_time) 548 + " {:>7.2f}%".format(idle_time / total_time * 100) 549 ) 550 usage_lines = plan.format_usage(usage, O=O) 551 for i, line in enumerate(usage_lines): 552 line_suffix = null_suffix 553 if i + 1 == len(usage_lines): 554 line_suffix = suffix 555 O.write(prefix + line + line_suffix + "\n") 556 if active_threads: 557 active_thread = unsigned(pr.active_thread) 558 if active_thread != 0: 559 print_threads(thread_plan, [active_thread], indent=True, O=O) 560 561 562@header("{:>4s} {:>20s} {:>20s} {:>20s}".format("cpu", "time-mach", "cycles", "insns")) 563def GetRecountSnapshot(cpu, snap, O=None): 564 (insns, cycles) = (0, 0) 565 if hasattr(snap, "rsn_cycles"): 566 (insns, cycles) = (snap.rsn_insns, snap.rsn_cycles) 567 return O.format( 568 "{:4d} {:20d} {:20d} {:20d}", cpu, snap.rsn_time_mach, cycles, insns 569 ) 570 571 572def GetRecountProcessorState(pr): 573 state_time = pr.pr_recount.rpr_state_last_abs_time 574 state = state_time >> 63 575 return ( 576 pr.pr_recount.rpr_snap, 577 "I" if state == 1 else "A", 578 state_time & ~(0x1 << 63), 579 ) 580 581 582@header( 583 "{:>20s} {:>4s} {:>6s} {:>18s} {:>18s} {:>18s} {:>18s} {:>18s}".format( 584 "processor", 585 "cpu", 586 "state", 587 "last-idle-change", 588 "last-user-change", 589 "last-disp", 590 "since-idle-change", 591 "since-user-change", 592 ) 593) 594def GetRecountProcessorDiagnostics(pr, cur_time, O=None): 595 (snap, state, time) = GetRecountProcessorState(pr) 596 cpu_id = unsigned(pr.cpu_id) 597 last_usrchg = snap.rsn_time_mach 598 since_usrchg = cur_time - last_usrchg 599 last_disp = "{}{:>d}".format( 600 "*" if cur_time == unsigned(pr.last_dispatch) else "", pr.last_dispatch 601 ) 602 return O.format( 603 "{:>#20x} {:4d} {:>6s} {:>18d} {:>18d} {:>18s} {:>18d} {:>18d}", 604 unsigned(pr), 605 cpu_id, 606 state, 607 time, 608 last_usrchg, 609 last_disp, 610 cur_time - time, 611 since_usrchg, 612 ) 613 614 615@header( 616 "{:>12s} {:>6s} {:>12s} {:>20s} {:>20s}".format( 617 "group", "level", "time", "cycles", "insns" 618 ) 619) 620def RecountDiagnoseTask(task_ptrs, cmd_options={}, O=None): # noqa: E74 621 if "-F" in cmd_options: 622 tasks = FindTasksByName(cmd_options["-F"]) 623 else: 624 tasks = [kern.GetValueFromAddress(t, "task_t") for t in task_ptrs] 625 626 line_fmt = "{:20s} = {:10.3f}" 627 row_fmt = "{:>12s} {:>6s} {:>12.3f} {:>20d} {:>20d}" 628 629 task_plan = RecountPlan("task", mach_times=False) 630 term_plan = RecountPlan("task_terminated", mach_times=False) 631 for task in tasks: 632 print_task_description(task) 633 with O.table(RecountDiagnoseTask.header): 634 task_sum = task_plan.sum_tracks(task.tk_recount.rtk_lifetime) 635 for line in task_sum.fmt_basic_args(): 636 line = line[:-1] 637 print(O.format(row_fmt, "task", *line)) 638 639 term_sum = term_plan.sum_usages(task.tk_recount.rtk_terminated) 640 for line in term_sum.fmt_basic_args(): 641 print(O.format(row_fmt, "terminated", *line)) 642 term_sum_ns = term_sum.time() 643 644 threads_sum = RecountSum(mach_times=True) 645 threads_time_mach = threads_sum.time() 646 for thread in IterateQueue(task.threads, "thread *", "task_threads"): 647 usr_time, sys_time = GetThreadUserSysTime(thread) 648 threads_time_mach += usr_time + sys_time 649 650 threads_sum_ns = kern.GetNanotimeFromAbstime(threads_time_mach) 651 print(line_fmt.format("threads CPU", threads_sum_ns / 1e9)) 652 653 all_threads_sum_ns = threads_sum_ns + term_sum_ns 654 print(line_fmt.format("all threads CPU", all_threads_sum_ns / 1e9)) 655 656 print(line_fmt.format("discrepancy", task_sum.time() - all_threads_sum_ns)) 657 658 659def RecountDiagnose(cmd_args=[], cmd_options={}, O=None): # noqa: E741 660 if cmd_args is None or len(cmd_args) == 0: 661 raise ArgumentError("diagnose subcommand required") 662 663 if cmd_args[0] == "task": 664 validate_args(cmd_options, ["F"]) 665 RecountDiagnoseTask(cmd_args[1:], cmd_options=cmd_options, O=O) 666 else: 667 raise ArgumentError("{}: invalid diagnose subcommand".format(cmd_args[0])) 668 669 670def RecountTriage(cmd_options={}, O=None): # noqa: E741 671 prs = get_all_processors() 672 print("processors") 673 with O.table(GetRecountProcessorDiagnostics.header, indent=True): 674 max_dispatch = max([unsigned(pr.last_dispatch) for pr in prs]) 675 for pr in prs: 676 print(GetRecountProcessorDiagnostics(pr, cur_time=max_dispatch, O=O)) 677 678 print("snapshots") 679 with O.table(GetRecountSnapshot.header, indent=True): 680 for i, pr in enumerate(prs): 681 print(GetRecountSnapshot(i, pr.pr_recount.rpr_snap, O=O)) 682