56 static const unsigned int NSTATES = 6;
57 enum Enum {
INIT=0, RUN=1, DOWNLOAD=2, MERGE=3, FINISHED=4, FAILED=5 };
58 static const char* name[NSTATES] =
59 {
"INIT",
"RUNNING",
"DOWNLOAD",
"MERGE",
"FINISHED",
"FAILED" };
60 Enum
parse(
const std::string& what)
62 for (
unsigned int i = 0; i != NSTATES; ++i) {
63 if (what == name[i]) {
return static_cast<Enum
>(i); }
65 throw std::runtime_error(
"PrunDriver.cxx: Failed to parse job state string");
73 enum Enum { DONE=0, PENDING=1, FAIL=2 };
76 struct TransitionRule {
77 JobState::Enum fromState;
79 JobState::Enum toState;
83 const std::string origDir;
84 TmpCd(
const std::string & dir)
85 : origDir(gSystem->pwd())
87 gSystem->cd(dir.c_str());
91 gSystem->cd(origDir.c_str());
99 static const std::string defaultState = JobState::name[JobState::INIT];
101 return JobState::parse(
label);
104static JobState::Enum
nextState(JobState::Enum state, Status::Enum status)
108 static constexpr std::array<TransitionRule, 12> TABLE =
110 {JobState::INIT, Status::DONE, JobState::RUN},
111 {JobState::INIT, Status::PENDING, JobState::INIT},
112 {JobState::INIT, Status::FAIL, JobState::FAILED},
113 {JobState::RUN, Status::DONE, JobState::DOWNLOAD},
114 {JobState::RUN, Status::PENDING, JobState::RUN},
115 {JobState::RUN, Status::FAIL, JobState::FAILED},
116 {JobState::DOWNLOAD, Status::DONE, JobState::MERGE},
117 {JobState::DOWNLOAD, Status::PENDING, JobState::DOWNLOAD},
118 {JobState::DOWNLOAD, Status::FAIL, JobState::FAILED},
119 {JobState::MERGE, Status::DONE, JobState::FINISHED},
120 {JobState::MERGE, Status::PENDING, JobState::MERGE},
121 {JobState::MERGE, Status::FAIL, JobState::DOWNLOAD}
123 for (
const TransitionRule& rule : TABLE) {
124 if (rule.fromState == state && rule.status == status) {
128 throw std::logic_error(
"PrunDriver.cxx: Missing state transition rule");
136 o.
setString(
"nc_cmtConfig", gSystem->ExpandPathName(
"$AnalysisBase_PLATFORM"));
137 o.
setString(
"nc_useAthenaPackages",
"true");
138 const std::string mergestr =
"elg_merge jobdef.root %OUT %IN";
150 static std::mutex
mutex;
155 const std::string& location)
157 using namespace EL::msgEventLoop;
164 std::filesystem::create_directories(location, ec);
167 ANA_MSG_ERROR(
"Failed to create directory " << location <<
": " << ec.message());
171 std::vector<std::string> datasets;
174 if (entry.type ==
"CONTAINER" || entry.type ==
"DIDType.CONTAINER")
175 datasets.push_back (entry.name);
179 for (
const auto& result : downloadResult)
181 if (result.notDownloaded != 0)
184 }
catch (
const std::exception& e) {
186 ANA_MSG_ERROR(
"Failed to download " << name <<
": " << e.what());
206 static std::mutex
mutex;
209 static std::set<std::string> loadedMacros;
210 if (loadedMacros.insert(macroFile).second) {
214 TPython::Bind(sample,
"ELG_SAMPLE");
216 const std::string code =
217 std::string(
"_anyresult = ROOT.std.make_any['int'](") + func +
"(ELG_SAMPLE))";
218 TPython::Exec(code.c_str(), &result);
219 TPython::Bind(
nullptr,
"ELG_SAMPLE");
220 return std::any_cast<int>(result);
226 using namespace EL::msgEventLoop;
228 ANA_MSG_INFO(
"Submitting " << sample->name() <<
"..." );
237 if (isFirstSample && ret == 1){
239 throw std::runtime_error(
"PrunDriver.cxx: aborting due to tarball creation issue");
243 sample->meta()->setString(
"nc_ELG_state_details",
244 "problem submitting");
248 sample->meta()->setDouble(
"nc_jediTaskID", ret);
261 int ret =
callPythonOnSample(
"EventLoopGrid/ELG_jediState.py",
"ELG_jediState", sample);
263 if (ret == Status::DONE)
return Status::DONE;
264 if (ret == Status::FAIL)
return Status::FAIL;
267 if (ret != 90) { sample->meta()->setString(
"nc_ELG_state_details",
"task status other than done/finished/failed/running"); }
270 sample->meta()->setString(
"nc_ELG_state_details",
271 "problem checking jedi task status");
274 return Status::PENDING;
280 using namespace EL::msgEventLoop;
286 ANA_MSG_INFO(
"Downloading output from: " << sample->name() <<
"...");
291 if (container[container.size()-1] ==
'/') {
292 container.resize(container.size() - 1);
294 container +=
"_hist/";
298 if (not downloadOk) {
301 sample->meta()->setString(
"nc_ELG_state_details",
302 "error, check log for details");
303 return Status::PENDING;
314 using namespace EL::msgEventLoop;
318 if (container[container.size()-1] ==
'/') {
319 container.resize(container.size() - 1);
321 container +=
"_hist/";
322 const std::string dir =
"elg/download/" + container;
324 const std::string fileName =
"hist-output.root";
326 const std::string target = Form(
"hist-%s.root", sample->name().c_str());
330 namespace fs = std::filesystem;
331 const std::string needle =
"." + fileName;
332 std::vector<std::string>
files;
334 for (fs::recursive_directory_iterator it(dir, ec), end; it != end; it.increment(ec)) {
336 if (it->is_regular_file() &&
337 it->path().filename().string().find(needle) != std::string::npos) {
338 files.push_back(it->path().string());
345 std::vector<std::string> duplicates;
346 for (
size_t i = 1; i <
files.size(); ++i) {
349 if (not duplicates.empty()) {
350 std::ostringstream dup;
351 for (
const std::string& name : duplicates) { dup <<
' ' << name; }
352 ANA_MSG_WARNING(
"Ignoring duplicate input file(s) for merging:" << dup.str());
356 if (not
files.size()) {
358 "Requeueing sample for download...");
359 sample->meta()->setString(
"nc_ELG_state_details",
"retry, files were lost");
366 sample->meta()->setString(
"nc_ELG_state_details",
367 "error, check log for details");
368 fs::remove(target, ec);
369 return Status::PENDING;
374 fs::remove(
file, ec);
379 fs::remove_all(dir, ec);
381 ANA_MSG_WARNING(
"Failed to remove download directory " << dir <<
": " << ec.message());
393 sample->meta()->setString(
"nc_ELG_state_details",
"");
395 Status::Enum status = Status::PENDING;
398 status =
submit(sample, isFirstSample);
403 case JobState::DOWNLOAD:
406 case JobState::MERGE:
407 status =
merge(sample);
409 case JobState::FINISHED:
410 case JobState::FAILED:
415 sample->meta()->setString(
"nc_ELG_state", JobState::name[state]);
419 const size_t nThreads)
425 bool isFirstSample =
true;
428 workList.push_back([sample, isFirstSample, state]()->
void{
429 if (state == JobState::INIT) {
440 }
catch (
const std::exception& e) {
441 using namespace EL::msgEventLoop;
444 ANA_MSG_ERROR (
"Exception while processing " << sample->name()
445 <<
": " << e.what());
447 sample->meta()->setString (
"nc_ELG_state_details",
448 std::string (
"exception: ") + e.what());
449 sample->meta()->setString (
"nc_ELG_state",
450 JobState::name[JobState::FAILED]);
455 isFirstSample =
false;
469 static std::optional<std::string> cached;
470 if (cached.has_value()) {
return cached; }
475 " from pandatools import PsubUtils\n"
476 " _nick = str(PsubUtils.getNickname())\n"
477 "except Exception:\n"
479 "_anyresult = ROOT.std.make_any['std::string'](_nick)\n";
480 TPython::Exec(code, &result);
481 const std::string nickname = std::any_cast<std::string>(result);
485 if (nickname.empty() || nickname.length() > 20) {
return std::nullopt; }
491 const std::string & pattern)
493 const std::string sampleName = sampleMeta.
castString(
"sample_name");
495 using namespace EL::msgEventLoop;
497 TString out = pattern.c_str();
500 const std::optional<std::string> nickname =
gridNickname();
501 if (not nickname.has_value()){
502 ANA_MSG_WARNING(
"No proxy available - cannot use nickname yet. Will try a late replacement.");
504 out.ReplaceAll(
"%nickname%", *nickname);
507 out.ReplaceAll(
"%in:name%", sampleName);
509 std::stringstream
ss(sampleName);
512 while(std::getline(
ss, item,
'.')) {
513 std::stringstream sskey;
514 sskey <<
"%in:name[" << ++field <<
"]%";
515 out.ReplaceAll(sskey.str(), item);
517 while (out.Index(
"%in:") != -1) {
518 int i1 = out.Index(
"%in:");
519 int i2 = out.Index(
"%", i1+1);
521 ANA_MSG_ERROR(
"malformed output name pattern, unterminated %in: token in \""
522 << out.Data() <<
"\"");
525 TString metaName = out(i1+4, i2-i1-4);
526 out.ReplaceAll(
"%in:"+metaName+
"%",
527 sampleMeta.
castString(std::string(metaName.Data())));
529 out.ReplaceAll(
"/",
"");
535 std::string out =
"hist:hist-output.root";
537 end = job.outputEnd(); os != end; ++os) {
538 const std::string name = os->label() +
".root";
539 const std::string ds =
541 out +=
"," + (ds.empty() ? name : ds +
":" + name);
551 TFile
file(fileName.c_str(),
"RECREATE");
553 outputs.SetOwner(
true);
555 outputs.Add(o->Clone());
556 file.WriteTObject(&job.jobConfig(),
"jobConfig",
"SingleKey");
557 file.WriteTObject(&outputs,
"outputs",
"SingleKey");
558 bool haveDefault =
false;
561 file.WriteObject(&
meta,
meta.castString(
"sample_name").c_str());
564 file.WriteObject (&
meta,
"defaultMetaObject");
575 const std::string outputFile =
"*" +
outputLabel +
".root*";
576 const std::string outDSSuffix =
'_' +
outputLabel +
".root/";
578 auto outSample = std::make_unique<SH::SampleGrid>(sample->name());
580 outSample->meta()->setString(
"nc_grid", outputDS);
581 outSample->meta()->setString(
"nc_grid_filter", outputFile);
582 out.add(std::move(outSample));
599 using namespace msgEventLoop;
605 const std::string jobELGDir = data.submitDir +
"/elg";
606 const std::string runShFile = jobELGDir +
"/runjob.sh";
607 const std::string mergeShFile = jobELGDir +
"/elg_merge";
611 const std::string jobDefFile = jobELGDir +
"/jobdef.root";
613 namespace fs = std::filesystem;
615 fs::create_directories(jobELGDir, ec);
617 ANA_MSG_ERROR(
"could not create directory " << jobELGDir <<
": " << ec.message());
618 return StatusCode::FAILURE;
622 const auto copyExecutable =
623 [&] (
const std::string& from,
const std::string& to) ->
StatusCode {
625 fs::copy_file(from, to, fs::copy_options::overwrite_existing, ec2);
627 ANA_MSG_ERROR(
"could not copy " << from <<
" to " << to <<
": " << ec2.message());
628 return StatusCode::FAILURE;
630 fs::permissions(to, fs::perms::owner_exec | fs::perms::group_exec |
631 fs::perms::others_exec, fs::perm_options::add, ec2);
633 ANA_MSG_ERROR(
"could not make " << to <<
" executable: " << ec2.message());
634 return StatusCode::FAILURE;
636 return StatusCode::SUCCESS;
638 ANA_CHECK(copyExecutable(runShOrig, runShFile));
639 ANA_CHECK(copyExecutable(mergeShOrig, mergeShFile));
644 if (listToShipToGrid.size()){
646 "Creating symbolic links for additional files or directories to be sent to grid.\n"
647 "For root or heavy files you should also add their name (not the full path) to EL::Job::optUserFiles.\n"
648 "Otherwise prun ignores those files."
651 std::vector<std::string> vect_filesOrDirToShip;
652 for (
auto&& part : std::views::split(listToShipToGrid,
',')) vect_filesOrDirToShip.emplace_back(part.begin(), part.end());
654 for (
const std::string & fileOrDirToShip: vect_filesOrDirToShip){
655 ANA_MSG_INFO ((
"Creating symbolic link for: " +fileOrDirToShip).c_str());
656 const fs::path linkPath =
657 fs::path(jobELGDir) / fs::path(fileOrDirToShip).filename();
659 fs::remove(linkPath, ec);
660 fs::create_symlink(fileOrDirToShip, linkPath, ec);
662 ANA_MSG_ERROR(
"could not create symbolic link " << linkPath.string()
663 <<
" -> " << fileOrDirToShip <<
": " << ec.message());
664 return StatusCode::FAILURE;
674 meta.fetchDefaults(data.options);
677 std::string outputSampleName =
meta.castString(
"nc_outputSampleName");
678 if (outputSampleName.empty()) {
679 outputSampleName =
"user.%nickname%.%in:name%";
682 meta.setString(
"nc_inDS",
meta.castString(
"nc_grid", sample->name()));
683 meta.setString(
"nc_writeInputToTxt",
"IN:input.txt");
684 meta.setString(
"nc_match",
meta.castString(
"nc_grid_filter"));
685 const std::string execstr =
"runjob.sh " + sample->name();
686 meta.setString(
"nc_exec", execstr);
687 meta.setString(
"nc_framework",
"EventLoopGrid");
693 out != data.job->outputEnd(); ++out) {
695 shOut.
save(data.submitDir +
"/output-" + out->label());
698 shHist.
save(data.submitDir +
"/output-hist");
700 TmpCd keepDir(jobELGDir);
704 sh.save(data.submitDir +
"/input");
705 data.submitted =
true;
718 return ::StatusCode::SUCCESS;
726 TmpCd tmpDir(data.submitDir);
732 const size_t nRunThreads =
options()->castDouble(
"nc_run_threads", 0);
733 const size_t nDlThreads =
options()->castDouble(
"nc_download_threads", 0);
741 std::cout << std::endl;
749 std::cout << sample->name() <<
"\t";
753 case JobState::DOWNLOAD:
754 case JobState::MERGE:
755 std::cout << JobState::name[state] <<
"\t";
757 case JobState::FINISHED:
758 std::cout <<
"\033[1;32m" << JobState::name[state] <<
"\033[0m\t";
760 case JobState::FAILED:
761 std::cout <<
"\033[1;31m" << JobState::name[state] <<
"\033[0m\t";
764 std::cout <<
details << std::endl;
766 allDone &= (state == JobState::FINISHED || state == JobState::FAILED);
769 std::cout << std::endl;
771 data.retrieved =
true;
772 data.completed = allDone;
773 return ::StatusCode::SUCCESS;
779 TmpCd tmpDir(location);
789 std::cout << sample->name() <<
"\t" << JobState::name[state]
790 <<
"\t" <<
details << std::endl;
795 const std::string& task,
796 const std::string& state)
801 TmpCd tmpDir(location);
805 if (not
sh.get(task)) {
806 std::cout <<
"Unknown task: " << task << std::endl;
807 std::cout <<
"Choose one of: " << std::endl;
811 JobState::parse(state);
812 sh.get(task)->meta()->setString(
"nc_ELG_state", state);
#define RCU_NEW_INVARIANT(x)
#define RCU_READ_INVARIANT(x)
virtual void lock()=0
Interface to allow an object to lock itself when made const in SG.
const std::string outputLabel
std::string PathResolverFindCalibFile(const std::string &logical_file_name)
static SH::MetaObject defaultOpts()
static void processAllInState(const SH::SampleHandler &sh, JobState::Enum state, const size_t nThreads)
ClassImp(EL::PrunDriver) namespace
static Status::Enum submit(SH::Sample *const sample, const bool isFirstSample)
static bool downloadContainer(const std::string &name, const std::string &location)
static std::string outputFileNames(const EL::Job &job)
static JobState::Enum nextState(JobState::Enum state, Status::Enum status)
static std::string formatOutputName(const SH::MetaObject &sampleMeta, const std::string &pattern)
static Status::Enum checkPandaTask(SH::Sample *const sample)
static Status::Enum download(SH::Sample *const sample)
static void saveJobDef(const std::string &fileName, const EL::Job &job, const SH::SampleHandler &sh)
static std::mutex & logMutex()
static std::optional< std::string > gridNickname()
static JobState::Enum sampleState(SH::Sample *sample)
static SH::SampleHandler outputSH(const SH::SampleHandler &in, const std::string &outputLabel)
static int callPythonOnSample(const char *macroFile, const char *func, SH::Sample *sample)
static void processTask(SH::Sample *const sample, const bool isFirstSample)
SH::MetaObject * options()
the list of options to jobs with this driver
virtual::StatusCode doManagerStep(Detail::ManagerData &data) const
const OutputStream * outputIter
static const std::string optGridPrunShipAdditionalFilesOrDirs
Enables to ship additional files to the tarbal sent to the grid Should be a list of comma separated p...
static const std::string optContainerSuffix
a Driver to submit jobs via prun
static void status(const std::string &location)
::StatusCode doRetrieve(Detail::ManagerData &data) const
static void setState(const std::string &location, const std::string &task, const std::string &state)
void testInvariant() const
A class that manages a list of Sample objects.
void save(const std::string &directory) const
save the list of samples to the given directory
a base class that manages a set of files belonging to a particular data set and the associated meta-d...
const std::string process
std::vector< std::string > files
file names and file pointers
std::string label(const std::string &format, int i)
@ doRetrieve
call the actual doRetrieve method
@ submitJob
do the actual job submission
::StatusCode StatusCode
StatusCode definition for legacy code.
void hadd(const std::string &output_file, const std::vector< std::string > &input_files, unsigned max_files)
effects: perform the hadd functionality guarantee: basic failures: out of memory III failures: i/o er...
std::vector< RucioDownloadResult > rucioDownloadList(const std::string &location, const std::vector< std::string > &datasets)
run rucio-download with multiple datasets
std::vector< RucioListDidsEntry > rucioListDids(const std::string &dataset)
run rucio-list-dids for the given dataset
DataModel_detail::iterator< DVL > unique(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end)
Specialization of unique for DataVector/List.
void sort(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end)
Specialization of sort for DataVector/List.
an internal data structure for passing data between different manager objects anbd step