Skip to content

Commit 68f2950

Browse files
authored
Merge pull request #255 from d-ryzhikov/async-profile
Add support for coroutine profiling
2 parents 895c4ac + f9d95ef commit 68f2950

6 files changed

Lines changed: 75 additions & 56 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ mprofile_*.dat
1111

1212
# virtual environment
1313
venv/
14+
.python-version

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
language: python
22
python:
3-
- "2.7"
43
- "3.4"
54
- "3.5"
65
- "3.6"

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
PYTHON ?= python
22

3-
.PHONY: test
3+
.PHONY: test develop
44

55
test:
66
$(PYTHON) -m memory_profiler test/test_func.py
@@ -18,6 +18,7 @@ test:
1818
$(PYTHON) test/test_exception.py
1919
$(PYTHON) test/test_exit_code.py
2020
$(PYTHON) test/test_mprof.py
21+
$(PYTHON) test/test_async.py
2122

2223
develop:
2324
pip install -e .

memory_profiler.py

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77

88
_CMD_USAGE = "python -m memory_profiler script_file.py"
99

10-
from functools import wraps
10+
from asyncio import coroutine, iscoroutinefunction
11+
from contextlib import contextmanager
12+
from functools import partial, wraps
13+
import builtins
1114
import inspect
1215
import linecache
1316
import logging
@@ -18,7 +21,6 @@
1821
import time
1922
import traceback
2023
import warnings
21-
import contextlib
2224

2325
if sys.platform == "win32":
2426
# any value except signal.CTRL_C_EVENT and signal.CTRL_BREAK_EVENT
@@ -43,17 +45,8 @@
4345
line_cell_magic = lambda func: func
4446
magics_class = lambda cls: cls
4547

46-
PY2 = sys.version_info[0] == 2
47-
4848
_TWO_20 = float(2 ** 20)
4949

50-
if PY2:
51-
import __builtin__ as builtins
52-
to_str = lambda x: x
53-
from future_builtins import filter
54-
else:
55-
import builtins
56-
to_str = lambda x: str(x)
5750

5851
# .. get available packages ..
5952
try:
@@ -580,7 +573,7 @@ def f(*args, **kwds):
580573

581574
return f
582575

583-
@contextlib.contextmanager
576+
@contextmanager
584577
def call_on_stack(self, func, *args, **kwds):
585578
self.current_stack_level += 1
586579
self.stack[func].append(self.current_stack_level)
@@ -701,16 +694,27 @@ def add_function(self, func):
701694
else:
702695
self.code_map.add(code)
703696

697+
@contextmanager
698+
def _count_ctxmgr(self):
699+
self.enable_by_count()
700+
try:
701+
yield
702+
finally:
703+
self.disable_by_count()
704+
704705
def wrap_function(self, func):
705706
""" Wrap a function to profile it.
706707
"""
707708

708-
def f(*args, **kwds):
709-
self.enable_by_count()
710-
try:
711-
return func(*args, **kwds)
712-
finally:
713-
self.disable_by_count()
709+
if iscoroutinefunction(func):
710+
@coroutine
711+
def f(*args, **kwargs):
712+
with self._count_ctxmgr():
713+
yield from func(*args, **kwargs)
714+
else:
715+
def f(*args, **kwds):
716+
with self._count_ctxmgr():
717+
return func(*args, **kwds)
714718

715719
return f
716720

@@ -831,7 +835,7 @@ def show_results(prof, stream=None, precision=1):
831835
inc = u''
832836
occurences = u''
833837
tmp = template.format(lineno, total_mem, inc, occurences, all_lines[lineno - 1])
834-
stream.write(to_str(tmp))
838+
stream.write(tmp)
835839
stream.write(u'\n\n')
836840

837841

