codegreen 0.4.9__tar.gz → 0.5.0__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 (166) hide show
  1. {codegreen-0.4.9 → codegreen-0.5.0}/CMakeLists.txt +4 -15
  2. {codegreen-0.4.9 → codegreen-0.5.0}/MANIFEST.in +0 -1
  3. {codegreen-0.4.9/codegreen.egg-info → codegreen-0.5.0}/PKG-INFO +2 -2
  4. {codegreen-0.4.9 → codegreen-0.5.0}/README.md +1 -1
  5. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/__main__.py +10 -14
  6. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/config.py +29 -13
  7. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/harness.py +22 -4
  8. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/profilers.py +74 -116
  9. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/results.py +50 -33
  10. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/suites/benchmarksgame.py +19 -3
  11. codegreen-0.5.0/benchmark/validation/experiments.py +425 -0
  12. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/__init__.py +5 -10
  13. codegreen-0.5.0/codegreen/analysis/gpu_metrics.py +236 -0
  14. codegreen-0.5.0/codegreen/analysis/hwcounters.py +163 -0
  15. codegreen-0.5.0/codegreen/analysis/languages.py +65 -0
  16. codegreen-0.5.0/codegreen/analysis/line_sampling.py +288 -0
  17. codegreen-0.5.0/codegreen/analysis/metrics.py +143 -0
  18. codegreen-0.5.0/codegreen/analysis/pyalloc.py +65 -0
  19. codegreen-0.5.0/codegreen/analysis/sampling.py +322 -0
  20. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/cli/cli.py +548 -717
  21. codegreen-0.5.0/codegreen/config.json +336 -0
  22. codegreen-0.5.0/codegreen/hw_events/amd_zen.json +2 -0
  23. codegreen-0.5.0/codegreen/hw_events/arm_neoverse.json +3 -0
  24. codegreen-0.5.0/codegreen/hw_events/generic.json +5 -0
  25. codegreen-0.5.0/codegreen/hw_events/intel.json +2 -0
  26. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/__init__.py +1 -2
  27. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/configs/TEMPLATE.json +5 -2
  28. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/configs/c.json +4 -0
  29. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/configs/cpp.json +4 -0
  30. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/configs/java.json +17 -0
  31. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/configs/javascript.json +3 -0
  32. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/configs/python.json +4 -0
  33. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/language_engine.py +73 -0
  34. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/language_runtimes/c/codegreen_runtime.h +8 -0
  35. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/language_runtimes/java/codegreen/runtime/CodeGreenStandaloneRuntime.java +132 -38
  36. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/language_runtimes/python/codegreen_runtime.py +549 -169
  37. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/CMakeLists.txt +3 -0
  38. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/nemb/codegreen_energy.hpp +35 -7
  39. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/nemb/config_loader.hpp +20 -19
  40. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/nemb/core/energy_provider.hpp +94 -7
  41. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/nemb/core/measurement_coordinator.hpp +61 -14
  42. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/nemb/drivers/amd_gpu_provider.hpp +1 -44
  43. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/nemb/drivers/intel_rapl_provider.hpp +3 -1
  44. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/nemb/drivers/nvidia_gpu_provider.hpp +31 -39
  45. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/src/nemb/codegreen_energy.cpp +327 -129
  46. codegreen-0.5.0/codegreen/measurement/src/nemb/config_loader.cpp +207 -0
  47. codegreen-0.5.0/codegreen/measurement/src/nemb/core/energy_provider.cpp +161 -0
  48. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/src/nemb/core/measurement_coordinator.cpp +135 -87
  49. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/src/nemb/drivers/amd_gpu_provider.cpp +31 -110
  50. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/src/nemb/drivers/amd_rapl_provider.cpp +14 -2
  51. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/src/nemb/drivers/intel_rapl_provider.cpp +39 -61
  52. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/src/nemb/drivers/nvidia_gpu_provider.cpp +101 -69
  53. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/src/nemb/drivers/windows_emi_provider.cpp +2 -0
  54. codegreen-0.5.0/codegreen/measurement/tests/nemb_validation_test.cpp +547 -0
  55. {codegreen-0.4.9 → codegreen-0.5.0/codegreen.egg-info}/PKG-INFO +2 -2
  56. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen.egg-info/SOURCES.txt +13 -2
  57. {codegreen-0.4.9 → codegreen-0.5.0}/pyproject.toml +2 -1
  58. {codegreen-0.4.9 → codegreen-0.5.0}/setup.py +1 -0
  59. codegreen-0.5.0/tests/test_gpu_metrics.py +128 -0
  60. codegreen-0.5.0/tests/test_hwcounters.py +210 -0
  61. {codegreen-0.4.9 → codegreen-0.5.0}/tests/test_instrumentation.py +259 -12
  62. codegreen-0.5.0/tests/test_line_sampling.py +140 -0
  63. {codegreen-0.4.9 → codegreen-0.5.0}/tests/test_report_audit.py +924 -12
  64. codegreen-0.5.0/tests/test_sampling.py +141 -0
  65. {codegreen-0.4.9 → codegreen-0.5.0}/tests/test_timeline.py +15 -0
  66. codegreen-0.4.9/benchmark/validation/experiments.py +0 -247
  67. codegreen-0.4.9/codegreen/analysis/sampling.py +0 -157
  68. codegreen-0.4.9/codegreen/config.json +0 -262
  69. codegreen-0.4.9/codegreen/instrumentation/config.py +0 -73
  70. codegreen-0.4.9/codegreen/measurement/src/nemb/config_loader.cpp +0 -313
  71. codegreen-0.4.9/codegreen/measurement/src/nemb/core/energy_provider.cpp +0 -91
  72. codegreen-0.4.9/codegreen/measurement/tests/nemb_validation_test.cpp +0 -383
  73. codegreen-0.4.9/config/codegreen.json +0 -177
  74. codegreen-0.4.9/tests/test_sampling.py +0 -72
  75. {codegreen-0.4.9 → codegreen-0.5.0}/LICENSE +0 -0
  76. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/__init__.py +0 -0
  77. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/_workload_gpt2.py +0 -0
  78. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/_workload_gpt2_session.py +0 -0
  79. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/_workload_gpt2_session_staged.py +0 -0
  80. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/cg_modes_compare.py +0 -0
  81. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/compilers.py +0 -0
  82. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/suites/__init__.py +0 -0
  83. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/suites/base.py +0 -0
  84. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/suites/dacapo.py +0 -0
  85. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/suites/perfopt.py +0 -0
  86. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/suites/renaissance.py +0 -0
  87. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/validation/__init__.py +0 -0
  88. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/validation/analysis.py +0 -0
  89. {codegreen-0.4.9 → codegreen-0.5.0}/benchmark/validation/reporting.py +0 -0
  90. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/__init__.py +0 -0
  91. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/_ts_java.py +0 -0
  92. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/cfg/__init__.py +0 -0
  93. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/cfg/builder.py +0 -0
  94. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/cfg/callgraph.py +0 -0
  95. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/cfg/dataflow.py +0 -0
  96. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/cfg/energy_flow.py +0 -0
  97. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/cfg/features.py +0 -0
  98. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/cfg/pdg.py +0 -0
  99. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/cfg/types.py +0 -0
  100. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/cfg/visualization.py +0 -0
  101. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/efg/__init__.py +0 -0
  102. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/efg/adapter.py +0 -0
  103. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/efg/example/Sample.java +0 -0
  104. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/efg/example/__init__.py +0 -0
  105. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/efg/example/demo.py +0 -0
  106. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/efg/graph.py +0 -0
  107. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/efg/heuristics.py +0 -0
  108. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/efg/paths.py +0 -0
  109. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/efg/serialize.py +0 -0
  110. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/efg/types.py +0 -0
  111. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analysis/timeline.py +0 -0
  112. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analyzer/__init__.py +0 -0
  113. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/analyzer/plot.py +0 -0
  114. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/cli/__init__.py +0 -0
  115. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/cli/entrypoint.py +0 -0
  116. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/ast_processor.py +0 -0
  117. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/brace_normaliser.py +0 -0
  118. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/bridge_analyze.py +0 -0
  119. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/bridge_instrument.py +0 -0
  120. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/engine.py +0 -0
  121. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/language_configs.py +0 -0
  122. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/language_runtimes/cpp/codegreen/runtime.hpp +0 -0
  123. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/language_runtimes/java/codegreen/runtime/CodeGreenRuntime.java +0 -0
  124. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/language_runtimes/python/codegreen_otlp_sink.py +0 -0
  125. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/instrumentation/language_runtimes/python/codegreen_prometheus_sink.py +0 -0
  126. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/adapters/language_adapter.hpp +0 -0
  127. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/adapters/python_bridge_adapter.hpp +0 -0
  128. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/config.hpp +0 -0
  129. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/energy_code_mapper.hpp +0 -0
  130. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/energy_storage.hpp +0 -0
  131. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/measurement.hpp +0 -0
  132. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/measurement_engine.hpp +0 -0
  133. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/measurement_session.hpp +0 -0
  134. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/nemb/accuracy_validator.hpp +0 -0
  135. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/nemb/drivers/amd_rapl_provider.hpp +0 -0
  136. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/nemb/drivers/arm_energy_provider.hpp +0 -0
  137. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/nemb/drivers/darwin_ioreport_provider.hpp +0 -0
  138. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/nemb/drivers/darwin_kpc_provider.hpp +0 -0
  139. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/nemb/drivers/windows_emi_provider.hpp +0 -0
  140. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/nemb/hal/counter_manager.hpp +0 -0
  141. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/nemb/utils/non_blocking_file_reader.hpp +0 -0
  142. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/include/nemb/utils/precision_timer.hpp +0 -0
  143. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/src/energy_code_mapper.cpp +0 -0
  144. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/src/nemb/accuracy_validator.cpp +0 -0
  145. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/src/nemb/drivers/arm_energy_provider.cpp +0 -0
  146. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/src/nemb/drivers/darwin_ioreport_provider.cpp +0 -0
  147. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/src/nemb/drivers/darwin_kpc_provider.cpp +0 -0
  148. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/src/nemb/hal/counter_manager.cpp +0 -0
  149. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/src/nemb/utils/non_blocking_file_reader.cpp +0 -0
  150. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/src/nemb/utils/precision_timer.cpp +0 -0
  151. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/measurement/tests/rapl_provider_test.cpp +0 -0
  152. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/utils/__init__.py +0 -0
  153. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/utils/binary.py +0 -0
  154. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen/utils/platform.py +0 -0
  155. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen.egg-info/dependency_links.txt +0 -0
  156. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen.egg-info/entry_points.txt +0 -0
  157. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen.egg-info/not-zip-safe +0 -0
  158. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen.egg-info/requires.txt +0 -0
  159. {codegreen-0.4.9 → codegreen-0.5.0}/codegreen.egg-info/top_level.txt +0 -0
  160. {codegreen-0.4.9 → codegreen-0.5.0}/requirements.txt +0 -0
  161. {codegreen-0.4.9 → codegreen-0.5.0}/setup.cfg +0 -0
  162. {codegreen-0.4.9 → codegreen-0.5.0}/tests/test_callgraph_python.py +0 -0
  163. {codegreen-0.4.9 → codegreen-0.5.0}/tests/test_efg.py +0 -0
  164. {codegreen-0.4.9 → codegreen-0.5.0}/tests/test_examples.py +0 -0
  165. {codegreen-0.4.9 → codegreen-0.5.0}/tests/test_hotspot_taxonomy.py +0 -0
  166. {codegreen-0.4.9 → codegreen-0.5.0}/tests/test_instrumentation_braces.py +0 -0
