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,927 @@
1
+ import copy
2
+ import datetime
3
+ import functools
4
+ import json
5
+ import os
6
+ import socket
7
+ import sys
8
+ import traceback
9
+ from zlib import adler32
10
+
11
+ import numpy as np
12
+ from pandacommon.pandalogger import logger_utils
13
+ from pandacommon.pandalogger.PandaLogger import PandaLogger
14
+ from pandacommon.pandautils.PandaUtils import naive_utcnow
15
+ from pandacommon.pandautils.thread_utils import GenericThread
16
+ from scipy import stats
17
+
18
+ from pandaserver.config import panda_config
19
+
20
+ # logger
21
+ main_logger = PandaLogger().getLogger("metric_collector")
22
+
23
+ # dry run
24
+ DRY_RUN = False
25
+
26
+ # list of metrics in FetchData to fetch data and update to DB. Format: (metric, key_type, period_minutes)
27
+ metric_list = [
28
+ ("gshare_preference", "gshare", 5),
29
+ ("analy_pmerge_jobs_wait_time", "site", 30),
30
+ ("analy_site_eval", "site", 30),
31
+ ("users_jobs_stats", "both", 5),
32
+ ("analy_user_eval", "neither", 10),
33
+ ]
34
+
35
+ # constant maps
36
+ class_value_rank_map = {1: "A_sites", 0: "B_sites", -1: "C_sites"}
37
+
38
+
39
+ def get_now_time_str():
40
+ """
41
+ Return string of nowtime that can be stored in DB
42
+ """
43
+ now_time = naive_utcnow()
44
+ ts_str = now_time.strftime("%Y-%m-%d %H:%M:%S")
45
+ return ts_str
46
+
47
+
48
+ def conf_interval_upper(n, mean, stdev, cl=0.95):
49
+ """
50
+ Get estimated confidence level
51
+ """
52
+ max_value = 999999
53
+ ciu = stats.t.ppf(cl, (n - 1), loc=mean, scale=stdev)
54
+ ciu = min(ciu, max_value)
55
+ return ciu
56
+
57
+
58
+ def weighted_stats(values, weights):
59
+ """
60
+ Return sum of weights, weighted mean and standard deviation
61
+ """
62
+ sum_of_weights = np.sum(weights)
63
+ mean = np.average(values, weights=weights)
64
+ variance = np.average((values - mean) ** 2, weights=weights)
65
+ stdev = np.sqrt(variance)
66
+ return sum_of_weights, mean, stdev
67
+
68
+
69
+ # get site slot to-running rate statistics
70
+ def get_site_strr_stats(tbuf, time_window=21600, cutoff=300):
71
+ """
72
+ :param time_window: float, time window in hours to compute slot to-running rate
73
+ """
74
+ # log
75
+ tmp_log = logger_utils.make_logger(main_logger, "get_site_strr_stats")
76
+ # timestamps
77
+ current_time = naive_utcnow()
78
+ starttime_max = current_time - datetime.timedelta(seconds=cutoff)
79
+ starttime_min = current_time - datetime.timedelta(seconds=time_window)
80
+ # rounded with 10 minutes
81
+ starttime_max_rounded = starttime_max.replace(minute=starttime_max.minute // 10 * 10, second=0, microsecond=0)
82
+ starttime_min_rounded = starttime_min.replace(minute=starttime_min.minute // 10 * 10, second=0, microsecond=0)
83
+ real_interval_hours = (starttime_max_rounded - starttime_min_rounded).total_seconds() / 3600
84
+ # define the var map of query parameters
85
+ var_map = {
86
+ ":startTimeMin": starttime_min_rounded,
87
+ ":startTimeMax": starttime_max_rounded,
88
+ }
89
+ # sql to query on jobs-tables (jobsactive4 and jobsArchived4)
90
+ sql_jt = """
91
+ SELECT computingSite, coreCount, COUNT(*) FROM %s
92
+ WHERE vo='atlas'
93
+ AND startTime IS NOT NULL AND startTime>=:startTimeMin AND startTime<:startTimeMax
94
+ AND jobStatus IN ('running', 'holding', 'transferring', 'finished', 'cancelled')
95
+ """
96
+ sql_jt += """
97
+ GROUP BY computingSite, coreCount
98
+ """
99
+ # job tables
100
+ tables = [
101
+ "ATLAS_PANDA.jobsActive4",
102
+ "ATLAS_PANDA.jobsArchived4",
103
+ ]
104
+ # get
105
+ return_map = {}
106
+ try:
107
+ for table in tables:
108
+ sql_exe = (sql_jt) % table
109
+ res = tbuf.querySQL(sql_exe, var_map)
110
+ # create map
111
+ for panda_site, core_count, n_count in res:
112
+ # add site
113
+ return_map.setdefault(panda_site, 0)
114
+ # skip null coreCount
115
+ if core_count is None or not core_count:
116
+ continue
117
+ # increase to-running rate
118
+ to_running_rate = n_count * core_count / real_interval_hours if real_interval_hours > 0 else 0
119
+ return_map[panda_site] += to_running_rate
120
+ # end loop
121
+ # tmp_log.debug('done')
122
+ return True, return_map
123
+ except Exception as e:
124
+ tmp_log.error(f"Exception {e.__class__.__name__}: {e}")
125
+ return False, {}
126
+
127
+
128
+ # get each share and its leaf share
129
+ def fill_leaf_shares(key, val, the_list):
130
+ if val is None:
131
+ the_list.append(key)
132
+ else:
133
+ for k, v in val.items():
134
+ fill_leaf_shares(k, v, the_list)
135
+
136
+
137
+ class MetricsDB(object):
138
+ """
139
+ Proxy to access the metrics table in DB
140
+ """
141
+
142
+ def __init__(self, tbuf):
143
+ self.tbuf = tbuf
144
+
145
+ def _decor(method):
146
+ def _decorator(_method, *args, **kwargs):
147
+ @functools.wraps(_method)
148
+ def _wrapped_method(self, *args, **kwargs):
149
+ try:
150
+ _method(self, *args, **kwargs)
151
+ except Exception as exc:
152
+ pass
153
+
154
+ return _wrapped_method
155
+
156
+ return _decorator(method)
157
+
158
+ def update(self, metric, key_type, entity_dict):
159
+ tmp_log = logger_utils.make_logger(main_logger, "MetricsDB.update")
160
+ # tmp_log.debug('start key={0} site={1}, gshare={2}'.format(key, site, gshare))
161
+ # sql
162
+ # sql_update = (
163
+ # """UPDATE ATLAS_PANDA.Metrics SET """
164
+ # """value_json = json_mergepatch(value_json, :patch_value_json), """
165
+ # """timestamp = :timestamp """
166
+ # """WHERE computingSite=:site AND gshare=:gshare AND metric=:metric """
167
+ # )
168
+ sql_update = (
169
+ """UPDATE ATLAS_PANDA.Metrics SET """
170
+ """value_json = :patch_value_json , """
171
+ """timestamp = :timestamp """
172
+ """WHERE computingSite=:site AND gshare=:gshare AND metric=:metric """
173
+ )
174
+ sql_insert = """INSERT INTO ATLAS_PANDA.Metrics """ """VALUES ( """ """:site, :gshare, :metric, :patch_value_json, :timestamp """ """) """
175
+ # now
176
+ now_time = naive_utcnow()
177
+ # var map template
178
+ varMap_template = {
179
+ ":site": None,
180
+ ":gshare": None,
181
+ ":metric": metric,
182
+ ":timestamp": now_time,
183
+ ":patch_value_json": None,
184
+ }
185
+ # make var map list
186
+ varMap_list = []
187
+ if key_type == "neither":
188
+ # values to json string
189
+ try:
190
+ patch_value_json = json.dumps(entity_dict)
191
+ except Exception:
192
+ tmp_log.error(traceback.format_exc() + " " + str(v))
193
+ return
194
+ # initialize varMap
195
+ varMap = varMap_template.copy()
196
+ varMap.update(
197
+ {
198
+ ":site": "NULL",
199
+ ":gshare": "NULL",
200
+ ":patch_value_json": patch_value_json,
201
+ }
202
+ )
203
+ # append to the list
204
+ varMap_list.append(varMap)
205
+ else:
206
+ for entity, v in entity_dict.items():
207
+ # values to json string
208
+ try:
209
+ patch_value_json = json.dumps(v)
210
+ except Exception:
211
+ tmp_log.error(traceback.format_exc() + " " + str(v))
212
+ return
213
+ # initialize varMap
214
+ varMap = varMap_template.copy()
215
+ varMap[":patch_value_json"] = patch_value_json
216
+ # update varMap according to key_type
217
+ if key_type == "site":
218
+ varMap.update(
219
+ {
220
+ ":site": entity,
221
+ ":gshare": "NULL",
222
+ }
223
+ )
224
+ elif key_type == "gshare":
225
+ varMap.update(
226
+ {
227
+ ":site": "NULL",
228
+ ":gshare": entity,
229
+ }
230
+ )
231
+ elif key_type == "both":
232
+ varMap.update(
233
+ {
234
+ ":site": entity[0],
235
+ ":gshare": entity[1],
236
+ }
237
+ )
238
+ # append to the list
239
+ varMap_list.append(varMap)
240
+ # update
241
+ n_row = self.tbuf.executemanySQL(sql_update, varMap_list)
242
+ # try insert if not all rows updated
243
+ if n_row < len(varMap_list):
244
+ try:
245
+ tmp_log.debug(f"only {n_row}/{len(varMap_list)} rows updated for metric={metric} ; trying insert")
246
+ for varMap in varMap_list:
247
+ self.tbuf.querySQLS(sql_insert, varMap)
248
+ tmp_log.debug(f"inserted for metric={metric}")
249
+ except Exception:
250
+ tmp_log.warning(f"failed to insert for metric={metric}")
251
+ else:
252
+ tmp_log.debug(f"updated for metric={metric}")
253
+ # done
254
+ # tmp_log.debug('done key={0} site={1}, gshare={2}'.format(key, site, gshare))
255
+
256
+ def get_metrics(self, metric, key_type=None, fresher_than_minutes_ago=120):
257
+ tmp_log = logger_utils.make_logger(main_logger, "MetricsDB.get_metrics")
258
+ # tmp_log.debug('start key={0} site={1}, gshare={2}'.format(key, site, gshare))
259
+ # sql
260
+ sql_query = (
261
+ """SELECT computingSite, gshare, value_json """ """FROM ATLAS_PANDA.Metrics """ """WHERE metric = :metric """ """AND timestamp >= :min_timestamp """
262
+ )
263
+ # now
264
+ now_time = naive_utcnow()
265
+ # var map
266
+ varMap = {
267
+ ":metric": metric,
268
+ ":min_timestamp": now_time - datetime.timedelta(minutes=fresher_than_minutes_ago),
269
+ }
270
+ # query
271
+ res = self.tbuf.querySQL(sql_query, varMap)
272
+ if res is None:
273
+ tmp_log.warning(f"failed to query metric={metric}")
274
+ return
275
+ # key type default
276
+ if key_type is None:
277
+ key = {x[0]: x[1] for x in metric_list}.get(metric, "both")
278
+ # return map
279
+ ret_map = {}
280
+ for computingSite, gshare, value_json in res:
281
+ key = (computingSite, gshare)
282
+ if key_type == "site":
283
+ key = computingSite
284
+ elif key_type == "gshare":
285
+ key = gshare
286
+ elif key_type == "neither":
287
+ key = None
288
+ if value_json is None:
289
+ tmp_log.warning("missing data for " + str((computingSite, gshare)))
290
+ continue
291
+ try:
292
+ value_dict = json.loads(value_json)
293
+ except Exception:
294
+ tmp_log.error(traceback.format_exc() + " " + str((computingSite, gshare)) + str(value_json))
295
+ continue
296
+ else:
297
+ if key is None:
298
+ ret_map = copy.deepcopy(value_dict)
299
+ else:
300
+ ret_map[key] = value_dict
301
+ # return
302
+ return ret_map
303
+
304
+
305
+ class FetchData(object):
306
+ """
307
+ methods to fetch or evaluate data values to store
308
+ """
309
+
310
+ def __init__(self, tbuf):
311
+ self.tbuf = tbuf
312
+ # initialize stored data
313
+ self.gshare_status = None
314
+
315
+ def analy_pmerge_jobs_wait_time(self):
316
+ tmp_log = logger_utils.make_logger(main_logger, "FetchData")
317
+ # sql
318
+ sql_get_jobs_archived4 = (
319
+ "SELECT pandaID, computingSite "
320
+ "FROM ATLAS_PANDA.jobsArchived4 "
321
+ "WHERE prodSourceLabel='user' "
322
+ "AND gshare='User Analysis' "
323
+ "AND (processingType='pmerge' OR prodUserName='gangarbt') "
324
+ "AND modificationTime>:modificationTime "
325
+ )
326
+ sql_get_jobs_active4 = (
327
+ "SELECT pandaID, computingSite "
328
+ "FROM ATLAS_PANDA.jobsActive4 "
329
+ "WHERE prodSourceLabel='user' "
330
+ "AND gshare='User Analysis' "
331
+ "AND jobStatus IN ('running', 'holding', 'merging', 'transferring', 'finished', 'failed', 'closed', 'cancelled') "
332
+ "AND (processingType='pmerge' OR prodUserName='gangarbt') "
333
+ "AND modificationTime>:modificationTime "
334
+ )
335
+ sql_get_latest_job_mtime_status = (
336
+ "SELECT jobStatus, MIN(modificationTime) " "FROM ATLAS_PANDA.jobs_StatusLog " "WHERE pandaID=:pandaID " "GROUP BY jobStatus "
337
+ )
338
+ sql_get_site_workflow = "SELECT /* use_json_type */ scj.data.workflow " "FROM ATLAS_PANDA.schedconfig_json scj " "WHERE scj.panda_queue=:computingSite "
339
+ sql_get_long_queuing_job_wait_time_template = (
340
+ "SELECT COUNT(*), AVG(CURRENT_DATE-creationtime) "
341
+ "FROM ATLAS_PANDA.jobsActive4 "
342
+ "WHERE prodSourceLabel='user' "
343
+ "AND gshare='User Analysis' "
344
+ "AND jobStatus IN {q_status_list_str} "
345
+ "AND (processingType='pmerge') "
346
+ "AND computingSite=:computingSite "
347
+ "AND (CURRENT_DATE-creationtime)>:w_mean "
348
+ )
349
+ try:
350
+ # initialize
351
+ tmp_site_dict = dict()
352
+ # now time
353
+ now_time = naive_utcnow()
354
+ # get user jobs
355
+ varMap = {
356
+ ":modificationTime": now_time - datetime.timedelta(days=4),
357
+ }
358
+ archived4_jobs_list = self.tbuf.querySQL(sql_get_jobs_archived4, varMap)
359
+ active4_jobs_list = self.tbuf.querySQL(sql_get_jobs_active4, varMap)
360
+ all_jobs_set = set()
361
+ all_jobs_set.update(archived4_jobs_list)
362
+ all_jobs_set.update(active4_jobs_list)
363
+ n_tot_jobs = len(all_jobs_set)
364
+ tmp_log.debug(f"got total {n_tot_jobs} jobs")
365
+ # loop over jobs to get modificationTime when activated and running
366
+ cc = 0
367
+ for pandaID, site in all_jobs_set:
368
+ if not site:
369
+ continue
370
+ varMap = {":pandaID": pandaID}
371
+ status_mtime_list = self.tbuf.querySQL(sql_get_latest_job_mtime_status, varMap)
372
+ status_mtime_dict = dict(status_mtime_list)
373
+ if "activated" not in status_mtime_dict or "running" not in status_mtime_dict:
374
+ continue
375
+ wait_time = status_mtime_dict["running"] - status_mtime_dict["activated"]
376
+ wait_time_sec = wait_time.total_seconds()
377
+ if wait_time_sec < 0:
378
+ tmp_log.warning(f"job {pandaID} has negative wait time")
379
+ continue
380
+ run_age_sec = int((now_time - status_mtime_dict["running"]).total_seconds())
381
+ if run_age_sec < 0:
382
+ tmp_log.warning(f"job {pandaID} has negative run age")
383
+ continue
384
+ tmp_site_dict.setdefault(site, {"wait_time": [], "run_age": []})
385
+ tmp_site_dict[site]["wait_time"].append(wait_time_sec)
386
+ tmp_site_dict[site]["run_age"].append(run_age_sec)
387
+ # log message
388
+ if cc > 0 and cc % 5000 == 0:
389
+ tmp_log.debug(f"... queried {cc:9d} jobs ...")
390
+ cc += 1
391
+ tmp_log.debug(f"queried {cc} jobs")
392
+ # evaluate stats
393
+ site_dict = dict()
394
+ for site, data_dict in tmp_site_dict.items():
395
+ site_dict.setdefault(site, {})
396
+ n_jobs = len(data_dict["wait_time"])
397
+ # init with nan
398
+ mean = np.nan
399
+ stdev = np.nan
400
+ median = np.nan
401
+ cl95upp = np.nan
402
+ sum_of_weights = np.nan
403
+ w_mean = np.nan
404
+ w_stdev = np.nan
405
+ w_cl95upp = np.nan
406
+ long_q_n = np.nan
407
+ long_q_mean = np.nan
408
+ # fill the stats values
409
+ if n_jobs > 0:
410
+ wait_time_array = np.array(data_dict["wait_time"])
411
+ run_age_array = np.array(data_dict["run_age"])
412
+ # stats
413
+ mean = np.mean(wait_time_array)
414
+ stdev = np.std(wait_time_array)
415
+ median = np.median(wait_time_array)
416
+ cl95upp = conf_interval_upper(n=n_jobs, mean=mean, stdev=stdev, cl=0.95)
417
+ # weighted by run age (weight halves every 12 hours)
418
+ weight_array = np.exp2(-run_age_array / (12 * 60 * 60))
419
+ sum_of_weights, w_mean, w_stdev = weighted_stats(wait_time_array, weight_array)
420
+ w_cl95upp = conf_interval_upper(n=sum_of_weights + 1, mean=w_mean, stdev=w_stdev, cl=0.95)
421
+ # current long queuing jobs
422
+ if w_mean:
423
+ q_status_list_str = "('activated', 'sent')"
424
+ varMap = {
425
+ ":computingSite": site,
426
+ }
427
+ (site_workflow,) = self.tbuf.querySQL(sql_get_site_workflow, varMap)[0]
428
+ if site_workflow and site_workflow.startswith("push"):
429
+ q_status_list_str = "('activated', 'sent', 'starting')"
430
+ varMap = {
431
+ ":computingSite": site,
432
+ ":w_mean": w_mean / (24 * 60 * 60),
433
+ }
434
+ sql_get_long_queuing_job_wait_time = sql_get_long_queuing_job_wait_time_template.format(q_status_list_str=q_status_list_str)
435
+ (long_q_n, long_q_mean_day) = self.tbuf.querySQL(sql_get_long_queuing_job_wait_time, varMap)[0]
436
+ if long_q_mean_day:
437
+ long_q_mean = long_q_mean_day * (24 * 60 * 60)
438
+ else:
439
+ long_q_mean = w_mean
440
+ long_q_n = 0
441
+ # update
442
+ site_dict[site].update(
443
+ {
444
+ "n": n_jobs,
445
+ "mean": mean,
446
+ "stdev": stdev,
447
+ "med": median,
448
+ "cl95upp": cl95upp,
449
+ "sum_of_weights": sum_of_weights,
450
+ "w_mean": w_mean,
451
+ "w_stdev": w_stdev,
452
+ "w_cl95upp": w_cl95upp,
453
+ "long_q_n": long_q_n,
454
+ "long_q_mean": long_q_mean,
455
+ }
456
+ )
457
+ # log
458
+ tmp_log.debug(
459
+ (
460
+ "site={site}, n={n}, "
461
+ "mean={mean:.3f}, stdev={stdev:.3f}, med={med:.3f}, cl95upp={cl95upp:.3f}, "
462
+ "sum_of_weights={sum_of_weights:.3f}, "
463
+ "w_mean={w_mean:.3f}, w_stdev={w_stdev:.3f}, w_cl95upp={w_cl95upp:.3f}, "
464
+ "long_q_n={long_q_n}, long_q_mean={long_q_mean:.3f} "
465
+ ).format(site=site, **site_dict[site])
466
+ )
467
+ # turn nan into None
468
+ for key in site_dict[site]:
469
+ if np.isnan(site_dict[site][key]):
470
+ site_dict[site][key] = None
471
+ # return
472
+ return site_dict
473
+ except Exception:
474
+ tmp_log.error(traceback.format_exc())
475
+
476
+ def gshare_preference(self):
477
+ tmp_log = logger_utils.make_logger(main_logger, "FetchData")
478
+ try:
479
+ # get share and hs info
480
+ if self.gshare_status is None:
481
+ self.gshare_status = self.tbuf.getGShareStatus()
482
+ share_name_tree_dict = self.tbuf.get_tree_of_gshare_names()
483
+ # initialize
484
+ gshare_dict = dict()
485
+ total_hs = sum([leaf["target"] for leaf in self.gshare_status])
486
+ # rank and data
487
+ for idx, leaf in enumerate(self.gshare_status):
488
+ rank = idx + 1
489
+ gshare = leaf["name"]
490
+ gshare_dict[gshare] = {
491
+ "gshare": gshare,
492
+ "rank": rank,
493
+ "queuing_hs": leaf["queuing"],
494
+ "running_hs": leaf["running"],
495
+ "target_hs": leaf["target"],
496
+ "usage_perc": leaf["running"] / leaf["target"] if leaf["target"] > 0 else 999999,
497
+ "queue_perc": leaf["queuing"] / leaf["target"] if leaf["target"] > 0 else 999999,
498
+ "norm_usage_perc": leaf["running"] / total_hs if total_hs > 0 else 999999,
499
+ "norm_queue_perc": leaf["queuing"] / total_hs if total_hs > 0 else 999999,
500
+ "norm_target_perc": leaf["target"] / total_hs if total_hs > 0 else 999999,
501
+ "proj_target_hs": 0,
502
+ "eqiv_target_hs": 0,
503
+ "eqiv_usage_perc": 0,
504
+ }
505
+ tmp_log.debug("rank={rank}, gshare={gshare}, usage={usage_perc:.3%}, queue={queue_perc:.3%} ".format(**gshare_dict[gshare]))
506
+ # add L1 share
507
+ tmp_L1_leaves_map = {}
508
+ l1_share_dict = {}
509
+ for l1_share, val in share_name_tree_dict.items():
510
+ tmp_L1_leaves_map.setdefault(l1_share, [])
511
+ fill_leaf_shares(l1_share, val, tmp_L1_leaves_map[l1_share])
512
+ for l1_share, leaves_list in tmp_L1_leaves_map.items():
513
+ l1_share_name = f"L1 {l1_share}"
514
+ l1_share_dict.setdefault(
515
+ l1_share_name,
516
+ {
517
+ "gshare": l1_share_name,
518
+ "rank": -1,
519
+ "queuing_hs": 0,
520
+ "running_hs": 0,
521
+ "target_hs": 0,
522
+ "usage_perc": 0,
523
+ "queue_perc": 0,
524
+ },
525
+ )
526
+ val_dict = l1_share_dict[l1_share_name]
527
+ for leaf in leaves_list:
528
+ if leaf in gshare_dict:
529
+ for field in ["queuing_hs", "running_hs", "target_hs"]:
530
+ val_dict.setdefault(field, 0)
531
+ val_dict[field] += gshare_dict[leaf].get(field, 0)
532
+ val_dict["usage_perc"] = val_dict["running_hs"] / val_dict["target_hs"] if val_dict["target_hs"] > 0 else 999999
533
+ val_dict["queue_perc"] = val_dict["queuing_hs"] / val_dict["target_hs"] if val_dict["target_hs"] > 0 else 999999
534
+ val_dict["norm_usage_perc"] = val_dict["running_hs"] / total_hs if total_hs > 0 else 999999
535
+ val_dict["norm_queue_perc"] = val_dict["queuing_hs"] / total_hs if total_hs > 0 else 999999
536
+ val_dict["norm_target_perc"] = val_dict["target_hs"] / total_hs
537
+ val_dict["proj_target_hs"] = min(
538
+ val_dict["target_hs"],
539
+ max(val_dict["running_hs"], val_dict["queuing_hs"] / 2),
540
+ )
541
+ proj_total_hs = sum([v["proj_target_hs"] for v in l1_share_dict.values()])
542
+ idle_total_hs = total_hs - proj_total_hs
543
+ idle_l1_share_list = []
544
+ idle_threshold_perc = 0.85
545
+ for l1_share_name, val_dict in l1_share_dict.items():
546
+ if val_dict["usage_perc"] < idle_threshold_perc and val_dict["proj_target_hs"] < val_dict["target_hs"]:
547
+ # idle l1 shares
548
+ idle_l1_share_list.append(l1_share_name)
549
+ busy_total_hs = sum([v["target_hs"] for k, v in l1_share_dict.items() if k not in idle_l1_share_list]) + 1
550
+ for l1_share_name, val_dict in l1_share_dict.items():
551
+ if l1_share_name in idle_l1_share_list:
552
+ # idle l1 shares
553
+ val_dict["eqiv_target_hs"] = val_dict["target_hs"]
554
+ val_dict["eqiv_usage_perc"] = val_dict["usage_perc"]
555
+ else:
556
+ # busy l1 shares
557
+ val_dict["eqiv_target_hs"] = val_dict["target_hs"] + idle_total_hs * val_dict["target_hs"] / busy_total_hs
558
+ val_dict["eqiv_usage_perc"] = val_dict["running_hs"] / val_dict["eqiv_target_hs"]
559
+ tmp_log.debug(
560
+ (
561
+ "share={gshare}, usage={usage_perc:.3%}, queue={queue_perc:.3%}, "
562
+ "target_hs={target_hs:.0f}, eqiv_target_hs={eqiv_target_hs:.0f}, "
563
+ "norm_target_perc={norm_target_perc:.3%}, eqiv_usage_perc={eqiv_usage_perc:.3%}, "
564
+ "norm_usage={norm_usage_perc:.3%}, norm_queue={norm_queue_perc:.3%} "
565
+ ).format(**val_dict)
566
+ )
567
+ gshare_dict.update(l1_share_dict)
568
+ # return
569
+ return gshare_dict
570
+ except Exception:
571
+ tmp_log.error(traceback.format_exc())
572
+
573
+ def analy_site_eval(self):
574
+ tmp_log = logger_utils.make_logger(main_logger, "FetchData")
575
+ try:
576
+ # initialize
577
+ site_dict = dict()
578
+ class_A_set = set()
579
+ class_B_set = set()
580
+ class_C_set = set()
581
+ # get resource_type of sites (GRID, hpc, cloud, ...) from schedconfig
582
+ res = self.tbuf.querySQL(
583
+ ("SELECT /* use_json_type */ scj.panda_queue, scj.data.resource_type " "FROM ATLAS_PANDA.schedconfig_json scj "),
584
+ {},
585
+ )
586
+ site_resource_type_map = {site: resource_type for site, resource_type in res}
587
+ # MetricsDB
588
+ mdb = MetricsDB(self.tbuf)
589
+ # get analysis jobs wait time stats
590
+ apjwt_dict = mdb.get_metrics("analy_pmerge_jobs_wait_time", "site")
591
+ # evaluate derived values from stats
592
+ # max of w_cl95upp and long_q_mean for ranking. Only consider GRID sites
593
+ ranking_wait_time_list = []
594
+ for site, v in apjwt_dict.items():
595
+ if site_resource_type_map.get(site) != "GRID":
596
+ continue
597
+ try:
598
+ if v["w_cl95upp"] is not None and v["long_q_mean"] is not None:
599
+ ranking_wait_time = np.maximum(v["w_cl95upp"], v["long_q_mean"])
600
+ ranking_wait_time_list.append(ranking_wait_time)
601
+ else:
602
+ tmp_log.warning(
603
+ ("site={site} none value, skipped : w_cl95upp={w_cl95upp} long_q_mean={long_q_mean} ").format(
604
+ w_cl95upp=v["w_cl95upp"], long_q_mean=v["long_q_mean"]
605
+ )
606
+ )
607
+ continue
608
+ except KeyError:
609
+ continue
610
+ first_one_third_wait_time = np.nanquantile(np.array(ranking_wait_time_list), 0.333)
611
+ last_one_third_wait_time = np.nanquantile(np.array(ranking_wait_time_list), 0.667)
612
+ tmp_log.debug(f"GRID n_sites= {len(ranking_wait_time_list)} wait time PR33={first_one_third_wait_time:.3f} PR67={last_one_third_wait_time:.3f}")
613
+ # get to-running rate of sites
614
+ tmp_st, site_6h_strr_map = get_site_strr_stats(self.tbuf, time_window=60 * 60 * 6)
615
+ if not tmp_st:
616
+ tmp_log.error("failed to get 6h-slot-to-running-rate")
617
+ tmp_st, site_1d_strr_map = get_site_strr_stats(self.tbuf, time_window=60 * 60 * 24)
618
+ if not tmp_st:
619
+ tmp_log.error("failed to get 1d-slot-to-running-rate")
620
+ # for each site
621
+ for site in apjwt_dict:
622
+ # from wait time stats
623
+ # TODO: to consider failure rate, site fullness, etc.
624
+ v = apjwt_dict[site]
625
+ # evaluate derived values
626
+ try:
627
+ if v["w_cl95upp"] is not None and v["long_q_mean"] is not None:
628
+ v["ranking_wait_time"] = np.maximum(v["w_cl95upp"], v["long_q_mean"])
629
+ # v['is_slowing_down'] = (v['long_q_mean'] > v['w_cl95upp'] and v['long_q_n'] >= 3)
630
+ else:
631
+ tmp_log.warning(f"site={site} none value, skipped : w_cl95upp={v['w_cl95upp']} long_q_mean={v['long_q_mean']} ")
632
+ continue
633
+ except KeyError as e:
634
+ tmp_log.warning(f"site={site} misses value, skipped : {e} ")
635
+ continue
636
+ # initialize
637
+ site_dict[site] = dict()
638
+ # resource type of the site
639
+ site_dict[site]["resource_type"] = site_resource_type_map.get(site)
640
+ # to-running rate
641
+ site_6h_strr = site_6h_strr_map.get(site, 0)
642
+ site_1d_strr = site_1d_strr_map.get(site, 0)
643
+ site_dict[site]["strr_6h"] = site_6h_strr
644
+ site_dict[site]["strr_1d"] = site_1d_strr
645
+ # classify
646
+ if (
647
+ v["ranking_wait_time"] <= max(first_one_third_wait_time, 3600)
648
+ or (v["w_cl95upp"] <= max(first_one_third_wait_time, 3600) and v["long_q_n"] <= 3)
649
+ ) and site_1d_strr > 0:
650
+ # class A (1)
651
+ site_dict[site]["class"] = 1
652
+ class_A_set.add(site)
653
+ elif v["ranking_wait_time"] > max(last_one_third_wait_time, 10800):
654
+ # class C (-1)
655
+ site_dict[site]["class"] = -1
656
+ class_C_set.add(site)
657
+ else:
658
+ # class B (0)
659
+ site_dict[site]["class"] = 0
660
+ class_B_set.add(site)
661
+ # log
662
+ tmp_log.debug(
663
+ ("site={site}, class={class}, type={resource_type}, strr_6h={strr_6h:.3f}, strr_1d={strr_1d:.3f} ").format(site=site, **site_dict[site])
664
+ )
665
+ # turn nan into None
666
+ for key in site_dict[site]:
667
+ _val = site_dict[site][key]
668
+ if not isinstance(_val, str) and np.isnan(_val):
669
+ site_dict[site][key] = None
670
+ # log
671
+ tmp_log.debug(
672
+ ("class_A ({}) : {} ; class_B ({}) : {} ; class_C ({}) : {}").format(
673
+ len(class_A_set),
674
+ ",".join(sorted(list(class_A_set))),
675
+ len(class_B_set),
676
+ ",".join(sorted(list(class_B_set))),
677
+ len(class_C_set),
678
+ ",".join(sorted(list(class_C_set))),
679
+ )
680
+ )
681
+ # return
682
+ return site_dict
683
+ except Exception:
684
+ tmp_log.error(traceback.format_exc())
685
+
686
+ def users_jobs_stats(self):
687
+ prod_source_label = "user"
688
+ tmp_log = logger_utils.make_logger(main_logger, "FetchData")
689
+ tmp_log.debug("start")
690
+ try:
691
+ # initialize
692
+ site_gshare_dict = dict()
693
+ # get users jobs stats
694
+ jobsStatsPerUser = {}
695
+ varMap = {}
696
+ varMap[":prodSourceLabel"] = prod_source_label
697
+ varMap[":pmerge"] = "pmerge"
698
+ varMap[":gangarbt"] = "gangarbt"
699
+ sqlJ = (
700
+ "SELECT COUNT(*),SUM(coreCount),prodUserName,jobStatus,gshare,computingSite "
701
+ "FROM ATLAS_PANDA.jobsActive4 "
702
+ "WHERE prodSourceLabel=:prodSourceLabel "
703
+ "AND processingType<>:pmerge "
704
+ "AND prodUserName<>:gangarbt "
705
+ "GROUP BY prodUserName,jobStatus,gshare,computingSite "
706
+ )
707
+ # result
708
+ res = self.tbuf.querySQL(sqlJ, varMap)
709
+ if res is None:
710
+ tmp_log.debug(f"got {res} ")
711
+ else:
712
+ tmp_log.debug(f"total {len(res)} ")
713
+ # make map
714
+ for cnt, n_slots, prodUserName, jobStatus, gshare, computingSite in res:
715
+ # append to PerUser map
716
+ jobsStatsPerUser.setdefault(computingSite, {})
717
+ jobsStatsPerUser[computingSite].setdefault(gshare, {})
718
+ jobsStatsPerUser[computingSite][gshare].setdefault(
719
+ prodUserName,
720
+ {
721
+ "nDefined": 0,
722
+ "nAssigned": 0,
723
+ "nActivated": 0,
724
+ "nStarting": 0,
725
+ "nQueue": 0,
726
+ "nRunning": 0,
727
+ "slotsDefined": 0,
728
+ "slotsAssigned": 0,
729
+ "slotsActivated": 0,
730
+ "slotsStarting": 0,
731
+ "slotsQueue": 0,
732
+ "slotsRunning": 0,
733
+ },
734
+ )
735
+ jobsStatsPerUser[computingSite][gshare].setdefault(
736
+ "_total",
737
+ {
738
+ "nDefined": 0,
739
+ "nAssigned": 0,
740
+ "nActivated": 0,
741
+ "nStarting": 0,
742
+ "nQueue": 0,
743
+ "nRunning": 0,
744
+ "slotsDefined": 0,
745
+ "slotsAssigned": 0,
746
+ "slotsActivated": 0,
747
+ "slotsStarting": 0,
748
+ "slotsQueue": 0,
749
+ "slotsRunning": 0,
750
+ },
751
+ )
752
+ # count # of running/done and activated
753
+ if jobStatus in ["defined", "assigned", "activated", "starting"]:
754
+ status_name = f"n{jobStatus.capitalize()}"
755
+ slots_status_name = f"slots{jobStatus.capitalize()}"
756
+ jobsStatsPerUser[computingSite][gshare][prodUserName][status_name] += cnt
757
+ jobsStatsPerUser[computingSite][gshare][prodUserName]["nQueue"] += cnt
758
+ jobsStatsPerUser[computingSite][gshare]["_total"][status_name] += cnt
759
+ jobsStatsPerUser[computingSite][gshare]["_total"]["nQueue"] += cnt
760
+ jobsStatsPerUser[computingSite][gshare][prodUserName][slots_status_name] += n_slots
761
+ jobsStatsPerUser[computingSite][gshare][prodUserName]["slotsQueue"] += n_slots
762
+ jobsStatsPerUser[computingSite][gshare]["_total"][slots_status_name] += n_slots
763
+ jobsStatsPerUser[computingSite][gshare]["_total"]["slotsQueue"] += n_slots
764
+ elif jobStatus in ["running"]:
765
+ jobsStatsPerUser[computingSite][gshare][prodUserName]["nRunning"] += cnt
766
+ jobsStatsPerUser[computingSite][gshare]["_total"]["nRunning"] += cnt
767
+ jobsStatsPerUser[computingSite][gshare][prodUserName]["slotsRunning"] += n_slots
768
+ jobsStatsPerUser[computingSite][gshare]["_total"]["slotsRunning"] += n_slots
769
+ # fill
770
+ for computingSite in jobsStatsPerUser:
771
+ g_dict = jobsStatsPerUser[computingSite]
772
+ for gshare in g_dict:
773
+ data_dict = g_dict[gshare]
774
+ site_gshare_dict[(computingSite, gshare)] = data_dict
775
+ tmp_log.debug(f"site={computingSite}, gshare={gshare}, stats={data_dict}")
776
+ # done
777
+ tmp_log.debug("done")
778
+ return site_gshare_dict
779
+ except Exception:
780
+ tmp_log.error(traceback.format_exc())
781
+
782
+ def analy_user_eval(self):
783
+ tmp_log = logger_utils.make_logger(main_logger, "FetchData")
784
+ try:
785
+ # initialize
786
+ user_dict = dict()
787
+ # MetricsDB
788
+ mdb = MetricsDB(self.tbuf)
789
+ # get analysis site classification evalutation
790
+ ase_dict = mdb.get_metrics("analy_site_eval", "site", fresher_than_minutes_ago=120)
791
+ # get users jobs stats
792
+ ujs_dict = mdb.get_metrics("users_jobs_stats", fresher_than_minutes_ago=15)
793
+ # for each site x gshare
794
+ for (site, gshare), usage_dict in ujs_dict.items():
795
+ # count only User Analysis & Express Analysis
796
+ if gshare not in ["User Analysis", "Express Analysis"]:
797
+ continue
798
+ # get site evaluation data
799
+ try:
800
+ site_eval_dict = ase_dict[site]
801
+ except KeyError:
802
+ tmp_log.warning(f"analy_site_eval missed site={site} gshare={gshare}, skipped ")
803
+ continue
804
+ # get site class value
805
+ try:
806
+ site_class_value = site_eval_dict["class"]
807
+ except KeyError:
808
+ tmp_log.warning(f"analy_site_eval class missed for site={site} gshare={gshare}, skipped ")
809
+ continue
810
+ else:
811
+ site_class_rank = class_value_rank_map[site_class_value]
812
+ # site resource_type
813
+ site_resource_type = site_eval_dict.get("resource_type")
814
+ # skip non-GRID sites
815
+ if site_resource_type != "GRID":
816
+ continue
817
+ # evaluate usage of GRID sites for each user
818
+ for user, v in usage_dict.items():
819
+ # initialize
820
+ user_dict.setdefault(user, {"user": user})
821
+ for _rank in class_value_rank_map.values():
822
+ user_dict[user].setdefault(
823
+ _rank,
824
+ {
825
+ "nQueue": 0,
826
+ "nRunning": 0,
827
+ "slotsQueue": 0,
828
+ "slotsRunning": 0,
829
+ },
830
+ )
831
+ # fill nQ & nR at each site class of each user
832
+ user_dict[user][site_class_rank]["nQueue"] += v["nQueue"]
833
+ user_dict[user][site_class_rank]["nRunning"] += v["nRunning"]
834
+ user_dict[user][site_class_rank]["slotsQueue"] += v["slotsQueue"]
835
+ user_dict[user][site_class_rank]["slotsRunning"] += v["slotsRunning"]
836
+ # evaluate derived values for each user
837
+ threshold_A = self.tbuf.getConfigValue("analy_eval", "USER_USAGE_THRESHOLD_A")
838
+ if threshold_A is None:
839
+ threshold_A = 1000
840
+ threshold_B = self.tbuf.getConfigValue("analy_eval", "USER_USAGE_THRESHOLD_B")
841
+ if threshold_B is None:
842
+ threshold_B = 8000
843
+ for user, d in copy.deepcopy(user_dict).items():
844
+ run_slots_A = d["A_sites"]["slotsRunning"]
845
+ run_slots_AB = d["A_sites"]["slotsRunning"] + d["B_sites"]["slotsRunning"]
846
+ # remaining allowed slots
847
+ rem_slots_A = max(threshold_A - run_slots_A, 0)
848
+ rem_slots_B = max(threshold_B - run_slots_AB, 0)
849
+ user_dict[user]["rem_slots_A"] = rem_slots_A
850
+ user_dict[user]["rem_slots_B"] = rem_slots_B
851
+ user_dict[user]["rem_slot_ratio_A"] = rem_slots_A / threshold_A
852
+ user_dict[user]["rem_slot_ratio_B"] = rem_slots_B / threshold_B
853
+ # log
854
+ tmp_log.debug(f"{user_dict}")
855
+ # return
856
+ return user_dict
857
+ except Exception:
858
+ tmp_log.error(traceback.format_exc())
859
+
860
+
861
+ # main
862
+ def main(tbuf=None, **kwargs):
863
+ requester_id = GenericThread().get_full_id(__name__, sys.modules[__name__].__file__)
864
+
865
+ # instantiate TB
866
+ if tbuf is None:
867
+ from pandaserver.taskbuffer.TaskBuffer import taskBuffer
868
+
869
+ taskBuffer.init(
870
+ panda_config.dbhost,
871
+ panda_config.dbpasswd,
872
+ nDBConnection=1,
873
+ useTimeout=True,
874
+ requester=requester_id,
875
+ )
876
+ else:
877
+ taskBuffer = tbuf
878
+ # pid
879
+ my_pid = os.getpid()
880
+ my_full_pid = f"{socket.getfqdn().split('.')[0]}-{os.getpgrp()}-{my_pid}"
881
+ # go
882
+ if DRY_RUN:
883
+ # dry run, regardless of lock, not update DB
884
+ fetcher = FetchData(taskBuffer)
885
+ # loop over all fetch data methods to run and update to DB
886
+ for metric_name, key_type, period in metric_list:
887
+ main_logger.debug(f"(dry-run) start {metric_name}")
888
+ # fetch data and update DB
889
+ the_method = getattr(fetcher, metric_name)
890
+ fetched_data = the_method()
891
+ if fetched_data is None:
892
+ main_logger.warning(f"(dry-run) {metric_name} got no valid data")
893
+ continue
894
+ main_logger.debug(f"(dry-run) done {metric_name}")
895
+ else:
896
+ # real run, will update DB
897
+ # instantiate
898
+ mdb = MetricsDB(taskBuffer)
899
+ fetcher = FetchData(taskBuffer)
900
+ # loop over all fetch data methods to run and update to DB
901
+ for metric_name, key_type, period in metric_list:
902
+ # metric lock
903
+ lock_component_name = f"pandaMetr.{metric_name:.16}.{adler32(metric_name.encode('utf-8')):0x}"
904
+ # try to get lock
905
+ got_lock = taskBuffer.lockProcess_PANDA(component=lock_component_name, pid=my_full_pid, time_limit=period)
906
+ if got_lock:
907
+ main_logger.debug(f"got lock of {metric_name}")
908
+ else:
909
+ main_logger.debug(f"{metric_name} locked by other process; skipped...")
910
+ continue
911
+ main_logger.debug(f"start {metric_name}")
912
+ # fetch data and update DB
913
+ the_method = getattr(fetcher, metric_name)
914
+ fetched_data = the_method()
915
+ if fetched_data is None:
916
+ main_logger.warning(f"{metric_name} got no valid data")
917
+ continue
918
+ mdb.update(metric=metric_name, key_type=key_type, entity_dict=fetched_data)
919
+ main_logger.debug(f"done {metric_name}")
920
+ # stop taskBuffer if created inside this script
921
+ if tbuf is None:
922
+ taskBuffer.cleanup(requester=requester_id)
923
+
924
+
925
+ # run
926
+ if __name__ == "__main__":
927
+ main()