scope-profiler 0.2.2__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/PKG-INFO +37 -4
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/README.md +31 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/pyproject.toml +7 -3
- scope_profiler-0.2.4/src/scope_profiler/mpi_launch.py +119 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/post_processing.py +129 -48
- scope_profiler-0.2.4/src/scope_profiler/prof_export.py +208 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/profile_config.py +11 -12
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/profile_manager.py +21 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/region_profiler.py +45 -6
- scope_profiler-0.2.4/src/scope_profiler/speedscope_export.py +271 -0
- scope_profiler-0.2.4/src/scope_profiler/tests/check_mpi_launch.py +66 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/tests/test_app.py +2 -0
- scope_profiler-0.2.4/src/scope_profiler/tests/test_prof_export.py +225 -0
- scope_profiler-0.2.4/src/scope_profiler/tests/test_repeated_finalize.py +99 -0
- scope_profiler-0.2.4/src/scope_profiler/tests/test_speedscope_export.py +227 -0
- scope_profiler-0.2.4/src/scope_profiler/tests/unit/__init__.py +0 -0
- scope_profiler-0.2.4/src/scope_profiler/tests/unit/test_mpi_launch.py +90 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler.egg-info/PKG-INFO +37 -4
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler.egg-info/SOURCES.txt +10 -1
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler.egg-info/requires.txt +6 -3
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/setup.cfg +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/__init__.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/__main__.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/h5reader.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/inspection.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/metadata.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/mpi_region.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/plotting_scripts.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/region.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/summary.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/tests/__init__.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/tests/examples.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/tests/examples_pylikwid.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/tests/pylikwid_readme.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/tests/test_buffer_growth.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/tests/test_inspection.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/tests/test_metadata.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/tests/test_mpi.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/tests/test_overhead.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/tests/test_post_processing.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/tests/test_reader_api.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler/tests/test_readme.py +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler.egg-info/dependency_links.txt +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler.egg-info/entry_points.txt +0 -0
- {scope_profiler-0.2.2 → scope_profiler-0.2.4}/src/scope_profiler.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scope-profiler
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Profile code regions in python, optionally with LIKWID markers.
|
|
5
5
|
Author: Max
|
|
6
6
|
Project-URL: Source, https://github.com/max-models/scope-profiler
|
|
@@ -16,7 +16,8 @@ Requires-Python: >=3.10
|
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
Requires-Dist: h5py
|
|
18
18
|
Requires-Dist: numpy
|
|
19
|
-
|
|
19
|
+
Provides-Extra: line-profiler
|
|
20
|
+
Requires-Dist: line-profiler; extra == "line-profiler"
|
|
20
21
|
Provides-Extra: mpi
|
|
21
22
|
Requires-Dist: mpi4py; extra == "mpi"
|
|
22
23
|
Provides-Extra: pproc
|
|
@@ -25,11 +26,12 @@ Requires-Dist: jupyterlab; extra == "pproc"
|
|
|
25
26
|
Requires-Dist: matplotlib; extra == "pproc"
|
|
26
27
|
Requires-Dist: maxplotlibx>=0.1.5; extra == "pproc"
|
|
27
28
|
Requires-Dist: pandas; extra == "pproc"
|
|
29
|
+
Requires-Dist: snakeviz; extra == "pproc"
|
|
28
30
|
Provides-Extra: dev
|
|
29
31
|
Requires-Dist: black[jupyter]; extra == "dev"
|
|
30
32
|
Requires-Dist: isort; extra == "dev"
|
|
31
33
|
Requires-Dist: ruff; extra == "dev"
|
|
32
|
-
Requires-Dist: scope-profiler[docs,mpi,pproc,test]; extra == "dev"
|
|
34
|
+
Requires-Dist: scope-profiler[docs,line-profiler,mpi,pproc,test]; extra == "dev"
|
|
33
35
|
Provides-Extra: docs
|
|
34
36
|
Requires-Dist: myst-parser; extra == "docs"
|
|
35
37
|
Requires-Dist: nbconvert; extra == "docs"
|
|
@@ -38,7 +40,7 @@ Requires-Dist: pre-commit; extra == "docs"
|
|
|
38
40
|
Requires-Dist: pyproject-fmt; extra == "docs"
|
|
39
41
|
Requires-Dist: sphinx; extra == "docs"
|
|
40
42
|
Requires-Dist: sphinx-book-theme; extra == "docs"
|
|
41
|
-
Requires-Dist: scope-profiler[pproc]; extra == "docs"
|
|
43
|
+
Requires-Dist: scope-profiler[line-profiler,pproc]; extra == "docs"
|
|
42
44
|
Provides-Extra: test
|
|
43
45
|
Requires-Dist: coverage; extra == "test"
|
|
44
46
|
Requires-Dist: pytest; extra == "test"
|
|
@@ -427,3 +429,34 @@ straight from the JSON:
|
|
|
427
429
|
scope-profiler pproc profiling_data.h5 -o figures \
|
|
428
430
|
--export-data --export-data-format json --skip-plot-images
|
|
429
431
|
```
|
|
432
|
+
|
|
433
|
+
### Viewing a run in snakeviz
|
|
434
|
+
|
|
435
|
+
`--export-prof` writes the profile in the `.prof` format of the standard
|
|
436
|
+
library's `cProfile`, so a run can be explored with
|
|
437
|
+
[snakeviz](https://jiffyclub.github.io/snakeviz/) or `python -m pstats`:
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
scope-profiler pproc profiling_data.h5 -o figures --export-prof --skip-plot-images
|
|
441
|
+
snakeviz figures/profile_rank0.prof
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Regions become "functions": `cumtime` is a region's total wall time and
|
|
445
|
+
`tottime` is that minus the time spent in its nested regions. One file is
|
|
446
|
+
written per exported rank, since `.prof` has no notion of ranks — see
|
|
447
|
+
[the CLI docs](docs/source/cli.md) for the caveats of the reconstruction.
|
|
448
|
+
|
|
449
|
+
### Viewing a run in speedscope
|
|
450
|
+
|
|
451
|
+
`--export-speedscope` writes the run as a
|
|
452
|
+
[speedscope](https://www.speedscope.app) JSON file. Unlike `.prof`, it keeps
|
|
453
|
+
every individual call, so the timeline shows the run as it happened:
|
|
454
|
+
|
|
455
|
+
```bash
|
|
456
|
+
scope-profiler pproc profiling_data.h5 -o figures --export-speedscope --skip-plot-images
|
|
457
|
+
npx speedscope figures/profile.speedscope.json # or drop the file on speedscope.app
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
One file is written per input, holding one profile per exported rank, all
|
|
461
|
+
sharing a time origin so ranks stay aligned. See
|
|
462
|
+
[the CLI docs](docs/source/cli.md) for details.
|
|
@@ -382,3 +382,34 @@ straight from the JSON:
|
|
|
382
382
|
scope-profiler pproc profiling_data.h5 -o figures \
|
|
383
383
|
--export-data --export-data-format json --skip-plot-images
|
|
384
384
|
```
|
|
385
|
+
|
|
386
|
+
### Viewing a run in snakeviz
|
|
387
|
+
|
|
388
|
+
`--export-prof` writes the profile in the `.prof` format of the standard
|
|
389
|
+
library's `cProfile`, so a run can be explored with
|
|
390
|
+
[snakeviz](https://jiffyclub.github.io/snakeviz/) or `python -m pstats`:
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
scope-profiler pproc profiling_data.h5 -o figures --export-prof --skip-plot-images
|
|
394
|
+
snakeviz figures/profile_rank0.prof
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Regions become "functions": `cumtime` is a region's total wall time and
|
|
398
|
+
`tottime` is that minus the time spent in its nested regions. One file is
|
|
399
|
+
written per exported rank, since `.prof` has no notion of ranks — see
|
|
400
|
+
[the CLI docs](docs/source/cli.md) for the caveats of the reconstruction.
|
|
401
|
+
|
|
402
|
+
### Viewing a run in speedscope
|
|
403
|
+
|
|
404
|
+
`--export-speedscope` writes the run as a
|
|
405
|
+
[speedscope](https://www.speedscope.app) JSON file. Unlike `.prof`, it keeps
|
|
406
|
+
every individual call, so the timeline shows the run as it happened:
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
scope-profiler pproc profiling_data.h5 -o figures --export-speedscope --skip-plot-images
|
|
410
|
+
npx speedscope figures/profile.speedscope.json # or drop the file on speedscope.app
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
One file is written per input, holding one profile per exported rank, all
|
|
414
|
+
sharing a time origin so ranks stay aligned. See
|
|
415
|
+
[the CLI docs](docs/source/cli.md) for details.
|
|
@@ -5,7 +5,7 @@ requires = [ "setuptools", "wheel" ]
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "scope-profiler"
|
|
8
|
-
version = "0.2.
|
|
8
|
+
version = "0.2.4"
|
|
9
9
|
description = "Profile code regions in python, optionally with LIKWID markers."
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
keywords = [ "python" ]
|
|
@@ -24,6 +24,9 @@ classifiers = [
|
|
|
24
24
|
dependencies = [
|
|
25
25
|
"h5py",
|
|
26
26
|
"numpy",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
optional-dependencies.line-profiler = [
|
|
27
30
|
"line-profiler",
|
|
28
31
|
]
|
|
29
32
|
|
|
@@ -37,13 +40,14 @@ optional-dependencies.pproc = [
|
|
|
37
40
|
"matplotlib",
|
|
38
41
|
"maxplotlibx >= 0.1.5",
|
|
39
42
|
"pandas",
|
|
43
|
+
"snakeviz",
|
|
40
44
|
]
|
|
41
45
|
|
|
42
46
|
optional-dependencies.dev = [
|
|
43
47
|
"black[jupyter]",
|
|
44
48
|
"isort",
|
|
45
49
|
"ruff",
|
|
46
|
-
"scope-profiler[docs,
|
|
50
|
+
"scope-profiler[docs,line-profiler,mpi,pproc,test]",
|
|
47
51
|
]
|
|
48
52
|
# https://medium.com/@pratikdomadiya123/build-project-documentation-quickly-with-the-sphinx-python-2a9732b66594
|
|
49
53
|
optional-dependencies.docs = [
|
|
@@ -54,7 +58,7 @@ optional-dependencies.docs = [
|
|
|
54
58
|
"pyproject-fmt",
|
|
55
59
|
"sphinx",
|
|
56
60
|
"sphinx-book-theme",
|
|
57
|
-
"scope-profiler[pproc]",
|
|
61
|
+
"scope-profiler[line-profiler,pproc]",
|
|
58
62
|
]
|
|
59
63
|
optional-dependencies.test = [
|
|
60
64
|
"coverage",
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Detection of whether the process was launched by an MPI launcher.
|
|
2
|
+
|
|
3
|
+
Importing ``mpi4py.MPI`` calls ``MPI_Init``, and any collective (``bcast``,
|
|
4
|
+
``Barrier``, ...) issued afterwards costs something even on a single process.
|
|
5
|
+
A plain ``python script.py`` run should therefore never touch MPI at all, even
|
|
6
|
+
when mpi4py happens to be installed. This module answers the only question
|
|
7
|
+
that decides it: was this process started by ``mpirun``/``mpiexec``/``srun``
|
|
8
|
+
(or an equivalent launcher)?
|
|
9
|
+
|
|
10
|
+
The answer is read from the environment the launcher sets up, so it is
|
|
11
|
+
available before mpi4py is imported.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
# Per-process variables exported by the process managers behind the common
|
|
18
|
+
# launchers. Each is set only for processes started *by* the launcher, so the
|
|
19
|
+
# presence of any one of them means "this rank belongs to an MPI job".
|
|
20
|
+
# SLURM_PROCID is deliberately absent: it is also set for the script of a
|
|
21
|
+
# plain `sbatch` job, which is not an MPI launch. `srun` is covered by the
|
|
22
|
+
# PMI/PMIX variables its MPI plugin exports.
|
|
23
|
+
_LAUNCHER_ENV_VARS = (
|
|
24
|
+
"OMPI_COMM_WORLD_RANK", # Open MPI (and derivatives: Spectrum, ...)
|
|
25
|
+
"PMI_RANK", # MPICH, Intel MPI, MS-MPI, Cray, srun (pmi2)
|
|
26
|
+
"PMIX_RANK", # PMIx, used by srun --mpi=pmix and Open MPI 5
|
|
27
|
+
"MV2_COMM_WORLD_RANK", # MVAPICH2
|
|
28
|
+
"MPI_LOCALRANKID", # Hydra (mpiexec.hydra)
|
|
29
|
+
"ALPS_APP_PE", # Cray ALPS aprun
|
|
30
|
+
"PALS_RANKID", # Cray PALS palsrun
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Escape hatch: force the decision either way without touching code, e.g. for
|
|
34
|
+
# a launcher whose variables are not listed above.
|
|
35
|
+
_OVERRIDE_ENV_VAR = "SCOPE_PROFILER_MPI"
|
|
36
|
+
|
|
37
|
+
_TRUE_VALUES = ("1", "true", "yes", "on")
|
|
38
|
+
_FALSE_VALUES = ("0", "false", "no", "off")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _override() -> bool | None:
|
|
42
|
+
"""Value of ``SCOPE_PROFILER_MPI``, or None if unset/unrecognized."""
|
|
43
|
+
value = os.environ.get(_OVERRIDE_ENV_VAR)
|
|
44
|
+
if value is None:
|
|
45
|
+
return None
|
|
46
|
+
value = value.strip().lower()
|
|
47
|
+
if value in _TRUE_VALUES:
|
|
48
|
+
return True
|
|
49
|
+
if value in _FALSE_VALUES:
|
|
50
|
+
return False
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def launched_under_mpi() -> bool:
|
|
55
|
+
"""Whether this process was started by an MPI launcher.
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
bool
|
|
60
|
+
True if a launcher's per-rank environment variable is present, or if
|
|
61
|
+
the application itself already initialized MPI (in which case using
|
|
62
|
+
the communicator is free). ``SCOPE_PROFILER_MPI=0``/``1`` overrides
|
|
63
|
+
the detection.
|
|
64
|
+
"""
|
|
65
|
+
override = _override()
|
|
66
|
+
if override is not None:
|
|
67
|
+
return override
|
|
68
|
+
|
|
69
|
+
if any(var in os.environ for var in _LAUNCHER_ENV_VARS):
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
# The application may have initialized MPI itself (embedded interpreter,
|
|
73
|
+
# or an explicit `from mpi4py import MPI`). Only inspect mpi4py if it is
|
|
74
|
+
# already imported: importing it here is exactly what must be avoided.
|
|
75
|
+
mpi_module = sys.modules.get("mpi4py.MPI")
|
|
76
|
+
if mpi_module is not None:
|
|
77
|
+
try:
|
|
78
|
+
return bool(mpi_module.Is_initialized())
|
|
79
|
+
except AttributeError:
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_comm(use_mpi: bool | None = None):
|
|
86
|
+
"""Return ``MPI.COMM_WORLD``, or None when MPI must not be used.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
use_mpi : bool or None, optional
|
|
91
|
+
None (default) decides via :func:`launched_under_mpi`. True forces the
|
|
92
|
+
communicator (and fails loudly if mpi4py is missing); False disables
|
|
93
|
+
MPI unconditionally.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
mpi4py.MPI.Intracomm or None
|
|
98
|
+
The world communicator, or None if this is not an MPI run or mpi4py
|
|
99
|
+
is unavailable.
|
|
100
|
+
"""
|
|
101
|
+
if use_mpi is False:
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
if use_mpi is None and not launched_under_mpi():
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
from mpi4py import MPI
|
|
109
|
+
except ImportError:
|
|
110
|
+
if use_mpi:
|
|
111
|
+
raise ImportError(
|
|
112
|
+
"MPI profiling was requested (use_mpi=True) but mpi4py is not "
|
|
113
|
+
"installed."
|
|
114
|
+
)
|
|
115
|
+
# Launched by mpirun without mpi4py available: fall back to treating
|
|
116
|
+
# this rank as a standalone process rather than failing the run.
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
return MPI.COMM_WORLD
|
|
@@ -13,6 +13,8 @@ from scope_profiler.plotting_scripts import (
|
|
|
13
13
|
plot_speedup,
|
|
14
14
|
write_region_statistics_json,
|
|
15
15
|
)
|
|
16
|
+
from scope_profiler.prof_export import export_prof
|
|
17
|
+
from scope_profiler.speedscope_export import export_speedscope
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
def parse_ranks(spec: str, verbose: bool = False) -> list[int]:
|
|
@@ -155,14 +157,39 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
155
157
|
"consistent colors."
|
|
156
158
|
),
|
|
157
159
|
)
|
|
160
|
+
parser.add_argument(
|
|
161
|
+
"--export-prof",
|
|
162
|
+
action="store_true",
|
|
163
|
+
help=(
|
|
164
|
+
"Also write one profile_rank<N>.prof file per exported rank in the "
|
|
165
|
+
"cProfile/pstats format, so the run can be browsed with external "
|
|
166
|
+
"tools (`snakeviz profile_rank0.prof`, `python -m pstats ...`). "
|
|
167
|
+
"The call graph is reconstructed from region nesting; only ranks "
|
|
168
|
+
"selected with --ranks are exported (default: rank 0). Requires "
|
|
169
|
+
"-o/--output."
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
parser.add_argument(
|
|
173
|
+
"--export-speedscope",
|
|
174
|
+
action="store_true",
|
|
175
|
+
help=(
|
|
176
|
+
"Also write a profile.speedscope.json file holding one profile per "
|
|
177
|
+
"exported rank, viewable at https://www.speedscope.app (or with "
|
|
178
|
+
"`npx speedscope profile.speedscope.json`). Unlike --export-prof "
|
|
179
|
+
"this keeps every individual call, so the timeline shows the run "
|
|
180
|
+
"as it happened. The call graph is reconstructed from region "
|
|
181
|
+
"nesting; only ranks selected with --ranks are exported (default: "
|
|
182
|
+
"rank 0). Requires -o/--output."
|
|
183
|
+
),
|
|
184
|
+
)
|
|
158
185
|
parser.add_argument(
|
|
159
186
|
"--skip-plot-images",
|
|
160
187
|
action="store_true",
|
|
161
188
|
help=(
|
|
162
189
|
"Do not render/save the PNG plot images, only the outputs from "
|
|
163
|
-
"--export-data. Useful when
|
|
164
|
-
"side (e.g. with Plotly) from
|
|
165
|
-
"
|
|
190
|
+
"--export-data/--export-prof/--export-speedscope. Useful when "
|
|
191
|
+
"charts are rendered entirely client-side (e.g. with Plotly) from "
|
|
192
|
+
"the exported data. Requires one of those export options."
|
|
166
193
|
),
|
|
167
194
|
)
|
|
168
195
|
return parser
|
|
@@ -203,8 +230,18 @@ def main(argv: list[str] | None = None):
|
|
|
203
230
|
if args.export_data and not args.output:
|
|
204
231
|
parser.error("--export-data requires -o/--output.")
|
|
205
232
|
|
|
206
|
-
if args.
|
|
207
|
-
parser.error("--
|
|
233
|
+
if args.export_prof and not args.output:
|
|
234
|
+
parser.error("--export-prof requires -o/--output.")
|
|
235
|
+
|
|
236
|
+
if args.export_speedscope and not args.output:
|
|
237
|
+
parser.error("--export-speedscope requires -o/--output.")
|
|
238
|
+
|
|
239
|
+
exports_requested = args.export_data or args.export_prof or args.export_speedscope
|
|
240
|
+
if args.skip_plot_images and not exports_requested:
|
|
241
|
+
parser.error(
|
|
242
|
+
"--skip-plot-images requires --export-data, --export-prof or "
|
|
243
|
+
"--export-speedscope."
|
|
244
|
+
)
|
|
208
245
|
|
|
209
246
|
if args.ranks:
|
|
210
247
|
ranks = []
|
|
@@ -243,6 +280,11 @@ def main(argv: list[str] | None = None):
|
|
|
243
280
|
flame_data_path = None
|
|
244
281
|
durations_data_path = None
|
|
245
282
|
speedup_data_path = None
|
|
283
|
+
prof_path = None
|
|
284
|
+
prof_paths: list = []
|
|
285
|
+
speedscope_path = None
|
|
286
|
+
speedscope_paths: list = []
|
|
287
|
+
durations_paths: list = []
|
|
246
288
|
if args.output:
|
|
247
289
|
os.makedirs(args.output, exist_ok=True)
|
|
248
290
|
if not args.skip_plot_images:
|
|
@@ -266,62 +308,92 @@ def main(argv: list[str] | None = None):
|
|
|
266
308
|
speedup_data_path = os.path.join(
|
|
267
309
|
args.output, f"speedup_data.{data_ext}"
|
|
268
310
|
)
|
|
311
|
+
if args.export_prof:
|
|
312
|
+
prof_path = os.path.join(args.output, "profile.prof")
|
|
313
|
+
if args.export_speedscope:
|
|
314
|
+
speedscope_path = os.path.join(args.output, "profile.speedscope.json")
|
|
269
315
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
include=args.include,
|
|
275
|
-
exclude=args.exclude,
|
|
276
|
-
ranks=args.ranks,
|
|
277
|
-
cmap=args.cmap,
|
|
278
|
-
data_filepath=gantt_data_path,
|
|
279
|
-
data_format=args.export_data_format,
|
|
280
|
-
backend=args.backend,
|
|
281
|
-
)
|
|
316
|
+
# --skip-plot-images still needs the plotting functions to produce the
|
|
317
|
+
# --export-data files, but an export-only run should not touch the
|
|
318
|
+
# plotting backend at all.
|
|
319
|
+
render_plots = not args.skip_plot_images or args.export_data
|
|
282
320
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
data_format=args.export_data_format,
|
|
293
|
-
backend=args.backend,
|
|
294
|
-
)
|
|
321
|
+
if args.export_prof:
|
|
322
|
+
prof_paths = export_prof(
|
|
323
|
+
profiling_data=readers,
|
|
324
|
+
filepath=prof_path,
|
|
325
|
+
ranks=args.ranks,
|
|
326
|
+
include=args.include,
|
|
327
|
+
exclude=args.exclude,
|
|
328
|
+
verbose=False,
|
|
329
|
+
)
|
|
295
330
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
331
|
+
if args.export_speedscope:
|
|
332
|
+
speedscope_paths = export_speedscope(
|
|
333
|
+
profiling_data=readers,
|
|
334
|
+
filepath=speedscope_path,
|
|
335
|
+
ranks=args.ranks,
|
|
336
|
+
include=args.include,
|
|
337
|
+
exclude=args.exclude,
|
|
338
|
+
verbose=False,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
if render_plots:
|
|
342
|
+
plot_gantt(
|
|
343
|
+
profiling_data=readers,
|
|
344
|
+
filepath=gantt_path,
|
|
345
|
+
show=args.show,
|
|
346
|
+
include=args.include,
|
|
347
|
+
exclude=args.exclude,
|
|
348
|
+
ranks=args.ranks,
|
|
349
|
+
cmap=args.cmap,
|
|
350
|
+
data_filepath=gantt_data_path,
|
|
351
|
+
data_format=args.export_data_format,
|
|
352
|
+
backend=args.backend,
|
|
353
|
+
)
|
|
309
354
|
|
|
310
|
-
|
|
311
|
-
plot_speedup(
|
|
355
|
+
plot_flame(
|
|
312
356
|
profiling_data=readers,
|
|
313
|
-
|
|
357
|
+
filepath=flame_path,
|
|
358
|
+
show=args.show,
|
|
359
|
+
include=args.include,
|
|
360
|
+
exclude=args.exclude,
|
|
314
361
|
ranks=args.ranks,
|
|
315
|
-
|
|
362
|
+
cmap=args.cmap,
|
|
363
|
+
data_filepath=flame_data_path,
|
|
364
|
+
data_format=args.export_data_format,
|
|
365
|
+
backend=args.backend,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
durations_paths = plot_durations(
|
|
369
|
+
profiling_data=readers,
|
|
370
|
+
filepath=durations_path,
|
|
316
371
|
show=args.show,
|
|
317
372
|
include=args.include,
|
|
318
373
|
exclude=args.exclude,
|
|
374
|
+
ranks=args.ranks,
|
|
375
|
+
metrics=args.metrics,
|
|
319
376
|
cmap=args.cmap,
|
|
320
|
-
data_filepath=
|
|
377
|
+
data_filepath=durations_data_path,
|
|
321
378
|
data_format=args.export_data_format,
|
|
322
379
|
backend=args.backend,
|
|
323
380
|
)
|
|
324
381
|
|
|
382
|
+
if len(readers) > 1:
|
|
383
|
+
plot_speedup(
|
|
384
|
+
profiling_data=readers,
|
|
385
|
+
x_field=args.x_field,
|
|
386
|
+
ranks=args.ranks,
|
|
387
|
+
filepath=speedup_path,
|
|
388
|
+
show=args.show,
|
|
389
|
+
include=args.include,
|
|
390
|
+
exclude=args.exclude,
|
|
391
|
+
cmap=args.cmap,
|
|
392
|
+
data_filepath=speedup_data_path,
|
|
393
|
+
data_format=args.export_data_format,
|
|
394
|
+
backend=args.backend,
|
|
395
|
+
)
|
|
396
|
+
|
|
325
397
|
if statistics_path:
|
|
326
398
|
write_region_statistics_json(
|
|
327
399
|
profiling_data=readers,
|
|
@@ -333,7 +405,7 @@ def main(argv: list[str] | None = None):
|
|
|
333
405
|
|
|
334
406
|
if args.output and not args.show:
|
|
335
407
|
saved = [
|
|
336
|
-
path
|
|
408
|
+
str(path)
|
|
337
409
|
for path in (
|
|
338
410
|
gantt_path,
|
|
339
411
|
flame_path,
|
|
@@ -344,10 +416,19 @@ def main(argv: list[str] | None = None):
|
|
|
344
416
|
flame_data_path,
|
|
345
417
|
durations_data_path,
|
|
346
418
|
speedup_data_path,
|
|
419
|
+
*prof_paths,
|
|
420
|
+
*speedscope_paths,
|
|
347
421
|
)
|
|
348
422
|
if path
|
|
349
423
|
]
|
|
350
424
|
print("Outputs saved to:\n " + "\n ".join(saved))
|
|
425
|
+
if prof_paths:
|
|
426
|
+
print(f"\nView a .prof file with: snakeviz {prof_paths[0]}")
|
|
427
|
+
if speedscope_paths:
|
|
428
|
+
print(
|
|
429
|
+
f"\nView {speedscope_paths[0]} at https://www.speedscope.app "
|
|
430
|
+
"(or: npx speedscope <file>)"
|
|
431
|
+
)
|
|
351
432
|
|
|
352
433
|
|
|
353
434
|
if __name__ == "__main__":
|