@@ -46,15 +46,8 @@ include_directories(${CMAKE_SOURCE_DIR}/codegreen/measurement/include)
46
46
  add_subdirectory(codegreen/measurement)
47
47
 
48
48
 
49
- # Copy config to build dir
50
- if(EXISTS "${CMAKE_SOURCE_DIR}/config/codegreen.json")
51
- file(MAKE_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/config)
52
- configure_file(
53
- ${CMAKE_SOURCE_DIR}/config/codegreen.json
54
- ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/config/codegreen.json
55
- COPYONLY
56
- )
57
- endif()
49
+ # NEMB finds its config at <library dir>/../config.json; place the bundled one there so the build tree works
50
+ configure_file(${CMAKE_SOURCE_DIR}/codegreen/config.json ${CMAKE_BINARY_DIR}/config.json COPYONLY)
58
51
 
59
52
  # Dev-install: copy NEMB library to project root for pip editable installs
60
53
  add_custom_target(dev-install ALL
@@ -73,17 +66,13 @@ install(TARGETS codegreen-nemb
73
66
  RUNTIME DESTINATION lib
74
67
  )
75
68
 
69
+ install(FILES ${CMAKE_SOURCE_DIR}/codegreen/config.json DESTINATION share/codegreen) # <prefix>/lib/../share/codegreen
70
+
76
71
  install(FILES
77
72
  ${CMAKE_SOURCE_DIR}/codegreen/instrumentation/language_runtimes/python/codegreen_runtime.py
78
73
  DESTINATION bin/runtime
79
74
  )
80
75
 
81
- if(EXISTS "${CMAKE_SOURCE_DIR}/config/codegreen.json")
82
- install(FILES ${CMAKE_SOURCE_DIR}/config/codegreen.json
83
- DESTINATION bin/config
84
- )
85
- endif()
86
-
87
76
  set(CPACK_PACKAGE_NAME "CodeGreen")
88
77
  set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
89
78
  set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Energy-aware software development tool")
@@ -1,5 +1,4 @@
1
1
  include README.md LICENSE requirements.txt pyproject.toml CMakeLists.txt setup.py
2
- include config/codegreen.json
3
2
 
4
3
  recursive-include codegreen/measurement CMakeLists.txt *.cpp *.c *.h *.hpp
5
4
  recursive-include codegreen/instrumentation/language_runtimes *.py *.h *.hpp *.java
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codegreen
3
- Version: 0.4.9
3
+ Version: 0.5.0
4
4
  Summary: Know the true energy cost of your code
5
5
  Author-email: Saurabhsingh Rajput <saurabh@dal.ca>
6
6
  Maintainer-email: Saurabhsingh Rajput <saurabh@dal.ca>
@@ -637,7 +637,7 @@ See `codegreen/analysis/efg/README.md` for the module layout, a runnable example
637
637
  ```bash
638
638
  # Fail pipeline if energy exceeds budget
639
639
  codegreen run --budget 10.0 --json -- python tests/benchmark.py
640
- # Exit code 1 if mean energy > 10 Joules
640
+ # Exit code 1 if mean energy > 10 Joules, 3 if the measurement is invalid (see validity.reasons)
641
641
  ```
642
642
 
643
643
  ## Upgrading
@@ -192,7 +192,7 @@ See `codegreen/analysis/efg/README.md` for the module layout, a runnable example
192
192
  ```bash
193
193
  # Fail pipeline if energy exceeds budget
194
194
  codegreen run --budget 10.0 --json -- python tests/benchmark.py
195
- # Exit code 1 if mean energy > 10 Joules
195
+ # Exit code 1 if mean energy > 10 Joules, 3 if the measurement is invalid (see validity.reasons)
196
196
  ```
197
197
 
198
198
  ## Upgrading
@@ -1,9 +1,9 @@
1
1
  """Entry point for `python3 -m benchmark`."""
2
2
  import argparse
3
- import sys
4
3
  from pathlib import Path
5
4
  from benchmark.config import BenchmarkConfig
6
5
  from benchmark.harness import BenchmarkHarness
6
+ from benchmark.suites.benchmarksgame import BenchmarksgameSuite
7
7
 
8
8
  def main():
9
9
  parser = argparse.ArgumentParser(description="CodeGreen Energy Benchmark Suite")
@@ -18,27 +18,23 @@ def main():
18
18
  args = parser.parse_args()
19
19
 
20
20
  config = BenchmarkConfig.from_yaml(args.config) if args.config else BenchmarkConfig.default()
21
- harness = BenchmarkHarness(config)
21
+ suite = BenchmarksgameSuite(Path(config.benchmarks_dir) if config.benchmarks_dir else None,
22
+ languages=args.languages, problems=args.problems, sizes=args.sizes)
23
+ harness = BenchmarkHarness(suite, repetitions=args.repetitions, warmup_runs=config.warmup_runs,
24
+ timeout_seconds=config.timeout_seconds, min_runtime_seconds=config.min_runtime_seconds)
22
25
 
23
26
  try:
24
- collector = harness.run_suite(
25
- problems=args.problems,
26
- languages=args.languages,
27
- sizes=args.sizes,
28
- profilers=args.profilers,
29
- repetitions=args.repetitions,
30
- )
27
+ collector = harness.run_suite(profilers=args.profilers, repetitions=args.repetitions)
31
28
  summaries = collector.summarize_all()
32
29
  print(f"\n{'='*60}")
33
30
  print(f"Results: {len(collector.results)} runs")
34
31
  print(f"{'='*60}")
35
32
  for key, data in sorted(summaries.items()):
36
- e = data.get("energy")
37
- t = data.get("time")
38
- valid = data.get("valid_runs", 0)
39
- total = data.get("total_runs", 0)
33
+ e, t = data["energy"], data["time"]
40
34
  energy_str = f"{e.mean:.4f} +/- {e.std:.4f} J" if e else "N/A"
41
- print(f" {key}: energy={energy_str} time={t.mean:.3f}s valid={valid}/{total}")
35
+ time_str = f"{t.mean:.3f}s" if t else "N/A"
36
+ print(f" {key}: energy={energy_str} time={time_str} valid={data['valid_runs']}/{data['total_runs']}"
37
+ f" invalid={data['invalid_reasons']}")
42
38
 
43
39
  if args.output:
44
40
  collector.to_json(args.output)
@@ -15,7 +15,7 @@ class SystemState:
15
15
  cpu_model: str = ""
16
16
  cpu_governor: str = ""
17
17
  kernel_version: str = ""
18
- rapl_domains: List[str] = field(default_factory=list)
18
+ power_events: List[str] = field(default_factory=list)
19
19
 
20
20
  @classmethod
21
21
  def capture(cls) -> "SystemState":
@@ -34,17 +34,8 @@ class SystemState:
34
34
  state.cpu_governor = f.read().strip()
35
35
  except (FileNotFoundError, OSError):
36
36
  pass
37
- import os
38
- for i in range(10):
39
- path = f"/sys/class/powercap/intel-rapl:0:{i}/name"
40
- if os.path.exists(path):
41
- try:
42
- with open(path) as f:
43
- state.rapl_domains.append(f.read().strip())
44
- except OSError:
45
- pass
46
- else:
47
- break
37
+ from benchmark.profilers import perf_power_events
38
+ state.power_events = perf_power_events()
48
39
  return state
49
40
 
50
41
  @dataclass
@@ -66,7 +57,7 @@ class RunResult:
66
57
  language: str
67
58
  size: str
68
59
  profiler: str
69
- energy_joules: float
60
+ energy_joules: Optional[float]
70
61
  time_seconds: float
71
62
  output_valid: bool
72
63
  repetition: int = 0
@@ -74,6 +65,31 @@ class RunResult:
74
65
  suite: str = "benchmarksgame"
75
66
  timestamp: datetime = field(default_factory=datetime.now)
76
67
  checkpoints: List[Dict[str, Any]] = field(default_factory=list)
68
+ validity: Optional[Dict[str, Any]] = None
69
+
70
+ @property
71
+ def measurement_valid(self) -> bool:
72
+ """No fatal validity reason; profilers without a validity contract (perf, native) report None."""
73
+ return self.validity is None or self.validity["status"] == "valid"
74
+
75
+ # perf power events summed as the CPU-scope truth, mirroring the RAPL adapters' top-level domain rule: the first set
76
+ # whose first event the power PMU offers wins (psys supersedes package + dram), using the offered events of that set.
77
+ PERF_CPU_EVENT_SETS = (("energy-psys",), ("energy-pkg", "energy-ram"))
78
+
79
+ @dataclass
80
+ class ValidityTolerances:
81
+ """Expectations of benchmark.validation.experiments.ValidityExperiment. Windows are in sampler intervals
82
+ (sampler.interval_ms of the run itself); a window below min_window_samples - 1 intervals must be flagged
83
+ TOTAL_BELOW_RESOLUTION, one at or above min_window_samples + 1 must not, the two intervals between are either (one
84
+ late reading costs the window a reading). A sampler allowed on fewer than min_affinity_cpus CPUs must raise the
85
+ AFFINITY_SINGLE_CPU warning and may be SAMPLER_STARVED, as may any run whose reading gaps exceed codegreen's gap policy.
86
+ cv_max_pct maps a minimum window (s) to the largest CV (%) of valid energies across repetitions."""
87
+ min_window_samples: int = 2
88
+ min_affinity_cpus: int = 2
89
+ exclusive_rel_tol: float = 0.01
90
+ run_vs_perf_rel_tol: float = 0.03
91
+ run_vs_perf_min_duration_s: float = 1.0
92
+ cv_max_pct: Dict[float, float] = field(default_factory=lambda: {1.0: 5.0, 0.1: 10.0})
77
93
 
78
94
  @dataclass
79
95
  class BenchmarkConfig:
@@ -3,7 +3,6 @@ import math
3
3
  import subprocess
4
4
  import sys
5
5
  import time
6
- from pathlib import Path
7
6
  from typing import Callable, List, Optional
8
7
 
9
8
  from benchmark.config import RunResult
@@ -49,11 +48,13 @@ class BenchmarkHarness:
49
48
  """Run a single task with a single profiler."""
50
49
  reps = repetitions or self.repetitions
51
50
  profiler = get_profiler(profiler_name)
51
+ if not profiler.is_available():
52
+ self.progress_callback(f" Skipping {task.name} [{profiler_name}]: {profiler.unavailable_reason()}")
53
+ return []
54
+ # Build the task (compile, construct run command); the build may name the source anew (Java public class)
55
+ task = self.suite.build(task)
52
56
  if hasattr(profiler, 'set_source') and task.source_file:
53
57
  profiler.set_source(task.source_file, task.language)
54
-
55
- # Build the task (compile, construct run command)
56
- task = self.suite.build(task)
57
58
  if not task.run_command:
58
59
  self.progress_callback(f" No run command for {task.name}, skipping")
59
60
  return []
@@ -96,6 +97,7 @@ class BenchmarkHarness:
96
97
  variant=task.variant,
97
98
  suite=self.suite.name,
98
99
  checkpoints=profile_result.checkpoints,
100
+ validity=profile_result.validity,
99
101
  )
