panda-server-source 0.8.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 (375) hide show
  1. panda_server_source-0.8.0.dist-info/METADATA +55 -0
  2. panda_server_source-0.8.0.dist-info/RECORD +375 -0
  3. panda_server_source-0.8.0.dist-info/WHEEL +4 -0
  4. panda_server_source-0.8.0.dist-info/licenses/LICENSE.txt +202 -0
  5. pandajedi/__init__.py +0 -0
  6. pandajedi/jedibrokerage/AtlasAnalJobBroker.py +2343 -0
  7. pandajedi/jedibrokerage/AtlasBrokerUtils.py +1292 -0
  8. pandajedi/jedibrokerage/AtlasProdJobBroker.py +2064 -0
  9. pandajedi/jedibrokerage/AtlasProdTaskBroker.py +629 -0
  10. pandajedi/jedibrokerage/GenJobBroker.py +381 -0
  11. pandajedi/jedibrokerage/JobBrokerBase.py +133 -0
  12. pandajedi/jedibrokerage/TaskBrokerBase.py +15 -0
  13. pandajedi/jedibrokerage/__init__.py +0 -0
  14. pandajedi/jediconfig/__init__.py +0 -0
  15. pandajedi/jediconfig/jedi_config.py +40 -0
  16. pandajedi/jedicore/FactoryBase.py +182 -0
  17. pandajedi/jedicore/Interaction.py +433 -0
  18. pandajedi/jedicore/JediDBProxy.py +56 -0
  19. pandajedi/jedicore/JediDBProxyPool.py +37 -0
  20. pandajedi/jedicore/JediDBSchemaInfo.py +17 -0
  21. pandajedi/jedicore/JediException.py +13 -0
  22. pandajedi/jedicore/JediTaskBuffer.py +984 -0
  23. pandajedi/jedicore/JediTaskBufferInterface.py +50 -0
  24. pandajedi/jedicore/MsgWrapper.py +124 -0
  25. pandajedi/jedicore/ProcessUtils.py +34 -0
  26. pandajedi/jedicore/SiteCandidate.py +90 -0
  27. pandajedi/jedicore/ThreadUtils.py +205 -0
  28. pandajedi/jedicore/__init__.py +0 -0
  29. pandajedi/jediddm/AtlasDDMClient.py +1708 -0
  30. pandajedi/jediddm/DDMClientBase.py +16 -0
  31. pandajedi/jediddm/DDMInterface.py +83 -0
  32. pandajedi/jediddm/GenDDMClient.py +17 -0
  33. pandajedi/jediddm/__init__.py +0 -0
  34. pandajedi/jedidog/AtlasAnalWatchDog.py +648 -0
  35. pandajedi/jedidog/AtlasDataCarouselWatchDog.py +189 -0
  36. pandajedi/jedidog/AtlasDataLocalityUpdaterWatchDog.py +215 -0
  37. pandajedi/jedidog/AtlasProdWatchDog.py +443 -0
  38. pandajedi/jedidog/AtlasQueueFillerWatchDog.py +800 -0
  39. pandajedi/jedidog/AtlasTaskWithholderWatchDog.py +245 -0
  40. pandajedi/jedidog/AtlasWorkflowManagerWatchDog.py +63 -0
  41. pandajedi/jedidog/GenWatchDog.py +21 -0
  42. pandajedi/jedidog/JumboWatchDog.py +147 -0
  43. pandajedi/jedidog/TypicalWatchDogBase.py +122 -0
  44. pandajedi/jedidog/WatchDogBase.py +53 -0
  45. pandajedi/jedidog/__init__.py +0 -0
  46. pandajedi/jediexec/reniceJEDI.py +25 -0
  47. pandajedi/jedigen/AtlasTaskGenerator.py +110 -0
  48. pandajedi/jedigen/TaskGeneratorBase.py +15 -0
  49. pandajedi/jedigen/__init__.py +0 -0
  50. pandajedi/jedimsgprocessor/__init__.py +0 -0
  51. pandajedi/jedimsgprocessor/ai_parameter_adjuster_msg_processor +71 -0
  52. pandajedi/jedimsgprocessor/atlas_idds_msg_processor.py +66 -0
  53. pandajedi/jedimsgprocessor/base_msg_processor.py +33 -0
  54. pandajedi/jedimsgprocessor/forwarding_msg_processor.py +29 -0
  55. pandajedi/jedimsgprocessor/hpo_msg_processor.py +93 -0
  56. pandajedi/jedimsgprocessor/jedi_contents_feeder_msg_processor.py +63 -0
  57. pandajedi/jedimsgprocessor/jedi_job_generator_msg_processor.py +185 -0
  58. pandajedi/jedimsgprocessor/jedi_post_processor_msg_processor.py +82 -0
  59. pandajedi/jedimsgprocessor/kafka_msg_processor.py +44 -0
  60. pandajedi/jedimsgprocessor/panda_callback_msg_processor.py +182 -0
  61. pandajedi/jedimsgprocessor/panda_to_jedi_msg_processor.py +74 -0
  62. pandajedi/jedimsgprocessor/processing_msg_processor.py +134 -0
  63. pandajedi/jedimsgprocessor/status_report_msg_processor.py +129 -0
  64. pandajedi/jedimsgprocessor/tape_carousel_msg_processor.py +118 -0
  65. pandajedi/jedimsgprocessor/workflow_manager_msg_processor.py +89 -0
  66. pandajedi/jediorder/ContentsFeeder.py +785 -0
  67. pandajedi/jediorder/JediDaemon.py +27 -0
  68. pandajedi/jediorder/JediKnight.py +55 -0
  69. pandajedi/jediorder/JediMaster.py +238 -0
  70. pandajedi/jediorder/JediMsgProcessor.py +32 -0
  71. pandajedi/jediorder/JobBroker.py +33 -0
  72. pandajedi/jediorder/JobGenerator.py +2763 -0
  73. pandajedi/jediorder/JobSplitter.py +308 -0
  74. pandajedi/jediorder/JobThrottler.py +28 -0
  75. pandajedi/jediorder/PostProcessor.py +166 -0
  76. pandajedi/jediorder/TaskBroker.py +268 -0
  77. pandajedi/jediorder/TaskCommando.py +334 -0
  78. pandajedi/jediorder/TaskGenerator.py +17 -0
  79. pandajedi/jediorder/TaskRefiner.py +611 -0
  80. pandajedi/jediorder/TaskSetupper.py +17 -0
  81. pandajedi/jediorder/WatchDog.py +79 -0
  82. pandajedi/jediorder/__init__.py +0 -0
  83. pandajedi/jedipprocess/AtlasAnalPostProcessor.py +404 -0
  84. pandajedi/jedipprocess/AtlasPostProcessorUtils.py +50 -0
  85. pandajedi/jedipprocess/AtlasProdPostProcessor.py +178 -0
  86. pandajedi/jedipprocess/GenPostProcessor.py +35 -0
  87. pandajedi/jedipprocess/MailTemplates.py +185 -0
  88. pandajedi/jedipprocess/PostProcessorBase.py +269 -0
  89. pandajedi/jedipprocess/__init__.py +0 -0
  90. pandajedi/jedirefine/AtlasAnalTaskRefiner.py +259 -0
  91. pandajedi/jedirefine/AtlasProdTaskRefiner.py +162 -0
  92. pandajedi/jedirefine/GenTaskRefiner.py +61 -0
  93. pandajedi/jedirefine/RefinerUtils.py +154 -0
  94. pandajedi/jedirefine/TaskRefinerBase.py +996 -0
  95. pandajedi/jedirefine/__init__.py +0 -0
  96. pandajedi/jedisetup/AtlasTaskSetupper.py +309 -0
  97. pandajedi/jedisetup/GenTaskSetupper.py +16 -0
  98. pandajedi/jedisetup/SimpleTaskSetupper.py +152 -0
  99. pandajedi/jedisetup/TaskSetupperBase.py +15 -0
  100. pandajedi/jedisetup/__init__.py +0 -0
  101. pandajedi/jeditest/SchemaChecker.py +41 -0
  102. pandajedi/jeditest/addContWoSoTask.py +54 -0
  103. pandajedi/jeditest/addDataCarouselRequest.py +47 -0
  104. pandajedi/jeditest/addEventServiceTask.py +101 -0
  105. pandajedi/jeditest/addHpoTask.py +81 -0
  106. pandajedi/jeditest/addIDDSStageInRequest.py +56 -0
  107. pandajedi/jeditest/addNonAtlasTask.py +71 -0
  108. pandajedi/jeditest/addNonAtlasTaskEnc.py +64 -0
  109. pandajedi/jeditest/addTestTask.py +124 -0
  110. pandajedi/jeditest/addTestTaskParamToDEFT.py +70 -0
  111. pandajedi/jeditest/addTestTaskWithMerger.py +97 -0
  112. pandajedi/jeditest/brokerTest.py +78 -0
  113. pandajedi/jeditest/cancelDataCarouselRequest.py +35 -0
  114. pandajedi/jeditest/contentsFeederTest.py +28 -0
  115. pandajedi/jeditest/contentsFeederTestWithTask.py +18 -0
  116. pandajedi/jeditest/ddmTest.py +4 -0
  117. pandajedi/jeditest/jobGenForOneTask.py +38 -0
  118. pandajedi/jeditest/jobGeneratorTest.py +43 -0
  119. pandajedi/jeditest/jobSplitterTest.py +104 -0
  120. pandajedi/jeditest/jobThrottlerTest.py +22 -0
  121. pandajedi/jeditest/msgProcessorTest.py +9 -0
  122. pandajedi/jeditest/postProcessorTest.py +18 -0
  123. pandajedi/jeditest/resubmitDataCarouselRequest.py +35 -0
  124. pandajedi/jeditest/resubmitTaskParam.py +20 -0
  125. pandajedi/jeditest/taskBrokerTest.py +22 -0
  126. pandajedi/jeditest/taskBrokerTestWithTask.py +38 -0
  127. pandajedi/jeditest/taskBufferTest.py +5 -0
  128. pandajedi/jeditest/taskCommandoTest.py +28 -0
  129. pandajedi/jeditest/taskRefinerTest.py +30 -0
  130. pandajedi/jeditest/testOneTaskToRefine.py +49 -0
  131. pandajedi/jeditest/testOneTaskToTaskBroker.py +42 -0
  132. pandajedi/jeditest/testPandaDDMMap.py +62 -0
  133. pandajedi/jeditest/updateDataCarouselRequestParams.py +50 -0
  134. pandajedi/jeditest/watchDogTest.py +25 -0
  135. pandajedi/jeditest/workQueueTest.py +22 -0
  136. pandajedi/jedithrottle/AtlasAnalJobThrottler.py +19 -0
  137. pandajedi/jedithrottle/AtlasProdJobThrottler.py +18 -0
  138. pandajedi/jedithrottle/GenJobThrottler.py +26 -0
  139. pandajedi/jedithrottle/JobThrottlerBase.py +496 -0
  140. pandajedi/jedithrottle/__init__.py +0 -0
  141. pandaserver/__init__.py +0 -0
  142. pandaserver/api/__init__.py +0 -0
  143. pandaserver/api/v1/__init__.py +0 -0
  144. pandaserver/api/v1/common.py +339 -0
  145. pandaserver/api/v1/credential_management_api.py +372 -0
  146. pandaserver/api/v1/data_carousel_api.py +264 -0
  147. pandaserver/api/v1/event_api.py +269 -0
  148. pandaserver/api/v1/file_server_api.py +942 -0
  149. pandaserver/api/v1/harvester_api.py +445 -0
  150. pandaserver/api/v1/http_client.py +166 -0
  151. pandaserver/api/v1/idds_api.py +180 -0
  152. pandaserver/api/v1/job_api.py +463 -0
  153. pandaserver/api/v1/metaconfig_api.py +122 -0
  154. pandaserver/api/v1/pilot_api.py +895 -0
  155. pandaserver/api/v1/statistics_api.py +207 -0
  156. pandaserver/api/v1/system_api.py +186 -0
  157. pandaserver/api/v1/task_api.py +1520 -0
  158. pandaserver/api/v1/tests/__init__.py +0 -0
  159. pandaserver/api/v1/tests/credential_management_api_tests.py +116 -0
  160. pandaserver/api/v1/tests/data_carousel_api_tests.py +82 -0
  161. pandaserver/api/v1/tests/harvester_api_tests.py +188 -0
  162. pandaserver/api/v1/tests/idds_api_tests.py +45 -0
  163. pandaserver/api/v1/tests/job_api_tests.py +112 -0
  164. pandaserver/api/v1/tests/pilot_api_tests.py +154 -0
  165. pandaserver/api/v1/tests/statistics_api_tests.py +104 -0
  166. pandaserver/api/v1/tests/system_api_tests.py +58 -0
  167. pandaserver/api/v1/tests/task_api_tests.py +590 -0
  168. pandaserver/api/v1/version.py +2 -0
  169. pandaserver/api/v1/workflow_api.py +136 -0
  170. pandaserver/brokerage/SiteMapper.py +406 -0
  171. pandaserver/brokerage/__init__.py +0 -0
  172. pandaserver/brokerage/broker.py +272 -0
  173. pandaserver/config/__init__.py +0 -0
  174. pandaserver/config/config_utils.py +15 -0
  175. pandaserver/config/daemon_config.py +29 -0
  176. pandaserver/config/mcp_config.py +38 -0
  177. pandaserver/config/panda_config.py +233 -0
  178. pandaserver/configurator/Carbon.py +65 -0
  179. pandaserver/configurator/Configurator.py +908 -0
  180. pandaserver/configurator/__init__.py +0 -0
  181. pandaserver/configurator/aux.py +73 -0
  182. pandaserver/daemons/__init__.py +0 -0
  183. pandaserver/daemons/master.py +110 -0
  184. pandaserver/daemons/master_systemd.py +79 -0
  185. pandaserver/daemons/scripts/__init__.py +0 -0
  186. pandaserver/daemons/scripts/add_main.py +264 -0
  187. pandaserver/daemons/scripts/add_sub.py +221 -0
  188. pandaserver/daemons/scripts/cache_pilots.py +47 -0
  189. pandaserver/daemons/scripts/cache_schedconfig.py +286 -0
  190. pandaserver/daemons/scripts/carbon.py +50 -0
  191. pandaserver/daemons/scripts/configurator.py +94 -0
  192. pandaserver/daemons/scripts/copyArchive.py +1387 -0
  193. pandaserver/daemons/scripts/datasetManager.py +1154 -0
  194. pandaserver/daemons/scripts/dummy_test.py +25 -0
  195. pandaserver/daemons/scripts/evpPD2P.py +144 -0
  196. pandaserver/daemons/scripts/hs_scrapers.py +264 -0
  197. pandaserver/daemons/scripts/metric_collector.py +927 -0
  198. pandaserver/daemons/scripts/panda_activeusers_query.py +80 -0
  199. pandaserver/daemons/scripts/pilotStreaming.py +109 -0
  200. pandaserver/daemons/scripts/process_workflow_files_daemon.py +166 -0
  201. pandaserver/daemons/scripts/recover_lost_files_daemon.py +177 -0
  202. pandaserver/daemons/scripts/task_evaluator.py +405 -0
  203. pandaserver/daemons/scripts/tmpwatch.py +70 -0
  204. pandaserver/daemons/scripts/worker_synchronization.py +97 -0
  205. pandaserver/daemons/utils.py +753 -0
  206. pandaserver/dataservice/DataServiceUtils.py +323 -0
  207. pandaserver/dataservice/ErrorCode.py +25 -0
  208. pandaserver/dataservice/RecoverLostFilesCore.py +322 -0
  209. pandaserver/dataservice/__init__.py +0 -0
  210. pandaserver/dataservice/activator.py +79 -0
  211. pandaserver/dataservice/adder_atlas_plugin.py +1106 -0
  212. pandaserver/dataservice/adder_dummy_plugin.py +31 -0
  213. pandaserver/dataservice/adder_gen.py +954 -0
  214. pandaserver/dataservice/adder_plugin_base.py +27 -0
  215. pandaserver/dataservice/adder_result.py +75 -0
  216. pandaserver/dataservice/adder_simple_plugin.py +160 -0
  217. pandaserver/dataservice/closer.py +247 -0
  218. pandaserver/dataservice/closer_atlas_plugin.py +90 -0
  219. pandaserver/dataservice/ddm.py +1381 -0
  220. pandaserver/dataservice/ddm_handler.py +95 -0
  221. pandaserver/dataservice/dyn_data_distributer.py +824 -0
  222. pandaserver/dataservice/event_lookup_client_ei.py +77 -0
  223. pandaserver/dataservice/event_picker.py +268 -0
  224. pandaserver/dataservice/finisher.py +194 -0
  225. pandaserver/dataservice/setupper.py +225 -0
  226. pandaserver/dataservice/setupper_atlas_plugin.py +1851 -0
  227. pandaserver/dataservice/setupper_dummy_plugin.py +47 -0
  228. pandaserver/dataservice/setupper_plugin_base.py +67 -0
  229. pandaserver/jobdispatcher/DispatcherUtils.py +35 -0
  230. pandaserver/jobdispatcher/ErrorCode.py +19 -0
  231. pandaserver/jobdispatcher/JobDispatcher.py +1625 -0
  232. pandaserver/jobdispatcher/Protocol.py +443 -0
  233. pandaserver/jobdispatcher/Watcher.py +186 -0
  234. pandaserver/jobdispatcher/__init__.py +0 -0
  235. pandaserver/pandamcp/__init__.py +0 -0
  236. pandaserver/pandamcp/mcp_main.py +56 -0
  237. pandaserver/pandamcp/mcp_test_client.py +95 -0
  238. pandaserver/pandamcp/mcp_utils.py +74 -0
  239. pandaserver/proxycache/__init__.py +0 -0
  240. pandaserver/proxycache/panda_proxy_cache.py +240 -0
  241. pandaserver/proxycache/token_cache.py +141 -0
  242. pandaserver/server/.gacl +6 -0
  243. pandaserver/server/panda.py +539 -0
  244. pandaserver/srvcore/CoreUtils.py +572 -0
  245. pandaserver/srvcore/MailUtils.py +106 -0
  246. pandaserver/srvcore/__init__.py +0 -0
  247. pandaserver/srvcore/allowed_methods.py +100 -0
  248. pandaserver/srvcore/oidc_utils.py +152 -0
  249. pandaserver/srvcore/panda_request.py +196 -0
  250. pandaserver/srvcore/srv_msg_utils.py +42 -0
  251. pandaserver/taskbuffer/ConBridge.py +513 -0
  252. pandaserver/taskbuffer/DBProxyPool.py +95 -0
  253. pandaserver/taskbuffer/DataCarousel.py +3053 -0
  254. pandaserver/taskbuffer/DatasetSpec.py +137 -0
  255. pandaserver/taskbuffer/DdmSpec.py +201 -0
  256. pandaserver/taskbuffer/ErrorCode.py +87 -0
  257. pandaserver/taskbuffer/EventServiceUtils.py +396 -0
  258. pandaserver/taskbuffer/FileSpec.py +290 -0
  259. pandaserver/taskbuffer/GlobalShares.py +244 -0
  260. pandaserver/taskbuffer/HarvesterMetricsSpec.py +92 -0
  261. pandaserver/taskbuffer/Initializer.py +82 -0
  262. pandaserver/taskbuffer/InputChunk.py +1080 -0
  263. pandaserver/taskbuffer/JediCacheSpec.py +57 -0
  264. pandaserver/taskbuffer/JediDatasetSpec.py +588 -0
  265. pandaserver/taskbuffer/JediFileSpec.py +242 -0
  266. pandaserver/taskbuffer/JediTaskSpec.py +1687 -0
  267. pandaserver/taskbuffer/JobSpec.py +1006 -0
  268. pandaserver/taskbuffer/JobUtils.py +232 -0
  269. pandaserver/taskbuffer/NucleusSpec.py +116 -0
  270. pandaserver/taskbuffer/OraDBProxy.py +174 -0
  271. pandaserver/taskbuffer/PanDAMsgProcessor.py +8 -0
  272. pandaserver/taskbuffer/PandaDBSchemaInfo.py +18 -0
  273. pandaserver/taskbuffer/ParseJobXML.py +350 -0
  274. pandaserver/taskbuffer/PickleFileSpec.py +18 -0
  275. pandaserver/taskbuffer/PickleJobSpec.py +25 -0
  276. pandaserver/taskbuffer/PrioUtil.py +40 -0
  277. pandaserver/taskbuffer/ProcessGroups.py +111 -0
  278. pandaserver/taskbuffer/ResourceSpec.py +175 -0
  279. pandaserver/taskbuffer/SQLDumper.py +37 -0
  280. pandaserver/taskbuffer/SQLManager.py +60 -0
  281. pandaserver/taskbuffer/SiteSpec.py +304 -0
  282. pandaserver/taskbuffer/SupErrors.py +10 -0
  283. pandaserver/taskbuffer/TaskBuffer.py +2882 -0
  284. pandaserver/taskbuffer/TaskBufferInterface.py +163 -0
  285. pandaserver/taskbuffer/Utils.py +848 -0
  286. pandaserver/taskbuffer/WorkQueue.py +227 -0
  287. pandaserver/taskbuffer/WorkQueueMapper.py +203 -0
  288. pandaserver/taskbuffer/WorkerSpec.py +143 -0
  289. pandaserver/taskbuffer/WrappedCursor.py +487 -0
  290. pandaserver/taskbuffer/WrappedPickle.py +88 -0
  291. pandaserver/taskbuffer/WrappedPostgresConn.py +24 -0
  292. pandaserver/taskbuffer/__init__.py +0 -0
  293. pandaserver/taskbuffer/db_proxy_mods/OraDBProxy_structure.png +0 -0
  294. pandaserver/taskbuffer/db_proxy_mods/__init__.py +7 -0
  295. pandaserver/taskbuffer/db_proxy_mods/base_module.py +841 -0
  296. pandaserver/taskbuffer/db_proxy_mods/data_carousel_module.py +772 -0
  297. pandaserver/taskbuffer/db_proxy_mods/entity_module.py +3804 -0
  298. pandaserver/taskbuffer/db_proxy_mods/job_complex_module.py +5661 -0
  299. pandaserver/taskbuffer/db_proxy_mods/job_standalone_module.py +2640 -0
  300. pandaserver/taskbuffer/db_proxy_mods/metrics_module.py +874 -0
  301. pandaserver/taskbuffer/db_proxy_mods/misc_standalone_module.py +4272 -0
  302. pandaserver/taskbuffer/db_proxy_mods/task_complex_module.py +6172 -0
  303. pandaserver/taskbuffer/db_proxy_mods/task_event_module.py +4953 -0
  304. pandaserver/taskbuffer/db_proxy_mods/task_standalone_module.py +4999 -0
  305. pandaserver/taskbuffer/db_proxy_mods/task_utils_module.py +2063 -0
  306. pandaserver/taskbuffer/db_proxy_mods/worker_module.py +1944 -0
  307. pandaserver/taskbuffer/db_proxy_mods/workflow_module.py +832 -0
  308. pandaserver/taskbuffer/retryModule.py +552 -0
  309. pandaserver/taskbuffer/task_split_rules.py +308 -0
  310. pandaserver/taskbuffer/workflow_processor.py +249 -0
  311. pandaserver/taskbuffer/wrapped_oracle_conn.py +18 -0
  312. pandaserver/test/SchemaChecker.py +40 -0
  313. pandaserver/test/__init__.py +0 -0
  314. pandaserver/test/banUser.py +43 -0
  315. pandaserver/test/boostPrio.py +19 -0
  316. pandaserver/test/boostUser.py +35 -0
  317. pandaserver/test/callbackDDM.py +11 -0
  318. pandaserver/test/daod_on_demand.py +95 -0
  319. pandaserver/test/finishJob.py +184 -0
  320. pandaserver/test/finishTaskJEDI.py +11 -0
  321. pandaserver/test/getJobs.py +53 -0
  322. pandaserver/test/killJob.py +79 -0
  323. pandaserver/test/killJobLowPrio.py +99 -0
  324. pandaserver/test/killJobsInTask.py +56 -0
  325. pandaserver/test/killTask.py +80 -0
  326. pandaserver/test/killTaskJEDI.py +11 -0
  327. pandaserver/test/killUser.py +83 -0
  328. pandaserver/test/lsst/README.txt +7 -0
  329. pandaserver/test/lsst/lsstSubmit.py +139 -0
  330. pandaserver/test/lsst/lsstSubmitMERGEtest.py +141 -0
  331. pandaserver/test/lsst/lsstSubmitPhosim332.py +53 -0
  332. pandaserver/test/lsst/prepare-client-tarball-from-bigpanda-server.sh +41 -0
  333. pandaserver/test/reassignJobs.py +17 -0
  334. pandaserver/test/reassignSite.py +82 -0
  335. pandaserver/test/reassignTask.py +117 -0
  336. pandaserver/test/reassignWaiting.py +39 -0
  337. pandaserver/test/recoverLostFiles.py +7 -0
  338. pandaserver/test/reloadInputDS.py +51 -0
  339. pandaserver/test/sendCommandToJob.py +26 -0
  340. pandaserver/test/setDebugMode.py +38 -0
  341. pandaserver/test/setPriority.py +28 -0
  342. pandaserver/test/testEvgen.py +60 -0
  343. pandaserver/test/testEvgen17.py +58 -0
  344. pandaserver/test/testG4sim.py +81 -0
  345. pandaserver/test/testG4sim17.py +90 -0
  346. pandaserver/test/testGlobalShares.py +131 -0
  347. pandaserver/test/testReco.py +103 -0
  348. pandaserver/test/testSimulReco14.py +98 -0
  349. pandaserver/test/testSiteMap.py +26 -0
  350. pandaserver/test/testUpdateWorkerPilotStatus.py +9 -0
  351. pandaserver/userinterface/Client.py +1686 -0
  352. pandaserver/userinterface/UserIF.py +1799 -0
  353. pandaserver/userinterface/__init__.py +0 -0
  354. pandaserver/workflow/__init__.py +0 -0
  355. pandaserver/workflow/data_handler_plugins/base_data_handler.py +48 -0
  356. pandaserver/workflow/data_handler_plugins/ddm_collection_data_handler.py +100 -0
  357. pandaserver/workflow/data_handler_plugins/panda_task_data_handler.py +140 -0
  358. pandaserver/workflow/pcwl_test.py +122 -0
  359. pandaserver/workflow/pcwl_utils.py +384 -0
  360. pandaserver/workflow/psnakemake_container.json +68 -0
  361. pandaserver/workflow/psnakemake_task.json +62 -0
  362. pandaserver/workflow/psnakemake_test.py +60 -0
  363. pandaserver/workflow/snakeparser/__init__.py +3 -0
  364. pandaserver/workflow/snakeparser/extensions.py +74 -0
  365. pandaserver/workflow/snakeparser/log.py +53 -0
  366. pandaserver/workflow/snakeparser/names.py +3 -0
  367. pandaserver/workflow/snakeparser/parser.py +332 -0
  368. pandaserver/workflow/snakeparser/utils.py +29 -0
  369. pandaserver/workflow/step_handler_plugins/base_step_handler.py +85 -0
  370. pandaserver/workflow/step_handler_plugins/panda_task_step_handler.py +280 -0
  371. pandaserver/workflow/workflow_base.py +531 -0
  372. pandaserver/workflow/workflow_core.py +1974 -0
  373. pandaserver/workflow/workflow_core_smoketest.py +388 -0
  374. pandaserver/workflow/workflow_parser.py +222 -0
  375. pandaserver/workflow/workflow_utils.py +1047 -0
