Cmds: Support --write-profile <filename>

Add an option which makes the program write profiling statistics in pstats
format to a specified file.

Visualize e.g. with:

  gprof2dot -f pstats <filename> -o <dotfile>
  dot -Tpng | display <dotfile>

Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
Jan Lindemann 2025-06-05 16:58:26 +02:00
commit 958c077da3

View file

@ -9,6 +9,8 @@ import asyncio
from argparse import ArgumentParser from argparse import ArgumentParser
from typing import Optional from typing import Optional
import cProfile
import jwutils import jwutils
from jwutils.log import * from jwutils.log import *
@ -68,6 +70,7 @@ class Cmds: # export
self.__parser.add_argument('--log-flags', help='Log flags', default=log_flags) self.__parser.add_argument('--log-flags', help='Log flags', default=log_flags)
self.__parser.add_argument('--log-level', help='Log level', default=log_level) self.__parser.add_argument('--log-level', help='Log level', default=log_level)
self.__parser.add_argument('--backtrace', help='Show exception backtraces', action='store_true', default=False) self.__parser.add_argument('--backtrace', help='Show exception backtraces', action='store_true', default=False)
self.__parser.add_argument('--write-profile', help='Profile code and store output to file', default=None)
if self.__modules == None: if self.__modules == None:
self.__modules = [ '__main__' ] self.__modules = [ '__main__' ]
subcmds = set() subcmds = set()
@ -99,6 +102,7 @@ class Cmds: # export
set_flags(self.args.log_flags) set_flags(self.args.log_flags)
set_level(self.args.log_level) set_level(self.args.log_level)
self.__back_trace = self.args.backtrace self.__back_trace = self.args.backtrace
exit_status = 0
# This is the toplevel parser, i.e. no func member has been added to the args via # This is the toplevel parser, i.e. no func member has been added to the args via
# #
@ -112,17 +116,27 @@ class Cmds: # export
self.__parser.print_help() self.__parser.print_help()
return None return None
if self.__back_trace: pr = None if self.args.write_profile is None else cProfile.Profile()
if pr is not None:
pr.enable()
try:
ret = await self.args.func(self.args) ret = await self.args.func(self.args)
else: except Exception as e:
try: if hasattr(e, 'message'):
ret = await self.args.func(self.args) slog(ERR, e.message)
except Exception as e: else:
if hasattr(e, 'message'): slog(ERR, f'Exception: {type(e)}: {e}')
slog(ERR, e.message) exit_status = 1
else: if self.__back_trace:
slog(ERR, f'Exception: {type(e)}: {e}') raise
sys.exit(1) finally:
if pr is not None:
pr.disable()
slog(NOTICE, f'Writing profile statistics to {self.args.write_profile}')
pr.dump_stats(self.args.write_profile)
sys.exit(exit_status)
def __del__(self): def __del__(self):
if self.__own_eloop: if self.__own_eloop: