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