carm-roofline 1.0.2.dev0__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.dev0 → carm_roofline-1.0.2.dev2}/PKG-INFO +15 -4
  2. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/README.md +14 -3
  3. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/__init__.py +8 -1
  4. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/identity.py +115 -13
  5. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/output/arithmetic.py +6 -6
  6. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/output/jsonl.py +12 -12
  7. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/output/memory.py +2 -2
  8. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/output/memory_sweep.py +5 -5
  9. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/output/roofline.py +6 -8
  10. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/suites/memory_sweep.py +3 -3
  11. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/carm.py +5 -2
  12. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/carm_roofline.egg-info/PKG-INFO +15 -4
  13. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/gui/assets/style.css +16 -42
  14. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/gui/components.py +7 -39
  15. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/gui/data.py +33 -21
  16. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/gui/factory.py +13 -56
  17. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/gui/ids.py +2 -10
  18. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/output_utils.py +13 -20
  19. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/profiling/__init__.py +2 -0
  20. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/profiling/aggregation.py +98 -43
  21. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/profiling/config.py +5 -11
  22. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/profiling/output.py +6 -0
  23. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/profiling/shared.py +24 -16
  24. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/pyproject.toml +1 -1
  25. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/test/test_gui_data.py +69 -17
  26. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/test_bench/test_bench.c +2 -0
  27. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/test_bench/test_bench.h +2 -0
  28. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/test_bench/wrapper.inl +7 -0
  29. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/units.py +8 -0
  30. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/LICENSE +0 -0
  31. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/architecture.py +0 -0
  32. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/arm.py +0 -0
  33. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/config.py +0 -0
  34. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/detect.py +0 -0
  35. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/frequency.py +0 -0
  36. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/memory.py +0 -0
  37. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/riscv.py +0 -0
  38. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/tests/arm/features.c +0 -0
  39. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/tests/arm/frequency.h +0 -0
  40. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/tests/arm/vlen.c +0 -0
  41. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/tests/frequency.c +0 -0
  42. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/tests/riscv/frequency.h +0 -0
  43. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/tests/riscv/rvv_version.c +0 -0
  44. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/tests/riscv/vlen.c +0 -0
  45. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/tests/x86/cache.c +0 -0
  46. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/tests/x86/features.c +0 -0
  47. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/tests/x86/frequency.h +0 -0
  48. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/tests/x86/x86_avx512/frequency.h +0 -0
  49. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/architecture/x86.py +0 -0
  50. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/arguments.py +0 -0
  51. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/__init__.py +0 -0
  52. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/benchmark.py +0 -0
  53. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/benchmarking.py +0 -0
  54. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/generation/__init__.py +0 -0
  55. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/generation/arm.py +0 -0
  56. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/generation/code_gen/__init__.py +0 -0
  57. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/generation/code_gen/data_type.py +0 -0
  58. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/generation/code_gen/instruction.py +0 -0
  59. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/generation/code_gen/operation.py +0 -0
  60. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/generation/code_gen/register.py +0 -0
  61. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/generation/isa.py +0 -0
  62. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/generation/parameters.py +0 -0
  63. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/generation/riscv.py +0 -0
  64. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/generation/x86.py +0 -0
  65. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/interface.py +0 -0
  66. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/output/__init__.py +0 -0
  67. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/output/base.py +0 -0
  68. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/output/common.py +0 -0
  69. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/output/mixed.py +0 -0
  70. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/result.py +0 -0
  71. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/suites/__init__.py +0 -0
  72. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/suites/arithmetic.py +0 -0
  73. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/suites/base.py +0 -0
  74. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/suites/memory.py +0 -0
  75. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/suites/mixed.py +0 -0
  76. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/benchmark/suites/roofline.py +0 -0
  77. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/carm_roofline.egg-info/SOURCES.txt +0 -0
  78. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/carm_roofline.egg-info/dependency_links.txt +0 -0
  79. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/carm_roofline.egg-info/entry_points.txt +0 -0
  80. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/carm_roofline.egg-info/requires.txt +0 -0
  81. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/carm_roofline.egg-info/top_level.txt +0 -0
  82. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/context.py +0 -0
  83. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/error.py +0 -0
  84. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/exec_interface.py +0 -0
  85. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/gui/__init__.py +0 -0
  86. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/gui/assets/CARM_icon.svg +0 -0
  87. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/gui/assets/CHAMP_logo.svg +0 -0
  88. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/gui/assets/menu_icon.png +0 -0
  89. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/gui/config.py +0 -0
  90. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/profiling/backends.py +0 -0
  91. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/profiling/model.py +0 -0
  92. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/profiling/papi_backend.py +0 -0
  93. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/profiling/papi_loader.py +0 -0
  94. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/profiling/papi_metrics.py +0 -0
  95. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/profiling/perf_backend.py +0 -0
  96. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/profiling/perf_loader.py +0 -0
  97. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/profiling/perf_metrics.py +0 -0
  98. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/results_paths.py +0 -0
  99. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/roofline_assembly.py +0 -0
  100. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/run_config.py +0 -0
  101. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/setup.cfg +0 -0
  102. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/test/test_isa_codegen_integration.py +0 -0
  103. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/test/test_memory_suite_generation.py +0 -0
  104. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/test/test_output_module.py +0 -0
  105. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/test/test_roofline_assembly.py +0 -0
  106. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/test/test_typed_benchmarks.py +0 -0
  107. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/test_bench/__init__.py +0 -0
  108. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/test_bench/builder.py +0 -0
  109. {carm_roofline-1.0.2.dev0 → carm_roofline-1.0.2.dev2}/utils.py +0 -0
  110. {carm_roofline-1.0.2.dev0 → 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.dev0
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.value),
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)
@@ -33,7 +33,7 @@ def _collect_gops_by_isa(isa_suites: dict[str, ISABenchmarkSuite]) -> dict[str,
33
33
  if not isinstance(bench.results, ArithmeticBenchmarkResult):
34
34
  continue
35
35
 
36
- gops_value = float(bench.results.performance.value) / 1e9
36
+ gops_value = float(bench.results.performance) / 1e9
37
37
  if not math.isfinite(gops_value):
38
38
  warn(f"Skipping invalid GOPS value for ISA {isa}: {gops_value}")
39
39
  continue
@@ -73,7 +73,7 @@ def _print_table(context: CARMContext, isa_suites: dict[str, ISABenchmarkSuite])
73
73
  )
