acprof 0.3.0__py3-none-any.whl

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 (704) hide show
  1. acprof/__init__.py +3 -0
  2. acprof/__main__.py +5 -0
  3. acprof/_bundle/.dockerignore +20 -0
  4. acprof/_bundle/LICENSE +202 -0
  5. acprof/_bundle/NOTICE +43 -0
  6. acprof/_bundle/acprof/__init__.py +3 -0
  7. acprof/_bundle/acprof/__main__.py +5 -0
  8. acprof/_bundle/acprof/analysis/__init__.py +1 -0
  9. acprof/_bundle/acprof/analysis/audit.py +276 -0
  10. acprof/_bundle/acprof/analysis/comparison.py +364 -0
  11. acprof/_bundle/acprof/analysis/compatibility.py +223 -0
  12. acprof/_bundle/acprof/analysis/conditions.py +185 -0
  13. acprof/_bundle/acprof/analysis/independent_comparison.py +191 -0
  14. acprof/_bundle/acprof/analysis/latency_model.py +1106 -0
  15. acprof/_bundle/acprof/analysis/latency_report.py +577 -0
  16. acprof/_bundle/acprof/analysis/model.py +276 -0
  17. acprof/_bundle/acprof/analysis/precision.py +51 -0
  18. acprof/_bundle/acprof/analysis/uncertainty.py +144 -0
  19. acprof/_bundle/acprof/application/__init__.py +1 -0
  20. acprof/_bundle/acprof/application/download.py +14 -0
  21. acprof/_bundle/acprof/application/errors.py +5 -0
  22. acprof/_bundle/acprof/application/notifications.py +302 -0
  23. acprof/_bundle/acprof/application/preparation.py +333 -0
  24. acprof/_bundle/acprof/application/resources.py +66 -0
  25. acprof/_bundle/acprof/application/run.py +462 -0
  26. acprof/_bundle/acprof/application/terminal_log.py +136 -0
  27. acprof/_bundle/acprof/artifact_layout.py +261 -0
  28. acprof/_bundle/acprof/artifacts.py +161 -0
  29. acprof/_bundle/acprof/capabilities.py +404 -0
  30. acprof/_bundle/acprof/cli/__init__.py +1 -0
  31. acprof/_bundle/acprof/cli/audit.py +74 -0
  32. acprof/_bundle/acprof/cli/auto.py +41 -0
  33. acprof/_bundle/acprof/cli/backfill_compute.py +498 -0
  34. acprof/_bundle/acprof/cli/compare.py +34 -0
  35. acprof/_bundle/acprof/cli/coverage.py +70 -0
  36. acprof/_bundle/acprof/cli/doctor.py +47 -0
  37. acprof/_bundle/acprof/cli/inspect.py +101 -0
  38. acprof/_bundle/acprof/cli/load.py +190 -0
  39. acprof/_bundle/acprof/cli/main.py +64 -0
  40. acprof/_bundle/acprof/cli/model_store.py +22 -0
  41. acprof/_bundle/acprof/cli/plot.py +211 -0
  42. acprof/_bundle/acprof/cli/posthoc.py +113 -0
  43. acprof/_bundle/acprof/cli/probe.py +186 -0
  44. acprof/_bundle/acprof/cli/report.py +35 -0
  45. acprof/_bundle/acprof/cli/results.py +37 -0
  46. acprof/_bundle/acprof/cli/run.py +54 -0
  47. acprof/_bundle/acprof/cli/stats.py +94 -0
  48. acprof/_bundle/acprof/cli/tui.py +65 -0
  49. acprof/_bundle/acprof/config.py +177 -0
  50. acprof/_bundle/acprof/container/__init__.py +1 -0
  51. acprof/_bundle/acprof/container/compat/__init__.py +1 -0
  52. acprof/_bundle/acprof/container/compat/transformers_dynamic.py +54 -0
  53. acprof/_bundle/acprof/container/compat/transformers_model_types.json +1091 -0
  54. acprof/_bundle/acprof/container/compute_profile_runner.py +392 -0
  55. acprof/_bundle/acprof/container/download_model.py +165 -0
  56. acprof/_bundle/acprof/container/execution.py +73 -0
  57. acprof/_bundle/acprof/container/handlers/__init__.py +298 -0
  58. acprof/_bundle/acprof/container/handlers/audio.py +743 -0
  59. acprof/_bundle/acprof/container/handlers/custom_pipeline.py +74 -0
  60. acprof/_bundle/acprof/container/handlers/cv.py +321 -0
  61. acprof/_bundle/acprof/container/handlers/diffusion.py +728 -0
  62. acprof/_bundle/acprof/container/handlers/moss.py +64 -0
  63. acprof/_bundle/acprof/container/handlers/multimodal.py +609 -0
  64. acprof/_bundle/acprof/container/handlers/nlp.py +747 -0
  65. acprof/_bundle/acprof/container/handlers/onnx_tasks.py +231 -0
  66. acprof/_bundle/acprof/container/handlers/onnxruntime.py +90 -0
  67. acprof/_bundle/acprof/container/handlers/structured.py +185 -0
  68. acprof/_bundle/acprof/container/handlers/timeseries.py +178 -0
  69. acprof/_bundle/acprof/container/load_policy.py +81 -0
  70. acprof/_bundle/acprof/container/loading_quality.py +48 -0
  71. acprof/_bundle/acprof/container/local_pipeline.py +31 -0
  72. acprof/_bundle/acprof/container/model_files.py +280 -0
  73. acprof/_bundle/acprof/container/model_probe.py +80 -0
  74. acprof/_bundle/acprof/container/onnx_session.py +150 -0
  75. acprof/_bundle/acprof/container/runtime_manifest.py +150 -0
  76. acprof/_bundle/acprof/container/runtime_validate.py +156 -0
  77. acprof/_bundle/acprof/container/server.py +209 -0
  78. acprof/_bundle/acprof/container/torch_execution.py +55 -0
  79. acprof/_bundle/acprof/container/validation.py +172 -0
  80. acprof/_bundle/acprof/cpu_affinity.py +35 -0
  81. acprof/_bundle/acprof/dependency_locks.py +129 -0
  82. acprof/_bundle/acprof/experiment.py +356 -0
  83. acprof/_bundle/acprof/extensions/__init__.py +294 -0
  84. acprof/_bundle/acprof/extensions/builtin/manifest.json +3591 -0
  85. acprof/_bundle/acprof/extensions/onnxruntime/manifest.json +191 -0
  86. acprof/_bundle/acprof/extensions/schema.py +238 -0
  87. acprof/_bundle/acprof/extensions/transformers/4.57.6.json +1639 -0
  88. acprof/_bundle/acprof/extensions/transformers/5.10.4.json +1812 -0
  89. acprof/_bundle/acprof/failures.py +115 -0
  90. acprof/_bundle/acprof/hardware_conditions.py +18 -0
  91. acprof/_bundle/acprof/hf_download.py +143 -0
  92. acprof/_bundle/acprof/hf_endpoints.py +62 -0
  93. acprof/_bundle/acprof/hf_transport.py +238 -0
  94. acprof/_bundle/acprof/host/__init__.py +1 -0
  95. acprof/_bundle/acprof/host/automation.py +161 -0
  96. acprof/_bundle/acprof/host/client.py +520 -0
  97. acprof/_bundle/acprof/host/collection_history.py +77 -0
  98. acprof/_bundle/acprof/host/collection_workflow.py +285 -0
  99. acprof/_bundle/acprof/host/command.py +119 -0
  100. acprof/_bundle/acprof/host/container_lifecycle.py +225 -0
  101. acprof/_bundle/acprof/host/container_state.py +128 -0
  102. acprof/_bundle/acprof/host/coverage_state.py +259 -0
  103. acprof/_bundle/acprof/host/detect.py +669 -0
  104. acprof/_bundle/acprof/host/docker_runtime.py +359 -0
  105. acprof/_bundle/acprof/host/doctor.py +158 -0
  106. acprof/_bundle/acprof/host/env_utils.py +233 -0
  107. acprof/_bundle/acprof/host/execution_conditions.py +87 -0
  108. acprof/_bundle/acprof/host/gpu_device.py +66 -0
  109. acprof/_bundle/acprof/host/hardware_conditions.py +130 -0
  110. acprof/_bundle/acprof/host/images/__init__.py +1 -0
  111. acprof/_bundle/acprof/host/images/dependencies.py +169 -0
  112. acprof/_bundle/acprof/host/images/environment.py +313 -0
  113. acprof/_bundle/acprof/host/images/graph.py +143 -0
  114. acprof/_bundle/acprof/host/images/identity.py +6 -0
  115. acprof/_bundle/acprof/host/images/inventory.py +445 -0
  116. acprof/_bundle/acprof/host/images/runtime.py +457 -0
  117. acprof/_bundle/acprof/host/images/scan.py +154 -0
  118. acprof/_bundle/acprof/host/input_plan.py +1056 -0
  119. acprof/_bundle/acprof/host/interface_probe.py +141 -0
  120. acprof/_bundle/acprof/host/largest_scale_probe.py +566 -0
  121. acprof/_bundle/acprof/host/matrix_plan.py +98 -0
  122. acprof/_bundle/acprof/host/measurement/__init__.py +1 -0
  123. acprof/_bundle/acprof/host/measurement/boundary.py +162 -0
  124. acprof/_bundle/acprof/host/measurement/capture.py +139 -0
  125. acprof/_bundle/acprof/host/measurement/checkpoint.py +275 -0
  126. acprof/_bundle/acprof/host/measurement/config.py +212 -0
  127. acprof/_bundle/acprof/host/measurement/diagnostics.py +247 -0
  128. acprof/_bundle/acprof/host/measurement/energy_validation.py +172 -0
  129. acprof/_bundle/acprof/host/measurement/errors.py +25 -0
  130. acprof/_bundle/acprof/host/measurement/execution.py +396 -0
  131. acprof/_bundle/acprof/host/measurement/load.py +237 -0
  132. acprof/_bundle/acprof/host/measurement/metrics.py +975 -0
  133. acprof/_bundle/acprof/host/measurement/monitors.py +143 -0
  134. acprof/_bundle/acprof/host/measurement/publication.py +171 -0
  135. acprof/_bundle/acprof/host/measurement/requests.py +97 -0
  136. acprof/_bundle/acprof/host/measurement/types.py +82 -0
  137. acprof/_bundle/acprof/host/model_coverage.py +337 -0
  138. acprof/_bundle/acprof/host/model_errors.py +116 -0
  139. acprof/_bundle/acprof/host/model_inspection.py +107 -0
  140. acprof/_bundle/acprof/host/model_schema.py +35 -0
  141. acprof/_bundle/acprof/host/model_store.py +733 -0
  142. acprof/_bundle/acprof/host/network_preflight.py +229 -0
  143. acprof/_bundle/acprof/host/orchestrator.py +1133 -0
  144. acprof/_bundle/acprof/host/permissions.py +117 -0
  145. acprof/_bundle/acprof/host/platform_metadata.py +63 -0
  146. acprof/_bundle/acprof/host/posthoc/__init__.py +12 -0
  147. acprof/_bundle/acprof/host/posthoc/backfill.py +415 -0
  148. acprof/_bundle/acprof/host/posthoc/context.py +400 -0
  149. acprof/_bundle/acprof/host/posthoc/plans.py +597 -0
  150. acprof/_bundle/acprof/host/posthoc/service.py +303 -0
  151. acprof/_bundle/acprof/host/posthoc/storage.py +491 -0
  152. acprof/_bundle/acprof/host/preflight.py +370 -0
  153. acprof/_bundle/acprof/host/profiling/__init__.py +1 -0
  154. acprof/_bundle/acprof/host/profiling/compute.py +436 -0
  155. acprof/_bundle/acprof/host/profiling/compute_plan.py +261 -0
  156. acprof/_bundle/acprof/host/profiling/execution.py +547 -0
  157. acprof/_bundle/acprof/host/profiling/execution_plan.py +576 -0
  158. acprof/_bundle/acprof/host/profiling/progress.py +83 -0
  159. acprof/_bundle/acprof/host/profiling/support.py +252 -0
  160. acprof/_bundle/acprof/host/profiling/tools/__init__.py +1 -0
  161. acprof/_bundle/acprof/host/profiling/tools/advisor.py +164 -0
  162. acprof/_bundle/acprof/host/profiling/tools/compute_parsers.py +409 -0
  163. acprof/_bundle/acprof/host/profiling/tools/execution_environment.py +135 -0
  164. acprof/_bundle/acprof/host/profiling/tools/execution_parsers.py +364 -0
  165. acprof/_bundle/acprof/host/profiling/tools/massif.py +418 -0
  166. acprof/_bundle/acprof/host/profiling/tools/ncu.py +696 -0
  167. acprof/_bundle/acprof/host/profiling/tools/nsys.py +326 -0
  168. acprof/_bundle/acprof/host/profiling/tools/tool_discovery.py +229 -0
  169. acprof/_bundle/acprof/host/profiling/tools/torch.py +158 -0
  170. acprof/_bundle/acprof/host/result_cleanup.py +65 -0
  171. acprof/_bundle/acprof/host/run_state.py +639 -0
  172. acprof/_bundle/acprof/host/runtime_settings.py +10 -0
  173. acprof/_bundle/acprof/host/runtime_validation.py +302 -0
  174. acprof/_bundle/acprof/host/source_bundle.py +96 -0
  175. acprof/_bundle/acprof/host/startup_probe.py +95 -0
  176. acprof/_bundle/acprof/host/static_metadata.py +936 -0
  177. acprof/_bundle/acprof/host/task_support.py +121 -0
  178. acprof/_bundle/acprof/installation.py +26 -0
  179. acprof/_bundle/acprof/latency_slo.py +62 -0
  180. acprof/_bundle/acprof/messages.py +26 -0
  181. acprof/_bundle/acprof/metric_registry.py +315 -0
  182. acprof/_bundle/acprof/model_candidates.py +297 -0
  183. acprof/_bundle/acprof/model_contract.py +234 -0
  184. acprof/_bundle/acprof/model_dependencies.py +93 -0
  185. acprof/_bundle/acprof/model_dependency_domain.py +45 -0
  186. acprof/_bundle/acprof/model_dependency_eval.py +438 -0
  187. acprof/_bundle/acprof/model_dependency_flow.py +200 -0
  188. acprof/_bundle/acprof/model_evidence.py +115 -0
  189. acprof/_bundle/acprof/model_metadata_analysis.py +156 -0
  190. acprof/_bundle/acprof/model_repository.py +149 -0
  191. acprof/_bundle/acprof/model_resolution.py +129 -0
  192. acprof/_bundle/acprof/model_review.py +155 -0
  193. acprof/_bundle/acprof/model_source_analysis.py +299 -0
  194. acprof/_bundle/acprof/model_spec.py +204 -0
  195. acprof/_bundle/acprof/model_support.py +105 -0
  196. acprof/_bundle/acprof/model_transforms.py +81 -0
  197. acprof/_bundle/acprof/monitors/__init__.py +1 -0
  198. acprof/_bundle/acprof/monitors/common.py +63 -0
  199. acprof/_bundle/acprof/monitors/energy_cpu.py +651 -0
  200. acprof/_bundle/acprof/monitors/energy_nvml.py +381 -0
  201. acprof/_bundle/acprof/monitors/gpu_metrics.py +123 -0
  202. acprof/_bundle/acprof/monitors/interfaces.py +74 -0
  203. acprof/_bundle/acprof/monitors/perf_mips.py +617 -0
  204. acprof/_bundle/acprof/monitors/rapl_topology.py +100 -0
  205. acprof/_bundle/acprof/monitors/resource_metrics.py +472 -0
  206. acprof/_bundle/acprof/monitors/resource_readers.py +435 -0
  207. acprof/_bundle/acprof/monitors/resource_usage.py +450 -0
  208. acprof/_bundle/acprof/monitors/sampling.py +79 -0
  209. acprof/_bundle/acprof/network_policy.py +182 -0
  210. acprof/_bundle/acprof/notifications.py +279 -0
  211. acprof/_bundle/acprof/packet/__init__.py +1 -0
  212. acprof/_bundle/acprof/packet/merge_packet_latency.py +369 -0
  213. acprof/_bundle/acprof/packet/sniff_parse_pcap.py +240 -0
  214. acprof/_bundle/acprof/pixel_metrics.py +103 -0
  215. acprof/_bundle/acprof/platform.py +180 -0
  216. acprof/_bundle/acprof/plotting/__init__.py +1 -0
  217. acprof/_bundle/acprof/plotting/config.py +487 -0
  218. acprof/_bundle/acprof/plotting/data.py +236 -0
  219. acprof/_bundle/acprof/plotting/diagnostics.py +747 -0
  220. acprof/_bundle/acprof/plotting/latency.py +638 -0
  221. acprof/_bundle/acprof/plotting/metrics.py +648 -0
  222. acprof/_bundle/acprof/plotting/plotly-LICENSE.txt +21 -0
  223. acprof/_bundle/acprof/plotting/report.py +66 -0
  224. acprof/_bundle/acprof/plotting/styles.py +111 -0
  225. acprof/_bundle/acprof/precision.py +42 -0
  226. acprof/_bundle/acprof/preparation_events.py +78 -0
  227. acprof/_bundle/acprof/progress_events.py +105 -0
  228. acprof/_bundle/acprof/quality.py +153 -0
  229. acprof/_bundle/acprof/resource_budget.py +29 -0
  230. acprof/_bundle/acprof/result_csv.py +263 -0
  231. acprof/_bundle/acprof/result_layers.py +365 -0
  232. acprof/_bundle/acprof/run_args.py +223 -0
  233. acprof/_bundle/acprof/run_options.py +225 -0
  234. acprof/_bundle/acprof/runtime_dependencies.py +195 -0
  235. acprof/_bundle/acprof/runtime_profiles.py +258 -0
  236. acprof/_bundle/acprof/runtime_settings.py +72 -0
  237. acprof/_bundle/acprof/source_identity.py +68 -0
  238. acprof/_bundle/acprof/telemetry.py +225 -0
  239. acprof/_bundle/acprof/testing/__init__.py +1 -0
  240. acprof/_bundle/acprof/testing/evidence.py +117 -0
  241. acprof/_bundle/acprof/testing/plugin.py +126 -0
  242. acprof/_bundle/acprof/testing/provenance.py +53 -0
  243. acprof/_bundle/acprof/testing/sharding.py +14 -0
  244. acprof/_bundle/acprof/tui/__init__.py +1 -0
  245. acprof/_bundle/acprof/tui/app.py +934 -0
  246. acprof/_bundle/acprof/tui/app_contracts.py +43 -0
  247. acprof/_bundle/acprof/tui/catalog_controller.py +120 -0
  248. acprof/_bundle/acprof/tui/commands.py +209 -0
  249. acprof/_bundle/acprof/tui/configuration_controller.py +436 -0
  250. acprof/_bundle/acprof/tui/diagnostics.py +340 -0
  251. acprof/_bundle/acprof/tui/downloads.py +56 -0
  252. acprof/_bundle/acprof/tui/environment.py +248 -0
  253. acprof/_bundle/acprof/tui/experiment_catalog.py +279 -0
  254. acprof/_bundle/acprof/tui/experiment_controller.py +132 -0
  255. acprof/_bundle/acprof/tui/experiment_picker.py +273 -0
  256. acprof/_bundle/acprof/tui/field_validation.py +62 -0
  257. acprof/_bundle/acprof/tui/i18n.py +57 -0
  258. acprof/_bundle/acprof/tui/image_controller.py +525 -0
  259. acprof/_bundle/acprof/tui/images.py +737 -0
  260. acprof/_bundle/acprof/tui/input.py +191 -0
  261. acprof/_bundle/acprof/tui/localization_controller.py +110 -0
  262. acprof/_bundle/acprof/tui/log.py +310 -0
  263. acprof/_bundle/acprof/tui/model_candidates.py +278 -0
  264. acprof/_bundle/acprof/tui/model_controller.py +111 -0
  265. acprof/_bundle/acprof/tui/model_input.py +12 -0
  266. acprof/_bundle/acprof/tui/model_store.py +166 -0
  267. acprof/_bundle/acprof/tui/preflight_controller.py +271 -0
  268. acprof/_bundle/acprof/tui/preparation.py +269 -0
  269. acprof/_bundle/acprof/tui/presentation.py +46 -0
  270. acprof/_bundle/acprof/tui/process.py +111 -0
  271. acprof/_bundle/acprof/tui/process_controller.py +218 -0
  272. acprof/_bundle/acprof/tui/profile_controller.py +109 -0
  273. acprof/_bundle/acprof/tui/progress.py +195 -0
  274. acprof/_bundle/acprof/tui/recovery.py +114 -0
  275. acprof/_bundle/acprof/tui/recovery_controller.py +169 -0
  276. acprof/_bundle/acprof/tui/rendering.py +75 -0
  277. acprof/_bundle/acprof/tui/reports.py +303 -0
  278. acprof/_bundle/acprof/tui/result_controller.py +279 -0
  279. acprof/_bundle/acprof/tui/review_inputs.py +54 -0
  280. acprof/_bundle/acprof/tui/run_form.py +125 -0
  281. acprof/_bundle/acprof/tui/run_planning.py +97 -0
  282. acprof/_bundle/acprof/tui/run_results.py +175 -0
  283. acprof/_bundle/acprof/tui/scrollbar.py +65 -0
  284. acprof/_bundle/acprof/tui/settings.py +203 -0
  285. acprof/_bundle/acprof/tui/slash_controller.py +95 -0
  286. acprof/_bundle/acprof/tui/stages.py +97 -0
  287. acprof/_bundle/acprof/tui/storage.py +150 -0
  288. acprof/_bundle/acprof/tui/table.py +213 -0
  289. acprof/_bundle/acprof/tui/themes.py +112 -0
  290. acprof/_bundle/acprof/tui/translation_catalog.py +1861 -0
  291. acprof/_bundle/acprof/tui/tui.tcss +373 -0
  292. acprof/_bundle/acprof/tui/views.py +706 -0
  293. acprof/_bundle/acprof/window_records.py +165 -0
  294. acprof/_bundle/acprof/workloads/__init__.py +222 -0
  295. acprof/_bundle/acprof/workloads/audio.py +621 -0
  296. acprof/_bundle/acprof/workloads/contract.py +116 -0
  297. acprof/_bundle/acprof/workloads/cv.py +240 -0
  298. acprof/_bundle/acprof/workloads/diffusion.py +410 -0
  299. acprof/_bundle/acprof/workloads/multimodal.py +329 -0
  300. acprof/_bundle/acprof/workloads/nlp.py +151 -0
  301. acprof/_bundle/acprof/workloads/structured.py +151 -0
  302. acprof/_bundle/acprof/workloads/timeseries.py +69 -0
  303. acprof/_bundle/assets/audio/librispeech-clean-test-en-30s/audio.wav +0 -0
  304. acprof/_bundle/assets/audio/librispeech-clean-test-en-30s/source.json +81 -0
  305. acprof/_bundle/dockerfiles/environment_tools.py +314 -0
  306. acprof/_bundle/dockerfiles/locks/audio-cpu.artifacts.json +503 -0
  307. acprof/_bundle/dockerfiles/locks/audio-cpu.txt +143 -0
  308. acprof/_bundle/dockerfiles/locks/audio-cu124.artifacts.json +600 -0
  309. acprof/_bundle/dockerfiles/locks/audio-cu124.txt +171 -0
  310. acprof/_bundle/dockerfiles/locks/audio-cu128.artifacts.json +544 -0
  311. acprof/_bundle/dockerfiles/locks/audio-cu128.txt +155 -0
  312. acprof/_bundle/dockerfiles/locks/custom-multimodal-cpu.artifacts.json +510 -0
  313. acprof/_bundle/dockerfiles/locks/custom-multimodal-cpu.txt +145 -0
  314. acprof/_bundle/dockerfiles/locks/custom-multimodal-cu124.artifacts.json +607 -0
  315. acprof/_bundle/dockerfiles/locks/custom-multimodal-cu124.txt +173 -0
  316. acprof/_bundle/dockerfiles/locks/custom-multimodal-cu128.artifacts.json +642 -0
  317. acprof/_bundle/dockerfiles/locks/custom-multimodal-cu128.txt +183 -0
  318. acprof/_bundle/dockerfiles/locks/cv-cpu.artifacts.json +279 -0
  319. acprof/_bundle/dockerfiles/locks/cv-cpu.txt +79 -0
  320. acprof/_bundle/dockerfiles/locks/cv-cu124.artifacts.json +376 -0
  321. acprof/_bundle/dockerfiles/locks/cv-cu124.txt +107 -0
  322. acprof/_bundle/dockerfiles/locks/cv-cu128.artifacts.json +411 -0
  323. acprof/_bundle/dockerfiles/locks/cv-cu128.txt +117 -0
  324. acprof/_bundle/dockerfiles/locks/diffusion-cpu.artifacts.json +335 -0
  325. acprof/_bundle/dockerfiles/locks/diffusion-cpu.txt +95 -0
  326. acprof/_bundle/dockerfiles/locks/diffusion-cu124.artifacts.json +432 -0
  327. acprof/_bundle/dockerfiles/locks/diffusion-cu124.txt +123 -0
  328. acprof/_bundle/dockerfiles/locks/diffusion-cu128.artifacts.json +467 -0
  329. acprof/_bundle/dockerfiles/locks/diffusion-cu128.txt +133 -0
  330. acprof/_bundle/dockerfiles/locks/moss-transformers5104.artifacts.json +621 -0
  331. acprof/_bundle/dockerfiles/locks/moss-transformers5104.txt +177 -0
  332. acprof/_bundle/dockerfiles/locks/multimodal-transformers4576.artifacts.json +635 -0
  333. acprof/_bundle/dockerfiles/locks/multimodal-transformers4576.txt +181 -0
  334. acprof/_bundle/dockerfiles/locks/nlp-cpu.artifacts.json +363 -0
  335. acprof/_bundle/dockerfiles/locks/nlp-cpu.txt +103 -0
  336. acprof/_bundle/dockerfiles/locks/nlp-cu124.artifacts.json +460 -0
  337. acprof/_bundle/dockerfiles/locks/nlp-cu124.txt +131 -0
  338. acprof/_bundle/dockerfiles/locks/nlp-cu128.artifacts.json +495 -0
  339. acprof/_bundle/dockerfiles/locks/nlp-cu128.txt +141 -0
  340. acprof/_bundle/dockerfiles/locks/onnxruntime-cpu.artifacts.json +261 -0
  341. acprof/_bundle/dockerfiles/locks/onnxruntime-cpu.txt +73 -0
  342. acprof/_bundle/dockerfiles/locks/platform-cpu.artifacts.json +99 -0
  343. acprof/_bundle/dockerfiles/locks/platform-cpu.txt +27 -0
  344. acprof/_bundle/dockerfiles/locks/platform-cu124.artifacts.json +196 -0
  345. acprof/_bundle/dockerfiles/locks/platform-cu124.txt +55 -0
  346. acprof/_bundle/dockerfiles/locks/platform-cu128.artifacts.json +231 -0
  347. acprof/_bundle/dockerfiles/locks/platform-cu128.txt +65 -0
  348. acprof/_bundle/dockerfiles/locks/platform-python-cpu.artifacts.json +37 -0
  349. acprof/_bundle/dockerfiles/locks/platform-python-cpu.txt +9 -0
  350. acprof/_bundle/dockerfiles/locks/structured-cpu.artifacts.json +279 -0
  351. acprof/_bundle/dockerfiles/locks/structured-cpu.txt +79 -0
  352. acprof/_bundle/dockerfiles/locks/structured-cu124.artifacts.json +376 -0
  353. acprof/_bundle/dockerfiles/locks/structured-cu124.txt +107 -0
  354. acprof/_bundle/dockerfiles/locks/structured-cu128.artifacts.json +411 -0
  355. acprof/_bundle/dockerfiles/locks/structured-cu128.txt +117 -0
  356. acprof/_bundle/dockerfiles/locks/system-trixie-amd64.json +620 -0
  357. acprof/_bundle/dockerfiles/locks/timeseries-cpu.artifacts.json +314 -0
  358. acprof/_bundle/dockerfiles/locks/timeseries-cpu.txt +89 -0
  359. acprof/_bundle/dockerfiles/locks/timeseries-cu124.artifacts.json +411 -0
  360. acprof/_bundle/dockerfiles/locks/timeseries-cu124.txt +117 -0
  361. acprof/_bundle/dockerfiles/locks/timeseries-cu128.artifacts.json +446 -0
  362. acprof/_bundle/dockerfiles/locks/timeseries-cu128.txt +127 -0
  363. acprof/_bundle/dockerfiles/locks/transformers5104-cpu.artifacts.json +594 -0
  364. acprof/_bundle/dockerfiles/locks/transformers5104-cpu.txt +169 -0
  365. acprof/_bundle/dockerfiles/locks/transformers5104-cu124.artifacts.json +691 -0
  366. acprof/_bundle/dockerfiles/locks/transformers5104-cu124.txt +197 -0
  367. acprof/_bundle/dockerfiles/locks/transformers5104-cu128.artifacts.json +726 -0
  368. acprof/_bundle/dockerfiles/locks/transformers5104-cu128.txt +207 -0
  369. acprof/_bundle/dockerfiles/platform.Dockerfile +22 -0
  370. acprof/_bundle/dockerfiles/requirements/audio.in +5 -0
  371. acprof/_bundle/dockerfiles/requirements/custom-multimodal.in +2 -0
  372. acprof/_bundle/dockerfiles/requirements/cv.in +4 -0
  373. acprof/_bundle/dockerfiles/requirements/diffusion.in +6 -0
  374. acprof/_bundle/dockerfiles/requirements/moss-transformers5104.in +9 -0
  375. acprof/_bundle/dockerfiles/requirements/multimodal-transformers4576.in +3 -0
  376. acprof/_bundle/dockerfiles/requirements/nlp.in +7 -0
  377. acprof/_bundle/dockerfiles/requirements/onnxruntime.in +9 -0
  378. acprof/_bundle/dockerfiles/requirements/service.in +8 -0
  379. acprof/_bundle/dockerfiles/requirements/structured.in +4 -0
  380. acprof/_bundle/dockerfiles/requirements/timeseries.in +2 -0
  381. acprof/_bundle/dockerfiles/requirements/timm.in +1 -0
  382. acprof/_bundle/dockerfiles/requirements/transformers5104.in +4 -0
  383. acprof/_bundle/dockerfiles/runtime-final.Dockerfile +34 -0
  384. acprof/_bundle/dockerfiles/runtime-model.Dockerfile +19 -0
  385. acprof/_bundle/dockerfiles/runtime.Dockerfile +23 -0
  386. acprof/_bundle/examples/coverage/regression.json +17 -0
  387. acprof/_bundle/examples/hardware-validation.json +19 -0
  388. acprof/_bundle/examples/multimodal/ultravox.model.json +29 -0
  389. acprof/_bundle/examples/nlp/smoke.py +111 -0
  390. acprof/_bundle/examples/onnxruntime/fixtures.py +164 -0
  391. acprof/_bundle/examples/onnxruntime/iris.json +16 -0
  392. acprof/_bundle/examples/onnxruntime/iris.model.json +7 -0
  393. acprof/_bundle/examples/onnxruntime/real_models.py +155 -0
  394. acprof/_bundle/examples/onnxruntime/smoke.py +68 -0
  395. acprof/_bundle/examples/structured/chronos_smoke.py +56 -0
  396. acprof/_bundle/examples/structured/export_models.py +71 -0
  397. acprof/_bundle/examples/structured/smoke.py +71 -0
  398. acprof/_bundle/licenses/CC-BY-4.0.txt +156 -0
  399. acprof/analysis/__init__.py +1 -0
  400. acprof/analysis/audit.py +276 -0
  401. acprof/analysis/comparison.py +364 -0
  402. acprof/analysis/compatibility.py +223 -0
  403. acprof/analysis/conditions.py +185 -0
  404. acprof/analysis/independent_comparison.py +191 -0
  405. acprof/analysis/latency_model.py +1106 -0
  406. acprof/analysis/latency_report.py +577 -0
  407. acprof/analysis/model.py +276 -0
  408. acprof/analysis/precision.py +51 -0
  409. acprof/analysis/uncertainty.py +144 -0
  410. acprof/application/__init__.py +1 -0
  411. acprof/application/download.py +14 -0
  412. acprof/application/errors.py +5 -0
  413. acprof/application/notifications.py +302 -0
  414. acprof/application/preparation.py +333 -0
  415. acprof/application/resources.py +66 -0
  416. acprof/application/run.py +462 -0
  417. acprof/application/terminal_log.py +136 -0
  418. acprof/artifact_layout.py +261 -0
  419. acprof/artifacts.py +161 -0
  420. acprof/capabilities.py +404 -0
  421. acprof/cli/__init__.py +1 -0
  422. acprof/cli/audit.py +74 -0
  423. acprof/cli/auto.py +41 -0
  424. acprof/cli/backfill_compute.py +498 -0
  425. acprof/cli/compare.py +34 -0
  426. acprof/cli/coverage.py +70 -0
  427. acprof/cli/doctor.py +47 -0
  428. acprof/cli/inspect.py +101 -0
  429. acprof/cli/load.py +190 -0
  430. acprof/cli/main.py +64 -0
  431. acprof/cli/model_store.py +22 -0
  432. acprof/cli/plot.py +211 -0
  433. acprof/cli/posthoc.py +113 -0
  434. acprof/cli/probe.py +186 -0
  435. acprof/cli/report.py +35 -0
  436. acprof/cli/results.py +37 -0
  437. acprof/cli/run.py +54 -0
  438. acprof/cli/stats.py +94 -0
  439. acprof/cli/tui.py +65 -0
  440. acprof/config.py +177 -0
  441. acprof/container/__init__.py +1 -0
  442. acprof/container/compat/__init__.py +1 -0
  443. acprof/container/compat/transformers_dynamic.py +54 -0
  444. acprof/container/compat/transformers_model_types.json +1091 -0
  445. acprof/container/compute_profile_runner.py +392 -0
  446. acprof/container/download_model.py +165 -0
  447. acprof/container/execution.py +73 -0
  448. acprof/container/handlers/__init__.py +298 -0
  449. acprof/container/handlers/audio.py +743 -0
  450. acprof/container/handlers/custom_pipeline.py +74 -0
  451. acprof/container/handlers/cv.py +321 -0
  452. acprof/container/handlers/diffusion.py +728 -0
  453. acprof/container/handlers/moss.py +64 -0
  454. acprof/container/handlers/multimodal.py +609 -0
  455. acprof/container/handlers/nlp.py +747 -0
  456. acprof/container/handlers/onnx_tasks.py +231 -0
  457. acprof/container/handlers/onnxruntime.py +90 -0
  458. acprof/container/handlers/structured.py +185 -0
  459. acprof/container/handlers/timeseries.py +178 -0
  460. acprof/container/load_policy.py +81 -0
  461. acprof/container/loading_quality.py +48 -0
  462. acprof/container/local_pipeline.py +31 -0
  463. acprof/container/model_files.py +280 -0
  464. acprof/container/model_probe.py +80 -0
  465. acprof/container/onnx_session.py +150 -0
  466. acprof/container/runtime_manifest.py +150 -0
  467. acprof/container/runtime_validate.py +156 -0
  468. acprof/container/server.py +209 -0
  469. acprof/container/torch_execution.py +55 -0
  470. acprof/container/validation.py +172 -0
  471. acprof/cpu_affinity.py +35 -0
  472. acprof/dependency_locks.py +129 -0
  473. acprof/experiment.py +356 -0
  474. acprof/extensions/__init__.py +294 -0
  475. acprof/extensions/builtin/manifest.json +3591 -0
  476. acprof/extensions/onnxruntime/manifest.json +191 -0
  477. acprof/extensions/schema.py +238 -0
  478. acprof/extensions/transformers/4.57.6.json +1639 -0
  479. acprof/extensions/transformers/5.10.4.json +1812 -0
  480. acprof/failures.py +115 -0
  481. acprof/hardware_conditions.py +18 -0
  482. acprof/hf_download.py +143 -0
  483. acprof/hf_endpoints.py +62 -0
  484. acprof/hf_transport.py +238 -0
  485. acprof/host/__init__.py +1 -0
  486. acprof/host/automation.py +161 -0
  487. acprof/host/client.py +520 -0
  488. acprof/host/collection_history.py +77 -0
  489. acprof/host/collection_workflow.py +285 -0
  490. acprof/host/command.py +119 -0
  491. acprof/host/container_lifecycle.py +225 -0
  492. acprof/host/container_state.py +128 -0
  493. acprof/host/coverage_state.py +259 -0
  494. acprof/host/detect.py +669 -0
  495. acprof/host/docker_runtime.py +359 -0
  496. acprof/host/doctor.py +158 -0
  497. acprof/host/env_utils.py +233 -0
  498. acprof/host/execution_conditions.py +87 -0
  499. acprof/host/gpu_device.py +66 -0
  500. acprof/host/hardware_conditions.py +130 -0
  501. acprof/host/images/__init__.py +1 -0
  502. acprof/host/images/dependencies.py +169 -0
  503. acprof/host/images/environment.py +313 -0
  504. acprof/host/images/graph.py +143 -0
  505. acprof/host/images/identity.py +6 -0
  506. acprof/host/images/inventory.py +445 -0
  507. acprof/host/images/runtime.py +457 -0
  508. acprof/host/images/scan.py +154 -0
  509. acprof/host/input_plan.py +1056 -0
  510. acprof/host/interface_probe.py +141 -0
  511. acprof/host/largest_scale_probe.py +566 -0
  512. acprof/host/matrix_plan.py +98 -0
  513. acprof/host/measurement/__init__.py +1 -0
  514. acprof/host/measurement/boundary.py +162 -0
  515. acprof/host/measurement/capture.py +139 -0
  516. acprof/host/measurement/checkpoint.py +275 -0
  517. acprof/host/measurement/config.py +212 -0
  518. acprof/host/measurement/diagnostics.py +247 -0
  519. acprof/host/measurement/energy_validation.py +172 -0
  520. acprof/host/measurement/errors.py +25 -0
  521. acprof/host/measurement/execution.py +396 -0
  522. acprof/host/measurement/load.py +237 -0
  523. acprof/host/measurement/metrics.py +975 -0
  524. acprof/host/measurement/monitors.py +143 -0
  525. acprof/host/measurement/publication.py +171 -0
  526. acprof/host/measurement/requests.py +97 -0
  527. acprof/host/measurement/types.py +82 -0
  528. acprof/host/model_coverage.py +337 -0
  529. acprof/host/model_errors.py +116 -0
  530. acprof/host/model_inspection.py +107 -0
  531. acprof/host/model_schema.py +35 -0
  532. acprof/host/model_store.py +733 -0
  533. acprof/host/network_preflight.py +229 -0
  534. acprof/host/orchestrator.py +1133 -0
  535. acprof/host/permissions.py +117 -0
  536. acprof/host/platform_metadata.py +63 -0
  537. acprof/host/posthoc/__init__.py +12 -0
  538. acprof/host/posthoc/backfill.py +415 -0
  539. acprof/host/posthoc/context.py +400 -0
  540. acprof/host/posthoc/plans.py +597 -0
  541. acprof/host/posthoc/service.py +303 -0
  542. acprof/host/posthoc/storage.py +491 -0
  543. acprof/host/preflight.py +370 -0
  544. acprof/host/profiling/__init__.py +1 -0
  545. acprof/host/profiling/compute.py +436 -0
  546. acprof/host/profiling/compute_plan.py +261 -0
  547. acprof/host/profiling/execution.py +547 -0
  548. acprof/host/profiling/execution_plan.py +576 -0
  549. acprof/host/profiling/progress.py +83 -0
  550. acprof/host/profiling/support.py +252 -0
  551. acprof/host/profiling/tools/__init__.py +1 -0
  552. acprof/host/profiling/tools/advisor.py +164 -0
  553. acprof/host/profiling/tools/compute_parsers.py +409 -0
  554. acprof/host/profiling/tools/execution_environment.py +135 -0
  555. acprof/host/profiling/tools/execution_parsers.py +364 -0
  556. acprof/host/profiling/tools/massif.py +418 -0
  557. acprof/host/profiling/tools/ncu.py +696 -0
  558. acprof/host/profiling/tools/nsys.py +326 -0
  559. acprof/host/profiling/tools/tool_discovery.py +229 -0
  560. acprof/host/profiling/tools/torch.py +158 -0
  561. acprof/host/result_cleanup.py +65 -0
  562. acprof/host/run_state.py +639 -0
  563. acprof/host/runtime_settings.py +10 -0
  564. acprof/host/runtime_validation.py +302 -0
  565. acprof/host/source_bundle.py +96 -0
  566. acprof/host/startup_probe.py +95 -0
  567. acprof/host/static_metadata.py +936 -0
  568. acprof/host/task_support.py +121 -0
  569. acprof/installation.py +26 -0
  570. acprof/latency_slo.py +62 -0
  571. acprof/messages.py +26 -0
  572. acprof/metric_registry.py +315 -0
  573. acprof/model_candidates.py +297 -0
  574. acprof/model_contract.py +234 -0
  575. acprof/model_dependencies.py +93 -0
  576. acprof/model_dependency_domain.py +45 -0
  577. acprof/model_dependency_eval.py +438 -0
  578. acprof/model_dependency_flow.py +200 -0
  579. acprof/model_evidence.py +115 -0
  580. acprof/model_metadata_analysis.py +156 -0
  581. acprof/model_repository.py +149 -0
  582. acprof/model_resolution.py +129 -0
  583. acprof/model_review.py +155 -0
  584. acprof/model_source_analysis.py +299 -0
  585. acprof/model_spec.py +204 -0
  586. acprof/model_support.py +105 -0
  587. acprof/model_transforms.py +81 -0
  588. acprof/monitors/__init__.py +1 -0
  589. acprof/monitors/common.py +63 -0
  590. acprof/monitors/energy_cpu.py +651 -0
  591. acprof/monitors/energy_nvml.py +381 -0
  592. acprof/monitors/gpu_metrics.py +123 -0
  593. acprof/monitors/interfaces.py +74 -0
  594. acprof/monitors/perf_mips.py +617 -0
  595. acprof/monitors/rapl_topology.py +100 -0
  596. acprof/monitors/resource_metrics.py +472 -0
  597. acprof/monitors/resource_readers.py +435 -0
  598. acprof/monitors/resource_usage.py +450 -0
  599. acprof/monitors/sampling.py +79 -0
  600. acprof/network_policy.py +182 -0
  601. acprof/notifications.py +279 -0
  602. acprof/packet/__init__.py +1 -0
  603. acprof/packet/merge_packet_latency.py +369 -0
  604. acprof/packet/sniff_parse_pcap.py +240 -0
  605. acprof/pixel_metrics.py +103 -0
  606. acprof/platform.py +180 -0
  607. acprof/plotting/__init__.py +1 -0
  608. acprof/plotting/config.py +487 -0
  609. acprof/plotting/data.py +236 -0
  610. acprof/plotting/diagnostics.py +747 -0
  611. acprof/plotting/latency.py +638 -0
  612. acprof/plotting/metrics.py +648 -0
  613. acprof/plotting/plotly-LICENSE.txt +21 -0
  614. acprof/plotting/report.css +61 -0
  615. acprof/plotting/report.html +68 -0
  616. acprof/plotting/report.js +295 -0
  617. acprof/plotting/report.py +66 -0
  618. acprof/plotting/styles.py +111 -0
  619. acprof/plotting/view_model.js +145 -0
  620. acprof/precision.py +42 -0
  621. acprof/preparation_events.py +78 -0
  622. acprof/progress_events.py +105 -0
  623. acprof/quality.py +153 -0
  624. acprof/resource_budget.py +29 -0
  625. acprof/result_csv.py +263 -0
  626. acprof/result_layers.py +365 -0
  627. acprof/run_args.py +223 -0
  628. acprof/run_options.py +225 -0
  629. acprof/runtime_dependencies.py +195 -0
  630. acprof/runtime_profiles.py +258 -0
  631. acprof/runtime_settings.py +72 -0
  632. acprof/source_identity.py +68 -0
  633. acprof/telemetry.py +225 -0
  634. acprof/testing/__init__.py +1 -0
  635. acprof/testing/evidence.py +117 -0
  636. acprof/testing/plugin.py +126 -0
  637. acprof/testing/provenance.py +53 -0
  638. acprof/testing/sharding.py +14 -0
  639. acprof/tui/__init__.py +1 -0
  640. acprof/tui/app.py +934 -0
  641. acprof/tui/app_contracts.py +43 -0
  642. acprof/tui/catalog_controller.py +120 -0
  643. acprof/tui/commands.py +209 -0
  644. acprof/tui/configuration_controller.py +436 -0
  645. acprof/tui/diagnostics.py +340 -0
  646. acprof/tui/downloads.py +56 -0
  647. acprof/tui/environment.py +248 -0
  648. acprof/tui/experiment_catalog.py +279 -0
  649. acprof/tui/experiment_controller.py +132 -0
  650. acprof/tui/experiment_picker.py +273 -0
  651. acprof/tui/field_validation.py +62 -0
  652. acprof/tui/i18n.py +57 -0
  653. acprof/tui/image_controller.py +525 -0
  654. acprof/tui/images.py +737 -0
  655. acprof/tui/input.py +191 -0
  656. acprof/tui/localization_controller.py +110 -0
  657. acprof/tui/log.py +310 -0
  658. acprof/tui/model_candidates.py +278 -0
  659. acprof/tui/model_controller.py +111 -0
  660. acprof/tui/model_input.py +12 -0
  661. acprof/tui/model_store.py +166 -0
  662. acprof/tui/preflight_controller.py +271 -0
  663. acprof/tui/preparation.py +269 -0
  664. acprof/tui/presentation.py +46 -0
  665. acprof/tui/process.py +111 -0
  666. acprof/tui/process_controller.py +218 -0
  667. acprof/tui/profile_controller.py +109 -0
  668. acprof/tui/progress.py +195 -0
  669. acprof/tui/recovery.py +114 -0
  670. acprof/tui/recovery_controller.py +169 -0
  671. acprof/tui/rendering.py +75 -0
  672. acprof/tui/reports.py +303 -0
  673. acprof/tui/result_controller.py +279 -0
  674. acprof/tui/review_inputs.py +54 -0
  675. acprof/tui/run_form.py +125 -0
  676. acprof/tui/run_planning.py +97 -0
  677. acprof/tui/run_results.py +175 -0
  678. acprof/tui/scrollbar.py +65 -0
  679. acprof/tui/settings.py +203 -0
  680. acprof/tui/slash_controller.py +95 -0
  681. acprof/tui/stages.py +97 -0
  682. acprof/tui/storage.py +150 -0
  683. acprof/tui/table.py +213 -0
  684. acprof/tui/themes.py +112 -0
  685. acprof/tui/translation_catalog.py +1861 -0
  686. acprof/tui/tui.tcss +373 -0
  687. acprof/tui/views.py +706 -0
  688. acprof/window_records.py +165 -0
  689. acprof/workloads/__init__.py +222 -0
  690. acprof/workloads/audio.py +621 -0
  691. acprof/workloads/contract.py +116 -0
  692. acprof/workloads/cv.py +240 -0
  693. acprof/workloads/diffusion.py +410 -0
  694. acprof/workloads/multimodal.py +329 -0
  695. acprof/workloads/nlp.py +151 -0
  696. acprof/workloads/structured.py +151 -0
  697. acprof/workloads/timeseries.py +69 -0
  698. acprof-0.3.0.dist-info/METADATA +117 -0
  699. acprof-0.3.0.dist-info/RECORD +704 -0
  700. acprof-0.3.0.dist-info/WHEEL +4 -0
  701. acprof-0.3.0.dist-info/entry_points.txt +2 -0
  702. acprof-0.3.0.dist-info/licenses/LICENSE +202 -0
  703. acprof-0.3.0.dist-info/licenses/NOTICE +43 -0
  704. acprof-0.3.0.dist-info/licenses/licenses/CC-BY-4.0.txt +156 -0
