-
Notifications
You must be signed in to change notification settings - Fork 378
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Independent child process monitoring #118 #134
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
0d956a1
multiprocessing example
bbengfort a846fa6
child process memory now recorded seperately in mpmprof
bbengfort e32679b
plot function for mpmproc
bbengfort bdf9995
merge with upstream
bbengfort aab15ed
merge mpmprof into mprof
bbengfort f1cee23
added max child usage marker and update readme
bbengfort 099c5c8
fixed image
bbengfort 9c62f0a
return child as nested list from memory_usage
bbengfort d333fda
minor update to readme
bbengfort File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,6 @@ MANIFEST | |
*.egg-info | ||
*.pyc | ||
*~ | ||
|
||
# Ignore mprof generated files | ||
mprofile_*.dat |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
""" | ||
An undecorated example of a script that allocates memory in multiprocessing | ||
workers to demonstrate the use of memory_profiler with multiple processes. | ||
|
||
Run this script with mprof run -M python multiprocessing_example.py | ||
You can then visualize the usage with mprof plot. | ||
""" | ||
|
||
import time | ||
import multiprocessing as mp | ||
|
||
# Big numbers | ||
X6 = 10 ** 6 | ||
X7 = 10 ** 7 | ||
|
||
|
||
def worker(num, wait, amt=X6): | ||
""" | ||
A function that allocates memory over time. | ||
""" | ||
frame = [] | ||
|
||
for idx in range(num): | ||
frame.extend([1] * amt) | ||
time.sleep(wait) | ||
|
||
del frame | ||
|
||
|
||
def main_sequential(): | ||
""" | ||
A sequential version of the work, where one worker is called at a time. | ||
""" | ||
worker(5, 5, X6) | ||
worker(5, 2, X7) | ||
worker(5, 5, X6) | ||
worker(5, 2, X7) | ||
|
||
|
||
def main_multiproc(): | ||
""" | ||
A multiprocessing version of the work, where workers work in their own | ||
child processes and are collected by the master process. | ||
""" | ||
pool = mp.Pool(processes=4) | ||
tasks = [ | ||
pool.apply_async(worker, args) for args in | ||
[(5, 5, X6), (5, 2, X7), (5, 5, X6), (5, 2, X7)] | ||
] | ||
|
||
results = [p.get() for p in tasks] | ||
|
||
|
||
if __name__ == '__main__': | ||
main_multiproc() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -86,6 +86,38 @@ def _repr_pretty_(self, p, cycle): | |
p.text(u'<MemitResult : ' + msg + u'>') | ||
|
||
|
||
def _get_child_memory(process, meminfo_attr=None): | ||
""" | ||
Returns a generator that yields memory for all child processes. | ||
""" | ||
if not has_psutil: | ||
raise NotImplementedError(( | ||
"The psutil module is required to monitor the " | ||
"memory usage of child processes." | ||
)) | ||
|
||
# Convert a pid to a process | ||
if isinstance(process, int): | ||
if process == -1: process = os.getpid() | ||
process = psutil.Process(process) | ||
|
||
if not meminfo_attr: | ||
# Use the psutil 2.0 attr if the older version isn't passed in. | ||
meminfo_attr = 'memory_info' if hasattr(process, 'memory_info') else 'get_memory_info' | ||
|
||
# Select the psutil function get the children similar to how we selected | ||
# the memory_info attr (a change from excepting the AttributeError). | ||
children_attr = 'children' if hasattr(process, 'children') else 'get_children' | ||
|
||
# Loop over the child processes and yield their memory | ||
try: | ||
for child in getattr(process, children_attr)(recursive=True): | ||
yield getattr(child, meminfo_attr)()[0] / _TWO_20 | ||
except psutil.NoSuchProcess: | ||
# https://github.com/fabianp/memory_profiler/issues/71 | ||
yield 0.0 | ||
|
||
|
||
def _get_memory(pid, backend, timestamps=False, include_children=False, filename=None): | ||
# .. low function to get memory consumption .. | ||
if pid == -1: | ||
|
@@ -111,12 +143,7 @@ def ps_util_tool(): | |
else 'get_memory_info' | ||
mem = getattr(process, meminfo_attr)()[0] / _TWO_20 | ||
if include_children: | ||
try: | ||
for p in process.children(recursive=True): | ||
mem += getattr(p, meminfo_attr)()[0] / _TWO_20 | ||
except psutil.NoSuchProcess: | ||
# https://github.com/fabianp/memory_profiler/issues/71 | ||
pass | ||
mem += sum(_get_child_memory(process, meminfo_attr)) | ||
if timestamps: | ||
return mem, time.time() | ||
else: | ||
|
@@ -128,9 +155,11 @@ def ps_util_tool(): | |
def posix_tool(): | ||
# .. scary stuff .. | ||
if include_children: | ||
raise NotImplementedError('The psutil module is required when to' | ||
' monitor memory usage of children' | ||
' processes') | ||
raise NotImplementedError(( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry about this, I think this was just a carry over from the historical merge; I can reset back to the original if needed. |
||
"The psutil module is required to monitor the " | ||
"memory usage of child processes." | ||
)) | ||
|
||
warnings.warn("psutil module not found. memory_profiler will be slow") | ||
# .. | ||
# .. memory usage in MiB .. | ||
|
@@ -211,8 +240,8 @@ def run(self): | |
|
||
|
||
def memory_usage(proc=-1, interval=.1, timeout=None, timestamps=False, | ||
include_children=False, max_usage=False, retval=False, | ||
stream=None, backend=None): | ||
include_children=False, multiprocess=False, max_usage=False, | ||
retval=False, stream=None, backend=None): | ||
""" | ||
Return the memory usage of a process or piece of code | ||
|
||
|
@@ -243,6 +272,12 @@ def memory_usage(proc=-1, interval=.1, timeout=None, timestamps=False, | |
timestamps : bool, optional | ||
if True, timestamps of memory usage measurement are collected as well. | ||
|
||
include_children : bool, optional | ||
if True, sum the memory of all forked processes as well | ||
|
||
multiprocess : bool, optional | ||
if True, track the memory usage of all forked processes. | ||
|
||
stream : File | ||
if stream is a File opened with write access, then results are written | ||
to this file instead of stored in memory and returned at the end of | ||
|
@@ -314,9 +349,22 @@ def memory_usage(proc=-1, interval=.1, timeout=None, timestamps=False, | |
mem_usage = _get_memory( | ||
proc.pid, backend, timestamps=timestamps, | ||
include_children=include_children) | ||
|
||
if stream is not None: | ||
stream.write("MEM {0:.6f} {1:.4f}\n".format(*mem_usage)) | ||
|
||
# Write children to the stream file | ||
if multiprocess: | ||
for idx, chldmem in enumerate(_get_child_memory(proc.pid)): | ||
stream.write("CHLD {0} {1:.6f} {2:.4f}\n".format(idx, chldmem, time.time())) | ||
else: | ||
# Create a nested list with the child memory | ||
if multiprocess: | ||
mem_usage = [mem_usage] | ||
for chldmem in _get_child_memory(proc.pid): | ||
mem_usage.append(chldmem) | ||
|
||
# Append the memory usage to the return value | ||
ret.append(mem_usage) | ||
else: | ||
ret = max(ret, | ||
|
@@ -348,7 +396,19 @@ def memory_usage(proc=-1, interval=.1, timeout=None, timestamps=False, | |
include_children=include_children) | ||
if stream is not None: | ||
stream.write("MEM {0:.6f} {1:.4f}\n".format(*mem_usage)) | ||
|
||
# Write children to the stream file | ||
if multiprocess: | ||
for idx, chldmem in enumerate(_get_child_memory(proc.pid)): | ||
stream.write("CHLD {0} {1:.6f} {2:.4f}\n".format(idx, chldmem, time.time())) | ||
else: | ||
# Create a nested list with the child memory | ||
if multiprocess: | ||
mem_usage = [mem_usage] | ||
for chldmem in _get_child_memory(proc.pid): | ||
mem_usage.append(chldmem) | ||
|
||
# Append the memory usage to the return value | ||
ret.append(mem_usage) | ||
else: | ||
ret = max([ret, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, quick question, as processes in modern linux share common memory after forking from their parent and only copy it on writes, wouldn't this
sum
report the wrong number?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately I'm not sure of the behavior of psutil with regards to linux forks; I simply used the same meminfo_attr as the summation function. If that's true then this is indeed a problem, but I'd guess that we'd have to dig into psutil to figure it out.