abinit/tests/Scripts/abimem.py

340 lines
12 KiB
Python
Executable File

#!/usr/bin/env python
"""
This script analyzes the `abimem_rank.mocc` files produced by Abinit when
the code is executed in memory-profiling mode.
"""
from __future__ import print_function, division, unicode_literals
__version__ = "0.1.0"
__author__ = "Matteo Giantomassi"
import sys
import os
import re
try:
import argparse
except ImportError:
print("abimem.py requires argparse module and python >= 2.7")
raise
import logging
logger = logging.getLogger(__name__)
from pprint import pprint
# We don't install with setup.py hence we have to add the directory [...]/abinit/tests to $PYTHONPATH
# TODO: Use Absolute imports and rename tests --> abitests to
# avoid possible conflicts with the packages in PYTHONPATH
# monty installs the subpackage paths and this breaks the import below
pack_dir, x = os.path.split(os.path.abspath(__file__))
pack_dir, x = os.path.split(pack_dir)
sys.path.insert(0, pack_dir)
pack_dir, x = os.path.split(pack_dir)
sys.path.insert(0, pack_dir)
try:
from tests.pymods.memprof import AbimemFile
except ImportError:
print("Cannot find tests.pymods.memprof module in PYTHONPATH.")
print("Very likely, you have copied the scripts from ~abinit/test/Scripts to another directory")
print("In this case, you have to add the abinit directory to the python path by executing:\n")
print(" export PYTHONPATH=~/abinit_directory\n")
print("before running the script.")
print("Note that there's no need to change PYTHONPATH if you invoke the script as ~abinit/tests/Scripts/abimem.py")
raise
from tests.pymods.termcolor import cprint
def summarize(options):
"""Print basic info to terminal."""
for memfile in options.memfiles:
print(memfile)
return 0
def leaks(options):
"""Find possible memory leaks."""
retcode = 0
for memfile in options.memfiles:
retcode += memfile.find_memleaks()
return retcode
def small(options):
"""Find small allocations."""
retcode = 0
for memfile in options.memfiles:
smalls = memfile.find_small_allocs() #nbytes=options.nbytes)
return retcode
def large(options):
"""Find large allocations."""
retcode = 0
for memfile in options.memfiles:
larges = memfile.find_large_allocs() #nbytes=options.nbytes)
return retcode
def intense(options):
"""Find routines with intensive allocations."""
retcode = 0
for memfile in options.memfiles:
intens = memfile.get_intense_dataframe()
print(intens)
#df = memfile.get_hotspots_dataframe()
#print(df)
return retcode
def peaks(options):
"""Find memory peaks."""
retcode = 0
maxlen = 20
for memfile in options.memfiles:
#print(memfile.get_peaks(maxlen=maxlen, as_dataframe=True))
for i, peak in enumerate(memfile.get_peaks(maxlen=maxlen)):
print("[%d] %s" % (i, peak))
memfile.plot_peaks(maxlen=maxlen, title="Memory peaks")
return retcode
def weird(options):
"""Find weird allocations. i.e. pointers <= 0."""
retcode = 0
for memfile in options.memfiles:
memfile.find_weird_ptrs()
return retcode
def zerosized(options):
"""Find zero-sized allocations."""
retcode = 0
for memfile in options.memfiles:
elist = memfile.find_zerosized()
if elist:
print("Found %d zero-sized entries:" % len(elist))
pprint(elist)
else:
print("No zero-sized found")
return retcode
def plot(options):
"""Plot data with matplotlib."""
for memfile in options.memfiles:
memfile.expose()
return 0
def panel(options):
"""
Open GUI in web browser, requires panel package
"""
try:
import panel # noqa: F401
except ImportError as exc:
cprint("Use `conda install panel` or `pip install panel` to install the python package.", "red")
raise exc
import matplotlib
matplotlib.use("Agg")
for memfile in options.memfiles:
memfile.get_panel().show()
return 0
def ipython(options):
"""Open file in ipython shell."""
# Start ipython shell with namespace
# Use embed because I don't know how to show a header with start_ipython.
import IPython
abifile = options.memfiles[0]
IPython.embed(header="""
The Abinit file is bound to the `parsers` variable.
Use `abifile.<TAB>` to list available methods.
Use e.g. `abifile.plot?` to access docstring and `abifile.plot??` to visualize source.
Use `print(abifile)` to print the object.
""")
return 0
def get_parser(with_epilog=False):
# Parent parser for common options.
copts_parser = argparse.ArgumentParser(add_help=False)
#copts_parser.add_argument('paths', nargs="+", help="List of ABIMEM files.")
copts_parser.add_argument('paths', nargs="+", help="List of ABIMEM files or directory containing ABIMEM files.")
copts_parser.add_argument('-v', '--verbose', default=0, action='count', # -vv --> verbose=2
help='Verbose, can be supplied multiple times to increase verbosity.')
copts_parser.add_argument('-sns', "--seaborn", const="paper", default=None, action='store', nargs='?', type=str,
help='Use seaborn settings. Accept value defining context in ("paper", "notebook", "talk", "poster"). Default: paper')
copts_parser.add_argument('-mpl', "--mpl-backend", default=None,
help=("Set matplotlib interactive backend. "
"Possible values: GTKAgg, GTK3Agg, GTK, GTKCairo, GTK3Cairo, WXAgg, WX, TkAgg, Qt4Agg, Qt5Agg, macosx."
"See also: https://matplotlib.org/faq/usage_faq.html#what-is-a-backend."))
copts_parser.add_argument('--loglevel', default="ERROR", type=str,
help="Set the loglevel. Possible values: CRITICAL, ERROR (default), WARNING, INFO, DEBUG.")
# Parent parser for commands supporting (ipython/jupyter)
#ipy_parser = argparse.ArgumentParser(add_help=False)
#ipy_parser.add_argument('-nb', '--notebook', default=False, action="store_true", help='Generate jupyter notebook.')
#ipy_parser.add_argument('--foreground', action='store_true', default=False,
# help="Run jupyter notebook in the foreground.")
#ipy_parser.add_argument('-ipy', '--ipython', default=False, action="store_true", help='Invoke ipython terminal.')
# Parent parser for commands supporting (jupyter notebooks)
#nb_parser = argparse.ArgumentParser(add_help=False)
#nb_parser.add_argument('-nb', '--notebook', default=False, action="store_true", help='Generate jupyter notebook.')
#nb_parser.add_argument('--foreground', action='store_true', default=False,
# help="Run jupyter notebook in the foreground.")
# Build the main parser.
parser = argparse.ArgumentParser(epilog=get_epilog() if with_epilog else "",
formatter_class=argparse.RawDescriptionHelpFormatter)
# Create the parsers for the sub-commands
subparsers = parser.add_subparsers(dest='command', help='sub-command help', description="Valid subcommands")
# Build Subparsers for commands
p_summarize = subparsers.add_parser('summarize', parents=[copts_parser], help=summarize.__doc__)
# Build Subparsers for commands
p_leaks = subparsers.add_parser('leaks', parents=[copts_parser], help=leaks.__doc__)
# Subparser for small
p_small = subparsers.add_parser('small', parents=[copts_parser], help=small.__doc__)
# Subparser for large
p_large = subparsers.add_parser('large', parents=[copts_parser], help=large.__doc__)
# Subparser for intense
p_intense = subparsers.add_parser('intense', parents=[copts_parser], help=intense.__doc__)
# Subparser for peaks command.
p_peaks = subparsers.add_parser('peaks', parents=[copts_parser], help=peaks.__doc__)
# Subparser for weird command.
p_weird = subparsers.add_parser('weird', parents=[copts_parser], help=weird.__doc__)
# Subparser for zerosized command.
p_zerosized = subparsers.add_parser('zerosized', parents=[copts_parser], help=zerosized.__doc__)
# Subparser for plot command.
p_plot = subparsers.add_parser('plot', parents=[copts_parser], help=plot.__doc__)
# Subparser for panel command.
p_panel = subparsers.add_parser('panel', parents=[copts_parser], help=panel.__doc__)
# Subparser for ipython command.
p_ipython = subparsers.add_parser('ipython', parents=[copts_parser], help=ipython.__doc__)
return parser
def get_epilog():
return """\
Usage example:
abimem.py summarize [FILES] => Print basic info to terminal
abimem.py leaks [FILES] => Find possible memory leaks in FILE(s)
abimem.py small [FILES] => Find small memory allocations in FILE(s)
abimem.py large [FILES] => Find large memory allocations in FILE(s)
abimem.py intense [FILES] => Find periods of intense memory allocation in FILE(s)
abimem.py peaks [FILES] => Find peaks in memory allocation in FILE(s)
abimem.py plot [FILES] => Plot memory allocations in FILE(s) with matplotlib
abimem.py ipython [FILE] => Open file in ipython shell.
FILES could be either a list of files or a single directory containing abimem_ran.mocc files.
TIP: To profile the python code add `prof` before the command e.g.
abimem.py prof leaks [FILES]
"""
def main():
def show_examples_and_exit(err_msg=None, error_code=1):
"""Display the usage of the script."""
sys.stderr.write(str_examples())
if err_msg: sys.stderr.write("Fatal Error\n" + err_msg + "\n")
sys.exit(error_code)
parser = get_parser(with_epilog=True)
# Parse the command line.
try:
options = parser.parse_args()
except Exception:
show_examples_and_exit(error_code=1)
# Parse command line.
try:
options = parser.parse_args()
except Exception as exc:
show_examples_and_exit(error_code=1)
# loglevel is bound to the string value obtained from the command line argument.
# Convert to upper case to allow the user to specify --loglevel=DEBUG or --loglevel=debug
#numeric_level = getattr(logging, options.loglevel.upper(), None)
#if not isinstance(numeric_level, int):
# raise ValueError('Invalid log level: %s' % options.loglevel)
#logging.basicConfig(level=numeric_level)
if options.seaborn:
# Use seaborn settings.
import seaborn as sns
sns.set(context=options.seaborn, style='darkgrid', palette='deep',
font='sans-serif', font_scale=1, color_codes=False, rc=None)
if os.path.isfile(options.paths[0]):
options.memfiles = [AbimemFile(path) for path in options.paths]
else:
# Walk directory tree and find abimem files.
top = options.paths[0]
if len(options.paths) != 1:
raise ValueError("Expecting one argument with dirname, got %s" % len(options.paths))
if not os.path.isdir(top):
raise ValueError("Expecting existenting directory, got %s" % top)
re_abimem = re.compile("^abimem_rank(\d+)\.mocc$")
paths = []
for dirpath, dirnames, filenames in os.walk(top):
for f in filenames:
if not re_abimem.match(f): continue
paths.append(os.path.join(dirpath, f))
options.paths = paths
print("Will analyze %s abimem file(s)" % len(paths))
if not paths: sys.exit(1)
options.memfiles = [AbimemFile(path) for path in paths]
# Dispatch
return globals()[options.command](options)
if __name__ == "__main__":
# Check whether we are in profiling mode
try:
do_prof = sys.argv[1] == "prof"
if do_prof: sys.argv.pop(1)
except:
do_prof = False
if not do_prof:
sys.exit(main())
else:
import pstats, cProfile
cProfile.runctx("main()", globals(), locals(), "Profile.prof")
s = pstats.Stats("Profile.prof")
s.strip_dirs().sort_stats("time").print_stats()