100
102
  results.append(result)
101
103
  self.collector.add(result)
@@ -105,6 +107,22 @@ class BenchmarkHarness:
105
107
  self.progress_callback(f" Run {i+1} failed: {e}")
106
108
  return results
107
109
 
110
+ def tasks(self, problem: str, languages: List[str], sizes: Optional[List[str]] = None) -> List[Task]:
111
+ """Tasks of one problem at the given languages and sizes (suite defaults when None); the default suite is
112
+ benchmarksgame."""
113
+ if self.suite is None:
114
+ from benchmark.suites.benchmarksgame import BenchmarksgameSuite
115
+ self.suite = BenchmarksgameSuite()
116
+ found = self.suite.discover({"problems": [problem], "languages": languages, "sizes": sizes})
117
+ if not found:
118
+ raise ValueError(f"{self.suite.name} has no task for {problem} in {languages} at sizes {sizes}")
119
+ return found
120
+
121
+ def run_problem(self, problem: str, languages: List[str], sizes: List[str], profilers: List[str],
122
+ repetitions: Optional[int] = None) -> List[RunResult]:
123
+ """Run one problem for every language, size and profiler; returns the results of all runs."""
124
+ return [r for t in self.tasks(problem, languages, sizes) for p in profilers for r in self.run_task(t, p, repetitions)]
125
+
108
126
  def run_suite(self, profilers: Optional[List[str]] = None,
109
127
  repetitions: Optional[int] = None,
110
128
  filters: Optional[dict] = None) -> ResultCollector:
