Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 8 additions & 199 deletions Lib/cProfile.py
Original file line number Diff line number Diff line change
@@ -1,205 +1,14 @@
"""Python interface for the 'lsprof' profiler.
Compatible with the 'profile' module.
"""

__all__ = ["run", "runctx", "Profile"]

import _lsprof
import importlib.machinery
import importlib.util
import io
import profile as _pyprofile

# ____________________________________________________________
# Simple interface

def run(statement, filename=None, sort=-1):
return _pyprofile._Utils(Profile).run(statement, filename, sort)

def runctx(statement, globals, locals, filename=None, sort=-1):
return _pyprofile._Utils(Profile).runctx(statement, globals, locals,
filename, sort)

run.__doc__ = _pyprofile.run.__doc__
runctx.__doc__ = _pyprofile.runctx.__doc__

# ____________________________________________________________

class Profile(_lsprof.Profiler):
"""Profile(timer=None, timeunit=None, subcalls=True, builtins=True)

Builds a profiler object using the specified timer function.
The default timer is a fast built-in one based on real time.
For custom timer functions returning integers, timeunit can
be a float specifying a scale (i.e. how long each integer unit
is, in seconds).
"""

# Most of the functionality is in the base class.
# This subclass only adds convenient and backward-compatible methods.

def print_stats(self, sort=-1):
import pstats
if not isinstance(sort, tuple):
sort = (sort,)
pstats.Stats(self).strip_dirs().sort_stats(*sort).print_stats()

def dump_stats(self, file):
import marshal
with open(file, 'wb') as f:
self.create_stats()
marshal.dump(self.stats, f)

def create_stats(self):
self.disable()
self.snapshot_stats()
"""Compatibility wrapper for cProfile module.

def snapshot_stats(self):
entries = self.getstats()
self.stats = {}
callersdicts = {}
# call information
for entry in entries:
func = label(entry.code)
nc = entry.callcount # ncalls column of pstats (before '/')
cc = nc - entry.reccallcount # ncalls column of pstats (after '/')
tt = entry.inlinetime # tottime column of pstats
ct = entry.totaltime # cumtime column of pstats
callers = {}
callersdicts[id(entry.code)] = callers
self.stats[func] = cc, nc, tt, ct, callers
# subcall information
for entry in entries:
if entry.calls:
func = label(entry.code)
for subentry in entry.calls:
try:
callers = callersdicts[id(subentry.code)]
except KeyError:
continue
nc = subentry.callcount
cc = nc - subentry.reccallcount
tt = subentry.inlinetime
ct = subentry.totaltime
if func in callers:
prev = callers[func]
nc += prev[0]
cc += prev[1]
tt += prev[2]
ct += prev[3]
callers[func] = nc, cc, tt, ct

# The following two methods can be called by clients to use
# a profiler to profile a statement, given as a string.

def run(self, cmd):
import __main__
dict = __main__.__dict__
return self.runctx(cmd, dict, dict)

def runctx(self, cmd, globals, locals):
self.enable()
try:
exec(cmd, globals, locals)
finally:
self.disable()
return self

# This method is more useful to profile a single function call.
def runcall(self, func, /, *args, **kw):
self.enable()
try:
return func(*args, **kw)
finally:
self.disable()

def __enter__(self):
self.enable()
return self

def __exit__(self, *exc_info):
self.disable()

# ____________________________________________________________
This module maintains backward compatibility by importing from the new
profiling.tracing module.
"""

def label(code):
if isinstance(code, str):
return ('~', 0, code) # built-in functions ('~' sorts at the end)
else:
return (code.co_filename, code.co_firstlineno, code.co_name)
from profiling.tracing import run, runctx, Profile

# ____________________________________________________________
__all__ = ["run", "runctx", "Profile"]

def main():
import os
if __name__ == "__main__":
import sys
import runpy
import pstats
from optparse import OptionParser
usage = "cProfile.py [-o output_file_path] [-s sort] [-m module | scriptfile] [arg] ..."
parser = OptionParser(usage=usage)
parser.allow_interspersed_args = False
parser.add_option('-o', '--outfile', dest="outfile",
help="Save stats to <outfile>", default=None)
parser.add_option('-s', '--sort', dest="sort",
help="Sort order when printing to stdout, based on pstats.Stats class",
default=2,
choices=sorted(pstats.Stats.sort_arg_dict_default))
parser.add_option('-m', dest="module", action="store_true",
help="Profile a library module", default=False)

if not sys.argv[1:]:
parser.print_usage()
sys.exit(2)

(options, args) = parser.parse_args()
sys.argv[:] = args

