Add pyinstrument: a statistical profiler (#5196)

* Add pyinstrument: a statistical profiler

It is easier than cprofile, in that it can show stacktrace
of where things are slow.

Also refactored/moved debugging related utils to a separate
module

* use ipdb first, then fallback to pdb

* use ipdb first, then fallback to pdb
This commit is contained in:
Saugat Pachhai
2021-01-04 15:23:40 +05:45
committed by GitHub
parent 862ab8de62
commit 261ddc947b
3 changed files with 96 additions and 51 deletions
+90
View File
@@ -0,0 +1,90 @@
import argparse
from contextlib import ExitStack, contextmanager
@contextmanager
def instrument(html_output):
"""Run a statistical profiler"""
try:
from pyinstrument import Profiler # pylint: disable=import-error
except ImportError:
print("Failed to run profiler, pyinstrument is not installed")
yield
return
profiler = Profiler()
profiler.start()
yield
profiler.stop()
if html_output:
profiler.open_in_browser()
return
print(profiler.output_text(unicode=True, color=True))
@contextmanager
def profile(dump):
"""Run a cprofile"""
import cProfile
prof = cProfile.Profile()
prof.enable()
yield
prof.disable()
if not dump:
prof.print_stats(sort="cumtime")
return
prof.dump_stats(dump)
@contextmanager
def debug():
try:
yield
except Exception: # pylint: disable=broad-except
try:
import ipdb as pdb # noqa: T100, pylint: disable=import-error
except ImportError:
import pdb # noqa: T100
pdb.post_mortem()
@contextmanager
def debugtools(args):
with ExitStack() as stack:
if args.pdb:
stack.enter_context(debug())
if args.cprofile:
stack.enter_context(profile(args.cprofile_dump))
if args.instrument:
stack.enter_context(instrument(args.instrument_open))
yield
def add_debugging_flags(parser):
parser.add_argument(
"--cprofile",
action="store_true",
default=False,
help=argparse.SUPPRESS,
)
parser.add_argument("--cprofile-dump", help=argparse.SUPPRESS)
parser.add_argument(
"--pdb", action="store_true", default=False, help=argparse.SUPPRESS,
)
parser.add_argument(
"--instrument",
action="store_true",
default=False,
help=argparse.SUPPRESS,
)
parser.add_argument(
"--instrument-open",
action="store_true",
default=False,
help=argparse.SUPPRESS,
)
+2 -13
View File
@@ -4,6 +4,7 @@ import logging
import os
import sys
from ._debug import add_debugging_flags
from .command import (
add,
cache,
@@ -139,19 +140,7 @@ def get_parent_parser():
in order to prevent some weird behavior.
"""
parent_parser = argparse.ArgumentParser(add_help=False)
parent_parser.add_argument(
"--cprofile",
action="store_true",
default=False,
help=argparse.SUPPRESS,
)
parent_parser.add_argument("--cprofile-dump", help=argparse.SUPPRESS)
parent_parser.add_argument(
"--pdb", action="store_true", default=False, help=argparse.SUPPRESS,
)
add_debugging_flags(parent_parser)
log_level_group = parent_parser.add_mutually_exclusive_group()
log_level_group.add_argument(
"-q", "--quiet", action="count", default=0, help="Be quiet."
+4 -38
View File
@@ -2,9 +2,9 @@
import errno
import logging
from contextlib import contextmanager
from dvc import analytics
from dvc._debug import debugtools
from dvc.cli import parse_args
from dvc.config import ConfigError
from dvc.exceptions import DvcException, DvcParserError, NotDvcRepoError
@@ -22,39 +22,6 @@ from dvc.utils import error_link
logger = logging.getLogger("dvc")
@contextmanager
def profile(enable, dump):
if not enable:
yield
return
import cProfile
prof = cProfile.Profile()
prof.enable()
yield
prof.disable()
if not dump:
prof.print_stats(sort="cumtime")
return
prof.dump_stats(dump)
@contextmanager
def debug(enable):
try:
yield
return
except Exception:
if enable:
import pdb # noqa: T100
pdb.post_mortem()
raise
def main(argv=None): # noqa: C901
"""Run dvc CLI command.
@@ -84,10 +51,9 @@ def main(argv=None): # noqa: C901
logger.trace(args)
with profile(enable=args.cprofile, dump=args.cprofile_dump):
with debug(args.pdb):
cmd = args.func(args)
ret = cmd.run()
with debugtools(args):
cmd = args.func(args)
ret = cmd.run()
except ConfigError:
logger.exception("configuration error")
ret = 251