###################################### # # MEM VISUALIZE PARSER # Eric J Anderson # # cd X:\payne\src\dev\rage\base\tools\misctools # python mem_visualize_parser.py
  [output_dir]
#
# Example: X:\payne\src\dev\rage\base\tools\misctools>python mem_visualize_parser.py pre.txt post.txt
#
######################################

import os
import os.path
import re
import sys

######################################
# GLOBAL
#
g_stack = []
g_window = 50

g_pre_path = ""
g_pre_lines = []
g_pre_data = {}

g_post_path = ""
g_post_lines = []
g_post_data = {}

g_output_dir = "X:\\"
g_pre_output_data = []
g_post_output_data = []
g_leak_overview = []

######################################
# USAGE
#
def get_filename(path):
	# Directory
	filename = path
	index = filename.rfind('\\')
	if index > -1:
		filename = filename[index+1:len(filename)]
	else:
		index = filename.rfind('/')
		if index > -1:
			filename = filename[index+1:len(filename)]
	
	# Extension
	#index = filename.rfind('.')
	#if index > -1:
	#	filename = filename[0:index]
		
	return filename

def usage():
	if len(sys.argv) < 3:
		print 'USAGE: python mem_visualize_parser.py 
  [output_dir]'
		exit(-1)
	
	global g_pre_path
	g_pre_path = sys.argv[1]
	print "Pre Path:\t", g_pre_path
	
	global g_post_path
	g_post_path = sys.argv[2]
	print "Post Path:\t", g_post_path
	
	global g_output_dir
	if len(sys.argv) > 3 and os.path.isdir(sys.argv[3]):
		g_output_dir = sys.argv[3]
		if not (g_output_dir.endswith('\\') or g_output_dir.endswith('/')):
			g_output_dir = g_output_dir + '\\'
	
	print "Output Dir:\t", g_output_dir

######################################
# PARSE
#
def get_space(line):
	spaces = 0
	for ch in line:
		if ch != ' ':
			break
		spaces = spaces + 1
	
	return spaces

def get_key(line):
	key = ""
	index = line.find('|')
	if index > -1:
		key = line[0:index + 1]
		
	return key

def get_size(line):	
	num = 0
	end = line.find("(in ")
	if end > -1:
		begin = line.rfind(" | ", 0, end)
		if begin > -1:
			num = int(line[begin+3:end-1])
		
	return num
	
def get_allocs(line):	
	num = 0
	index = line.find("(in ")
	if index > -1:
		num = int(line[index+4:line.find(" allocations")])
		
	return num

def get_prefix(line):
	data = ""
	index = line.find(" | ")
	if index > -1:
		data = line[0:index]
		
	return data
	
def get_postfix(line):
	data = ""
	index = line.rfind(" | ")
	if index > -1:
		data = line[index+3:len(line)]
	
	return data

######################################
# UTILITY
#
def progress(value):
	if 0 == (value % 20):
		sys.stdout.write("\b|")
	elif 0 == (value % 15):
		sys.stdout.write("\b\\")
	elif 0 == (value % 10):
		sys.stdout.write("\b-")
	elif 0 == (value % 5):
		sys.stdout.write("\b/")

def print_stack():
	for line in g_stack:
		print line

def create_key():
	data = ""
	for line in g_stack:
		data = data + line + '\n'
	return data
	
######################################
# READ
#
def create_data(line_array):
	if len(line_array) == 0:
		return {}
	
	global g_stack
	g_stack = []
	line_map = {}
	
	i = 0
	for line in line_array:
		prefix = get_prefix(line)
		spaces = get_space(line)		
		
		if i == 0:
			g_stack.append(prefix)
		else:
			unwind = len(g_stack)
			if spaces < unwind:
				# Save the callstack
				prev_line = line_array[i - 1]
				line_map[create_key()] = [get_size(prev_line), get_allocs(prev_line)]
				while spaces < unwind:
					g_stack.pop()
					spaces += 1
			
			g_stack.append(prefix)
		
		i += 1
	
	# Save the callstack
	line_map[create_key()] = [get_size(line), get_allocs(line)]
	
	return line_map

def read():
	# Pre
	global g_pre_lines
	global g_pre_data
	fin = open(g_pre_path, "r")
	g_pre_lines = fin.readlines()
	g_pre_data = create_data(g_pre_lines)
	fin.close()
	
	'''
	for key, data in g_pre_data.items():
		print key, " - ", "Size: ", data[0]
	'''
	
	# Post
	global g_post_lines
	global g_post_data
	fin = open(g_post_path, "r")		
	g_post_lines = fin.readlines()
	g_post_data = create_data(g_post_lines)
	fin.close()

######################################
# WRITE
#
def write():
	pre_filename = get_filename(g_pre_path)
	post_filename = get_filename(g_post_path)
	
	# Pre
	i = 0
	fout = open(g_output_dir + pre_filename + "__" + post_filename, "w")
	for data in g_pre_output_data:
		fout.write(str(i) + " | " + str(data[1]) + " bytes | " + str(data[2]) + " allocations\n")
		fout.write(data[0] + "\n")
		i += 1
	fout.close()
	
	# Post
	i = 0
	fout = open(g_output_dir + post_filename + "__" + pre_filename, "w")
	for data in g_post_output_data:
		fout.write(str(i) + " | " + str(data[1]) + " (+" + str(data[1] - g_pre_output_data[i][1]) + ") bytes | " + str(data[2]) + " (+" + str(data[2] - g_pre_output_data[i][2]) + ") allocations\n")
		fout.write(data[0] + "\n")
		i += 1
	fout.close()
	
	# Leak
	fout = open(g_output_dir + post_filename + "__" + pre_filename + "__overview.txt", "w")
	fout.write("[MEMORY LEAKS]\n")
	for item in g_leak_overview:
		fout.write(item[0] + " | " + str(item[1]) + " | " + str(item[2]) + '\n')
	fout.close()

######################################
# COMPARE
#
def insensitive_compare(x, y):
	return cmp(x.lower(), y.lower())

def insensitive_compare_array(x, y):
	return cmp(x[0].lower(), y[0].lower())
	
def compare():
	global g_leak_overview
	global g_pre_output_data
	global g_post_output_data
	
	i = 1
	
	# Sort the
	post_data_keys = []
	for key in g_post_data.keys():
		post_data_keys.append(key)
	post_data_keys.sort(cmp = insensitive_compare)
	
	# Debug
	for post_key in post_data_keys:
		progress(i)
		
		post_data = g_post_data[post_key]
		post_size = post_data[0]
		post_allocs = post_data[1]
		
		if g_pre_data.has_key(post_key):
			pre_data = g_pre_data[post_key]
			pre_size = pre_data[0]
			pre_allocs = pre_data[1]
			
			if (post_allocs > pre_allocs) and (post_size > pre_size):
				index = post_key.rfind("  ")
				function = post_key[index+2:len(post_key)-1]
				g_leak_overview.append([function, post_size - pre_size, post_allocs - pre_allocs])
				g_pre_output_data.append([post_key, pre_size, pre_allocs])
				g_post_output_data.append([post_key, post_size, post_allocs])
		
		i += 1
	
	# Debug
	if len(g_leak_overview) > 0:
		print "\n[MEMORY LEAKS]"
		g_leak_overview.sort(cmp = insensitive_compare_array)
		for item in g_leak_overview:
			print item[0], " | ", item[1], " | ", item[2]

######################################
# MAIN
#
def main():
	usage()
	read()
	compare()
	write()
	
main()