-
-
Notifications
You must be signed in to change notification settings - Fork 29.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GH-122548: Implement branch taken and not taken events for sys.monitoring #122564
base: main
Are you sure you want to change the base?
Conversation
|
Thanks! I will try this out in the next few days. |
|
Playing with it on some simple code, the basics look really good here. I'll work on adapting coverage.py to get some timings. |
|
I've add |
|
Is def foo():
for i in range(3):
print(i)This is without requesting any Edit: mmm, I don't see |
|
I'm missing some branch events for this code: def foo(n):
while n<3:
print(n)
n += 1
return None
if __name__ == "__main__":
foo(0)Here's what happens: I was expecting branch events for iterations 1 and 2. This is without ever returning |
No it should be immutable, like
It is a bug. I'll take a look. Fixed now |
This is mostly likely because I haven't merged in the fix in #122934 yet. |
|
Should all be fixed now. |
|
It is great to see progress on this, thanks for pushing it forward. I have some questions about the behavior of This is my program for seeing the sys.monitoring events: run_sysmon.py# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
"""Run sys.monitoring on a file of Python code."""
import functools
import sys
print(sys.version)
the_program = sys.argv[1]
code = compile(open(the_program).read(), filename=the_program, mode="exec")
my_id = sys.monitoring.COVERAGE_ID
sys.monitoring.use_tool_id(my_id, "run_sysmon.py")
register = functools.partial(sys.monitoring.register_callback, my_id)
events = sys.monitoring.events
def bytes_to_lines(code):
"""Make a dict mapping byte code offsets to line numbers."""
b2l = {}
cur_line = 0
for bstart, bend, lineno in code.co_lines():
for boffset in range(bstart, bend, 2):
b2l[boffset] = lineno
return b2l
def show_off(label, code, instruction_offset):
if code.co_filename == the_program:
b2l = bytes_to_lines(code)
print(f"{label}: {code.co_filename}@{instruction_offset} #{b2l[instruction_offset]}")
def show_line(label, code, line_number):
if code.co_filename == the_program:
print(f"{label}: {code.co_filename} #{line_number}")
def show_off_off(label, code, instruction_offset, destination_offset):
if code.co_filename == the_program:
b2l = bytes_to_lines(code)
print(
f"{label}: {code.co_filename}@{instruction_offset}->{destination_offset} "
+ f"#{b2l[instruction_offset]}->{b2l[destination_offset]}"
)
def sysmon_py_start(code, instruction_offset):
show_off("PY_START", code, instruction_offset)
sys.monitoring.set_local_events(
my_id,
code,
events.PY_RETURN
| events.PY_RESUME
| events.LINE
| events.BRANCH_TAKEN
| events.BRANCH_NOT_TAKEN
| events.JUMP,
)
def sysmon_py_resume(code, instruction_offset):
show_off("PY_RESUME", code, instruction_offset)
return sys.monitoring.DISABLE
def sysmon_py_return(code, instruction_offset, retval):
show_off("PY_RETURN", code, instruction_offset)
return sys.monitoring.DISABLE
def sysmon_line(code, line_number):
show_line("LINE", code, line_number)
return sys.monitoring.DISABLE
def sysmon_branch(code, instruction_offset, destination_offset):
show_off_off("BRANCH", code, instruction_offset, destination_offset)
return sys.monitoring.DISABLE
def sysmon_branch_taken(code, instruction_offset, destination_offset):
show_off_off("BRANCH_TAKEN", code, instruction_offset, destination_offset)
return sys.monitoring.DISABLE
def sysmon_branch_not_taken(code, instruction_offset, destination_offset):
show_off_off("BRANCH_NOT_TAKEN", code, instruction_offset, destination_offset)
return sys.monitoring.DISABLE
def sysmon_jump(code, instruction_offset, destination_offset):
show_off_off("JUMP", code, instruction_offset, destination_offset)
return sys.monitoring.DISABLE
sys.monitoring.set_events(
my_id,
events.PY_START | events.PY_UNWIND,
)
register(events.PY_START, sysmon_py_start)
register(events.PY_RESUME, sysmon_py_resume)
register(events.PY_RETURN, sysmon_py_return)
# register(events.PY_UNWIND, sysmon_py_unwind_arcs)
register(events.LINE, sysmon_line)
#register(events.BRANCH, sysmon_branch)
register(events.BRANCH_TAKEN, sysmon_branch_taken)
register(events.BRANCH_NOT_TAKEN, sysmon_branch_not_taken)
register(events.JUMP, sysmon_jump)
exec(code)My test program (withbranches.py): from contextlib import nullcontext
def ok():
with nullcontext():
return "good"
def bad():
with nullcontext():
1/0
print(ok())
try:
print(bad())
except:
print("whoops")
print("done")I get this output: I have two questions:
|
|
With statements are complicated things https://peps.python.org/pep-0343/#specification-the-with-statement. with mngr: # line 1
BODY # line 2is equivalent to: It looks like we decided (well, I decided and no one disagreed 🙂) that the call to |
|
I see, thanks. I mistakenly thought the branch was "exception happened or it didn't," but it's "exception happened and was handled by the context manager or it wasn't." I don't think that's a branch that a coverage.py user would want exposed to them, but I could be swayed. The re-visiting of the |
|
@nedbat def foo(a,b):
return (a +
b)Execution order:
|
|
I've updated this branch to use I've left the |
This is a draft PR.
Before merging, it needs:
@nedbat
@jaltmayerpizzorno