@@ -1,21 +1,27 @@
1
1
  """Energy profiler interfaces for CodeGreen and perf RAPL."""
2
2
  import json
3
+ import os
3
4
  import re
5
+ import shlex
6
+ import shutil
4
7
  import subprocess
5
8
  import tempfile
6
9
  import time
7
10
  from abc import ABC, abstractmethod
8
11
  from dataclasses import dataclass
9
12
  from pathlib import Path
10
- from typing import Any, Dict, List
13
+ from typing import Any, Dict, List, Optional
14
+
15
+ from benchmark.config import PERF_CPU_EVENT_SETS
11
16
 
12
17
  @dataclass
13
18
  class ProfileResult:
14
- energy_joules: float
19
+ energy_joules: Optional[float]
15
20
  time_seconds: float
16
21
  output: str
17
22
  checkpoints: List[Dict[str, Any]]
18
23
  raw_data: Dict[str, Any]
24
+ validity: Optional[Dict[str, Any]] = None
19
25
 
20
26
  class ProfilerInterface(ABC):
21
27
  @abstractmethod
@@ -26,11 +32,22 @@ class ProfilerInterface(ABC):
26
32
  def is_available(self) -> bool:
27
33
  pass
28
34
 
35
+ def unavailable_reason(self) -> str:
36
+ return f"{type(self).__name__} is not available on this host"
37
+
38
+ def perf_power_events() -> List[str]:
39
+ """RAPL events the kernel power PMU offers through perf (empty without perf or without a power PMU)."""
40
+ if not shutil.which("perf"):
41
+ return []
42
+ out = subprocess.run(["perf", "list", "power"], capture_output=True, text=True, timeout=60).stdout
43
+ return sorted(set(re.findall(r"power/(energy-[\w-]+)/", out)))
44
+
29
45
  class CodeGreenProfiler(ProfilerInterface):
