@@ -35,6 +35,21 @@ def supports_trampoline_profiling():
3535 raise unittest .SkipTest ("perf trampoline profiling not supported" )
3636
3737
38+ def _perf_env (** env_vars ):
39+ env = os .environ .copy ()
40+ # Keep perf's output stable regardless of the builder's perf config.
41+ env .update (
42+ {
43+ "DEBUGINFOD_URLS" : "" ,
44+ "PERF_CONFIG" : os .devnull ,
45+ }
46+ )
47+ if env_vars :
48+ env .update (env_vars )
49+ env ["PYTHON_JIT" ] = "0"
50+ return env
51+
52+
3853class TestPerfTrampoline (unittest .TestCase ):
3954 def setUp (self ):
4055 super ().setUp ()
@@ -63,13 +78,12 @@ def baz():
6378 """
6479 with temp_dir () as script_dir :
6580 script = make_script (script_dir , "perftest" , code )
66- env = {** os .environ , "PYTHON_JIT" : "0" }
6781 with subprocess .Popen (
6882 [sys .executable , "-Xperf" , script ],
6983 text = True ,
7084 stderr = subprocess .PIPE ,
7185 stdout = subprocess .PIPE ,
72- env = env ,
86+ env = _perf_env () ,
7387 ) as process :
7488 stdout , stderr = process .communicate ()
7589
@@ -133,13 +147,12 @@ def baz():
133147 """
134148 with temp_dir () as script_dir :
135149 script = make_script (script_dir , "perftest" , code )
136- env = {** os .environ , "PYTHON_JIT" : "0" }
137150 with subprocess .Popen (
138151 [sys .executable , "-Xperf" , script ],
139152 text = True ,
140153 stderr = subprocess .PIPE ,
141154 stdout = subprocess .PIPE ,
142- env = env ,
155+ env = _perf_env () ,
143156 ) as process :
144157 stdout , stderr = process .communicate ()
145158
@@ -199,13 +212,12 @@ def test_trampoline_works_after_fork_with_many_code_objects(self):
199212 """
200213 with temp_dir () as script_dir :
201214 script = make_script (script_dir , "perftest" , code )
202- env = {** os .environ , "PYTHON_JIT" : "0" }
203215 with subprocess .Popen (
204216 [sys .executable , "-Xperf" , script ],
205217 text = True ,
206218 stderr = subprocess .PIPE ,
207219 stdout = subprocess .PIPE ,
208- env = env ,
220+ env = _perf_env () ,
209221 ) as process :
210222 stdout , stderr = process .communicate ()
211223
@@ -236,13 +248,12 @@ def baz():
236248 """
237249 with temp_dir () as script_dir :
238250 script = make_script (script_dir , "perftest" , code )
239- env = {** os .environ , "PYTHON_JIT" : "0" }
240251 with subprocess .Popen (
241252 [sys .executable , script ],
242253 text = True ,
243254 stderr = subprocess .PIPE ,
244255 stdout = subprocess .PIPE ,
245- env = env ,
256+ env = _perf_env () ,
246257 ) as process :
247258 stdout , stderr = process .communicate ()
248259
@@ -339,9 +350,12 @@ def perf_command_works():
339350 "-c" ,
340351 'print("hello")' ,
341352 )
342- env = {** os .environ , "PYTHON_JIT" : "0" }
343353 stdout = subprocess .check_output (
344- cmd , cwd = script_dir , text = True , stderr = subprocess .STDOUT , env = env
354+ cmd ,
355+ cwd = script_dir ,
356+ text = True ,
357+ stderr = subprocess .STDOUT ,
358+ env = _perf_env (),
345359 )
346360 except (subprocess .SubprocessError , OSError ):
347361 return False
@@ -353,43 +367,49 @@ def perf_command_works():
353367
354368
355369def run_perf (cwd , * args , use_jit = False , ** env_vars ):
356- env = os .environ .copy ()
357- if env_vars :
358- env .update (env_vars )
359- env ["PYTHON_JIT" ] = "0"
370+ env = _perf_env (** env_vars )
360371 output_file = cwd + "/perf_output.perf"
361- if not use_jit :
362- base_cmd = (
363- "perf" ,
364- "record" ,
365- "--no-buildid" ,
366- "--no-buildid-cache" ,
367- "-g" ,
368- "--call-graph=fp" ,
369- "-o" , output_file ,
370- "--"
371- )
372+ base_cmd = [
373+ "perf" ,
374+ "record" ,
375+ "--no-buildid" ,
376+ "--no-buildid-cache" ,
377+ "-g" ,
378+ "--call-graph=dwarf,65528" if use_jit else "--call-graph=fp" ,
379+ ]
380+ if use_jit :
381+ perf_commands = []
382+ # Some builders have low perf_event_mlock_kb limits.
383+ mmap_sizes = ("4M" , "2M" , "1M" , "512K" , "256K" , "128K" , None )
384+ for mmap_size in mmap_sizes :
385+ command = base_cmd .copy ()
386+ if mmap_size is not None :
387+ command += ["-F99" , "-k1" , "-m" , mmap_size ]
388+ else :
389+ command += ["-F99" , "-k1" ]
390+ command += ["-o" , output_file , "--" ]
391+ perf_commands .append (command )
372392 else :
373- base_cmd = (
374- "perf" ,
375- "record" ,
376- "--no-buildid" ,
377- "--no-buildid-cache" ,
378- "-g" ,
379- "--call-graph=dwarf,65528" ,
380- "-F99" ,
381- "-k1" ,
382- "-o" ,
383- output_file ,
384- "--" ,
393+ perf_commands = [base_cmd + ["-o" , output_file , "--" ]]
394+
395+ mmap_pages_error = "try again with a smaller value of -m/--mmap_pages"
396+ for index , base_cmd in enumerate (perf_commands ):
397+ proc = subprocess .run (
398+ base_cmd + list (args ),
399+ stdout = subprocess .PIPE ,
400+ stderr = subprocess .PIPE ,
401+ env = env ,
402+ text = True ,
385403 )
386- proc = subprocess .run (
387- base_cmd + args ,
388- stdout = subprocess .PIPE ,
389- stderr = subprocess .PIPE ,
390- env = env ,
391- text = True ,
392- )
404+ if (
405+ proc .returncode
406+ and use_jit
407+ and index != len (perf_commands ) - 1
408+ and mmap_pages_error in proc .stderr
409+ ):
410+ continue
411+ break
412+
393413 if proc .returncode :
394414 print (proc .stderr , file = sys .stderr )
395415 raise ValueError (f"Perf failed with return code { proc .returncode } " )
@@ -419,54 +439,77 @@ def run_perf(cwd, *args, use_jit=False, **env_vars):
419439
420440
421441class TestPerfProfilerMixin :
422- def run_perf (self , script_dir , perf_mode , script ):
442+ PERF_CAPTURE_ATTEMPTS = 3
443+
444+ def run_perf (self , script_dir , script , activate_trampoline = True ):
423445 raise NotImplementedError ()
424446
447+ def run_perf_with_retries (
448+ self , script_dir , script , expected_symbols = (), activate_trampoline = True
449+ ):
450+ stdout = stderr = ""
451+ for _ in range (self .PERF_CAPTURE_ATTEMPTS ):
452+ stdout , stderr = self .run_perf (
453+ script_dir , script , activate_trampoline = activate_trampoline
454+ )
455+ if activate_trampoline and any (
456+ symbol not in stdout for symbol in expected_symbols
457+ ):
458+ continue
459+ break
460+ return stdout , stderr
461+
425462 def test_python_calls_appear_in_the_stack_if_perf_activated (self ):
426463 with temp_dir () as script_dir :
427464 code = """if 1:
465+ from itertools import repeat
466+
428467 def foo(n):
429- x = 0
430- for i in range(n):
431- x += i
468+ for _ in repeat(None, n):
469+ pass
432470
433471 def bar(n):
434472 foo(n)
435473
436474 def baz(n):
437475 bar(n)
438476
439- baz(10000000 )
477+ baz(40000000 )
440478 """
441479 script = make_script (script_dir , "perftest" , code )
442- stdout , stderr = self .run_perf (script_dir , script )
443- self .assertEqual (stderr , "" )
480+ expected_symbols = [
481+ f"py::foo:{ script } " ,
482+ f"py::bar:{ script } " ,
483+ f"py::baz:{ script } " ,
484+ ]
485+ stdout , _ = self .run_perf_with_retries (
486+ script_dir , script , expected_symbols
487+ )
444488
445- self .assertIn (f"py::foo:{ script } " , stdout )
446- self .assertIn (f"py::bar:{ script } " , stdout )
447- self .assertIn (f"py::baz:{ script } " , stdout )
489+ for expected_symbol in expected_symbols :
490+ self .assertIn (expected_symbol , stdout )
448491
449492 def test_python_calls_do_not_appear_in_the_stack_if_perf_deactivated (self ):
450493 with temp_dir () as script_dir :
451494 code = """if 1:
495+ from itertools import repeat
496+
452497 def foo(n):
453- x = 0
454- for i in range(n):
455- x += i
498+ for _ in repeat(None, n):
499+ pass
456500
457501 def bar(n):
458502 foo(n)
459503
460504 def baz(n):
461505 bar(n)
462506
463- baz(10000000 )
507+ baz(40000000 )
464508 """
465509 script = make_script (script_dir , "perftest" , code )
466- stdout , stderr = self .run_perf (
510+ stdout , _ = self .run_perf_with_retries (
467511 script_dir , script , activate_trampoline = False
468512 )
469- self .assertEqual (stderr , "" )
470513
471514 self .assertNotIn (f"py::foo:{ script } " , stdout )
472515 self .assertNotIn (f"py::bar:{ script } " , stdout )
@@ -535,13 +578,12 @@ def compile_trampolines_for_all_functions():
535578
536579 with temp_dir () as script_dir :
537580 script = make_script (script_dir , "perftest" , code )
538- env = {** os .environ , "PYTHON_JIT" : "0" }
539581 with subprocess .Popen (
540582 [sys .executable , "-Xperf" , script ],
541583 universal_newlines = True ,
542584 stderr = subprocess .PIPE ,
543585 stdout = subprocess .PIPE ,
544- env = env ,
586+ env = _perf_env () ,
545587 ) as process :
546588 stdout , stderr = process .communicate ()
547589
0 commit comments