@@ -34,6 +34,21 @@ def supports_trampoline_profiling():
3434 raise unittest .SkipTest ("perf trampoline profiling not supported" )
3535
3636
37+ def _perf_env (** env_vars ):
38+ env = os .environ .copy ()
39+ # Keep perf's output stable regardless of the builder's perf config.
40+ env .update (
41+ {
42+ "DEBUGINFOD_URLS" : "" ,
43+ "PERF_CONFIG" : os .devnull ,
44+ }
45+ )
46+ if env_vars :
47+ env .update (env_vars )
48+ env ["PYTHON_JIT" ] = "0"
49+ return env
50+
51+
3752class TestPerfTrampoline (unittest .TestCase ):
3853 def setUp (self ):
3954 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
@@ -132,13 +146,12 @@ def baz():
132146 """
133147 with temp_dir () as script_dir :
134148 script = make_script (script_dir , "perftest" , code )
135- env = {** os .environ , "PYTHON_JIT" : "0" }
136149 with subprocess .Popen (
137150 [sys .executable , "-Xperf" , script ],
138151 text = True ,
139152 stderr = subprocess .PIPE ,
140153 stdout = subprocess .PIPE ,
141- env = env ,
154+ env = _perf_env () ,
142155 ) as process :
143156 stdout , stderr = process .communicate ()
144157
@@ -198,13 +211,12 @@ def test_trampoline_works_after_fork_with_many_code_objects(self):
198211 """
199212 with temp_dir () as script_dir :
200213 script = make_script (script_dir , "perftest" , code )
201- env = {** os .environ , "PYTHON_JIT" : "0" }
202214 with subprocess .Popen (
203215 [sys .executable , "-Xperf" , script ],
204216 text = True ,
205217 stderr = subprocess .PIPE ,
206218 stdout = subprocess .PIPE ,
207- env = env ,
219+ env = _perf_env () ,
208220 ) as process :
209221 stdout , stderr = process .communicate ()
210222
@@ -235,13 +247,12 @@ def baz():
235247 """
236248 with temp_dir () as script_dir :
237249 script = make_script (script_dir , "perftest" , code )
238- env = {** os .environ , "PYTHON_JIT" : "0" }
239250 with subprocess .Popen (
240251 [sys .executable , script ],
241252 text = True ,
242253 stderr = subprocess .PIPE ,
243254 stdout = subprocess .PIPE ,
244- env = env ,
255+ env = _perf_env () ,
245256 ) as process :
246257 stdout , stderr = process .communicate ()
247258
@@ -338,9 +349,12 @@ def perf_command_works():
338349 "-c" ,
339350 'print("hello")' ,
340351 )
341- env = {** os .environ , "PYTHON_JIT" : "0" }
342352 stdout = subprocess .check_output (
343- cmd , cwd = script_dir , text = True , stderr = subprocess .STDOUT , env = env
353+ cmd ,
354+ cwd = script_dir ,
355+ text = True ,
356+ stderr = subprocess .STDOUT ,
357+ env = _perf_env (),
344358 )
345359 except (subprocess .SubprocessError , OSError ):
346360 return False
@@ -352,43 +366,49 @@ def perf_command_works():
352366
353367
354368def run_perf (cwd , * args , use_jit = False , ** env_vars ):
355- env = os .environ .copy ()
356- if env_vars :
357- env .update (env_vars )
358- env ["PYTHON_JIT" ] = "0"
369+ env = _perf_env (** env_vars )
359370 output_file = cwd + "/perf_output.perf"
360- if not use_jit :
361- base_cmd = (
362- "perf" ,
363- "record" ,
364- "--no-buildid" ,
365- "--no-buildid-cache" ,
366- "-g" ,
367- "--call-graph=fp" ,
368- "-o" , output_file ,
369- "--"
370- )
371+ base_cmd = [
372+ "perf" ,
373+ "record" ,
374+ "--no-buildid" ,
375+ "--no-buildid-cache" ,
376+ "-g" ,
377+ "--call-graph=dwarf,65528" if use_jit else "--call-graph=fp" ,
378+ ]
379+ if use_jit :
380+ perf_commands = []
381+ # Some builders have low perf_event_mlock_kb limits.
382+ mmap_sizes = ("4M" , "2M" , "1M" , "512K" , "256K" , "128K" , None )
383+ for mmap_size in mmap_sizes :
384+ command = base_cmd .copy ()
385+ if mmap_size is not None :
386+ command += ["-F99" , "-k1" , "-m" , mmap_size ]
387+ else :
388+ command += ["-F99" , "-k1" ]
389+ command += ["-o" , output_file , "--" ]
390+ perf_commands .append (command )
371391 else :
372- base_cmd = (
373- "perf" ,
374- "record" ,
375- "--no-buildid" ,
376- "--no-buildid-cache" ,
377- "-g" ,
378- "--call-graph=dwarf,65528" ,
379- "-F99" ,
380- "-k1" ,
381- "-o" ,
382- output_file ,
383- "--" ,
392+ perf_commands = [base_cmd + ["-o" , output_file , "--" ]]
393+
394+ mmap_pages_error = "try again with a smaller value of -m/--mmap_pages"
395+ for index , base_cmd in enumerate (perf_commands ):
396+ proc = subprocess .run (
397+ base_cmd + list (args ),
398+ stdout = subprocess .PIPE ,
399+ stderr = subprocess .PIPE ,
400+ env = env ,
401+ text = True ,
384402 )
385- proc = subprocess .run (
386- base_cmd + args ,
387- stdout = subprocess .PIPE ,
388- stderr = subprocess .PIPE ,
389- env = env ,
390- text = True ,
391- )
403+ if (
404+ proc .returncode
405+ and use_jit
406+ and index != len (perf_commands ) - 1
407+ and mmap_pages_error in proc .stderr
408+ ):
409+ continue
410+ break
411+
392412 if proc .returncode :
393413 print (proc .stderr , file = sys .stderr )
394414 raise ValueError (f"Perf failed with return code { proc .returncode } " )
@@ -418,54 +438,77 @@ def run_perf(cwd, *args, use_jit=False, **env_vars):
418438
419439
420440class TestPerfProfilerMixin :
421- def run_perf (self , script_dir , perf_mode , script ):
441+ PERF_CAPTURE_ATTEMPTS = 3
442+
443+ def run_perf (self , script_dir , script , activate_trampoline = True ):
422444 raise NotImplementedError ()
423445
446+ def run_perf_with_retries (
447+ self , script_dir , script , expected_symbols = (), activate_trampoline = True
448+ ):
449+ stdout = stderr = ""
450+ for _ in range (self .PERF_CAPTURE_ATTEMPTS ):
451+ stdout , stderr = self .run_perf (
452+ script_dir , script , activate_trampoline = activate_trampoline
453+ )
454+ if activate_trampoline and any (
455+ symbol not in stdout for symbol in expected_symbols
456+ ):
457+ continue
458+ break
459+ return stdout , stderr
460+
424461 def test_python_calls_appear_in_the_stack_if_perf_activated (self ):
425462 with temp_dir () as script_dir :
426463 code = """if 1:
464+ from itertools import repeat
465+
427466 def foo(n):
428- x = 0
429- for i in range(n):
430- x += i
467+ for _ in repeat(None, n):
468+ pass
431469
432470 def bar(n):
433471 foo(n)
434472
435473 def baz(n):
436474 bar(n)
437475
438- baz(10000000 )
476+ baz(40000000 )
439477 """
440478 script = make_script (script_dir , "perftest" , code )
441- stdout , stderr = self .run_perf (script_dir , script )
442- self .assertEqual (stderr , "" )
479+ expected_symbols = [
480+ f"py::foo:{ script } " ,
481+ f"py::bar:{ script } " ,
482+ f"py::baz:{ script } " ,
483+ ]
484+ stdout , _ = self .run_perf_with_retries (
485+ script_dir , script , expected_symbols
486+ )
443487
444- self .assertIn (f"py::foo:{ script } " , stdout )
445- self .assertIn (f"py::bar:{ script } " , stdout )
446- self .assertIn (f"py::baz:{ script } " , stdout )
488+ for expected_symbol in expected_symbols :
489+ self .assertIn (expected_symbol , stdout )
447490
448491 def test_python_calls_do_not_appear_in_the_stack_if_perf_deactivated (self ):
449492 with temp_dir () as script_dir :
450493 code = """if 1:
494+ from itertools import repeat
495+
451496 def foo(n):
452- x = 0
453- for i in range(n):
454- x += i
497+ for _ in repeat(None, n):
498+ pass
455499
456500 def bar(n):
457501 foo(n)
458502
459503 def baz(n):
460504 bar(n)
461505
462- baz(10000000 )
506+ baz(40000000 )
463507 """
464508 script = make_script (script_dir , "perftest" , code )
465- stdout , stderr = self .run_perf (
509+ stdout , _ = self .run_perf_with_retries (
466510 script_dir , script , activate_trampoline = False
467511 )
468- self .assertEqual (stderr , "" )
469512
470513 self .assertNotIn (f"py::foo:{ script } " , stdout )
471514 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