30
46
  """Uses codegreen measure CLI for energy measurement."""
31
47
  def __init__(self):
32
48
  self.source_path: Path | None = None
33
49
  self.language: str | None = None
50
+ self.extra_args: List[str] = []
34
51
 
35
52
  def set_source(self, source: Path, language: str = None):
36
53
  """Set source file and language for measurement."""
@@ -46,25 +63,31 @@ class CodeGreenProfiler(ProfilerInterface):
46
63
  if cmd[0] == "python3":
47
64
  args = cmd[2:] if len(cmd) > 2 else []
48
65
  elif cmd[0] == "java":
49
- args = cmd[3:] if len(cmd) > 3 else [] # java -cp dir class args
66
+ args = cmd[4:] # java -cp dir class args
50
67
  else:
51
68
  args = cmd[1:] if len(cmd) > 1 else [] # binary args
52
69
  else:
53
70
  # Fallback: detect from command
54
71
  lang, source, args = self._detect_from_cmd(cmd)
55
72
 
56
- import os
57
- full_cmd = ["codegreen", "measure", lang, source, "--json", "--timeout", str(timeout)] + args
73
+ full_cmd = ["codegreen", "measure", lang, source, "--json", "--timeout", str(timeout), *self.extra_args] + args
58
74
  env = {**os.environ, "PYTHONUNBUFFERED": "1"}
