carm-roofline 1.0.2.dev1__tar.gz → 1.0.2.dev2__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 (110) hide show
  1. {carm_roofline-1.0.2.dev1/carm_roofline.egg-info → carm_roofline-1.0.2.dev2}/PKG-INFO +15 -4
  2. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/README.md +14 -3
  3. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/__init__.py +8 -1
  4. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/identity.py +115 -13
  5. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/carm.py +4 -1
  6. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2/carm_roofline.egg-info}/PKG-INFO +15 -4
  7. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/profiling/__init__.py +2 -0
  8. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/profiling/aggregation.py +20 -20
  9. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/profiling/config.py +3 -10
  10. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/profiling/output.py +6 -0
  11. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/profiling/shared.py +22 -14
  12. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/pyproject.toml +1 -1
  13. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/test_bench/test_bench.c +2 -0
  14. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/test_bench/test_bench.h +2 -0
  15. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/test_bench/wrapper.inl +7 -0
  16. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/LICENSE +0 -0
  17. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/architecture.py +0 -0
  18. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/arm.py +0 -0
  19. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/config.py +0 -0
  20. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/detect.py +0 -0
  21. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/frequency.py +0 -0
  22. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/memory.py +0 -0
  23. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/riscv.py +0 -0
  24. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/tests/arm/features.c +0 -0
  25. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/tests/arm/frequency.h +0 -0
  26. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/tests/arm/vlen.c +0 -0
  27. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/tests/frequency.c +0 -0
  28. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/tests/riscv/frequency.h +0 -0
  29. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/tests/riscv/rvv_version.c +0 -0
  30. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/tests/riscv/vlen.c +0 -0
  31. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/tests/x86/cache.c +0 -0
  32. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/tests/x86/features.c +0 -0
  33. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/tests/x86/frequency.h +0 -0
  34. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/tests/x86/x86_avx512/frequency.h +0 -0
  35. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/architecture/x86.py +0 -0
  36. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/arguments.py +0 -0
  37. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/__init__.py +0 -0
  38. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/benchmark.py +0 -0
  39. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/benchmarking.py +0 -0
  40. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/generation/__init__.py +0 -0
  41. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/generation/arm.py +0 -0
  42. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/generation/code_gen/__init__.py +0 -0
  43. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/generation/code_gen/data_type.py +0 -0
  44. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/generation/code_gen/instruction.py +0 -0
  45. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/generation/code_gen/operation.py +0 -0
  46. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/generation/code_gen/register.py +0 -0
  47. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/generation/isa.py +0 -0
  48. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/generation/parameters.py +0 -0
  49. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/generation/riscv.py +0 -0
  50. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/generation/x86.py +0 -0
  51. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/interface.py +0 -0
  52. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/output/__init__.py +0 -0
  53. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/output/arithmetic.py +0 -0
  54. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/output/base.py +0 -0
  55. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/output/common.py +0 -0
  56. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/output/jsonl.py +0 -0
  57. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/output/memory.py +0 -0
  58. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/output/memory_sweep.py +0 -0
  59. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/output/mixed.py +0 -0
  60. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/output/roofline.py +0 -0
  61. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/result.py +0 -0
  62. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/suites/__init__.py +0 -0
  63. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/suites/arithmetic.py +0 -0
  64. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/suites/base.py +0 -0
  65. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/suites/memory.py +0 -0
  66. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/suites/memory_sweep.py +0 -0
  67. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/suites/mixed.py +0 -0
  68. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/benchmark/suites/roofline.py +0 -0
  69. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/carm_roofline.egg-info/SOURCES.txt +0 -0
  70. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/carm_roofline.egg-info/dependency_links.txt +0 -0
  71. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/carm_roofline.egg-info/entry_points.txt +0 -0
  72. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/carm_roofline.egg-info/requires.txt +0 -0
  73. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/carm_roofline.egg-info/top_level.txt +0 -0
  74. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/context.py +0 -0
  75. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/error.py +0 -0
  76. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/exec_interface.py +0 -0
  77. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/gui/__init__.py +0 -0
  78. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/gui/assets/CARM_icon.svg +0 -0
  79. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/gui/assets/CHAMP_logo.svg +0 -0
  80. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/gui/assets/menu_icon.png +0 -0
  81. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/gui/assets/style.css +0 -0
  82. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/gui/components.py +0 -0
  83. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/gui/config.py +0 -0
  84. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/gui/data.py +0 -0
  85. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/gui/factory.py +0 -0
  86. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/gui/ids.py +0 -0
  87. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/output_utils.py +0 -0
  88. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/profiling/backends.py +0 -0
  89. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/profiling/model.py +0 -0
  90. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/profiling/papi_backend.py +0 -0
  91. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/profiling/papi_loader.py +0 -0
  92. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/profiling/papi_metrics.py +0 -0
  93. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/profiling/perf_backend.py +0 -0
  94. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/profiling/perf_loader.py +0 -0
  95. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/profiling/perf_metrics.py +0 -0
  96. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/results_paths.py +0 -0
  97. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/roofline_assembly.py +0 -0
  98. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/run_config.py +0 -0
  99. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/setup.cfg +0 -0
  100. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/test/test_gui_data.py +0 -0
  101. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/test/test_isa_codegen_integration.py +0 -0
  102. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/test/test_memory_suite_generation.py +0 -0
  103. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/test/test_output_module.py +0 -0
  104. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/test/test_roofline_assembly.py +0 -0
  105. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/test/test_typed_benchmarks.py +0 -0
  106. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/test_bench/__init__.py +0 -0
  107. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/test_bench/builder.py +0 -0
  108. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/units.py +0 -0
  109. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/utils.py +0 -0
  110. {carm_roofline-1.0.2.dev1 → carm_roofline-1.0.2.dev2}/workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: carm-roofline