@@ -0,0 +1,639 @@
1
+ """主实验状态、目录互斥与 case 恢复;所有操作均在测量窗口之外。"""
2
+ from __future__ import annotations
3
+
4
+ import hashlib
5
+ import importlib.metadata
6
+ import json
7
+ import math
8
+ import os
9
+ import platform
10
+ import re
11
+ import shutil
12
+ import stat
13
+ from dataclasses import asdict
14
+ from datetime import UTC, datetime
15
+ from pathlib import Path
16
+ from typing import TextIO
17
+ from uuid import uuid4
18
+
19
+ from acprof.artifact_layout import ArtifactLayout
20
+ from acprof.artifacts import atomic_write_json, file_sha256, replace_file_durably, sync_directory
21
+ from acprof.host.execution_conditions import measurement_environment
22
+ from acprof.messages import Message, join_messages, message
23
+ from acprof.platform import detect_environment
24
+ from acprof.result_csv import expected_measurements, read_result_csv
25
+ from acprof.run_args import flatten_run_options
26
+ from acprof.source_identity import measurement_sources, source_fingerprint
27
+
28
+ RUN_STATE_NAME = "run_state.json"
29
+ RESULT_LOCK_NAME = ".acprof-result.lock"
30
+ MAX_RUN_STATE_BYTES = 4 * 1024 * 1024
31
+ # Native Linux only. Never derive this machine-wide per-user namespace from TMPDIR.
32
+ # Test runners inject an isolated directory in-process, not via a production env option.
33
+ MEASUREMENT_LOCK_ROOT = Path("/tmp")
34
+
35
+
36
+ def _sync_directory_chain(directory: Path, root: Path) -> None:
37
+ """Persist recovery-directory entries from a copied file up to the experiment root."""
38
+ directory = directory.absolute()
39
+ root = root.absolute()
40
+ if not directory.is_relative_to(root):
41
+ raise ValueError(f"Recovery directory escapes experiment root: {directory}")
42
+ while True:
43
+ sync_directory(directory)
44
+ if directory == root:
45
+ return
46
+ directory = directory.parent
47
+
48
+
49
+ class RunStateError(RuntimeError):
50
+ pass
51
+
52
+
53
+ def _validate_lock_descriptor(descriptor: int, *, label: str) -> None:
54
+ info = os.fstat(descriptor)
55
+ if not stat.S_ISREG(info.st_mode) or info.st_uid != os.getuid() or info.st_nlink != 1:
56
+ raise OSError(f"{label} lock is not a regular file owned only by this user")
57
+
58
+
59
+ def check_result_directory_idle(directory: str | Path) -> None:
60
+ """Fail closed when an existing result-directory lock is active or malformed."""
61
+ import fcntl
62
+
63
+ path = ArtifactLayout.discover(directory).path(RESULT_LOCK_NAME)
64
+ try:
65
+ descriptor = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC)
66
+ except FileNotFoundError:
67
+ return
68
+ except OSError as exc:
69
+ raise RunStateError(f"无法检查结果目录锁;锁文件不可用:{path} ({exc})") from exc
70
+ try:
71
+ _validate_lock_descriptor(descriptor, label="result")
72
+ fcntl.flock(descriptor, fcntl.LOCK_SH | fcntl.LOCK_NB)
73
+ except BlockingIOError as exc:
74
+ raise RunStateError(message("原实验仍有进程写入,请等待其退出后再恢复。")) from exc
75
+ except OSError as exc:
76
+ raise RunStateError(f"无法检查结果目录锁;锁文件不可用:{path} ({exc})") from exc
77
+ finally:
78
+ os.close(descriptor)
79
+
80
+
81
+ def _finite_json_number(raw: str) -> float:
82
+ value = float(raw)
83
+ if not math.isfinite(value):
84
+ raise ValueError(message("run_state.json 包含非有限数值"))
85
+ return value
86
+
87
+
88
+ def utc_now() -> str:
89
+ return datetime.now(UTC).isoformat()
90
+
91
+
92
+ def host_identity(project_dir: str | Path) -> dict:
93
+ source_hash = source_fingerprint(project_dir, measurement_sources(project_dir),
94
+ scope="measurement-source-v2")
95
+ packages = sorted((dist.metadata["Name"] or "", dist.version)
96
+ for dist in importlib.metadata.distributions())
97
+ machine_id = Path("/etc/machine-id")
98
+ return {
99
+ **detect_environment().metadata(),
100
+ "machine": platform.machine(), "kernel": platform.release(), "hostname": platform.node(),
101
+ "machine_id_sha256": file_sha256(machine_id) if machine_id.is_file() else None,
102
+ "python": platform.python_version(), "source_sha256": source_hash,
103
+ "packages_sha256": hashlib.sha256(json.dumps(packages).encode()).hexdigest(),
104
+ }
105
+
106
+
107
+ def run_options(args) -> dict:
108
+ from acprof.host.gpu_device import selected_gpu_device
109
+ ignored = {"resume", "rebuild_image", "output_dir", "notify"}
110
+ options = {name: value for name, value in flatten_run_options(vars(args)).items() if name not in ignored}
111
+ # Freeze even default policies: they affect both timing and retained evidence.
112
+ options.setdefault("sampling_policy", "burst")
113
+ options.setdefault("raw_sample_limit", 100000)
114
+ from acprof.model_repository import model_source
115
+ source = model_source(options.get("model_source"))
116
+ if source == "huggingface":
117
+ options.pop("model_source", None) # Preserve historical HF resume options.
118
+ else:
119
+ options["model_source"] = source
120
+ if options.get("revision") is None:
121
+ options.pop("revision", None)
122
+ if options.get("input_scale_policy", "auto") == "auto":
123
+ options.pop("input_scale_policy", None)
124
+ device = selected_gpu_device()
125
+ if device:
126
+ options["gpu_device"] = device["uuid"]
127
+ elif options.get("gpu_device") is None:
128
+ options.pop("gpu_device", None)
129
+ from acprof.cpu_affinity import normalize_cpu_set
130
+ cpuset = normalize_cpu_set(options.get("cpuset_cpus", ""))
131
+ if cpuset:
132
+ options["cpuset_cpus"] = cpuset
133
+ else:
134
+ options.pop("cpuset_cpus", None)
135
+ for name in ("cpus", "mems", "gpus"):
136
+ options[name] = ",".join(part.strip().lower() for part in options[name].split(",") if part.strip())
137
+ # New options only enter pre-execution identity when explicitly requested.
138
+ # Absent defaults retain the old serialized options; no observed values enter here.
139
+ options["measurement_environment"] = measurement_environment()
140
+ if options.get("workload_spec"):
141
+ path = Path(options["workload_spec"]).expanduser().resolve()
142
+ options["workload_spec"] = str(path)
143
+ options["workload_spec_sha256"] = file_sha256(path)
144
+ if options.get("model_spec"):
145
+ path = Path(options["model_spec"]).expanduser().resolve()
146
+ options["model_spec"] = str(path)
147
+ options["model_spec_sha256"] = file_sha256(path)
148
+ else:
149
+ options.pop("model_spec", None)
150
+ return options
151
+
152
+
153
+ def load_run_state(directory: str | Path) -> dict:
154
+ try:
155
+ layout = ArtifactLayout.discover(directory)
156
+ path = layout.path(RUN_STATE_NAME)
157
+ with path.open("rb") as stream:
158
+ content = stream.read(MAX_RUN_STATE_BYTES + 1)
159
+ if len(content) > MAX_RUN_STATE_BYTES:
160
+ raise ValueError(message("run_state.json 超过 4 MiB 读取上限"))
161
+ try:
162
+ payload = json.loads(content.decode("utf-8"), parse_float=_finite_json_number,
163
+ parse_constant=_finite_json_number)
164
+ except json.JSONDecodeError as exc:
165
+ raise ValueError(message("run_state.json 的 JSON 格式无效")) from exc
166
+ except (OSError, UnicodeError, ValueError, RecursionError) as exc:
167
+ reason = exc.args[0] if exc.args and isinstance(exc.args[0], Message) else str(exc)
168
+ raise RunStateError(message(
169
+ "无法读取恢复状态 {0};历史实验请使用新输出目录:{1}", directory, reason,
170
+ )) from exc
171
+ if not isinstance(payload, dict) or payload.get("schema_version") != 1:
172
+ raise RunStateError(message("不支持的主实验状态格式:{0}", path))
173
+ if payload.get("layout_version", 1) != layout.layout_version:
174
+ raise RunStateError(message("恢复状态与产物布局不一致:{0}", path))
175
+ return payload
176
+
177
+
178
+ def _expected(data: dict, cpu=None, mem=None, gpu=None):
179
+ options = data["options"]
180
+ return expected_measurements(
181
+ [cpu] if cpu is not None else [int(x) for x in options["cpus"].split(",")],
182
+ [mem] if mem is not None else [int(x) for x in options["mems"].split(",")],
183
+ [gpu] if gpu is not None else options["gpus"].split(","),
184
+ data["runtime"]["planned"]["scales"], options["warmup"], options["repeat"],
185
+ )
186
+
187
+
188
+ def recovery_phase(directory: str | Path, data: dict) -> str:
189
+ layout = ArtifactLayout.discover(directory)
190
+ if data.get("status") == "complete":
191
+ return "complete"
192
+ if (data.get("runtime") or data.get("cases") or layout.result_csv.exists()
193
+ or any(layout.root.glob("result_case_*.csv"))
194
+ or any((layout.root / ".acprof/work/cases").glob("*/result.csv"))):
195
+ return "measurement"
196
+ return "preparation"
197
+
198
+
199
+ def validate_resume(directory: str | Path, data: dict, *, project_dir: str | Path,
200
+ options: dict | None = None) -> str:
201
+ """Read-only checks shared by recovery review and the locked writer.
202
+
203
+ Image availability and live hardware preflight remain the runner's responsibility.
204
+ Returning a phase never grants permission to mix changed measurement identities.
205
+ """
206
+ layout = ArtifactLayout.discover(directory)
207
+ issues = []
208
+ if not isinstance(data.get("options"), dict) or not isinstance(data.get("host"), dict):
209
+ raise RunStateError(message("原实验缺少完整的参数或主机身份,无法续跑。"))
210
+ if options is not None:
211
+ previous = {"profiling_mode": "full", **flatten_run_options(data["options"])}
212
+ current = {"profiling_mode": "full", **flatten_run_options(options)}
213
+ changed = sorted(name for name in previous.keys() | current.keys()
214
+ if (name in previous) != (name in current) or previous.get(name) != current.get(name))
215
+ if changed:
216
+ issues.append(message("实验参数已变化:{0}", ", ".join(changed)))
217
+ previous_host, current_host = data["host"], host_identity(project_dir)
218
+ labels = {"source_sha256": "AC-Prof 采集源码", "packages_sha256": "Python 依赖",
219
+ "python": "Python 版本"}
220
+ for name in sorted(previous_host.keys() | current_host.keys()):
221
+ if previous_host.get(name) != current_host.get(name):
222
+ issues.append(message("{0}已变化", message(labels.get(name, "主机身份({0})"), name)))
223
+ if issues:
224
+ raise RunStateError(message("无法续跑;请使用新实验保留原结果。\n{0}", join_messages("\n", issues)))
225
+ if not isinstance(data.get("cases", {}), dict) or not isinstance(data.get("artifacts", {}), dict):
226
+ raise RunStateError(message("原实验的 case 或产物记录无效。"))
227
+ phase = recovery_phase(directory, data)
228
+ complete = phase == "complete"
229
+ if not data.get("runtime") and phase != "preparation":
230
+ raise RunStateError(message("已有测量证据但缺少冻结运行环境,不能按准备失败重试。"))
231
+ # Completed CSVs may have documented posthoc changes; preserve that contract.
232
+ if complete:
233
+ read_result_csv(layout.result_csv, expected=_expected(data))
234
+ for name, digest in data.get("artifacts", {}).items():
235
+ if complete and Path(name).name not in {"matrix_plan.json", "startup_oom_pruning.json"}:
236
+ continue
237
+ path = layout.contained(name)
238
+ if not path.is_file() or file_sha256(path) != digest:
239
+ raise RunStateError(message("恢复产物缺失或已改变:{0};请恢复原文件或创建新实验。", path))
240
+ if not complete:
241
+ for name, record in data.get("cases", {}).items():
242
+ if not isinstance(record, dict):
243
+ raise RunStateError(message("原实验的 case 或产物记录无效。"))
244
+ if record.get("status") == "complete":
245
+ path = layout.contained(name)
246
+ if not path.is_file() or file_sha256(path) != record.get("sha256"):
247
+ raise RunStateError(message("已完成 case 的 CSV 缺失或改变:{0}", path))
248
+ elif record.get("measurement_status") == "complete":
249
+ from acprof.host.measurement.checkpoint import read_measurement_checkpoint
250
+ case = layout.case_from_csv(layout.contained(name))
251
+ cpu, mem, gpu = record["resources"]
252
+ try:
253
+ read_measurement_checkpoint(case, record["measurement_checkpoint"],
254
+ run_id=data["run_id"], attempt_id=record["case_attempt_id"],
255
+ expected=_expected(data, cpu, mem, gpu))
256
+ except (OSError, ValueError, KeyError) as exc:
257
+ raise RunStateError(f"已完成测量的原始证据无效:{case.case_id}: {exc}") from exc
258
+ return phase
259
+
260
+
261
+ class ResultDirectoryLock:
262
+ """Linux advisory lock: process exit releases ownership without stale PID recovery."""
263
+ def __init__(self, directory: str | Path, *, new: bool = False):
264
+ layout = ArtifactLayout.for_new_run(directory) if new else ArtifactLayout.discover(directory)
265
+ self.path = layout.path(RESULT_LOCK_NAME)
266
+ self.stream: TextIO | None = None
267
+
268
+ def __enter__(self):
269
+ import fcntl
270
+
271
+ self.path.parent.mkdir(parents=True, exist_ok=True)
272
+ try:
273
+ descriptor = os.open(
274
+ self.path,
275
+ os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW | os.O_CLOEXEC,
276
+ 0o600,
277
+ )
278
+ self.stream = os.fdopen(descriptor, "a+")
279
+ _validate_lock_descriptor(self.stream.fileno(), label="result")
280
+ fcntl.flock(self.stream.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
281
+ except OSError as exc:
282
+ self.__exit__()
283
+ raise RunStateError(
284
+ "无法取得结果目录锁;另一个采集或补采进程可能正在使用结果目录,"
285
+ f"或锁文件不可用:{self.path} ({exc})"
286
+ ) from exc
287
+ return self
288
+
289
+ def __exit__(self, *_args):
290
+ if self.stream is not None:
291
+ self.stream.close()
292
+ self.stream = None
293
+
294
+
295
+ class MeasurementLock(ResultDirectoryLock):
296
+ """同机同用户的实验串行化,防止跨结果目录共享端口及采样资源。"""
297
+ def __init__(self):
298
+ self.path = MEASUREMENT_LOCK_ROOT / f"acprof-measurement-{os.getuid()}.lock"
299
+ self.stream = None
300
+
301
+ def __enter__(self):
302
+ import fcntl
303
+
304
+ try:
305
+ descriptor = os.open(self.path, os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW | os.O_CLOEXEC, 0o600)
306
+ self.stream = os.fdopen(descriptor, "a+")
307
+ _validate_lock_descriptor(self.stream.fileno(), label="measurement")
308
+ fcntl.flock(self.stream.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
309
+ except OSError as error:
310
+ self.__exit__()
311
+ raise RunStateError(
312
+ "无法取得本机测量锁;同一用户可能已有 AC-Prof 采集或补采运行,"
313
+ f"或锁文件不可用:{self.path} ({error})"
314
+ ) from error
315
+ return self
316
+
317
+
318
+ class RunState:
319
+ def __init__(self, directory: str | Path, options: dict, *, resume: bool, project_dir: str,
320
+ preparation_artifacts: dict[str, str] | None = None):
321
+ self.directory = Path(directory).resolve()
322
+ self.layout = ArtifactLayout.discover(self.directory) if resume else ArtifactLayout.for_new_run(self.directory)
323
+ self.path = self.layout.path(RUN_STATE_NAME)
324
+ self.lock = ResultDirectoryLock(self.directory, new=not resume)
325
+ self.measurement_lock = MeasurementLock()
326
+ self.measurement_lock.__enter__()
327
+ try:
328
+ self.lock.__enter__()
329
+ attempt = {"started_at": utc_now(), "pid": os.getpid(), "resume": resume}
330
+ if resume:
331
+ self.data = load_run_state(self.directory)
332
+ phase = validate_resume(self.directory, self.data, project_dir=project_dir, options=options)
333
+ if phase == "preparation":
334
+ attempt["preparation_backup"] = self._archive_preparation()
335
+ self.data["status"] = "preparing"
336
+ else:
337
+ prepared = preparation_artifacts or {}
338
+ allowed_prepared = {str(self.layout.path(name).relative_to(self.directory))
339
+ for name in ("auto_report.json", "model_resolution.json")}
340
+ if set(prepared) - allowed_prepared:
341
+ raise RunStateError("自动准备产物包含不允许的文件")
342
+ for name, digest in prepared.items():
343
+ path = self.layout.contained(name)
344
+ if not path.is_file() or file_sha256(path) != digest:
345
+ raise RunStateError(f"自动准备产物缺失或已改变:{path}")
346
+ try:
347
+ self.layout.initialize(allowed_files=prepared)
348
+ except ValueError as exc:
349
+ raise RunStateError(str(exc)) from exc
350
+ self.data = {"schema_version": 1, "layout_version": 2, "run_id": uuid4().hex, "created_at": utc_now(),
351
+ "status": "preparing", "options": options, "host": host_identity(project_dir),
352
+ "cases": {}, "artifacts": {}, "attempts": []}
353
+ self.data["attempts"].append(attempt)
354
+ self.save()
355
+ except BaseException:
356
+ self.lock.__exit__(None, None, None)
357
+ self.measurement_lock.__exit__(None, None, None)
358
+ raise
359
+
360
+ @property
361
+ def ready(self) -> bool:
362
+ return "runtime" in self.data
363
+
364
+ @property
365
+ def complete(self) -> bool:
366
+ return self.data.get("status") == "complete"
367
+
368
+ def save(self) -> None:
369
+ self.data["updated_at"] = utc_now()
370
+ atomic_write_json(self.path, self.data)
371
+
372
+ def artifact_path(self, relative: str) -> Path:
373
+ try:
374
+ return self.layout.contained(relative)
375
+ except ValueError as exc:
376
+ raise RunStateError(str(exc)) from exc
377
+
378
+ def _archive_preparation(self) -> str:
379
+ """Copy preparation evidence under the writer lock before any retry overwrites it."""
380
+ names = ("run_state.json", "auto_report.json", "model_resolution.json",
381
+ "interface_validation.json", "runtime_validation.json", "static_meta.json",
382
+ "capability_report.json", "collection_history.json", "input_scale_plan.json",
383
+ "compute_profile_plan.json", "execution_profile_plan.json")
384
+ sources = [self.layout.path(name) for name in names]
385
+ for name in ("logs", "compute_profiles", "execution_profiles"):
386
+ sources.extend(self.layout.path(name).rglob("*"))
387
+ backup = self.layout.path("interrupted_cases").parent / "preparation_attempts" / uuid4().hex
388
+ backup_directories: set[Path] = set()
389
+ for source in sources:
390
+ source = self.artifact_path(str(source.relative_to(self.directory)))
391
+ if not source.exists():
392
+ continue
393
+ if source.is_dir():
394
+ continue
395
+ if not source.is_file():
396
+ raise RunStateError(message("不支持的准备产物类型:{0}", source))
397
+ target = backup / source.relative_to(self.directory)
398
+ target.parent.mkdir(parents=True, exist_ok=True)
399
+ shutil.copy2(source, target)
400
+ with target.open("rb") as stream:
401
+ os.fsync(stream.fileno())
402
+ backup_directories.add(target.parent)
403
+ for directory in sorted(backup_directories, key=lambda path: len(path.parts), reverse=True):
404
+ _sync_directory_chain(directory, self.directory)
405
+ return str(backup.relative_to(self.directory))
406
+
407
+ def bind_matrix_plan(self, plan: dict) -> None:
408
+ from acprof.host.matrix_plan import MATRIX_PLAN_NAME
409
+ from acprof.host.startup_probe import PROBE_NAME
410
+ previous = self.data.get("matrix_plan_sha256")
411
+ if previous is not None and previous != plan["plan_sha256"]:
412
+ raise RunStateError("冻结 matrix plan 身份已改变")
413
+ names = [MATRIX_PLAN_NAME]
414
+ if plan["identity"]["prune_startup_oom"]:
415
+ names.append(PROBE_NAME)
416
+ for name in names:
417
+ name = str(self.layout.path(name).relative_to(self.directory))
418
+ checksum = file_sha256(self.artifact_path(name))
419
+ recorded = self.data["artifacts"].get(name)
420
+ if recorded is not None and recorded != checksum:
421
+ raise RunStateError(f"冻结计划或 probe 证据已改变:{name}")
422
+ self.data["artifacts"][name] = checksum
423
+ self.data["matrix_plan_sha256"] = plan["plan_sha256"]
424
+ self.save()
425
+
426
+ def bind_runtime(self, task, image, planned, compute_plan: str, execution_plan: str) -> None:
427
+ paths = [str(self.layout.path(name).relative_to(self.directory))
428
+ for name in ("static_meta.json", "collection_history.json", "input_scale_plan.json")]
429
+ for value in (compute_plan, execution_plan):
430
+ if value:
431
+ paths.append(str(Path(value).resolve().relative_to(self.directory)))
432
+ for name in paths:
433
+ path = self.artifact_path(name)
434
+ if path.is_file():
435
+ self.data["artifacts"][name] = file_sha256(path)
436
+ plan = asdict(planned)
437
+ if plan["plan_file"]:
438
+ plan["plan_file"] = str(Path(plan["plan_file"]).resolve().relative_to(self.directory))
439
+ self.data["runtime"] = {
440
+ "task": {key: value for key, value in asdict(task).items() if key != "repository_sources"},
441
+ "image": asdict(image), "planned": plan,
442
+ "compute_plan": str(Path(compute_plan).resolve().relative_to(self.directory)) if compute_plan else "",
443
+ "execution_plan": str(Path(execution_plan).resolve().relative_to(self.directory)) if execution_plan else "",
444
+ }
445
+ self.data["status"] = "running"
446
+ self.save()
447
+
448
+ def restore_runtime(self):
449
+ from acprof.host.detect import TaskInfo
450
+ from acprof.host.images.runtime import ImageInfo, require_image_identity
451
+ from acprof.host.input_plan import PlannedInputScales
452
+ snapshot = self.data["runtime"]
453
+ image = ImageInfo(**snapshot["image"])
454
+ if not image.tag.startswith("sha256:"):
455
+ raise RunStateError("恢复要求原实验的不可变 image ID;历史标签镜像请使用新输出目录")
456
+ require_image_identity(image.tag, image.runtime_environment)
457
+ plan = dict(snapshot["planned"])
458
+ if plan.get("plan_file"):
459
+ plan["plan_file"] = str(self.artifact_path(plan["plan_file"]))
460
+ return (TaskInfo(**snapshot["task"]), image, PlannedInputScales(**plan),
461
+ str(self.artifact_path(snapshot["compute_plan"])) if snapshot["compute_plan"] else "",
462
+ str(self.artifact_path(snapshot["execution_plan"])) if snapshot["execution_plan"] else "")
463
+
464
+ def expected(self, cpu=None, mem=None, gpu=None):
465
+ return _expected(self.data, cpu, mem, gpu)
466
+
467
+ def prepare_case(self, filename: str, cpu: int, mem: int, gpu: str) -> str | None:
468
+ path = (self.layout.case("", cpu, mem, gpu).csv if self.layout.layout_version == 2
469
+ else self.artifact_path(filename))
470
+ filename = str(path.relative_to(self.directory))
471
+ case = self.layout.case_from_csv(path)
472
+ record = self.data["cases"].get(filename, {})
473
+ if record.get("status") == "complete":
474
+ if not path.is_file() or file_sha256(path) != record["sha256"]:
475
+ raise RunStateError(f"已完成 case 的 CSV 缺失或改变:{path}")
476
+ read_result_csv(path, expected=self.expected(cpu, mem, gpu))
477
+ return str(path)
478
+ if record.get("case_attempt_id") and record.get("measurement_status") != "complete":
479
+ from acprof.host.measurement.checkpoint import discover_measurement_checkpoint
480
+ try:
481
+ reference = discover_measurement_checkpoint(
482
+ case, run_id=self.data["run_id"], attempt_id=record["case_attempt_id"],
483
+ expected=self.expected(cpu, mem, gpu),
484
+ )
485
+ except (OSError, ValueError, KeyError) as exc:
486
+ raise RunStateError(f"已发布测量快照无法恢复:{exc}") from exc
487
+ if reference is not None:
488
+ record.update(status="measured", measurement_status="complete", processing_status="pending",
489
+ measurement_checkpoint=reference, recovered_checkpoint_at=utc_now())
490
+ self.save()
491
+ if record.get("measurement_status") == "complete":
492
+ self.processing_checkpoint(str(path), cpu, mem, gpu)
493
+ return None
494
+ candidates = [*case.temporary_files(), case.retained_requests, case.idle]
495
+ existing = list(dict.fromkeys(candidate for candidate in candidates if candidate.exists() or candidate.is_symlink()))
496
+ previous_attempts = list(record.get("previous_attempts", []))
497
+ previous = {name: value for name, value in record.items() if name != "previous_attempts"}
498
+ if existing:
499
+ backup = self.layout.path("interrupted_cases") / case.case_id / uuid4().hex
500
+ backup.mkdir(parents=True)
501
+ backup_directories: set[Path] = set()
502
+ # Preserve every source before removing any file, including partial PCAPs.
503
+ for source in existing:
504
+ self.artifact_path(str(source.relative_to(self.directory)))
505
+ if source.is_symlink() or not source.is_file():
506
+ raise RunStateError(f"不支持的 case 产物类型:{source}")
507
+ # Raw samples and idle diagnostics share case IDs; retain provenance.
508
+ target = backup / source.relative_to(self.directory)
509
+ target.parent.mkdir(parents=True, exist_ok=True)
510
+ shutil.copy2(source, target)
511
+ with target.open("rb") as stream:
512
+ os.fsync(stream.fileno())
513
+ backup_directories.add(target.parent)
514
+ for directory in sorted(backup_directories, key=lambda path: len(path.parts), reverse=True):
515
+ _sync_directory_chain(directory, self.directory)
516
+ self.data["cases"][filename] = {"status": "archived", "backup": str(backup.relative_to(self.directory))}
517
+ previous["backup"] = str(backup.relative_to(self.directory))
518
+ self.save()
519
+ for source in existing:
520
+ source.unlink()
521
+ if previous:
522
+ previous_attempts.append(previous)
523
+ self.data["cases"][filename] = {
524
+ "status": "running", "started_at": utc_now(), "case_attempt_id": uuid4().hex,
525
+ "measurement_status": "running", "processing_status": "pending",
526
+ "resources": [cpu, mem, gpu],
527
+ "previous_attempts": previous_attempts,
528
+ }
529
+ self.save()
530
+ path.parent.mkdir(parents=True, exist_ok=True)
531
+ return None
532
+
533
+ def case_attempt_id(self, path: str) -> str:
534
+ record = self.data["cases"][str(Path(path).relative_to(self.directory))]
535
+ return record["case_attempt_id"]
536
+
537
+ def bind_case_container(self, path: str, container_id: str) -> None:
538
+ """Record cleanup ownership before acquisition, including crash recovery."""
539
+ if not re.fullmatch(r"[a-f0-9]{64}", container_id):
540
+ raise RunStateError("Case container must have an immutable Docker ID")
541
+ record = self.data["cases"][str(Path(path).relative_to(self.directory))]
542
+ record.update(cleanup_container_id=container_id, cleanup_status="pending")
543
+ self.save()
544
+
545
+ def checkpoint_measurement(self, path: str, cpu: int, mem: int, gpu: str, *, processing: dict,
546
+ container_id: str) -> None:
547
+ from acprof.host.measurement.checkpoint import publish_measurement_checkpoint
548
+ key = str(Path(path).relative_to(self.directory))
549
+ record = self.data["cases"][key]
550
+ reference = publish_measurement_checkpoint(
551
+ self.layout.case_from_csv(path), run_id=self.data["run_id"],
552
+ attempt_id=record["case_attempt_id"], processing=processing,
553
+ expected=self.expected(cpu, mem, gpu),
554
+ )
555
+ record.update(status="measured", measurement_status="complete", measured_at=utc_now(),
556
+ processing_status="pending", measurement_checkpoint=reference, cleanup_status="pending",
557
+ cleanup_container_id=container_id)
558
+ self.save()
559
+
560
+ def processing_checkpoint(self, path: str, cpu: int, mem: int, gpu: str) -> dict | None:
561
+ from acprof.host.measurement.checkpoint import read_measurement_checkpoint
562
+ record = self.data["cases"].get(str(Path(path).relative_to(self.directory)), {})
563
+ if record.get("measurement_status") != "complete":
564
+ return None
565
+ try:
566
+ checkpoint = read_measurement_checkpoint(
567
+ self.layout.case_from_csv(path), record["measurement_checkpoint"],
568
+ run_id=self.data["run_id"], attempt_id=record["case_attempt_id"],
569
+ expected=self.expected(cpu, mem, gpu),
570
+ )
571
+ except (OSError, ValueError, KeyError) as exc:
572
+ raise RunStateError(f"已完成测量的原始证据无效:{exc}") from exc
573
+ if record.get("cleanup_status") != "complete":
574
+ from acprof.host import command as host_command
575
+ from acprof.host.container_lifecycle import recover_cleanup_debt
576
+ # The existing lifecycle protocol removes only verified abandoned
577
+ # containers, then proves the recorded immutable ID is absent.
578
+ recovery = recover_cleanup_debt(
579
+ [{"container_id": record.get("cleanup_container_id", "")}], host_command.run_command,
580
+ )
581
+ case = self.layout.case_from_csv(path)
582
+ error = case.sidecar("cleanup_error")
583
+ if error.exists():
584
+ target = self.layout.contained(f"raw/cleanup/{case.case_id}/{record['case_attempt_id']}.json")
585
+ target.parent.mkdir(parents=True, exist_ok=True)
586
+ if target.exists():
587
+ raise RunStateError(f"Cleanup evidence already exists: {target}")
588
+ replace_file_durably(error, target)
589
+ record.update(cleanup_status="complete", cleanup_recovery=recovery)
590
+ self.save()
591
+ return checkpoint
592
+
593
+ def record_case_processing(self, path: str, status: str) -> None:
594
+ if status not in {"running", "failed", "complete"}:
595
+ raise ValueError(f"Invalid case processing status: {status}")
596
+ record = self.data["cases"][str(Path(path).relative_to(self.directory))]
597
+ record["processing_status"] = status
598
+ if status == "running":
599
+ record["processing_attempts"] = record.get("processing_attempts", 0) + 1
600
+ self.save()
601
+
602
+ def record_case_cleanup(self, path: str, status: str) -> None:
603
+ if status not in {"complete", "failed"}:
604
+ raise ValueError(f"Invalid case cleanup status: {status}")
605
+ record = self.data["cases"][str(Path(path).relative_to(self.directory))]
606
+ if record.get("measurement_status") == "complete":
607
+ record["cleanup_status"] = status
608
+ self.save()
609
+
610
+ def finish_case(self, path: str, cpu: int, mem: int, gpu: str) -> None:
611
+ _, rows = read_result_csv(path, expected=self.expected(cpu, mem, gpu))
612
+ case = self.layout.case_from_csv(path)
613
+ case.retain_requests()
614
+ key = str(Path(path).relative_to(self.directory))
615
+ record = self.data["cases"].get(key, {})
616
+ self.data["cases"][key] = {
617
+ **record, "processing_status": "complete",
618
+ "measurement_status": "complete" if record.get("measurement_status") == "complete" else "not_measured",
619
+ "status": "complete", "completed_at": utc_now(), "sha256": file_sha256(path),
620
+ "row_count": len(rows), "error_rows": sum(row.get("status") == "error" for row in rows),
621
+ }
622
+ self.save()
623
+
624
+ def finish(self, final_csv: str) -> None:
625
+ _, rows = read_result_csv(final_csv, expected=self.expected())
626
+ self.data.update(status="complete", completed_at=utc_now(), result_sha256=file_sha256(final_csv),
627
+ row_count=len(rows), outcome="partial" if any(row.get("status") == "error" for row in rows) else "ok")
628
+ self.save()
629
+
630
+ def close(self, outcome: str = "interrupted") -> None:
631
+ try:
632
+ if not self.complete:
633
+ self.data["status"] = outcome
634
+ self.data["attempts"][-1]["ended_at"] = utc_now()
635
+ self.data["attempts"][-1]["outcome"] = self.data["status"]
636
+ self.save()
637
+ finally:
638
+ self.lock.__exit__(None, None, None)
639
+ self.measurement_lock.__exit__(None, None, None)
@@ -0,0 +1,10 @@
1
+ """Explicit host overrides that select a locked inference environment."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+
6
+
7
+ def runtime_build_overrides() -> dict[str, str]:
8
+ """Keep preparation, fingerprints and coverage recovery on the same inputs."""
9
+ return {name: os.environ.get(name, "").strip() for name in (
10
+ "ACPROF_NLP_TORCH_INDEX_URL", "ACPROF_NLP_TORCH_SPEC", "ACPROF_HOST_CUDA_VERSION")}