edq.cli.profile.flame

Profile some Python target using Python's cprofile stdlib.

 1"""
 2Profile some Python target using Python's cprofile stdlib.
 3"""
 4
 5import argparse
 6import os
 7import shutil
 8import sys
 9
10import pyflame.sampler
11
12import edq.core.argparser
13import edq.util.dirent
14import edq.util.profile
15
16FLAMEGRAPH_URL: str = 'https://github.com/brendangregg/FlameGraph/blob/master/flamegraph.pl'
17
18DEFAULT_FLAMEGRAPH_PATH: str = 'flamegraph.pl'
19DEFAULT_OUTPUT_PATH: str = 'flame.svg'
20DEFAULT_SAMPLE_INTERVAL_SECS: float = 0.000001
21
22def run_cli(args: argparse.Namespace) -> int:
23    """ Run the CLI. """
24
25    flamegraph_path = shutil.which(args.flamegraph_path)
26    if (flamegraph_path is None):
27        # Check for relative paths.
28        if (os.path.exists(args.flamegraph_path)):
29            flamegraph_path = os.path.abspath(args.flamegraph_path)
30        else:
31            raise ValueError(f"Failed to find flamegraph script ('{args.flamegraph_path}'), download from '{FLAMEGRAPH_URL}'.")
32
33    flamegraph_extra_args = [
34        '--width', '2000',
35    ]
36
37    sampler = pyflame.sampler.Sampler(args.sample_interval_secs)
38    edq.util.profile.exec_for_profile(args.run_target, imports = args.imports, is_module = args.module, module_args = args.module_args)
39    stack_counts = sampler.stop()
40
41    svg_text = pyflame.render.stack_counts_to_svg(stack_counts, flamegraph_path, flamegraph_extra_args)
42    edq.util.dirent.write_file(args.out_path, svg_text)
43
44    print(f"Wrote flamegraph to '{args.out_path}'.")
45
46    return 0
47
48def main() -> int:
49    """ Get a parser, parse the args, and call run. """
50    return run_cli(_get_parser().parse_args())
51
52def _get_parser() -> argparse.ArgumentParser:
53    """ Get the parser. """
54
55    parser = edq.core.argparser.get_default_parser(__doc__.strip())
56
57    parser.add_argument('run_target', metavar = 'RUN_TARGET',
58        action = 'store', type = str,
59        help = 'The code to profile.')
60
61    parser.add_argument('module_args', metavar = 'MODULE_ARGS',
62        action = 'store', type = str, nargs = '*',
63        help = 'Additional args to pass to the target if `--module` is used.')
64
65    parser.add_argument('--module', '-m', dest = 'module',
66        action = 'store_true', default = False,
67        help = 'Treat the provided run target as a module, and all other positional arguments are arguments to the module (default: %(default)s).')
68
69    parser.add_argument('--import', dest = 'imports',
70        action = 'append', default = [],
71        help = 'Import this module before running the command.')
72
73    parser.add_argument('--out', dest = 'out_path',
74        action = 'store', type = str, default = DEFAULT_OUTPUT_PATH,
75        help = 'The path where the output famegraph will be written (default: %(default)s).')
76
77    parser.add_argument('--flamegraph-path', dest = 'flamegraph_path',
78        action = 'store', type = str, default = DEFAULT_FLAMEGRAPH_PATH,
79        help = 'The path (or name in $PATH) of the flamegraph script (default: %(default)s).')
80
81    parser.add_argument('--sample-interval', dest = 'sample_interval_secs',
82        action = 'store', type = float, default = DEFAULT_SAMPLE_INTERVAL_SECS,
83        help = 'The number of seconds between samples (default: %(default)s).')
84
85    return parser
86
87if (__name__ == '__main__'):
88    sys.exit(main())
FLAMEGRAPH_URL: str = 'https://github.com/brendangregg/FlameGraph/blob/master/flamegraph.pl'
DEFAULT_FLAMEGRAPH_PATH: str = 'flamegraph.pl'
DEFAULT_OUTPUT_PATH: str = 'flame.svg'
DEFAULT_SAMPLE_INTERVAL_SECS: float = 1e-06
def run_cli(args: argparse.Namespace) -> int:
23def run_cli(args: argparse.Namespace) -> int:
24    """ Run the CLI. """
25
26    flamegraph_path = shutil.which(args.flamegraph_path)
27    if (flamegraph_path is None):
28        # Check for relative paths.
29        if (os.path.exists(args.flamegraph_path)):
30            flamegraph_path = os.path.abspath(args.flamegraph_path)
31        else:
32            raise ValueError(f"Failed to find flamegraph script ('{args.flamegraph_path}'), download from '{FLAMEGRAPH_URL}'.")
33
34    flamegraph_extra_args = [
35        '--width', '2000',
36    ]
37
38    sampler = pyflame.sampler.Sampler(args.sample_interval_secs)
39    edq.util.profile.exec_for_profile(args.run_target, imports = args.imports, is_module = args.module, module_args = args.module_args)
40    stack_counts = sampler.stop()
41
42    svg_text = pyflame.render.stack_counts_to_svg(stack_counts, flamegraph_path, flamegraph_extra_args)
43    edq.util.dirent.write_file(args.out_path, svg_text)
44
45    print(f"Wrote flamegraph to '{args.out_path}'.")
46
47    return 0

Run the CLI.

def main() -> int:
49def main() -> int:
50    """ Get a parser, parse the args, and call run. """
51    return run_cli(_get_parser().parse_args())

Get a parser, parse the args, and call run.