ATLAS Offline Software
Loading...
Searching...
No Matches
PrunDriver.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
3*/
4
7
8
9
11#include <EventLoop/Algorithm.h>
14#include <EventLoop/Job.h>
19#include <RootCoreUtils/hadd.h>
26
27
28#include <TList.h>
29#include <TPython.h>
30#include <TROOT.h>
31#include <TFile.h>
32#include <TSystem.h>
33
34#include <algorithm>
35#include <any>
36#include <array>
37#include <cstdlib>
38#include <filesystem>
39#include <iostream>
40#include <optional>
41#include <set>
42#include <sstream>
43#include <string>
44#include <vector>
45#include <stdexcept>
46
47#include <ranges>
48
49#include "pool.h"
50#include <mutex>
51
53
54namespace {
55 namespace JobState {
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)
61 {
62 for (unsigned int i = 0; i != NSTATES; ++i) {
63 if (what == name[i]) { return static_cast<Enum>(i); }
64 }
65 throw std::runtime_error("PrunDriver.cxx: Failed to parse job state string");
66 }
67 }
68
69 // When changing the values in the enum make sure
70 // corresponding values in `data/ELG_jediState.py` script
71 // are changed accordingly
72 namespace Status {
73 enum Enum { DONE=0, PENDING=1, FAIL=2 };
74 }
75
76 struct TransitionRule {
77 JobState::Enum fromState;
78 Status::Enum status;
79 JobState::Enum toState;
80 };
81
82 struct TmpCd {
83 const std::string origDir;
84 TmpCd(const std::string & dir)
85 : origDir(gSystem->pwd())
86 {
87 gSystem->cd(dir.c_str());
88 }
89 ~TmpCd()
90 {
91 gSystem->cd(origDir.c_str());
92 }
93 };
94}
95
96static JobState::Enum sampleState(SH::Sample* sample)
97{
98 RCU_REQUIRE(sample);
99 static const std::string defaultState = JobState::name[JobState::INIT];
100 std::string label = sample->meta()->castString ("nc_ELG_state", defaultState, SH::MetaObject::CAST_NOCAST_DEFAULT);
101 return JobState::parse(label);
102}
103
104static JobState::Enum nextState(JobState::Enum state, Status::Enum status)
105{
106 RCU_REQUIRE(state != JobState::FINISHED);
107 RCU_REQUIRE(state != JobState::FAILED);
108 static constexpr std::array<TransitionRule, 12> TABLE =
109 {{
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}
122 }};
123 for (const TransitionRule& rule : TABLE) {
124 if (rule.fromState == state && rule.status == status) {
125 return rule.toState;
126 }
127 }
128 throw std::logic_error("PrunDriver.cxx: Missing state transition rule");
129}
130
132{
134 o.setString("nc_nGBPerJob", "MAX");
135 o.setString("nc_mergeOutput", "true");
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";
139 o.setString("nc_mergeScript", mergestr);
140 return o;
141}
142
143// Serialize message output from the worker threads. In standalone builds the
144// EL::msgEventLoop MsgStream is a single shared std::ostringstream with no
145// internal locking (and the printer writes to std::cout unguarded), so
146// concurrent ANA_MSG_* calls from the download/run threads would otherwise race
147// on that shared buffer.
148static std::mutex& logMutex()
149{
150 static std::mutex mutex;
151 return mutex;
152}
153
154static bool downloadContainer(const std::string& name,
155 const std::string& location)
156{
157 using namespace EL::msgEventLoop;
158 RCU_ASSERT(not name.empty());
159 RCU_ASSERT(name[name.size()-1] == '/');
160 RCU_ASSERT(not location.empty());
161
162 try {
163 std::error_code ec;
164 std::filesystem::create_directories(location, ec);
165 if (ec) {
166 std::lock_guard<std::mutex> lock(logMutex());
167 ANA_MSG_ERROR("Failed to create directory " << location << ": " << ec.message());
168 return false;
169 }
170
171 std::vector<std::string> datasets;
172 for (auto& entry : SH::rucioListDids (name))
173 {
174 if (entry.type == "CONTAINER" || entry.type == "DIDType.CONTAINER")
175 datasets.push_back (entry.name);
176 }
177
178 auto downloadResult = SH::rucioDownloadList (location, datasets);
179 for (const auto& result : downloadResult)
180 {
181 if (result.notDownloaded != 0)
182 return false;
183 }
184 } catch (const std::exception& e) {
185 std::lock_guard<std::mutex> lock(logMutex());
186 ANA_MSG_ERROR("Failed to download " << name << ": " << e.what());
187 return false;
188 }
189 return true;
190}
191
192// Call the Python function @c func (defined in the macro file @c macroFile,
193// loaded once on first use) on @c sample and return its integer result.
194//
195// The whole macro-load / bind / exec / unbind sequence is serialized under a
196// single mutex. @c ELG_SAMPLE is one global Python name shared by every
197// caller, and the underlying CPython interpreter must be entered with the GIL
198// held, so without this lock concurrent callers (e.g. the RUN-state polling
199// threads) would clobber each other's binding and query the wrong sample's
200// status. Serializing is cheap here since these calls only poll task state.
201// The alternative would be to pass the needed metadata as function arguments
202// rather than through a global binding.
203static int callPythonOnSample(const char* macroFile, const char* func,
204 SH::Sample* sample)
205{
206 static std::mutex mutex;
207 std::lock_guard<std::mutex> lock(mutex);
208
209 static std::set<std::string> loadedMacros;
210 if (loadedMacros.insert(macroFile).second) {
211 TPython::LoadMacro(PathResolverFindCalibFile(macroFile).c_str());
212 }
213
214 TPython::Bind(sample, "ELG_SAMPLE");
215 std::any result;
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);
221}
222
223static Status::Enum submit(SH::Sample* const sample, const bool isFirstSample)
224{
225 RCU_REQUIRE(sample);
226 using namespace EL::msgEventLoop;
227
228 ANA_MSG_INFO( "Submitting " << sample->name() << "..." );
229
230 int ret = callPythonOnSample("EventLoopGrid/ELG_prun.py", "ELG_prun", sample);
231
232 // Tarball is created for the first sample to be submitted
233 // then the tarball is simply reused for the other samples
234 // If the returned value is 1 it implies the tarball creation failed
235 // See EventLoopGrid/data/ELG_prun.py script
236 // Abort any further processing as the tarball was not succesfully created
237 if (isFirstSample && ret == 1){
238 ANA_MSG_ERROR("Failed to create tarball");
239 throw std::runtime_error("PrunDriver.cxx: aborting due to tarball creation issue");
240 }
241
242 if (ret < 100) {
243 sample->meta()->setString("nc_ELG_state_details",
244 "problem submitting");
245 return Status::FAIL;
246 }
247
248 sample->meta()->setDouble("nc_jediTaskID", ret);
249
250 // Let's also tell people about their task ID
251 ANA_MSG_INFO( "Task submitted; jediTaskID=" << ret );
252
253 return Status::DONE;
254}
255
256static Status::Enum checkPandaTask(SH::Sample* const sample)
257{
258 RCU_REQUIRE(sample);
259 RCU_REQUIRE(static_cast<int>(sample->meta()->castDouble("nc_jediTaskID",0, SH::MetaObject::CAST_NOCAST_DEFAULT)) > 100);
260
261 int ret = callPythonOnSample("EventLoopGrid/ELG_jediState.py", "ELG_jediState", sample);
262
263 if (ret == Status::DONE) return Status::DONE;
264 if (ret == Status::FAIL) return Status::FAIL;
265
266 // Value 90 corresponds to `running` state of the job
267 if (ret != 90) { sample->meta()->setString("nc_ELG_state_details", "task status other than done/finished/failed/running"); }
268 // Value 99 is returned if there is error in the script (import, missing ID)
269 if (ret == 99) {
270 sample->meta()->setString("nc_ELG_state_details",
271 "problem checking jedi task status");
272 }
273
274 return Status::PENDING;
275}
276
277static Status::Enum download(SH::Sample* const sample)
278{
279 RCU_REQUIRE(sample);
280 using namespace EL::msgEventLoop;
281
282 // This can run on several download threads at once; the shared message stream
283 // is not thread-safe, so serialize the logging (see logMutex()).
284 {
285 std::lock_guard<std::mutex> lock(logMutex());
286 ANA_MSG_INFO("Downloading output from: " << sample->name() << "...");
287 }
288
289 std::string container = sample->meta()->castString("nc_outDS", "", SH::MetaObject::CAST_NOCAST_DEFAULT);
290 RCU_ASSERT(not container.empty());
291 if (container[container.size()-1] == '/') {
292 container.resize(container.size() - 1);
293 }
294 container += "_hist/";
295
296 bool downloadOk = downloadContainer(container, "elg/download/" + container);
297
298 if (not downloadOk) {
299 std::lock_guard<std::mutex> lock(logMutex());
300 ANA_MSG_ERROR("Failed to download one or more files");
301 sample->meta()->setString("nc_ELG_state_details",
302 "error, check log for details");
303 return Status::PENDING;
304 }
305
306 return Status::DONE;
307}
308
309static Status::Enum merge(SH::Sample* const sample)
310{
311 RCU_REQUIRE(sample);
312 // The MERGE state is always processed single-threaded, so the logging here
313 // needs no serialization (unlike download(), see logMutex()).
314 using namespace EL::msgEventLoop;
315
316 std::string container = sample->meta()->castString("nc_outDS", "", SH::MetaObject::CAST_NOCAST_DEFAULT);
317 RCU_ASSERT(not container.empty());
318 if (container[container.size()-1] == '/') {
319 container.resize(container.size() - 1);
320 }
321 container += "_hist/";
322 const std::string dir = "elg/download/" + container;
323
324 const std::string fileName = "hist-output.root";
325
326 const std::string target = Form("hist-%s.root", sample->name().c_str());
327
328 // Collect the per-job output files, i.e. those whose name contains
329 // ".hist-output.root" (matching the old `find -name "*.hist-output.root*"`).
330 namespace fs = std::filesystem;
331 const std::string needle = "." + fileName;
332 std::vector<std::string> files;
333 std::error_code ec;
334 for (fs::recursive_directory_iterator it(dir, ec), end; it != end; it.increment(ec)) {
335 if (ec) { break; }
336 if (it->is_regular_file() &&
337 it->path().filename().string().find(needle) != std::string::npos) {
338 files.push_back(it->path().string());
339 }
340 }
341
342 std::sort(files.begin(), files.end());
343 // Duplicates are not expected (the directory scan never lists a file twice),
344 // but drop them with a real runtime check rather than a side-effecting assert.
345 std::vector<std::string> duplicates;
346 for (size_t i = 1; i < files.size(); ++i) {
347 if (files[i] == files[i - 1]) { duplicates.push_back(files[i]); }
348 }
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());
353 files.erase(std::unique(files.begin(), files.end()), files.end());
354 }
355
356 if (not files.size()) {
357 ANA_MSG_ERROR("Found no input files for merging! "
358 "Requeueing sample for download...");
359 sample->meta()->setString("nc_ELG_state_details", "retry, files were lost");
360 return Status::FAIL;
361 }
362
363 try {
364 RCU::hadd(target.c_str(), files);
365 } catch (...) {
366 sample->meta()->setString("nc_ELG_state_details",
367 "error, check log for details");
368 fs::remove(target, ec);
369 return Status::PENDING;
370 }
371
372 // Remove the merged inputs and the (now empty) download tree.
373 for (const std::string& file : files) {
374 fs::remove(file, ec);
375 if (ec) {
376 ANA_MSG_WARNING("Failed to remove merged input " << file << ": " << ec.message());
377 }
378 }
379 fs::remove_all(dir, ec);
380 if (ec) {
381 ANA_MSG_WARNING("Failed to remove download directory " << dir << ": " << ec.message());
382 }
383
384 return Status::DONE;
385}
386
387static void processTask(SH::Sample* const sample, const bool isFirstSample)
388{
389 RCU_REQUIRE(sample);
390
391 JobState::Enum state = sampleState(sample);
392
393 sample->meta()->setString("nc_ELG_state_details", "");
394
395 Status::Enum status = Status::PENDING;
396 switch (state) {
397 case JobState::INIT:
398 status = submit(sample, isFirstSample);
399 break;
400 case JobState::RUN:
401 status = checkPandaTask(sample);
402 break;
403 case JobState::DOWNLOAD:
404 status = download(sample);
405 break;
406 case JobState::MERGE:
407 status = merge(sample);
408 break;
409 case JobState::FINISHED:
410 case JobState::FAILED:
411 break;
412 }
413
414 state = nextState(state, status);
415 sample->meta()->setString("nc_ELG_state", JobState::name[state]);
416}
417
418static void processAllInState(const SH::SampleHandler& sh, JobState::Enum state,
419 const size_t nThreads)
420{
421 RCU_REQUIRE(sh.size());
422
423 WorkList workList;
424
425 bool isFirstSample = true;
426 for (SH::Sample* const sample : sh) {
427 if (sampleState(sample) == state) {
428 workList.push_back([sample, isFirstSample, state]()->void{
429 if (state == JobState::INIT) {
430 // INIT is always processed single-threaded, so let an exception
431 // (e.g. the deliberate tarball-creation abort in submit()) propagate
432 // and stop the submission.
433 processTask(sample, isFirstSample);
434 } else {
435 // On a worker thread an escaping exception would call std::terminate.
436 // Record it against the sample and mark it FAILED so the pool
437 // finishes processing the remaining samples.
438 try {
439 processTask(sample, isFirstSample);
440 } catch (const std::exception& e) {
441 using namespace EL::msgEventLoop;
442 {
443 std::lock_guard<std::mutex> lock(logMutex());
444 ANA_MSG_ERROR ("Exception while processing " << sample->name()
445 << ": " << e.what());
446 }
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]);
451 }
452 }
453 });
454 // Change boolean to false as already processed one sample
455 isFirstSample = false;
456 }
457 }
458 process(workList, nThreads);
459}
460
461// Look up the grid nickname via panda's PsubUtils, going through TPython (which
462// is already used for submission) instead of spawning a python subprocess.
463// Returns nullopt when the nickname cannot be determined (e.g. no valid proxy
464// yet), so the caller can leave %nickname% in the output pattern for the
465// python side to substitute later. Only a successful lookup is cached, so a
466// proxy created later in the same process is still picked up.
467static std::optional<std::string> gridNickname()
468{
469 static std::optional<std::string> cached;
470 if (cached.has_value()) { return cached; }
471
472 std::any result;
473 const char* code =
474 "try:\n"
475 " from pandatools import PsubUtils\n"
476 " _nick = str(PsubUtils.getNickname())\n"
477 "except Exception:\n"
478 " _nick = ''\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);
482
483 // An empty result means the lookup failed; a very long one is panda's
484 // "no proxy" message rather than an actual nickname.
485 if (nickname.empty() || nickname.length() > 20) { return std::nullopt; }
486 cached = nickname;
487 return cached;
488}
489
490static std::string formatOutputName(const SH::MetaObject& sampleMeta,
491 const std::string & pattern)
492{
493 const std::string sampleName = sampleMeta.castString("sample_name");
494 RCU_REQUIRE(not pattern.empty());
495 using namespace EL::msgEventLoop;
496
497 TString out = pattern.c_str();
498
499 // Handle case of no proxy; will create a proxy later in the submission
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.");
503 } else {
504 out.ReplaceAll("%nickname%", *nickname);
505 }
506
507 out.ReplaceAll("%in:name%", sampleName);
508
509 std::stringstream ss(sampleName);
510 std::string item;
511 int field = 0;
512 while(std::getline(ss, item, '.')) {
513 std::stringstream sskey;
514 sskey << "%in:name[" << ++field << "]%";
515 out.ReplaceAll(sskey.str(), item);
516 }
517 while (out.Index("%in:") != -1) {
518 int i1 = out.Index("%in:");
519 int i2 = out.Index("%", i1+1);
520 if (i2 == -1) {
521 ANA_MSG_ERROR("malformed output name pattern, unterminated %in: token in \""
522 << out.Data() << "\"");
523 break;
524 }
525 TString metaName = out(i1+4, i2-i1-4);
526 out.ReplaceAll("%in:"+metaName+"%",
527 sampleMeta.castString(std::string(metaName.Data())));
528 }
529 out.ReplaceAll("/", "");
530 return out.Data();
531}
532
533static std::string outputFileNames(const EL::Job& job)
534{
535 std::string out = "hist:hist-output.root";
536 for (EL::Job::outputIter os = job.outputBegin(),
537 end = job.outputEnd(); os != end; ++os) {
538 const std::string name = os->label() + ".root";
539 const std::string ds =
540 os->options()->castString(EL::OutputStream::optContainerSuffix);
541 out += "," + (ds.empty() ? name : ds + ":" + name);
542 }
543 return out;
544}
545
546// Save algortihms and lists of inputs and outputs to a root file
547static void saveJobDef(const std::string& fileName,
548 const EL::Job& job,
549 const SH::SampleHandler& sh)
550{
551 TFile file(fileName.c_str(), "RECREATE");
552 TList outputs;
553 outputs.SetOwner(true);
554 for (EL::Job::outputIter o = job.outputBegin(); o !=job.outputEnd(); ++o)
555 outputs.Add(o->Clone());
556 file.WriteTObject(&job.jobConfig(), "jobConfig", "SingleKey");
557 file.WriteTObject(&outputs, "outputs", "SingleKey");
558 bool haveDefault = false;
559 for (SH::Sample* const sample : sh) {
560 const SH::MetaObject& meta = *(sample->meta());
561 file.WriteObject(&meta, meta.castString("sample_name").c_str());
562 if (!haveDefault)
563 {
564 file.WriteObject (&meta, "defaultMetaObject");
565 haveDefault = true;
566 }
567 }
568}
569
570// Create a sample handler with grid locations of outputs with given label
572 const std::string& outputLabel)
573{
575 const std::string outputFile = "*" + outputLabel + ".root*";
576 const std::string outDSSuffix = '_' + outputLabel + ".root/";
577 for (SH::Sample* const sample : in) {
578 auto outSample = std::make_unique<SH::SampleGrid>(sample->name());
579 const std::string outputDS = sample->meta()->castString("nc_outDS", "", SH::MetaObject::CAST_NOCAST_DEFAULT) + outDSSuffix;
580 outSample->meta()->setString("nc_grid", outputDS);
581 outSample->meta()->setString("nc_grid_filter", outputFile);
582 out.add(std::move(outSample));
583 }
584 out.fetch(in);
585 return out;
586}
587
590
595
596::StatusCode EL::PrunDriver ::
597doManagerStep (Detail::ManagerData& data) const
598{
599 using namespace msgEventLoop;
601 switch (data.step)
602 {
604 {
605 const std::string jobELGDir = data.submitDir + "/elg";
606 const std::string runShFile = jobELGDir + "/runjob.sh";
607 const std::string mergeShFile = jobELGDir + "/elg_merge";
608 const std::string runShOrig = PathResolverFindCalibFile("EventLoopGrid/runjob.sh");
609 const std::string mergeShOrig = PathResolverFindCalibFile("EventLoopGrid/elg_merge");
610
611 const std::string jobDefFile = jobELGDir + "/jobdef.root";
612
613 namespace fs = std::filesystem;
614 std::error_code ec;
615 fs::create_directories(jobELGDir, ec);
616 if (ec) {
617 ANA_MSG_ERROR("could not create directory " << jobELGDir << ": " << ec.message());
618 return StatusCode::FAILURE;
619 }
620 // Copy the grid scripts into the submission directory and make them
621 // executable, aborting submission if either step fails.
622 const auto copyExecutable =
623 [&] (const std::string& from, const std::string& to) -> StatusCode {
624 std::error_code ec2;
625 fs::copy_file(from, to, fs::copy_options::overwrite_existing, ec2);
626 if (ec2) {
627 ANA_MSG_ERROR("could not copy " << from << " to " << to << ": " << ec2.message());
628 return StatusCode::FAILURE;
629 }
630 fs::permissions(to, fs::perms::owner_exec | fs::perms::group_exec |
631 fs::perms::others_exec, fs::perm_options::add, ec2);
632 if (ec2) {
633 ANA_MSG_ERROR("could not make " << to << " executable: " << ec2.message());
634 return StatusCode::FAILURE;
635 }
636 return StatusCode::SUCCESS;
637 };
638 ANA_CHECK(copyExecutable(runShOrig, runShFile));
639 ANA_CHECK(copyExecutable(mergeShOrig, mergeShFile));
640
641 // create symbolic links for additionnal files/directories if any to ship to the grid
642 std::string listToShipToGrid = data.options.castString(EL::Job::optGridPrunShipAdditionalFilesOrDirs, "");
643 // parse the list of comma separated files and/or directories to ship to the grid
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."
649 );
650
651 std::vector<std::string> vect_filesOrDirToShip;
652 for (auto&& part : std::views::split(listToShipToGrid, ',')) vect_filesOrDirToShip.emplace_back(part.begin(), part.end());
653 // Create symbolic links of files or directories to the submission directory
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();
658 // emulate `ln -sf`: replace any pre-existing link/file
659 fs::remove(linkPath, ec);
660 fs::create_symlink(fileOrDirToShip, linkPath, ec);
661 if (ec) {
662 ANA_MSG_ERROR("could not create symbolic link " << linkPath.string()
663 << " -> " << fileOrDirToShip << ": " << ec.message());
664 return StatusCode::FAILURE;
665 }
666 }
667 ANA_MSG_INFO ("Finished creation of symbolic links");
668 }
669
670 const SH::SampleHandler& sh = data.job->sampleHandler();
671
672 for (SH::Sample* const sample : sh) {
673 SH::MetaObject& meta = *sample->meta();
674 meta.fetchDefaults(data.options);
675 meta.fetchDefaults(defaultOpts());
676 meta.setString("nc_outputs", outputFileNames(*data.job));
677 std::string outputSampleName = meta.castString("nc_outputSampleName");
678 if (outputSampleName.empty()) {
679 outputSampleName = "user.%nickname%.%in:name%";
680 }
681 meta.setString("nc_outDS", formatOutputName(meta, outputSampleName));
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");
688 }
689
690 saveJobDef(jobDefFile, *data.job, sh);
691
692 for (EL::Job::outputIter out = data.job->outputBegin();
693 out != data.job->outputEnd(); ++out) {
694 SH::SampleHandler shOut = outputSH(sh, out->label());
695 shOut.save(data.submitDir + "/output-" + out->label());
696 }
697 SH::SampleHandler shHist = outputSH(sh, "hist-output");
698 shHist.save(data.submitDir + "/output-hist");
699
700 TmpCd keepDir(jobELGDir);
701
702 processAllInState(sh, JobState::INIT, 0);
703
704 sh.save(data.submitDir + "/input");
705 data.submitted = true;
706 }
707 break;
708
710 {
711 ANA_CHECK (doRetrieve (data));
712 }
713 break;
714
715 default:
716 (void) true; // safe to do nothing
717 }
718 return ::StatusCode::SUCCESS;
719}
720
722{
723 RCU_READ_INVARIANT(this);
724 RCU_REQUIRE(not data.submitDir.empty());
725
726 TmpCd tmpDir(data.submitDir);
727
729 sh.load("input");
730 RCU_ASSERT(sh.size());
731
732 const size_t nRunThreads = options()->castDouble("nc_run_threads", 0);
733 const size_t nDlThreads = options()->castDouble("nc_download_threads", 0);
734 processAllInState(sh, JobState::INIT, 0);
735 processAllInState(sh, JobState::RUN, nRunThreads);
736 processAllInState(sh, JobState::DOWNLOAD, nDlThreads);
737 processAllInState(sh, JobState::MERGE, 0);
738
739 sh.save("input");
740
741 std::cout << std::endl;
742
743 bool allDone = true;
744 for (SH::Sample* const sample : sh) {
745 JobState::Enum state = sampleState(sample);
746 std::string details = sample->meta()->castString("nc_ELG_state_details", "", SH::MetaObject::CAST_NOCAST_DEFAULT);
747 if (not details.empty()) { details = '(' + details + ')'; }
748
749 std::cout << sample->name() << "\t";
750 switch (state) {
751 case JobState::INIT:
752 case JobState::RUN:
753 case JobState::DOWNLOAD:
754 case JobState::MERGE:
755 std::cout << JobState::name[state] << "\t";
756 break;
757 case JobState::FINISHED:
758 std::cout << "\033[1;32m" << JobState::name[state] << "\033[0m\t";
759 break;
760 case JobState::FAILED:
761 std::cout << "\033[1;31m" << JobState::name[state] << "\033[0m\t";
762 break;
763 }
764 std::cout << details << std::endl;
765
766 allDone &= (state == JobState::FINISHED || state == JobState::FAILED);
767 }
768
769 std::cout << std::endl;
770
771 data.retrieved = true;
772 data.completed = allDone;
773 return ::StatusCode::SUCCESS;
774}
775
776void EL::PrunDriver::status(const std::string& location)
777{
778 RCU_REQUIRE(not location.empty());
779 TmpCd tmpDir(location);
781 sh.load("input");
782 RCU_ASSERT(sh.size());
783 processAllInState(sh, JobState::RUN, 0);
784 sh.save("input");
785 for (SH::Sample* const sample : sh) {
786 JobState::Enum state = sampleState(sample);
787 std::string details = sample->meta()->castString("nc_ELG_state_details", "", SH::MetaObject::CAST_NOCAST_DEFAULT);
788 if (not details.empty()) { details = '(' + details + ')'; }
789 std::cout << sample->name() << "\t" << JobState::name[state]
790 << "\t" << details << std::endl;
791 }
792}
793
794void EL::PrunDriver::setState(const std::string& location,
795 const std::string& task,
796 const std::string& state)
797{
798 RCU_REQUIRE(not location.empty());
799 RCU_REQUIRE(not task.empty());
800 RCU_REQUIRE(not state.empty());
801 TmpCd tmpDir(location);
803 sh.load("input");
804 RCU_ASSERT(sh.size());
805 if (not sh.get(task)) {
806 std::cout << "Unknown task: " << task << std::endl;
807 std::cout << "Choose one of: " << std::endl;
808 sh.print();
809 return;
810 }
811 JobState::parse(state);
812 sh.get(task)->meta()->setString("nc_ELG_state", state);
813 sh.save("input");
814}
#define RCU_ASSERT(x)
Definition Assert.h:210
#define RCU_NEW_INVARIANT(x)
Definition Assert.h:221
#define RCU_REQUIRE(x)
Definition Assert.h:196
#define RCU_READ_INVARIANT(x)
Definition Assert.h:217
#define ANA_MSG_ERROR(xmsg,...)
Macro printing error messages.
#define ANA_MSG_INFO(xmsg,...)
Macro printing info messages.
#define ANA_CHECK(EXP)
check whether the given expression was successful
#define ANA_MSG_WARNING(xmsg,...)
Macro printing warning messages.
#define INIT(__TYPE)
virtual void lock()=0
Interface to allow an object to lock itself when made const in SG.
static Double_t fs
static Double_t ss
const std::string outputLabel
std::string PathResolverFindCalibFile(const std::string &logical_file_name)
std::vector< WorkUnit > WorkList
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
Definition Job.h:42
const OutputStream * outputIter
Definition Job.h:139
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...
Definition Job.h:473
static const std::string optContainerSuffix
a Driver to submit jobs via prun
Definition PrunDriver.h:19
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 meta-data to be associated with an object.
Definition MetaObject.h:48
@ CAST_NOCAST_DEFAULT
cast and return the default value if the input has the wrong type
Definition MetaObject.h:70
void setString(const std::string &name, const std::string &value)
set the meta-data string with the given name
std::string castString(const std::string &name, const std::string &def_val="", CastMode mode=CAST_ERROR_THROW) const
the meta-data string with the given name
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...
Definition Sample.h:49
STL class.
std::map< std::string, std::string, std::less<> > parse(const std::string &list)
const std::string process
std::vector< std::string > files
file names and file pointers
Definition hcg.cxx:52
std::string label(const std::string &format, int i)
Definition label.h:19
@ doRetrieve
call the actual doRetrieve method
@ submitJob
do the actual job submission
Definition ManagerStep.h:92
::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...
Definition hadd.cxx:29
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
Definition merge.py:1
-diff
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
Definition ManagerData.h:46
TFile * file