From d062d78eda98aeda56c798b584fc2a880546df81 Mon Sep 17 00:00:00 2001 From: Ian Ward Date: Mon, 13 May 2024 12:32:23 -0400 Subject: [PATCH 1/4] ckan profile profile^W /url --- ckan/cli/profile.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/ckan/cli/profile.py b/ckan/cli/profile.py index c85826e9bf8..f6aa04e1e0f 100644 --- a/ckan/cli/profile.py +++ b/ckan/cli/profile.py @@ -7,11 +7,10 @@ from . import error_shout -@click.group( - short_help=u"Code speed profiler.", invoke_without_command=True, -) -@click.pass_context -def profile(ctx: click.Context): +@click.command(short_help="Code speed profiler.") +@click.argument(u"url") +@click.argument(u"user", required=False, default=u"visitor") +def profile(url: str, user: str): """Provide a ckan url and it will make the request and record how long each function call took in a file that can be read by pstats.Stats (command-line) or runsnakerun (gui). @@ -28,14 +27,6 @@ def profile(ctx: click.Context): You may need to install python module: cProfile """ - if ctx.invoked_subcommand is None: - ctx.invoke(main) - - -@profile.command('profile', short_help=u"Code speed profiler.",) -@click.argument(u"url") -@click.argument(u"user", required=False, default=u"visitor") -def main(url: str, user: str): import cProfile from ckan.tests.helpers import _get_test_app From 97d4c3365ba6e503fd7021228770a15a8ab51046 Mon Sep 17 00:00:00 2001 From: Ian Ward Date: Mon, 13 May 2024 23:22:20 -0400 Subject: [PATCH 2/4] profile hot requests, best-of-n --- ckan/cli/profile.py | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/ckan/cli/profile.py b/ckan/cli/profile.py index f6aa04e1e0f..ac561a88a2e 100644 --- a/ckan/cli/profile.py +++ b/ckan/cli/profile.py @@ -8,12 +8,14 @@ @click.command(short_help="Code speed profiler.") -@click.argument(u"url") -@click.argument(u"user", required=False, default=u"visitor") -def profile(url: str, user: str): +@click.argument("url") +@click.argument("user", required=False, default="visitor") +@click.option('--cold', is_flag=True, default=False, help='measure first call') +@click.option('-b', '--best-of', type=int, default=3, help='best of N calls') +def profile(url: str, user: str, cold: bool, best_of: int): """Provide a ckan url and it will make the request and record how long each function call took in a file that can be read by - pstats.Stats (command-line) or runsnakerun (gui). + pstats.Stats (command-line) or SnakeViz (web). Usage: profile URL [username] @@ -27,28 +29,37 @@ def profile(url: str, user: str): You may need to install python module: cProfile """ - import cProfile + from cProfile import Profile from ckan.tests.helpers import _get_test_app app = _get_test_app() - def profile_url(url: str): # type: ignore # noqa + def profile_url(url: str): try: app.get( - url, status=[200], environ_overrides={"REMOTE_USER": str(user)} + url, status=200, environ_overrides={"REMOTE_USER": str(user)} ) except KeyboardInterrupt: raise except Exception: error_shout(traceback.format_exc()) - output_filename = u"ckan%s.profile" % re.sub( - u"[/?]", u".", url.replace(u"/", u".") - ) - profile_command = u"profile_url('%s')" % url - cProfile.runctx( - profile_command, globals(), locals(), filename=output_filename - ) + if not cold: + profile_url(url) + + best = None + for _n in range(best_of): + with Profile() as pr: + profile_url(url) + if best is None or (best.getstats()[0].totaltime + > pr.getstats()[0].totaltime): + best = pr + + if best is None: + return + + output_filename = "ckan%s.profile" % re.sub(r"[\W]", ".", url) + best.dump_stats(output_filename) import pstats stats = pstats.Stats(output_filename) From c7a84792982899ed34cbebc04a01730ce7f93b27 Mon Sep 17 00:00:00 2001 From: Ian Ward Date: Mon, 20 May 2024 12:04:34 -0400 Subject: [PATCH 3/4] type: ignore --- ckan/cli/profile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ckan/cli/profile.py b/ckan/cli/profile.py index ac561a88a2e..b8934b9fb5e 100644 --- a/ckan/cli/profile.py +++ b/ckan/cli/profile.py @@ -51,8 +51,8 @@ def profile_url(url: str): for _n in range(best_of): with Profile() as pr: profile_url(url) - if best is None or (best.getstats()[0].totaltime - > pr.getstats()[0].totaltime): + if best is None or (best.getstats()[0].totaltime # type: ignore + > pr.getstats()[0].totaltime): # type: ignore best = pr if best is None: From b8b99b39fd60c1b1a25f4c61d65bc1a898a51ec2 Mon Sep 17 00:00:00 2001 From: Ian Ward Date: Mon, 20 May 2024 12:32:19 -0400 Subject: [PATCH 4/4] changes --- changes/8223.feature | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changes/8223.feature diff --git a/changes/8223.feature b/changes/8223.feature new file mode 100644 index 00000000000..ed6f2f987d9 --- /dev/null +++ b/changes/8223.feature @@ -0,0 +1,5 @@ +fix profile cli, add --cold and --best-of options. + +By default cli profile will now run the request once (cold), then give the +best of the next 3 (hot) runs. Use --cold --best-of=1 for the old cli profile +behavior.