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