Skip to content

Commit c40ef76

Browse files
committed
2. fix morebuiltins.funcs.LineProfiler to handle nested function calls correctly.
1 parent 800ea15 commit c40ef76

File tree

4 files changed

+55
-24
lines changed

4 files changed

+55
-24
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
### 1.3.4 (2025-09-17)
44
1. fix `morebuiltins.utils.gen_id` wrong length issue.
5+
2. fix `morebuiltins.funcs.LineProfiler` to handle nested function calls correctly.
56

67
### 1.3.3 (2025-09-01)
78
1. add `morebuiltins.logs.LogHelper.handle_crash` to log uncaught exceptions.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ print(morebuiltins.__file__)
135135

136136
1.36 `base_decode` - Decode a base-N string to a number.
137137

138-
1.37 `gen_id` - Generate a unique ID based on the current time and random bytes
138+
1.37 `gen_id` - Generate a unique ID based on the current time and random bytes.
139139

140140
1.38 `timeti` - Return the number of iterations per second for a given statement.
141141

doc.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1163,11 +1163,15 @@ Examples:
11631163

11641164

11651165

1166-
1.37 `gen_id` - Generate a unique ID based on the current time and random bytes
1166+
1.37 `gen_id` - Generate a unique ID based on the current time and random bytes.
11671167

11681168

11691169
```python
11701170

1171+
If rand_len=0 the length of ID will be 18, rand_len=4 the length of ID will be 22.
1172+
ID format:
1173+
- {YYMMDD_HHMMSS}_{4-digit base62 of microsecond}{rand_len urandom hex string}
1174+
11711175
Args:
11721176
rand_len (int, optional): Defaults to 4.
11731177

@@ -1182,6 +1186,8 @@ True
11821186
>>> ids = [time.sleep(0.000001) or gen_id() for _ in range(1000)]
11831187
>>> len(set(ids))
11841188
1000
1189+
>>> [len(gen_id(i)[:]) for i in range(10)]
1190+
[18, 19, 20, 21, 22, 23, 24, 25, 26, 27]
11851191

11861192
```
11871193

morebuiltins/funcs.py

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -739,21 +739,44 @@ def check_recursion(function: Callable, return_error=False):
739739
return None
740740

741741

742+
class LineTime(object):
743+
__slots__ = ("line_no", "start_time", "end_time", "code")
744+
745+
def __init__(
746+
self, line_no: int, start_time: float, end_time: Optional[float], code: str
747+
):
748+
self.line_no = line_no
749+
self.start_time = start_time
750+
self.end_time = end_time
751+
self.code = code
752+
753+
742754
class LineProfiler:
743755
"""Line-by-line performance profiler."""
744756

745757
stdout = sys.stdout
746758

747-
def __init__(self):
748-
self.line_times: List[Tuple[int, float, str]] = []
759+
def __init__(self) -> None:
760+
self.line_times: List[LineTime] = []
749761
self.start_time = 0.0
750762
self.total_time = 0.0
751763
self.line_cache: Dict[str, list] = {}
764+
self.filled = 0
752765

753766
def trace_calls(self, frame, event, arg):
754767
"""Trace each line of function calls"""
768+
current_time = time.perf_counter()
769+
if self.filled < len(self.line_times):
770+
self.filled += 1
771+
last_item = self.line_times[-1]
772+
if last_item.end_time is None:
773+
self.line_times[-1] = LineTime(
774+
last_item.line_no,
775+
last_item.start_time,
776+
current_time,
777+
last_item.code,
778+
)
755779
if event == "line":
756-
current_time = time.perf_counter()
757780
line_no = frame.f_lineno
758781
filename = frame.f_code.co_filename
759782
# Get the code content of current line
@@ -777,7 +800,7 @@ def trace_calls(self, frame, event, arg):
777800
except Exception:
778801
line_content = "-"
779802
self.line_cache.setdefault(filename, [])
780-
self.line_times.append((line_no, current_time, line_content))
803+
self.line_times.append(LineTime(line_no, current_time, None, line_content))
781804
return self.trace_calls
782805

783806
def _get_function_base_indent(self, func_name: str):
@@ -797,23 +820,23 @@ def calculate_and_print_stats(self, func_name: str):
797820
return
798821
# Calculate base indentation level of function
799822
base_indent = self._get_function_base_indent(func_name)
823+
print(f"{'=' * 95}", file=self.stdout)
800824
print(f"`{func_name}` profiling report:", file=self.stdout)
801825
start_time = time.strftime("%Y-%m-%d %H:%M:%S")
802826
print(
803-
f"{start_time} | Total: {self.total_time * 1000:.3f} ms",
827+
f"Start at {start_time} | Total: {self.total_time * 1000:.3f} ms",
804828
file=self.stdout,
805829
)
806830
# Calculate execution time for each line
807831
line_durations = []
808-
for i in range(1, len(self.line_times)):
809-
prev_time = self.line_times[i - 1][1]
810-
curr_time = self.line_times[i][1]
811-
duration = curr_time - prev_time
832+
for item in self.line_times:
833+
if item.end_time is None:
834+
continue
812835
line_durations.append(
813836
(
814-
self.line_times[i - 1][0], # line number
815-
duration, # duration
816-
self.line_times[i - 1][2], # code content
837+
item.line_no, # line number
838+
item.end_time - item.start_time, # duration
839+
item.code, # code content
817840
)
818841
)
819842
# Sort by line number and merge time for same lines
@@ -882,19 +905,20 @@ def line_profiler(func: Callable) -> Callable:
882905
>>> example_function()
883906
45
884907
>>> output = LineProfiler.stdout.getvalue()
885-
>>> output.splitlines()[0].startswith("`example_function` profiling report:")
908+
>>> output.splitlines()[0].startswith("=" * 95) and "`example_function` profiling report:" in output
886909
True
887910
>>> LineProfiler.stdout = sys.stdout # Restore original stdout
888911
889-
# `example_function` profiling report:
890-
# 2025-07-26 17:09:58 | Total: 1.122 ms
891-
# ===============================================================================================
892-
# Line % Total(ms) Count Avg(ms) Source Code
893-
# -----------------------------------------------------------------------------------------------
894-
# 3 73 0.825 1 0.825 -
895-
# 4 3 0.040 11 0.004 -
896-
# 5 4 0.050 10 0.005 -
897-
# ===============================================================================================
912+
===============================================================================================
913+
`example_function` profiling report:
914+
Start at 2025-07-26 17:09:58 | Total: 1.122 ms
915+
===============================================================================================
916+
Line % Total(ms) Count Avg(ms) Source Code
917+
-----------------------------------------------------------------------------------------------
918+
3 73 0.825 1 0.825 -
919+
4 3 0.040 11 0.004 -
920+
5 4 0.050 10 0.005 -
921+
===============================================================================================
898922
"""
899923

900924
@wraps(func)

0 commit comments

Comments
 (0)