Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 39 additions & 14 deletions line_profiler/_line_profiler.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ from cpython.version cimport PY_VERSION_HEX
from libc.stdint cimport int64_t

from libcpp.unordered_map cimport unordered_map
import threading

# long long int is at least 64 bytes assuming c99
ctypedef unsigned long long int uint64
Expand Down Expand Up @@ -117,13 +118,14 @@ cpdef _code_replace(func, code, co_code):
"""
Implements CodeType.replace for Python < 3.8
"""
code = func.__code__
if hasattr(code, 'replace'):
# python 3.8+
code = func.__code__.replace(co_code=co_code)
else:
# python <3.8
co = code
code = CodeType(co.co_argcount, co.co_kwonlyargcount,
code = type(code)(co.co_argcount, co.co_kwonlyargcount,
co.co_nlocals, co.co_stacksize, co.co_flags,
co_code, co.co_consts, co.co_names,
co.co_varnames, co.co_filename, co.co_name,
Expand Down Expand Up @@ -174,18 +176,19 @@ cdef class LineProfiler:
>>> self.print_stats()
"""
cdef unordered_map[int64, unordered_map[int64, LineTime]] _c_code_map
cdef unordered_map[int64, LastTime] _c_last_time
# Mapping between thread-id and map of LastTime
cdef unordered_map[int64, unordered_map[int64, LastTime]] _c_last_time
cdef public list functions
cdef public dict code_hash_map, dupes_map
cdef public double timer_unit
cdef public long enable_count
cdef public object threaddata

def __init__(self, *functions):
self.functions = []
self.code_hash_map = {}
self.dupes_map = {}
self.timer_unit = hpTimerUnit()
self.enable_count = 0
self.threaddata = threading.local()

for func in functions:
self.add_function(func)
Expand Down Expand Up @@ -229,6 +232,14 @@ cdef class LineProfiler:

self.functions.append(func)

property enable_count:
def __get__(self):
if not hasattr(self.threaddata, 'enable_count'):
self.threaddata.enable_count = 0
return self.threaddata.enable_count
def __set__(self, value):
self.threaddata.enable_count = value

def enable_by_count(self):
""" Enable the profiler if it hasn't been enabled before.
"""
Expand Down Expand Up @@ -263,7 +274,7 @@ cdef class LineProfiler:

@property
def c_last_time(self):
return <dict>self._c_last_time
return (<dict>self._c_last_time)[threading.get_ident()]

@property
def code_map(self):
Expand Down Expand Up @@ -292,7 +303,7 @@ cdef class LineProfiler:
line_profiler 4.0 no longer directly maintains last_time, but this will
construct something similar for backwards compatibility.
"""
c_last_time = self.c_last_time
c_last_time = (<dict>self._c_last_time)[threading.get_ident()]
code_hash_map = self.code_hash_map
py_last_time = {}
for code, code_hashes in code_hash_map.items():
Expand All @@ -303,7 +314,7 @@ cdef class LineProfiler:


cpdef disable(self):
self._c_last_time.clear()
self._c_last_time[threading.get_ident()].clear()
unset_trace()

def get_stats(self):
Expand All @@ -318,8 +329,21 @@ cdef class LineProfiler:
for entry in self.code_hash_map[code]:
entries += list(cmap[entry].values())
key = label(code)
stats[key] = [(e["lineno"], e["nhits"], e["total_time"]) for e in entries]
stats[key].sort()

entries = [(e["lineno"], e["nhits"], e["total_time"]) for e in entries]
# If there are multiple entries for a line, this will sort them by increasing # hits
entries.sort()

# NOTE: v4.x may produce more than one entry per line. For example:
# 1: for x in range(10):
# 2: pass
# will produce a 1-hit entry on line 1, and 10-hit entries on lines 1 and 2
# This doesn't affect `print_stats`, because it uses the last entry for a given line (line number is
# used a dict key so earlier entries are overwritten), but to keep compatability with other tools,
# let's only keep the last entry for each line
# Remove all but the last entry for each line
entries = list({e[0]: e for e in entries}.values())
stats[key] = entries
return LineStats(stats, self.timer_unit)

@cython.boundscheck(False)
Expand All @@ -345,8 +369,9 @@ PyObject *arg):
code_hash = compute_line_hash(block_hash, py_frame.f_lineno)
if self._c_code_map.count(code_hash):
time = hpTimer()
if self._c_last_time.count(block_hash):
old = self._c_last_time[block_hash]
ident = threading.get_ident()
if self._c_last_time[ident].count(block_hash):
old = self._c_last_time[ident][block_hash]
line_entries = self._c_code_map[code_hash]
key = old.f_lineno
if not line_entries.count(key):
Expand All @@ -356,12 +381,12 @@ PyObject *arg):
if what == PyTrace_LINE:
# Get the time again. This way, we don't record much time wasted
# in this function.
self._c_last_time[block_hash] = LastTime(py_frame.f_lineno, hpTimer())
elif self._c_last_time.count(block_hash):
self._c_last_time[ident][block_hash] = LastTime(py_frame.f_lineno, 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
# are profiling a generator that is being pumped past its end.
self._c_last_time.erase(self._c_last_time.find(block_hash))
self._c_last_time[ident].erase(self._c_last_time[ident].find(block_hash))

return 0

Expand Down