scope-profiler 0.2__tar.gz → 0.2.1__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 → scope_profiler-0.2.1}/PKG-INFO +42 -1
- {scope_profiler-0.2 → scope_profiler-0.2.1}/README.md +41 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/pyproject.toml +1 -1
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/__init__.py +7 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/h5reader.py +19 -0
- scope_profiler-0.2.1/src/scope_profiler/metadata.py +86 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/plotting_scripts.py +322 -31
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/post_processing.py +85 -5
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/profile_config.py +12 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/profile_manager.py +5 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/tests/test_app.py +43 -0
- scope_profiler-0.2.1/src/scope_profiler/tests/test_post_processing.py +511 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler.egg-info/PKG-INFO +42 -1
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler.egg-info/SOURCES.txt +1 -0
- scope_profiler-0.2/src/scope_profiler/tests/test_post_processing.py +0 -221
- {scope_profiler-0.2 → scope_profiler-0.2.1}/setup.cfg +0 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/__main__.py +0 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/mpi_region.py +0 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/region.py +0 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/region_profiler.py +0 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/tests/__init__.py +0 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/tests/examples.py +0 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/tests/examples_pylikwid.py +0 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/tests/pylikwid_readme.py +0 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/tests/test_mpi.py +0 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/tests/test_overhead.py +0 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/tests/test_readme.py +0 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler.egg-info/dependency_links.txt +0 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler.egg-info/entry_points.txt +0 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler.egg-info/requires.txt +0 -0
- {scope_profiler-0.2 → scope_profiler-0.2.1}/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.1
|
|
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
|
|
@@ -284,3 +284,44 @@ scope-profiler pproc profiling_data.h5 --cmap viridis -o figures
|
|
|
284
284
|
By default the flame graph covers rank 0, since it represents a single
|
|
285
285
|
execution's call stack; pass `ranks=[...]` to render one flame graph per
|
|
286
286
|
requested rank.
|
|
287
|
+
|
|
288
|
+
## Exporting plot data
|
|
289
|
+
|
|
290
|
+
Every `plot_*` function accepts a `data_filepath` argument that writes the
|
|
291
|
+
exact data behind the chart to a file, so it can be re-parsed and re-plotted
|
|
292
|
+
later without the original HDF5 file. `data_format` selects `"csv"` (default)
|
|
293
|
+
or `"json"`:
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
plot_gantt(reader, filepath="gantt_plot.png", data_filepath="gantt_data.csv")
|
|
297
|
+
plot_gantt(
|
|
298
|
+
reader,
|
|
299
|
+
filepath="gantt_plot.png",
|
|
300
|
+
data_filepath="gantt_data.json",
|
|
301
|
+
data_format="json",
|
|
302
|
+
)
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
The JSON payload additionally includes a `colors` map (region or file label
|
|
306
|
+
to `#rrggbb`) matching the colors used in the matplotlib plot, so a
|
|
307
|
+
JavaScript charting library like Plotly can reproduce the same look.
|
|
308
|
+
|
|
309
|
+
`scope-profiler pproc --export-data` does the same for every plot in one
|
|
310
|
+
run, writing `gantt_data`, `flame_data`, `durations_data`, and (for multiple
|
|
311
|
+
input files) `speedup_data` alongside the PNGs. Pass `--export-data-format
|
|
312
|
+
json` to get `.json` files instead of the default `.csv`:
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
scope-profiler pproc profiling_data.h5 -o figures --export-data
|
|
316
|
+
scope-profiler pproc profiling_data.h5 -o figures --export-data --export-data-format json
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Pass `--skip-plot-images` (requires `--export-data`) to skip rendering the
|
|
320
|
+
PNGs entirely and only write the exported data plus `region_statistics.json`
|
|
321
|
+
— useful when a website renders charts client-side (e.g. with Plotly)
|
|
322
|
+
straight from the JSON:
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
scope-profiler pproc profiling_data.h5 -o figures \
|
|
326
|
+
--export-data --export-data-format json --skip-plot-images
|
|
327
|
+
```
|
|
@@ -241,3 +241,44 @@ scope-profiler pproc profiling_data.h5 --cmap viridis -o figures
|
|
|
241
241
|
By default the flame graph covers rank 0, since it represents a single
|
|
242
242
|
execution's call stack; pass `ranks=[...]` to render one flame graph per
|
|
243
243
|
requested rank.
|
|
244
|
+
|
|
245
|
+
## Exporting plot data
|
|
246
|
+
|
|
247
|
+
Every `plot_*` function accepts a `data_filepath` argument that writes the
|
|
248
|
+
exact data behind the chart to a file, so it can be re-parsed and re-plotted
|
|
249
|
+
later without the original HDF5 file. `data_format` selects `"csv"` (default)
|
|
250
|
+
or `"json"`:
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
plot_gantt(reader, filepath="gantt_plot.png", data_filepath="gantt_data.csv")
|
|
254
|
+
plot_gantt(
|
|
255
|
+
reader,
|
|
256
|
+
filepath="gantt_plot.png",
|
|
257
|
+
data_filepath="gantt_data.json",
|
|
258
|
+
data_format="json",
|
|
259
|
+
)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
The JSON payload additionally includes a `colors` map (region or file label
|
|
263
|
+
to `#rrggbb`) matching the colors used in the matplotlib plot, so a
|
|
264
|
+
JavaScript charting library like Plotly can reproduce the same look.
|
|
265
|
+
|
|
266
|
+
`scope-profiler pproc --export-data` does the same for every plot in one
|
|
267
|
+
run, writing `gantt_data`, `flame_data`, `durations_data`, and (for multiple
|
|
268
|
+
input files) `speedup_data` alongside the PNGs. Pass `--export-data-format
|
|
269
|
+
json` to get `.json` files instead of the default `.csv`:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
scope-profiler pproc profiling_data.h5 -o figures --export-data
|
|
273
|
+
scope-profiler pproc profiling_data.h5 -o figures --export-data --export-data-format json
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Pass `--skip-plot-images` (requires `--export-data`) to skip rendering the
|
|
277
|
+
PNGs entirely and only write the exported data plus `region_statistics.json`
|
|
278
|
+
— useful when a website renders charts client-side (e.g. with Plotly)
|
|
279
|
+
straight from the JSON:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
scope-profiler pproc profiling_data.h5 -o figures \
|
|
283
|
+
--export-data --export-data-format json --skip-plot-images
|
|
284
|
+
```
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
"""scope-profiler: lightweight region-based profiling for Python and HPC applications."""
|
|
2
2
|
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
3
5
|
from scope_profiler.profile_manager import ProfileManager
|
|
4
6
|
|
|
7
|
+
try:
|
|
8
|
+
__version__ = version("scope-profiler")
|
|
9
|
+
except PackageNotFoundError:
|
|
10
|
+
__version__ = "unknown"
|
|
11
|
+
|
|
5
12
|
__all__ = [
|
|
6
13
|
"ProfileManager",
|
|
7
14
|
]
|
|
@@ -35,6 +35,7 @@ class ProfilingH5Reader:
|
|
|
35
35
|
"""
|
|
36
36
|
self._file_path = Path(file_path)
|
|
37
37
|
self._num_ranks = 0
|
|
38
|
+
self._metadata: dict = {}
|
|
38
39
|
if not self.file_path.exists():
|
|
39
40
|
raise FileNotFoundError(f"HDF5 file not found: {self.file_path}")
|
|
40
41
|
|
|
@@ -42,8 +43,13 @@ class ProfilingH5Reader:
|
|
|
42
43
|
_region_dict = {}
|
|
43
44
|
region_names = []
|
|
44
45
|
with h5py.File(self.file_path, "r") as f:
|
|
46
|
+
if "metadata" in f:
|
|
47
|
+
self._metadata = dict(f["metadata"].attrs)
|
|
48
|
+
|
|
45
49
|
# Iterate over all rank groups
|
|
46
50
|
for rank_group_name, rank_group in f.items():
|
|
51
|
+
if rank_group_name == "metadata":
|
|
52
|
+
continue
|
|
47
53
|
self._num_ranks += 1
|
|
48
54
|
if verbose:
|
|
49
55
|
print(f"{rank_group_name = }")
|
|
@@ -104,6 +110,19 @@ class ProfilingH5Reader:
|
|
|
104
110
|
"""
|
|
105
111
|
return self._file_path
|
|
106
112
|
|
|
113
|
+
@property
|
|
114
|
+
def metadata(self) -> dict:
|
|
115
|
+
"""
|
|
116
|
+
Get environment metadata for the run (gathered from rank 0).
|
|
117
|
+
|
|
118
|
+
Returns
|
|
119
|
+
-------
|
|
120
|
+
dict
|
|
121
|
+
Metadata dict (hostname, OpenMP thread count, platform, versions,
|
|
122
|
+
etc.), or an empty dict if the file predates metadata collection.
|
|
123
|
+
"""
|
|
124
|
+
return self._metadata
|
|
125
|
+
|
|
107
126
|
@property
|
|
108
127
|
def num_ranks(self) -> int:
|
|
109
128
|
"""
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Collects descriptive metadata about the environment a profiling run executed in."""
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
4
|
+
import datetime
|
|
5
|
+
import getpass
|
|
6
|
+
import os
|
|
7
|
+
import platform
|
|
8
|
+
import socket
|
|
9
|
+
from typing import Dict, Union
|
|
10
|
+
|
|
11
|
+
MetadataValue = Union[str, int]
|
|
12
|
+
|
|
13
|
+
# Common OpenMP runtime library names across platforms/compilers.
|
|
14
|
+
_OMP_LIBRARY_NAMES = (
|
|
15
|
+
"libomp.so",
|
|
16
|
+
"libgomp.so.1",
|
|
17
|
+
"libiomp5.so",
|
|
18
|
+
"libomp.dylib",
|
|
19
|
+
"libiomp5.dylib",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _detect_omp_num_threads() -> int:
|
|
24
|
+
"""Best-effort detection of the number of OpenMP threads available.
|
|
25
|
+
|
|
26
|
+
Tries to query the OpenMP runtime directly via ``omp_get_max_threads``,
|
|
27
|
+
so the recorded value is correct even when ``OMP_NUM_THREADS`` is unset
|
|
28
|
+
(OpenMP then defaults to the number of available cores rather than 1).
|
|
29
|
+
Falls back to the ``OMP_NUM_THREADS`` environment variable, then to 1.
|
|
30
|
+
"""
|
|
31
|
+
for libname in _OMP_LIBRARY_NAMES:
|
|
32
|
+
try:
|
|
33
|
+
lib = ctypes.CDLL(libname)
|
|
34
|
+
return int(lib.omp_get_max_threads())
|
|
35
|
+
except (OSError, AttributeError):
|
|
36
|
+
continue
|
|
37
|
+
|
|
38
|
+
env_value = os.environ.get("OMP_NUM_THREADS")
|
|
39
|
+
if env_value:
|
|
40
|
+
try:
|
|
41
|
+
# OMP_NUM_THREADS may be a comma-separated list for nested
|
|
42
|
+
# parallelism; the first value is the outermost level.
|
|
43
|
+
return int(env_value.split(",")[0].strip())
|
|
44
|
+
except ValueError:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
return 1
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def collect_metadata(mpi_size: int = 1) -> Dict[str, MetadataValue]:
|
|
51
|
+
"""Gather metadata describing the current run's environment.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
mpi_size : int, optional
|
|
56
|
+
Number of MPI ranks the run was launched with (default: 1). Used to
|
|
57
|
+
derive ``total_cores`` (``mpi_size * omp_num_threads``), a single
|
|
58
|
+
combined parallelism value useful as a scaling-plot x-axis.
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
dict
|
|
63
|
+
Mapping of metadata field name to a str or int value, suitable for
|
|
64
|
+
storing as HDF5 attributes.
|
|
65
|
+
"""
|
|
66
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
scope_profiler_version = version("scope-profiler")
|
|
70
|
+
except PackageNotFoundError:
|
|
71
|
+
scope_profiler_version = "unknown"
|
|
72
|
+
|
|
73
|
+
omp_num_threads = _detect_omp_num_threads()
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
"timestamp": datetime.datetime.now().isoformat(),
|
|
77
|
+
"hostname": socket.gethostname(),
|
|
78
|
+
"platform": platform.platform(),
|
|
79
|
+
"python_version": platform.python_version(),
|
|
80
|
+
"scope_profiler_version": scope_profiler_version,
|
|
81
|
+
"working_directory": os.getcwd(),
|
|
82
|
+
"omp_num_threads": omp_num_threads,
|
|
83
|
+
"mpi_size": mpi_size,
|
|
84
|
+
"total_cores": mpi_size * omp_num_threads,
|
|
85
|
+
"user": getpass.getuser(),
|
|
86
|
+
}
|