-
Notifications
You must be signed in to change notification settings - Fork 133
FIX: LineProfiler.last_time always empty (and sometimes errors out)
#344
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
Conversation
LineProfiler.last_time always emptyLineProfiler.last_time always empty (and sometimes errors out)
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #344 +/- ##
=======================================
Coverage 87.36% 87.36%
=======================================
Files 18 18
Lines 1630 1630
Branches 347 347
=======================================
Hits 1424 1424
Misses 151 151
Partials 55 55 Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
|
Let's fix it, but maybe we drop it in 5.0. I'd like to get all of your backwards compatible changes integrated, but I think we can make some API changes to build a better tool (e.g. use For the KeyError, is there any reason that is causing problem? I suppose an returning an empty dictionary is an ok solution, but it also would make sense to raise the KeyError too. The empty dict doesn't correspond to any thread dictionary, so yes it has the same type, but it feels conceptually different to me. |
|
The suppression of the But then these mostly boil down to opinions anyway,1 and you have a point in that the empty dict does somehow dissociate (If we were to revert to letting the try:
return (<dict>self._c_last_time)[threading.get_ident()]
except KeyError:
raise KeyError('no profiling data on '
f'thread id {threading.get_ident()}') from Nonewhat do you think?) Footnotes
|
|
I buy that a KeyError on a property is not intuitive. On the surface, that's a good enough reason that we can patch this in. Perhaps it would make more sense to return |
|
Nothing in particular; I'll roll back the |
line_profiler/_line_profiler.pyx::LineProfiler
c_last_time
Now catching the `KeyError` when trying to access the attribute
from a threading without prior profiling and returning an empty
dict
last_time
Now using `.c_last_time` instead of `._c_last_time` directly
test_last_time()
New test showing the problematic behavior of
`LineProfiler.last_time` (inconsistency w/`.c_last_time`)
line_profiler/_line_profiler.pyx::LineProfiler.c_last_time
No longer suppressing the `KeyError` when accessed on a thread
without profiling data;
now wrapping the `KeyError` with a more helpful message
tests/test_line_profiler.py::test_last_time()
Updated to reflect the above change
|
I rebased this on the latest main to test before merging. Otherwise I think everything in here looks good. |
|
Thanks for the rebase. Yep this is a rather simple PR and we can probably merge it as-is. |
The bug(s)
The behaviors of
LineProfiler.c_last_timeandLineProfiler.last_timeare inconsistent:The causes
KeyErrorBoth
.c_last_timeand.last_timedirectly access the underlying._c_last_timewith the keythreading.get_ident(). However, it is not necessarily set ifline_profiler/_line_profiler.pyx::python_trace_callback()hasn't been called on the thread yet.Failing assertion
The implementation of
line_profiler/_line_profiler.pyx::LineProfiler.last_time(type:dict[CodeType, LastTime]) basically scans.c_last_time(type:dict[int, LastTime]) for line-hash keys matching values in.code_hash_map(type:dict[CodeType, list[int]]). However, as seen inpython_trace_callback()in the same file (and hinted at in the above example), the keys in.c_last_timeare block hashes (hashes of the whole bytecode) not line hashes (re-hashes thereof incorporating line-number information). Hence nothing matches and the empty result.The fix
.last_timehas been fixed to doblock_hash = hash(code.co_code)instead of to scan thecode_hashes(which are line hashes) corresponding tocodein.code_hash_map..c_last_timenow catches theKeyErrorwhen accessing._c_last_timeandgracefully returns an empty dict(updated 29 May) attaches a more informative error message..last_timenow uses.c_last_timeinstead of directly accessing._c_last_time.tests/test_line_profiler.py::test_last_time()is added.Performance implications
Should be minimal since
.last_timeis documented as a legacy/backward-compatible API, and nothing else directly uses.c_last_time. (The performance-criticalLineProfiler.disable()andpython_trace_callback()both use._c_last_timedirectly.)Disclaimers
.last_timeis documented as legacy, so one may argue that the PR is unnecessary. But since the API is here, I'd argue it should either be fixed or dropped.