74
74
 
75
75
  ops_per_inst = isa_instance.ops_per_inst(benchmark.params.data_type, benchmark.params.operation)
76
- total_insts = (benchmark.params.num_ops.value // ops_per_inst) * res.num_repetitions if ops_per_inst else 0
76
+ total_insts = (int(benchmark.params.num_ops) // ops_per_inst) * res.num_repetitions if ops_per_inst else 0
77
77
  cycles = Cycles.from_time_and_frequency(res.time_taken, frequency)
78
78
  ipc = 0.0 if cycles.value == 0 else total_insts / cycles.value
79
79
  ops_per_cycle = ops_per_inst * ipc
@@ -84,17 +84,17 @@ def _print_table(context: CARMContext, isa_suites: dict[str, ISABenchmarkSuite])
84
84
  "operation": benchmark.params.operation.name,
85
85
  "benchmark": benchmark_name,
86
86
  "threads": benchmark.params.num_threads,
87
- "gops": float(res.performance.value) / 1e9,
87
+ "gops": float(res.performance) / 1e9,
88
88
  "gops_display": str(res.performance),
89
89
  "ipc": ipc,
90
- "frequency_hz": float(frequency.value),
90
+ "frequency_hz": float(frequency),
91
91
  "frequency_display": str(frequency),
92
92
  "ops_per_instruction": ops_per_inst,
93
93
  "ops_per_cycle": ops_per_cycle,
94
- "time_seconds": float(res.time_taken.value),
94
+ "time_seconds": float(res.time_taken),
95
95
  "time_display": str(res.time_taken),
96
96
  "repetitions": int(res.num_repetitions),
97
- "cycles": float(cycles.value),
97
+ "cycles": float(cycles),
98
98
  "cycles_display": str(cycles),
99
99
  }
100
100
  )