# The script that we're profiling may chdir, so capture the absolute path
# to the output file at startup.
if options.outfile is not None:
options.outfile = os.path.abspath(options.outfile)

if len(args) > 0:
if options.module:
code = "run_module(modname, run_name='__main__')"
globs = {
'run_module': runpy.run_module,
'modname': args[0]
}
else:
progname = args[0]
sys.path.insert(0, os.path.dirname(progname))
with io.open_code(progname) as fp:
code = compile(fp.read(), progname, 'exec')
spec = importlib.machinery.ModuleSpec(name='__main__', loader=None,
origin=progname)
module = importlib.util.module_from_spec(spec)
# Set __main__ so that importing __main__ in the profiled code will
# return the same namespace that the code is executing under.
sys.modules['__main__'] = module
# Ensure that we're using the same __dict__ instance as the module
# for the global variables so that updates to globals are reflected
# in the module's namespace.
globs = module.__dict__
globs.update({
'__spec__': spec,
'__file__': spec.origin,
'__name__': spec.name,
'__package__': None,
'__cached__': None,
})

try:
runctx(code, globs, None, options.outfile, options.sort)
except BrokenPipeError as exc:
# Prevent "Exception ignored" during interpreter shutdown.
sys.stdout = None
sys.exit(exc.errno)
else:
parser.print_usage()
return parser

# When invoked as main program, invoke the profiler on a script
if __name__ == '__main__':
from profiling.tracing.__main__ import main
main()
72 changes: 72 additions & 0 deletions Lib/profile/profile.py → Lib/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,18 @@
import sys
import time
import marshal
import warnings

__all__ = ["run", "runctx", "Profile"]

# Emit deprecation warning as per PEP 799
warnings.warn(
"The profile module is deprecated and will be removed in Python 3.17. "
"Use profiling.tracing (or cProfile) for tracing profilers instead.",
DeprecationWarning,
stacklevel=2
)

# Sample timer for use with
#i_count = 0
#def integer_timer():
Expand Down Expand Up @@ -550,3 +559,66 @@ def f(m, f1=f1):
return mean

#****************************************************************************

def main():
import os
from optparse import OptionParser

usage = "profile.py [-o output_file_path] [-s sort] [-m module | scriptfile] [arg] ..."
parser = OptionParser(usage=usage)
parser.allow_interspersed_args = False
parser.add_option('-o', '--outfile', dest="outfile",
help="Save stats to <outfile>", default=None)
parser.add_option('-m', dest="module", action="store_true",
help="Profile a library module.", default=False)
parser.add_option('-s', '--sort', dest="sort",
help="Sort order when printing to stdout, based on pstats.Stats class",
default=-1)

if not sys.argv[1:]:
parser.print_usage()
sys.exit(2)

(options, args) = parser.parse_args()
sys.argv[:] = args

# The script that we're profiling may chdir, so capture the absolute path
# to the output file at startup.
if options.outfile is not None:
options.outfile = os.path.abspath(options.outfile)

if len(args) > 0:
if options.module:
import runpy
code = "run_module(modname, run_name='__main__')"
globs = {
'run_module': runpy.run_module,
'modname': args[0]
}
else:
progname = args[0]
sys.path.insert(0, os.path.dirname(progname))
with io.open_code(progname) as fp:
code = compile(fp.read(), progname, 'exec')
spec = importlib.machinery.ModuleSpec(name='__main__', loader=None,
origin=progname)
globs = {
'__spec__': spec,
'__file__': spec.origin,
'__name__': spec.name,
'__package__': None,
'__cached__': None,
}
try:
runctx(code, globs, None, options.outfile, options.sort)
except BrokenPipeError as exc:
# Prevent "Exception ignored" during interpreter shutdown.
sys.stdout = None
sys.exit(exc.errno)
else:
parser.print_usage()
return parser

# When invoked as main program, invoke the profiler on a script
if __name__ == '__main__':
main()
6 changes: 0 additions & 6 deletions Lib/profile/__init__.py

This file was deleted.

69 changes: 0 additions & 69 deletions Lib/profile/__main__.py

This file was deleted.

13 changes: 13 additions & 0 deletions Lib/profiling/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Python profiling tools.

This package provides two types of profilers:

- profiling.tracing: Deterministic tracing profiler that instruments every
function call and return. Higher overhead but provides exact call counts
and timing.

- profiling.sampling: Statistical sampling profiler that periodically samples
the call stack. Low overhead and suitable for production use.
"""

__all__ = ["tracing", "sampling"]
Loading
Loading