Skip to content

Commit 56aa07b

Browse files
committed
Support per-line profiling for phase functions
This is done via `line_profiler`. Ramble already supports `cProfile` via `ramble -p`, but sometimes it's useful to get per-line information. Example output (the output itself is boring, illustration only...) ``` $ ramble workspace analyze --profile-phase prepare_analysis Timer unit: 1e-09 s Total time: 5.34e-07 s File: hpc-dev/ramble/lib/ramble/ramble/application.py Function: _prepare_analysis at line 1682 Line # Hits Time Per Hit % Time Line Contents ============================================================== 1682 def _prepare_analysis(self, workspace, app_inst=None): 1683 """Prepapre experiment for analysis extraction 1684 1685 This function performs any actions that are necessary before the 1686 figures of merit, and success criteria can be properly extracted. 1687 1688 This function can be overridden at the application level to perform 1689 application specific processing of the output. 1690 """ 1691 1 534.0 534.0 100.0 pass Total time: 0.0016208 s File: hpc-dev/ramble/var/ramble/repos/builtin/modifiers/gcp-metadata/modifier.py Function: _prepare_analysis at line 233 Line # Hits Time Per Hit % Time Line Contents ============================================================== 233 def _prepare_analysis(self, workspace): 234 1 631403.0 631403.0 39.0 self._process_id_list() 235 1 497900.0 497900.0 30.7 self._process_id_map() 236 1 491493.0 491493.0 30.3 self._process_physical_hosts(workspace) ```
1 parent 034b5e4 commit 56aa07b

File tree

6 files changed

+75
-17
lines changed

6 files changed

+75
-17
lines changed

lib/ramble/ramble/application.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,19 @@ def _run_phase_hook(obj, workspace, pipeline, hook):
120120

121121
hook_func_name = f"_{hook}"
122122
if hasattr(obj, hook_func_name):
123-
phase_func = getattr(obj, hook_func_name)
123+
phase_func = _get_phase_func_wrapper(workspace, getattr(obj, hook_func_name), hook)
124124
phase_func(workspace)
125125

126126

127+
def _get_phase_func_wrapper(workspace, phase_func, phase_name):
128+
if workspace.profile_config is None:
129+
return phase_func
130+
(profiler, profile_phases) = workspace.profile_config
131+
if phase_name not in profile_phases:
132+
return phase_func
133+
return profiler(phase_func)
134+
135+
127136
class ApplicationBase(metaclass=ApplicationMeta):
128137
name = None
129138
_builtin_name = NS_SEPARATOR.join(("builtin", "{name}"))
@@ -648,7 +657,7 @@ def run_phase(self, pipeline, phase, workspace):
648657

649658
for _, obj in self._objects(exclude_types=[ramble.repository.ObjectTypes.applications]):
650659
_run_phase_hook(obj, workspace, pipeline, phase)
651-
phase_func = phase_node.attribute
660+
phase_func = _get_phase_func_wrapper(workspace, phase_node.attribute, phase)
652661
phase_func(workspace, app_inst=self)
653662
self._phase_times[phase] = time.time() - start_time
654663

lib/ramble/ramble/cmd/common/arguments.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,19 @@ def include_phase_dependencies():
148148
)
149149

150150

151+
@arg
152+
def profile_phases():
153+
return Args(
154+
"--profile-phase",
155+
nargs="+",
156+
action="append",
157+
default=None,
158+
dest="profile_phases",
159+
help="phases to be profiled by line_profiler",
160+
required=False,
161+
)
162+
163+
151164
@arg
152165
def where():
153166
return Args(

lib/ramble/ramble/cmd/workspace.py

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import sys
1111
import tempfile
1212
import argparse
13+
import itertools
1314

1415
import llnl.util.tty as tty
1516
import llnl.util.tty.color as color
@@ -443,12 +444,25 @@ def workspace_concretize(args):
443444

444445

445446
def workspace_run_pipeline(args, pipeline):
446-
include_phase_dependencies = getattr(args, "include_phase_dependencies", None)
447-
if include_phase_dependencies:
448-
with ramble.config.override("config:include_phase_dependencies", True):
447+
profile_phases = getattr(args, "profile_phases", None)
448+
if profile_phases:
449+
import line_profiler
450+
451+
profiler = line_profiler.LineProfiler()
452+
profiler.enable()
453+
p_phases = set(itertools.chain.from_iterable(profile_phases))
454+
pipeline.workspace.profile_config = (profiler, p_phases)
455+
try:
456+
include_phase_dependencies = getattr(args, "include_phase_dependencies", None)
457+
if include_phase_dependencies:
458+
with ramble.config.override("config:include_phase_dependencies", True):
459+
pipeline.run()
460+
else:
449461
pipeline.run()
450-
else:
451-
pipeline.run()
462+
finally:
463+
if profile_phases:
464+
profiler.disable()
465+
profiler.print_stats()
452466

453467

454468
def workspace_setup_setup_parser(subparser):
@@ -464,7 +478,14 @@ def workspace_setup_setup_parser(subparser):
464478

465479
arguments.add_common_arguments(
466480
subparser,
467-
["phases", "include_phase_dependencies", "where", "exclude_where", "filter_tags"],
481+
[
482+
"phases",
483+
"include_phase_dependencies",
484+
"where",
485+
"exclude_where",
486+
"filter_tags",
487+
"profile_phases",
488+
],
468489
)
469490

470491

@@ -530,7 +551,14 @@ def workspace_analyze_setup_parser(subparser):
530551

531552
arguments.add_common_arguments(
532553
subparser,
533-
["phases", "include_phase_dependencies", "where", "exclude_where", "filter_tags"],
554+
[
555+
"phases",
556+
"include_phase_dependencies",
557+
"where",
558+
"exclude_where",
559+
"filter_tags",
560+
"profile_phases",
561+
],
534562
)
535563

536564

@@ -587,7 +615,9 @@ def workspace_push_to_cache_setup_parser(subparser):
587615
"-d", dest="cache_path", default=None, required=True, help="Path to cache."
588616
)
589617

