Skip to content

Commit ae2b85c

Browse files
committed
Address major performance issue with recreating requests sessions over and over
1 parent 16c1346 commit ae2b85c

File tree

3 files changed

+91
-3
lines changed

3 files changed

+91
-3
lines changed

pipenv/routines/install.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,6 @@ def do_init(
701701
pypi_mirror=pypi_mirror,
702702
categories=categories,
703703
)
704-
err.print(packages_updated)
705704

706705
if not allow_global and not deploy and "PIPENV_ACTIVE" not in os.environ:
707706
console.print(

pipenv/utils/developers.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import cProfile
2+
import functools
3+
import os
4+
import pstats
5+
import threading
6+
from datetime import datetime
7+
from pstats import SortKey
8+
9+
# Keep track of lock stats
10+
lock_stats = {"acquire_count": 0, "wait_times": [], "acquire_locations": []}
11+
12+
# Patch threading.Lock to track usage
13+
original_lock = threading.Lock
14+
15+
16+
def instrumented_lock():
17+
lock = original_lock()
18+
original_acquire = lock.acquire
19+
20+
@functools.wraps(original_acquire)
21+
def traced_acquire(*args, **kwargs):
22+
import traceback
23+
24+
lock_stats["acquire_count"] += 1
25+
# Get call location
26+
stack = traceback.extract_stack()
27+
# Get the caller's frame (excluding this function)
28+
caller = stack[-2]
29+
lock_stats["acquire_locations"].append(f"{caller.filename}:{caller.lineno}")
30+
return original_acquire(*args, **kwargs)
31+
32+
lock.acquire = traced_acquire
33+
return lock
34+
35+
36+
# Apply the patch
37+
threading.Lock = instrumented_lock
38+
39+
40+
def profile_method(output_dir="profiles"):
41+
"""
42+
Decorator to profile pipenv method execution.
43+
"""
44+
45+
def decorator(func):
46+
@functools.wraps(func)
47+
def wrapper(*args, **kwargs):
48+
# Create output directory if it doesn't exist
49+
os.makedirs(output_dir, exist_ok=True)
50+
51+
# Create profile filename with timestamp
52+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
53+
profile_name = f"{func.__name__}_{timestamp}"
54+
profile_path = os.path.join(output_dir, f"{profile_name}.prof")
55+
56+
# Setup profiler
57+
profiler = cProfile.Profile()
58+
59+
# Start profiling
60+
profiler.enable()
61+
62+
try:
63+
# Run the actual pipenv command
64+
result = func(*args, **kwargs)
65+
66+
return result
67+
finally:
68+
# Stop profiling
69+
profiler.disable()
70+
71+
# Save stats
72+
stats = pstats.Stats(profiler)
73+
stats.sort_stats(SortKey.CUMULATIVE)
74+
stats.dump_stats(profile_path)
75+
print(f"\nProfile saved to: {profile_path}")
76+
stats.print_stats(20)
77+
# Print lock statistics
78+
print("\nLock Statistics:")
79+
print(f"Total lock acquisitions: {lock_stats['acquire_count']}")
80+
print("\nTop 10 lock acquisition locations:")
81+
from collections import Counter
82+
83+
locations = Counter(lock_stats["acquire_locations"])
84+
for loc, count in locations.most_common(10):
85+
print(f"{count:5d} times: {loc}")
86+
87+
return wrapper
88+
89+
return decorator

pipenv/utils/resolver.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import sys
66
import tempfile
77
import warnings
8-
from functools import lru_cache
8+
from functools import cached_property, lru_cache
99
from pathlib import Path
1010
from typing import Dict, List, Optional
1111

@@ -308,7 +308,7 @@ def pip_options(self):
308308
)
309309
return pip_options
310310

311-
@property
311+
@cached_property
312312
def session(self):
313313
return self.pip_command._build_session(self.pip_options)
314314

0 commit comments

Comments
 (0)