59
75
 
60
76
  start = time.perf_counter()
61
77
  result = subprocess.run(full_cmd, capture_output=True, text=True, timeout=timeout + 30, env=env)
62
78
  elapsed = time.perf_counter() - start
63
-
64
- energy, checkpoints, raw, program_output = self._parse_output(result.stdout)
65
- if energy <= 0.0 and checkpoints:
66
- energy = 0.0
67
- return ProfileResult(energy_joules=energy, time_seconds=elapsed, output=program_output, checkpoints=checkpoints, raw_data=raw)
79
+ # The JSON result is printed on every exit code (exit 3 = invalid measurement); signed energy is kept as reported.
80
+ try:
81
+ raw = json.loads(result.stdout)
82
+ except json.JSONDecodeError:
83
+ raise RuntimeError(f"codegreen measure exit {result.returncode} without a JSON result: {result.stderr[-500:]}")
84
+ validity = raw.get("validity")
85
+ if validity is None or result.returncode not in (0, 3):
86
+ detail = {**(validity or {}).get("detail", {}),
87
+ "MEASURE_FAILED": raw.get("error") or f"codegreen measure exit {result.returncode}"}
88
+ validity = {**(validity or {"warnings": {}}), "status": "invalid", "reasons": list(detail), "detail": detail}
89
+ return ProfileResult((raw.get("summary") or {}).get("total_energy_j"), elapsed, "",
90
+ raw.get("checkpoints") or [], raw, validity)
68
91
 
69
92
  def _detect_from_cmd(self, cmd: List[str]) -> tuple:
70
93
  """Fallback: detect language, source, args from command."""
@@ -72,118 +95,56 @@ class CodeGreenProfiler(ProfilerInterface):
72
95
  return "python", cmd[1] if len(cmd) > 1 else cmd[0], cmd[2:] if len(cmd) > 2 else []
73
96
  elif cmd[0] == "java":
74
97
  # java -cp dir class args -> need source from set_source
