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.
Files changed (31) hide show
  1. {scope_profiler-0.2 → scope_profiler-0.2.1}/PKG-INFO +42 -1
  2. {scope_profiler-0.2 → scope_profiler-0.2.1}/README.md +41 -0
  3. {scope_profiler-0.2 → scope_profiler-0.2.1}/pyproject.toml +1 -1
  4. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/__init__.py +7 -0
  5. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/h5reader.py +19 -0
  6. scope_profiler-0.2.1/src/scope_profiler/metadata.py +86 -0
  7. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/plotting_scripts.py +322 -31
  8. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/post_processing.py +85 -5
  9. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/profile_config.py +12 -0
  10. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/profile_manager.py +5 -0
  11. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/tests/test_app.py +43 -0
  12. scope_profiler-0.2.1/src/scope_profiler/tests/test_post_processing.py +511 -0
  13. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler.egg-info/PKG-INFO +42 -1
  14. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler.egg-info/SOURCES.txt +1 -0
  15. scope_profiler-0.2/src/scope_profiler/tests/test_post_processing.py +0 -221
  16. {scope_profiler-0.2 → scope_profiler-0.2.1}/setup.cfg +0 -0
  17. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/__main__.py +0 -0
  18. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/mpi_region.py +0 -0
  19. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/region.py +0 -0
  20. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/region_profiler.py +0 -0
  21. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/tests/__init__.py +0 -0
  22. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/tests/examples.py +0 -0
  23. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/tests/examples_pylikwid.py +0 -0
  24. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/tests/pylikwid_readme.py +0 -0
  25. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/tests/test_mpi.py +0 -0
  26. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/tests/test_overhead.py +0 -0
  27. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler/tests/test_readme.py +0 -0
  28. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler.egg-info/dependency_links.txt +0 -0
  29. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler.egg-info/entry_points.txt +0 -0
  30. {scope_profiler-0.2 → scope_profiler-0.2.1}/src/scope_profiler.egg-info/requires.txt +0 -0
  31. {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
+ ```
@@ -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.1"
9
9
  description = "Profile code regions in python, optionally with LIKWID markers."
10
10
  readme = "README.md"
11
11
  keywords = [ "python" ]
@@ -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
+ }