@@ -1121,12 +1125,23 @@ def profile(func=None, stream=None, precision=1, backend='psutil'):
11211125
if not tracemalloc.is_tracing():
11221126
tracemalloc.start()
11231127
if func is not None:
1124-
@wraps(func)
1125-
def wrapper(*args, **kwargs):
1126-
prof = LineProfiler(backend=backend)
1127-
val = prof(func)(*args, **kwargs)
1128-
show_results(prof, stream=stream, precision=precision)
1129-
return val
1128+
get_prof = partial(LineProfiler, backend=backend)
1129+
show_results_bound = partial(
1130+
show_results, stream=stream, precision=precision
1131+
)
1132+
if iscoroutinefunction(func):
1133+
@coroutine
1134+
def wrapper(*args, **kwargs):
1135+
prof = get_prof()
1136+
val = yield from prof(func)(*args, **kwargs)
1137+
show_results_bound(prof)
1138+
return val
1139+
else:
1140+
def wrapper(*args, **kwargs):
1141+
prof = get_prof()
1142+
val = prof(func)(*args, **kwargs)
1143+
show_results_bound(prof)
1144+
return val
11301145

11311146
return wrapper
11321147
else:
@@ -1198,16 +1213,13 @@ def run_module_with_profiler(module, profiler, backend, passed_args=[]):
11981213
ns = dict(_CLEAN_GLOBALS, profile=profiler)
11991214
_backend = choose_backend(backend)
12001215
sys.argv = [module] + passed_args
1201-
if PY2:
1216+
if _backend == 'tracemalloc' and has_tracemalloc:
1217+
tracemalloc.start()
1218+
try:
12021219
run_module(module, run_name="__main__", init_globals=ns)
1203-
else:
1204-
if _backend == 'tracemalloc' and has_tracemalloc:
1205-
tracemalloc.start()
1206-
try:
1207-
run_module(module, run_name="__main__", init_globals=ns)
1208-
finally:
1209-
if has_tracemalloc and tracemalloc.is_tracing():
1210-
tracemalloc.stop()
1220+
finally:
1221+
if has_tracemalloc and tracemalloc.is_tracing():
1222+
tracemalloc.stop()
12111223

12121224

12131225
class LogFile(object):

setup.py

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,14 @@
1-
import os
2-
import io
31
import re
42
from setuptools import setup
53

64

75
# https://packaging.python.org/guides/single-sourcing-package-version/
8-
def read(*names, **kwargs):
9-
with io.open(
10-
os.path.join(os.path.dirname(__file__), *names),
11-
encoding=kwargs.get("encoding", "utf8")
12-
) as fp:
13-
return fp.read()
14-
15-
16-
def find_version(*file_paths):
17-
version_file = read(*file_paths)
18-
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
19-
version_file, re.M)
6+
def find_version(file_paths):
7+
with open(file_paths) as f:
8+
version_file = f.read()
9+
version_match = re.search(
10+
r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M
11+
)
2012
if version_match:
2113
return version_match.group(1)
2214

@@ -27,18 +19,14 @@ def find_version(*file_paths):
2719
Intended Audience :: Developers
2820
License :: OSI Approved :: BSD License
2921
Programming Language :: Python
30-
Programming Language :: Python :: 2
31-
Programming Language :: Python :: 2.6
32-
Programming Language :: Python :: 2.7
3322
Programming Language :: Python :: 3
34-
Programming Language :: Python :: 3.2
35-
Programming Language :: Python :: 3.3
3623
Topic :: Software Development
3724
Operating System :: POSIX
3825
Operating System :: Unix
3926
4027
"""
4128

29+
4230
setup(
4331
name='memory_profiler',
4432
description='A module for monitoring memory usage of a python program',
@@ -49,9 +37,10 @@ def find_version(*file_paths):
4937
url='https://github.com/pythonprofilers/memory_profiler',
5038
py_modules=['memory_profiler', 'mprof'],
5139
entry_points={
52-
'console_scripts' : ['mprof = mprof:main'],
40+
'console_scripts': ['mprof = mprof:main'],
5341
},
5442
install_requires=['psutil'],
43+
python_requires='>=3.4',
5544
classifiers=[_f for _f in CLASSIFIERS.split('\n') if _f],
5645
license='BSD'
5746
)

test/test_async.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import asyncio
2+
3+
from memory_profiler import profile
4+
5+
6+
@profile
7+
@asyncio.coroutine
8+
def my_func():
9+
a = [1] * (10 ** 6)
10+
b = [2] * (2 * 10 ** 7)
11+
yield from asyncio.sleep(1e-2)
12+
del b
13+
14+
15+
if __name__ == '__main__':
16+
loop = asyncio.get_event_loop()
17+
loop.run_until_complete(my_func())

0 commit comments

Comments
 (0)