75
- return "java", str(self.source_path) if self.source_path else "", cmd[3:] if len(cmd) > 3 else []
98
+ return "java", str(self.source_path) if self.source_path else "", cmd[4:]
76
99
  else:
77
100
  # Compiled binary
78
101
  source = str(self.source_path) if self.source_path else cmd[0]
79
102
  lang = "cpp" if (".gpp" in source or "cpp" in source) else "c"
80
103
  return lang, source, cmd[1:] if len(cmd) > 1 else []
81
104
 
82
- def _parse_output(self, stdout: str) -> tuple:
83
- energy, checkpoints, raw, program_output = 0.0, [], {}, ""
84
- try:
85
- raw = json.loads(stdout)
86
- except (json.JSONDecodeError, ValueError):
87
- return energy, checkpoints, raw, stdout
88
-
89
- # Extract energy from summary (primary) or checkpoints (fallback)
90
- summary = raw.get("summary", {})
91
- if summary.get("total_energy_j", 0) > 0:
92
- energy = summary["total_energy_j"]
93
-
94
- checkpoints = raw.get("checkpoints", [])
95
- if energy <= 0 and len(checkpoints) >= 2:
96
- first_j = checkpoints[0].get("joules", 0)
97
- last_j = checkpoints[-1].get("joules", 0)
98
- energy = last_j - first_j
99
- if energy < 0:
100
- energy = 0.0
101
-
102
- return energy, checkpoints, raw, program_output
103
-
104
105
  def is_available(self) -> bool:
105
- try:
106
- with open("/sys/class/powercap/intel-rapl:0/energy_uj") as f:
107
- f.read()
108
- return True
109
- except (FileNotFoundError, PermissionError, OSError):
110
- return False
106
+ return shutil.which("codegreen") is not None
111
107
 
112
108
  class PerfProfiler(ProfilerInterface):
109
+ """perf stat -a over the command: system-wide RAPL energy at the CPU domain set of PERF_CPU_EVENT_SETS, the
110
+ truth source for `codegreen run/measure --sensors rapl`."""
113
111
  def __init__(self):
114
- self.perf_output_file: Path | None = None
115
- self._events = self._detect_events()
112
+ offered = perf_power_events()
113
+ self.events = next(([e for e in s if e in offered] for s in PERF_CPU_EVENT_SETS if s[0] in offered), [])
116
114
  self.repeat_count: int = 1
117
115
 
118
- def __del__(self):
119
- self._cleanup()
120
-
121
- def _detect_events(self) -> str:
122
- try:
123
- result = subprocess.run(["perf", "list", "power"], capture_output=True, text=True, timeout=5)
124
- events = []
125
- if "energy-pkg" in result.stdout:
126
- events.append("power/energy-pkg/")
127
- if "energy-ram" in result.stdout:
128
- events.append("power/energy-ram/")
129
- return ",".join(events) if events else "power/energy-pkg/"
130
- except Exception:
131
- return "power/energy-pkg/"
132
-
133
116
  def run(self, cmd: List[str], timeout: int = 300) -> ProfileResult:
134
- with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
135
- self.perf_output_file = Path(f.name)
136
- if self.repeat_count > 1:
137
- shell_cmd = " ".join(f"'{c}'" for c in cmd)
138
- loop_cmd = f"for i in $(seq 1 {self.repeat_count}); do {shell_cmd}; done"
139
- full_cmd = ["perf", "stat", "-e", self._events, "-o",
140
- str(self.perf_output_file), "--", "bash", "-c", loop_cmd]
141
- else:
142
- full_cmd = ["perf", "stat", "-e", self._events, "-o",
143
- str(self.perf_output_file), "--"] + cmd
144
- start = time.perf_counter()
145
- result = subprocess.run(full_cmd, capture_output=True, text=True, timeout=timeout)
146
- elapsed = time.perf_counter() - start
147
- energy = self._parse_perf_output()
148
- if self.repeat_count > 1:
149
- energy /= self.repeat_count
150
- elapsed /= self.repeat_count
151
- return ProfileResult(
152
- energy_joules=energy,
153
- time_seconds=elapsed,
154
- output=result.stdout,
155
- checkpoints=[],
156
- raw_data={"perf_output": self.perf_output_file.read_text() if self.perf_output_file.exists() else "",
157
- "repeat_count": self.repeat_count}
158
- )
159
-
160
- def _parse_perf_output(self) -> float:
161
- if not self.perf_output_file or not self.perf_output_file.exists():
162
- return 0.0
163
- content = self.perf_output_file.read_text()
164
- total = 0.0
165
- pkg_match = re.search(r'([\d.,]+)\s+Joules\s+power/energy-pkg/', content)
166
- if pkg_match:
167
- total += float(pkg_match.group(1).replace(',', ''))
168
- ram_match = re.search(r'([\d.,]+)\s+Joules\s+power/energy-ram/', content)
169
- if ram_match:
170
- total += float(ram_match.group(1).replace(',', ''))
171
- return total
172
-
173
- def _cleanup(self):
174
- if self.perf_output_file and self.perf_output_file.exists():
175
- try:
176
- self.perf_output_file.unlink()
177
- except OSError:
178
- pass
179
- self.perf_output_file = None
117
+ body = cmd if self.repeat_count == 1 else [
118
+ "bash", "-c", f"for i in $(seq {self.repeat_count}); do {shlex.join(cmd)}; done"]
119
+ with tempfile.NamedTemporaryFile(suffix=".csv", delete=False) as f:
120
+ out = Path(f.name)
121
+ try:
122
+ start = time.perf_counter()
123
+ result = subprocess.run(["perf", "stat", "-a", "-x,", "-e", ",".join(f"power/{e}/" for e in self.events),
124
+ "-o", str(out), "--", *body], capture_output=True, text=True, timeout=timeout)
125
+ elapsed = time.perf_counter() - start
126
+ text = out.read_text()
127
+ finally:
128
+ out.unlink()
129
+ if result.returncode:
130
+ raise RuntimeError(f"perf stat exit {result.returncode}: {result.stderr[-500:]}")
131
+ events = {}
132
+ for f in (line.split(",") for line in text.splitlines()):
133
+ if len(f) > 2 and f[2].startswith("power/"):
134
+ if not f[0][:1].isdigit():
135
+ raise RuntimeError(f"perf could not count {f[2]}: {f[0]}")
136
+ events[f[2][6:-1]] = float(f[0]) / self.repeat_count
137
+ if sorted(events) != sorted(self.events):
138
+ raise RuntimeError(f"perf counted {sorted(events)}, expected {self.events}")
139
+ return ProfileResult(sum(events.values()), elapsed / self.repeat_count, result.stdout, [],
140
+ {"events_j": events, "repeat_count": self.repeat_count})
180
141
 
