Skip to content

Commit 1a318b5

Browse files
authored
Merge pull request #344 from TTsangSC/last-time-fix
FIX: `LineProfiler.last_time` always empty (and sometimes errors out)
2 parents 7ffc0f1 + 1db6433 commit 1a318b5

2 files changed

Lines changed: 51 additions & 7 deletions

File tree

line_profiler/_line_profiler.pyx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,19 @@ cdef class LineProfiler:
471471

472472
@property
473473
def c_last_time(self):
474-
return (<dict>self._c_last_time)[threading.get_ident()]
474+
"""
475+
Raises:
476+
KeyError
477+
If no profiling data is available on the current thread.
478+
"""
479+
try:
480+
return (<dict>self._c_last_time)[threading.get_ident()]
481+
except KeyError as e:
482+
# We haven't actually profiled anything yet
483+
raise (KeyError('No profiling data on the current thread '
484+
'(`threading.get_ident()` = '
485+
f'{threading.get_ident()})')
486+
.with_traceback(e.__traceback__)) from None
475487

476488
@property
477489
def code_map(self):
@@ -500,13 +512,12 @@ cdef class LineProfiler:
500512
line_profiler 4.0 no longer directly maintains last_time, but this will
501513
construct something similar for backwards compatibility.
502514
"""
503-
c_last_time = (<dict>self._c_last_time)[threading.get_ident()]
504-
code_hash_map = self.code_hash_map
515+
c_last_time = self.c_last_time
505516
py_last_time = {}
506-
for code, code_hashes in code_hash_map.items():
507-
for code_hash in code_hashes:
508-
if code_hash in c_last_time:
509-
py_last_time[code] = c_last_time[code_hash]
517+
for code in self.code_hash_map:
518+
block_hash = hash(code.co_code)
519+
if block_hash in c_last_time:
520+
py_last_time[code] = c_last_time[block_hash]
510521
return py_last_time
511522

512523

tests/test_line_profiler.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,39 @@ def test_init():
8080
}
8181

8282

83+
def test_last_time():
84+
"""
85+
Test that `LineProfiler.c_last_time` and `LineProfiler.last_time`
86+
are consistent.
87+
"""
88+
prof = LineProfiler()
89+
with pytest.raises(KeyError, match='[Nn]o profiling data'):
90+
prof.c_last_time
91+
92+
def get_last_time(prof, *, c=False):
93+
try:
94+
return getattr(prof, 'c_last_time' if c else 'last_time')
95+
except KeyError:
96+
return {}
97+
98+
@prof
99+
def func():
100+
return (get_last_time(prof, c=True).copy(),
101+
get_last_time(prof).copy())
102+
103+
# These are always empty outside a profiling context
104+
# (hence the need of the above function to capture the transient
105+
# values)
106+
assert not get_last_time(prof, c=True)
107+
assert not get_last_time(prof)
108+
# Inside `func()`, both should get an entry therefor
109+
clt, lt = func()
110+
assert not get_last_time(prof, c=True)
111+
assert not get_last_time(prof)
112+
assert set(clt) == {hash(func.__wrapped__.__code__.co_code)}
113+
assert set(lt) == {func.__wrapped__.__code__}
114+
115+
83116
def test_enable_disable():
84117
lp = LineProfiler()
85118
assert lp.enable_count == 0

0 commit comments

Comments
 (0)