Displays detailed statistics in how much memory your python script uses.
#!/usr/bin/env python
# prints memory usage used when running a python file
from __future__ import print_function
from subprocess import Popen, PIPE
from sys import argv
from os.path import isfile
from os import environ
from getpass import getuser
from json import dumps
__all__ = 'script_memory_usage',
__doc__='''
Displays detailed statistics in how much memory your python script uses.
if you dont have a debug version of python compileded, build it following these
directions they make it easy.
http://pythonextensionpatterns.readthedocs.io/en/latest/debugging/debug_python.html
The "python_debug" is a symlink I made to the executable 'python' file that is
sitting in that directory once I compiled it. That was made with this command
sitting in the home directory. Feel free to put it anywhere though, the compilation
seems to be stable no matter where you move it.
ln -s ./Python-2.7.13/debug/python ./python_debug '''
__author__='Cody Kochmann'
__email__='kochmanncody@gmail.com'
# path to your debug compilation of python
DEBUG_PATH='/home/{}/python_debug'.format(getuser())
# enable the stats we want
environ['PYTHONMALLOCSTATS']='1'
# reads a file and splits the output
readlines = lambda i:tuple(ii.replace('\n','') for ii in open(i,'r').readlines())
# removes empty strings from a list of strings
remove_empty_strings = lambda i:tuple(ii for ii in i if len(ii.strip()))
# extracts what are technically the keys of the lines
line_keys=lambda i:(ii.split(' ')[0].replace('# ','') for ii in remove_empty_strings(i))
# extracts what are technically the values of the lines
line_values = lambda i:(int(ii.split(' ')[-1].replace(',','')) for ii in remove_empty_strings(i))
# converts the list of lines into a dict that can be processed
make_data_usable = lambda i:dict(zip(line_keys(i),line_values(i)))
# joins two dicts with eachother and subtracts the bigger value from the smaller.
subtract_similar_keys = lambda a,b:{k:(b[k]-a[k] if b[k]>a[k] else a[k]-b[k]) for k in a if k in b}
# takes two file names and parses a dict with the numeric difference between the two
parse_logs = lambda a,b:subtract_similar_keys(make_data_usable(a.split('\n')),make_data_usable(b.split('\n')))
# runs a python file with the custom python setup
run_file = lambda i:Popen([DEBUG_PATH, i], stdout=PIPE, stderr=PIPE).communicate()[1] # 1 is here because we need stderr
# cleans the output of getting the memory info
clean_test_output = lambda i:'\n'.join(i.split('\n\n')[-2:])
# returns the memory information parsed from stderr
memory_info = lambda i:clean_test_output(run_file(i))
# runs a dry run to see how much memory python uses by default
dry_run = lambda:Popen([DEBUG_PATH,'-c','""'], stdout=PIPE, stderr=PIPE).communicate()[1] # 1 is here because we need stderr
# returns the memory info from a dry run
dry_run_info = lambda:clean_test_output(dry_run())
prettyfy = lambda i:print(dumps(i, sort_keys=True, indent=4, separators=(',', ': ')))
# returns a dict with the memory usage of a python script
script_memory_usage = lambda i:parse_logs(memory_info(i),dry_run_info())
# this is literally just an alias to give it both a main and a named primary funtion
main = script_memory_usage
if __name__ == '__main__':
assert isfile(argv[-1])
error_message = 'add a script you want to run as an argument (other than specifically this one)'
assert __file__.split('/')[-1] != argv[-1].split('/')[-1], error_message
prettyfy(main(argv[-1]))
cody@G505:~$ python script_memory_usage.py zict.py
{
"Total": 786432,
"arenas allocated current": 3,
"arenas allocated total": 7,
"arenas highwater mark": 4,
"arenas reclaimed": 4,
"bytes in allocated blocks": 464560,
"bytes in available blocks": 311248,
"bytes lost to arena alignment": 0,
"bytes lost to pool headers": 9360,
"bytes lost to quantization": 13552,
"times object malloc called": 224358
}