181
142
  def is_available(self) -> bool:
182
- try:
183
- result = subprocess.run(["perf", "list", "power"], capture_output=True, text=True, timeout=5)
184
- return "energy-pkg" in result.stdout
185
- except (subprocess.TimeoutExpired, FileNotFoundError):
186
- return False
143
+ return bool(self.events)
144
+
145
+ def unavailable_reason(self) -> str:
146
+ return (f"perf offers none of the power events {PERF_CPU_EVENT_SETS} (`perf list power`: "
147
+ f"{perf_power_events() or 'none'}); install perf on a host with a RAPL power PMU")
187
148
 
188
149
  class NativeProfiler(ProfilerInterface):
189
150
  """Runs without energy profiling - for baseline timing."""
@@ -192,7 +153,7 @@ class NativeProfiler(ProfilerInterface):
192
153
  result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
193
154
  elapsed = time.perf_counter() - start
194
155
  return ProfileResult(
195
- energy_joules=0.0,
156
+ energy_joules=None,
196
157
  time_seconds=elapsed,
197
158
  output=result.stdout,
198
159
  checkpoints=[],
@@ -208,7 +169,7 @@ class CodeCarbonProfiler(ProfilerInterface):
208
169
  try:
209
170
  from codecarbon import EmissionsTracker
210
171
  except ImportError:
211
- return ProfileResult(0.0, 0.0, "", [], {"error": "codecarbon not installed"})
172
+ raise RuntimeError("codecarbon not installed")
212
173
  tracker = EmissionsTracker(log_level="error", save_to_file=False)
213
174
  tracker.start()
214
175
  start = time.perf_counter()
@@ -220,11 +181,8 @@ class CodeCarbonProfiler(ProfilerInterface):
220
181
  {"emissions_kg_co2": emissions, "energy_kwh": tracker._total_energy.kWh})
221
182
 
222
183
  def is_available(self) -> bool:
223
- try:
224
- import codecarbon
225
- return True
226
- except ImportError:
227
- return False
184
+ import importlib.util
185
+ return importlib.util.find_spec("codecarbon") is not None
228
186
 
229
187
 
230
188
  class JoularJXProfiler(ProfilerInterface):
@@ -240,7 +198,7 @@ class JoularJXProfiler(ProfilerInterface):
240
198
 
241
199
  def run(self, cmd: List[str], timeout: int = 300) -> ProfileResult:
242
200
  if not self.agent_path:
243
- return ProfileResult(0.0, 0.0, "", [], {"error": "JoularJX agent not found"})
201
+ raise RuntimeError("JoularJX agent not found")
244
202
  jx_cmd = cmd.copy()
245
203
  if cmd[0] == "java":
246
204
  jx_cmd.insert(1, f"-javaagent:{self.agent_path}")
@@ -253,7 +211,7 @@ class JoularJXProfiler(ProfilerInterface):
253
211
  def _parse_joularjx_output(self) -> float:
254
212
  csv_path = Path("joularJX-all-methods-energy.csv")
255
213
  if not csv_path.exists():
256
- return 0.0
214
+ raise RuntimeError(f"JoularJX wrote no {csv_path}")
257
215
  total = 0.0
258
216
  for line in csv_path.read_text().splitlines()[1:]:
259
217
  parts = line.split(",")