@@ -0,0 +1,2063 @@
1
+ import datetime
2
+ import json
3
+ import math
4
+ import random
5
+ import re
6
+ import sys
7
+ import traceback
8
+ import uuid
9
+ from statistics import mean
10
+
11
+ import numpy
12
+ from pandacommon.pandalogger.LogWrapper import LogWrapper
13
+ from pandacommon.pandautils.PandaUtils import (
14
+ batched,
15
+ get_sql_IN_bind_variables,
16
+ naive_utcnow,
17
+ )
18
+
19
+ from pandaserver.config import panda_config
20
+ from pandaserver.srvcore import CoreUtils
21
+ from pandaserver.taskbuffer import EventServiceUtils, JobUtils
22
+ from pandaserver.taskbuffer.db_proxy_mods.base_module import BaseModule, varNUMBER
23
+ from pandaserver.taskbuffer.InputChunk import InputChunk
24
+ from pandaserver.taskbuffer.JediDatasetSpec import (
25
+ INPUT_TYPES_var_map,
26
+ INPUT_TYPES_var_str,
27
+ JediDatasetSpec,
28
+ MERGE_TYPES_var_map,
29
+ MERGE_TYPES_var_str,
30
+ PROCESS_TYPES_var_map,
31
+ PROCESS_TYPES_var_str,
32
+ )
33
+ from pandaserver.taskbuffer.JediFileSpec import JediFileSpec
34
+ from pandaserver.taskbuffer.JediTaskSpec import JediTaskSpec, is_msg_driven
35
+ from pandaserver.taskbuffer.JobSpec import JobSpec, get_task_queued_time
36
+ from pandaserver.taskbuffer.task_split_rules import decode_split_rule
37
+
38
+
39
+ # Module class to define task related methods that are used by TaskComplex methods
40
+ class TaskUtilsModule(BaseModule):
41
+ # constructor
42
+ def __init__(self, log_stream: LogWrapper):
43
+ super().__init__(log_stream)
44
+
45
+ # check if item is matched with one of list items
46
+ def isMatched(self, itemName, pattList):
47
+ for tmpName in pattList:
48
+ # normal pattern
49
+ if re.search(tmpName, itemName) is not None or tmpName == itemName:
50
+ return True
51
+ # return
52
+ return False
53
+
54
+ # fix associated files in staging
55
+ def fix_associated_files_in_staging(self, jeditaskid, primary_id=None, secondary_id=None):
56
+ comment = " /* JediDBProxy.fix_associated_files_in_staging */"
57
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jeditaskid}")
58
+ tmpLog.debug("start")
59
+ # get primary dataset
60
+ if primary_id is None:
61
+ sqlGD = f"SELECT datasetID FROM {panda_config.schemaJEDI}.JEDI_Datasets WHERE jediTaskID=:jediTaskID AND type=:type AND masterID IS NULL "
62
+ varMap = dict()
63
+ varMap[":jediTaskID"] = jeditaskid
64
+ varMap[":type"] = "input"
65
+ self.cur.execute(sqlGD + comment, varMap)
66
+ resGD = self.cur.fetchone()
67
+ if resGD is None:
68
+ return
69
+ (primary_id,) = resGD
70
+ # get secondary dataset
71
+ if secondary_id is not None:
72
+ secondary_id_list = [secondary_id]
73
+ else:
74
+ sqlGS = f"SELECT datasetID FROM {panda_config.schemaJEDI}.JEDI_Datasets WHERE jediTaskID=:jediTaskID AND type=:type AND masterID IS NOT NULL "
75
+ varMap = dict()
76
+ varMap[":jediTaskID"] = jeditaskid
77
+ varMap[":type"] = "pseudo_input"
78
+ self.cur.execute(sqlGS + comment, varMap)
79
+ resGDA = self.cur.fetchall()
80
+ secondary_id_list = [tmpID for tmpID, in resGDA]
81
+ if len(secondary_id_list) == 0:
82
+ return
83
+ # get primary files
84
+ sqlGP = f"SELECT status FROM {panda_config.schemaJEDI}.JEDI_Dataset_Contents WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID ORDER BY lfn "
85
+ varMap = dict()
86
+ varMap[":jediTaskID"] = jeditaskid
87
+ varMap[":datasetID"] = primary_id
88
+ self.cur.execute(sqlGP + comment, varMap)
89
+ resFP = self.cur.fetchall()
90
+ primaryList = [status for status, in resFP]
91
+ # sql to get secondary files
92
+ sqlGS = ("SELECT fileID,status FROM {0}.JEDI_Dataset_Contents " " WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID " "ORDER BY fileID ").format(
93
+ panda_config.schemaJEDI
94
+ )
95
+ # sql to update files
96
+ sqlUS = (
97
+ "UPDATE {0}.JEDI_Dataset_Contents "
98
+ "SET status=:new_status "
99
+ "WHERE jediTaskID=:jediTaskID "
100
+ "AND datasetID=:datasetID "
101
+ "AND fileID=:fileID "
102
+ "AND status=:old_status "
103
+ ).format(panda_config.schemaJEDI)
104
+ # sql to update dataset
105
+ sqlUD = (
106
+ "UPDATE {0}.JEDI_Datasets "
107
+ "SET nFilesToBeUsed="
108
+ "(SELECT COUNT(*) FROM {0}.JEDI_Dataset_Contents "
109
+ "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND status<>:status) "
110
+ "WHERE jediTaskID=:jediTaskID "
111
+ "AND datasetID=:datasetID "
112
+ ).format(panda_config.schemaJEDI)
113
+ # loop over secondary datasets
114
+ for secondaryID in secondary_id_list:
115
+ # get secondary files
116
+ varMap = dict()
117
+ varMap[":jediTaskID"] = jeditaskid
118
+ varMap[":datasetID"] = secondaryID
119
+ self.cur.execute(sqlGS + comment, varMap)
120
+ resFS = self.cur.fetchall()
121
+ # check files
122
+ n = 0
123
+ for priStatus, (secFileID, secStatus) in zip(primaryList, resFS):
124
+ if priStatus != "staging" and secStatus == "staging":
125
+ # update files
126
+ varMap = dict()
127
+ varMap[":jediTaskID"] = jeditaskid
128
+ varMap[":datasetID"] = secondaryID
129
+ varMap[":fileID"] = secFileID
130
+ varMap[":old_status"] = "staging"
131
+ varMap[":new_status"] = "ready"
132
+ self.cur.execute(sqlUS + comment, varMap)
133
+ n += self.cur.rowcount
134
+ # update dataset
135
+ varMap = dict()
136
+ varMap[":jediTaskID"] = jeditaskid
137
+ varMap[":datasetID"] = secondaryID
138
+ varMap[":status"] = "staging"
139
+ self.cur.execute(sqlUD + comment, varMap)
140
+ tmpLog.debug(f"updated {n} files for datasetID={secondaryID}")
141
+
142
+ # enable jumbo jobs in a task
143
+ def enableJumboInTask_JEDI(self, jediTaskID, eventService, site, useJumbo, splitRule):
144
+ comment = " /* JediDBProxy.enableJumboInTask_JEDI */"
145
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
146
+ tmpLog.debug(f"eventService={eventService} site={site} useJumbo={useJumbo}")
147
+ if eventService == 1 and site is None and useJumbo is None:
148
+ taskSpec = JediTaskSpec()
149
+ taskSpec.splitRule = splitRule
150
+ # go to scouting
151
+ if taskSpec.useScout() and not taskSpec.isPostScout():
152
+ return
153
+ # check if should enable jumbo
154
+ toEnable = self.toEnableJumbo_JEDI(jediTaskID)
155
+ if not toEnable:
156
+ return
157
+ # get nJumbo jobs
158
+ sqlLK = f"SELECT value, type FROM {panda_config.schemaPANDA}.CONFIG "
159
+ sqlLK += "WHERE component=:component AND key=:key AND app=:app "
160
+ varMap = dict()
161
+ varMap[":component"] = "taskrefiner"
162
+ varMap[":app"] = "jedi"
163
+ varMap[":key"] = "AES_NUM_JUMBO_PER_TASK"
164
+ self.cur.execute(sqlLK + comment, varMap)
165
+ resLK = self.cur.fetchone()
166
+ try:
167
+ (nJumboJobs,) = resLK
168
+ nJumboJobs = int(nJumboJobs)
169
+ except Exception:
170
+ nJumboJobs = 1
171
+ # enable jumbo
172
+ # self.enableJumboJobs(jediTaskID, nJumboJobs, False, False)
173
+
174
+ # check if should enable jumbo
175
+ def toEnableJumbo_JEDI(self, jediTaskID):
176
+ comment = " /* JediDBProxy.toEnableJumbo_JEDI */"
177
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
178
+ tmpLog.debug("start")
179
+ try:
180
+ # sql to get thresholds
181
+ sqlLK = f"SELECT value FROM {panda_config.schemaPANDA}.CONFIG "
182
+ sqlLK += "WHERE component=:component AND key=:key AND app=:app "
183
+ # sql to get nevents
184
+ sqlAV = f"SELECT nEvents,nFilesToBeUsed,nFilesUsed FROM {panda_config.schemaJEDI}.JEDI_Datasets "
185
+ sqlAV += "WHERE jediTaskID=:jediTaskID "
186
+ sqlAV += f"AND type IN ({INPUT_TYPES_var_str}) "
187
+ sqlAV += "AND masterID IS NULL "
188
+ # sql to get # of active jumbo jobs
189
+ sqlAJ = "SELECT COUNT(*) "
190
+ sqlAJ += "FROM {0}.JEDI_Tasks tabT,{0}.JEDI_AUX_Status_MinTaskID tabA ".format(panda_config.schemaJEDI)
191
+ sqlAJ += "WHERE tabT.status=tabA.status AND tabT.jediTaskID>=tabA.min_jediTaskID "
192
+ sqlAJ += "AND tabT.eventService=:eventService AND tabT.useJumbo IS NOT NULL AND tabT.useJumbo<>:useJumbo "
193
+ sqlAJ += "AND tabT.site IS NULL AND tabT.status IN (:st1,:st2,:st3) "
194
+ # get thresholds
195
+ configMaxJumbo = "AES_MAX_NUM_JUMBO_TASKS"
196
+ varMap = dict()
197
+ varMap[":component"] = "taskrefiner"
198
+ varMap[":app"] = "jedi"
199
+ varMap[":key"] = configMaxJumbo
200
+ self.cur.execute(sqlLK + comment, varMap)
201
+ resLK = self.cur.fetchone()
202
+ if resLK is None:
203
+ tmpLog.debug(f"False since {configMaxJumbo} is not defined")
204
+ return False
205
+ try:
206
+ (maxJumbo,) = resLK
207
+ except Exception:
208
+ tmpLog.debug(f"False since {configMaxJumbo} is not an int")
209
+ return False
210
+ varMap = dict()
211
+ varMap[":component"] = "taskrefiner"
212
+ varMap[":app"] = "jedi"
213
+ varMap[":key"] = "AES_MIN_EVENTS_PER_JUMBO_TASK"
214
+ self.cur.execute(sqlLK + comment, varMap)
215
+ resLK = self.cur.fetchone()
216
+ try:
217
+ (minEvents,) = resLK
218
+ minEvents = int(minEvents)
219
+ except Exception:
220
+ minEvents = 100 * 1000 * 1000
221
+ # get nevents
222
+ varMap = dict()
223
+ varMap[":jediTaskID"] = jediTaskID
224
+ varMap.update(INPUT_TYPES_var_map)
225
+ self.cur.execute(sqlAV + comment, varMap)
226
+ resAV = self.cur.fetchone()
227
+ if resAV is None:
228
+ tmpLog.debug("False since cannot get nEvents")
229
+ return False
230
+ nEvents, nFilesToBeUsed, nFilesUsed = resAV
231
+ try:
232
+ nEvents = nEvents * nFilesToBeUsed // (nFilesToBeUsed - nFilesUsed)
233
+ except Exception:
234
+ tmpLog.debug(f"False since cannot get effective nEvents from nEvents={nEvents} nFilesToBeUsed={nFilesToBeUsed} nFilesUsed={nFilesUsed}")
235
+ return False
236
+ if nEvents < minEvents:
237
+ tmpLog.debug(f"False since effective nEvents={nEvents} < minEventsJumbo={minEvents}")
238
+ return False
239
+ # get num jombo tasks
240
+ varMap = dict()
241
+ varMap[":eventService"] = 1
242
+ varMap[":useJumbo"] = "D"
243
+ varMap[":st1"] = "ready"
244
+ varMap[":st2"] = "pending"
245
+ varMap[":st3"] = "running"
246
+ self.cur.execute(sqlAJ + comment, varMap)
247
+ resAJ = self.cur.fetchone()
248
+ nJumbo = 0
249
+ if resAJ is not None:
250
+ (nJumbo,) = resAJ
251
+ if nJumbo > maxJumbo:
252
+ tmpLog.debug(f"False since nJumbo={nJumbo} > maxJumbo={maxJumbo}")
253
+ return False
254
+ tmpLog.debug("True since nJumbo={0} < maxJumbo={1} and nEvents={0} > minEventsJumbo={1}".format(nJumbo, maxJumbo, nEvents, minEvents))
255
+ return True
256
+ except Exception:
257
+ # roll back
258
+ self._rollback()
259
+ # error
260
+ self.dump_error_message(tmpLog)
261
+ return False
262
+
263
+ # get scout job data
264
+ def getScoutJobData_JEDI(
265
+ self,
266
+ jediTaskID,
267
+ useTransaction=False,
268
+ scoutSuccessRate=None,
269
+ mergeScout=False,
270
+ flagJob=False,
271
+ setPandaID=None,
272
+ site_mapper=None,
273
+ task_spec=None,
274
+ task_params_map=None,
275
+ ):
276
+ comment = " /* JediDBProxy.getScoutJobData_JEDI */"
277
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
278
+ tmpLog.debug(f"start mergeScout={mergeScout}")
279
+ returnMap = {}
280
+ extraInfo = {}
281
+
282
+ # get percentile rank and margin for memory
283
+ ramCountRank = self.getConfigValue("dbproxy", "SCOUT_RAMCOUNT_RANK", "jedi")
284
+ if ramCountRank is None:
285
+ ramCountRank = 75
286
+ ramCountMargin = self.getConfigValue("dbproxy", "SCOUT_RAMCOUNT_MARGIN", "jedi")
287
+ if ramCountMargin is None:
288
+ ramCountMargin = 10
289
+ # get percentile rank for cpuTime
290
+ cpuTimeRank = self.getConfigValue("dbproxy", "SCOUT_CPUTIME_RANK", "jedi")
291
+ if cpuTimeRank is None:
292
+ cpuTimeRank = 95
293
+
294
+ # sql to get preset values
295
+ sqlGPV = (
296
+ "SELECT prodSourceLabel, outDiskUnit, walltime, ramUnit, baseRamCount, workDiskCount, cpuEfficiency, baseWalltime, "
297
+ "splitRule, memory_leak_core, memory_leak_x2 "
298
+ f"FROM {panda_config.schemaJEDI}.JEDI_Tasks "
299
+ "WHERE jediTaskID = :jediTaskID "
300
+ )
301
+
302
+ # sql to get scout job data from JEDI
303
+ sqlSCF = (
304
+ "SELECT tabF.PandaID, tabF.fsize, tabF.startEvent, tabF.endEvent, tabF.nEvents, tabF.type "
305
+ f"FROM {panda_config.schemaJEDI}.JEDI_Datasets tabD, {panda_config.schemaJEDI}.JEDI_Dataset_Contents tabF "
306
+ "WHERE tabD.jediTaskID = tabF.jediTaskID "
307
+ "AND tabD.jediTaskID = :jediTaskID "
308
+ "AND tabF.status = :status "
309
+ "AND tabD.datasetID = tabF.datasetID "
310
+ )
311
+ if not mergeScout:
312
+ sqlSCF += f"AND tabF.type IN ({INPUT_TYPES_var_str}) "
313
+ else:
314
+ sqlSCF += f"AND tabD.type IN ({MERGE_TYPES_var_str}) "
315
+ sqlSCF += "AND tabD.masterID IS NULL "
316
+ if setPandaID is not None:
317
+ sqlSCF += "AND tabF.PandaID=:usePandaID "
318
+
319
+ # sql to check scout success rate
320
+ sqlCSSR = "SELECT COUNT(*),SUM(is_finished),SUM(is_failed) FROM "
321
+ sqlCSSR += (
322
+ "(SELECT DISTINCT tabF.PandaID,CASE WHEN tabF.status='finished' THEN 1 ELSE 0 END is_finished,"
323
+ "CASE WHEN tabF.status='ready' AND "
324
+ "(tabF.maxAttempt<=tabF.attemptNr OR "
325
+ "(tabF.maxfailure IS NOT NULL AND tabF.maxFailure<=tabF.failedAttempt)) THEN 1 ELSE 0 END "
326
+ "is_failed "
327
+ )
328
+ sqlCSSR += "FROM {0}.JEDI_Datasets tabD, {0}.JEDI_Dataset_Contents tabF WHERE ".format(panda_config.schemaJEDI)
329
+ sqlCSSR += "tabD.jediTaskID=tabF.jediTaskID AND tabD.jediTaskID=:jediTaskID AND tabF.PandaID IS NOT NULL "
330
+ sqlCSSR += "AND tabD.datasetID=tabF.datasetID "
331
+ sqlCSSR += f"AND tabF.type IN ({INPUT_TYPES_var_str}) "
332
+ sqlCSSR += "AND tabD.masterID IS NULL "
333
+ sqlCSSR += ") tmp_sub "
334
+
335
+ # sql to get normal scout job data from Panda
336
+ sqlSCDN = (
337
+ "SELECT eventService, jobsetID, PandaID, jobStatus, outputFileBytes, jobMetrics, cpuConsumptionTime, "
338
+ "actualCoreCount, coreCount, startTime, endTime, computingSite, maxPSS, specialHandling, nEvents, "
339
+ "totRBYTES, totWBYTES, inputFileBytes, memory_leak, memory_leak_x2, modificationhost "
340
+ f"FROM {panda_config.schemaPANDA}.jobsArchived4 "
341
+ "WHERE PandaID=:pandaID AND jobStatus=:jobStatus AND jediTaskID=:jediTaskID "
342
+ "UNION "
343
+ "SELECT eventService, jobsetID, PandaID, jobStatus, outputFileBytes, jobMetrics, cpuConsumptionTime, "
344
+ "actualCoreCount, coreCount, startTime, endTime, computingSite, maxPSS, specialHandling, nEvents, "
345
+ "totRBYTES, totWBYTES, inputFileBytes, memory_leak, memory_leak_x2, modificationhost "
346
+ f"FROM {panda_config.schemaPANDAARCH}.jobsArchived "
347
+ "WHERE PandaID=:pandaID AND jobStatus=:jobStatus AND jediTaskID=:jediTaskID "
348
+ "AND modificationTime>(CURRENT_DATE-30) "
349
+ )
350
+
351
+ # sql to get ES scout job data from Panda
352
+ sqlSCDE = (
353
+ "SELECT eventService, jobsetID, PandaID, jobStatus, outputFileBytes, jobMetrics, cpuConsumptionTime, "
354
+ "actualCoreCount, coreCount, startTime, endTime, computingSite, maxPSS, specialHandling, nEvents, "
355
+ "totRBYTES, totWBYTES, inputFileBytes, memory_leak, memory_leak_x2, modificationhost "
356
+ f"FROM {panda_config.schemaPANDA}.jobsArchived4 "
357
+ "WHERE jobsetID=:pandaID AND jobStatus=:jobStatus AND jediTaskID=:jediTaskID "
358
+ "UNION "
359
+ "SELECT eventService, jobsetID, PandaID, jobStatus, outputFileBytes, jobMetrics, cpuConsumptionTime, "
360
+ "actualCoreCount, coreCount, startTime, endTime, computingSite, maxPSS, specialHandling, nEvents, "
361
+ "totRBYTES, totWBYTES, inputFileBytes, memory_leak, memory_leak_x2, modificationhost "
362
+ f"FROM {panda_config.schemaPANDAARCH}.jobsArchived "
363
+ "WHERE jobsetID=:pandaID AND jobStatus=:jobStatus AND jediTaskID=:jediTaskID "
364
+ "AND modificationTime>(CURRENT_DATE-14) "
365
+ )
366
+
367
+ # get size of lib
368
+ sqlLIB = "SELECT MAX(fsize) "
369
+ sqlLIB += "FROM {0}.JEDI_Datasets tabD, {0}.JEDI_Dataset_Contents tabF WHERE ".format(panda_config.schemaJEDI)
370
+ sqlLIB += "tabD.jediTaskID=tabF.jediTaskID AND tabD.jediTaskID=:jediTaskID AND tabF.status=:status AND "
371
+ sqlLIB += "tabD.type=:type AND tabF.type=:type "
372
+
373
+ # get core power
374
+ sqlCore = f"SELECT /* use_json_type */ scj.data.corepower FROM {panda_config.schemaJEDI}.schedconfig_json scj "
375
+ sqlCore += "WHERE panda_queue=:site "
376
+
377
+ # get num of new jobs
378
+ sqlNumJobs = f"SELECT SUM(nFiles),SUM(nFilesFinished),SUM(nFilesUsed) FROM {panda_config.schemaJEDI}.JEDI_Datasets "
379
+ sqlNumJobs += "WHERE jediTaskID=:jediTaskID "
380
+ sqlNumJobs += f"AND type IN ({INPUT_TYPES_var_str}) "
381
+ sqlNumJobs += "AND masterID IS NULL "
382
+
383
+ # get num of new jobs with event
384
+ sql_num_jobs_event = (
385
+ "SELECT SUM(n_events), SUM(CASE WHEN status='finished' THEN n_events ELSE 0 END) FROM ("
386
+ "SELECT (CASE WHEN tabF.endEvent IS NULL THEN tabF.nEvents ELSE tabF.endEvent-tabF.startEvent+1 END) n_events,tabF.status "
387
+ f"FROM {panda_config.schemaJEDI}.JEDI_Datasets tabD, "
388
+ f"{panda_config.schemaJEDI}.JEDI_Dataset_Contents tabF "
389
+ "WHERE tabD.jediTaskID=:jediTaskID AND tabF.jediTaskID=tabD.jediTaskID "
390
+ )
391
+ sql_num_jobs_event += f"AND tabF.datasetID=tabD.datasetID AND tabD.type IN ({INPUT_TYPES_var_str}) "
392
+ sql_num_jobs_event += "AND tabD.masterID IS NULL) tmp_tab "
393
+
394
+ if useTransaction:
395
+ # begin transaction
396
+ self.conn.begin()
397
+ self.cur.arraysize = 100000
398
+
399
+ # get preset values to the task
400
+ varMap = {}
401
+ varMap[":jediTaskID"] = jediTaskID
402
+ self.cur.execute(sqlGPV + comment, varMap)
403
+ resGPV = self.cur.fetchone()
404
+ if resGPV is not None:
405
+ (
406
+ prodSourceLabel,
407
+ preOutDiskUnit,
408
+ preWalltime,
409
+ preRamUnit,
410
+ preBaseRamCount,
411
+ preWorkDiskCount,
412
+ preCpuEfficiency,
413
+ preBaseWalltime,
414
+ splitRule,
415
+ memory_leak_core,
416
+ memory_leak_x2,
417
+ ) = resGPV
418
+ else:
419
+ prodSourceLabel = None
420
+ preOutDiskUnit = None
421
+ preWalltime = 0
422
+ preRamUnit = None
423
+ preBaseRamCount = 0
424
+ preWorkDiskCount = 0
425
+ preCpuEfficiency = None
426
+ preBaseWalltime = None
427
+ splitRule = None
428
+ if preOutDiskUnit is not None and preOutDiskUnit.endswith("PerEvent"):
429
+ preOutputScaleWithEvents = True
430
+ else:
431
+ preOutputScaleWithEvents = False
432
+ if preCpuEfficiency is None:
433
+ preCpuEfficiency = 100
434
+ if preBaseWalltime is None:
435
+ preBaseWalltime = 0
436
+
437
+ # don't use baseRamCount for pmerge
438
+ if mergeScout:
439
+ preBaseRamCount = 0
440
+
441
+ # use original ramCount if available
442
+ if task_params_map is not None:
443
+ preCpuTime = task_params_map.get("cpuTime")
444
+ preCpuTimeUnit = task_params_map.get("cpuTimeUnit")
445
+ if preCpuTime and not preCpuTimeUnit:
446
+ preCpuTimeUnit = "HS06sPerEvent"
447
+ if mergeScout:
448
+ preRamCount = task_params_map.get("mergeRamCount")
449
+ else:
450
+ preRamCount = task_params_map.get("ramCount")
451
+ if preRamCount:
452
+ preRamCount = int(preRamCount)
453
+ else:
454
+ preCpuTime = None
455
+ preRamCount = None
456
+ preCpuTimeUnit = None
457
+ # get preCpuTime in sec
458
+ try:
459
+ if preCpuTime and preCpuTimeUnit and preCpuTimeUnit.startswith("m"):
460
+ preCpuTime = float(preCpuTime) / 1000.0
461
+ except Exception:
462
+ pass
463
+ extraInfo["oldCpuTime"] = preCpuTime
464
+ extraInfo["oldRamCount"] = preRamCount
465
+
466
+ # get minimum ram count
467
+ minRamCount = self.getConfigValue("dbproxy", "SCOUT_RAMCOUNT_MIN", "jedi")
468
+
469
+ # get limit for short jobs
470
+ shortExecTime = self.getConfigValue("dbproxy", f"SCOUT_SHORT_EXECTIME_{prodSourceLabel}", "jedi")
471
+ if shortExecTime is None:
472
+ shortExecTime = 0
473
+
474
+ # get limit for cpu-inefficient jobs
475
+ lowCpuEff = self.getConfigValue("dbproxy", f"SCOUT_LOW_CPU_EFFICIENCY_{prodSourceLabel}", "jedi")
476
+ if lowCpuEff is None:
477
+ lowCpuEff = 0
478
+
479
+ # cap on diskIO
480
+ capOnDiskIO = self.getConfigValue("dbproxy", "SCOUT_DISK_IO_CAP", "jedi")
481
+ extraInfo["shortExecTime"] = shortExecTime
482
+ extraInfo["cpuEfficiencyCap"] = lowCpuEff
483
+
484
+ # get the size of lib
485
+ varMap = {}
486
+ varMap[":jediTaskID"] = jediTaskID
487
+ varMap[":type"] = "lib"
488
+ varMap[":status"] = "finished"
489
+ self.cur.execute(sqlLIB + comment, varMap)
490
+ resLIB = self.cur.fetchone()
491
+ libSize = None
492
+ if resLIB is not None:
493
+ try:
494
+ (libSize,) = resLIB
495
+ libSize /= 1024 * 1024
496
+ except Exception:
497
+ pass
498
+
499
+ # get files
500
+ varMap = {}
501
+ varMap[":jediTaskID"] = jediTaskID
502
+ varMap[":status"] = "finished"
503
+ if not mergeScout:
504
+ varMap.update(INPUT_TYPES_var_map)
505
+ else:
506
+ varMap.update(MERGE_TYPES_var_map)
507
+ if setPandaID is not None:
508
+ varMap[":usePandaID"] = setPandaID
509
+ self.cur.execute(sqlSCF + comment, varMap)
510
+ resList = self.cur.fetchall()
511
+ # scout succeeded or not
512
+ scoutSucceeded = True
513
+ if not resList:
514
+ scoutSucceeded = False
515
+ tmpLog.debug("no scouts succeeded")
516
+ extraInfo["successRate"] = 0
517
+ elif not mergeScout and task_spec and task_spec.useScout():
518
+ # check scout success rate
519
+ varMap = {":jediTaskID": jediTaskID}
520
+ varMap.update(INPUT_TYPES_var_map)
521
+ self.cur.execute(sqlCSSR + comment, varMap)
522
+ scTotal, scOK, scNG = self.cur.fetchone()
523
+
524
+ if scTotal > 0 and scOK + scNG > 0:
525
+ extraInfo["successRate"] = scOK / (scOK + scNG)
526
+ else:
527
+ extraInfo["successRate"] = 0
528
+ tmpLog.debug(
529
+ f"""scout total={scTotal} finished={scOK} failed={scNG} target_rate={None if scoutSuccessRate is None else scoutSuccessRate/10} actual_rate={extraInfo["successRate"]}"""
530
+ )
531
+ if scoutSuccessRate and scTotal and extraInfo["successRate"] < scoutSuccessRate / 10:
532
+ tmpLog.debug("not enough scouts succeeded")
533
+ scoutSucceeded = False
534
+
535
+ # upper limit
536
+ limitWallTime = 999999999
537
+
538
+ # loop over all files
539
+ outSizeList = []
540
+ outSizeDict = {}
541
+ walltimeList = []
542
+ walltimeDict = {}
543
+ memSizeList = []
544
+ memSizeDict = {}
545
+ leak_list = []
546
+ leak_dict = {}
547
+ leak_x2_list = []
548
+ leak_x2_dict = {}
549
+ workSizeList = []
550
+ cpuTimeList = []
551
+ cpuTimeDict = {}
552
+ ioIntentList = []
553
+ ioIntentDict = {}
554
+ cpuEffList = []
555
+ cpuEffDict = {}
556
+ cpuEffMap = {}
557
+ finishedJobs = []
558
+ inFSizeList = []
559
+ inFSizeMap = {}
560
+ inEventsMap = {}
561
+ corePowerMap = {}
562
+ jMetricsMap = {}
563
+ execTimeMap = {}
564
+ siteMap = {}
565
+ diskIoList = []
566
+ pandaIDList = set()
567
+ totInSizeMap = {}
568
+ masterInSize = {}
569
+ coreCountMap = {}
570
+ pseudoInput = set()
571
+ total_actual_input_size = 0
572
+ for pandaID, fsize, startEvent, endEvent, nEvents, fType in resList:
573
+ pandaIDList.add(pandaID)
574
+ if pandaID not in inFSizeMap:
575
+ inFSizeMap[pandaID] = 0
576
+ # get effective file size
577
+ effectiveFsize = CoreUtils.getEffectiveFileSize(fsize, startEvent, endEvent, nEvents)
578
+ inFSizeMap[pandaID] += effectiveFsize
579
+ # events
580
+ if pandaID not in inEventsMap:
581
+ inEventsMap[pandaID] = 0
582
+ inEventsMap[pandaID] += CoreUtils.getEffectiveNumEvents(startEvent, endEvent, nEvents)
583
+ # master input size
584
+ if pandaID not in masterInSize:
585
+ masterInSize[pandaID] = 0
586
+ masterInSize[pandaID] += fsize
587
+ total_actual_input_size += fsize
588
+ if fType == "pseudo_input":
589
+ pseudoInput.add(pandaID)
590
+
591
+ # get nFiles
592
+ totalJobs = 0
593
+ totFiles = 0
594
+ totFinished = 0
595
+ nNewJobs = 0
596
+ total_jobs_with_event = 0
597
+ if not mergeScout:
598
+ # estimate the number of new jobs with the number of files
599
+ varMap = dict()
600
+ varMap[":jediTaskID"] = jediTaskID
601
+ varMap.update(INPUT_TYPES_var_map)
602
+ self.cur.execute(sqlNumJobs + comment, varMap)
603
+ resNumJobs = self.cur.fetchone()
604
+ if resNumJobs is not None:
605
+ totFiles, totFinished, totUsed = resNumJobs
606
+ if totFinished > 0:
607
+ totalJobs = int(totFiles * len(pandaIDList) // totFinished)
608
+ nNewJobs = int((totFiles - totUsed) * len(pandaIDList) // totFinished)
609
+ # take into account the size limits coming from scouts, dataset boundaries, etc
610
+ if (
611
+ task_spec
612
+ and not task_spec.getNumFilesPerJob()
613
+ and not task_spec.getNumEventsPerJob()
614
+ and not task_spec.getMaxSizePerJob()
615
+ and not task_spec.getMaxNumFilesPerJob()
616
+ ):
617
+ # average input size
618
+ avg_actual_input_size = total_actual_input_size / 1024 / 1024 / len(pandaIDList)
619
+ # scale with actual size / allowed size
620
+ nNewJobs = int(nNewJobs * avg_actual_input_size / InputChunk.maxInputSizeAvalanche)
621
+ totalJobs = int(totalJobs * avg_actual_input_size / InputChunk.maxInputSizeAvalanche)
622
+ # estimate the number of new jobs with size
623
+ var_map = dict()
624
+ var_map[":jediTaskID"] = jediTaskID
625
+ var_map.update(INPUT_TYPES_var_map)
626
+ self.cur.execute(sql_num_jobs_event + comment, var_map)
627
+ res_num_jobs_event = self.cur.fetchone()
628
+ if res_num_jobs_event:
629
+ total_in_event, total_finished_event = res_num_jobs_event
630
+ if total_finished_event is not None and total_finished_event > 0:
631
+ total_jobs_with_event = int(total_in_event * len(pandaIDList) // total_finished_event)
632
+ # take into account size limit for scouts
633
+ if (
634
+ task_spec
635
+ and task_spec.useScout()
636
+ and not task_spec.getNumFilesPerJob()
637
+ and not task_spec.getNumEventsPerJob()
638
+ and not task_spec.getMaxSizePerJob()
639
+ and not task_spec.getMaxNumFilesPerJob()
640
+ ):
641
+ total_jobs_with_event = int(total_jobs_with_event * InputChunk.maxInputSizeScouts / InputChunk.maxInputSizeAvalanche)
642
+ extraInfo["expectedNumJobs"] = totalJobs
643
+ extraInfo["numFinishedJobs"] = len(pandaIDList)
644
+ extraInfo["nFiles"] = totFiles
645
+ extraInfo["nFilesFinished"] = totFinished
646
+ extraInfo["nNewJobs"] = nNewJobs
647
+ extraInfo["expectedNumJobsWithEvent"] = total_jobs_with_event
648
+
649
+ # loop over all jobs
650
+ loopPandaIDs = list(inFSizeMap.keys())
651
+ random.shuffle(loopPandaIDs)
652
+ loopPandaIDs = loopPandaIDs[:1000]
653
+ for loopPandaID in loopPandaIDs:
654
+ totalFSize = inFSizeMap[loopPandaID]
655
+ # get job data
656
+ varMap = {}
657
+ varMap[":pandaID"] = loopPandaID
658
+ varMap[":jobStatus"] = "finished"
659
+ varMap[":jediTaskID"] = jediTaskID
660
+ self.cur.execute(sqlSCDN + comment, varMap)
661
+ resData = self.cur.fetchone()
662
+ if resData is not None:
663
+ eventServiceJob = resData[0]
664
+ jobsetID = resData[1]
665
+ resDataList = [resData]
666
+ # get all ES jobs since input is associated to only one consumer
667
+ if eventServiceJob == EventServiceUtils.esJobFlagNumber:
668
+ varMap = {}
669
+ varMap[":pandaID"] = jobsetID
670
+ varMap[":jobStatus"] = "finished"
671
+ varMap[":jediTaskID"] = jediTaskID
672
+ self.cur.execute(sqlSCDE + comment, varMap)
673
+ resDataList = self.cur.fetchall()
674
+ # loop over all jobs
675
+ for oneResData in resDataList:
676
+ (
677
+ eventServiceJob,
678
+ jobsetID,
679
+ pandaID,
680
+ jobStatus,
681
+ outputFileBytes,
682
+ jobMetrics,
683
+ cpuConsumptionTime,
684
+ actualCoreCount,
685
+ defCoreCount,
686
+ startTime,
687
+ endTime,
688
+ computingSite,
689
+ maxPSS,
690
+ specialHandling,
691
+ nEvents,
692
+ totRBYTES,
693
+ totWBYTES,
694
+ inputFileByte,
695
+ memory_leak,
696
+ memory_leak_x2,
697
+ modificationhost,
698
+ ) = oneResData
699
+
700
+ # event service job
701
+ is_event_service = eventServiceJob == EventServiceUtils.esJobFlagNumber and not EventServiceUtils.isJobCloningSH(specialHandling)
702
+ # add inputSize and nEvents
703
+ if pandaID not in inFSizeMap:
704
+ inFSizeMap[pandaID] = totalFSize
705
+ if pandaID not in inEventsMap or is_event_service:
706
+ inEventsMap[pandaID] = nEvents
707
+ totInSizeMap[pandaID] = inputFileByte
708
+
709
+ siteMap[pandaID] = computingSite
710
+
711
+ # --- core power by host (CPU model) ---
712
+ benchmarks = []
713
+ atlas_site = "NO_SITE" # in case of no match
714
+ if site_mapper:
715
+ atlas_site = site_mapper.getSite(computingSite).pandasite
716
+ benchmarks = self.get_cpu_benchmarks_by_host(atlas_site, modificationhost) or []
717
+
718
+ vals = [v for _, v in benchmarks]
719
+ benchmark_specific = next(
720
+ (v for s, v in benchmarks if s and atlas_site and (s.upper() in atlas_site.upper() or atlas_site.upper() in s.upper())), 0
721
+ )
722
+ benchmark_average = mean(vals) if vals else 0
723
+
724
+ # --- core power by site (fallback) ---
725
+ if not benchmark_specific and not benchmark_average:
726
+ if computingSite not in corePowerMap:
727
+ self.cur.execute(sqlCore + comment, {":site": computingSite})
728
+ row = self.cur.fetchone()
729
+ corePowerMap[computingSite] = float(row[0]) if row and row[0] is not None else None
730
+ corePower = corePowerMap[computingSite]
731
+ else:
732
+ corePower = benchmark_specific or benchmark_average
733
+
734
+ # --- final fallback default ---
735
+ if not corePower or corePower == 0:
736
+ corePower = 10
737
+
738
+ finishedJobs.append(pandaID)
739
+ inFSizeList.append(totalFSize)
740
+ jMetricsMap[pandaID] = jobMetrics
741
+
742
+ # core count
743
+ coreCount = JobUtils.getCoreCount(actualCoreCount, defCoreCount, jobMetrics)
744
+ coreCountMap[pandaID] = coreCount
745
+
746
+ # output size
747
+ tmpWorkSize = 0
748
+ if not is_event_service:
749
+ try:
750
+ try:
751
+ # add size of intermediate files
752
+ if jobMetrics is not None:
753
+ tmpMatch = re.search("workDirSize=(\d+)", jobMetrics)
754
+ tmpWorkSize = int(tmpMatch.group(1))
755
+ tmpWorkSize /= 1024 * 1024
756
+ except Exception:
757
+ pass
758
+ if preOutDiskUnit is None or "Fixed" not in preOutDiskUnit:
759
+ if preOutputScaleWithEvents:
760
+ # scale with events
761
+ if pandaID in inEventsMap and inEventsMap[pandaID] > 0:
762
+ tmpVal = int(math.ceil(float(outputFileBytes) / inEventsMap[pandaID]))
763
+ if pandaID not in inEventsMap or inEventsMap[pandaID] >= 10:
764
+ outSizeList.append(tmpVal)
765
+ outSizeDict[tmpVal] = pandaID
766
+ else:
767
+ # scale with input size
768
+ tmpVal = int(math.ceil(float(outputFileBytes) / totalFSize))
769
+ if pandaID not in inEventsMap or inEventsMap[pandaID] >= 10:
770
+ outSizeList.append(tmpVal)
771
+ outSizeDict[tmpVal] = pandaID
772
+ except Exception:
773
+ pass
774
+
775
+ # execution time
776
+ if eventServiceJob != EventServiceUtils.esMergeJobFlagNumber:
777
+ try:
778
+ tmpVal = cpuConsumptionTime
779
+ walltimeList.append(tmpVal)
780
+ walltimeDict[tmpVal] = pandaID
781
+ except Exception:
782
+ pass
783
+ try:
784
+ execTimeMap[pandaID] = endTime - startTime
785
+ except Exception:
786
+ pass
787
+
788
+ # CPU time
789
+ if eventServiceJob != EventServiceUtils.esMergeJobFlagNumber:
790
+ try:
791
+ if preCpuTimeUnit not in ["HS06sPerEventFixed", "mHS06sPerEventFixed"]:
792
+ tmpVal = JobUtils.getHS06sec(
793
+ startTime, endTime, corePower, coreCount, baseWalltime=preBaseWalltime, cpuEfficiency=preCpuEfficiency
794
+ )
795
+ if pandaID in inEventsMap and inEventsMap[pandaID] > 0:
796
+ tmpVal /= float(inEventsMap[pandaID])
797
+ if (
798
+ pandaID not in inEventsMap
799
+ or inEventsMap[pandaID] >= (10 * coreCount)
800
+ or pandaID in pseudoInput
801
+ or (
802
+ inEventsMap[pandaID] < (10 * coreCount)
803
+ and pandaID in execTimeMap
804
+ and execTimeMap[pandaID] > datetime.timedelta(seconds=6 * 3600)
805
+ )
806
+ ):
807
+ cpuTimeList.append(tmpVal)
808
+ cpuTimeDict[tmpVal] = pandaID
809
+ except Exception:
810
+ pass
811
+
812
+ # IO
813
+ if eventServiceJob != EventServiceUtils.esMergeJobFlagNumber:
814
+ try:
815
+ tmpTimeDelta = endTime - startTime
816
+ tmpVal = totalFSize * 1024.0 + float(outputFileBytes) / 1024.0
817
+ tmpVal = tmpVal / float(tmpTimeDelta.seconds + tmpTimeDelta.days * 24 * 3600)
818
+ tmpVal /= float(coreCount)
819
+ ioIntentList.append(tmpVal)
820
+ ioIntentDict[tmpVal] = pandaID
821
+ except Exception:
822
+ pass
823
+
824
+ # disk IO
825
+ if eventServiceJob != EventServiceUtils.esMergeJobFlagNumber:
826
+ try:
827
+ tmpTimeDelta = endTime - startTime
828
+ tmpVal = totRBYTES + totWBYTES
829
+ tmpVal /= float(tmpTimeDelta.seconds + tmpTimeDelta.days * 24 * 3600)
830
+ diskIoList.append(tmpVal)
831
+ except Exception:
832
+ pass
833
+
834
+ # memory leak
835
+ if eventServiceJob != EventServiceUtils.esMergeJobFlagNumber:
836
+ try:
837
+ memory_leak_core_tmp = float(memory_leak) / float(coreCount)
838
+ memory_leak_core_tmp = int(math.ceil(memory_leak_core_tmp))
839
+ leak_list.append(memory_leak_core_tmp)
840
+ leak_dict[memory_leak_core_tmp] = pandaID
841
+ except Exception:
842
+ pass
843
+ # memory leak chi2 measurement
844
+ try:
845
+ memory_leak_x2_tmp = int(memory_leak_x2)
846
+ leak_x2_list.append(memory_leak_x2_tmp)
847
+ leak_x2_dict[memory_leak_x2_tmp] = pandaID
848
+ except Exception:
849
+ pass
850
+
851
+ # RAM size
852
+ if eventServiceJob != EventServiceUtils.esMergeJobFlagNumber:
853
+ try:
854
+ if preRamUnit == "MBPerCoreFixed":
855
+ pass
856
+ elif preRamUnit == "MBPerCore":
857
+ if maxPSS > 0:
858
+ tmpPSS = maxPSS
859
+ if preBaseRamCount not in [0, None]:
860
+ tmpPSS -= preBaseRamCount * 1024
861
+ tmpPSS = float(tmpPSS) / float(coreCount)
862
+ tmpPSS = int(math.ceil(tmpPSS))
863
+ memSizeList.append(tmpPSS)
864
+ memSizeDict[tmpPSS] = pandaID
865
+ else:
866
+ if maxPSS > 0:
867
+ tmpMEM = maxPSS
868
+ else:
869
+ tmpMatch = re.search("vmPeakMax=(\d+)", jobMetrics)
870
+ tmpMEM = int(tmpMatch.group(1))
871
+ memSizeList.append(tmpMEM)
872
+ memSizeDict[tmpMEM] = pandaID
873
+ except Exception:
874
+ pass
875
+
876
+ # use lib size as workdir size
877
+ if tmpWorkSize is None or (libSize is not None and libSize > tmpWorkSize):
878
+ tmpWorkSize = libSize
879
+ if tmpWorkSize is not None:
880
+ workSizeList.append(tmpWorkSize)
881
+
882
+ # CPU efficiency
883
+ if eventServiceJob != EventServiceUtils.esMergeJobFlagNumber:
884
+ try:
885
+ tmpTimeDelta = endTime - startTime
886
+ float(tmpTimeDelta.seconds + tmpTimeDelta.days * 24 * 3600)
887
+ tmpCpuEff = int(
888
+ math.ceil(float(cpuConsumptionTime) / (coreCount * float(tmpTimeDelta.seconds + tmpTimeDelta.days * 24 * 3600)) * 100)
889
+ )
890
+ cpuEffList.append(tmpCpuEff)
891
+ cpuEffDict[tmpCpuEff] = pandaID
892
+ cpuEffMap[pandaID] = tmpCpuEff
893
+ except Exception:
894
+ pass
895
+
896
+ # add tags
897
+ def addTag(jobTagMap, idDict, value, tagStr):
898
+ if value in idDict:
899
+ tmpPandaID = idDict[value]
900
+ if tmpPandaID not in jobTagMap:
901
+ jobTagMap[tmpPandaID] = []
902
+ if tagStr not in jobTagMap[tmpPandaID]:
903
+ jobTagMap[tmpPandaID].append(tagStr)
904
+
905
+ # calculate values
906
+ jobTagMap = {}
907
+ if outSizeList != []:
908
+ median, origValues = CoreUtils.percentile(outSizeList, 75, outSizeDict)
909
+ for origValue in origValues:
910
+ addTag(jobTagMap, outSizeDict, origValue, "outDiskCount")
911
+ median /= 1024
912
+ # upper limit 10MB output per 1MB input
913
+ upperLimit = 10 * 1024
914
+ if median > upperLimit:
915
+ median = upperLimit
916
+ returnMap["outDiskCount"] = int(median)
917
+ if preOutputScaleWithEvents:
918
+ returnMap["outDiskUnit"] = "kBPerEvent"
919
+ else:
920
+ returnMap["outDiskUnit"] = "kB"
921
+
922
+ if walltimeList != []:
923
+ maxWallTime = max(walltimeList)
924
+ extraInfo["maxCpuConsumptionTime"] = maxWallTime
925
+ extraInfo["maxExecTime"] = execTimeMap[walltimeDict[maxWallTime]]
926
+ extraInfo["defCoreCount"] = coreCountMap[walltimeDict[maxWallTime]]
927
+ addTag(jobTagMap, walltimeDict, maxWallTime, "walltime")
928
+ # cut off of 60min
929
+ if maxWallTime < 60 * 60:
930
+ maxWallTime = 0
931
+ median = float(maxWallTime) / float(max(inFSizeList)) * 1.5
932
+ median = math.ceil(median)
933
+ returnMap["walltime"] = int(median)
934
+ # use preset value if larger
935
+ if preWalltime is not None and (preWalltime > returnMap["walltime"] or preWalltime < 0):
936
+ returnMap["walltime"] = preWalltime
937
+ # upper limit
938
+ if returnMap["walltime"] > limitWallTime:
939
+ returnMap["walltime"] = limitWallTime
940
+ returnMap["walltimeUnit"] = "kSI2kseconds"
941
+
942
+ if cpuTimeList != []:
943
+ maxCpuTime, origValues = CoreUtils.percentile(cpuTimeList, cpuTimeRank, cpuTimeDict)
944
+ for origValue in origValues:
945
+ addTag(jobTagMap, cpuTimeDict, origValue, "cpuTime")
946
+ try:
947
+ extraInfo["execTime"] = execTimeMap[cpuTimeDict[origValue]]
948
+ except Exception:
949
+ pass
950
+ maxCpuTime *= 1.5
951
+ if maxCpuTime < 10:
952
+ maxCpuTime *= 1000
953
+ returnMap["cpuTimeUnit"] = "mHS06sPerEvent"
954
+ if extraInfo["oldCpuTime"]:
955
+ extraInfo["oldCpuTime"] = int(extraInfo["oldCpuTime"] * 1000)
956
+ elif preCpuTimeUnit is not None:
957
+ # for mHS06sPerEvent -> HS06sPerEvent
958
+ returnMap["cpuTimeUnit"] = "HS06sPerEvent"
959
+ maxCpuTime = int(math.ceil(maxCpuTime))
960
+ returnMap["cpuTime"] = maxCpuTime
961
+
962
+ if ioIntentList != []:
963
+ maxIoIntent = max(ioIntentList)
964
+ addTag(jobTagMap, ioIntentDict, maxIoIntent, "ioIntensity")
965
+ maxIoIntent = int(math.ceil(maxIoIntent))
966
+ returnMap["ioIntensity"] = maxIoIntent
967
+ returnMap["ioIntensityUnit"] = "kBPerS"
968
+
969
+ if diskIoList != []:
970
+ aveDiskIo = sum(diskIoList) // len(diskIoList)
971
+ aveDiskIo = int(math.ceil(aveDiskIo))
972
+ if capOnDiskIO is not None:
973
+ aveDiskIo = min(aveDiskIo, capOnDiskIO)
974
+ returnMap["diskIO"] = aveDiskIo
975
+ returnMap["diskIOUnit"] = "kBPerS"
976
+
977
+ if leak_list:
978
+ ave_leak = int(math.ceil(sum(leak_list) / len(leak_list)))
979
+ returnMap["memory_leak_core"] = ave_leak
980
+
981
+ if leak_x2_list:
982
+ ave_leak_x2 = int(math.ceil(sum(leak_x2_list) / len(leak_x2_list)))
983
+ returnMap["memory_leak_x2"] = ave_leak_x2
984
+
985
+ if memSizeList != []:
986
+ memVal, origValues = CoreUtils.percentile(memSizeList, ramCountRank, memSizeDict)
987
+ for origValue in origValues:
988
+ addTag(jobTagMap, memSizeDict, origValue, "ramCount")
989
+ memVal = memVal * (100 + ramCountMargin) // 100
990
+ memVal /= 1024
991
+ memVal = int(memVal)
992
+ if memVal < 0:
993
+ memVal = 1
994
+ if minRamCount is not None and minRamCount > memVal:
995
+ memVal = minRamCount
996
+ if preRamUnit == "MBPerCore":
997
+ returnMap["ramUnit"] = preRamUnit
998
+ returnMap["ramCount"] = memVal
999
+ elif preRamUnit == "MBPerCoreFixed":
1000
+ returnMap["ramUnit"] = preRamUnit
1001
+ else:
1002
+ returnMap["ramUnit"] = "MB"
1003
+ returnMap["ramCount"] = memVal
1004
+
1005
+ if workSizeList != []:
1006
+ median = max(workSizeList)
1007
+ returnMap["workDiskCount"] = int(median)
1008
+ returnMap["workDiskUnit"] = "MB"
1009
+ # use preset value if larger
1010
+ if preWorkDiskCount is not None and preWorkDiskCount > returnMap["workDiskCount"]:
1011
+ returnMap["workDiskCount"] = preWorkDiskCount
1012
+
1013
+ if cpuEffList != []:
1014
+ minCpuEfficiency = int(numpy.median(cpuEffList))
1015
+ addTag(jobTagMap, cpuEffDict, minCpuEfficiency, "cpuEfficiency")
1016
+ extraInfo["minCpuEfficiency"] = minCpuEfficiency
1017
+
1018
+ n_all_short_jobs = 0
1019
+ n_short_jobs_with_copy_to_scratch = 0
1020
+ total_jobs_including_short_jobs = 0
1021
+ longestShortExecTime = 0
1022
+ for tmpPandaID, tmpExecTime in execTimeMap.items():
1023
+ is_copy_scratch = False
1024
+ if tmpExecTime <= datetime.timedelta(minutes=shortExecTime):
1025
+ longestShortExecTime = max(longestShortExecTime, tmpExecTime.total_seconds())
1026
+ if site_mapper and task_spec:
1027
+ # ignore if the site enforces to use copy-to-scratch
1028
+ tmpSiteSpec = site_mapper.getSite(siteMap[tmpPandaID])
1029
+ if not task_spec.useLocalIO() and not CoreUtils.use_direct_io_for_job(task_spec, tmpSiteSpec, None):
1030
+ n_short_jobs_with_copy_to_scratch += 1
1031
+ n_all_short_jobs += 1
1032
+ total_jobs_including_short_jobs += 1
1033
+ extraInfo["n_all_short_jobs"] = n_all_short_jobs
1034
+ extraInfo["n_short_jobs_with_copy_to_scratch"] = n_short_jobs_with_copy_to_scratch
1035
+ extraInfo["total_jobs_including_short_jobs"] = total_jobs_including_short_jobs
1036
+ extraInfo["longestShortExecTime"] = longestShortExecTime
1037
+ nInefficientJobs = 0
1038
+ for tmpPandaID, tmpCpuEff in cpuEffMap.items():
1039
+ if tmpCpuEff < lowCpuEff:
1040
+ nInefficientJobs += 1
1041
+ extraInfo["nInefficientJobs"] = nInefficientJobs
1042
+ extraInfo["nTotalForIneff"] = len(cpuEffMap)
1043
+ # tag jobs
1044
+ if flagJob:
1045
+ for tmpPandaID, tmpTags in jobTagMap.items():
1046
+ self.updateJobMetrics_JEDI(jediTaskID, tmpPandaID, jMetricsMap[tmpPandaID], tmpTags)
1047
+ # reset NG
1048
+ taskSpec = JediTaskSpec()
1049
+ taskSpec.splitRule = splitRule
1050
+ if not mergeScout and taskSpec.getTgtMaxOutputForNG() is not None and "outDiskCount" in returnMap:
1051
+ # look for PandaID for outDiskCount
1052
+ for tmpPandaID, tmpTags in jobTagMap.items():
1053
+ if "outDiskCount" in tmpTags:
1054
+ # get total and the largest output fsize
1055
+ sqlBig = f"SELECT SUM(fsize) FROM {panda_config.schemaPANDA}.filesTable4 WHERE PandaID=:PandaID AND type=:type GROUP BY dataset "
1056
+ varMap = dict()
1057
+ varMap[":PandaID"] = tmpPandaID
1058
+ varMap[":type"] = "output"
1059
+ self.cur.execute(sqlBig + comment, varMap)
1060
+ resBig = self.cur.fetchall()
1061
+ outTotal = 0
1062
+ outBig = 0
1063
+ for (tmpFsize,) in resBig:
1064
+ outTotal += tmpFsize
1065
+ if tmpFsize > outBig:
1066
+ outBig = tmpFsize
1067
+ if outTotal * outBig > 0:
1068
+ # get NG
1069
+ taskSpec.outDiskCount = returnMap["outDiskCount"]
1070
+ taskSpec.outDiskUnit = returnMap["outDiskUnit"]
1071
+ expectedOutSize = outTotal * taskSpec.getTgtMaxOutputForNG() * 1024 * 1024 * 1024 // outBig
1072
+ outDiskCount = taskSpec.getOutDiskSize()
1073
+ if "workDiskCount" in returnMap:
1074
+ taskSpec.workDiskCount = returnMap["workDiskCount"]
1075
+ else:
1076
+ taskSpec.workDiskCount = preWorkDiskCount
1077
+ taskSpec.workDiskUnit = "MB"
1078
+ workDiskCount = taskSpec.getWorkDiskSize()
1079
+ if outDiskCount == 0:
1080
+ # to avoid zero-division
1081
+ outDiskCount = 1
1082
+ scaleFactor = expectedOutSize // outDiskCount
1083
+ if preOutputScaleWithEvents:
1084
+ # scaleFactor is num of events
1085
+ try:
1086
+ expectedInSize = (
1087
+ (inFSizeMap[tmpPandaID] + totInSizeMap[tmpPandaID] - masterInSize[tmpPandaID]) * scaleFactor // inEventsMap[tmpPandaID]
1088
+ )
1089
+ newNG = expectedOutSize + expectedInSize + workDiskCount - InputChunk.defaultOutputSize
1090
+ except Exception:
1091
+ newNG = None
1092
+ else:
1093
+ # scaleFactor is input size
1094
+ newNG = expectedOutSize + scaleFactor * (1024 * 1024) - InputChunk.defaultOutputSize
1095
+ if newNG is not None:
1096
+ newNG /= 1024 * 1024 * 1024
1097
+ if newNG <= 0:
1098
+ newNG = 1
1099
+ maxNG = 100
1100
+ if newNG > maxNG:
1101
+ newNG = maxNG
1102
+ returnMap["newNG"] = int(newNG)
1103
+ if useTransaction:
1104
+ # commit
1105
+ if not self._commit():
1106
+ raise RuntimeError("Commit error")
1107
+ # filtered dump
1108
+ if scoutSucceeded and not mergeScout and len(returnMap) > 0:
1109
+ tmpMsg = f"scouts got for jediTaskID={jediTaskID} "
1110
+ tmpKeys = sorted(returnMap.keys())
1111
+ for tmpKey in tmpKeys:
1112
+ tmpMsg += f"{tmpKey}={returnMap[tmpKey]} "
1113
+ for tmpPandaID, tmpTags in jobTagMap.items():
1114
+ for tmpTag in tmpTags:
1115
+ tmpMsg += f"{tmpTag}_by={tmpPandaID} "
1116
+ tmpLog.info(tmpMsg[:-1])
1117
+ # return
1118
+ tmpLog.debug(f"succeeded={scoutSucceeded} data={str(returnMap)} extra={str(extraInfo)} tag={jobTagMap}")
1119
+ return scoutSucceeded, returnMap, extraInfo
1120
+
1121
+ # set scout job data
1122
+ def setScoutJobData_JEDI(self, taskSpec, useCommit, useExhausted, site_mapper):
1123
+ comment = " /* JediDBProxy.setScoutJobData_JEDI */"
1124
+ jediTaskID = taskSpec.jediTaskID
1125
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID} label={taskSpec.prodSourceLabel}")
1126
+ tmpLog.debug("start")
1127
+ # get thresholds for exausted
1128
+ ramThr = self.getConfigValue("dbproxy", "RAM_THR_EXAUSTED", "jedi")
1129
+ if ramThr is None:
1130
+ ramThr = 4
1131
+ ramThr *= 1000
1132
+ # send tasks to exhausted when task.successRate > rate >= thr
1133
+ minNumOkScoutsForExhausted = self.getConfigValue("dbproxy", f"SCOUT_MIN_OK_RATE_EXHAUSTED_{taskSpec.prodSourceLabel}", "jedi")
1134
+ scoutSuccessRate = taskSpec.getScoutSuccessRate()
1135
+ if scoutSuccessRate and minNumOkScoutsForExhausted:
1136
+ if scoutSuccessRate > minNumOkScoutsForExhausted * 10:
1137
+ scoutSuccessRate = minNumOkScoutsForExhausted * 10
1138
+ else:
1139
+ minNumOkScoutsForExhausted = None
1140
+ if useCommit:
1141
+ # begin transaction
1142
+ self.conn.begin()
1143
+ task_params_str = self.getTaskParamsWithID_JEDI(jediTaskID, use_commit=False)
1144
+ task_params_map = json.loads(task_params_str)
1145
+ # set average job data
1146
+ scoutSucceeded, scoutData, extraInfo = self.getScoutJobData_JEDI(
1147
+ jediTaskID,
1148
+ scoutSuccessRate=scoutSuccessRate,
1149
+ flagJob=True,
1150
+ site_mapper=site_mapper,
1151
+ task_spec=taskSpec,
1152
+ task_params_map=task_params_map,
1153
+ )
1154
+ # sql to update task data
1155
+ if scoutData != {}:
1156
+ varMap = {}
1157
+ varMap[":jediTaskID"] = jediTaskID
1158
+ sqlTSD = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET "
1159
+ for scoutKey, scoutVal in scoutData.items():
1160
+ # skip new NG
1161
+ if scoutKey in ["newNG"]:
1162
+ continue
1163
+ tmpScoutKey = f":{scoutKey}"
1164
+ varMap[tmpScoutKey] = scoutVal
1165
+ sqlTSD += f"{scoutKey}={tmpScoutKey},"
1166
+ sqlTSD = sqlTSD[:-1]
1167
+ sqlTSD += " WHERE jediTaskID=:jediTaskID "
1168
+ tmpLog.debug(sqlTSD + comment + str(varMap))
1169
+ self.cur.execute(sqlTSD + comment, varMap)
1170
+ # update NG
1171
+ if "newNG" in scoutData:
1172
+ taskSpec.setSplitRule("nGBPerJob", str(scoutData["newNG"]))
1173
+ varMap = {}
1174
+ varMap[":jediTaskID"] = jediTaskID
1175
+ varMap[":splitRule"] = taskSpec.splitRule
1176
+ sqlTSL = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET splitRule=:splitRule "
1177
+ sqlTSL += " WHERE jediTaskID=:jediTaskID "
1178
+ tmpLog.debug(sqlTSL + comment + str(varMap))
1179
+ self.cur.execute(sqlTSL + comment, varMap)
1180
+
1181
+ # set average merge job data
1182
+ mergeScoutSucceeded = None
1183
+ if taskSpec.mergeOutput():
1184
+ mergeScoutSucceeded, mergeScoutData, mergeExtraInfo = self.getScoutJobData_JEDI(jediTaskID, mergeScout=True, task_params_map=task_params_map)
1185
+ if mergeScoutData != {}:
1186
+ varMap = {}
1187
+ varMap[":jediTaskID"] = jediTaskID
1188
+ sqlTSD = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET "
1189
+ for mergeScoutKey, mergeScoutVal in mergeScoutData.items():
1190
+ # only walltime and ramCount
1191
+ if not mergeScoutKey.startswith("walltime") and not mergeScoutKey.startswith("ram"):
1192
+ continue
1193
+ tmpScoutKey = f":{mergeScoutKey}"
1194
+ varMap[tmpScoutKey] = mergeScoutVal
1195
+ sqlTSD += f"merge{mergeScoutKey}={tmpScoutKey},"
1196
+ sqlTSD = sqlTSD[:-1]
1197
+ sqlTSD += " WHERE jediTaskID=:jediTaskID "
1198
+ tmpLog.debug(sqlTSD + comment + str(varMap))
1199
+ self.cur.execute(sqlTSD + comment, varMap)
1200
+
1201
+ # go to exhausted if necessary
1202
+ nNewJobsCutoff = 20
1203
+ if useExhausted and scoutSucceeded and extraInfo["nNewJobs"] > nNewJobsCutoff:
1204
+ # check cpuTime
1205
+ if taskSpec.useHS06() and "cpuTime" in scoutData and "execTime" in extraInfo:
1206
+ minExecTime = 24
1207
+ wrong_cputime_thr = self.getConfigValue("dbproxy", "SCOUT_WRONG_CPUTIME_THRESHOLD", "jedi")
1208
+ if wrong_cputime_thr is None:
1209
+ wrong_cputime_thr = 2
1210
+ if (
1211
+ wrong_cputime_thr > 0
1212
+ and extraInfo["oldCpuTime"] not in [0, None]
1213
+ and scoutData["cpuTime"] > wrong_cputime_thr * extraInfo["oldCpuTime"]
1214
+ and extraInfo["execTime"] > datetime.timedelta(hours=minExecTime)
1215
+ ):
1216
+ errMsg = f"""#KV #ATM action=set_exhausted reason=scout_cpuTime ({scoutData["cpuTime"]}) is larger than {wrong_cputime_thr}*task_cpuTime ({extraInfo["oldCpuTime"]}) and execTime ({extraInfo["execTime"]}) > {minExecTime} hours"""
1217
+ tmpLog.info(errMsg)
1218
+ taskSpec.setErrDiag(errMsg)
1219
+ taskSpec.status = "exhausted"
1220
+
1221
+ # check ramCount
1222
+ if taskSpec.status != "exhausted":
1223
+ if (
1224
+ taskSpec.ramPerCore()
1225
+ and "ramCount" in scoutData
1226
+ and extraInfo["oldRamCount"] is not None
1227
+ and extraInfo["oldRamCount"] < ramThr < scoutData["ramCount"]
1228
+ ):
1229
+ errMsg = f"#KV #ATM action=set_exhausted reason=scout_ramCount {scoutData['ramCount']} MB is larger than {ramThr} MB "
1230
+ errMsg += f"while requested task_ramCount {extraInfo['oldRamCount']} MB is less than {ramThr} MB"
1231
+ tmpLog.info(errMsg)
1232
+ taskSpec.setErrDiag(errMsg)
1233
+ taskSpec.status = "exhausted"
1234
+
1235
+ # check memory leak
1236
+ if taskSpec.status != "exhausted":
1237
+ memory_leak_core_max = self.getConfigValue("dbproxy", f"SCOUT_MEM_LEAK_PER_CORE_{taskSpec.prodSourceLabel}", "jedi")
1238
+ memory_leak_core = scoutData.get("memory_leak_core")
1239
+ memory_leak_x2 = scoutData.get("memory_leak_x2") # TODO: decide what to do with it
1240
+ if memory_leak_core and memory_leak_core_max and memory_leak_core > memory_leak_core_max:
1241
+ errMsg = f"#ATM #KV action=set_exhausted since reason=scout_memory_leak {memory_leak_core} is larger than {memory_leak_core_max}"
1242
+ tmpLog.info(errMsg)
1243
+ taskSpec.setErrDiag(errMsg)
1244
+ # taskSpec.status = 'exhausted'
1245
+
1246
+ # short job check
1247
+ sl_changed = False
1248
+ if taskSpec.status != "exhausted":
1249
+ # get exectime threshold for exhausted
1250
+ maxShortJobs = self.getConfigValue("dbproxy", f"SCOUT_NUM_SHORT_{taskSpec.prodSourceLabel}", "jedi")
1251
+ shortJobCutoff = self.getConfigValue("dbproxy", f"SCOUT_THR_SHORT_{taskSpec.prodSourceLabel}", "jedi")
1252
+ if maxShortJobs and shortJobCutoff:
1253
+ # many short jobs w/o copy-to-scratch
1254
+ had_many_short_jobs = (
1255
+ extraInfo["total_jobs_including_short_jobs"] > 0
1256
+ and extraInfo["n_all_short_jobs"] / extraInfo["total_jobs_including_short_jobs"] >= maxShortJobs / 10
1257
+ )
1258
+ if had_many_short_jobs:
1259
+ toExhausted = True
1260
+ # check expected number of jobs
1261
+ if shortJobCutoff and min(extraInfo["expectedNumJobs"], extraInfo["expectedNumJobsWithEvent"]) < shortJobCutoff:
1262
+ tmpLog.debug(
1263
+ "not to set exhausted or change split rule since expect num of jobs "
1264
+ "min({} file-based, {} event-based) is less than {}".format(
1265
+ extraInfo["expectedNumJobs"], extraInfo["expectedNumJobsWithEvent"], shortJobCutoff
1266
+ )
1267
+ )
1268
+ toExhausted = False
1269
+ # remove wrong rules
1270
+ if toExhausted and self.getConfigValue("dbproxy", f"SCOUT_CHANGE_SR_{taskSpec.prodSourceLabel}", "jedi"):
1271
+ updateSL = []
1272
+ removeSL = []
1273
+ if taskSpec.getNumFilesPerJob() is not None:
1274
+ taskSpec.removeNumFilesPerJob()
1275
+ removeSL.append("nFilesPerJob")
1276
+ if taskSpec.getMaxSizePerJob() is not None:
1277
+ taskSpec.removeMaxSizePerJob()
1278
+ removeSL.append("nGBPerJob")
1279
+ MAX_NUM_FILES = 200
1280
+ if taskSpec.getMaxNumFilesPerJob() is not None and taskSpec.getMaxNumFilesPerJob() < MAX_NUM_FILES:
1281
+ taskSpec.setMaxNumFilesPerJob(str(MAX_NUM_FILES))
1282
+ updateSL.append("MF")
1283
+ if updateSL or removeSL:
1284
+ sl_changed = True
1285
+ tmpMsg = "#KV #ATM action=change_split_rule reason=many_shorter_jobs"
1286
+ if removeSL:
1287
+ tmpMsg += f" removed {','.join(removeSL)},"
1288
+ if updateSL:
1289
+ tmpMsg += f" changed {','.join(updateSL)},"
1290
+ tmpMsg = tmpMsg[:-1]
1291
+ tmpLog.info(tmpMsg)
1292
+ varMap = {}
1293
+ varMap[":jediTaskID"] = jediTaskID
1294
+ varMap[":splitRule"] = taskSpec.splitRule
1295
+ sqlTSL = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET splitRule=:splitRule "
1296
+ sqlTSL += " WHERE jediTaskID=:jediTaskID "
1297
+ tmpLog.debug(sqlTSL + comment + str(varMap))
1298
+ self.cur.execute(sqlTSL + comment, varMap)
1299
+ toExhausted = False
1300
+ # check scaled walltime
1301
+ if toExhausted:
1302
+ scMsg = ""
1303
+ if taskSpec.useScout():
1304
+ scaled_max_walltime = extraInfo["longestShortExecTime"]
1305
+ scaled_max_walltime *= InputChunk.maxInputSizeAvalanche / InputChunk.maxInputSizeScouts
1306
+ scaled_max_walltime = int(scaled_max_walltime / 60)
1307
+ if scaled_max_walltime > extraInfo["shortExecTime"]:
1308
+ tmpLog.debug(
1309
+ "not to set exhausted since scaled execution time ({}) is longer "
1310
+ "than {} min".format(scaled_max_walltime, extraInfo["shortExecTime"])
1311
+ )
1312
+ toExhausted = False
1313
+ else:
1314
+ scMsg = " and scaled execution time ({} = walltime * {}/{}) less than {} min".format(
1315
+ scaled_max_walltime, InputChunk.maxInputSizeAvalanche, InputChunk.maxInputSizeScouts, extraInfo["shortExecTime"]
1316
+ )
1317
+ # check if copy-to-scratch was imposed
1318
+ if toExhausted:
1319
+ if (
1320
+ extraInfo["total_jobs_including_short_jobs"] > 0
1321
+ and (extraInfo["n_all_short_jobs"] - extraInfo["n_short_jobs_with_copy_to_scratch"])
1322
+ / extraInfo["total_jobs_including_short_jobs"]
1323
+ < maxShortJobs / 10
1324
+ ):
1325
+ tmpLog.debug(
1326
+ "not to set exhausted since {}/{} scout jobs were enforced to run with copy-to-scratch".format(
1327
+ extraInfo["n_short_jobs_with_copy_to_scratch"], extraInfo["total_jobs_including_short_jobs"]
1328
+ )
1329
+ )
1330
+ toExhausted = False
1331
+ # go to exhausted
1332
+ if toExhausted:
1333
+ errMsg = "#ATM #KV action=set_exhausted since reason=many_shorter_jobs "
1334
+ errMsg += (
1335
+ "{}/{} jobs (greater than {}0%, excluding {} jobs forced "
1336
+ "to run with copy-to-scratch) ran faster than {} min, "
1337
+ "and the expected num of jobs min({} file-based, {} event-based) exceeds {} {}".format(
1338
+ (extraInfo["n_all_short_jobs"] - extraInfo["n_short_jobs_with_copy_to_scratch"]),
1339
+ extraInfo["total_jobs_including_short_jobs"],
1340
+ maxShortJobs,
1341
+ extraInfo["n_short_jobs_with_copy_to_scratch"],
1342
+ extraInfo["shortExecTime"],
1343
+ extraInfo["expectedNumJobs"],
1344
+ extraInfo["expectedNumJobsWithEvent"],
1345
+ shortJobCutoff,
1346
+ scMsg,
1347
+ )
1348
+ )
1349
+ tmpLog.info(errMsg)
1350
+ taskSpec.setErrDiag(errMsg)
1351
+ taskSpec.status = "exhausted"
1352
+
1353
+ # CPU efficiency
1354
+ if taskSpec.status != "exhausted" and not sl_changed:
1355
+ # OK if minCpuEfficiency is satisfied
1356
+ if taskSpec.getMinCpuEfficiency() and extraInfo["minCpuEfficiency"] >= taskSpec.getMinScoutEfficiency():
1357
+ pass
1358
+ else:
1359
+ # get inefficiency threshold for exhausted
1360
+ maxIneffJobs = self.getConfigValue("dbproxy", f"SCOUT_NUM_CPU_INEFFICIENT_{taskSpec.prodSourceLabel}", "jedi")
1361
+ ineffJobCutoff = self.getConfigValue("dbproxy", f"SCOUT_THR_CPU_INEFFICIENT_{taskSpec.prodSourceLabel}", "jedi")
1362
+ tmp_skip = False
1363
+ # check if low CPU efficiency is due to high IO intensity, and if so, allow more inefficient jobs
1364
+ max_io_intensity_for_exhausted = self.getConfigValue("dbproxy", f"SCOUT_MAX_IO_INTENSITY_FOR_EXHAUSTED_{taskSpec.prodSourceLabel}", "jedi")
1365
+ io_intensity = scoutData.get("ioIntensity", 0)
1366
+ if max_io_intensity_for_exhausted is not None and max_io_intensity_for_exhausted < io_intensity:
1367
+ high_io_intensity = True
1368
+ errMsg = (
1369
+ f"not to set exhausted since high IO intensity ({io_intensity} kBPerS) is greater than {max_io_intensity_for_exhausted}, although "
1370
+ )
1371
+ else:
1372
+ high_io_intensity = False
1373
+ errMsg = "#ATM #KV action=set_exhausted since reason=low_efficiency "
1374
+ if taskSpec.getMinCpuEfficiency() and extraInfo["minCpuEfficiency"] < taskSpec.getMinCpuEfficiency():
1375
+ tmp_skip = True
1376
+ errMsg += f"lowest CPU efficiency {extraInfo['minCpuEfficiency']} is less than getMinCpuEfficiency={taskSpec.getMinCpuEfficiency()}"
1377
+ elif (
1378
+ maxIneffJobs
1379
+ and extraInfo["nTotalForIneff"] > 0
1380
+ and extraInfo["nInefficientJobs"] / extraInfo["nTotalForIneff"] >= maxIneffJobs / 10
1381
+ and ineffJobCutoff
1382
+ and max(extraInfo["expectedNumJobs"], extraInfo["expectedNumJobsWithEvent"]) > ineffJobCutoff
1383
+ ):
1384
+ tmp_skip = True
1385
+ errMsg += (
1386
+ "{}/{} jobs (greater than {}/10) had lower CPU efficiencies than {} "
1387
+ "and expected num of jobs max({} file-based est, {} event-based est) is larger than {}".format(
1388
+ extraInfo["nInefficientJobs"],
1389
+ extraInfo["nTotalForIneff"],
1390
+ maxIneffJobs,
1391
+ extraInfo["cpuEfficiencyCap"],
1392
+ extraInfo["expectedNumJobs"],
1393
+ extraInfo["expectedNumJobsWithEvent"],
1394
+ ineffJobCutoff,
1395
+ )
1396
+ )
1397
+ if tmp_skip:
1398
+ tmpLog.info(errMsg)
1399
+ # set exhausted if not due to high IO intensity, continue otherwise
1400
+ if not high_io_intensity:
1401
+ taskSpec.setErrDiag(errMsg)
1402
+ taskSpec.status = "exhausted"
1403
+
1404
+ # cpu abuse
1405
+ if taskSpec.status != "exhausted":
1406
+ try:
1407
+ abuseOffset = 2
1408
+ if extraInfo["maxCpuConsumptionTime"] > extraInfo["maxExecTime"].total_seconds() * extraInfo["defCoreCount"] * abuseOffset:
1409
+ errMsg = f"#ATM #KV action=set_exhausted since reason=over_cpu_consumption {extraInfo['maxCpuConsumptionTime']} sec "
1410
+ errMsg += "is larger than jobDuration*coreCount*safety ({0}*{1}*{2}). ".format(
1411
+ extraInfo["maxExecTime"].total_seconds(), extraInfo["defCoreCount"], abuseOffset
1412
+ )
1413
+ errMsg += "Running multi-core payload on single core queues? #ATM"
1414
+ tmpLog.info(errMsg)
1415
+ taskSpec.setErrDiag(errMsg)
1416
+ taskSpec.status = "exhausted"
1417
+ except Exception:
1418
+ tmpLog.error("failed to check CPU abuse")
1419
+ pass
1420
+
1421
+ # low success rate
1422
+ if taskSpec.status != "exhausted" and minNumOkScoutsForExhausted:
1423
+ if taskSpec.getScoutSuccessRate() and "successRate" in extraInfo and extraInfo["successRate"] < taskSpec.getScoutSuccessRate() / 10:
1424
+ errMsg = "#ATM #KV action=set_exhausted since reason=low_success_rate between {} and {} ".format(
1425
+ minNumOkScoutsForExhausted, taskSpec.getScoutSuccessRate() / 10
1426
+ )
1427
+ tmpLog.info(errMsg)
1428
+ taskSpec.setErrDiag(errMsg)
1429
+ taskSpec.status = "exhausted"
1430
+
1431
+ if useCommit:
1432
+ # commit
1433
+ if not self._commit():
1434
+ raise RuntimeError("Commit error")
1435
+ # reset the task resource type
1436
+ try:
1437
+ self.reset_resource_type_task(jediTaskID, useCommit)
1438
+ except Exception:
1439
+ tmpLog.error(f"reset_resource_type_task excepted with: {traceback.format_exc()}")
1440
+
1441
+ return scoutSucceeded, mergeScoutSucceeded
1442
+
1443
+ # update input datasets stage-in done (according to message from iDDS, called by other methods, etc.)
1444
+ def updateInputDatasetsStaged_JEDI(self, jeditaskid, scope, dsnames_dict=None, use_commit=True, by=None):
1445
+ comment = " /* JediDBProxy.updateInputDatasetsStaged_JEDI */"
1446
+ tmp_tag = f"jediTaskID={jeditaskid}"
1447
+ if by:
1448
+ tmp_tag += f" by={by}"
1449
+ tmpLog = self.create_tagged_logger(comment, tmp_tag)
1450
+ tmpLog.debug("start")
1451
+ try:
1452
+ # update all files when scope is None
1453
+ if scope is None:
1454
+ dsnames_dict = [None]
1455
+ retVal = 0
1456
+ # varMap
1457
+ varMap = dict()
1458
+ varMap[":jediTaskID"] = jeditaskid
1459
+ varMap[":type1"] = "input"
1460
+ varMap[":type2"] = "pseudo_input"
1461
+ varMap[":old_status"] = "staging"
1462
+ varMap[":new_status"] = "pending"
1463
+ # sql with dataset name
1464
+ sqlUD = (
1465
+ "UPDATE {0}.JEDI_Dataset_Contents "
1466
+ "SET status=:new_status "
1467
+ "WHERE jediTaskID=:jediTaskID "
1468
+ "AND datasetID IN ("
1469
+ "SELECT datasetID FROM {0}.JEDI_Datasets "
1470
+ "WHERE jediTaskID=:jediTaskID AND type IN (:type1,:type2) AND datasetName=:datasetName) "
1471
+ "AND status=:old_status "
1472
+ ).format(panda_config.schemaJEDI)
1473
+ # sql without dataset name
1474
+ sql_wo_dataset_name = (
1475
+ "UPDATE {0}.JEDI_Dataset_Contents "
1476
+ "SET status=:new_status "
1477
+ "WHERE jediTaskID=:jediTaskID "
1478
+ "AND datasetID IN ("
1479
+ "SELECT datasetID FROM {0}.JEDI_Datasets "
1480
+ "WHERE jediTaskID=:jediTaskID AND type IN (:type1,:type2)) "
1481
+ "AND status=:old_status "
1482
+ ).format(panda_config.schemaJEDI)
1483
+ # begin transaction
1484
+ if use_commit:
1485
+ self.conn.begin()
1486
+ for dsname in dsnames_dict:
1487
+ if scope:
1488
+ varMap[":datasetName"] = f"{scope}:{dsname}"
1489
+ sql = sqlUD
1490
+ else:
1491
+ sql = sql_wo_dataset_name
1492
+ tmpLog.debug(f"running sql: {sql} {str(varMap)}")
1493
+ self.cur.execute(sql + comment, varMap)
1494
+ retVal += self.cur.rowcount
1495
+ self.fix_associated_files_in_staging(jeditaskid)
1496
+ # update task to trigger CF immediately
1497
+ if retVal:
1498
+ sqlUT = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET modificationTime=CURRENT_DATE-1 WHERE jediTaskID=:jediTaskID AND lockedBy IS NULL "
1499
+ varMap = dict()
1500
+ varMap[":jediTaskID"] = jeditaskid
1501
+ self.cur.execute(sqlUT + comment, varMap)
1502
+ tmpLog.debug(f"unlocked task with {self.cur.rowcount}")
1503
+ # commit
1504
+ if use_commit:
1505
+ if not self._commit():
1506
+ raise RuntimeError("Commit error")
1507
+ tmpLog.debug(f"updated {retVal} files")
1508
+ return retVal
1509
+ except Exception:
1510
+ if use_commit:
1511
+ # roll back
1512
+ self._rollback()
1513
+ # error
1514
+ self.dump_error_message(tmpLog)
1515
+ return None
1516
+
1517
+ # kill child tasks
1518
+ def killChildTasks_JEDI(self, jediTaskID, taskStatus, useCommit=True):
1519
+ comment = " /* JediDBProxy.killChildTasks_JEDI */"
1520
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
1521
+ tmpLog.debug("start")
1522
+ retTasks = []
1523
+ try:
1524
+ # sql to get child tasks
1525
+ sqlGT = f"SELECT jediTaskID,status FROM {panda_config.schemaJEDI}.JEDI_Tasks "
1526
+ sqlGT += "WHERE parent_tid=:jediTaskID AND parent_tid<>jediTaskID "
1527
+ # sql to change status
1528
+ sqlCT = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks "
1529
+ sqlCT += "SET status=:status,errorDialog=:errorDialog,stateChangeTime=CURRENT_DATE "
1530
+ sqlCT += "WHERE jediTaskID=:jediTaskID "
1531
+ # begin transaction
1532
+ if useCommit:
1533
+ self.conn.begin()
1534
+ # get tasks
1535
+ varMap = {}
1536
+ varMap[":jediTaskID"] = jediTaskID
1537
+ self.cur.execute(sqlGT + comment, varMap)
1538
+ resList = self.cur.fetchall()
1539
+ for cJediTaskID, cTaskStatus in resList:
1540
+ # no more changes
1541
+ if cTaskStatus in JediTaskSpec.statusToRejectExtChange():
1542
+ continue
1543
+ # change status
1544
+ cTaskStatus = "toabort"
1545
+ varMap = {}
1546
+ varMap[":jediTaskID"] = cJediTaskID
1547
+ varMap[":status"] = cTaskStatus
1548
+ varMap[":errorDialog"] = f"parent task is {taskStatus}"
1549
+ self.cur.execute(sqlCT + comment, varMap)
1550
+ tmpLog.debug(f"set {cTaskStatus} to jediTaskID={cJediTaskID}")
1551
+ # add missing record_task_status_change and push_task_status_message updates
1552
+ self.record_task_status_change(cJediTaskID)
1553
+ self.push_task_status_message(None, cJediTaskID, cTaskStatus)
1554
+ # kill child
1555
+ tmpStat = self.killChildTasks_JEDI(cJediTaskID, cTaskStatus, useCommit=False)
1556
+ if not tmpStat:
1557
+ raise RuntimeError("Failed to kill child tasks")
1558
+ # commit
1559
+ if useCommit:
1560
+ if not self._commit():
1561
+ raise RuntimeError("Commit error")
1562
+ # return
1563
+ tmpLog.debug("done")
1564
+ return True
1565
+ except Exception:
1566
+ # roll back
1567
+ if useCommit:
1568
+ self._rollback()
1569
+ # error
1570
+ self.dump_error_message(tmpLog)
1571
+ return False
1572
+
1573
+ # task attempt start logging
1574
+ def log_task_attempt_start(self, jedi_task_id):
1575
+ comment = " /* JediDBProxy.log_task_attempt_start */"
1576
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id}")
1577
+ tmpLog.debug("start")
1578
+ # sql
1579
+ sqlGLTA = f"SELECT MAX(attemptnr) FROM {panda_config.schemaJEDI}.TASK_ATTEMPTS WHERE jediTaskID=:jediTaskID "
1580
+ sqlELTA = (
1581
+ "UPDATE {0}.TASK_ATTEMPTS "
1582
+ "SET (endtime, endstatus) = ( "
1583
+ "SELECT CURRENT_DATE,status "
1584
+ "FROM {0}.JEDI_Tasks "
1585
+ "WHERE jediTaskID=:jediTaskID "
1586
+ ") "
1587
+ "WHERE jediTaskID=:jediTaskID "
1588
+ "AND attemptnr=:last_attemptnr "
1589
+ "AND endtime IS NULL "
1590
+ ).format(panda_config.schemaJEDI)
1591
+ sqlITA = (
1592
+ "INSERT INTO {0}.TASK_ATTEMPTS "
1593
+ "(jeditaskid, attemptnr, starttime, startstatus) "
1594
+ "SELECT jediTaskID, GREATEST(:grandAttemptNr, COALESCE(attemptNr, 0)), CURRENT_DATE, status "
1595
+ "FROM {0}.JEDI_Tasks "
1596
+ "WHERE jediTaskID=:jediTaskID "
1597
+ ).format(panda_config.schemaJEDI)
1598
+ # get grand attempt number
1599
+ varMap = dict()
1600
+ varMap[":jediTaskID"] = jedi_task_id
1601
+ self.cur.execute(sqlGLTA + comment, varMap)
1602
+ (last_attemptnr,) = self.cur.fetchone()
1603
+ grand_attemptnr = 0
1604
+ if last_attemptnr is not None:
1605
+ grand_attemptnr = last_attemptnr + 1
1606
+ # end last attempt in case log_task_attempt_end is not called
1607
+ varMap = dict()
1608
+ varMap[":jediTaskID"] = jedi_task_id
1609
+ varMap[":last_attemptnr"] = last_attemptnr
1610
+ self.cur.execute(sqlELTA + comment, varMap)
1611
+ varMap = dict()
1612
+ varMap[":jediTaskID"] = jedi_task_id
1613
+ varMap[":grandAttemptNr"] = grand_attemptnr
1614
+ # insert task attempt
1615
+ self.cur.execute(sqlITA + comment, varMap)
1616
+ tmpLog.debug("done")
1617
+
1618
+ # task attempt end logging
1619
+ def log_task_attempt_end(self, jedi_task_id):
1620
+ comment = " /* JediDBProxy.log_task_attempt_end */"
1621
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id}")
1622
+ tmpLog.debug("start")
1623
+ varMap = dict()
1624
+ varMap[":jediTaskID"] = jedi_task_id
1625
+ # sql
1626
+ sqlUTA = (
1627
+ "UPDATE {0}.TASK_ATTEMPTS "
1628
+ "SET (endtime, endstatus) = ( "
1629
+ "SELECT CURRENT_DATE,status "
1630
+ "FROM {0}.JEDI_Tasks "
1631
+ "WHERE jediTaskID=:jediTaskID "
1632
+ ") "
1633
+ "WHERE jediTaskID=:jediTaskID "
1634
+ "AND endtime IS NULL "
1635
+ ).format(panda_config.schemaJEDI)
1636
+ self.cur.execute(sqlUTA + comment, varMap)
1637
+ tmpLog.debug("done")
1638
+
1639
+ # duplicate files for reuse
1640
+ def duplicateFilesForReuse_JEDI(self, datasetSpec):
1641
+ comment = " /* JediDBProxy.duplicateFilesForReuse_JEDI */"
1642
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={datasetSpec.jediTaskID} datasetID={datasetSpec.datasetID}")
1643
+ try:
1644
+ tmpLog.debug(f"start random={datasetSpec.isRandom()}")
1645
+ # sql to get unique files
1646
+ sqlCT = "SELECT COUNT(*) FROM ("
1647
+ sqlCT += "SELECT distinct lfn,startEvent,endEvent "
1648
+ sqlCT += f"FROM {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
1649
+ sqlCT += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID "
1650
+ sqlCT += ") "
1651
+ # sql to read file spec
1652
+ defaultVales = {}
1653
+ defaultVales["status"] = "ready"
1654
+ defaultVales["PandaID"] = None
1655
+ defaultVales["attemptNr"] = 0
1656
+ defaultVales["failedAttempt"] = 0
1657
+ defaultVales["ramCount"] = 0
1658
+ sqlFR = f"INSERT INTO {panda_config.schemaJEDI}.JEDI_Dataset_Contents ({JediFileSpec.columnNames()}) "
1659
+ sqlFR += f"SELECT {JediFileSpec.columnNames(useSeq=True, defaultVales=defaultVales)} FROM ( "
1660
+ sqlFR += f"SELECT {JediFileSpec.columnNames(defaultVales=defaultVales, skipDefaultAttr=True)} "
1661
+ sqlFR += f"FROM {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
1662
+ sqlFR += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID AND fileID IN ( "
1663
+ sqlFR += "SELECT /*+ UNNEST */ MIN(fileID) minFileID "
1664
+ sqlFR += f"FROM {panda_config.schemaJEDI}.JEDI_Dataset_Contents "
1665
+ sqlFR += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID "
1666
+ sqlFR += "GROUP BY lfn,startEvent,endEvent) "
1667
+ if not datasetSpec.isRandom():
1668
+ sqlFR += "ORDER BY fileID) "
1669
+ else:
1670
+ sqlFR += "ORDER BY DBMS_RANDOM.value) "
1671
+ # sql to update dataset record
1672
+ sqlDU = f"UPDATE {panda_config.schemaJEDI}.JEDI_Datasets "
1673
+ sqlDU += "SET nFiles=nFiles+:iFiles,nFilesTobeUsed=nFilesTobeUsed+:iFiles "
1674
+ sqlDU += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID "
1675
+ # read unique files
1676
+ varMap = {}
1677
+ varMap[":jediTaskID"] = datasetSpec.jediTaskID
1678
+ varMap[":datasetID"] = datasetSpec.datasetID
1679
+ self.cur.execute(sqlCT + comment, varMap)
1680
+ resCT = self.cur.fetchone()
1681
+ (iFile,) = resCT
1682
+ # insert files
1683
+ varMap = {}
1684
+ varMap[":jediTaskID"] = datasetSpec.jediTaskID
1685
+ varMap[":datasetID"] = datasetSpec.datasetID
1686
+ self.cur.execute(sqlFR + comment, varMap)
1687
+ # update dataset
1688
+ if iFile > 0:
1689
+ varMap = {}
1690
+ varMap[":jediTaskID"] = datasetSpec.jediTaskID
1691
+ varMap[":datasetID"] = datasetSpec.datasetID
1692
+ varMap[":iFiles"] = iFile
1693
+ self.cur.execute(sqlDU + comment, varMap)
1694
+ tmpLog.debug(f"inserted {iFile} files")
1695
+ return iFile
1696
+ except Exception:
1697
+ # error
1698
+ self.dump_error_message(tmpLog)
1699
+ return 0
1700
+
1701
+ # increase seq numbers
1702
+ def increaseSeqNumber_JEDI(self, datasetSpec, n_records):
1703
+ comment = " /* JediDBProxy.increaseSeqNumber_JEDI */"
1704
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={datasetSpec.jediTaskID} datasetID={datasetSpec.datasetID}")
1705
+ tmpLog.debug("start")
1706
+ try:
1707
+ # sql to get max LFN
1708
+ sqlCT = (
1709
+ "SELECT lfn,maxAttempt,maxFailure FROM {0}.JEDI_Dataset_Contents "
1710
+ "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID "
1711
+ "AND fileID=("
1712
+ "SELECT MAX(fileID) FROM {0}.JEDI_Dataset_Contents "
1713
+ "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID"
1714
+ ") "
1715
+ ).format(panda_config.schemaJEDI)
1716
+ varMap = {}
1717
+ varMap[":jediTaskID"] = datasetSpec.jediTaskID
1718
+ varMap[":datasetID"] = datasetSpec.datasetID
1719
+ self.cur.execute(sqlCT + comment, varMap)
1720
+ resCT = self.cur.fetchone()
1721
+ baseLFN, maxAttempt, maxFailure = resCT
1722
+ baseLFN = int(baseLFN) + 1
1723
+ # current date
1724
+ timeNow = naive_utcnow()
1725
+ # make files
1726
+ varMaps = []
1727
+ n_records = math.ceil(n_records)
1728
+ for i in range(n_records):
1729
+ fileSpec = JediFileSpec()
1730
+ fileSpec.jediTaskID = datasetSpec.jediTaskID
1731
+ fileSpec.datasetID = datasetSpec.datasetID
1732
+ fileSpec.GUID = str(uuid.uuid4())
1733
+ fileSpec.type = datasetSpec.type
1734
+ fileSpec.status = "ready"
1735
+ fileSpec.proc_status = "ready"
1736
+ fileSpec.lfn = baseLFN + i
1737
+ fileSpec.scope = None
1738
+ fileSpec.fsize = 0
1739
+ fileSpec.checksum = None
1740
+ fileSpec.creationDate = timeNow
1741
+ fileSpec.attemptNr = 0
1742
+ fileSpec.failedAttempt = 0
1743
+ fileSpec.maxAttempt = maxAttempt
1744
+ fileSpec.maxFailure = maxFailure
1745
+ fileSpec.ramCount = 0
1746
+ # make vars
1747
+ varMap = fileSpec.valuesMap(useSeq=True)
1748
+ varMaps.append(varMap)
1749
+ # sql for insert
1750
+ sqlIn = f"INSERT INTO {panda_config.schemaJEDI}.JEDI_Dataset_Contents ({JediFileSpec.columnNames()}) "
1751
+ sqlIn += JediFileSpec.bindValuesExpression()
1752
+ self.cur.executemany(sqlIn + comment, varMaps)
1753
+ # sql to update dataset record
1754
+ sqlDU = f"UPDATE {panda_config.schemaJEDI}.JEDI_Datasets "
1755
+ sqlDU += "SET nFiles=nFiles+:iFiles,nFilesTobeUsed=nFilesTobeUsed+:iFiles "
1756
+ sqlDU += "WHERE jediTaskID=:jediTaskID AND datasetID=:datasetID "
1757
+ varMap = {}
1758
+ varMap[":jediTaskID"] = datasetSpec.jediTaskID
1759
+ varMap[":datasetID"] = datasetSpec.datasetID
1760
+ varMap[":iFiles"] = n_records
1761
+ self.cur.execute(sqlDU + comment, varMap)
1762
+ tmpLog.debug(f"inserted {n_records} files")
1763
+ return n_records
1764
+ except Exception:
1765
+ # error
1766
+ self.dump_error_message(tmpLog)
1767
+ return 0
1768
+
1769
+ # get JEDI task with ID
1770
+ def getTaskWithID_JEDI(self, jediTaskID, fullFlag, lockTask=False, pid=None, lockInterval=None, clearError=False):
1771
+ comment = " /* JediDBProxy.getTaskWithID_JEDI */"
1772
+ tmpLog = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID}")
1773
+ tmpLog.debug(f"start lockTask={lockTask}")
1774
+ # return value for failure
1775
+ failedRet = False, None
1776
+ try:
1777
+ # sql
1778
+ sql = f"SELECT {JediTaskSpec.columnNames()} "
1779
+ sql += f"FROM {panda_config.schemaJEDI}.JEDI_Tasks WHERE jediTaskID=:jediTaskID "
1780
+ if lockInterval is not None:
1781
+ sql += "AND (lockedTime IS NULL OR lockedTime<:timeLimit) "
1782
+ if lockTask:
1783
+ sql += "AND lockedBy IS NULL FOR UPDATE NOWAIT"
1784
+ sqlLock = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET lockedBy=:lockedBy,lockedTime=CURRENT_DATE"
1785
+ if clearError:
1786
+ sqlLock += ",errorDialog=NULL"
1787
+ sqlLock += " WHERE jediTaskID=:jediTaskID "
1788
+ varMap = {}
1789
+ varMap[":jediTaskID"] = jediTaskID
1790
+ if lockInterval is not None:
1791
+ varMap[":timeLimit"] = naive_utcnow() - datetime.timedelta(minutes=lockInterval)
1792
+ # begin transaction
1793
+ self.conn.begin()
1794
+ # select
1795
+ res = None
1796
+ try:
1797
+ self.cur.execute(sql + comment, varMap)
1798
+ res = self.cur.fetchone()
1799
+ if res is not None:
1800
+ # template to generate job parameters
1801
+ jobParamsTemplate = None
1802
+ if fullFlag:
1803
+ # sql to read template
1804
+ sqlJobP = f"SELECT jobParamsTemplate FROM {panda_config.schemaJEDI}.JEDI_JobParams_Template "
1805
+ sqlJobP += "WHERE jediTaskID=:jediTaskID "
1806
+ self.cur.execute(sqlJobP + comment, varMap)
1807
+ for (clobJobP,) in self.cur:
1808
+ if clobJobP is not None:
1809
+ jobParamsTemplate = clobJobP
1810
+ break
1811
+ if lockTask:
1812
+ varMap = {}
1813
+ varMap[":lockedBy"] = pid
1814
+ varMap[":jediTaskID"] = jediTaskID
1815
+ self.cur.execute(sqlLock + comment, varMap)
1816
+ except Exception:
1817
+ errType, errValue = sys.exc_info()[:2]
1818
+ if self.isNoWaitException(errValue):
1819
+ # resource busy and acquire with NOWAIT specified
1820
+ tmpLog.debug("skip locked")
1821
+ else:
1822
+ # failed with something else
1823
+ raise errType(errValue)
1824
+ # commit
1825
+ if not self._commit():
1826
+ raise RuntimeError("Commit error")
1827
+ if res is not None:
1828
+ taskSpec = JediTaskSpec()
1829
+ taskSpec.pack(res)
1830
+ if jobParamsTemplate is not None:
1831
+ taskSpec.jobParamsTemplate = jobParamsTemplate
1832
+ else:
1833
+ taskSpec = None
1834
+ if taskSpec is None:
1835
+ tmpLog.debug("done with skip")
1836
+ else:
1837
+ tmpLog.debug("done with got")
1838
+ return True, taskSpec
1839
+ except Exception:
1840
+ # roll back
1841
+ self._rollback()
1842
+ # error
1843
+ self.dump_error_message(tmpLog)
1844
+ return failedRet
1845
+
1846
+ # resolve parent task id for a child task
1847
+ def _resolve_parent_task_id(self, jedi_task_id: int) -> tuple[str, int | None]:
1848
+ """
1849
+ Resolve parent task ID for a given child task.
1850
+
1851
+ Returns a tuple ``(status, parent_task_id)`` where status is one of:
1852
+ "ok" : parent task id exists and parent row exists
1853
+ "child_not_found" : child task does not exist
1854
+ "no_parent" : parent_tid is NULL or self-referential
1855
+ "parent_not_found" : parent_tid exists but parent row is missing
1856
+ "error" : database/system error while resolving
1857
+ """
1858
+ comment = " /* DBProxy._resolve_parent_task_id */"
1859
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id}")
1860
+ tmp_log.debug("start")
1861
+ try:
1862
+ sql_child = f"SELECT parent_tid FROM {panda_config.schemaJEDI}.JEDI_Tasks WHERE jediTaskID=:jediTaskID "
1863
+ sql_parent = f"SELECT jediTaskID FROM {panda_config.schemaJEDI}.JEDI_Tasks WHERE jediTaskID=:parentTaskID "
1864
+
1865
+ self.conn.begin()
1866
+ self.cur.execute(sql_child + comment, {":jediTaskID": jedi_task_id})
1867
+ res_child = self.cur.fetchone()
1868
+
1869
+ if res_child is None:
1870
+ if not self._commit():
1871
+ raise RuntimeError("Commit error")
1872
+ tmp_log.debug("done with child_not_found")
1873
+ return "child_not_found", None
1874
+
1875
+ (parent_task_id,) = res_child
1876
+ if parent_task_id in [None, jedi_task_id]:
1877
+ if not self._commit():
1878
+ raise RuntimeError("Commit error")
1879
+ tmp_log.debug(f"done with no_parent parent={parent_task_id}")
1880
+ return "no_parent", parent_task_id
1881
+
1882
+ self.cur.execute(sql_parent + comment, {":parentTaskID": parent_task_id})
1883
+ res_parent = self.cur.fetchone()
1884
+ if not self._commit():
1885
+ raise RuntimeError("Commit error")
1886
+
1887
+ if res_parent is None:
1888
+ tmp_log.debug(f"done with parent_not_found parent={parent_task_id}")
1889
+ return "parent_not_found", parent_task_id
1890
+
1891
+ tmp_log.debug(f"done with ok parent={parent_task_id}")
1892
+ return "ok", parent_task_id
1893
+ except Exception:
1894
+ self._rollback()
1895
+ self.dump_error_message(tmp_log)
1896
+ return "error", None
1897
+
1898
+ # get task details as a JSON-serializable dict
1899
+ def get_task_details_json(self, jedi_task_id: int, resolve_parent: bool = False, include_resolve_status: bool = False):
1900
+ """
1901
+ Read-only helper that returns task info for jedi_task_id as a plain dict.
1902
+
1903
+ Similar to getTaskWithID_JEDI but:
1904
+ - Does NOT lock the task record (no FOR UPDATE).
1905
+ - Returns a plain dict instead of a JediTaskSpec object.
1906
+ - Can resolve parent task details when ``resolve_parent=True``.
1907
+ - Adds two extra keys to the dict:
1908
+ job_execution_params : template string to execute a job (or None)
1909
+ task_creation_arguments : command-line arguments to create the job if applicable (or None)
1910
+
1911
+ Args:
1912
+ jedi_task_id: child task ID by default. When ``resolve_parent=True``, this is
1913
+ treated as child task ID used to resolve and fetch the parent task details.
1914
+ resolve_parent: when True, resolve parent_tid and return only parent task details.
1915
+ Returns None if child doesn't exist, no valid parent exists, parent row is
1916
+ missing, or any error occurs.
1917
+ include_resolve_status: when True, return a tuple
1918
+ ``(status, parent_task_id, task_dict_or_none)`` where ``status`` is one of
1919
+ ``ok``, ``child_not_found``, ``no_parent``, ``parent_not_found``,
1920
+ ``target_not_found``, or ``error``.
1921
+
1922
+ Returns None when the target task is not found or on error.
1923
+ """
1924
+ comment = " /* DBProxy.get_task_details_json */"
1925
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id} resolve_parent={resolve_parent}")
1926
+ tmp_log.debug("start")
1927
+ try:
1928
+ target_task_id = jedi_task_id
1929
+ resolve_status = "ok"
1930
+ parent_task_id = None
1931
+ if resolve_parent:
1932
+ resolve_status, parent_task_id = self._resolve_parent_task_id(jedi_task_id)
1933
+ if resolve_status != "ok":
1934
+ tmp_log.debug(f"resolve_parent failed status={resolve_status} child={jedi_task_id} parent={parent_task_id}")
1935
+ if include_resolve_status:
1936
+ return resolve_status, parent_task_id, None
1937
+ return None
1938
+ target_task_id = parent_task_id
1939
+ tmp_log.debug(f"resolved parent child={jedi_task_id} parent={target_task_id}")
1940
+
1941
+ var_map = {":jediTaskID": target_task_id}
1942
+
1943
+ # --- read the main task row (no locking) ---
1944
+ sql = f"SELECT {JediTaskSpec.columnNames()} FROM {panda_config.schemaJEDI}.JEDI_Tasks WHERE jediTaskID=:jediTaskID "
1945
+ self.conn.begin()
1946
+ self.cur.arraysize = 10
1947
+ self.cur.execute(sql + comment, var_map)
1948
+ res = self.cur.fetchone()
1949
+ if not self._commit():
1950
+ raise RuntimeError("Commit error")
1951
+
1952
+ if res is None:
1953
+ if resolve_parent:
1954
+ tmp_log.debug(f"parent task not found child={jedi_task_id} parent={target_task_id}")
1955
+ else:
1956
+ tmp_log.debug("task not found")
1957
+ if include_resolve_status:
1958
+ status = "target_not_found" if resolve_parent else "task_not_found"
1959
+ return status, target_task_id, None
1960
+ return None
1961
+
1962
+ # build flat dict from the DB row
1963
+ task_dict = {}
1964
+ for idx, attr in enumerate(JediTaskSpec.attributes):
1965
+ task_dict[attr] = res[idx]
1966
+ # decode splitRule string to human-readable string
1967
+ if attr == "splitRule" and task_dict[attr] is not None:
1968
+ task_dict[attr] = decode_split_rule(task_dict[attr])
1969
+
1970
+ # --- read jobParamsTemplate (CLOB) ---
1971
+ sql_read_job_params = f"SELECT jobParamsTemplate FROM {panda_config.schemaJEDI}.JEDI_JobParams_Template WHERE jediTaskID=:jediTaskID "
1972
+ try:
1973
+ _ok, rows = self.getClobObj(sql_read_job_params, var_map)
1974
+ task_dict["job_execution_params"] = rows[0][0] if rows else None
1975
+ except Exception:
1976
+ tmp_log.warning("failed to read jobParamsTemplate")
1977
+
1978
+ # --- read taskParams (CLOB) ---
1979
+ sql_read_task_params = f"SELECT taskParams FROM {panda_config.schemaJEDI}.JEDI_TaskParams WHERE jediTaskID=:jediTaskID "
1980
+ try:
1981
+ _ok, rows = self.getClobObj(sql_read_task_params, var_map)
1982
+ task_params = json.loads(rows[0][0])
1983
+ task_dict["task_creation_arguments"] = task_params.get("cliParams", None)
1984
+ task_dict["raw_task_params"] = task_params
1985
+ except Exception:
1986
+ tmp_log.warning("failed to read taskParams")
1987
+
1988
+ tmp_log.debug("done")
1989
+ if include_resolve_status:
1990
+ return "ok", target_task_id, task_dict
1991
+ return task_dict
1992
+
1993
+ except Exception:
1994
+ self._rollback()
1995
+ self.dump_error_message(tmp_log)
1996
+ if include_resolve_status:
1997
+ return "error", None, None
1998
+ return None
1999
+
2000
+ # check parent task status
2001
+ def checkParentTask_JEDI(self, parent_task_id: int, jedi_task_id: int = None, use_commit: bool = True) -> str | None:
2002
+ """
2003
+ Check the status of parent task
2004
+ Args:
2005
+ parent_task_id: JEDI task ID of parent task
2006
+ jedi_task_id: JEDI task ID of child task (for logging purpose)
2007
+ use_commit: whether to use commit/rollback
2008
+ Returns:
2009
+ "completed": parent task is done/finished
2010
+ "corrupted": parent task is broken/aborted/failed
2011
+ "running": parent task is still running
2012
+ "unknown": parent task is not found
2013
+ None: error
2014
+ """
2015
+ comment = " /* JediDBProxy.checkParentTask_JEDI */"
2016
+ tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id} parent={parent_task_id}")
2017
+ tmp_log.debug("start")
2018
+ ret_val = None
2019
+ try:
2020
+
2021
+ sql = f"SELECT status FROM {panda_config.schemaJEDI}.JEDI_Tasks "
2022
+ sql += "WHERE jediTaskID=:jediTaskID "
2023
+ var_map = {":jediTaskID": parent_task_id}
2024
+ # start transaction
2025
+ if use_commit:
2026
+ self.conn.begin()
2027
+ self.cur.execute(sql + comment, var_map)
2028
+ res_tk = self.cur.fetchone()
2029
+ if use_commit:
2030
+ # commit
2031
+ if not self._commit():
2032
+ raise RuntimeError("Commit error")
2033
+ if res_tk is None:
2034
+ tmp_log.error("parent not found")
2035
+ ret_val = "unknown"
2036
+ else:
2037
+ # task status
2038
+ (task_status,) = res_tk
2039
+ tmp_log.debug(f"parent status = {task_status}")
2040
+ if task_status in ["done", "finished"]:
2041
+ # parent is completed
2042
+ ret_val = "completed"
2043
+ elif task_status in ["broken", "aborted", "failed"]:
2044
+ # parent is corrupted
2045
+ ret_val = "corrupted"
2046
+ else:
2047
+ # parent is running
2048
+ ret_val = "running"
2049
+ # return
2050
+ tmp_log.debug(f"done with {ret_val}")
2051
+ return ret_val
2052
+ except Exception:
2053
+ if use_commit:
2054
+ # roll back
2055
+ self._rollback()
2056
+ # error
2057
+ self.dump_error_message(tmp_log)
2058
+ return ret_val
2059
+
2060
+
2061
+ # get module
2062
+ def get_task_utils_module(base_mod) -> TaskUtilsModule:
2063
+ return base_mod.get_composite_module("task_utils")