From 3a163afcc481971d6328ec348d2ce3c7f8e09f0a Mon Sep 17 00:00:00 2001 From: joncrall Date: Thu, 2 Nov 2023 17:02:18 -0400 Subject: [PATCH] Replace .f_lineno with PyFrame_GetLineNumber to support 3.12 --- line_profiler/_line_profiler.pyx | 11 ++++++-- tests/test_explicit_profile.py | 45 ++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/line_profiler/_line_profiler.pyx b/line_profiler/_line_profiler.pyx index f11b6b57..044b17b0 100644 --- a/line_profiler/_line_profiler.pyx +++ b/line_profiler/_line_profiler.pyx @@ -89,6 +89,9 @@ cdef extern from "Python.h": cdef int PyTrace_C_EXCEPTION cdef int PyTrace_C_RETURN + cdef int PyFrame_GetLineNumber(PyFrameObject *frame) + + cdef extern from "timers.c": PY_LONG_LONG hpTimer() double hpTimerUnit() @@ -419,13 +422,17 @@ cdef extern int python_trace_callback(object self_, PyFrameObject *py_frame, cdef int64 code_hash cdef int64 block_hash cdef unordered_map[int64, LineTime] line_entries + cdef uint64 linenum self = self_ if what == PyTrace_LINE or what == PyTrace_RETURN: # Normally we'd need to DECREF the return from get_frame_code, but Cython does that for us block_hash = hash(get_frame_code(py_frame)) - code_hash = compute_line_hash(block_hash, py_frame.f_lineno) + + linenum = PyFrame_GetLineNumber(py_frame) + code_hash = compute_line_hash(block_hash, linenum) + if self._c_code_map.count(code_hash): time = hpTimer() ident = threading.get_ident() @@ -440,7 +447,7 @@ cdef extern int python_trace_callback(object self_, PyFrameObject *py_frame, if what == PyTrace_LINE: # Get the time again. This way, we don't record much time wasted # in this function. - self._c_last_time[ident][block_hash] = LastTime(py_frame.f_lineno, hpTimer()) + self._c_last_time[ident][block_hash] = LastTime(linenum, hpTimer()) elif self._c_last_time[ident].count(block_hash): # We are returning from a function, not executing a line. Delete # the last_time record. It may have already been deleted if we diff --git a/tests/test_explicit_profile.py b/tests/test_explicit_profile.py index be3371d1..45700050 100644 --- a/tests/test_explicit_profile.py +++ b/tests/test_explicit_profile.py @@ -4,6 +4,28 @@ import ubelt as ub +def test_simple_explicit_nonglobal_usage(): + """ + python -c "from test_explicit_profile import *; test_simple_explicit_nonglobal_usage()" + """ + from line_profiler import LineProfiler + profiler = LineProfiler() + + def func(a): + return a + 1 + + profiled_func = profiler(func) + + # Run Once + profiled_func(1) + + lstats = profiler.get_stats() + print(f'lstats.timings={lstats.timings}') + print(f'lstats.unit={lstats.unit}') + print(f'profiler.code_hash_map={profiler.code_hash_map}') + profiler.print_stats() + + def _demo_explicit_profile_script(): return ub.codeblock( ''' @@ -140,12 +162,23 @@ def test_explicit_profile_with_in_code_enable(): """ Test that the user can enable the profiler explicitly from within their code. + + CommandLine: + pytest tests/test_explicit_profile.py -s -k test_explicit_profile_with_in_code_enable """ temp_dpath = ub.Path(tempfile.mkdtemp()) code = ub.codeblock( ''' from line_profiler import profile + import ubelt as ub + print('') + print('') + print('start test') + + print('profile = {}'.format(ub.urepr(profile, nl=1))) + print(f'profile._profile={profile._profile}') + print(f'profile.enabled={profile.enabled}') @profile def func1(a): @@ -153,10 +186,16 @@ def func1(a): profile.enable(output_prefix='custom_output') + print('profile = {}'.format(ub.urepr(profile, nl=1))) + print(f'profile._profile={profile._profile}') + print(f'profile.enabled={profile.enabled}') + @profile def func2(a): return a + 1 + print('func2 = {}'.format(ub.urepr(func2, nl=1))) + profile.disable() @profile @@ -173,6 +212,8 @@ def func4(a): func2(1) func3(1) func4(1) + + profile._profile ''') with ub.ChDir(temp_dpath): @@ -259,3 +300,7 @@ def func4(a): assert output_fpath.exists() assert (temp_dpath / 'profile_output.lprof').exists() temp_dpath.delete() + +if __name__ == '__main__': + ... + test_simple_explicit_nonglobal_usage()