590-
arguments.add_common_arguments(subparser, ["where", "exclude_where", "filter_tags"])
618+
arguments.add_common_arguments(
619+
subparser, ["where", "exclude_where", "filter_tags", "profile_phases"]
620+
)
591621

592622

593623
def workspace_info_setup_parser(subparser):
@@ -981,7 +1011,8 @@ def workspace_archive_setup_parser(subparser):
9811011
)
9821012

9831013
arguments.add_common_arguments(
984-
subparser, ["phases", "include_phase_dependencies", "where", "exclude_where"]
1014+
subparser,
1015+
["phases", "include_phase_dependencies", "where", "exclude_where", "profile_phases"],
9851016
)
9861017

9871018

@@ -1024,7 +1055,8 @@ def workspace_mirror_setup_parser(subparser):
10241055
)
10251056

10261057
arguments.add_common_arguments(
1027-
subparser, ["phases", "include_phase_dependencies", "where", "exclude_where"]
1058+
subparser,
1059+
["phases", "include_phase_dependencies", "where", "exclude_where", "profile_phases"],
10281060
)
10291061

10301062

lib/ramble/ramble/workspace/workspace.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,9 @@ def __init__(self, root, dry_run=False, read_default_template=True):
487487

488488
self.deployment_name = self.name
489489

490+
# Used by profiling phases
491+
self.profile_config = None
492+
490493
def _re_read(self):
491494
"""Reinitialize the workspace object if it has been written (this
492495
may not be true if the workspace was just created in this running

requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ coverage
66
pre-commit
77
pytest-xdist
88
pytest-cov
9+
line_profiler

share/ramble/ramble-completion.bash

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,7 @@ _ramble_workspace_activate() {
646646
}
647647

648648
_ramble_workspace_archive() {
649-
RAMBLE_COMPREPLY="-h --help --tar-archive -t --prefix -p --upload-url -u --include-secrets --phases --include-phase-dependencies --where --exclude-where"
649+
RAMBLE_COMPREPLY="-h --help --tar-archive -t --prefix -p --upload-url -u --include-secrets --phases --include-phase-dependencies --where --exclude-where --profile-phase"
650650
}
651651

652652
_ramble_workspace_deactivate() {
@@ -667,15 +667,15 @@ _ramble_workspace_concretize() {
667667
}
668668

669669
_ramble_workspace_setup() {
670-
RAMBLE_COMPREPLY="-h --help --dry-run --phases --include-phase-dependencies --where --exclude-where --filter-tags"
670+
RAMBLE_COMPREPLY="-h --help --dry-run --phases --include-phase-dependencies --where --exclude-where --filter-tags --profile-phase"
671671
}
672672

673673
_ramble_workspace_analyze() {
674-
RAMBLE_COMPREPLY="-h --help -f --formats -u --upload -p --print-results -s --summary-only --phases --include-phase-dependencies --where --exclude-where --filter-tags"
674+
RAMBLE_COMPREPLY="-h --help -f --formats -u --upload -p --print-results -s --summary-only --phases --include-phase-dependencies --where --exclude-where --filter-tags --profile-phase"
675675
}
676676

677677
_ramble_workspace_push_to_cache() {
678-
RAMBLE_COMPREPLY="-h --help -d --where --exclude-where --filter-tags"
678+
RAMBLE_COMPREPLY="-h --help -d --where --exclude-where --filter-tags --profile-phase"
679679
}
680680

681681
_ramble_workspace_info() {
@@ -692,7 +692,7 @@ _ramble_workspace_edit() {
692692
}
693693

694694
_ramble_workspace_mirror() {
695-
RAMBLE_COMPREPLY="-h --help -d --dry-run --phases --include-phase-dependencies --where --exclude-where"
695+
RAMBLE_COMPREPLY="-h --help -d --dry-run --phases --include-phase-dependencies --where --exclude-where --profile-phase"
696696
}
697697

698698
_ramble_workspace_experiment_logs() {

0 commit comments

Comments
 (0)