Initial revision
diff --git a/Lib/profile.doc b/Lib/profile.doc
new file mode 100644
index 0000000..753d159
--- /dev/null
+++ b/Lib/profile.doc
@@ -0,0 +1,74 @@
+The Python Profiler
+
+To use the profiler in its simplest form:
+
+	>>> import profile
+	>>> profile.run(statement)
+
+This will execute the statement and print statistics.  To get more
+information out of the profiler, use:
+
+	>>> import profile
+	>>> profile.run(statement, dump_file)
+
+where dump_file is a string naming a file to which the (binary)
+profile statistics is to be dumped.  The binary format is a dump of a
+dictionary.  The key is the function name in the format described
+above; the value is a tuple consisting of, in order, number of calls,
+total time spent in the function, total time spent in the function and
+all functions called from it, a list of functions called by this
+function, and a list of functions that called this function.  The dump
+can be read back using the following code:
+
+	>>> import marshal
+	>>> f = open(dump_file, 'r')
+	>>> dict = marshal.load(f)
+	>>> f.close()
+
+An easier way of doing this is by using the class `Stats' which is
+also defined in profile:
+
+	>>> import profile
+	>>> s = profile.Stats().init(dump_file)
+
+The following methods are defined for instances of `Stats':
+
+	print_stats()	-- Print the statistics in a format similar to
+			   the format profile.run() uses.
+	print_callers()	-- For each function, print all functions
+			   which it calls.
+	print_callees()	-- For each function, print all functions from
+			   which it is called.
+	sort_stats(n)	-- Sort the statistics for subsequent
+			   printing.  The argument determines on which
+			   field the output should be sorted.
+			   Possibilities are
+				-1	function name
+				0	number of calls
+				1	total time spent in a function
+				2	total time spent in a function
+					plus all functions it called
+	strip_dirs()	-- Strip the directory names off of the file
+			   names which are part of the function names.
+			   This undoes the effect of sort_stats(), but
+			   a subsequent sort_stats() does work.
+
+The methods sort_stats and strip_dirs may change in the future.
+
+Output of profile.run(statement) and of the print_stats() method of
+the `Stats' class consists of the following fields.
+
+	Number of times the function was called.
+	Total time spent in the function.
+	Mean time per function call (second field divided by first).
+	Total time spent in the function and all functions it called,
+		recursively.
+	Mean time time spent in the function and all functions it
+		called (fourth field divided by first).
+	Name of the function in the format
+		<file name>:<line number>(<function name>)
+
+The output of the print_callers and print_callees methods consists of
+the name of the function and the names of all function it called or
+was called from.  The latter names are followed by a parenthesised
+number which is the number of calls for this function.
diff --git a/Lib/profile.py b/Lib/profile.py
new file mode 100755
index 0000000..1c7a6e2
--- /dev/null
+++ b/Lib/profile.py
@@ -0,0 +1,405 @@
+#
+# Class for profiling python code.
+# Author: Sjoerd Mullender
+#
+# See the accompanying document profile.doc for more information.
+
+import sys
+import codehack
+import posix
+
+class Profile():
+
+	def init(self):
+		self.timings = {}
+		self.debug = None
+		self.call_level = 0
+		self.profile_func = None
+		self.profiling = 0
+		return self
+
+	def profile(self, funcname):
+		if not self.profile_func:
+			self.profile_func = {}
+		self.profile_func[funcname] = 1
+
+	def trace_dispatch(self, frame, event, arg):
+		if event == 'call':
+			funcname = codehack.getcodename(frame.f_code)
+			if self.profile_func and not self.profiling:
+				if self.profile_func.has_key(funcname):
+					return
+				self.profiling = 1
+			t = posix.times()
+			t = t[0] + t[1]
+			lineno = codehack.getlineno(frame.f_code)
+			filename = frame.f_code.co_filename
+			key = filename + ':' + `lineno` + '(' + funcname + ')'
+			self.call_level = depth(frame)
+			self.cur_frame = frame
+			pframe = frame.f_back
+			if self.debug:
+				s0 = 'call: ' + key + ' depth: ' + `self.call_level` + ' time: ' + `t`
+			if pframe:
+				pkey = pframe.f_code.co_filename + ':' + \
+					  `codehack.getlineno(pframe.f_code)` + '(' + \
+					  codehack.getcodename(pframe.f_code) + ')'
+				if self.debug:
+					s1 = 'parent: ' + pkey
+				if pframe.f_locals.has_key('__start_time'):
+					st = pframe.f_locals['__start_time']
+					nc, tt, ct, callers, callees = self.timings[pkey]
+					if self.debug:
+						s1 = s1+' before: st='+`st`+' nc='+`nc`+' tt='+`tt`+' ct='+`ct`
+					if callers.has_key(key):
+						callers[key] = callers[key] + 1
+					else:
+						callers[key] = 1
+					if self.debug:
+						s1 = s1+' after: st='+`st`+' nc='+`nc`+' tt='+`tt+(t-st)`+' ct='+`ct`
+					self.timings[pkey] = nc, tt + (t - st), ct, callers, callees
+			if self.timings.has_key(key):
+				nc, tt, ct, callers, callees = self.timings[key]
+			else:
+				nc, tt, ct, callers, callees = 0, 0, 0, {}, {}
+			if self.debug:
+				s0 = s0+' before: nc='+`nc`+' tt='+`tt`+' ct='+`ct`
+				s0 = s0+' after: nc='+`nc+1`+' tt='+`tt`+' ct='+`ct`
+			if pframe:
+				if callees.has_key(pkey):
+					callees[pkey] = callees[pkey] + 1
+				else:
+					callees[pkey] = 1
+			self.timings[key] = nc + 1, tt, ct, callers, callees
+			frame.f_locals['__start_time'] = t
+			if self.debug:
+				print s0
+				print s1
+			return
+		if event == 'return':
+			if self.profile_func:
+				if not self.profiling:
+					return
+				if self.profile_func.has_key(codehack.getcodename(frame.f_code)):
+					self.profiling = 0
+			self.call_level = depth(frame)
+			self.cur_frame = frame
+			pframe = frame.f_back
+			if self.debug:
+				s0 = 'return: '
+			else:
+				s0 = None
+			self.handle_return(pframe, frame, s0)
+			return
+		if event == 'exception':
+			if self.profile_func and not self.profiling:
+				return
+			call_level = depth(frame)
+			if call_level < self.call_level:
+				if call_level <> self.call_level - 1:
+					print 'heh!',call_level,self.call_level
+				if self.debug:
+					s0 = 'exception: '
+				else:
+					s0 = None
+				self.handle_return(self.cur_frame, frame, s0)
+			self.call_level = call_level
+			self.cur_frame = frame
+			return
+		print 'profile.Profile.dispatch: unknown debugging event:', `event`
+		return
+
+	def handle_return(self, pframe, frame, s0):
+		t = posix.times()
+		t = t[0] + t[1]
+		funcname = codehack.getcodename(frame.f_code)
+		lineno = codehack.getlineno(frame.f_code)
+		filename = frame.f_code.co_filename
+		key = filename + ':' + `lineno` + '(' + funcname + ')'
+		if self.debug:
+			s0 = s0 + key + ' depth: ' + `self.call_level` + ' time: ' + `t`
+		if pframe:
+			funcname = codehack.getcodename(frame.f_code)
+			lineno = codehack.getlineno(frame.f_code)
+			filename = frame.f_code.co_filename
+			pkey = filename + ':' + `lineno` + '(' + funcname + ')'
+			if self.debug:
+				s1 = 'parent: '+pkey
+			if pframe.f_locals.has_key('__start_time') and \
+				  self.timings.has_key(pkey):
+				st = pframe.f_locals['__start_time']
+				nc, tt, ct, callers, callees = self.timings[pkey]
+				if self.debug:
+					s1 = s1+' before: st='+`st`+' nc='+`nc`+' tt='+`tt`+' ct='+`ct`
+					s1 = s1+' after: st='+`t`+' nc='+`nc`+' tt='+`tt`+' ct='+`ct+(t-st)`
+				self.timings[pkey] = nc, tt, ct + (t - st), callers, callees
+				pframe.f_locals['__start_time'] = t
+		if self.timings.has_key(key):
+			nc, tt, ct, callers, callees = self.timings[key]
+		else:
+			nc, tt, ct, callers, callees = 0, 0, 0, {}, {}
+		if frame.f_locals.has_key('__start_time'):
+			st = frame.f_locals['__start_time']
+		else:
+			st = t
+		if self.debug:
+			s0 = s0+' before: st='+`st`+' nc='+`nc`+' tt='+`tt`+' ct='+`ct`
+			s0 = s0+' after: nc='+`nc`+' tt='+`tt+(t-st)`+' ct='+`ct+(t-st)`
+			print s0
+			print s1
+		self.timings[key] = nc, tt + (t - st), ct + (t - st), callers, callees
+
+	def print_stats(self):
+		import string
+		s = string.rjust('# calls', 8)
+		s = s + ' ' + string.rjust('tot time', 8)
+		s = s + ' ' + string.rjust('per call', 8)
+		s = s + ' ' + string.rjust('cum time', 8)
+		s = s + ' ' + string.rjust('per call', 8)
+		print s + ' filename(function)'
+		for key in self.timings.keys():
+			nc, tt, ct, callers, callees = self.timings[key]
+			if nc == 0:
+				continue
+			s = string.rjust(`nc`, 8)
+			s = s + ' ' + string.rjust(`tt`, 8)
+			s = s + ' ' + string.rjust(`tt/nc`, 8)
+			s = s + ' ' + string.rjust(`ct`, 8)
+			s = s + ' ' + string.rjust(`ct/nc`, 8)
+			print s + ' ' + key
+
+	def dump_stats(self, file):
+		import marshal
+		f = open(file, 'w')
+		marshal.dump(self.timings, f)
+		f.close()
+
+	# The following two functions can be called by clients to use
+	# a debugger to debug a statement, given as a string.
+	
+	def run(self, cmd):
+		import __main__
+		dict = __main__.__dict__
+		self.runctx(cmd, dict, dict)
+	
+	def runctx(self, cmd, globals, locals):
+##		self.reset()
+		sys.setprofile(self.trace_dispatch)
+		try:
+##			try:
+				exec(cmd + '\n', globals, locals)
+##			except ProfQuit:
+##				pass
+		finally:
+##			self.quitting = 1
+			sys.setprofile(None)
+		# XXX What to do if the command finishes normally?
+
+def depth(frame):
+	d = 0
+	while frame:
+		d = d + 1
+		frame = frame.f_back
+	return d
+
+def run(statement, *args):
+	prof = Profile().init()
+	try:
+		prof.run(statement)
+	except SystemExit:
+		pass
+	if len(args) == 0:
+		prof.print_stats()
+	else:
+		prof.dump_stats(args[0])
+
+def cv_float(val, width):
+	import string
+	s = `val`
+	try:
+		e = string.index(s, 'e')
+		exp = s[e+1:]
+		s = s[:e]
+		width = width - len(exp) - 1
+	except string.index_error:
+		exp = ''
+	try:
+		d = string.index(s, '.')
+		frac = s[d+1:]
+		s = s[:d]
+		width = width - len(s) - 1
+	except string.index_error:
+		if exp <> '':
+			return s + 'e' + exp
+		else:
+			return s
+	if width < 0:
+		width = 0
+	while width < len(frac):
+		c = frac[width]
+		frac = frac[:width]
+		if ord(c) >= ord('5'):
+			carry = 1
+			for i in range(width-1, -1, -1):
+				if frac[i] == '9':
+					frac = frac[:i]
+					# keep if trailing zeroes are wanted
+					# + '0' + frac[i+1:width]
+				else:
+					frac = frac[:i] + chr(ord(frac[i])+1) + frac[i+1:width]
+					carry = 0
+					break
+			if carry:
+				for i in range(len(s)-1, -1, -1):
+					if s[i] == '9':
+						s = s[:i] + '0' + s[i+1:]
+						if i == 0:
+							# gets one wider, so
+							# should shorten
+							# fraction by one
+							s = '1' + s
+							if width > 0:
+								width = width - 1
+					else:
+						s = s[:i] + chr(ord(s[i])+1) + s[i+1:]
+						break
+	# delete trailing zeroes
+	for i in range(len(frac)-1, -1, -1):
+		if frac[i] == '0':
+			frac = frac[:i]
+		else:
+			break
+	# build up the number
+	if width > 0 and len(frac) > 0:
+		s = s + '.' + frac[:width]
+	if exp <> '':
+		s = s + 'e' + exp
+	return s
+
+def print_line(nc, tt, ct, callers, callees, key):
+	import string
+	s = string.rjust(cv_float(nc,8), 8)
+	s = s + ' ' + string.rjust(cv_float(tt,8), 8)
+	if nc == 0:
+		s = s + ' '*9
+	else:
+		s = s + ' ' + string.rjust(cv_float(tt/nc,8), 8)
+	s = s + ' ' + string.rjust(cv_float(ct,8), 8)
+	if nc == 0:
+		s = s + ' '*9
+	else:
+		s = s + ' ' + string.rjust(cv_float(ct/nc,8), 8)
+	print s + ' ' + key
+
+class Stats():
+	def init(self, file):
+		import marshal
+		f = open(file, 'r')
+		self.stats = marshal.load(f)
+		f.close()
+		self.stats_list = None
+		return self
+
+	def print_stats(self):
+		import string
+		s = string.rjust('# calls', 8)
+		s = s + ' ' + string.rjust('tot time', 8)
+		s = s + ' ' + string.rjust('per call', 8)
+		s = s + ' ' + string.rjust('cum time', 8)
+		s = s + ' ' + string.rjust('per call', 8)
+		print s + ' filename(function)'
+		if self.stats_list:
+			for i in range(len(self.stats_list)):
+				nc, tt, ct, callers, callees, key = self.stats_list[i]
+				print_line(nc, tt, ct, callers, callees, key)
+		else:
+			for key in self.stats.keys():
+				nc, tt, ct, callers, callees = self.stats[key]
+				print_line(nc, tt, ct, callers, callees, key)
+
+	def print_callers(self):
+		if self.stats_list:
+			for i in range(len(self.stats_list)):
+				nc, tt, ct, callers, callees, key = self.stats_list[i]
+				print key,
+				for func in callers.keys():
+					print func+'('+`callers[func]`+')',
+				print
+		else:
+			for key in self.stats.keys():
+				nc, tt, ct, callers, callees = self.stats[key]
+				print key,
+				for func in callers.keys():
+					print func+'('+`callers[func]`+')',
+				print
+
+	def print_callees(self):
+		if self.stats_list:
+			for i in range(len(self.stats_list)):
+				nc, tt, ct, callers, callees, key = self.stats_list[i]
+				print key,
+				for func in callees.keys():
+					print func+'('+`callees[func]`+')',
+				print
+		else:
+			for key in self.stats.keys():
+				nc, tt, ct, callers, callees = self.stats[key]
+				print key,
+				for func in callees.keys():
+					print func+'('+`callees[func]`+')',
+				print
+
+	def sort_stats(self, field):
+		stats_list = []
+		for key in self.stats.keys():
+			t = self.stats[key]
+			nt = ()
+			for i in range(len(t)):
+				if i == field:
+					nt = (t[i],) + nt[:]
+				else:
+					nt = nt[:] + (t[i],)
+			if field == -1:
+				nt = (key,) + nt
+			else:
+				nt = nt + (key,)
+			stats_list.append(nt)
+		stats_list.sort()
+		self.stats_list = []
+		for i in range(len(stats_list)):
+			t = stats_list[i]
+			if field == -1:
+				nt = t[1:] + t[0:1]
+			else:
+				nt = t[1:]
+				nt = nt[:field] + t[0:1] + nt[field:]
+			self.stats_list.append(nt)
+
+	def strip_dirs(self):
+		import os
+		newstats = {}
+		for key in self.stats.keys():
+			nc, tt, ct, callers, callees = self.stats[key]
+			newkey = os.path.basename(key)
+			newcallers = {}
+			for c in callers.keys():
+				newcallers[os.path.basename(c)] = callers[c]
+			newcallees = {}
+			for c in callees.keys():
+				newcallees[os.path.basename(c)] = callees[c]
+			newstats[newkey] = nc, tt, ct, newcallers, newcallees
+		self.stats = newstats
+		self.stats_list = None
+
+# test command
+def debug():
+	prof = Profile().init()
+	prof.debug = 1
+	try:
+		prof.run('import x; x.main()')
+	except SystemExit:
+		pass
+	prof.print_stats()
+
+def test():
+	run('import x; x.main()')