CodyKochmann
3/25/2017 - 4:07 PM

Displays detailed statistics in how much memory your python script uses.

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
}