Created on 2017-04-20 09:36 by louielu, last changed 2017-04-22 09:41 by louielu.
|PR 1212||open||louielu, 2017-04-20 10:22|
|PR 1253||open||louielu, 2017-04-22 09:41|
|msg291957 - (view)||Author: Louie Lu (louielu) *||Date: 2017-04-20 09:36|
This is a sub-problem of #9285, in #9285, we aim to provide cProfile and profile a context manager, this will need to add code like this: def __enter__(self): self.set_cmd('') sys.setprofile(self.dispatcher) return self Unfortunately, when setting up profiler via `sys.setprofile`, it will immediately work on next line `return self`, which cause the assertion inside `trace_dispatch_return` claim this is a "Bad return". Technically, `profile.Profile` can not go return upper than it's frame inside the frame stack. This behavior can be observed by this code: def fib(n): if n > 2: return fib(n - 1) + fib(n - 2) return n def foo(): pr = profile.Profile() # Profile was set in the `foo` frame, it can't get more upper than this # that means, we can't return to global frame when this profile is set sys.setprofile(pr.dispatcher) fib(5) # We didn't stop the profile here via sys.setprofile(None) # So it will return 0xDEADBEAF to global frame and cause a bad return return 0xDEADBEAF foo() Here this issue will provide the test of this behavior, then will make some modify in #9285 to prevent this situation when using profile as a context manager.
|msg292107 - (view)||Author: Nick Coghlan (ncoghlan) *||Date: 2017-04-22 05:55|
Thanks Louie. I've adjusted the issue title, as I think the core RFE is that we'd like the following helper function to work exactly the same way as calling sys.setprofile directly: def setprofile_helper(f): sys.setprofile(f) The following utility function can be used to demonstrate the current discrepancy: def profile_call(setprofile, call): pr = profile.Profile() setprofile(pr.dispatcher) try: call() finally: setprofile(None) return pr Specifically: ``` >>> profile_call(sys.setprofile, lambda:print("Profiled!")) Profiled! <profile.Profile object at 0x7fb2265966d8> >>> profile_call(setprofile_helper, lambda:print("Profiled!")) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in profile_call File "<stdin>", line 2, in setprofile_helper File "/usr/lib64/python3.5/profile.py", line 209, in trace_dispatch_i if self.dispatch[event](self, frame, t): File "/usr/lib64/python3.5/profile.py", line 293, in trace_dispatch_return assert frame is self.cur[-2].f_back, ("Bad return", self.cur[-3]) AssertionError: ('Bad return', ('profile', 0, 'profiler')) ``` If we can figure out a way to do it reliably, the most desirable approach would be to make helper functions like the above "just work", perhaps by remembering the stack frame where the profiler was *defined*, and using that as the reference point to construct the synthetic stack the first time a trace dispatch occurs. If that transparent-to-the-user approach doesn't appear to be feasible, an alternative would be to offer a new private method on Profile objects to adjust their synthetic stack by one level, as if `sys.setprofile` had been called *before* the call into the helper function. That is, a profiler-aware alternative to the helper function above might look like: def profile_helper(pr): sys.setprofile(pr.dispatcher) pr._adjust_stack() and that's then the approach that the `__enter__` method would use to implement context management support. The advantage of the latter private option is that it wouldn't need to support arbitrary helper functions, just the specific implementation of `__enter__` for issue 9285. In either case, I *don't* think changing the assertion is the right answer - instead, I think the internal profiler state needs to be adjusted so the assertion passes without modification.
|msg292112 - (view)||Author: Louie Lu (louielu) *||Date: 2017-04-22 09:20|
Thanks, Nick. Your analysis is very helpful. After some testing, I found the problem here is because when we using `sys.setprofile` in the helper function, we didn't simulate the call (from where profiler create to helper function), that cause profile's frame link breakup, so if we want to return to outside of helper function, it will report a bad return. A straightforward method is the latter method you propose, to make a profiler-aware function manually insert the frame into profiler's frame stack: def _adjust_frame(self): frame = sys._getframe(1) # Get helper function frame self.dispatch['call'](self, frame, 0) And adjust *before* install profiler: def profile_helper(pr): pr._adjust_frame() sys.setprofile(pr.dispatcher) Then we can get the correct thing we need.
|2017-04-22 09:41:37||louielu||set||pull_requests: + pull_request1368|
|2017-04-22 09:20:28||louielu||set||messages: + msg292112|
title: Add profile test case for trace_dispatch_return assertion -> Allow helper functions to wrap sys.setprofile
|2017-04-20 10:22:07||louielu||set||pull_requests: + pull_request1334|