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,874 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Dict
|
|
6
|
+
|
|
7
|
+
from pandacommon.pandalogger.LogWrapper import LogWrapper
|
|
8
|
+
from pandacommon.pandautils.PandaUtils import naive_utcnow
|
|
9
|
+
|
|
10
|
+
from pandaserver.config import panda_config
|
|
11
|
+
from pandaserver.srvcore import CoreUtils
|
|
12
|
+
from pandaserver.taskbuffer.db_proxy_mods.base_module import BaseModule
|
|
13
|
+
from pandaserver.taskbuffer.JobSpec import JobSpec, get_task_queued_time
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Module class to define metrics related methods
|
|
17
|
+
class MetricsModule(BaseModule):
|
|
18
|
+
# constructor
|
|
19
|
+
def __init__(self, log_stream: LogWrapper):
|
|
20
|
+
super().__init__(log_stream)
|
|
21
|
+
|
|
22
|
+
# set job or task metrics
|
|
23
|
+
def set_workload_metrics(self, jedi_task_id: int, panda_id: int | None, metrics: dict, use_commit: bool = True) -> bool:
|
|
24
|
+
"""
|
|
25
|
+
Set job or task metrics
|
|
26
|
+
|
|
27
|
+
:param jedi_task_id: jediTaskID
|
|
28
|
+
:param panda_id: PandaID. None to set task
|
|
29
|
+
:param metrics: metrics data
|
|
30
|
+
:param use_commit: use commit
|
|
31
|
+
:return: True if success
|
|
32
|
+
"""
|
|
33
|
+
comment = " /* DBProxy.set_workload_metrics */"
|
|
34
|
+
if panda_id is not None:
|
|
35
|
+
tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id} PandaID={panda_id}")
|
|
36
|
+
else:
|
|
37
|
+
tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id}")
|
|
38
|
+
tmp_log.debug("start")
|
|
39
|
+
try:
|
|
40
|
+
if panda_id is not None:
|
|
41
|
+
table_name = "Job_Metrics"
|
|
42
|
+
var_map = {":jediTaskID": jedi_task_id, ":PandaID": panda_id}
|
|
43
|
+
else:
|
|
44
|
+
table_name = "Task_Metrics"
|
|
45
|
+
var_map = {":jediTaskID": jedi_task_id}
|
|
46
|
+
# check if data is already there
|
|
47
|
+
sql_check = f"SELECT data FROM {panda_config.schemaPANDA}.{table_name} WHERE jediTaskID=:jediTaskID "
|
|
48
|
+
if panda_id is not None:
|
|
49
|
+
sql_check += "AND PandaID=:PandaID "
|
|
50
|
+
# insert data
|
|
51
|
+
sql_insert = f"INSERT INTO {panda_config.schemaPANDA}.{table_name} "
|
|
52
|
+
if panda_id is not None:
|
|
53
|
+
sql_insert += "(jediTaskID,PandaID,creationTime,modificationTime,data) VALUES(:jediTaskID,:PandaID,CURRENT_DATE,CURRENT_DATE,:data) "
|
|
54
|
+
else:
|
|
55
|
+
sql_insert += "(jediTaskID,creationTime,modificationTime,data) VALUES(:jediTaskID,CURRENT_DATE,CURRENT_DATE,:data) "
|
|
56
|
+
# update data
|
|
57
|
+
sql_update = f"UPDATE {panda_config.schemaPANDA}.{table_name} SET modificationTime=CURRENT_DATE,data=:data WHERE jediTaskID=:jediTaskID "
|
|
58
|
+
if panda_id is not None:
|
|
59
|
+
sql_update += "AND PandaID=:PandaID "
|
|
60
|
+
# start transaction
|
|
61
|
+
if use_commit:
|
|
62
|
+
self.conn.begin()
|
|
63
|
+
# check if data is already there
|
|
64
|
+
self.cur.execute(sql_check + comment, var_map)
|
|
65
|
+
# read data
|
|
66
|
+
tmp_data = None
|
|
67
|
+
for (clob_data,) in self.cur:
|
|
68
|
+
try:
|
|
69
|
+
tmp_data = clob_data.read()
|
|
70
|
+
except AttributeError:
|
|
71
|
+
tmp_data = str(clob_data)
|
|
72
|
+
break
|
|
73
|
+
if not tmp_data:
|
|
74
|
+
# insert new data
|
|
75
|
+
var_map[":data"] = json.dumps(metrics, cls=CoreUtils.NonJsonObjectEncoder)
|
|
76
|
+
self.cur.execute(sql_insert + comment, var_map)
|
|
77
|
+
tmp_log.debug("inserted")
|
|
78
|
+
else:
|
|
79
|
+
# update existing data
|
|
80
|
+
tmp_data = json.loads(tmp_data, object_hook=CoreUtils.as_python_object)
|
|
81
|
+
tmp_data.update(metrics)
|
|
82
|
+
var_map[":data"] = json.dumps(tmp_data, cls=CoreUtils.NonJsonObjectEncoder)
|
|
83
|
+
self.cur.execute(sql_update + comment, var_map)
|
|
84
|
+
tmp_log.debug("updated")
|
|
85
|
+
if use_commit:
|
|
86
|
+
# commit
|
|
87
|
+
if not self._commit():
|
|
88
|
+
raise RuntimeError("Commit error")
|
|
89
|
+
tmp_log.debug("done")
|
|
90
|
+
return True
|
|
91
|
+
except Exception:
|
|
92
|
+
# roll back
|
|
93
|
+
self._rollback()
|
|
94
|
+
# error
|
|
95
|
+
self.dump_error_message(tmp_log)
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
# get job or task metrics
|
|
99
|
+
def get_workload_metrics(self, jedi_task_id: int, panda_id: int = None) -> tuple[bool, dict | None]:
|
|
100
|
+
"""
|
|
101
|
+
Get job metrics or task metrics
|
|
102
|
+
|
|
103
|
+
:param jedi_task_id: jediTaskID
|
|
104
|
+
:param panda_id: PandaID. None to get task metrics
|
|
105
|
+
:return: (False, None) if failed, otherwise (True, metrics)
|
|
106
|
+
"""
|
|
107
|
+
comment = " /* DBProxy.get_workload_metrics */"
|
|
108
|
+
if panda_id is not None:
|
|
109
|
+
tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id} PandaID={panda_id}")
|
|
110
|
+
else:
|
|
111
|
+
tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id}")
|
|
112
|
+
tmp_log.debug("start")
|
|
113
|
+
try:
|
|
114
|
+
if panda_id is not None:
|
|
115
|
+
table_name = "Job_Metrics"
|
|
116
|
+
var_map = {":jediTaskID": jedi_task_id, ":PandaID": panda_id}
|
|
117
|
+
else:
|
|
118
|
+
table_name = "Task_Metrics"
|
|
119
|
+
var_map = {":jediTaskID": jedi_task_id}
|
|
120
|
+
# get data
|
|
121
|
+
sql_get = f"SELECT data FROM {panda_config.schemaPANDA}.{table_name} WHERE jediTaskID=:jediTaskID "
|
|
122
|
+
if panda_id is not None:
|
|
123
|
+
sql_get += "AND PandaID=:PandaID "
|
|
124
|
+
self.cur.execute(sql_get + comment, var_map)
|
|
125
|
+
# read data
|
|
126
|
+
metrics = None
|
|
127
|
+
for (clob_data,) in self.cur:
|
|
128
|
+
try:
|
|
129
|
+
metrics = clob_data.read()
|
|
130
|
+
except AttributeError:
|
|
131
|
+
metrics = str(clob_data)
|
|
132
|
+
break
|
|
133
|
+
if metrics is not None:
|
|
134
|
+
metrics = json.loads(metrics, object_hook=CoreUtils.as_python_object)
|
|
135
|
+
tmp_log.debug(f"got {sys.getsizeof(metrics)} bytes")
|
|
136
|
+
else:
|
|
137
|
+
tmp_log.debug("no data")
|
|
138
|
+
return True, metrics
|
|
139
|
+
except Exception:
|
|
140
|
+
# error
|
|
141
|
+
self.dump_error_message(tmp_log)
|
|
142
|
+
return False, None
|
|
143
|
+
|
|
144
|
+
# get jobs' metrics in a task
|
|
145
|
+
def get_jobs_metrics_in_task(self, jedi_task_id: int) -> tuple[bool, list | None]:
|
|
146
|
+
"""
|
|
147
|
+
Get metrics of jobs in a task
|
|
148
|
+
|
|
149
|
+
:param jedi_task_id: jediTaskID
|
|
150
|
+
:return: (False, None) if failed, otherwise (True, list of [PandaID, metrics])
|
|
151
|
+
"""
|
|
152
|
+
comment = " /* DBProxy.get_jobs_metrics_in_task */"
|
|
153
|
+
tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id}")
|
|
154
|
+
tmp_log.debug("start")
|
|
155
|
+
try:
|
|
156
|
+
var_map = {":jediTaskID": jedi_task_id}
|
|
157
|
+
# get data
|
|
158
|
+
sql_get = f"SELECT PandaID,data FROM {panda_config.schemaPANDA}.Job_Metrics WHERE jediTaskID=:jediTaskID "
|
|
159
|
+
self.cur.execute(sql_get + comment, var_map)
|
|
160
|
+
# read data
|
|
161
|
+
metrics_list = []
|
|
162
|
+
for panda_id, clob_data in self.cur:
|
|
163
|
+
try:
|
|
164
|
+
tmp_data = clob_data.read()
|
|
165
|
+
except AttributeError:
|
|
166
|
+
tmp_data = str(clob_data)
|
|
167
|
+
metrics_list.append([panda_id, json.loads(tmp_data, object_hook=CoreUtils.as_python_object)])
|
|
168
|
+
tmp_log.debug(f"got metrics for {len(metrics_list)} jobs")
|
|
169
|
+
return True, metrics_list
|
|
170
|
+
except Exception:
|
|
171
|
+
# error
|
|
172
|
+
self.dump_error_message(tmp_log)
|
|
173
|
+
return False, None
|
|
174
|
+
|
|
175
|
+
# update task queued and activated times
|
|
176
|
+
def update_task_queued_activated_times(self, jedi_task_id: int) -> None:
|
|
177
|
+
"""
|
|
178
|
+
Update task queued time depending on the number of inputs to be processed. Update queued and activated times if they were None and inputs become available.
|
|
179
|
+
Set None if no input is available and record the queuing duration to task metrics.
|
|
180
|
+
|
|
181
|
+
:param jedi_task_id: task's jediTaskID
|
|
182
|
+
"""
|
|
183
|
+
comment = " /* DBProxy.update_task_queued_activated_times */"
|
|
184
|
+
tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id}")
|
|
185
|
+
tmp_log.debug("start")
|
|
186
|
+
# check if the task is queued
|
|
187
|
+
sql_check = (
|
|
188
|
+
f"SELECT status,oldStatus,queuedTime,activatedTime,currentPriority,gshare FROM {panda_config.schemaJEDI}.JEDI_Tasks WHERE jediTaskID=:jediTaskID "
|
|
189
|
+
)
|
|
190
|
+
var_map = {":jediTaskID": jedi_task_id}
|
|
191
|
+
self.cur.execute(sql_check + comment, var_map)
|
|
192
|
+
res = self.cur.fetchone()
|
|
193
|
+
if not res:
|
|
194
|
+
# not found
|
|
195
|
+
tmp_log.debug("task not found")
|
|
196
|
+
return
|
|
197
|
+
(task_status, task_old_status, queued_time, activated_time, current_priority, global_share) = res
|
|
198
|
+
has_input = False
|
|
199
|
+
active_status_list = ("ready", "running", "scouting", "scouted")
|
|
200
|
+
if task_status in active_status_list or (task_old_status in active_status_list and task_status == "pending"):
|
|
201
|
+
# check if the task has unprocessed inputs
|
|
202
|
+
sql_input = (
|
|
203
|
+
f"SELECT 1 FROM {panda_config.schemaJEDI}.JEDI_Datasets WHERE jediTaskID=:jediTaskID AND type IN (:type1,:type2) AND masterID IS NULL "
|
|
204
|
+
"AND nFilesToBeUsed>0 AND nFilesToBeUSed>nFilesUsed "
|
|
205
|
+
)
|
|
206
|
+
var_map = {":jediTaskID": jedi_task_id, ":type1": "input", ":type2": "pseudo_input"}
|
|
207
|
+
self.cur.execute(sql_input + comment, var_map)
|
|
208
|
+
res = self.cur.fetchone()
|
|
209
|
+
has_input = res is not None
|
|
210
|
+
# set queued time if it was None and inputs are available
|
|
211
|
+
if has_input:
|
|
212
|
+
if queued_time is None:
|
|
213
|
+
# update queued time
|
|
214
|
+
tmp_log.debug(f"set queued time when task is {task_status}")
|
|
215
|
+
sql_update = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET queuedTime=CURRENT_DATE WHERE jediTaskID=:jediTaskID AND queuedTime IS NULL "
|
|
216
|
+
var_map = {":jediTaskID": jedi_task_id}
|
|
217
|
+
self.cur.execute(sql_update + comment, var_map)
|
|
218
|
+
n_row = self.cur.rowcount
|
|
219
|
+
if n_row > 0 and activated_time is None:
|
|
220
|
+
# set activated time
|
|
221
|
+
tmp_log.debug(f"set activated time when task is {task_status}")
|
|
222
|
+
sql_update = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET activatedTime=CURRENT_DATE WHERE jediTaskID=:jediTaskID AND activatedTime IS NULL "
|
|
223
|
+
var_map = {":jediTaskID": jedi_task_id}
|
|
224
|
+
self.cur.execute(sql_update + comment, var_map)
|
|
225
|
+
else:
|
|
226
|
+
tmp_log.debug(f"keep current queued time {queued_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
227
|
+
# record queuing duration since the task has no more input to process
|
|
228
|
+
if queued_time is not None and not has_input:
|
|
229
|
+
# unset queued time
|
|
230
|
+
tmp_log.debug(f"unset queued time and record duration when task is {task_status}")
|
|
231
|
+
sql_update = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET queuedTime=NULL WHERE jediTaskID=:jediTaskID AND queuedTime IS NOT NULL "
|
|
232
|
+
var_map = {":jediTaskID": jedi_task_id}
|
|
233
|
+
self.cur.execute(sql_update + comment, var_map)
|
|
234
|
+
# get task metrics dict
|
|
235
|
+
tmp_success, task_metrics = self.get_workload_metrics(jedi_task_id, None)
|
|
236
|
+
if not tmp_success:
|
|
237
|
+
err_str = f"failed to get task metrics for jediTaskId={jedi_task_id}"
|
|
238
|
+
tmp_log.error(err_str)
|
|
239
|
+
return
|
|
240
|
+
# new
|
|
241
|
+
if not task_metrics:
|
|
242
|
+
task_metrics = {}
|
|
243
|
+
# add duration
|
|
244
|
+
task_metrics.setdefault("queuingPeriods", [])
|
|
245
|
+
if len(task_metrics["queuingPeriods"]) < 10000:
|
|
246
|
+
task_metrics["queuingPeriods"].append({"start": queued_time, "end": naive_utcnow(), "status": task_status})
|
|
247
|
+
tmp_success = self.set_workload_metrics(jedi_task_id, None, task_metrics, False)
|
|
248
|
+
if not tmp_success:
|
|
249
|
+
err_str = f"failed to update task metrics for jediTaskId={jedi_task_id}"
|
|
250
|
+
tmp_log.error(err_str)
|
|
251
|
+
return
|
|
252
|
+
else:
|
|
253
|
+
tmp_log.debug("skipped since queuing period list is too long")
|
|
254
|
+
tmp_log.debug(f"done in {task_status}")
|
|
255
|
+
|
|
256
|
+
# record job queuing period
|
|
257
|
+
def record_job_queuing_period(self, panda_id: int, job_spec: JobSpec = None) -> bool | None:
|
|
258
|
+
"""
|
|
259
|
+
Record queuing period in job metrics. Skip if job.jobMetrics doesn't contain task queued time
|
|
260
|
+
|
|
261
|
+
:param panda_id: Job's PandaID
|
|
262
|
+
:param job_spec: job spec. None to get it from the database
|
|
263
|
+
:return: True if success. False if failed. None if skipped
|
|
264
|
+
"""
|
|
265
|
+
comment = " /* DBProxy.record_job_queuing_period */"
|
|
266
|
+
tmp_log = self.create_tagged_logger(comment, f"PandaID={panda_id}")
|
|
267
|
+
tmp_log.debug(f"start with job spec: {job_spec is None}")
|
|
268
|
+
# get task queued time
|
|
269
|
+
if job_spec is None:
|
|
270
|
+
sql_check = f"SELECT jediTaskID,jobStatus,specialHandling FROM {panda_config.schemaPANDA}.jobsActive4 WHERE PandaID=:PandaID "
|
|
271
|
+
var_map = {":PandaID": panda_id}
|
|
272
|
+
self.cur.execute(sql_check + comment, var_map)
|
|
273
|
+
res = self.cur.fetchone()
|
|
274
|
+
if not res:
|
|
275
|
+
# not found
|
|
276
|
+
tmp_log.debug("job not found")
|
|
277
|
+
return None
|
|
278
|
+
jedi_task_id, job_status, tmp_str = res
|
|
279
|
+
else:
|
|
280
|
+
jedi_task_id = job_spec.jediTaskID
|
|
281
|
+
job_status = job_spec.jobStatus
|
|
282
|
+
tmp_str = job_spec.specialHandling
|
|
283
|
+
task_queued_time = get_task_queued_time(tmp_str)
|
|
284
|
+
# record queuing duration
|
|
285
|
+
if jedi_task_id and task_queued_time:
|
|
286
|
+
tmp_log.debug(f"to record queuing period")
|
|
287
|
+
# get job metrics dict
|
|
288
|
+
tmp_success, job_metrics = self.get_workload_metrics(jedi_task_id, panda_id)
|
|
289
|
+
if not tmp_success:
|
|
290
|
+
err_str = "Failed to get job metrics "
|
|
291
|
+
tmp_log.error(err_str)
|
|
292
|
+
return False
|
|
293
|
+
# new
|
|
294
|
+
if not job_metrics:
|
|
295
|
+
job_metrics = {}
|
|
296
|
+
# add duration
|
|
297
|
+
if "queuingPeriod" in job_metrics:
|
|
298
|
+
tmp_log.debug("skipped since queuing period already exists")
|
|
299
|
+
return None
|
|
300
|
+
else:
|
|
301
|
+
job_metrics["queuingPeriod"] = {
|
|
302
|
+
"start": task_queued_time,
|
|
303
|
+
"end": naive_utcnow(),
|
|
304
|
+
"status": job_status,
|
|
305
|
+
}
|
|
306
|
+
tmp_success = self.set_workload_metrics(jedi_task_id, panda_id, job_metrics, False)
|
|
307
|
+
if not tmp_success:
|
|
308
|
+
err_str = "Failed to update job metrics"
|
|
309
|
+
tmp_log.error(err_str)
|
|
310
|
+
return False
|
|
311
|
+
tmp_log.debug(f"done in {job_status}")
|
|
312
|
+
return True
|
|
313
|
+
else:
|
|
314
|
+
tmp_log.debug(f"skipped as jediTaskID={jedi_task_id} taskQueuedTime={task_queued_time}")
|
|
315
|
+
return None
|
|
316
|
+
|
|
317
|
+
# unset task active period
|
|
318
|
+
def unset_task_activated_time(self, jedi_task_id: int, task_status: str = None) -> bool | None:
|
|
319
|
+
"""
|
|
320
|
+
Unset activated time and record active period in task metrics
|
|
321
|
+
|
|
322
|
+
:param jedi_task_id: task's JediTaskID
|
|
323
|
+
:param task_status: task status. None to get it from the database
|
|
324
|
+
:return: True if success. False if failed. None if skipped
|
|
325
|
+
"""
|
|
326
|
+
frozen_status_list = ("done", "finished", "failed", "broken", "paused", "exhausted")
|
|
327
|
+
if task_status is not None and task_status not in frozen_status_list:
|
|
328
|
+
return None
|
|
329
|
+
comment = " /* DBProxy.record_task_active_period */"
|
|
330
|
+
tmp_log = self.create_tagged_logger(comment, f"JediTaskID={jedi_task_id}")
|
|
331
|
+
tmp_log.debug(f"start")
|
|
332
|
+
# get activated time
|
|
333
|
+
sql_check = f"SELECT status,activatedTime FROM {panda_config.schemaJEDI}.JEDI_Tasks WHERE jediTaskID=:jediTaskID "
|
|
334
|
+
var_map = {":jediTaskID": jedi_task_id}
|
|
335
|
+
self.cur.execute(sql_check + comment, var_map)
|
|
336
|
+
res = self.cur.fetchone()
|
|
337
|
+
if not res:
|
|
338
|
+
# not found
|
|
339
|
+
tmp_log.debug("task not found")
|
|
340
|
+
return None
|
|
341
|
+
task_status, activated_time = res
|
|
342
|
+
# record active duration
|
|
343
|
+
if task_status in frozen_status_list and activated_time:
|
|
344
|
+
tmp_log.debug(f"to record active period when task is {task_status}")
|
|
345
|
+
# get task metrics dict
|
|
346
|
+
tmp_success, task_metrics = self.get_workload_metrics(jedi_task_id, None)
|
|
347
|
+
if not tmp_success:
|
|
348
|
+
err_str = "Failed to get task metrics "
|
|
349
|
+
tmp_log.error(err_str)
|
|
350
|
+
return False
|
|
351
|
+
# new
|
|
352
|
+
if not task_metrics:
|
|
353
|
+
task_metrics = {}
|
|
354
|
+
# add duration
|
|
355
|
+
task_metrics.setdefault("activePeriod", [])
|
|
356
|
+
task_metrics["activePeriod"].append(
|
|
357
|
+
{
|
|
358
|
+
"start": activated_time,
|
|
359
|
+
"end": naive_utcnow(),
|
|
360
|
+
"status": task_status,
|
|
361
|
+
}
|
|
362
|
+
)
|
|
363
|
+
tmp_success = self.set_workload_metrics(jedi_task_id, None, task_metrics, False)
|
|
364
|
+
if not tmp_success:
|
|
365
|
+
err_str = "Failed to update task metrics"
|
|
366
|
+
tmp_log.error(err_str)
|
|
367
|
+
return False
|
|
368
|
+
# unset activated time
|
|
369
|
+
tmp_log.debug(f"unset activated time")
|
|
370
|
+
sql_update = f"UPDATE {panda_config.schemaJEDI}.JEDI_Tasks SET activatedTime=NULL WHERE jediTaskID=:jediTaskID AND activatedTime IS NOT NULL "
|
|
371
|
+
var_map = {":jediTaskID": jedi_task_id}
|
|
372
|
+
self.cur.execute(sql_update + comment, var_map)
|
|
373
|
+
tmp_log.debug(f"done in {task_status}")
|
|
374
|
+
return True
|
|
375
|
+
|
|
376
|
+
# check failure count due to corrupted files
|
|
377
|
+
def checkFailureCountWithCorruptedFiles(self, jediTaskID, pandaID):
|
|
378
|
+
comment = " /* DBProxy.checkFailureCountWithCorruptedFiles */"
|
|
379
|
+
tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jediTaskID} pandaID={pandaID}")
|
|
380
|
+
# sql to failure counts
|
|
381
|
+
sqlBD = "SELECT f2.lfn,COUNT(*) FROM ATLAS_PANDA.filesTable4 f1, ATLAS_PANDA.filesTable4 f2 "
|
|
382
|
+
sqlBD += "WHERE f1.PandaID=:PandaID AND f1.type=:type AND f1.status=:status "
|
|
383
|
+
sqlBD += "AND f2.lfn=f1.lfn AND f2.type=:type AND f2.status=:status AND f2.jediTaskID=:jediTaskID "
|
|
384
|
+
sqlBD += "GROUP BY f2.lfn "
|
|
385
|
+
varMap = {}
|
|
386
|
+
varMap[":jediTaskID"] = jediTaskID
|
|
387
|
+
varMap[":PandaID"] = pandaID
|
|
388
|
+
varMap[":status"] = "corrupted"
|
|
389
|
+
varMap[":type"] = "zipinput"
|
|
390
|
+
self.cur.execute(sqlBD + comment, varMap)
|
|
391
|
+
resBD = self.cur.fetchall()
|
|
392
|
+
tooMany = False
|
|
393
|
+
for lfn, nFailed in resBD:
|
|
394
|
+
tmp_log.debug(f"{nFailed} failures with {lfn}")
|
|
395
|
+
if nFailed >= 3:
|
|
396
|
+
tooMany = True
|
|
397
|
+
tmp_log.debug(f"too many failures : {tooMany}")
|
|
398
|
+
return tooMany
|
|
399
|
+
|
|
400
|
+
# calculate failure metrics, such as single failure rate and failed HEPScore, for a task
|
|
401
|
+
def get_task_failure_metrics(self, task_id, use_commit=True):
|
|
402
|
+
comment = " /* JediDBProxy.get_task_failure_metrics */"
|
|
403
|
+
tmp_log = self.create_tagged_logger(comment, f"jediTaskID={task_id}")
|
|
404
|
+
tmp_log.debug("start")
|
|
405
|
+
try:
|
|
406
|
+
# start transaction
|
|
407
|
+
if use_commit:
|
|
408
|
+
self.conn.begin()
|
|
409
|
+
# sql to get metrics
|
|
410
|
+
sql = (
|
|
411
|
+
f"SELECT SUM(is_finished),SUM(is_failed),SUM(HS06SEC*is_finished),SUM(HS06SEC*is_failed) "
|
|
412
|
+
f"FROM ("
|
|
413
|
+
f"SELECT PandaID, HS06SEC, CASE WHEN jobStatus='finished' THEN 1 ELSE 0 END is_finished, "
|
|
414
|
+
f"CASE WHEN jobStatus='failed' THEN 1 ELSE 0 END is_failed "
|
|
415
|
+
f"FROM {panda_config.schemaPANDA}.jobsArchived4 "
|
|
416
|
+
f"WHERE jediTaskID=:jediTaskID AND prodSourceLabel IN (:prodSourceLabel1,:prodSourceLabel2) "
|
|
417
|
+
f"UNION "
|
|
418
|
+
f"SELECT PandaID, HS06SEC, CASE WHEN jobStatus='finished' THEN 1 ELSE 0 END is_finished, "
|
|
419
|
+
f"CASE WHEN jobStatus='failed' THEN 1 ELSE 0 END is_failed "
|
|
420
|
+
f"FROM {panda_config.schemaPANDAARCH}.jobsArchived "
|
|
421
|
+
f"WHERE jediTaskID=:jediTaskID AND prodSourceLabel IN (:prodSourceLabel1,:prodSourceLabel2) "
|
|
422
|
+
f")"
|
|
423
|
+
)
|
|
424
|
+
var_map = {
|
|
425
|
+
":jediTaskID": task_id,
|
|
426
|
+
":prodSourceLabel1": "managed",
|
|
427
|
+
":prodSourceLabel2": "user",
|
|
428
|
+
}
|
|
429
|
+
self.cur.execute(sql + comment, var_map)
|
|
430
|
+
num_finished, num_failed, good_hep_score_sec, bad_hep_score_sec = self.cur.fetchone()
|
|
431
|
+
ret_dict = {
|
|
432
|
+
"num_failed": num_failed,
|
|
433
|
+
"single_failure_rate": (
|
|
434
|
+
round(num_failed / (num_finished + num_failed), 3)
|
|
435
|
+
if num_finished is not None and num_failed is not None and num_finished + num_failed
|
|
436
|
+
else None
|
|
437
|
+
),
|
|
438
|
+
"failed_hep_score_hour": int(bad_hep_score_sec / 60 / 60) if bad_hep_score_sec is not None else None,
|
|
439
|
+
"failed_hep_score_ratio": (
|
|
440
|
+
round(bad_hep_score_sec / (good_hep_score_sec + bad_hep_score_sec), 3)
|
|
441
|
+
if good_hep_score_sec is not None and bad_hep_score_sec is not None and good_hep_score_sec + bad_hep_score_sec
|
|
442
|
+
else None
|
|
443
|
+
),
|
|
444
|
+
}
|
|
445
|
+
# commit
|
|
446
|
+
if use_commit:
|
|
447
|
+
if not self._commit():
|
|
448
|
+
raise RuntimeError("Commit error")
|
|
449
|
+
# return
|
|
450
|
+
tmp_log.debug(f"got {ret_dict}")
|
|
451
|
+
return ret_dict
|
|
452
|
+
except Exception:
|
|
453
|
+
# roll back
|
|
454
|
+
if use_commit:
|
|
455
|
+
self._rollback()
|
|
456
|
+
# error
|
|
457
|
+
self.dump_error_message(tmp_log)
|
|
458
|
+
return None
|
|
459
|
+
|
|
460
|
+
# get averaged disk IO
|
|
461
|
+
def getAvgDiskIO_JEDI(self):
|
|
462
|
+
comment = " /* JediDBProxy.getAvgDiskIO_JEDI */"
|
|
463
|
+
tmp_log = self.create_tagged_logger(comment)
|
|
464
|
+
tmp_log.debug("start")
|
|
465
|
+
try:
|
|
466
|
+
# sql
|
|
467
|
+
sql = f"SELECT sum(prorated_diskio_avg * njobs) / sum(njobs), computingSite FROM {panda_config.schemaPANDA}.JOBS_SHARE_STATS "
|
|
468
|
+
sql += "WHERE jobStatus=:jobStatus GROUP BY computingSite "
|
|
469
|
+
var_map = dict()
|
|
470
|
+
var_map[":jobStatus"] = "running"
|
|
471
|
+
# begin transaction
|
|
472
|
+
self.conn.begin()
|
|
473
|
+
self.cur.execute(sql + comment, var_map)
|
|
474
|
+
resFL = self.cur.fetchall()
|
|
475
|
+
# commit
|
|
476
|
+
if not self._commit():
|
|
477
|
+
raise RuntimeError("Commit error")
|
|
478
|
+
ret_map = dict()
|
|
479
|
+
for avg, computing_site in resFL:
|
|
480
|
+
if avg:
|
|
481
|
+
avg = float(avg)
|
|
482
|
+
ret_map[computing_site] = avg
|
|
483
|
+
tmp_log.debug("done")
|
|
484
|
+
return ret_map
|
|
485
|
+
except Exception:
|
|
486
|
+
# roll back
|
|
487
|
+
self._rollback()
|
|
488
|
+
# error
|
|
489
|
+
self.dump_error_message(tmp_log)
|
|
490
|
+
return {}
|
|
491
|
+
|
|
492
|
+
# get carbon footprint for a task, the level has to be 'regional' or 'global'. If misspelled, it defaults to 'global'
|
|
493
|
+
def get_task_carbon_footprint(self, jedi_task_id, level):
|
|
494
|
+
comment = " /* JediDBProxy.get_task_carbon_footprint */"
|
|
495
|
+
tmp_log = self.create_tagged_logger(comment, f"jediTaskID={jedi_task_id} n_files={level}")
|
|
496
|
+
tmp_log.debug("start")
|
|
497
|
+
|
|
498
|
+
if level == "regional":
|
|
499
|
+
gco2_column = "GCO2_REGIONAL"
|
|
500
|
+
else:
|
|
501
|
+
gco2_column = "GCO2_GLOBAL"
|
|
502
|
+
|
|
503
|
+
try:
|
|
504
|
+
sql = (
|
|
505
|
+
"SELECT jobstatus, SUM(sum_gco2) FROM ( "
|
|
506
|
+
" SELECT jobstatus, SUM({gco2_column}) sum_gco2 FROM {active_schema}.jobsarchived4 "
|
|
507
|
+
" WHERE jeditaskid =:jeditaskid "
|
|
508
|
+
" GROUP BY jobstatus "
|
|
509
|
+
" UNION "
|
|
510
|
+
" SELECT jobstatus, SUM({gco2_column}) sum_gco2 FROM {archive_schema}.jobsarchived "
|
|
511
|
+
" WHERE jeditaskid =:jeditaskid "
|
|
512
|
+
" GROUP BY jobstatus)"
|
|
513
|
+
"GROUP BY jobstatus".format(gco2_column=gco2_column, active_schema=panda_config.schemaJEDI, archive_schema=panda_config.schemaPANDAARCH)
|
|
514
|
+
)
|
|
515
|
+
var_map = {":jeditaskid": jedi_task_id}
|
|
516
|
+
|
|
517
|
+
# start transaction
|
|
518
|
+
self.conn.begin()
|
|
519
|
+
self.cur.execute(sql + comment, var_map)
|
|
520
|
+
results = self.cur.fetchall()
|
|
521
|
+
|
|
522
|
+
footprint = {"total": 0}
|
|
523
|
+
data = False
|
|
524
|
+
for job_status, g_co2 in results:
|
|
525
|
+
if not g_co2:
|
|
526
|
+
g_co2 = 0
|
|
527
|
+
else:
|
|
528
|
+
data = True
|
|
529
|
+
footprint[job_status] = g_co2
|
|
530
|
+
footprint["total"] += g_co2
|
|
531
|
+
|
|
532
|
+
# commit
|
|
533
|
+
if not self._commit():
|
|
534
|
+
raise RuntimeError("Commit error")
|
|
535
|
+
|
|
536
|
+
tmp_log.debug(f"done: {footprint}")
|
|
537
|
+
|
|
538
|
+
if not data:
|
|
539
|
+
return None
|
|
540
|
+
|
|
541
|
+
return footprint
|
|
542
|
+
except Exception:
|
|
543
|
+
# roll back
|
|
544
|
+
self._rollback()
|
|
545
|
+
# error
|
|
546
|
+
self.dump_error_message(tmp_log)
|
|
547
|
+
return None
|
|
548
|
+
|
|
549
|
+
# get job statistics with work queue
|
|
550
|
+
def getJobStatisticsWithWorkQueue_JEDI(self, vo, prodSourceLabel, minPriority=None, cloud=None):
|
|
551
|
+
comment = " /* DBProxy.getJobStatisticsWithWorkQueue_JEDI */"
|
|
552
|
+
tmpLog = self.create_tagged_logger(comment, f"vo={vo} label={prodSourceLabel} cloud={cloud}")
|
|
553
|
+
tmpLog.debug(f"start minPriority={minPriority}")
|
|
554
|
+
sql0 = "SELECT computingSite,cloud,jobStatus,workQueue_ID,COUNT(*) FROM %s "
|
|
555
|
+
sql0 += "WHERE vo=:vo AND prodSourceLabel=:prodSourceLabel "
|
|
556
|
+
if cloud is not None:
|
|
557
|
+
sql0 += "AND cloud=:cloud "
|
|
558
|
+
tmpPrioMap = {}
|
|
559
|
+
if minPriority is not None:
|
|
560
|
+
sql0 += "AND currentPriority>=:minPriority "
|
|
561
|
+
tmpPrioMap[":minPriority"] = minPriority
|
|
562
|
+
sql0 += "GROUP BY computingSite,cloud,prodSourceLabel,jobStatus,workQueue_ID "
|
|
563
|
+
sqlMV = sql0
|
|
564
|
+
sqlMV = re.sub("COUNT\(\*\)", "SUM(num_of_jobs)", sqlMV)
|
|
565
|
+
sqlMV = re.sub("SELECT ", "SELECT /*+ RESULT_CACHE */ ", sqlMV)
|
|
566
|
+
tables = [f"{panda_config.schemaPANDA}.jobsActive4", f"{panda_config.schemaPANDA}.jobsDefined4"]
|
|
567
|
+
if minPriority is not None:
|
|
568
|
+
# read the number of running jobs with prio<=MIN
|
|
569
|
+
tables.append(f"{panda_config.schemaPANDA}.jobsActive4")
|
|
570
|
+
sqlMVforRun = re.sub("currentPriority>=", "currentPriority<=", sqlMV)
|
|
571
|
+
varMap = {}
|
|
572
|
+
varMap[":vo"] = vo
|
|
573
|
+
varMap[":prodSourceLabel"] = prodSourceLabel
|
|
574
|
+
if cloud is not None:
|
|
575
|
+
varMap[":cloud"] = cloud
|
|
576
|
+
for tmpPrio in tmpPrioMap.keys():
|
|
577
|
+
varMap[tmpPrio] = tmpPrioMap[tmpPrio]
|
|
578
|
+
returnMap = {}
|
|
579
|
+
try:
|
|
580
|
+
iActive = 0
|
|
581
|
+
for table in tables:
|
|
582
|
+
# start transaction
|
|
583
|
+
self.conn.begin()
|
|
584
|
+
# select
|
|
585
|
+
self.cur.arraysize = 10000
|
|
586
|
+
useRunning = None
|
|
587
|
+
if table == f"{panda_config.schemaPANDA}.jobsActive4":
|
|
588
|
+
mvTableName = f"{panda_config.schemaPANDA}.MV_JOBSACTIVE4_STATS"
|
|
589
|
+
# first count non-running and then running if minPriority is specified
|
|
590
|
+
if minPriority is not None:
|
|
591
|
+
if iActive == 0:
|
|
592
|
+
useRunning = False
|
|
593
|
+
else:
|
|
594
|
+
useRunning = True
|
|
595
|
+
iActive += 1
|
|
596
|
+
if useRunning in [None, False]:
|
|
597
|
+
sqlExeTmp = (sqlMV + comment) % mvTableName
|
|
598
|
+
else:
|
|
599
|
+
sqlExeTmp = (sqlMVforRun + comment) % mvTableName
|
|
600
|
+
else:
|
|
601
|
+
sqlExeTmp = (sql0 + comment) % table
|
|
602
|
+
self.cur.execute(sqlExeTmp, varMap)
|
|
603
|
+
res = self.cur.fetchall()
|
|
604
|
+
# commit
|
|
605
|
+
if not self._commit():
|
|
606
|
+
raise RuntimeError("Commit error")
|
|
607
|
+
# create map
|
|
608
|
+
for computingSite, cloud, jobStatus, workQueue_ID, nCount in res:
|
|
609
|
+
# count the number of non-running with prio>=MIN
|
|
610
|
+
if useRunning is True and jobStatus != "running":
|
|
611
|
+
continue
|
|
612
|
+
# count the number of running with prio<=MIN
|
|
613
|
+
if useRunning is False and jobStatus == "running":
|
|
614
|
+
continue
|
|
615
|
+
# add site
|
|
616
|
+
if computingSite not in returnMap:
|
|
617
|
+
returnMap[computingSite] = {}
|
|
618
|
+
# add workQueue
|
|
619
|
+
if workQueue_ID not in returnMap[computingSite]:
|
|
620
|
+
returnMap[computingSite][workQueue_ID] = {}
|
|
621
|
+
# add jobstatus
|
|
622
|
+
if jobStatus not in returnMap[computingSite][workQueue_ID]:
|
|
623
|
+
returnMap[computingSite][workQueue_ID][jobStatus] = 0
|
|
624
|
+
# add
|
|
625
|
+
returnMap[computingSite][workQueue_ID][jobStatus] += nCount
|
|
626
|
+
# return
|
|
627
|
+
tmpLog.debug("done")
|
|
628
|
+
return True, returnMap
|
|
629
|
+
except Exception:
|
|
630
|
+
# roll back
|
|
631
|
+
self._rollback()
|
|
632
|
+
# error
|
|
633
|
+
self.dump_error_message(tmpLog)
|
|
634
|
+
return False, {}
|
|
635
|
+
|
|
636
|
+
# get core statistics with VO and prodSourceLabel
|
|
637
|
+
def get_core_statistics(self, vo: str, prod_source_label: str) -> [bool, dict]:
|
|
638
|
+
comment = " /* DBProxy.get_core_statistics */"
|
|
639
|
+
tmpLog = self.create_tagged_logger(comment, f"vo={vo} label={prod_source_label}")
|
|
640
|
+
tmpLog.debug("start")
|
|
641
|
+
sql0 = f"SELECT /*+ RESULT_CACHE */ computingSite,jobStatus,SUM(num_of_cores) FROM {panda_config.schemaPANDA}.MV_JOBSACTIVE4_STATS "
|
|
642
|
+
sql0 += "WHERE vo=:vo AND prodSourceLabel=:prodSourceLabel "
|
|
643
|
+
sql0 += "GROUP BY computingSite,cloud,prodSourceLabel,jobStatus "
|
|
644
|
+
var_map = {":vo": vo, ":prodSourceLabel": prod_source_label}
|
|
645
|
+
return_map = {}
|
|
646
|
+
try:
|
|
647
|
+
self.conn.begin()
|
|
648
|
+
# select
|
|
649
|
+
self.cur.arraysize = 10000
|
|
650
|
+
self.cur.execute(sql0 + comment, var_map)
|
|
651
|
+
res = self.cur.fetchall()
|
|
652
|
+
# commit
|
|
653
|
+
if not self._commit():
|
|
654
|
+
raise RuntimeError("Commit error")
|
|
655
|
+
# create map
|
|
656
|
+
for computing_site, job_status, n_core in res:
|
|
657
|
+
# add site
|
|
658
|
+
return_map.setdefault(computing_site, {})
|
|
659
|
+
# add status
|
|
660
|
+
return_map[computing_site].setdefault(job_status, 0)
|
|
661
|
+
# add num cores
|
|
662
|
+
return_map[computing_site][job_status] += n_core
|
|
663
|
+
# return
|
|
664
|
+
tmpLog.debug("done")
|
|
665
|
+
return True, return_map
|
|
666
|
+
except Exception:
|
|
667
|
+
# roll back
|
|
668
|
+
self._rollback()
|
|
669
|
+
# error
|
|
670
|
+
self.dump_error_message(tmpLog)
|
|
671
|
+
return False, {}
|
|
672
|
+
|
|
673
|
+
# get job statistics by global share
|
|
674
|
+
def getJobStatisticsByGlobalShare(self, vo, exclude_rwq):
|
|
675
|
+
"""
|
|
676
|
+
:param vo: Virtual Organization
|
|
677
|
+
:param exclude_rwq: True/False. Indicates whether we want to indicate special workqueues from the statistics
|
|
678
|
+
"""
|
|
679
|
+
comment = " /* DBProxy.getJobStatisticsByGlobalShare */"
|
|
680
|
+
tmpLog = self.create_tagged_logger(comment, f" vo={vo}")
|
|
681
|
+
tmpLog.debug("start")
|
|
682
|
+
|
|
683
|
+
# define the var map of query parameters
|
|
684
|
+
var_map = {":vo": vo}
|
|
685
|
+
|
|
686
|
+
# sql to query on pre-cached job statistics tables (JOBS_SHARE_STATS and JOBSDEFINED_SHARE_STATS)
|
|
687
|
+
sql_jt = """
|
|
688
|
+
SELECT /*+ RESULT_CACHE */ computingSite, jobStatus, gShare, SUM(njobs) FROM %s
|
|
689
|
+
WHERE vo=:vo
|
|
690
|
+
"""
|
|
691
|
+
|
|
692
|
+
if exclude_rwq:
|
|
693
|
+
sql_jt += f"""
|
|
694
|
+
AND workqueue_id NOT IN
|
|
695
|
+
(SELECT queue_id FROM {panda_config.schemaPANDA}.jedi_work_queue WHERE queue_function = 'Resource')
|
|
696
|
+
"""
|
|
697
|
+
|
|
698
|
+
sql_jt += """
|
|
699
|
+
GROUP BY computingSite, jobStatus, gshare
|
|
700
|
+
"""
|
|
701
|
+
|
|
702
|
+
tables = [f"{panda_config.schemaPANDA}.JOBS_SHARE_STATS", f"{panda_config.schemaPANDA}.JOBSDEFINED_SHARE_STATS"]
|
|
703
|
+
|
|
704
|
+
return_map = {}
|
|
705
|
+
try:
|
|
706
|
+
for table in tables:
|
|
707
|
+
self.cur.arraysize = 10000
|
|
708
|
+
sql_exe = (sql_jt + comment) % table
|
|
709
|
+
self.cur.execute(sql_exe, var_map)
|
|
710
|
+
res = self.cur.fetchall()
|
|
711
|
+
|
|
712
|
+
# create map
|
|
713
|
+
for panda_site, status, gshare, n_count in res:
|
|
714
|
+
# add site
|
|
715
|
+
return_map.setdefault(panda_site, {})
|
|
716
|
+
# add global share
|
|
717
|
+
return_map[panda_site].setdefault(gshare, {})
|
|
718
|
+
# add job status
|
|
719
|
+
return_map[panda_site][gshare].setdefault(status, 0)
|
|
720
|
+
# increase count
|
|
721
|
+
return_map[panda_site][gshare][status] += n_count
|
|
722
|
+
|
|
723
|
+
tmpLog.debug("done")
|
|
724
|
+
return True, return_map
|
|
725
|
+
except Exception:
|
|
726
|
+
self.dump_error_message(tmpLog)
|
|
727
|
+
return False, {}
|
|
728
|
+
|
|
729
|
+
def getJobStatisticsByResourceType(self, workqueue):
|
|
730
|
+
"""
|
|
731
|
+
This function will return the job statistics for a particular workqueue, broken down by resource type
|
|
732
|
+
(SCORE, MCORE, etc.)
|
|
733
|
+
:param workqueue: workqueue object
|
|
734
|
+
"""
|
|
735
|
+
comment = " /* DBProxy.getJobStatisticsByResourceType */"
|
|
736
|
+
tmpLog = self.create_tagged_logger(comment, f"workqueue={workqueue}")
|
|
737
|
+
tmpLog.debug("start")
|
|
738
|
+
|
|
739
|
+
# define the var map of query parameters
|
|
740
|
+
var_map = {":vo": workqueue.VO}
|
|
741
|
+
|
|
742
|
+
# sql to query on pre-cached job statistics tables (JOBS_SHARE_STATS and JOBSDEFINED_SHARE_STATS)
|
|
743
|
+
sql_jt = "SELECT /*+ RESULT_CACHE */ jobstatus, resource_type, SUM(njobs) FROM %s WHERE vo=:vo "
|
|
744
|
+
|
|
745
|
+
if workqueue.is_global_share:
|
|
746
|
+
sql_jt += "AND gshare=:gshare "
|
|
747
|
+
sql_jt += f"AND workqueue_id NOT IN (SELECT queue_id FROM {panda_config.schemaPANDA}.jedi_work_queue WHERE queue_function = 'Resource') "
|
|
748
|
+
var_map[":gshare"] = workqueue.queue_name
|
|
749
|
+
else:
|
|
750
|
+
sql_jt += "AND workqueue_id=:workqueue_id "
|
|
751
|
+
var_map[":workqueue_id"] = workqueue.queue_id
|
|
752
|
+
|
|
753
|
+
sql_jt += "GROUP BY jobstatus, resource_type "
|
|
754
|
+
|
|
755
|
+
tables = [f"{panda_config.schemaPANDA}.JOBS_SHARE_STATS", f"{panda_config.schemaPANDA}.JOBSDEFINED_SHARE_STATS"]
|
|
756
|
+
|
|
757
|
+
return_map = {}
|
|
758
|
+
try:
|
|
759
|
+
for table in tables:
|
|
760
|
+
self.cur.arraysize = 10000
|
|
761
|
+
sql_exe = (sql_jt + comment) % table
|
|
762
|
+
self.cur.execute(sql_exe, var_map)
|
|
763
|
+
res = self.cur.fetchall()
|
|
764
|
+
|
|
765
|
+
# create map
|
|
766
|
+
for status, resource_type, n_count in res:
|
|
767
|
+
return_map.setdefault(status, {})
|
|
768
|
+
return_map[status][resource_type] = n_count
|
|
769
|
+
|
|
770
|
+
tmpLog.debug("done")
|
|
771
|
+
return True, return_map
|
|
772
|
+
except Exception:
|
|
773
|
+
self.dump_error_message(tmpLog)
|
|
774
|
+
return False, {}
|
|
775
|
+
|
|
776
|
+
def getJobStatisticsByResourceTypeSite(self, workqueue):
|
|
777
|
+
"""
|
|
778
|
+
This function will return the job statistics per site for a particular workqueue, broken down by resource type
|
|
779
|
+
(SCORE, MCORE, etc.)
|
|
780
|
+
:param workqueue: workqueue object
|
|
781
|
+
"""
|
|
782
|
+
comment = " /* DBProxy.getJobStatisticsByResourceTypeSite */"
|
|
783
|
+
tmpLog = self.create_tagged_logger(comment, f"workqueue={workqueue}")
|
|
784
|
+
tmpLog.debug("start")
|
|
785
|
+
|
|
786
|
+
# define the var map of query parameters
|
|
787
|
+
var_map = {":vo": workqueue.VO}
|
|
788
|
+
|
|
789
|
+
# sql to query on pre-cached job statistics tables (JOBS_SHARE_STATS and JOBSDEFINED_SHARE_STATS)
|
|
790
|
+
sql_jt = "SELECT /*+ RESULT_CACHE */ jobstatus, resource_type, computingSite, SUM(njobs) FROM %s WHERE vo=:vo "
|
|
791
|
+
|
|
792
|
+
if workqueue.is_global_share:
|
|
793
|
+
sql_jt += "AND gshare=:gshare "
|
|
794
|
+
sql_jt += f"AND workqueue_id NOT IN (SELECT queue_id FROM {panda_config.schemaPANDA}.jedi_work_queue WHERE queue_function = 'Resource') "
|
|
795
|
+
var_map[":gshare"] = workqueue.queue_name
|
|
796
|
+
else:
|
|
797
|
+
sql_jt += "AND workqueue_id=:workqueue_id "
|
|
798
|
+
var_map[":workqueue_id"] = workqueue.queue_id
|
|
799
|
+
|
|
800
|
+
sql_jt += "GROUP BY jobstatus, resource_type, computingSite "
|
|
801
|
+
|
|
802
|
+
tables = [f"{panda_config.schemaPANDA}.JOBS_SHARE_STATS", f"{panda_config.schemaPANDA}.JOBSDEFINED_SHARE_STATS"]
|
|
803
|
+
|
|
804
|
+
return_map = {}
|
|
805
|
+
try:
|
|
806
|
+
for table in tables:
|
|
807
|
+
self.cur.arraysize = 10000
|
|
808
|
+
sql_exe = (sql_jt + comment) % table
|
|
809
|
+
self.cur.execute(sql_exe, var_map)
|
|
810
|
+
res = self.cur.fetchall()
|
|
811
|
+
|
|
812
|
+
# create map
|
|
813
|
+
for status, resource_type, computingSite, n_count in res:
|
|
814
|
+
return_map.setdefault(computingSite, {})
|
|
815
|
+
return_map[computingSite].setdefault(resource_type, {})
|
|
816
|
+
return_map[computingSite][resource_type][status] = n_count
|
|
817
|
+
|
|
818
|
+
tmpLog.debug("done")
|
|
819
|
+
return True, return_map
|
|
820
|
+
except Exception:
|
|
821
|
+
self.dump_error_message(tmpLog)
|
|
822
|
+
return False, {}
|
|
823
|
+
|
|
824
|
+
# gets statistics on the number of jobs with a specific status for each nucleus at each site
|
|
825
|
+
def get_num_jobs_with_status_by_nucleus(self, vo: str, job_status: str) -> [bool, Dict[str, Dict[str, int]]]:
|
|
826
|
+
"""
|
|
827
|
+
This function will return the number of jobs with a specific status for each nucleus at each site.
|
|
828
|
+
|
|
829
|
+
:param vo: Virtual Organization
|
|
830
|
+
:param job_status: Job status
|
|
831
|
+
:return: True/False and Dictionary with the number of jobs with a specific status for each nucleus at each site
|
|
832
|
+
"""
|
|
833
|
+
comment = " /* DBProxy.get_num_jobs_with_status_by_nucleus */"
|
|
834
|
+
tmp_log = self.create_tagged_logger(comment, f" vo={vo} status={job_status}")
|
|
835
|
+
tmp_log.debug("start")
|
|
836
|
+
|
|
837
|
+
# define the var map of query parameters
|
|
838
|
+
var_map = {":vo": vo, ":job_status": job_status}
|
|
839
|
+
|
|
840
|
+
# sql to query on pre-cached job statistics table
|
|
841
|
+
sql_jt = """
|
|
842
|
+
SELECT /*+ RESULT_CACHE */ computingSite, nucleus, SUM(njobs) FROM %s
|
|
843
|
+
WHERE vo=:vo AND jobStatus=:job_status GROUP BY computingSite, nucleus
|
|
844
|
+
"""
|
|
845
|
+
|
|
846
|
+
if job_status in ["transferring", "running", "activated" "holding"]:
|
|
847
|
+
table = f"{panda_config.schemaPANDA}.JOBS_SHARE_STATS"
|
|
848
|
+
else:
|
|
849
|
+
table = f"{panda_config.schemaPANDA}.JOBSDEFINED_SHARE_STATS"
|
|
850
|
+
|
|
851
|
+
return_map = {}
|
|
852
|
+
try:
|
|
853
|
+
self.cur.arraysize = 10000
|
|
854
|
+
sql_exe = (sql_jt + comment) % table
|
|
855
|
+
self.cur.execute(sql_exe, var_map)
|
|
856
|
+
res = self.cur.fetchall()
|
|
857
|
+
|
|
858
|
+
# create a dict
|
|
859
|
+
for panda_site, nucleus, n_jobs in res:
|
|
860
|
+
if n_jobs:
|
|
861
|
+
# add site
|
|
862
|
+
return_map.setdefault(panda_site, {})
|
|
863
|
+
# add stat per nucleus
|
|
864
|
+
return_map[panda_site][nucleus] = n_jobs
|
|
865
|
+
tmp_log.debug("done")
|
|
866
|
+
return True, return_map
|
|
867
|
+
except Exception:
|
|
868
|
+
self.dump_error_message(tmp_log)
|
|
869
|
+
return False, {}
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
# get metrics module
|
|
873
|
+
def get_metrics_module(base_mod) -> MetricsModule:
|
|
874
|
+
return base_mod.get_composite_module("metrics")
|