123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- # MODULE: grass.benchmark
- #
- # AUTHOR(S): Vaclav Petras <wenzeslaus gmail com>
- #
- # PURPOSE: Benchmarking for GRASS GIS modules
- #
- # COPYRIGHT: (C) 2021 Vaclav Petras, and by the GRASS Development Team
- #
- # This program is free software under the GNU General Public
- # License (>=v2). Read the file COPYING that comes with GRASS
- # for details.
- """CLI for the benchmark package"""
- import argparse
- import sys
- from pathlib import Path
- from grass.benchmark import (
- join_results_from_files,
- load_results_from_file,
- nprocs_plot,
- num_cells_plot,
- save_results_to_file,
- )
- class CliUsageError(ValueError):
- """Raised when error is in the command line arguments.
- Used when the error is discovered only after argparse parsed the arguments.
- """
- # ArgumentError from argparse may work too, but it is not documented and
- # takes a reference argument which we don't have access to after the parse step.
- def join_results_cli(args):
- """Translate CLI parser result to API calls."""
- if args.prefixes and len(args.results) != len(args.prefixes):
- raise CliUsageError(
- f"Number of prefixes ({len(args.prefixes)}) needs to be the same"
- f" as the number of input result files ({len(args.results)})"
- )
- def select_only(result):
- return result.label == args.only
- if args.only:
- select_function = select_only
- else:
- select_function = None
- results = join_results_from_files(
- source_filenames=args.results,
- prefixes=args.prefixes,
- select=select_function,
- prefixes_as_labels=args.re_label,
- )
- save_results_to_file(results, args.output)
- def plot_nprocs_cli(args):
- """Translate CLI parser result to API calls."""
- results = load_results_from_file(args.input)
- nprocs_plot(
- results.results,
- filename=args.output,
- title=args.title,
- )
- def plot_cells_cli(args):
- """Translate CLI parser result to API calls."""
- results = load_results_from_file(args.input)
- num_cells_plot(
- results.results,
- filename=args.output,
- title=args.title,
- show_resolution=args.resolutions,
- )
- def get_executable_name():
- """Get name of the executable and module.
- This is a workaround for Python issue:
- argparse support for "python -m module" in help
- https://bugs.python.org/issue22240
- """
- executable = Path(sys.executable).stem
- return f"{executable} -m grass.benchmark"
- class ExtendAction(argparse.Action):
- """Support for agrparse action="extend" before Python 3.8
- Each parser instance needs the action to be registered.
- """
- # pylint: disable=too-few-public-methods
- def __call__(self, parser, namespace, values, option_string=None):
- items = getattr(namespace, self.dest) or []
- items.extend(values)
- setattr(namespace, self.dest, items)
- def add_subcommand_parser(subparsers, name, description):
- """Add parser for a subcommand into subparsers."""
- # help is in parent's help, description in subcommand's help.
- return subparsers.add_parser(name, help=description, description=description)
- def add_subparsers(parser, dest):
- """Add subparsers in a unified way.
- Uses title 'subcommands' for the list of commands
- (instead of the 'positional' which is the default).
- The *dest* should be 'command', 'subcommand', etc. with appropriate nesting.
- """
- if sys.version_info < (3, 7):
- # required as parameter is only in >=3.7.
- return parser.add_subparsers(title="subcommands", dest=dest)
- return parser.add_subparsers(title="subcommands", required=True, dest=dest)
- def add_results_subcommand(parent_subparsers):
- """Add results subcommand."""
- main_parser = add_subcommand_parser(
- parent_subparsers, "results", description="Manipulate results"
- )
- main_subparsers = add_subparsers(main_parser, dest="subcommand")
- join = main_subparsers.add_parser("join", help="Join results")
- join.add_argument("results", help="Files with results", nargs="*", metavar="file")
- join.add_argument("output", help="Output file", metavar="output_file")
- if sys.version_info < (3, 8):
- join.register("action", "extend", ExtendAction)
- join.add_argument(
- "--prefixes",
- help="Add prefixes to result labels per file",
- action="extend",
- nargs="*",
- metavar="text",
- )
- join.add_argument(
- "--only",
- help="Select only results with matching label",
- metavar="label",
- )
- join.add_argument(
- "--re-label",
- help="Use prefixes as the new labels",
- action="store_true",
- )
- join.set_defaults(handler=join_results_cli)
- def add_plot_io_arguments(parser):
- """Add input and output arguments to *parser*."""
- parser.add_argument("input", help="file with results (JSON)", metavar="input_file")
- parser.add_argument(
- "output", help="output file (e.g., PNG)", nargs="?", metavar="output_file"
- )
- def add_plot_title_argument(parser):
- """Add title argument to *parser*."""
- parser.add_argument(
- "--title",
- help="Title for the plot",
- metavar="text",
- )
- def add_plot_subcommand(parent_subparsers):
- """Add plot subcommand."""
- main_parser = add_subcommand_parser(
- parent_subparsers, "plot", description="Plot results"
- )
- main_subparsers = add_subparsers(main_parser, dest="subcommand")
- join = main_subparsers.add_parser("cells", help="Plot for variable number of cells")
- add_plot_io_arguments(join)
- add_plot_title_argument(join)
- join.add_argument(
- "--resolutions",
- help="Use resolutions for x axis instead of cell count",
- action="store_true",
- )
- join.set_defaults(handler=plot_cells_cli)
- nprocs = main_subparsers.add_parser(
- "nprocs", help="Plot for variable number of processing elements"
- )
- add_plot_io_arguments(nprocs)
- add_plot_title_argument(nprocs)
- nprocs.set_defaults(handler=plot_nprocs_cli)
- def define_arguments():
- """Define top level parser and create subparsers."""
- parser = argparse.ArgumentParser(
- description="Process results from module benchmarks.",
- prog=get_executable_name(),
- )
- subparsers = add_subparsers(parser, dest="command")
- add_results_subcommand(subparsers)
- add_plot_subcommand(subparsers)
- return parser
- def main(args=None):
- """Define and parse command line parameters then run the appropriate handler."""
- parser = define_arguments()
- args = parser.parse_args(args)
- try:
- args.handler(args)
- except CliUsageError as error:
- # Report a usage error and exit.
- sys.exit(f"ERROR: {error}")
- if __name__ == "__main__":
- main()
|