@@ -40,7 +40,7 @@ def _serialize_arithmetic(
40
40
  assert isinstance(res, ArithmeticBenchmarkResult)
41
41
 
42
42
  ops_per_inst = isa_instance.ops_per_inst(bench.params.data_type, bench.params.operation)
43
- total_insts = (bench.params.num_ops.value // ops_per_inst) * res.num_repetitions if ops_per_inst else 0
43
+ total_insts = (int(bench.params.num_ops) // ops_per_inst) * res.num_repetitions if ops_per_inst else 0
44
44
  cycles = Cycles.from_time_and_frequency(res.time_taken, frequency)
45
45
  ipc = 0.0 if cycles.value == 0 else total_insts / cycles.value
46
46
 
@@ -54,15 +54,15 @@ def _serialize_arithmetic(
54
54
  "timestamp": timestamp,
55
55
  "machine": machine,
56
56
  "operation": bench.params.operation.name,
57
- "num_ops": int(bench.params.num_ops.value),
58
- "performance_gops": res.performance.value / 1e9,
57
+ "num_ops": int(bench.params.num_ops),
58
+ "performance_gops": float(res.performance) / 1e9,
59
59
  "ipc": ipc,
60
- "frequency_hz": frequency.value,
60
+ "frequency_hz": float(frequency),
61
61
  "ops_per_instruction": ops_per_inst,
62
62
  "ops_per_cycle": ops_per_inst * ipc,
63
- "time_seconds": res.time_taken.value,
63
+ "time_seconds": float(res.time_taken),
64
64
  "repetitions": res.num_repetitions,
65
- "cycles": cycles.value,
65
+ "cycles": int(cycles),
66
66
  }
67
67
 
68
68
 
@@ -81,7 +81,7 @@ def _serialize_memory(
81
81
  assert isinstance(res, MemoryBenchmarkResult)
82
82
 
83
83
  bpi = isa_instance.bytes_per_inst(bench.params.data_type)
84
- total_insts = (bench.working_set_bytes.value // bpi) * res.num_repetitions
84
+ total_insts = (int(bench.working_set_bytes) // bpi) * res.num_repetitions
85
85
  cycles = Cycles.from_time_and_frequency(res.time_taken, frequency)
86
86
  ipc = 0.0 if cycles.value == 0 else total_insts / cycles.value
87
87
 
@@ -99,14 +99,14 @@ def _serialize_memory(
99
99
  "num_stores": bench.params.num_st,
100
100
  "cache_level": bench.cache_level,
101
101
  "memory_level_name": bench.params.memory_level_name,
102
- "size_per_thread_bytes": bench.params.size_per_thread.value,
103
- "working_set_bytes": bench.working_set_bytes.value,
102
+ "size_per_thread_bytes": int(bench.params.size_per_thread),
103
+ "working_set_bytes": int(bench.working_set_bytes),
104
104
  "layout_mode": bench.params.layout_mode.value,
105
- "bandwidth_gbps": res.bandwidth.value / 1e9,
105
+ "bandwidth_gbps": float(res.bandwidth) / 1e9,
106
106
  "ipc": ipc,
107
- "time_seconds": res.time_taken.value,
107
+ "time_seconds": float(res.time_taken),
108
108
  "repetitions": res.num_repetitions,
109
- "cycles": cycles.value,
109
+ "cycles": int(cycles),
110
110
  }
111
111
 
112
112
 
@@ -40,7 +40,7 @@ def _collect_bandwidth_by_label(isa_suites: dict[str, ISABenchmarkSuite]) -> dic
40
40
  continue
41
41
 
42
42
  level = res.cache_level or "unknown"
43
- bandwidth_value = float(res.bandwidth.value) / 1e9
43
+ bandwidth_value = float(res.bandwidth) / 1e9
44
44
  if not math.isfinite(bandwidth_value):
45
45
  warn(f"Skipping invalid bandwidth value for {isa}/{name}: {bandwidth_value}")
46
46
  continue
@@ -84,7 +84,7 @@ def _print_table(context: CARMContext, isa_suites: dict[str, ISABenchmarkSuite])
84
84
  assert bench.results is not None # for type checker
85
85
  level = bench.results.cache_level or "unknown"
86
86
  bytes_per_inst = isa_instance.bytes_per_inst(bench.params.data_type)
87
- total_insts = (bench.working_set_bytes.value // bytes_per_inst) * bench.results.num_repetitions
87
+ total_insts = (int(bench.working_set_bytes) // bytes_per_inst) * bench.results.num_repetitions
88
88
  cycles = Cycles.from_time_and_frequency(bench.results.time_taken, frequency)
89
89
 
90
90
  ipc = total_insts / cycles.value
@@ -79,7 +79,7 @@ def _print_table(context: CARMContext, isa_suites: dict[str, ISABenchmarkSuite])
79
79
  for _name, bench in sorted(mem_benchmarks.items(), key=lambda kv: kv[0]):
80
80
  assert bench.results is not None # for type checker
81
81
  bytes_per_inst = isa_instance.bytes_per_inst(bench.params.data_type)
82
- total_insts = (bench.working_set_bytes.value // bytes_per_inst) * bench.results.num_repetitions
82
+ total_insts = (int(bench.working_set_bytes) // bytes_per_inst) * bench.results.num_repetitions
83
83
  cycles = Cycles.from_time_and_frequency(bench.results.time_taken, frequency)
84
84
  cache_level = bench.results.cache_level or "unknown"
85
85
  table.add_row(
@@ -114,22 +114,22 @@ def _collect_sweep_series(
114
114
  for ws_bytes, cache_level, bandwidth in suite.get_sweep_data():
115
115
  if bandwidth is None:
116
116
  continue
117
- bw_val = float(bandwidth.value) / 1e9
117
+ bw_val = float(bandwidth) / 1e9
118
118
  if not math.isfinite(bw_val):
119
119
  continue
120
- points.append((ws_bytes.value, cache_level, bw_val))
120
+ points.append((int(ws_bytes), cache_level, bw_val))
121
121
  else:
122
122
  mem_benchmarks = suite.get_memory_benchmarks()
123
123
  for _name, bench in sorted(mem_benchmarks.items(), key=lambda kv: kv[0]):
124
124
  res = bench.results
125
125
  if not isinstance(res, MemoryBenchmarkResult):
126
126
  continue
127
- bw_val = float(res.bandwidth.value) / 1e9
127
+ bw_val = float(res.bandwidth) / 1e9
128
128
  if not math.isfinite(bw_val):
129
129
  continue
130
130
  ws = bench.working_set_bytes
131
131
  cache_level = res.cache_level or "unknown"
132
- points.append((ws.value, cache_level, bw_val))
132
+ points.append((int(ws), cache_level, bw_val))
133
133
 
134
134
  if points:
135
135
  points.sort(key=lambda t: t[0])
@@ -114,7 +114,7 @@ def _cache_sizes_kib(context: CARMContext) -> tuple[int, int, int]:
114
114
  size = getattr(level, "size", None)
115
115
  if size is None:
116
116
  continue
117
- kib = int(size.value // 1024)
117
+ kib = int(size) // 1024
118
118
  if name == "L1":
119
119
  l1_kib = kib
120
120
  elif name == "L2":
@@ -131,7 +131,7 @@ def _cache_sizes_kib(context: CARMContext) -> tuple[int, int, int]:
131
131
  def _find_dram_working_set_bytes(suite: ISABenchmarkSuite) -> int:
132
132
  for bench in suite.get_memory_benchmarks().values():
133
133
  if bench.cache_level == "DRAM" and getattr(bench, "working_set_bytes", None) is not None:
134
- return int(bench.working_set_bytes.value)
134
+ return int(bench.working_set_bytes)
135
135
  return 0
136
136
 
137
137
 
@@ -175,8 +175,8 @@ def _aggregate_level_metrics(
175
175
  level = bench.cache_level
176
176
  assert level is not None
177
177
  bytes_per_inst = isa_instance.bytes_per_inst(bench.params.data_type)
178
- bandwidth_by_level[level] = float(bench.results.bandwidth.value) / 1e9
179
- total_insts = (bench.working_set_bytes.value // bytes_per_inst) * bench.results.num_repetitions
178
+ bandwidth_by_level[level] = float(bench.results.bandwidth) / 1e9
179
+ total_insts = (int(bench.working_set_bytes) // bytes_per_inst) * bench.results.num_repetitions
180
180
  cycles = Cycles.from_time_and_frequency(bench.results.time_taken, frequency)
181
181
  ipc_by_level[level] = 0.0 if cycles.value == 0 else total_insts / cycles.value
182
182
 
@@ -208,12 +208,10 @@ def _pick_fp_for_combo(
208
208
  if bench.params.data_type != dt or bench.params.num_threads != nt:
209
209
  continue
210
210
  ops_per_inst = isa_instance.ops_per_inst(bench.params.data_type, bench.params.operation)
211
- total_insts = (
212
- (bench.params.num_ops.value // ops_per_inst) * bench.results.num_repetitions if ops_per_inst else 0
213
- )
211
+ total_insts = (int(bench.params.num_ops) // ops_per_inst) * bench.results.num_repetitions if ops_per_inst else 0
214
212
  cycles = Cycles.from_time_and_frequency(bench.results.time_taken, frequency)
215
213
  ipc = 0.0 if cycles.value == 0 else total_insts / cycles.value
216
- gflops = float(bench.results.performance.value) / 1e9
214
+ gflops = float(bench.results.performance) / 1e9
217
215
 
218
216
  if bench.params.operation == ArithmeticOperation.fma:
219
217
  fma_gflops = gflops
@@ -94,14 +94,14 @@ class MemorySweepBenchmarkSuite(ISABenchmarkSuite):
94
94
  # Step 2 - Determine sweep range.
95
95
  # ------------------------------------------------------------------
96
96
  _, _, l1_avail, _ = level_table[0]
97
- min_size_bytes: float = 0.1 * float(l1_avail.value)
97
+ min_size_bytes: float = 0.1 * float(l1_avail)
98
98
 
99
99
  if len(level_table) >= 2:
100
100
  _, _, last_cache_avail, _ = level_table[-2]
101
101
  else:
102
102
  _, _, last_cache_avail, _ = level_table[-1]
103
103
 
104
- max_size_bytes: float = 32.0 * float(last_cache_avail.value)
104
+ max_size_bytes: float = 32.0 * float(last_cache_avail)
105
105
 
106
106
  if max_size_bytes <= min_size_bytes:
107
107
  raise ValueError(
@@ -200,5 +200,5 @@ class MemorySweepBenchmarkSuite(ISABenchmarkSuite):
200
200
  level = bench.cache_level if bench.cache_level is not None else "UNKNOWN"
201
201
  rows.append((bench.working_set_bytes, level, bw))
202
202
 
203
- rows.sort(key=lambda t: t[0].value)
203
+ rows.sort(key=lambda t: t[0])
204
204
  return rows
@@ -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
 
@@ -126,7 +129,7 @@ SUBPARSERS = (
126
129
  ),
127
130
  SubParserInfo(
128
131
  name="profile",
129
- help_short="Profile applications with PAPI",
132
+ help_short="Profile applications for CARM analysis",
130
133
  help_long="Profile instrumented applications to compute roofline metrics (AI, GFLOP/s, bandwidth). "
131
134
  "Supports MPI, threaded, and hybrid applications. "
132
135
  "Usage: carm profile [options] -- <command>",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: carm-roofline
3
- Version: 1.0.2.dev0
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