3
- Version: 1.0.2.dev1
3
+ Version: 1.0.2.dev2
4
4
  Summary: CARM: Cache-Aware Roofline Model benchmarking and visualization toolkit
5
5
  Author: CHaMP Team
6
6
  License: Apache License
@@ -246,7 +246,6 @@ Dynamic: license-file
246
246
 
247
247
  # The CARM Tool
248
248
 
249
-
250
249
  Cache-Aware Roofline Model (CARM) toolkit - benchmark, visualize and profile Intel, AMD, ARM, RISC-V CPUs and NVIDIA, AMD GPUs.
251
250
 
252
251
  The package provides:
@@ -258,6 +257,10 @@ The package provides:
258
257
  > [!WARNING]
259
258
  > The development branch and associated python package are still experimental, and do not include all features yet. For a stable release, please refer to the main branch.
260
259
 
260
+ ## Documentation
261
+
262
+ You can find the full documentation at [https://champ-hub.github.io/carm-roofline](https://champ-hub.github.io/carm-roofline/).
263
+
261
264
  ## Installation
262
265
 
263
266
  Install from PyPI:
@@ -279,10 +282,17 @@ source carm-env/bin/activate
279
282
  pip install carm-roofline[all]
280
283
  ```
281
284
 
285
+ If cloning the repository directly (e.g. for development or access to example applications), use `--recurse-submodules` to also fetch the examples:
286
+
287
+ ```bash
288
+ git clone --recurse-submodules https://github.com/champ-hub/carm-roofline
289
+ ```
290
+
282
291
  ## Requirements
283
292
 
284
293
  - Python 3.9+
285
- - GCC (for compiling benchmark binaries)
294
+ - C compiler (e.g. gcc, clang)
295
+ - Optional: [PAPI](https://github.com/icl-utk-edu/papi) headers and binaries for profiling
286
296
 
287
297
  ## Command-Line Interface
288
298
 
@@ -296,7 +306,7 @@ Available subcommands:
296
306
 
297
307
  - `benchmark` - run benchmark suites
298
308
  - `gui` - launch the results dashboard
299
- - `profile` - coming soon
309
+ - `profile` - profile an application with PAPI or perf
300
310
 
301
311
  Show help:
302
312
 
@@ -304,6 +314,7 @@ Show help:
304
314
  carm --help
305
315
  carm benchmark --help
306
316
  carm gui --help
317
+ carm profile --help
307
318
  ```
308
319
 
309
320
  ## Basic Usage
@@ -1,7 +1,6 @@
1
1
 
2
2
  # The CARM Tool
3
3
 
4
-
5
4
  Cache-Aware Roofline Model (CARM) toolkit - benchmark, visualize and profile Intel, AMD, ARM, RISC-V CPUs and NVIDIA, AMD GPUs.
6
5
 
7
6
  The package provides:
@@ -13,6 +12,10 @@ The package provides:
13
12
  > [!WARNING]
14
13
  > The development branch and associated python package are still experimental, and do not include all features yet. For a stable release, please refer to the main branch.
15
14
 
15
+ ## Documentation
16
+
17
+ You can find the full documentation at [https://champ-hub.github.io/carm-roofline](https://champ-hub.github.io/carm-roofline/).
18
+
16
19
  ## Installation
17
20
 
18
21
  Install from PyPI:
@@ -34,10 +37,17 @@ source carm-env/bin/activate
34
37
  pip install carm-roofline[all]
35
38
  ```
36
39
 
40
+ If cloning the repository directly (e.g. for development or access to example applications), use `--recurse-submodules` to also fetch the examples:
41
+
42
+ ```bash
43
+ git clone --recurse-submodules https://github.com/champ-hub/carm-roofline
44
+ ```
45
+
37
46
  ## Requirements
38
47
 
39
48
  - Python 3.9+
40
- - GCC (for compiling benchmark binaries)
49
+ - C compiler (e.g. gcc, clang)
50
+ - Optional: [PAPI](https://github.com/icl-utk-edu/papi) headers and binaries for profiling
41
51
 
42
52
  ## Command-Line Interface
43
53
 
@@ -51,7 +61,7 @@ Available subcommands:
51
61
 
52
62
  - `benchmark` - run benchmark suites
53
63
  - `gui` - launch the results dashboard
54
- - `profile` - coming soon
64
+ - `profile` - profile an application with PAPI or perf
55
65
 
56
66
  Show help:
57
67
 
@@ -59,6 +69,7 @@ Show help:
59
69
  carm --help
60
70
  carm benchmark --help
61
71
  carm gui --help
72
+ carm profile --help
62
73
  ```
63
74
 
64
75
  ## Basic Usage
@@ -5,7 +5,13 @@ import contextvars
5
5
  from exec_interface import ExecutionInterface
6
6
 
7
7
  from .architecture import Architecture
8
- from .identity import MachineSignature, detect_machine_signature, generate_run_name, signature_from_architecture
8
+ from .identity import (
9
+ MachineSignature,
10
+ detect_machine_signature,
11
+ generate_run_name,
12
+ signature_from_architecture,
13
+ write_machine_json,
14
+ )
9
15
  from .memory import SimpleMemoryTopology
10
16
 
11
17
  # Context variable to hold the execution interface, for compatibility with simulation and native execution
@@ -31,4 +37,5 @@ __all__ = [
31
37
  "get_execution_interface",
32
38
  "set_execution_interface",
33
39
  "signature_from_architecture",
40
+ "write_machine_json",
34
41
  ]
@@ -11,6 +11,8 @@ machine yields the same name across runs.
11
11
  from __future__ import annotations
12
12
 
13
13
  import hashlib
14
+ import json
15
+ import os
14
16
  import platform
15
17
  import re
16
18
  from dataclasses import dataclass
@@ -22,6 +24,8 @@ from .memory import MemoryLevelInfo, MemoryTopology, MemoryTopologyLike
22
24
  if TYPE_CHECKING:
23
25
  from .architecture import Architecture
24
26
 
27
+ _BYTES_PER_GIB = 1024**3
28
+
25
29
 
26
30
  @dataclass(frozen=True)
27
31
  class CpuInfo:
@@ -55,17 +59,52 @@ class MachineSignature:
55
59
  vendor: str
56
60
  memory_levels: tuple[MemoryLevelSignature, ...]
57
61
 
62
+ def _canonical_hash_string(self) -> str:
63
+ """Canonical string fed to SHA-256 for the config hash.
64
+
65
+ Format: ``arch=<arch>;vendor=<vendor>;levels=<levels>`` where
66
+ ``<levels>`` is a ``|``-separated list of
67
+ ``<name>:<size_bytes>:<instances>:<num_sharing_threads>``.
68
+ """
69
+ levels_str = "|".join(
70
+ f"{lvl.name}:{lvl.size_bytes}:{lvl.instances}:{lvl.num_sharing_threads}" for lvl in self.memory_levels
71
+ )
72
+ return f"arch={self.arch};vendor={self.vendor};levels={levels_str}"
73
+
58
74
  @property
59
75
  def config_hash(self) -> str:
60
76
  """Deterministic 8-char hex SHA256 of non-model-name fields.
61
77
 
62
78
  Hash inputs: arch, vendor, memory_levels (name, size, instances, sharing).
79
+
80
+ .. note::
81
+ DRAM ``size_bytes`` comes from ``/proc/zoneinfo`` ``present`` pages
82
+ (physical capacity, stable across reboots). Falls back to a
83
+ rounded-``MemTotal`` value when zoneinfo is unavailable.
84
+ ``num_sharing_threads`` is set to 0 for DRAM (not a hardware
85
+ property; CPU online count may vary). Cache-level fields are
86
+ used verbatim.
63
87
  """
64
- levels_str = "|".join(
65
- f"{lvl.name}:{lvl.size_bytes}:{lvl.instances}:{lvl.num_sharing_threads}" for lvl in self.memory_levels
66
- )
67
- canonical = f"arch={self.arch};vendor={self.vendor};levels={levels_str}"
68
- return hashlib.sha256(canonical.encode("utf-8")).hexdigest()[:8]
88
+ return hashlib.sha256(self._canonical_hash_string().encode("utf-8")).hexdigest()[:8]
89
+
90
+ def to_dict(self) -> dict[str, object]:
91
+ """Serialize all fields for machine.json debugging output."""
92
+ return {
93
+ "model_name": self.model_name,
94
+ "arch": self.arch,
95
+ "vendor": self.vendor,
96
+ "config_hash": self.config_hash,
97
+ "hash_input": self._canonical_hash_string(),
98
+ "memory_levels": [
99
+ {
100
+ "name": lvl.name,
101
+ "size_bytes": lvl.size_bytes,
102
+ "instances": lvl.instances,
103
+ "num_sharing_threads": lvl.num_sharing_threads,
104
+ }
105
+ for lvl in self.memory_levels
106
+ ],
107
+ }
69
108
 
70
109
 
71
110
  def read_cpuinfo() -> CpuInfo:
@@ -102,21 +141,69 @@ def read_cpuinfo() -> CpuInfo:
102
141
  return CpuInfo(model_name=model_name, vendor=vendor)
103
142
 
104
143
 
144
+ def _get_physical_ram_bytes() -> int | None:
145
+ """Total physical RAM (bytes) from ``/proc/zoneinfo``, or ``None``.
146
+
147
+ Sums the ``present`` field across all zones — this is the number of
148
+ physical pages present, derived from the firmware's e820/device-tree
149
+ memory map at boot. It reflects hardware capacity and is stable across
150
+ reboots, unlike ``MemTotal`` which subtracts kernel runtime reservations.
151
+ """
152
+ try:
153
+ with open("/proc/zoneinfo") as f:
154
+ total_present = 0
155
+ for line in f:
156
+ parts = line.strip().split()
157
+ if len(parts) == 2 and parts[0] == "present":
158
+ total_present += int(parts[1])
159
+ return total_present * os.sysconf("SC_PAGE_SIZE")
160
+ except (OSError, ValueError):
161
+ return None
162
+
163
+
105
164
  def _levels_from_topology(topology: MemoryTopologyLike | None) -> tuple[MemoryLevelSignature, ...]:
106
- """Convert a memory topology to a tuple of deterministic level signatures."""
165
+ """Convert a memory topology to a tuple of deterministic level signatures.
166
+
167
+ DRAM levels use the ``present`` page count from ``/proc/zoneinfo`` (a
168
+ hardware-capacity value stable across reboots). Falls back to rounding
169
+ the topology's ``MemTotal``-based size to the nearest GiB when zoneinfo
170
+ is unavailable (containers, odd kernels).
171
+
172
+ ``num_sharing_threads`` is set to 0 for DRAM (not a hardware property;
173
+ ``shared_cpu_list`` varies with CPU online count).
174
+
175
+ Cache levels (L1/L2/L3) are passed through unchanged.
176
+ """
177
+ # Read physical RAM once — stable firmware value
178
+ physical_ram = _get_physical_ram_bytes()
179
+
107
180
  if topology is None:
108
181
  return ()
109
182
  levels: list[MemoryLevelSignature] = []
110
183
  for lvl in topology:
111
184
  if isinstance(lvl, MemoryLevelInfo):
112
- levels.append(
113
- MemoryLevelSignature(
114
- name=lvl.name,
115
- size_bytes=int(lvl.size),
116
- instances=lvl.instances,
117
- num_sharing_threads=lvl.num_sharing_threads,
185
+ if lvl.name == "DRAM":
186
+ if physical_ram is not None:
187
+ size_bytes = physical_ram
188
+ else:
189
+ size_bytes = round(int(lvl.size) / _BYTES_PER_GIB) * _BYTES_PER_GIB
190
+ levels.append(
191
+ MemoryLevelSignature(
192
+ name=lvl.name,
193
+ size_bytes=size_bytes,
194
+ instances=lvl.instances,
195
+ num_sharing_threads=0,
196
+ )
197
+ )
198
+ else:
199
+ levels.append(
200
+ MemoryLevelSignature(
201
+ name=lvl.name,
202
+ size_bytes=int(lvl.size),
203
+ instances=lvl.instances,
204
+ num_sharing_threads=lvl.num_sharing_threads,
205
+ )
118
206
  )
119
- )
120
207
  return tuple(levels)
121
208
 
122
209
 
@@ -206,3 +293,18 @@ def _short_model_name(name: str) -> str:
206
293
  def generate_run_name(signature: MachineSignature) -> str:
207
294
  """Generate ``"<short_model>_<config_hash>"`` (e.g. ``"Ryzen-7-7735HS_59486dd1"``)."""
208
295
  return f"{_short_model_name(signature.model_name)}_{signature.config_hash}"
296
+
297
+
298
+ def write_machine_json(signature: MachineSignature, directory: Path) -> None:
299
+ """Write machine.json to *directory* if it does not already exist.
300
+
301
+ The file contains the full MachineSignature serialized as JSON, including
302
+ all hash inputs and the computed hash, so future runs can debug why a
303
+ machine hash changed.
304
+ """
305
+ path = directory / "machine.json"
306
+ if path.exists():
307
+ return
308
+ directory.mkdir(parents=True, exist_ok=True)
309
+ with open(path, "x", encoding="utf-8") as f:
310
+ json.dump(signature.to_dict(), f, indent=2, sort_keys=True)
@@ -17,6 +17,7 @@ from architecture import (
17
17
  generate_run_name,
18
18
  set_execution_interface,
19
19
  signature_from_architecture,
20
+ write_machine_json,
20
21
  )
21
22
  from arguments import InsertsArguments, TopLevelHelpFormatter
22
23
  from benchmark.interface import run_full_benchmark
@@ -58,8 +59,9 @@ def _handle_benchmark(args: argparse.Namespace) -> int:
58
59
  # Make exec_iface globally available for architecture feature detection
59
60
  set_execution_interface(exec_iface)
60
61
  architecture = Architecture(args)
62
+ signature = signature_from_architecture(architecture)
61
63
  if args.name is None:
62
- args.name = generate_run_name(signature_from_architecture(architecture))
64
+ args.name = generate_run_name(signature)
63
65
  benchmarking = Benchmarking(args)
64
66
  run_config = RunConfig(args)
65
67
 
@@ -73,6 +75,7 @@ def _handle_benchmark(args: argparse.Namespace) -> int:
73
75
  detail("Dry run finished: benchmark code generated successfully.")
74
76
  return 0
75
77
 
78
+ write_machine_json(signature, run_config.output_dir / run_config.name)
76
79
  output_benchmark_results(context, benchmark_suites)
77
80
  return 0
78
81
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: carm-roofline
3
- Version: 1.0.2.dev1
3
+ Version: 1.0.2.dev2
4
4
  Summary: CARM: Cache-Aware Roofline Model benchmarking and visualization toolkit
5
5
  Author: CHaMP Team
6
6
  License: Apache License
@@ -246,7 +246,6 @@ Dynamic: license-file
246
246
 
247
247
  # The CARM Tool
248
248
 
249
-
250
249
  Cache-Aware Roofline Model (CARM) toolkit - benchmark, visualize and profile Intel, AMD, ARM, RISC-V CPUs and NVIDIA, AMD GPUs.
251
250
 
252
251
  The package provides:
@@ -258,6 +257,10 @@ The package provides:
258
257
  > [!WARNING]
259
258
  > The development branch and associated python package are still experimental, and do not include all features yet. For a stable release, please refer to the main branch.
260
259
 
260
+ ## Documentation
261
+
262
+ You can find the full documentation at [https://champ-hub.github.io/carm-roofline](https://champ-hub.github.io/carm-roofline/).
263
+
261
264
  ## Installation
262
265
 
263
266
  Install from PyPI:
@@ -279,10 +282,17 @@ source carm-env/bin/activate
279
282
  pip install carm-roofline[all]
280
283
  ```
281
284
 
285
+ If cloning the repository directly (e.g. for development or access to example applications), use `--recurse-submodules` to also fetch the examples:
286
+
287
+ ```bash
288
+ git clone --recurse-submodules https://github.com/champ-hub/carm-roofline
289
+ ```
290
+
282
291
  ## Requirements
283
292
 
284
293
  - Python 3.9+
285
- - GCC (for compiling benchmark binaries)
294
+ - C compiler (e.g. gcc, clang)
295
+ - Optional: [PAPI](https://github.com/icl-utk-edu/papi) headers and binaries for profiling
286
296
 
287
297
  ## Command-Line Interface
288
298
 
@@ -296,7 +306,7 @@ Available subcommands:
296
306
 
297
307
  - `benchmark` - run benchmark suites
298
308
  - `gui` - launch the results dashboard
299
- - `profile` - coming soon
309
+ - `profile` - profile an application with PAPI or perf
300
310
 
301
311
  Show help:
302
312
 
@@ -304,6 +314,7 @@ Show help:
304
314
  carm --help
305
315
  carm benchmark --help
306
316
  carm gui --help
317
+ carm profile --help
307
318
  ```
308
319
 
309
320
  ## Basic Usage
@@ -28,6 +28,7 @@ from .shared import (
28
28
  MetricDefinition,
29
29
  MetricResolutionConfig,
30
30
  MetricType,
31
+ RooflinePoint,
31
32
  compute_region_point,
32
33
  sum_roofline_points,
33
34
  )
@@ -43,6 +44,7 @@ __all__ = [
43
44
  "ProfilerBackend",
44
45
  "RankMetrics",
45
46
  "RegionMetrics",
47
+ "RooflinePoint",
46
48
  "RunMetadata",
47
49
  "RunResults",
48
50
  "ThreadMetrics",
@@ -25,6 +25,7 @@ from .shared import (
25
25
  MetricContext,
26
26
  MetricDefinition,
27
27
  MetricType,
28
+ RooflinePoint,
28
29
  compute_region_point,
29
30
  sum_roofline_points,
30
31
  )
@@ -59,7 +60,7 @@ def _region_points(
59
60
  regions: list[RegionMetrics],
60
61
  resolved: dict[MetricType, MetricDefinition],
61
62
  metric_ctx: MetricContext,
62
- ) -> list[dict[str, float]]:
63
+ ) -> list[RooflinePoint]:
63
64
  """Compute (flops, bytes, time_s) for each region in a list."""
64
65
  return [compute_region_point(r.counters, r.time_nsec, resolved, metric_ctx) for r in regions]
65
66
 
@@ -75,17 +76,16 @@ def aggregate_global(
75
76
  total time (regions within a thread execute sequentially, threads within
76
77
  a rank run in parallel).
77
78
  """
78
- thread_totals: list[dict[str, float]] = []
79
+ thread_totals: list[RooflinePoint] = []
79
80
  num_regions = 0
80
81
  for rank in run.ranks:
81
82
  for thread in rank.threads:
82
83
  thread_points = _region_points(thread.regions, resolved, metric_ctx)
83
84
  thread_totals.append(sum_roofline_points(thread_points))
84
85
  num_regions += len(thread_points)
85
-
86
- total_flops = sum(p["flops"] for p in thread_totals)
87
- total_bytes = sum(p["bytes"] for p in thread_totals)
88
- total_time = max((p["time_s"] for p in thread_totals), default=0.0)
86
+ total_flops = sum(p.flops for p in thread_totals)
87
+ total_bytes = sum(p.bytes for p in thread_totals)
88
+ total_time = max((p.time_s for p in thread_totals), default=0.0)
89
89
  num_threads = sum(len(r.threads) for r in run.ranks)
90
90
  return AggregatedPoint(
91
91
  label=run.metadata.name or "global",
@@ -110,16 +110,16 @@ def aggregate_per_rank(
110
110
  """
111
111
  points: list[AggregatedPoint] = []
112
112
  for rank in run.ranks:
113
- thread_totals: list[dict[str, float]] = []
113
+ thread_totals: list[RooflinePoint] = []
114
114
  num_regions = 0
115
115
  for thread in rank.threads:
116
116
  thread_points = _region_points(thread.regions, resolved, metric_ctx)
117
117
  thread_totals.append(sum_roofline_points(thread_points))
118
118
  num_regions += len(thread_points)
119
119
 
120
- total_flops = sum(p["flops"] for p in thread_totals)
121
- total_bytes = sum(p["bytes"] for p in thread_totals)
122
- total_time = max((p["time_s"] for p in thread_totals), default=0.0)
120
+ total_flops = sum(p.flops for p in thread_totals)
121
+ total_bytes = sum(p.bytes for p in thread_totals)
122
+ total_time = max((p.time_s for p in thread_totals), default=0.0)
123
123
  label = f"{run.metadata.name}_rank{rank.rank_id}" if run.metadata.name else f"rank{rank.rank_id}"
124
124
  points.append(
125
125
  AggregatedPoint(
@@ -154,9 +154,9 @@ def aggregate_per_thread(
154
154
  points.append(
155
155
  AggregatedPoint(
156
156
  label=label,
157
- total_flops=total["flops"],
158
- total_bytes=total["bytes"],
159
- runtime_s=total["time_s"],
157
+ total_flops=total.flops,
158
+ total_bytes=total.bytes,
159
+ runtime_s=total.time_s,
160
160
  num_ranks=1,
161
161
  num_threads=1,
162
162
  num_regions=len(thread_points),
@@ -177,7 +177,7 @@ def aggregate_per_region_merged(
177
177
  (parallel execution).
178
178
  """
179
179
  # by_region[region_name][(rank_id, thread_id)] = [point, point, ...]
180
- by_region: dict[str, dict[tuple[int, int], list[dict[str, float]]]] = {}
180
+ by_region: dict[str, dict[tuple[int, int], list[RooflinePoint]]] = {}
181
181
 
182
182
  for rank in run.ranks:
183
183
  for thread in rank.threads:
@@ -192,9 +192,9 @@ def aggregate_per_region_merged(
192
192
  # Sum within each thread (sequential calls)
193
193
  per_thread_sums = {key: sum_roofline_points(pt_list) for key, pt_list in thread_groups.items()}
194
194
 
195
- total_flops = sum(p["flops"] for p in per_thread_sums.values())
196
- total_bytes = sum(p["bytes"] for p in per_thread_sums.values())
197
- total_time = max((p["time_s"] for p in per_thread_sums.values()), default=0.0)
195
+ total_flops = sum(p.flops for p in per_thread_sums.values())
196
+ total_bytes = sum(p.bytes for p in per_thread_sums.values())
197
+ total_time = max((p.time_s for p in per_thread_sums.values()), default=0.0)
198
198
 
199
199
  rank_ids: set[int] = {rk for (rk, _) in thread_groups}
200
200
  label = f"{run.metadata.name}_{name}" if run.metadata.name else name
@@ -235,9 +235,9 @@ def aggregate_per_region_per_thread(
235
235
  points.append(
236
236
  AggregatedPoint(
237
237
  label=label,
238
- total_flops=region_point["flops"],
239
- total_bytes=region_point["bytes"],
240
- runtime_s=region_point["time_s"],
238
+ total_flops=region_point.flops,
239
+ total_bytes=region_point.bytes,
240
+ runtime_s=region_point.time_s,
241
241
  num_ranks=1,
242
242
  num_threads=1,
243
243
  num_regions=1,
@@ -6,7 +6,7 @@ import argparse
6
6
  from enum import Enum
7
7
  from pathlib import Path
8
8
 
9
- from architecture import detect_machine_signature, generate_run_name
9
+ from architecture import MachineSignature, detect_machine_signature, generate_run_name
10
10
  from arguments import InsertsArguments, add_verbose_argument, enum_action
11
11
  from benchmark.generation import ISA_NAME_TO_CLASS, DataType
12
12
  from results_paths import default_results_root
@@ -55,7 +55,6 @@ class ProfileConfig(InsertsArguments):
55
55
  verbose: Verbosity level (0-4).
56
56
  machine_name: Name for the results directory (each machine gets its own subdirectory).
57
57
  app_name: Application name recorded in the output metadata.
58
- results_dir: Directory to scan for existing profiling result files.
59
58
  keep_artifacts: Whether to keep raw profiling output files.
60
59
  papi_events: Optional comma-separated PAPI event override.
61
60
  perf_events: Optional comma-separated perf event override.
@@ -71,11 +70,11 @@ class ProfileConfig(InsertsArguments):
71
70
  self.aggregation: AggregationMode = args.aggregation
72
71
  self.verbose: int = args.verbose
73
72
  self.output_dir: Path = args.output_dir
73
+ self.machine_signature: MachineSignature = detect_machine_signature()
74
74
  self.machine_name: str = (
75
- args.machine_name if args.machine_name is not None else generate_run_name(detect_machine_signature())
75
+ args.machine_name if args.machine_name is not None else generate_run_name(self.machine_signature)
76
76
  )
77
77
  self.app_name: str = args.app_name if args.app_name is not None else _default_app_name(args.command)
78
- self.results_dir: Path = args.results_dir
79
78
  self.keep_artifacts: bool = args.keep_artifacts
80
79
  self.papi_events: str | None = args.papi_events
81
80
  self.perf_events: str | None = args.perf_events
@@ -121,12 +120,6 @@ class ProfileConfig(InsertsArguments):
121
120
  type=Path,
122
121
  help="Directory to write result files (default: platform user data dir for app 'carm')",
123
122
  )
124
- parser.add_argument(
125
- "--results-dir",
126
- default=default_results_root() / "profile",
127
- type=Path,
128
- help="Directory to scan for existing profiling result files (default: <output-dir>/profile)",
129
- )
130
123
  parser.add_argument(
131
124
  "--keep-artifacts",
132
125
  action="store_true",
@@ -10,6 +10,7 @@ import csv
10
10
  import json
11
11
  from datetime import datetime
12
12
 
13
+ from architecture import write_machine_json
13
14
  from output_utils import detail, info
14
15
 
15
16
  from .aggregation import AggregatedPoint
@@ -28,7 +29,12 @@ def write_profile_results(
28
29
  (GUI-compatible legacy format). JSONL is written to
29
30
  ``<output_dir>/<machine_name>/applications.jsonl`` (one appended line per run,
30
31
  embedding run metadata and the aggregated points).
32
+
33
+ A ``machine.json`` debugging file is also written to
34
+ ``<output_dir>/<machine_name>/machine.json`` on the first run.
31
35
  """
36
+ machine_dir = config.output_dir / config.machine_name
37
+ write_machine_json(config.machine_signature, machine_dir)
32
38
  write_applications_csv(points, config, run)
33
39
  write_profile_jsonl(run, config, points)
34
40
 
@@ -108,6 +108,22 @@ class MetricDefinition:
108
108
  warning: str | None = None
109
109
 
110
110
 
111
+ @dataclass(frozen=True)
112
+ class RooflinePoint:
113
+ """Computed roofline metrics for a single region or aggregated set."""
114
+
115
+ flops: float = 0.0
116
+ bytes: float = 0.0
117
+ time_s: float = 0.0
118
+
119
+ def __add__(self, other: RooflinePoint) -> RooflinePoint:
120
+ return RooflinePoint(
121
+ flops=self.flops + other.flops,
122
+ bytes=self.bytes + other.bytes,
123
+ time_s=self.time_s + other.time_s,
124
+ )
125
+
126
+
111
127
  # ---------------------------------------------------------------------------
112
128
  # Computing roofline point data from raw counters
113
129
  # ---------------------------------------------------------------------------
@@ -118,7 +134,7 @@ def compute_region_point(
118
134
  time_nsec: int,
119
135
  resolved: dict[MetricType, MetricDefinition],
120
136
  metric_ctx: MetricContext,
121
- ) -> dict[str, float]:
137
+ ) -> RooflinePoint:
122
138
  """Compute (flops, bytes, time_s) for a single region from raw counters.
123
139
 
124
140
  Only metrics whose required events are all present in *counters* are
@@ -129,8 +145,7 @@ def compute_region_point(
129
145
  time_nsec: Wall-clock time in nanoseconds for the region.
130
146
  resolved: Resolved metric implementations from ``resolve_metrics()``.
131
147
 
132
- Returns:
133
- A dict with keys ``flops``, ``bytes``, and ``time_s``.
148
+ A `RooflinePoint` with flops, bytes, and time_s.
134
149
  """
135
150
  float_counters = {k: float(v) for k, v in counters.items()}
136
151
  available_set = frozenset(counters)
@@ -147,23 +162,16 @@ def compute_region_point(
147
162
  if impl.required_events <= available_set:
148
163
  bytes_val = impl.compute(float_counters, metric_ctx)
149
164
 
150
- return {
151
- "flops": flops,
152
- "bytes": bytes_val,
153
- "time_s": time_nsec / 1e9,
154
- }
165
+ return RooflinePoint(flops=flops, bytes=bytes_val, time_s=time_nsec / 1e9)
155
166
 
156
167
 
157
- def sum_roofline_points(points: list[dict[str, float]]) -> dict[str, float]:
158
- """Sum a list of (flops, bytes, time_s) dicts.
168
+ def sum_roofline_points(points: list[RooflinePoint]) -> RooflinePoint:
169
+ """Sum a list of (flops, bytes, time_s) RooflinePoints.
159
170
 
160
171
  Flops, bytes, and time are all summed, all points in the list represent
161
172
  sequential execution within a single thread.
162
173
  """
163
- flops = sum(p["flops"] for p in points)
164
- bytes_val = sum(p["bytes"] for p in points)
165
- time_s = sum(p["time_s"] for p in points)
166
- return {"flops": flops, "bytes": bytes_val, "time_s": time_s}
174
+ return sum(points, start=RooflinePoint())
167
175
 
168
176
 
169
177
  def resolve_metrics(
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "carm-roofline"
3
- version = "1.0.2.dev1"
3
+ version = "1.0.2.dev2"
4
4
  description = "CARM: Cache-Aware Roofline Model benchmarking and visualization toolkit"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -170,6 +170,8 @@ measure_benchmark(const benchmark_metadata_t *metadata, wrapper_fn_t wrapper_fn,
170
170
  tdata[i].read_ptr = (void *) ((char *) combined_data + thread_offset);
171
171
  tdata[i].write_ptr = (void *) ((char *) combined_data + thread_offset + aligned_read);
172
172
  tdata[i].freq = freq;
173
+ tdata[i].read_size = aligned_read;
174
+ tdata[i].write_size = aligned_write;
173
175
 
174
176
  #if VERBOSITY >= 4
175
177
  if (tdata[i].read_ptr != NULL) {
@@ -47,6 +47,8 @@ typedef struct {
47
47
  void *read_ptr; /* Per-thread slice of the read (load) buffer */
48
48
  void *write_ptr; /* Per-thread slice of the write (store) buffer */
49
49
  float freq;
50
+ size_t read_size; /* Per-thread read buffer size (aligned, bytes) */
51
+ size_t write_size; /* Per-thread write buffer size (aligned, bytes) */
50
52
  /* Timing results per thread (nanoseconds) */
51
53
  uint64_t *elapsed_ns; /* Elapsed time in nanoseconds */
52
54
  } thread_wrapper_data_t;
@@ -19,6 +19,13 @@ static void *_WRAPPER_EXPAND(UBENCH_NAME)(void *arg)
19
19
  void *read_ptr = tdata->read_ptr;
20
20
  void *write_ptr = tdata->write_ptr;
21
21
 
22
+ /* Force COW: write one byte per page so the kernel allocates real physical pages (not the shared zero page). This
23
+ * ensures NUMA-correct first-touch placement and prevents load-only benchmarks from measuring L1 bandwidth
24
+ * regardless of working set size. */
25
+ for (size_t off = 0; off < tdata->read_size; off += 4096)
26
+ ((volatile char *) read_ptr)[off] = 0;
27
+ for (size_t off = 0; off < tdata->write_size; off += 4096)
28
+ ((volatile char *) write_ptr)[off] = 0;
22
29
  barrier_sync();
23
30
 
24
31
  uint64_t reps = CARM_BENCH_START_REPS;