mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
gh-131253: free-threaded build support for pystats (gh-137189)
Allow the --enable-pystats build option to be used with free-threading. The stats are now stored on a per-interpreter basis, rather than process global. For free-threaded builds, the stats structure is allocated per-thread and then periodically merged into the per-interpreter stats structure (on thread exit or when the reporting function is called). Most of the pystats related code has be moved into the file Python/pystats.c.
This commit is contained in:
parent
cf1a2c1ee4
commit
c98c5b3449
24 changed files with 1269 additions and 485 deletions
215
Lib/test/test_pystats.py
Normal file
215
Lib/test/test_pystats.py
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
import sys
|
||||
import textwrap
|
||||
import unittest
|
||||
from test.support import script_helper
|
||||
|
||||
# This function is available for the --enable-pystats config.
|
||||
HAVE_PYSTATS = hasattr(sys, '_stats_on')
|
||||
|
||||
TEST_TEMPLATE = """
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
THREADS = 2
|
||||
|
||||
class A:
|
||||
pass
|
||||
|
||||
class B:
|
||||
pass
|
||||
|
||||
def modify_class():
|
||||
# This is used as a rare event we can assume doesn't happen unless we do it.
|
||||
# It increments the "Rare event (set_class)" count.
|
||||
a = A()
|
||||
a.__class__ = B
|
||||
|
||||
TURNED_ON = False
|
||||
def stats_on():
|
||||
global TURNED_ON
|
||||
sys._stats_on()
|
||||
TURNED_ON = True
|
||||
|
||||
TURNED_OFF = False
|
||||
def stats_off():
|
||||
global TURNED_OFF
|
||||
sys._stats_off()
|
||||
TURNED_OFF = True
|
||||
|
||||
CLEARED = False
|
||||
def stats_clear():
|
||||
global CLEARED
|
||||
sys._stats_clear()
|
||||
CLEARED = True
|
||||
|
||||
def func_start():
|
||||
pass
|
||||
|
||||
def func_end():
|
||||
pass
|
||||
|
||||
def func_test(thread_id):
|
||||
pass
|
||||
|
||||
_TEST_CODE_
|
||||
|
||||
func_start()
|
||||
threads = []
|
||||
for i in range(THREADS):
|
||||
t = threading.Thread(target=func_test, args=(i,))
|
||||
threads.append(t)
|
||||
t.start()
|
||||
for t in threads:
|
||||
t.join()
|
||||
func_end()
|
||||
"""
|
||||
|
||||
|
||||
def run_test_code(
|
||||
test_code,
|
||||
args=[],
|
||||
env_vars=None,
|
||||
):
|
||||
"""Run test code and return the value of the "set_class" stats counter.
|
||||
"""
|
||||
code = textwrap.dedent(TEST_TEMPLATE)
|
||||
code = code.replace('_TEST_CODE_', textwrap.dedent(test_code))
|
||||
script_args = args + ['-c', code]
|
||||
env_vars = env_vars or {}
|
||||
res, _ = script_helper.run_python_until_end(*script_args, **env_vars)
|
||||
stderr = res.err.decode("ascii", "backslashreplace")
|
||||
for line in stderr.split('\n'):
|
||||
if 'Rare event (set_class)' in line:
|
||||
label, _, value = line.partition(':')
|
||||
return value.strip()
|
||||
return ''
|
||||
|
||||
|
||||
@unittest.skipUnless(HAVE_PYSTATS, "requires pystats build option")
|
||||
class TestPyStats(unittest.TestCase):
|
||||
"""Tests for pystats functionality (requires --enable-pystats build
|
||||
option).
|
||||
"""
|
||||
|
||||
def test_stats_toggle_on(self):
|
||||
"""Check the toggle on functionality.
|
||||
"""
|
||||
code = """
|
||||
def func_start():
|
||||
modify_class()
|
||||
"""
|
||||
|
||||
# If turned on with command line flag, should get one count.
|
||||
stat_count = run_test_code(code, args=['-X', 'pystats'])
|
||||
self.assertEqual(stat_count, '1')
|
||||
|
||||
# If turned on with env var, should get one count.
|
||||
stat_count = run_test_code(code, env_vars={'PYTHONSTATS': '1'})
|
||||
self.assertEqual(stat_count, '1')
|
||||
|
||||
# If not turned on, should be no counts.
|
||||
stat_count = run_test_code(code)
|
||||
self.assertEqual(stat_count, '')
|
||||
|
||||
code = """
|
||||
def func_start():
|
||||
modify_class()
|
||||
sys._stats_on()
|
||||
modify_class()
|
||||
"""
|
||||
# Not initially turned on but enabled by sys._stats_on(), should get
|
||||
# one count.
|
||||
stat_count = run_test_code(code)
|
||||
self.assertEqual(stat_count, '1')
|
||||
|
||||
def test_stats_toggle_on_thread(self):
|
||||
"""Check the toggle on functionality when threads are used.
|
||||
"""
|
||||
code = """
|
||||
def func_test(thread_id):
|
||||
if thread_id == 0:
|
||||
modify_class()
|
||||
stats_on()
|
||||
modify_class()
|
||||
else:
|
||||
while not TURNED_ON:
|
||||
pass
|
||||
modify_class()
|
||||
"""
|
||||
# Turning on in one thread will count in other thread.
|
||||
stat_count = run_test_code(code)
|
||||
self.assertEqual(stat_count, '2')
|
||||
|
||||
code = """
|
||||
def func_test(thread_id):
|
||||
if thread_id == 0:
|
||||
modify_class()
|
||||
stats_off()
|
||||
modify_class()
|
||||
else:
|
||||
while not TURNED_OFF:
|
||||
pass
|
||||
modify_class()
|
||||
"""
|
||||
# Turning off in one thread will not count in other threads.
|
||||
stat_count = run_test_code(code, args=['-X', 'pystats'])
|
||||
self.assertEqual(stat_count, '1')
|
||||
|
||||
def test_thread_exit_merge(self):
|
||||
"""Check that per-thread stats (when free-threading enabled) are merged.
|
||||
"""
|
||||
code = """
|
||||
def func_test(thread_id):
|
||||
modify_class()
|
||||
if thread_id == 0:
|
||||
raise SystemExit
|
||||
"""
|
||||
# Stats from a thread exiting early should still be counted.
|
||||
stat_count = run_test_code(code, args=['-X', 'pystats'])
|
||||
self.assertEqual(stat_count, '2')
|
||||
|
||||
def test_stats_dump(self):
|
||||
"""Check that sys._stats_dump() works.
|
||||
"""
|
||||
code = """
|
||||
def func_test(thread_id):
|
||||
if thread_id == 0:
|
||||
stats_on()
|
||||
else:
|
||||
while not TURNED_ON:
|
||||
pass
|
||||
modify_class()
|
||||
sys._stats_dump()
|
||||
stats_off()
|
||||
"""
|
||||
# Stats from a thread exiting early should still be counted.
|
||||
stat_count = run_test_code(code)
|
||||
self.assertEqual(stat_count, '1')
|
||||
|
||||
def test_stats_clear(self):
|
||||
"""Check that sys._stats_clear() works.
|
||||
"""
|
||||
code = """
|
||||
ready = False
|
||||
def func_test(thread_id):
|
||||
global ready
|
||||
if thread_id == 0:
|
||||
stats_on()
|
||||
modify_class()
|
||||
while not ready:
|
||||
pass # wait until other thread has called modify_class()
|
||||
stats_clear() # clears stats for all threads
|
||||
else:
|
||||
while not TURNED_ON:
|
||||
pass
|
||||
modify_class()
|
||||
ready = True
|
||||
"""
|
||||
# Clearing stats will clear for all threads
|
||||
stat_count = run_test_code(code)
|
||||
self.assertEqual(stat_count, '0')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue