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.
- panda_server_source-0.8.0.dist-info/METADATA +55 -0
- panda_server_source-0.8.0.dist-info/RECORD +375 -0
- panda_server_source-0.8.0.dist-info/WHEEL +4 -0
- panda_server_source-0.8.0.dist-info/licenses/LICENSE.txt +202 -0
- pandajedi/__init__.py +0 -0
- pandajedi/jedibrokerage/AtlasAnalJobBroker.py +2343 -0
- pandajedi/jedibrokerage/AtlasBrokerUtils.py +1292 -0
- pandajedi/jedibrokerage/AtlasProdJobBroker.py +2064 -0
- pandajedi/jedibrokerage/AtlasProdTaskBroker.py +629 -0
- pandajedi/jedibrokerage/GenJobBroker.py +381 -0
- pandajedi/jedibrokerage/JobBrokerBase.py +133 -0
- pandajedi/jedibrokerage/TaskBrokerBase.py +15 -0
- pandajedi/jedibrokerage/__init__.py +0 -0
- pandajedi/jediconfig/__init__.py +0 -0
- pandajedi/jediconfig/jedi_config.py +40 -0
- pandajedi/jedicore/FactoryBase.py +182 -0
- pandajedi/jedicore/Interaction.py +433 -0
- pandajedi/jedicore/JediDBProxy.py +56 -0
- pandajedi/jedicore/JediDBProxyPool.py +37 -0
- pandajedi/jedicore/JediDBSchemaInfo.py +17 -0
- pandajedi/jedicore/JediException.py +13 -0
- pandajedi/jedicore/JediTaskBuffer.py +984 -0
- pandajedi/jedicore/JediTaskBufferInterface.py +50 -0
- pandajedi/jedicore/MsgWrapper.py +124 -0
- pandajedi/jedicore/ProcessUtils.py +34 -0
- pandajedi/jedicore/SiteCandidate.py +90 -0
- pandajedi/jedicore/ThreadUtils.py +205 -0
- pandajedi/jedicore/__init__.py +0 -0
- pandajedi/jediddm/AtlasDDMClient.py +1708 -0
- pandajedi/jediddm/DDMClientBase.py +16 -0
- pandajedi/jediddm/DDMInterface.py +83 -0
- pandajedi/jediddm/GenDDMClient.py +17 -0
- pandajedi/jediddm/__init__.py +0 -0
- pandajedi/jedidog/AtlasAnalWatchDog.py +648 -0
- pandajedi/jedidog/AtlasDataCarouselWatchDog.py +189 -0
- pandajedi/jedidog/AtlasDataLocalityUpdaterWatchDog.py +215 -0
- pandajedi/jedidog/AtlasProdWatchDog.py +443 -0
- pandajedi/jedidog/AtlasQueueFillerWatchDog.py +800 -0
- pandajedi/jedidog/AtlasTaskWithholderWatchDog.py +245 -0
- pandajedi/jedidog/AtlasWorkflowManagerWatchDog.py +63 -0
- pandajedi/jedidog/GenWatchDog.py +21 -0
- pandajedi/jedidog/JumboWatchDog.py +147 -0
- pandajedi/jedidog/TypicalWatchDogBase.py +122 -0
- pandajedi/jedidog/WatchDogBase.py +53 -0
- pandajedi/jedidog/__init__.py +0 -0
- pandajedi/jediexec/reniceJEDI.py +25 -0
- pandajedi/jedigen/AtlasTaskGenerator.py +110 -0
- pandajedi/jedigen/TaskGeneratorBase.py +15 -0
- pandajedi/jedigen/__init__.py +0 -0
- pandajedi/jedimsgprocessor/__init__.py +0 -0
- pandajedi/jedimsgprocessor/ai_parameter_adjuster_msg_processor +71 -0
- pandajedi/jedimsgprocessor/atlas_idds_msg_processor.py +66 -0
- pandajedi/jedimsgprocessor/base_msg_processor.py +33 -0
- pandajedi/jedimsgprocessor/forwarding_msg_processor.py +29 -0
- pandajedi/jedimsgprocessor/hpo_msg_processor.py +93 -0
- pandajedi/jedimsgprocessor/jedi_contents_feeder_msg_processor.py +63 -0
- pandajedi/jedimsgprocessor/jedi_job_generator_msg_processor.py +185 -0
- pandajedi/jedimsgprocessor/jedi_post_processor_msg_processor.py +82 -0
- pandajedi/jedimsgprocessor/kafka_msg_processor.py +44 -0
- pandajedi/jedimsgprocessor/panda_callback_msg_processor.py +182 -0
- pandajedi/jedimsgprocessor/panda_to_jedi_msg_processor.py +74 -0
- pandajedi/jedimsgprocessor/processing_msg_processor.py +134 -0
- pandajedi/jedimsgprocessor/status_report_msg_processor.py +129 -0
- pandajedi/jedimsgprocessor/tape_carousel_msg_processor.py +118 -0
- pandajedi/jedimsgprocessor/workflow_manager_msg_processor.py +89 -0
- pandajedi/jediorder/ContentsFeeder.py +785 -0
- pandajedi/jediorder/JediDaemon.py +27 -0
- pandajedi/jediorder/JediKnight.py +55 -0
- pandajedi/jediorder/JediMaster.py +238 -0
- pandajedi/jediorder/JediMsgProcessor.py +32 -0
- pandajedi/jediorder/JobBroker.py +33 -0
- pandajedi/jediorder/JobGenerator.py +2763 -0
- pandajedi/jediorder/JobSplitter.py +308 -0
- pandajedi/jediorder/JobThrottler.py +28 -0
- pandajedi/jediorder/PostProcessor.py +166 -0
- pandajedi/jediorder/TaskBroker.py +268 -0
- pandajedi/jediorder/TaskCommando.py +334 -0
- pandajedi/jediorder/TaskGenerator.py +17 -0
- pandajedi/jediorder/TaskRefiner.py +611 -0
- pandajedi/jediorder/TaskSetupper.py +17 -0
- pandajedi/jediorder/WatchDog.py +79 -0
- pandajedi/jediorder/__init__.py +0 -0
- pandajedi/jedipprocess/AtlasAnalPostProcessor.py +404 -0
- pandajedi/jedipprocess/AtlasPostProcessorUtils.py +50 -0
- pandajedi/jedipprocess/AtlasProdPostProcessor.py +178 -0
- pandajedi/jedipprocess/GenPostProcessor.py +35 -0
- pandajedi/jedipprocess/MailTemplates.py +185 -0
- pandajedi/jedipprocess/PostProcessorBase.py +269 -0
- pandajedi/jedipprocess/__init__.py +0 -0
- pandajedi/jedirefine/AtlasAnalTaskRefiner.py +259 -0
- pandajedi/jedirefine/AtlasProdTaskRefiner.py +162 -0
- pandajedi/jedirefine/GenTaskRefiner.py +61 -0
- pandajedi/jedirefine/RefinerUtils.py +154 -0
- pandajedi/jedirefine/TaskRefinerBase.py +996 -0
- pandajedi/jedirefine/__init__.py +0 -0
- pandajedi/jedisetup/AtlasTaskSetupper.py +309 -0
- pandajedi/jedisetup/GenTaskSetupper.py +16 -0
- pandajedi/jedisetup/SimpleTaskSetupper.py +152 -0
- pandajedi/jedisetup/TaskSetupperBase.py +15 -0
- pandajedi/jedisetup/__init__.py +0 -0
- pandajedi/jeditest/SchemaChecker.py +41 -0
- pandajedi/jeditest/addContWoSoTask.py +54 -0
- pandajedi/jeditest/addDataCarouselRequest.py +47 -0
- pandajedi/jeditest/addEventServiceTask.py +101 -0
- pandajedi/jeditest/addHpoTask.py +81 -0
- pandajedi/jeditest/addIDDSStageInRequest.py +56 -0
- pandajedi/jeditest/addNonAtlasTask.py +71 -0
- pandajedi/jeditest/addNonAtlasTaskEnc.py +64 -0
- pandajedi/jeditest/addTestTask.py +124 -0
- pandajedi/jeditest/addTestTaskParamToDEFT.py +70 -0
- pandajedi/jeditest/addTestTaskWithMerger.py +97 -0
- pandajedi/jeditest/brokerTest.py +78 -0
- pandajedi/jeditest/cancelDataCarouselRequest.py +35 -0
- pandajedi/jeditest/contentsFeederTest.py +28 -0
- pandajedi/jeditest/contentsFeederTestWithTask.py +18 -0
- pandajedi/jeditest/ddmTest.py +4 -0
- pandajedi/jeditest/jobGenForOneTask.py +38 -0
- pandajedi/jeditest/jobGeneratorTest.py +43 -0
- pandajedi/jeditest/jobSplitterTest.py +104 -0
- pandajedi/jeditest/jobThrottlerTest.py +22 -0
- pandajedi/jeditest/msgProcessorTest.py +9 -0
- pandajedi/jeditest/postProcessorTest.py +18 -0
- pandajedi/jeditest/resubmitDataCarouselRequest.py +35 -0
- pandajedi/jeditest/resubmitTaskParam.py +20 -0
- pandajedi/jeditest/taskBrokerTest.py +22 -0
- pandajedi/jeditest/taskBrokerTestWithTask.py +38 -0
- pandajedi/jeditest/taskBufferTest.py +5 -0
- pandajedi/jeditest/taskCommandoTest.py +28 -0
- pandajedi/jeditest/taskRefinerTest.py +30 -0
- pandajedi/jeditest/testOneTaskToRefine.py +49 -0
- pandajedi/jeditest/testOneTaskToTaskBroker.py +42 -0
- pandajedi/jeditest/testPandaDDMMap.py +62 -0
- pandajedi/jeditest/updateDataCarouselRequestParams.py +50 -0
- pandajedi/jeditest/watchDogTest.py +25 -0
- pandajedi/jeditest/workQueueTest.py +22 -0
- pandajedi/jedithrottle/AtlasAnalJobThrottler.py +19 -0
- pandajedi/jedithrottle/AtlasProdJobThrottler.py +18 -0
- pandajedi/jedithrottle/GenJobThrottler.py +26 -0
- pandajedi/jedithrottle/JobThrottlerBase.py +496 -0
- pandajedi/jedithrottle/__init__.py +0 -0
- pandaserver/__init__.py +0 -0
- pandaserver/api/__init__.py +0 -0
- pandaserver/api/v1/__init__.py +0 -0
- pandaserver/api/v1/common.py +339 -0
- pandaserver/api/v1/credential_management_api.py +372 -0
- pandaserver/api/v1/data_carousel_api.py +264 -0
- pandaserver/api/v1/event_api.py +269 -0
- pandaserver/api/v1/file_server_api.py +942 -0
- pandaserver/api/v1/harvester_api.py +445 -0
- pandaserver/api/v1/http_client.py +166 -0
- pandaserver/api/v1/idds_api.py +180 -0
- pandaserver/api/v1/job_api.py +463 -0
- pandaserver/api/v1/metaconfig_api.py +122 -0
- pandaserver/api/v1/pilot_api.py +895 -0
- pandaserver/api/v1/statistics_api.py +207 -0
- pandaserver/api/v1/system_api.py +186 -0
- pandaserver/api/v1/task_api.py +1520 -0
- pandaserver/api/v1/tests/__init__.py +0 -0
- pandaserver/api/v1/tests/credential_management_api_tests.py +116 -0
- pandaserver/api/v1/tests/data_carousel_api_tests.py +82 -0
- pandaserver/api/v1/tests/harvester_api_tests.py +188 -0
- pandaserver/api/v1/tests/idds_api_tests.py +45 -0
- pandaserver/api/v1/tests/job_api_tests.py +112 -0
- pandaserver/api/v1/tests/pilot_api_tests.py +154 -0
- pandaserver/api/v1/tests/statistics_api_tests.py +104 -0
- pandaserver/api/v1/tests/system_api_tests.py +58 -0
- pandaserver/api/v1/tests/task_api_tests.py +590 -0
- pandaserver/api/v1/version.py +2 -0
- pandaserver/api/v1/workflow_api.py +136 -0
- pandaserver/brokerage/SiteMapper.py +406 -0
- pandaserver/brokerage/__init__.py +0 -0
- pandaserver/brokerage/broker.py +272 -0
- pandaserver/config/__init__.py +0 -0
- pandaserver/config/config_utils.py +15 -0
- pandaserver/config/daemon_config.py +29 -0
- pandaserver/config/mcp_config.py +38 -0
- pandaserver/config/panda_config.py +233 -0
- pandaserver/configurator/Carbon.py +65 -0
- pandaserver/configurator/Configurator.py +908 -0
- pandaserver/configurator/__init__.py +0 -0
- pandaserver/configurator/aux.py +73 -0
- pandaserver/daemons/__init__.py +0 -0
- pandaserver/daemons/master.py +110 -0
- pandaserver/daemons/master_systemd.py +79 -0
- pandaserver/daemons/scripts/__init__.py +0 -0
- pandaserver/daemons/scripts/add_main.py +264 -0
- pandaserver/daemons/scripts/add_sub.py +221 -0
- pandaserver/daemons/scripts/cache_pilots.py +47 -0
- pandaserver/daemons/scripts/cache_schedconfig.py +286 -0
- pandaserver/daemons/scripts/carbon.py +50 -0
- pandaserver/daemons/scripts/configurator.py +94 -0
- pandaserver/daemons/scripts/copyArchive.py +1387 -0
- pandaserver/daemons/scripts/datasetManager.py +1154 -0
- pandaserver/daemons/scripts/dummy_test.py +25 -0
- pandaserver/daemons/scripts/evpPD2P.py +144 -0
- pandaserver/daemons/scripts/hs_scrapers.py +264 -0
- pandaserver/daemons/scripts/metric_collector.py +927 -0
- pandaserver/daemons/scripts/panda_activeusers_query.py +80 -0
- pandaserver/daemons/scripts/pilotStreaming.py +109 -0
- pandaserver/daemons/scripts/process_workflow_files_daemon.py +166 -0
- pandaserver/daemons/scripts/recover_lost_files_daemon.py +177 -0
- pandaserver/daemons/scripts/task_evaluator.py +405 -0
- pandaserver/daemons/scripts/tmpwatch.py +70 -0
- pandaserver/daemons/scripts/worker_synchronization.py +97 -0
- pandaserver/daemons/utils.py +753 -0
- pandaserver/dataservice/DataServiceUtils.py +323 -0
- pandaserver/dataservice/ErrorCode.py +25 -0
- pandaserver/dataservice/RecoverLostFilesCore.py +322 -0
- pandaserver/dataservice/__init__.py +0 -0
- pandaserver/dataservice/activator.py +79 -0
- pandaserver/dataservice/adder_atlas_plugin.py +1106 -0
- pandaserver/dataservice/adder_dummy_plugin.py +31 -0
- pandaserver/dataservice/adder_gen.py +954 -0
- pandaserver/dataservice/adder_plugin_base.py +27 -0
- pandaserver/dataservice/adder_result.py +75 -0
- pandaserver/dataservice/adder_simple_plugin.py +160 -0
- pandaserver/dataservice/closer.py +247 -0
- pandaserver/dataservice/closer_atlas_plugin.py +90 -0
- pandaserver/dataservice/ddm.py +1381 -0
- pandaserver/dataservice/ddm_handler.py +95 -0
- pandaserver/dataservice/dyn_data_distributer.py +824 -0
- pandaserver/dataservice/event_lookup_client_ei.py +77 -0
- pandaserver/dataservice/event_picker.py +268 -0
- pandaserver/dataservice/finisher.py +194 -0
- pandaserver/dataservice/setupper.py +225 -0
- pandaserver/dataservice/setupper_atlas_plugin.py +1851 -0
- pandaserver/dataservice/setupper_dummy_plugin.py +47 -0
- pandaserver/dataservice/setupper_plugin_base.py +67 -0
- pandaserver/jobdispatcher/DispatcherUtils.py +35 -0
- pandaserver/jobdispatcher/ErrorCode.py +19 -0
- pandaserver/jobdispatcher/JobDispatcher.py +1625 -0
- pandaserver/jobdispatcher/Protocol.py +443 -0
- pandaserver/jobdispatcher/Watcher.py +186 -0
- pandaserver/jobdispatcher/__init__.py +0 -0
- pandaserver/pandamcp/__init__.py +0 -0
- pandaserver/pandamcp/mcp_main.py +56 -0
- pandaserver/pandamcp/mcp_test_client.py +95 -0
- pandaserver/pandamcp/mcp_utils.py +74 -0
- pandaserver/proxycache/__init__.py +0 -0
- pandaserver/proxycache/panda_proxy_cache.py +240 -0
- pandaserver/proxycache/token_cache.py +141 -0
- pandaserver/server/.gacl +6 -0
- pandaserver/server/panda.py +539 -0
- pandaserver/srvcore/CoreUtils.py +572 -0
- pandaserver/srvcore/MailUtils.py +106 -0
- pandaserver/srvcore/__init__.py +0 -0
- pandaserver/srvcore/allowed_methods.py +100 -0
- pandaserver/srvcore/oidc_utils.py +152 -0
- pandaserver/srvcore/panda_request.py +196 -0
- pandaserver/srvcore/srv_msg_utils.py +42 -0
- pandaserver/taskbuffer/ConBridge.py +513 -0
- pandaserver/taskbuffer/DBProxyPool.py +95 -0
- pandaserver/taskbuffer/DataCarousel.py +3053 -0
- pandaserver/taskbuffer/DatasetSpec.py +137 -0
- pandaserver/taskbuffer/DdmSpec.py +201 -0
- pandaserver/taskbuffer/ErrorCode.py +87 -0
- pandaserver/taskbuffer/EventServiceUtils.py +396 -0
- pandaserver/taskbuffer/FileSpec.py +290 -0
- pandaserver/taskbuffer/GlobalShares.py +244 -0
- pandaserver/taskbuffer/HarvesterMetricsSpec.py +92 -0
- pandaserver/taskbuffer/Initializer.py +82 -0
- pandaserver/taskbuffer/InputChunk.py +1080 -0
- pandaserver/taskbuffer/JediCacheSpec.py +57 -0
- pandaserver/taskbuffer/JediDatasetSpec.py +588 -0
- pandaserver/taskbuffer/JediFileSpec.py +242 -0
- pandaserver/taskbuffer/JediTaskSpec.py +1687 -0
- pandaserver/taskbuffer/JobSpec.py +1006 -0
- pandaserver/taskbuffer/JobUtils.py +232 -0
- pandaserver/taskbuffer/NucleusSpec.py +116 -0
- pandaserver/taskbuffer/OraDBProxy.py +174 -0
- pandaserver/taskbuffer/PanDAMsgProcessor.py +8 -0
- pandaserver/taskbuffer/PandaDBSchemaInfo.py +18 -0
- pandaserver/taskbuffer/ParseJobXML.py +350 -0
- pandaserver/taskbuffer/PickleFileSpec.py +18 -0
- pandaserver/taskbuffer/PickleJobSpec.py +25 -0
- pandaserver/taskbuffer/PrioUtil.py +40 -0
- pandaserver/taskbuffer/ProcessGroups.py +111 -0
- pandaserver/taskbuffer/ResourceSpec.py +175 -0
- pandaserver/taskbuffer/SQLDumper.py +37 -0
- pandaserver/taskbuffer/SQLManager.py +60 -0
- pandaserver/taskbuffer/SiteSpec.py +304 -0
- pandaserver/taskbuffer/SupErrors.py +10 -0
- pandaserver/taskbuffer/TaskBuffer.py +2882 -0
- pandaserver/taskbuffer/TaskBufferInterface.py +163 -0
- pandaserver/taskbuffer/Utils.py +848 -0
- pandaserver/taskbuffer/WorkQueue.py +227 -0
- pandaserver/taskbuffer/WorkQueueMapper.py +203 -0
- pandaserver/taskbuffer/WorkerSpec.py +143 -0
- pandaserver/taskbuffer/WrappedCursor.py +487 -0
- pandaserver/taskbuffer/WrappedPickle.py +88 -0
- pandaserver/taskbuffer/WrappedPostgresConn.py +24 -0
- pandaserver/taskbuffer/__init__.py +0 -0
- pandaserver/taskbuffer/db_proxy_mods/OraDBProxy_structure.png +0 -0
- pandaserver/taskbuffer/db_proxy_mods/__init__.py +7 -0
- pandaserver/taskbuffer/db_proxy_mods/base_module.py +841 -0
- pandaserver/taskbuffer/db_proxy_mods/data_carousel_module.py +772 -0
- pandaserver/taskbuffer/db_proxy_mods/entity_module.py +3804 -0
- pandaserver/taskbuffer/db_proxy_mods/job_complex_module.py +5661 -0
- pandaserver/taskbuffer/db_proxy_mods/job_standalone_module.py +2640 -0
- pandaserver/taskbuffer/db_proxy_mods/metrics_module.py +874 -0
- pandaserver/taskbuffer/db_proxy_mods/misc_standalone_module.py +4272 -0
- pandaserver/taskbuffer/db_proxy_mods/task_complex_module.py +6172 -0
- pandaserver/taskbuffer/db_proxy_mods/task_event_module.py +4953 -0
- pandaserver/taskbuffer/db_proxy_mods/task_standalone_module.py +4999 -0
- pandaserver/taskbuffer/db_proxy_mods/task_utils_module.py +2063 -0
- pandaserver/taskbuffer/db_proxy_mods/worker_module.py +1944 -0
- pandaserver/taskbuffer/db_proxy_mods/workflow_module.py +832 -0
- pandaserver/taskbuffer/retryModule.py +552 -0
- pandaserver/taskbuffer/task_split_rules.py +308 -0
- pandaserver/taskbuffer/workflow_processor.py +249 -0
- pandaserver/taskbuffer/wrapped_oracle_conn.py +18 -0
- pandaserver/test/SchemaChecker.py +40 -0
- pandaserver/test/__init__.py +0 -0
- pandaserver/test/banUser.py +43 -0
- pandaserver/test/boostPrio.py +19 -0
- pandaserver/test/boostUser.py +35 -0
- pandaserver/test/callbackDDM.py +11 -0
- pandaserver/test/daod_on_demand.py +95 -0
- pandaserver/test/finishJob.py +184 -0
- pandaserver/test/finishTaskJEDI.py +11 -0
- pandaserver/test/getJobs.py +53 -0
- pandaserver/test/killJob.py +79 -0
- pandaserver/test/killJobLowPrio.py +99 -0
- pandaserver/test/killJobsInTask.py +56 -0
- pandaserver/test/killTask.py +80 -0
- pandaserver/test/killTaskJEDI.py +11 -0
- pandaserver/test/killUser.py +83 -0
- pandaserver/test/lsst/README.txt +7 -0
- pandaserver/test/lsst/lsstSubmit.py +139 -0
- pandaserver/test/lsst/lsstSubmitMERGEtest.py +141 -0
- pandaserver/test/lsst/lsstSubmitPhosim332.py +53 -0
- pandaserver/test/lsst/prepare-client-tarball-from-bigpanda-server.sh +41 -0
- pandaserver/test/reassignJobs.py +17 -0
- pandaserver/test/reassignSite.py +82 -0
- pandaserver/test/reassignTask.py +117 -0
- pandaserver/test/reassignWaiting.py +39 -0
- pandaserver/test/recoverLostFiles.py +7 -0
- pandaserver/test/reloadInputDS.py +51 -0
- pandaserver/test/sendCommandToJob.py +26 -0
- pandaserver/test/setDebugMode.py +38 -0
- pandaserver/test/setPriority.py +28 -0
- pandaserver/test/testEvgen.py +60 -0
- pandaserver/test/testEvgen17.py +58 -0
- pandaserver/test/testG4sim.py +81 -0
- pandaserver/test/testG4sim17.py +90 -0
- pandaserver/test/testGlobalShares.py +131 -0
- pandaserver/test/testReco.py +103 -0
- pandaserver/test/testSimulReco14.py +98 -0
- pandaserver/test/testSiteMap.py +26 -0
- pandaserver/test/testUpdateWorkerPilotStatus.py +9 -0
- pandaserver/userinterface/Client.py +1686 -0
- pandaserver/userinterface/UserIF.py +1799 -0
- pandaserver/userinterface/__init__.py +0 -0
- pandaserver/workflow/__init__.py +0 -0
- pandaserver/workflow/data_handler_plugins/base_data_handler.py +48 -0
- pandaserver/workflow/data_handler_plugins/ddm_collection_data_handler.py +100 -0
- pandaserver/workflow/data_handler_plugins/panda_task_data_handler.py +140 -0
- pandaserver/workflow/pcwl_test.py +122 -0
- pandaserver/workflow/pcwl_utils.py +384 -0
- pandaserver/workflow/psnakemake_container.json +68 -0
- pandaserver/workflow/psnakemake_task.json +62 -0
- pandaserver/workflow/psnakemake_test.py +60 -0
- pandaserver/workflow/snakeparser/__init__.py +3 -0
- pandaserver/workflow/snakeparser/extensions.py +74 -0
- pandaserver/workflow/snakeparser/log.py +53 -0
- pandaserver/workflow/snakeparser/names.py +3 -0
- pandaserver/workflow/snakeparser/parser.py +332 -0
- pandaserver/workflow/snakeparser/utils.py +29 -0
- pandaserver/workflow/step_handler_plugins/base_step_handler.py +85 -0
- pandaserver/workflow/step_handler_plugins/panda_task_step_handler.py +280 -0
- pandaserver/workflow/workflow_base.py +531 -0
- pandaserver/workflow/workflow_core.py +1974 -0
- pandaserver/workflow/workflow_core_smoketest.py +388 -0
- pandaserver/workflow/workflow_parser.py +222 -0
- 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")
|