1 #!/usr/bin/env python3 2 # SPDX-License-Identifier: GPL-2.0 3 # 4 # Run a perf script command multiple times in 5 # options --cpu and --time so that each job pr 6 # of the data. 7 # 8 # Copyright (c) 2024, Intel Corporation. 9 10 import subprocess 11 import argparse 12 import pathlib 13 import shlex 14 import time 15 import copy 16 import sys 17 import os 18 import re 19 20 glb_prog_name = "parallel-perf.py" 21 glb_min_interval = 10.0 22 glb_min_samples = 64 23 24 class Verbosity(): 25 26 def __init__(self, quiet=False, verbos 27 self.normal = True 28 self.verbose = verbose 29 self.debug = debug 30 self.self_test = True 31 if self.debug: 32 self.verbose = True 33 if self.verbose: 34 quiet = False 35 if quiet: 36 self.normal = False 37 38 # Manage work (Start/Wait/Kill), as represente 39 class Work(): 40 41 def __init__(self, cmd, pipe_to, outpu 42 self.popen = None 43 self.consumer = None 44 self.cmd = cmd 45 self.pipe_to = pipe_to 46 self.output_dir = output_dir 47 self.cmdout_name = f"{output_d 48 self.stdout_name = f"{output_d 49 self.stderr_name = f"{output_d 50 51 def Command(self): 52 sh_cmd = [ shlex.quote(x) for 53 return " ".join(self.cmd) 54 55 def Stdout(self): 56 return open(self.stdout_name, 57 58 def Stderr(self): 59 return open(self.stderr_name, 60 61 def CreateOutputDir(self): 62 pathlib.Path(self.output_dir). 63 64 def Start(self): 65 if self.popen: 66 return 67 self.CreateOutputDir() 68 with open(self.cmdout_name, "w 69 f.write(self.Command() 70 f.write("\n") 71 stdout = self.Stdout() 72 stderr = self.Stderr() 73 if self.pipe_to: 74 self.popen = subproces 75 args = shlex.split(sel 76 self.consumer = subpro 77 else: 78 self.popen = subproces 79 80 def RemoveEmptyErrFile(self): 81 if os.path.exists(self.stderr_ 82 if os.path.getsize(sel 83 os.unlink(self 84 85 def Errors(self): 86 if os.path.exists(self.stderr_ 87 if os.path.getsize(sel 88 return [ f"Non 89 return [] 90 91 def TidyUp(self): 92 self.RemoveEmptyErrFile() 93 94 def RawPollWait(self, p, wait): 95 if wait: 96 return p.wait() 97 return p.poll() 98 99 def Poll(self, wait=False): 100 if not self.popen: 101 return None 102 result = self.RawPollWait(self 103 if self.consumer: 104 res = result 105 result = self.RawPollW 106 if result != None and 107 self.popen.kil 108 result = None 109 elif result == 0 and r 110 result = res 111 if result != None: 112 self.TidyUp() 113 return result 114 115 def Wait(self): 116 return self.Poll(wait=True) 117 118 def Kill(self): 119 if not self.popen: 120 return 121 self.popen.kill() 122 if self.consumer: 123 self.consumer.kill() 124 125 def KillWork(worklist, verbosity): 126 for w in worklist: 127 w.Kill() 128 for w in worklist: 129 w.Wait() 130 131 def NumberOfCPUs(): 132 return os.sysconf("SC_NPROCESSORS_ONLN 133 134 def NanoSecsToSecsStr(x): 135 if x == None: 136 return "" 137 x = str(x) 138 if len(x) < 10: 139 x = "0" * (10 - len(x)) + x 140 return x[:len(x) - 9] + "." + x[-9:] 141 142 def InsertOptionAfter(cmd, option, after): 143 try: 144 pos = cmd.index(after) 145 cmd.insert(pos + 1, option) 146 except: 147 cmd.append(option) 148 149 def CreateWorkList(cmd, pipe_to, output_dir, c 150 max_len = len(str(cpus[-1])) 151 cpu_dir_fmt = f"cpu-%.{max_len}u" 152 worklist = [] 153 pos = 0 154 for cpu in cpus: 155 if cpu >= 0: 156 cpu_dir = os.path.join 157 cpu_option = f"--cpu={ 158 else: 159 cpu_dir = output_dir 160 cpu_option = None 161 162 tr_dir_fmt = "time-range" 163 164 if len(time_ranges_by_cpu) > 1 165 time_ranges = time_ran 166 tr_dir_fmt += f"-{pos} 167 pos += 1 168 else: 169 time_ranges = time_ran 170 171 max_len = len(str(len(time_ran 172 tr_dir_fmt += f"-%.{max_len}u" 173 174 i = 0 175 for r in time_ranges: 176 if r == [None, None]: 177 time_option = 178 work_output_di 179 else: 180 time_option = 181 work_output_di 182 i += 1 183 work_cmd = list(cmd) 184 if time_option != None 185 InsertOptionAf 186 if cpu_option != None: 187 InsertOptionAf 188 w = Work(work_cmd, pip 189 worklist.append(w) 190 return worklist 191 192 def DoRunWork(worklist, nr_jobs, verbosity): 193 nr_to_do = len(worklist) 194 not_started = list(worklist) 195 running = [] 196 done = [] 197 chg = False 198 while True: 199 nr_done = len(done) 200 if chg and verbosity.normal: 201 nr_run = len(running) 202 print(f"\rThere are {n 203 if verbosity.verbose: 204 print() 205 chg = False 206 if nr_done == nr_to_do: 207 break 208 while len(running) < nr_jobs a 209 w = not_started.pop(0) 210 running.append(w) 211 if verbosity.verbose: 212 print("Startin 213 w.Start() 214 chg = True 215 if len(running): 216 time.sleep(0.1) 217 finished = [] 218 not_finished = [] 219 while len(running): 220 w = running.pop(0) 221 r = w.Poll() 222 if r == None: 223 not_finished.a 224 continue 225 if r == 0: 226 if verbosity.v 227 print( 228 finished.appen 229 chg = True 230 continue 231 if verbosity.normal an 232 print() 233 print("Job failed!\n 234 if w.pipe_to: 235 print(" pip 236 print("Killing outstan 237 KillWork(not_finished, 238 KillWork(running, verb 239 return False 240 running = not_finished 241 done += finished 242 errorlist = [] 243 for w in worklist: 244 errorlist += w.Errors() 245 if len(errorlist): 246 print("Errors:") 247 for e in errorlist: 248 print(e) 249 elif verbosity.normal: 250 print("\r"," "*50, "\rAll jobs 251 return True 252 253 def RunWork(worklist, nr_jobs=NumberOfCPUs(), 254 try: 255 return DoRunWork(worklist, nr_ 256 except: 257 for w in worklist: 258 w.Kill() 259 raise 260 return True 261 262 def ReadHeader(perf, file_name): 263 return subprocess.Popen([perf, "script 264 265 def ParseHeader(hdr): 266 result = {} 267 lines = hdr.split("\n") 268 for line in lines: 269 if ":" in line and line[0] == 270 pos = line.index(":") 271 name = line[1:pos-1].s 272 value = line[pos+1:].s 273 if name in result: 274 orig_name = na 275 nr = 2 276 while True: 277 name = 278 if nam 279 280 nr += 281 result[name] = value 282 return result 283 284 def HeaderField(hdr_dict, hdr_fld): 285 if hdr_fld not in hdr_dict: 286 raise Exception(f"'{hdr_fld}' 287 return hdr_dict[hdr_fld] 288 289 # Represent the position of an option within a 290 # and provide the option value and/or remove t 291 class OptPos(): 292 293 def Init(self, opt_element=-1, value_e 294 self.opt_element = opt_element 295 self.value_element = value_ele 296 self.opt_pos = opt_pos 297 self.value_pos = value_pos 298 self.error = error 299 300 def __init__(self, args, short_name, l 301 self.args = list(args) 302 self.default = default 303 n = 2 + len(long_name) 304 m = len(short_name) 305 pos = -1 306 for opt in args: 307 pos += 1 308 if m and opt[:2] == f" 309 if len(opt) == 310 if pos 311 312 else: 313 314 else: 315 self.I 316 return 317 if opt[:n] == f"--{lon 318 if len(opt) == 319 if pos 320 321 else: 322 323 elif opt[n] == 324 self.I 325 else: 326 self.I 327 return 328 if m and opt[:1] == "- 329 ipos = opt.ind 330 if "-" in opt[ 331 hpos = 332 if hpo 333 334 if ipos + 1 == 335 if pos 336 337 else: 338 339 else: 340 self.I 341 return 342 self.Init() 343 344 def Value(self): 345 if self.opt_element >= 0: 346 if self.opt_element != 347 return self.ar 348 else: 349 return self.ar 350 return self.default 351 352 def Remove(self, args): 353 if self.opt_element == -1: 354 return 355 if self.opt_element != self.va 356 del args[self.value_el 357 if self.opt_pos: 358 args[self.opt_element] 359 else: 360 del args[self.opt_elem 361 362 def DetermineInputFileName(cmd): 363 p = OptPos(cmd, "i", "input", "perf.da 364 if p.error: 365 raise Exception(f"perf command 366 file_name = p.Value() 367 if not os.path.exists(file_name): 368 raise Exception(f"perf command 369 return file_name 370 371 def ReadOption(args, short_name, long_name, er 372 p = OptPos(args, short_name, long_name 373 if p.error: 374 raise Exception(f"{err_prefix} 375 value = p.Value() 376 if remove: 377 p.Remove(args) 378 return value 379 380 def ExtractOption(args, short_name, long_name, 381 return ReadOption(args, short_name, lo 382 383 def ReadPerfOption(args, short_name, long_name 384 return ReadOption(args, short_name, lo 385 386 def ExtractPerfOption(args, short_name, long_n 387 return ExtractOption(args, short_name, 388 389 def PerfDoubleQuickCommands(cmd, file_name): 390 cpu_str = ReadPerfOption(cmd, "C", "cp 391 time_str = ReadPerfOption(cmd, "", "ti 392 # Use double-quick sampling to determi 393 times_cmd = ["perf", "script", "--ns", 394 if cpu_str != None and cpu_str != "": 395 times_cmd.append(f"--cpu={cpu_ 396 if time_str != None and time_str != "" 397 times_cmd.append(f"--time={tim 398 cnts_cmd = list(times_cmd) 399 cnts_cmd.append("-Fcpu") 400 times_cmd.append("-Fcpu,time") 401 return cnts_cmd, times_cmd 402 403 class CPUTimeRange(): 404 def __init__(self, cpu): 405 self.cpu = cpu 406 self.sample_cnt = 0 407 self.time_ranges = None 408 self.interval = 0 409 self.interval_remaining = 0 410 self.remaining = 0 411 self.tr_pos = 0 412 413 def CalcTimeRangesByCPU(line, cpu, cpu_time_ra 414 cpu_time_range = cpu_time_ranges[cpu] 415 cpu_time_range.remaining -= 1 416 cpu_time_range.interval_remaining -= 1 417 if cpu_time_range.remaining == 0: 418 cpu_time_range.time_ranges[cpu 419 return 420 if cpu_time_range.interval_remaining = 421 time = TimeVal(line[1][:-1], 0 422 time_ranges = cpu_time_range.t 423 time_ranges[cpu_time_range.tr_ 424 time_ranges.append([time, max_ 425 cpu_time_range.tr_pos += 1 426 cpu_time_range.interval_remain 427 428 def CountSamplesByCPU(line, cpu, cpu_time_rang 429 try: 430 cpu_time_ranges[cpu].sample_cn 431 except: 432 print("exception") 433 print("cpu", cpu) 434 print("len(cpu_time_ranges)", 435 raise 436 437 def ProcessCommandOutputLines(cmd, per_cpu, fn 438 # Assume CPU number is at beginning of 439 pat = re.compile(r"\s*\[[0-9]+\]") 440 p = subprocess.Popen(cmd, stdout=subpr 441 while True: 442 line = p.stdout.readline() 443 if line: 444 line = line.decode("ut 445 if pat.match(line): 446 line = line.sp 447 if per_cpu: 448 # Assu 449 cpu = 450 else: 451 cpu = 452 fn(line, cpu, 453 else: 454 break 455 p.wait() 456 457 def IntersectTimeRanges(new_time_ranges, time_ 458 pos = 0 459 new_pos = 0 460 # Can assume len(time_ranges) != 0 and 461 # Note also, there *must* be at least 462 while pos < len(time_ranges) and new_p 463 # new end < old start => no in 464 if new_time_ranges[new_pos][1] 465 del new_time_ranges[ne 466 continue 467 # new start > old end => no in 468 if new_time_ranges[new_pos][0] 469 pos += 1 470 if pos < len(time_rang 471 continue 472 # no next, so remove r 473 while new_pos < len(ne 474 del new_time_r 475 return 476 # Found an intersection 477 # new start < old start => adj 478 if new_time_ranges[new_pos][0] 479 new_time_ranges[new_po 480 # new end > old end => keep th 481 if new_time_ranges[new_pos][1] 482 r = [ time_ranges[pos] 483 new_time_ranges[new_po 484 new_pos += 1 485 new_time_ranges.insert 486 continue 487 # new [start, end] is within o 488 new_pos += 1 489 490 def SplitTimeRangesByTraceDataDensity(time_ran 491 if verbosity.normal: 492 print("\rAnalyzing...", flush= 493 if verbosity.verbose: 494 print() 495 cnts_cmd, times_cmd = PerfDoubleQuickC 496 497 nr_cpus = cpus[-1] + 1 if per_cpu else 498 if per_cpu: 499 nr_cpus = cpus[-1] + 1 500 cpu_time_ranges = [ CPUTimeRan 501 else: 502 nr_cpus = 1 503 cpu_time_ranges = [ CPUTimeRan 504 505 if verbosity.debug: 506 print("nr_cpus", nr_cpus) 507 print("cnts_cmd", cnts_cmd) 508 print("times_cmd", times_cmd) 509 510 # Count the number of "double quick" s 511 ProcessCommandOutputLines(cnts_cmd, pe 512 513 tot = 0 514 mx = 0 515 for cpu_time_range in cpu_time_ranges: 516 cnt = cpu_time_range.sample_cn 517 tot += cnt 518 if cnt > mx: 519 mx = cnt 520 if verbosity.debug: 521 print("cpu:", cpu_time 522 523 if min_size < 1: 524 min_size = 1 525 526 if mx < min_size: 527 # Too little data to be worth 528 if verbosity.debug: 529 print("Too little data 530 if nr == 0: 531 nr = 1 532 return [ SplitTimeRangesIntoN( 533 534 if nr: 535 divisor = nr 536 min_size = 1 537 else: 538 divisor = NumberOfCPUs() 539 540 interval = int(round(tot / divisor, 0) 541 if interval < min_size: 542 interval = min_size 543 544 if verbosity.debug: 545 print("divisor", divisor) 546 print("min_size", min_size) 547 print("interval", interval) 548 549 min_time = time_ranges[0][0] 550 max_time = time_ranges[-1][1] 551 552 for cpu_time_range in cpu_time_ranges: 553 cnt = cpu_time_range.sample_cn 554 if cnt == 0: 555 cpu_time_range.time_ra 556 continue 557 # Adjust target interval for C 558 # Determine number of interval 559 n = int(round(cnt / interval, 560 if n < 1: 561 n = 1 562 # Determine interval size, rou 563 d, m = divmod(cnt, n) 564 if m: 565 d += 1 566 cpu_time_range.interval = d 567 cpu_time_range.interval_remain 568 cpu_time_range.remaining = cnt 569 # Init. time ranges for each C 570 cpu_time_range.time_ranges = [ 571 572 # Set time ranges so that the same num 573 # will fall into each time range. 574 ProcessCommandOutputLines(times_cmd, p 575 576 for cpu_time_range in cpu_time_ranges: 577 if cpu_time_range.sample_cnt: 578 IntersectTimeRanges(cp 579 580 return [cpu_time_ranges[cpu].time_rang 581 582 def SplitSingleTimeRangeIntoN(time_range, n): 583 if n <= 1: 584 return [time_range] 585 start = time_range[0] 586 end = time_range[1] 587 duration = int((end - start + 1) / n) 588 if duration < 1: 589 return [time_range] 590 time_ranges = [] 591 for i in range(n): 592 time_ranges.append([start, sta 593 start += duration 594 time_ranges[-1][1] = end 595 return time_ranges 596 597 def TimeRangeDuration(r): 598 return r[1] - r[0] + 1 599 600 def TotalDuration(time_ranges): 601 duration = 0 602 for r in time_ranges: 603 duration += TimeRangeDuration( 604 return duration 605 606 def SplitTimeRangesByInterval(time_ranges, int 607 new_ranges = [] 608 for r in time_ranges: 609 duration = TimeRangeDuration(r 610 n = duration / interval 611 n = int(round(n, 0)) 612 new_ranges += SplitSingleTimeR 613 return new_ranges 614 615 def SplitTimeRangesIntoN(time_ranges, n, min_i 616 if n <= len(time_ranges): 617 return time_ranges 618 duration = TotalDuration(time_ranges) 619 interval = duration / n 620 if interval < min_interval: 621 interval = min_interval 622 return SplitTimeRangesByInterval(time_ 623 624 def RecombineTimeRanges(tr): 625 new_tr = copy.deepcopy(tr) 626 n = len(new_tr) 627 i = 1 628 while i < len(new_tr): 629 # if prev end + 1 == cur start 630 if new_tr[i - 1][1] + 1 == new 631 new_tr[i][0] = new_tr[ 632 del new_tr[i - 1] 633 else: 634 i += 1 635 return new_tr 636 637 def OpenTimeRangeEnds(time_ranges, min_time, m 638 if time_ranges[0][0] <= min_time: 639 time_ranges[0][0] = None 640 if time_ranges[-1][1] >= max_time: 641 time_ranges[-1][1] = None 642 643 def BadTimeStr(time_str): 644 raise Exception(f"perf command bad tim 645 646 def ValidateTimeRanges(time_ranges, time_str): 647 n = len(time_ranges) 648 for i in range(n): 649 start = time_ranges[i][0] 650 end = time_ranges[i][1] 651 if i != 0 and start <= time_ra 652 BadTimeStr(time_str) 653 if start > end: 654 BadTimeStr(time_str) 655 656 def TimeVal(s, dflt): 657 s = s.strip() 658 if s == "": 659 return dflt 660 a = s.split(".") 661 if len(a) > 2: 662 raise Exception(f"Bad time val 663 x = int(a[0]) 664 if x < 0: 665 raise Exception("Negative time 666 x *= 1000000000 667 if len(a) > 1: 668 x += int((a[1] + "000000000")[ 669 return x 670 671 def BadCPUStr(cpu_str): 672 raise Exception(f"perf command bad cpu 673 674 def ParseTimeStr(time_str, min_time, max_time) 675 if time_str == None or time_str == "": 676 return [[min_time, max_time]] 677 time_ranges = [] 678 for r in time_str.split(): 679 a = r.split(",") 680 if len(a) != 2: 681 BadTimeStr(time_str) 682 try: 683 start = TimeVal(a[0], 684 end = TimeVal(a[1], 685 except: 686 BadTimeStr(time_str) 687 time_ranges.append([start, end 688 ValidateTimeRanges(time_ranges, time_s 689 return time_ranges 690 691 def ParseCPUStr(cpu_str, nr_cpus): 692 if cpu_str == None or cpu_str == "": 693 return [-1] 694 cpus = [] 695 for r in cpu_str.split(","): 696 a = r.split("-") 697 if len(a) < 1 or len(a) > 2: 698 BadCPUStr(cpu_str) 699 try: 700 start = int(a[0].strip 701 if len(a) > 1: 702 end = int(a[1] 703 else: 704 end = start 705 except: 706 BadCPUStr(cpu_str) 707 if start < 0 or end < 0 or end 708 BadCPUStr(cpu_str) 709 cpus.extend(range(start, end + 710 cpus = list(set(cpus)) # Remove duplic 711 cpus.sort() 712 return cpus 713 714 class ParallelPerf(): 715 716 def __init__(self, a): 717 for arg_name in vars(a): 718 setattr(self, arg_name 719 self.orig_nr = self.nr 720 self.orig_cmd = list(self.cmd) 721 self.perf = self.cmd[0] 722 if os.path.exists(self.output_ 723 raise Exception(f"Outp 724 if self.jobs < 0 or self.nr < 725 raise Exception("Bad o 726 if self.nr != 0 and self.inter 727 raise Exception("Canno 728 if self.jobs == 0: 729 self.jobs = NumberOfCP 730 if self.nr == 0 and self.inter 731 if self.per_cpu: 732 self.nr = 1 733 else: 734 self.nr = self 735 736 def Init(self): 737 if self.verbosity.debug: 738 print("cmd", self.cmd) 739 self.file_name = DetermineInpu 740 self.hdr = ReadHeader(self.per 741 self.hdr_dict = ParseHeader(se 742 self.cmd_line = HeaderField(se 743 744 def ExtractTimeInfo(self): 745 self.min_time = TimeVal(Header 746 self.max_time = TimeVal(Header 747 self.time_str = ExtractPerfOpt 748 self.time_ranges = ParseTimeSt 749 if self.verbosity.debug: 750 print("time_ranges", s 751 752 def ExtractCPUInfo(self): 753 if self.per_cpu: 754 nr_cpus = int(HeaderFi 755 self.cpu_str = Extract 756 if self.cpu_str == Non 757 self.cpus = [ 758 else: 759 self.cpus = Pa 760 else: 761 self.cpu_str = None 762 self.cpus = [-1] 763 if self.verbosity.debug: 764 print("cpus", self.cpu 765 766 def IsIntelPT(self): 767 return self.cmd_line.find("int 768 769 def SplitTimeRanges(self): 770 if self.IsIntelPT() and self.i 771 self.split_time_ranges 772 SplitTimeRange 773 774 775 elif self.nr: 776 self.split_time_ranges 777 else: 778 self.split_time_ranges 779 780 def CheckTimeRanges(self): 781 for tr in self.split_time_rang 782 # Re-combined time ran 783 new_tr = RecombineTime 784 if new_tr != self.time 785 if self.verbos 786 print( 787 print( 788 raise Exceptio 789 790 def OpenTimeRangeEnds(self): 791 for time_ranges in self.split_ 792 OpenTimeRangeEnds(time 793 794 def CreateWorkList(self): 795 self.worklist = CreateWorkList 796 797 def PerfDataRecordedPerCPU(self): 798 if "--per-thread" in self.cmd_ 799 return False 800 return True 801 802 def DefaultToPerCPU(self): 803 # --no-per-cpu option takes pr 804 if self.no_per_cpu: 805 return False 806 if not self.PerfDataRecordedPe 807 return False 808 # Default to per-cpu for Intel 809 # because decoding can be done 810 if self.IsIntelPT(): 811 return True 812 return False 813 814 def Config(self): 815 self.Init() 816 self.ExtractTimeInfo() 817 if not self.per_cpu: 818 self.per_cpu = self.De 819 if self.verbosity.debug: 820 print("per_cpu", self. 821 self.ExtractCPUInfo() 822 self.SplitTimeRanges() 823 if self.verbosity.self_test: 824 self.CheckTimeRanges() 825 # Prefer open-ended time range 826 self.OpenTimeRangeEnds() 827 self.CreateWorkList() 828 829 def Run(self): 830 if self.dry_run: 831 print(len(self.worklis 832 for w in self.worklist 833 print(w.Comman 834 return True 835 result = RunWork(self.worklist 836 if self.verbosity.verbose: 837 print(glb_prog_name, " 838 return result 839 840 def RunParallelPerf(a): 841 pp = ParallelPerf(a) 842 pp.Config() 843 return pp.Run() 844 845 def Main(args): 846 ap = argparse.ArgumentParser( 847 prog=glb_prog_name, formatter_ 848 description = 849 """ 850 Run a perf script command multiple times in pa 851 --cpu and --time so that each job processes a 852 """, 853 epilog = 854 """ 855 Follow the options by '--' and then the perf s 856 857 $ perf record -a -- sleep 10 858 $ parallel-perf.py --nr=4 -- perf scri 859 All jobs finished successfully 860 $ tree parallel-perf-output/ 861 parallel-perf-output/ 862 ├── time-range-0 863 │ ├── cmd.txt 864 │ └── out.txt 865 ├── time-range-1 866 │ ├── cmd.txt 867 │ └── out.txt 868 ├── time-range-2 869 │ ├── cmd.txt 870 │ └── out.txt 871 └── time-range-3 872 ├── cmd.txt 873 └── out.txt 874 $ find parallel-perf-output -name cmd. 875 parallel-perf-output/time-range-0/cmd. 876 parallel-perf-output/time-range-1/cmd. 877 parallel-perf-output/time-range-2/cmd. 878 parallel-perf-output/time-range-3/cmd. 879 880 Any perf script command can be used, including 881 --dlfilter and --script, so that the benefit o 882 naturally extends to them also. 883 884 If option --pipe-to is used, standard output i 885 command. Beware, if the command fails (e.g. gr 886 considered a fatal error. 887 888 Final standard output is redirected to files n 889 subdirectories under the output directory. Sim 890 written to files named err.txt. In addition, f 891 corresponding perf script command. After proce 892 if they are empty. 893 894 If any job exits with a non-zero exit code, th 895 more are started. A message is printed if any 896 err.txt file. 897 898 There is a separate output subdirectory for ea 899 option is used, these are further grouped unde 900 901 $ parallel-perf.py --per-cpu --nr=2 -- 902 All jobs finished successfully 903 $ tree parallel-perf-output 904 parallel-perf-output/ 905 ├── cpu-0 906 │ ├── time-range-0 907 │ │ ├── cmd.txt 908 │ │ └── out.txt 909 │ └── time-range-1 910 │ ├── cmd.txt 911 │ └── out.txt 912 └── cpu-1 913 ├── time-range-0 914 │ ├── cmd.txt 915 │ └── out.txt 916 └── time-range-1 917 ├── cmd.txt 918 └── out.txt 919 $ find parallel-perf-output -name cmd. 920 parallel-perf-output/cpu-0/time-range- 921 parallel-perf-output/cpu-0/time-range- 922 parallel-perf-output/cpu-1/time-range- 923 parallel-perf-output/cpu-1/time-range- 924 925 Subdivisions of time range, and cpus if the -- 926 expressed by the --time and --cpu perf script 927 supplied perf script command has a --time opti 928 subdivided, otherwise the time range given by 929 'time of last sample' is used (refer perf scri 930 supplied perf script command may provide a --c 931 will be processed. 932 933 To prevent time intervals becoming too small, 934 be used. 935 936 Note there is special handling for processing 937 not specified and the perf record command cont 938 time range will be subdivided in order to prod 939 approximately the same amount of trace data. T 940 double-quick (--itrace=qqi) samples, and choos 941 approximately the same number of samples. In t 942 the same for each CPU processed. For Intel PT, 943 that can be overridden by --no-per-cpu. Note, 944 decoding produces 1 sample for each PSB synchr 945 come after a certain number of bytes output, d 946 perf Intel PT documentation). The minimum numb 947 will define a time range can be set by the --m 948 64. 949 """) 950 ap.add_argument("-o", "--output-dir", 951 ap.add_argument("-j", "--jobs", type=i 952 ap.add_argument("-n", "--nr", type=int 953 ap.add_argument("-i", "--interval", ty 954 ap.add_argument("-c", "--per-cpu", act 955 ap.add_argument("-m", "--min-interval" 956 ap.add_argument("-p", "--pipe-to", hel 957 ap.add_argument("-N", "--no-per-cpu", 958 ap.add_argument("-b", "--min_size", ty 959 ap.add_argument("-D", "--dry-run", act 960 ap.add_argument("-q", "--quiet", actio 961 ap.add_argument("-v", "--verbose", act 962 ap.add_argument("-d", "--debug", actio 963 cmd_line = list(args) 964 try: 965 split_pos = cmd_line.index("-- 966 cmd = cmd_line[split_pos + 1:] 967 args = cmd_line[:split_pos] 968 except: 969 cmd = None 970 args = cmd_line 971 a = ap.parse_args(args=args[1:]) 972 a.cmd = cmd 973 a.verbosity = Verbosity(a.quiet, a.ver 974 try: 975 if a.cmd == None: 976 if len(args) <= 1: 977 ap.print_help( 978 return True 979 raise Exception("Comma 980 return RunParallelPerf(a) 981 except Exception as e: 982 print("Fatal error: ", str(e)) 983 if a.debug: 984 raise 985 return False 986 987 if __name__ == "__main__": 988 if not Main(sys.argv): 989 sys.exit(1)
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.