BmnRoot 26.10.0-18-g4e3414f9
Software framework of the BM@N experiment
Loading...
Searching...
No Matches
nica-scheduler.cpp
Go to the documentation of this file.
1//========================================================================================================
2// Name : nica-scheduler.cpp
3// Author : Konstantin Gertsenberger
4// Description : NICA-Scheduler to run processing tasks of the ROOT-based frameworks through batch systems
5//========================================================================================================
6
7// own headers
8#include "batch_command.h"
10#include "function_set.h"
11
12// ROOT headers for work with the Unified Condition Database (UniConDa)
13#include "TSQLResult.h"
14#include "TSQLRow.h"
15#include "TSQLServer.h"
16#include "TString.h"
17#include "TSystem.h"
18
19// C++ headers
20#include <algorithm>
21#include <array>
22#include <cctype>
23#include <condition_variable>
24#include <fstream>
25#include <getopt.h>
26#include <glob.h>
27#include <libxml/parser.h>
28#include <libxml/tree.h>
29#include <map>
30#include <mutex>
31#include <sstream>
32#include <thread>
33#include <unistd.h>
34
35/* Predefined settings for the Unified Condition Database*/
37{
38 string db_host = "";
39 string db_name = "";
40 string db_username = "";
41 string db_password = "";
42
45 {
46 db_host = arg_db_settings.db_host;
47 db_name = arg_db_settings.db_name;
48 db_username = arg_db_settings.db_username;
49 db_password = arg_db_settings.db_password;
50 }
51 struct_database_settings(string arg_db_host, string arg_db_name, string arg_db_username, string arg_db_password)
52 {
53 db_host = arg_db_host;
54 db_name = arg_db_name;
55 db_username = arg_db_username;
56 db_password = arg_db_password;
57 }
59 {
60 if (this != &arg_db_settings) {
61 db_host = arg_db_settings.db_host;
62 db_name = arg_db_settings.db_name;
63 db_username = arg_db_settings.db_username;
64 db_password = arg_db_settings.db_password;
65 }
66 return *this;
67 }
68};
69#include "../settings.h"
70
71// debug flag for extended output
72bool isDebugMode = false;
73
74// C++11 does not include semaphore implementation (only in C++20)
75// therefore there is a manual implementation
77{
78 public:
79 Semaphore(unsigned int resource_count = 0)
80 : count_(resource_count)
81 {}
82
83 /**
84 * @brief Acquires a resource from the semaphore.
85 *
86 * Decrements the internal resource count. If the count is zero, the
87 * calling thread will block until another thread releases a resource by
88 * calling release().
89 */
90 void acquire()
91 {
92 unique_lock<mutex> lock(mutex_);
93 condition_.wait(lock, [this] { return count_ > 0; });
94 count_--;
95 }
96
97 /**
98 * @brief Releases a resource from the semaphore.
99 *
100 * Increments the internal resource count. If a thread is waiting for a
101 * resource to be released, it will be notified.
102 */
103 void release()
104 {
105 unique_lock<mutex> lock(mutex_);
106 count_++;
107 condition_.notify_one();
108 }
109
110 private:
111 mutex mutex_;
112 condition_variable condition_;
113 unsigned int count_;
114};
115
116// string structure to support null (no) value
118{
119 protected:
120 // string value
121 string strValue;
122 // whether the string is null (no value)
124
125 public:
127 : strValue("")
128 , isNullString(true)
129 {}
130 nullString(nullptr_t)
131 : strValue("")
132 , isNullString(true)
133 {}
134 nullString(char* pchar) { SetValue(pchar); }
135 nullString(string str)
136 : strValue(str)
137 , isNullString(false)
138 {}
140 : strValue(str.strValue)
142 {}
143
145 {
146 strValue = str.strValue;
147 isNullString = str.isNullString;
148 return *this;
149 }
150
151 bool operator==(nullptr_t) const { return isNullString; }
152 bool operator!=(nullptr_t) const { return !isNullString; }
153
154 void SetNull()
155 {
156 strValue = "";
157 isNullString = true;
158 }
159 void SetValue(string str)
160 {
161 strValue = str;
162 isNullString = false;
163 }
164 void SetValue(nullptr_t) { SetNull(); }
165 void SetValue(char* pchar)
166 {
167 if (pchar == nullptr)
168 SetNull();
169 else
170 SetValue((string)pchar);
171 }
172
173 string GetValue() { return strValue; }
174 bool isNull() { return isNullString; }
175};
176
177// structure with information on files to be processed
178// iParallelMode = processor count to parallel event processing of the input file ('-1' - was not set)
179// iMerge: 0 (chain) - save only result TChain with the data tree to the first file name (pointing to other files)
180// 1 (true) - merge to the first file name and delete partial files
181// 2 (preserve) - merge to the first file name and not delete partial files
182// -1 (false) - not to merge (default if 'merge' attribute is not set
184{
185 // input file path
186 string strFileIn;
187 // output file path or null if no output file set
189 // start event and event number to be processed (higher priority than for <macro>)
190 unique_ptr<int> ptrStartEvent, ptrEventCount;
191 // ???
193 // described upper, before the structure
195 // command and path for copying to a temporary storage
197 // command and path for copying from a temporary storage
199 // additional result file paths or masks to copy from a temporary storage
200 vector<string> vecGetExtraPath;
201 // paths of temporary files to be deleted
202 vector<string> vecCleanPath;
203
205 : strFileIn("")
206 , nstrFileOut(nullptr)
207 , ptrStartEvent(nullptr)
208 , ptrEventCount(nullptr)
209 , strParallelMode("")
210 , iParallelMode(-1)
211 , iMerge(-1)
212 , strPutCommand("")
213 , strPutPath("")
214 , strGetCommand("")
215 , strGetPath("")
216 {}
218 {
219 vecGetExtraPath.clear();
220 vecCleanPath.clear();
221 }
222};
223
224// structure with information on macro to be executed
225// macro must have the following sequence of parameters: input_file, output_file, start event, event count...
227{
228 // ROOT macro name
230 // ROOT macro path
232 // start event and event number to be processed
233 unique_ptr<int> ptrStartEvent, ptrEventCount;
234 // additional arguments of the macro after 'event count'
236 // file list to be processed
237 vector<unique_ptr<structFilePar>> vecFiles;
239 : nstrMacroName(nullptr)
240 , nstrMacroPath(nullptr)
241 , ptrStartEvent(nullptr)
242 , ptrEventCount(nullptr)
243 , strAddArgs("")
244 {}
246};
247
248// structure with information on one element of a job stack
249// iStackType: 0 - ROOT macro, 1 - command line
251{
252 // type of the job stack element
254 // macro parameters if the stack element is a <macro> tag
255 shared_ptr<structMacroPar> sptrMacroParameter;
256 // command line if the stack element is a <command> tag
258
260 : iStackType(-1)
261 , sptrMacroParameter(nullptr)
262 , nstrCommandline(nullptr)
263 {}
264 structJobStackPar(shared_ptr<structMacroPar> macro_par)
265 : iStackType(0)
266 , sptrMacroParameter(macro_par)
267 , nstrCommandline(nullptr)
268 {}
269 structJobStackPar(string command_line)
270 : iStackType(1)
271 , sptrMacroParameter(nullptr)
272 , nstrCommandline(command_line)
273 {}
274};
275
276// structure with information on job to be run
278{
279 // job name
281 // job name of the parent job, from which the current one is dependent
283 // list of ROOT macros in the job, it is also used for macro_input and output dependencies
284 vector<shared_ptr<structMacroPar>> vecMacros;
285 // ordered stack of <macro> and <command> tags in the job
286 vector<shared_ptr<structJobStackPar>> vecJobStack;
287 // run mode: 0 - local (on a multi-core machine), 1 - global (on a cluster), 2 - in container on a cluster
289 // container type
291 // string with processor count to be used
293 // maximum number of submitted jobs in the queue (0 - unlimited)
295 // maximum number of tasks in one batch array job (0 - unlimited)
297 // configuration file path
299 // priority of the job in a batch system
301 // string with RAM size requested for one process
303 // whether write a core dump if the task is crashed
305 // log file path
307 // queue name of a batch system
309 // Quality of Service name of a batch system
311 // a list of hosts, on which the job is performed
313 // working directory path
315 // setting of the Unified Condition Database
318 : strJobName("scheduler_job")
320 , iRunMode(0)
321 , strContainerType("")
322 , nstrProcCount(nullptr)
323 , iQueueLimit(0)
324 , iArrayLimit(0)
325 , strConfigPath("")
326 , nstrPriority(nullptr)
328 , isCoreDump(false)
329 , nstrLogs(nullptr)
330 , nstrQueue(nullptr)
331 , nstrQOS(nullptr)
332 , nstrHosts(nullptr)
333 , nstrWorkDir(nullptr)
334 {}
336 {
337 vecMacros.clear();
338 vecJobStack.clear();
339 }
340};
341
342// structure for parameters to transfer to the main thread in case of the LOCAL mode
344{
345 // job parameters to use in the main thread
346 shared_ptr<structJobPar> sptrJobParameter;
347 // semaphore to synchronize all the threads
348 shared_ptr<Semaphore> semJobSemaphore;
349 // mutex to synchronize all the threads
350 shared_ptr<mutex> mutJobMutex;
351 // thread number starting from 1
353};
354// structure for parameters to transfer from the main thread to sub-threads
355// sub-threads are used to parallelize by events, where threads process individual files
357{
358 // move parameters from the main thread to child ones
359 shared_ptr<structThreadPar> sptrThreadParameter;
360 // macro parameters to use in the child threads
361 shared_ptr<structMacroPar> sptrMacroParameter;
362 // start event for the current sub-thread
364 // event count to be processed by the current sub-thread
366 // real input file path for the sub-thread
367 string strFileIn;
368 // real output file path for the sub-thread
370 // sub-thread number
372};
373
374// declaration of function to parse criteria to select data from the Unified Condition Database
375void ParseDatabaseParameters(string input, TString& sql, bool is_simulation);
376// declaration of function to parse file list for the macro of the job
377bool ParseMacroFiles(xmlNodePtr sub_node,
378 shared_ptr<structJobPar> job_par,
379 shared_ptr<structMacroPar> macro_par,
380 multimap<string, unique_ptr<vector<nullString>>>& map_job_output,
381 batch_commands batch_com);
382
383// special function to color main info and error messages (cyan or red color)
384void info_message(const string msg, bool is_error = 0)
385{
386 bool is_terminal = isatty(fileno(stdout));
387
388 string color_off("\033[0m");
389 string msg_color; // message color
390 if (is_error)
391 msg_color = "\033[0;91m"; // light red color for errors
392 else
393 msg_color = "\033[0;36m"; // cyan color for info message
394
395 if (is_terminal)
396 cout << msg_color << msg << color_off << endl;
397 else
398 cout << msg << endl;
399}
400// concatenate batch job IDs to form a dependency list
401string concatenate_job_ids(const vector<string>& vec_job_id, char separator)
402{
403 string result = "";
404
405 for (size_t i = 0; i < vec_job_id.size(); i++) {
406 if (i > 0)
407 result += separator;
408 result += vec_job_id[i];
409 }
410
411 return result;
412}
413
414// get current number of jobs submitted by the current user to the batch queue
416{
417 if (batch_com.queue_count_command == "")
418 return 0;
419
420 array<char, 128> bufferQueue;
421 string command_output = "";
422 FILE* stream = popen(batch_com.queue_count_command.c_str(), "r");
423 if (stream == nullptr)
424 return -1;
425
426 while (fgets(bufferQueue.data(), static_cast<int>(bufferQueue.size()), stream) != nullptr)
427 command_output += bufferQueue.data();
428 pclose(stream);
429
430 command_output = find_first_number(command_output);
431 if (command_output == "")
432 return -1;
433
434 return atoi(command_output.c_str());
435}
436
437// wait until the current user queue has a free slot according to queuelimit
438bool wait_batch_queue_limit(batch_commands batch_com, int queue_limit)
439{
440 if (queue_limit <= 0)
441 return true;
442
443 while (true) {
444 int current_queue_count = get_batch_queue_job_count(batch_com);
445 if (current_queue_count < 0) {
446 info_message("ERROR nica-scheduler$ Batch queue job count was not defined. This job will be skipped!", 1);
447 return false;
448 }
449
450 if (current_queue_count < queue_limit)
451 return true;
452
453 if (isDebugMode)
454 cout << "DEBUG nica-scheduler$ The queue limit is reached: " << current_queue_count << "/" << queue_limit
455 << " job(s). Waiting for a free slot..." << endl;
456
457 sleep(30);
458 }
459}
460
461// replace string in the input text with the following options after ':'
462// '~N' - exclude first N-'count' symbols of the new_substring
463// e.g. {file_name_with_ext:~3} - remove first 3 symbols
464// '-N' - exclude last N-'count' symbols of the new_substring
465// e.g. {file_name:-4}- remove last 4 symbols
466void replace_string_in_text_ext(string& text, string old_substring, string new_substring)
467{
468 int start = -1, end, count, last_char = old_substring.length() - 1;
469 do {
470 start = text.find(old_substring.substr(0, last_char), start + 1);
471 if (start > -1) {
472 if (text[start + last_char] == ':') {
473 if (text[start + last_char + 1] == '~') {
474 end = text.find("}", start + last_char + 2);
475 if (end > -1) {
476 count = atoi(text.substr(start + last_char + 2, end - start - last_char - 2).c_str());
477 if (((unsigned int)count) < new_substring.length())
478 text.replace(start, end + 1 - start, new_substring.substr(count).c_str());
479 else
480 cout << "Warning: deletion of the first chars ('~' command symbol) was failed." << endl;
481 }
482 }
483 if (text[start + last_char + 1] == '-') {
484 end = text.find("}", start + last_char + 2);
485 if (end > -1) {
486 count = atoi(text.substr(start + last_char + 2, end - start - last_char - 2).c_str());
487 if (((unsigned int)count) < new_substring.length())
488 text.replace(start, end + 1 - start,
489 new_substring.substr(0, new_substring.length() - count).c_str());
490 else
491 cout << "Warning: deletion of the last chars ('-' command symbol) was failed." << endl;
492 }
493 }
494 } else
495 text.replace(start, last_char + 1, new_substring.c_str());
496 }
497 } while (start > -1);
498}
499
500// form output file name containing special (${}) variables
501string form_file_name(string out_name, string in_name, int counter, string batch_temp_dir)
502{
503 replace_string_in_text(out_name, "${counter}", counter);
504 replace_string_in_text(out_name, "${batch_temp_dir}", batch_temp_dir);
505
506 replace_string_in_text_ext(out_name, "${input}", in_name);
507 replace_string_in_text_ext(out_name, "${file_dir_name}", get_directory_name(in_name));
508 // ${file_name_with_ext} must be BEFORE ${file_name} because of searching without the last '}'
509 replace_string_in_text_ext(out_name, "${file_name_with_ext}", get_file_name_with_ext(in_name));
510 replace_string_in_text_ext(out_name, "${file_name}", get_file_name(in_name));
511
512 // ${first_number_fn} must be BEFORE ${first_number} because of searching without the last '}'
513 replace_string_in_text_ext(out_name, "${first_number_fn}", find_first_number(get_file_name(in_name)));
514 replace_string_in_text_ext(out_name, "${first_number}", find_first_number(in_name));
515 replace_string_in_text_ext(out_name, "${last_number}", find_last_number(in_name));
516
517 if (out_name.find("${user}") != string::npos) {
518 char* login_name = getlogin();
519 if (login_name != nullptr)
520 replace_string_in_text(out_name, "${user}", login_name);
521 }
522
523 return out_name;
524}
525
526// parse regular expression in the path and return a set of result file paths (for UNIX-systems)
527inline vector<string> glob(const string& path)
528{
529 glob_t glob_result;
530 glob(path.c_str(), GLOB_TILDE, nullptr, &glob_result);
531
532 vector<string> ret;
533 for (unsigned int i = 0; i < glob_result.gl_pathc; i++)
534 ret.push_back(string(glob_result.gl_pathv[i]));
535
536 globfree(&glob_result);
537 return ret;
538}
539// skip whitespaces in JSON input
540void skip_json_spaces(const string& text, size_t& pos)
541{
542 while ((pos < text.length()) && isspace(static_cast<unsigned char>(text[pos])))
543 pos++;
544}
545
546// parse JSON string value and unescape the most common JSON escape sequences
547bool parse_json_file_string(const string& text, size_t& pos, string& value, string& error)
548{
549 skip_json_spaces(text, pos);
550
551 if ((pos >= text.length()) || (text[pos] != '"')) {
552 error = "expected JSON string";
553 return false;
554 }
555
556 value = "";
557 pos++;
558
559 while (pos < text.length()) {
560 char ch = text[pos++];
561
562 if (ch == '"')
563 return true;
564
565 if (static_cast<unsigned char>(ch) < 0x20) {
566 error = "unescaped control character in JSON string";
567 return false;
568 }
569
570 if (ch != '\\') {
571 value += ch;
572 continue;
573 }
574
575 if (pos >= text.length()) {
576 error = "unfinished escape sequence in JSON string";
577 return false;
578 }
579
580 char esc = text[pos++];
581
582 switch (esc) {
583 case '"':
584 value += '"';
585 break;
586 case '\\':
587 value += '\\';
588 break;
589 case '/':
590 value += '/';
591 break;
592 case 'b':
593 value += '\b';
594 break;
595 case 'f':
596 value += '\f';
597 break;
598 case 'n':
599 value += '\n';
600 break;
601 case 'r':
602 value += '\r';
603 break;
604 case 't':
605 value += '\t';
606 break;
607 default:
608 error = "unsupported escape sequence in JSON string";
609 return false;
610 }
611 }
612
613 error = "unfinished JSON string";
614 return false;
615}
616
617// parse a JSON file list in the form: ["file1.root", "file2.root"]
618bool parse_json_file_list(const string& text, vector<string>& files, string& error)
619{
620 size_t pos = 0;
621
622 skip_json_spaces(text, pos);
623
624 if ((pos >= text.length()) || (text[pos] != '[')) {
625 error = "JSON input must be an array of strings";
626 return false;
627 }
628
629 pos++;
630 skip_json_spaces(text, pos);
631
632 if ((pos < text.length()) && (text[pos] == ']')) {
633 pos++;
634 skip_json_spaces(text, pos);
635
636 if (pos != text.length()) {
637 error = "unexpected characters after JSON array";
638 return false;
639 }
640
641 return true;
642 }
643
644 while (pos < text.length()) {
645 string file_path;
646
647 if (!parse_json_file_string(text, pos, file_path, error))
648 return false;
649
650 files.push_back(file_path);
651
652 skip_json_spaces(text, pos);
653
654 if ((pos < text.length()) && (text[pos] == ',')) {
655 pos++;
656 continue;
657 }
658
659 if ((pos < text.length()) && (text[pos] == ']')) {
660 pos++;
661 skip_json_spaces(text, pos);
662
663 if (pos != text.length()) {
664 error = "unexpected characters after JSON array";
665 return false;
666 }
667
668 return true;
669 }
670
671 error = "expected ',' or ']' in JSON array";
672 return false;
673 }
674
675 error = "unfinished JSON array";
676 return false;
677}
678
679bool append_input_files_from_json(const string& json_input_path, vector<string>& vecInputFiles)
680{
681 string str_json_input = expand_path(json_input_path);
682
683 ifstream jsonFile(str_json_input.c_str());
684
685 if (!jsonFile) {
686 info_message((TString::Format("ERROR nica-scheduler$ Can not open JSON file with file input list: '%s'",
687 str_json_input.c_str()))
688 .Data(),
689 1);
690
691 return true;
692 }
693
694 stringstream buffer;
695 buffer << jsonFile.rdbuf();
696
697 vector<string> vecJsonInputFiles;
698 string error;
699
700 if (!parse_json_file_list(buffer.str(), vecJsonInputFiles, error)) {
701 info_message((TString::Format("ERROR nica-scheduler$ Can not parse JSON file input list '%s': %s",
702 str_json_input.c_str(), error.c_str()))
703 .Data(),
704 1);
705
706 return true;
707 }
708
709 vecInputFiles.insert(vecInputFiles.end(), vecJsonInputFiles.begin(), vecJsonInputFiles.end());
710
711 if (isDebugMode)
712 cout << "DEBUG nica-scheduler$ File count in the JSON input list: " << vecJsonInputFiles.size() << endl;
713
714 return false;
715}
716// generate output file name concatenating with 'counter' value (for partial results)
717string GenerateOutputFilePath(string path, int counter)
718{
719 size_t last_point_idx = path.find_last_of(".");
720
721 string add_string = "_";
722 add_string += int_to_string(counter);
723
724 if (string::npos != last_point_idx)
725 return path.insert(last_point_idx, add_string);
726 else
727 return path.append(add_string);
728}
729
730// check whether a file path or mask contains shell wildcards
731bool has_wildcards(const string& path)
732{
733 return (path.find('?') != string::npos) || (path.find('*') != string::npos) || (path.find('[') != string::npos)
734 || (path.find('+') != string::npos);
735}
736
737// check whether a path should be considered relative to the <get> directory
738bool is_relative_file_path(const string& path)
739{
740 return (path != "") && (path[0] != '/') && (path[0] != '~') && (path[0] != '$')
741 && (path.find("://") == string::npos);
742}
743
744// copy additional result files declared by the <get extras=...> option
746{
747 if ((cur_file == nullptr) || (cur_file->nstrFileOut == nullptr) || cur_file->vecGetExtraPath.empty())
748 return;
749
750 string strOutputDir = get_directory_path(cur_file->nstrFileOut.GetValue());
751 // if the output path is a bare file name, copy extra files to the current directory
752 if (strOutputDir == "")
753 strOutputDir = ".";
754
755 for (const string& cur_extra_path : cur_file->vecGetExtraPath) {
756 TString expandedExtraPath = cur_extra_path.c_str();
757 gSystem->ExpandPathName(expandedExtraPath);
758
759 string strExtraPath = expandedExtraPath.Data();
760
761 vector<string> vec_extra_files;
762
763 if (has_wildcards(strExtraPath))
764 vec_extra_files = glob(strExtraPath);
765 else
766 vec_extra_files.push_back(strExtraPath);
767
768 if (vec_extra_files.empty()) {
769 cout << "nica-scheduler$ WARNING: no extra result files were found by mask: " << strExtraPath << endl;
770 continue;
771 }
772
773 for (const string& cur_extra_file : vec_extra_files) {
774 stringstream ssCopyExtraCommand;
775 ssCopyExtraCommand << cur_file->strGetCommand << " " << cur_extra_file << " "
776 << strOutputDir + string("/") + get_file_name_with_ext(cur_extra_file);
777
778 int system_return = system(ssCopyExtraCommand.str().c_str());
779 if (system_return != 0)
780 cout << "nica-scheduler$ WARNING: System call (in CopyExtraGetFiles) returned non-zero code: "
781 << system_return << endl;
782 }
783 }
784}
785// execute a command line as an element of the job stack in local mode
786void RunLocalCommandLine(shared_ptr<structThreadPar> thread_par, string command_line, int stack_counter)
787{
788 shared_ptr<structJobPar> job_par = thread_par->sptrJobParameter;
789 int thread_counter = thread_par->iThreadCounter;
790
791 // generate bash command string
792 stringstream ssBashCommand;
793 ssBashCommand << job_par->strConfigPath << command_line;
794 if (job_par->nstrLogs != nullptr)
795 ssBashCommand << " >> " << job_par->nstrLogs.GetValue() << "_" << to_string(thread_counter) << " 2>&1";
796
797 // generate output name for temporary bash file
798 stringstream ssBashFile;
799 long int t = time(nullptr);
800 ssBashFile << "temp_command_" << to_string(thread_counter) << "_" << to_string(stack_counter) << "_" << t << ".sh";
801
802 // create temporary bash file and write the command into it
803 ofstream bash_file;
804 bash_file.open(ssBashFile.str().c_str());
805 bash_file << ssBashCommand.str();
806 bash_file.close();
807
808 if (isDebugMode)
809 cout << "DEBUG nica-scheduler$ Local command line = " << ssBashCommand.str() << endl;
810
811 // lock thread for sequential output
812 thread_par->mutJobMutex->lock();
813 cout << "nica-scheduler$ Task " << thread_counter << " is running: command line - " << command_line << endl;
814 thread_par->mutJobMutex->unlock();
815
816 // run bash command
817 stringstream ssBashRun;
818 ssBashRun << "bash " << ssBashFile.str();
819 int system_return = system(ssBashRun.str().c_str());
820 if (system_return != 0)
821 cout << "nica-scheduler$ WARNING: System call (in RunLocalCommandLine) returned non-zero code: "
822 << system_return << endl;
823
824 // delete temporary bash file
825 if (!isDebugMode) {
826 stringstream ssRemoveCommand;
827 ssRemoveCommand << "rm -rf " << ssBashFile.str();
828 system_return = system(ssRemoveCommand.str().c_str());
829 if (system_return != 0)
830 cout << "nica-scheduler$ WARNING: System call (in RunLocalCommandLine) returned non-zero code: "
831 << system_return << endl;
832 } else
833 cout << "DEBUG nica-scheduler$ '" << ssBashFile.str() << "' bash file was used " << endl;
834}
835
836// sub-thread in working thread to process single file
837void SubThreadProcessFile(shared_ptr<structSubThreadPar> subthread_par)
838{
839 shared_ptr<structThreadPar> thread_par = subthread_par->sptrThreadParameter;
840
841 // get necessary parameters transferred to the sub-thread
842 string config_file = thread_par->sptrJobParameter->strConfigPath;
843 string macro_path = subthread_par->sptrMacroParameter->nstrMacroPath.GetValue();
844 string in_file = subthread_par->strFileIn;
845 string add_args = subthread_par->sptrMacroParameter->strAddArgs;
846 string logs = thread_par->sptrJobParameter->nstrLogs.GetValue();
847
848 int start_event = subthread_par->iStartEvent;
849 int event_count = subthread_par->iEventCount;
850 nullString out_file = subthread_par->nstrFileOut;
851
852 int thread_counter = thread_par->iThreadCounter;
853 int subthread_counter = subthread_par->iSubthreadCounter;
854
855 // using the parameters to generate job string to be executed
856 stringstream ssROOTCommand;
857 ssROOTCommand << config_file << "root -b -q '" << macro_path << "(";
858
859 // first macro argument - input file
860 bool is_first = true;
861 if (in_file != "") {
862 ssROOTCommand << "\"" << in_file << "\"";
863 is_first = false;
864 }
865 // second macro argument - output file
866 if (!out_file.isNull()) {
867 if (!is_first)
868 ssROOTCommand << ", ";
869 else
870 is_first = false;
871 ssROOTCommand << "\"" << out_file.GetValue() << "\"";
872 }
873 // third macro argument - start event number
874 if (!is_first)
875 ssROOTCommand << ", ";
876 else
877 is_first = false;
878 ssROOTCommand << to_string(start_event);
879 // fourth macro argument - event count
880 if (!is_first)
881 ssROOTCommand << ", ";
882 else
883 is_first = false;
884 ssROOTCommand << to_string(event_count);
885 // then add other arguments to the macro
886 if (add_args != "") {
887 if (!is_first)
888 ssROOTCommand << ", ";
889 else
890 is_first = false;
891 ssROOTCommand << add_args;
892 }
893
894 // end of the command with writing to the log or not
895 if (logs != "")
896 ssROOTCommand << ")' > " << logs << "_" << to_string(thread_counter) << "_" << to_string(subthread_counter)
897 << " 2>&1";
898 else
899 ssROOTCommand << ")'";
900
901 // write and execute temporary bash file
902 ofstream myfile;
903 long int t = time(nullptr);
904 // generate output name for temporary bash file
905 stringstream ssBashFile;
906 ssBashFile << "temp_" << to_string(thread_counter) << "_" << to_string(subthread_counter) << "_" << to_string(t)
907 << ".sh";
908 // open bash file and run the ROOT macro command
909 myfile.open(ssBashFile.str().c_str());
910 myfile << "export ROOT_HIST=0 " << endl;
911 myfile << ssROOTCommand.str();
912 myfile.close();
913 stringstream ssRunCommand;
914 ssRunCommand << "bash " << ssBashFile.str();
915
916 // Create output directory and check for failure
917 if (!out_file.isNull()) {
918 string outDir = get_directory_path(out_file.GetValue());
919 if (!outDir.empty()) {
920 string mkdirCmd = "mkdir -p \"" + outDir + "\"";
921 int ret = system(mkdirCmd.c_str());
922 if (ret != 0) {
923 thread_par->mutJobMutex->lock();
924 info_message("ERROR nica-scheduler$ Failed to create output directory for subtask "
925 + to_string(thread_counter) + ":" + to_string(subthread_counter) + ": " + outDir,
926 1);
927 thread_par->mutJobMutex->unlock();
928
929 // Remove temporary script file
930 if (!isDebugMode) {
931 string rmCmd = "rm -f \"" + ssBashFile.str() + "\"";
932 system(rmCmd.c_str());
933 }
934
935 thread_par->semJobSemaphore->release();
936 return;
937 }
938 }
939 }
940
941 // lock thread for sequential output
942 thread_par->mutJobMutex->lock();
943 cout << "nica-scheduler$ Subtask " << thread_counter << ":" << subthread_counter << " is running:" << endl
944 << "input - " << in_file << endl
945 << "output - " << (out_file.isNull() ? "<no output>" : out_file.GetValue()) << endl
946 << "start event - " << start_event << endl
947 << "event count - " << event_count << endl
948 << endl;
949 thread_par->mutJobMutex->unlock();
950
951 // run bash file with the job in the sub-thread
952 int system_return = system(ssRunCommand.str().c_str());
953 if (system_return != 0)
954 cout << "nica-scheduler$ WARNING: System call (in SubThreadProcessFile) returned non-zero code: "
955 << system_return << endl;
956
957 // delete temporary bash file
958 if (!isDebugMode) {
959 stringstream ssDeleteCommand;
960 ssDeleteCommand << "rm -rf " << ssBashFile.str();
961 system_return = system(ssDeleteCommand.str().c_str());
962 if (system_return != 0)
963 cout << "nica-scheduler$ WARNING: System call (in SubThreadProcessFile) returned non-zero code: "
964 << system_return << endl;
965 } else
966 cout << "DEBUG nica-scheduler$ '" << ssBashFile.str() << "' bash file was used " << endl;
967
968 thread_par->semJobSemaphore->release();
969}
970
971/* MAIN THREAD for LOCAL execution */
972void ThreadLocalProcess(shared_ptr<structThreadPar> thread_par)
973{
974 // initialization of variables
975 shared_ptr<structJobPar> job_par = thread_par->sptrJobParameter;
976 int thread_counter = thread_par->iThreadCounter;
977
978 // cycle for all elements of the job stack
979 int macro_counter = 0;
980 for (size_t ind_stack = 0; ind_stack < job_par->vecJobStack.size(); ind_stack++) {
981 shared_ptr<structJobStackPar> cur_stack = job_par->vecJobStack[ind_stack];
982
983 // if the stack element is a command line, execute it before the next macro
984 if (cur_stack->iStackType == 1) {
985 RunLocalCommandLine(thread_par, cur_stack->nstrCommandline.GetValue(), ind_stack + 1);
986 continue;
987 }
988
989 shared_ptr<structMacroPar> cur_macro = cur_stack->sptrMacroParameter;
990 macro_counter++;
991
992 // get current file for the current thread
993 structFilePar* cur_file = nullptr;
994 if (cur_macro->vecFiles.size() > 0)
995 cur_file = cur_macro->vecFiles[thread_counter - 1].get();
996
997 // if macro use no input files, then add only additional arguments if exist
998 if (cur_file == nullptr) {
999 // generate ROOT command from the job parameters without input and output files
1000 stringstream ssROOTCommand;
1001 ssROOTCommand << job_par->strConfigPath << "root -b -q '" << cur_macro->nstrMacroPath.GetValue() << "(";
1002 if (cur_macro->strAddArgs != "")
1003 ssROOTCommand << cur_macro->strAddArgs;
1004 if (job_par->nstrLogs != nullptr)
1005 ssROOTCommand << ")' > " << job_par->nstrLogs.GetValue() << "_" << to_string(thread_counter) << " 2>&1";
1006 else
1007 ssROOTCommand << ")'";
1008
1009 // display local command in DEBUG mode for processing without input files
1010 if (isDebugMode)
1011 cout << "DEBUG nica-scheduler$ Local ROOT command = " << ssROOTCommand.str() << endl;
1012
1013 // write and execute temporary bash file
1014 ofstream myfile;
1015 long int t = time(nullptr);
1016 // generate output name for temporary bash file
1017 stringstream ssBashFile;
1018 ssBashFile << "temp_" << to_string(thread_counter) << "_" << to_string(t) << ".sh";
1019 // open bash file and run the ROOT macro command
1020 myfile.open(ssBashFile.str().c_str());
1021 myfile << "export ROOT_HIST=0 " << endl;
1022 myfile << ssROOTCommand.str();
1023 myfile.close();
1024 stringstream ssRunCommand;
1025 ssRunCommand << "bash " << ssBashFile.str();
1026
1027 // lock thread for sequential output
1028 thread_par->mutJobMutex->lock();
1029 cout << "nica-scheduler$ Task " << macro_counter << " is running..." << endl << endl;
1030 thread_par->mutJobMutex->unlock();
1031
1032 // run bash file with the job in the sub-thread
1033 int system_return = system(ssRunCommand.str().c_str());
1034 if (system_return != 0)
1035 cout << "nica-scheduler$ WARNING: System call (in ThreadLocalProcess) returned non-zero code: "
1036 << system_return << endl;
1037
1038 // delete temporary bash file
1039 if (!isDebugMode) {
1040 stringstream ssDeleteCommand;
1041 ssDeleteCommand << "rm -rf " << ssBashFile.str();
1042 system_return = system(ssDeleteCommand.str().c_str());
1043 if (system_return != 0)
1044 cout << "nica-scheduler$ WARNING: System call (in ThreadLocalProcess) returned non-zero code: "
1045 << system_return << endl;
1046 } else
1047 cout << "DEBUG nica-scheduler$ '" << ssBashFile.str() << "' bash file was used " << endl;
1048
1049 } // if (cur_file == nullptr)
1050 // if there are input files for the macro
1051 else {
1052 // strRealInputFile - input file path for ROOT macro (for intermediate copy case)
1053 string strRealInputFile = cur_file->strFileIn;
1054 // strRealOutputFile - output file path for ROOT macro (for intermediate copy case)
1055 nullString nstrRealOutputFile = cur_file->nstrFileOut;
1056
1057 // copy file to the temporary location if it is set
1058 if (cur_file->strPutPath != "") {
1059 stringstream ssCopyCommand;
1060 // create the temporary directory if it does not exist
1061 ssCopyCommand << "mkdir -p \"" << get_directory_path(cur_file->strPutPath) << "\"; "
1062 << cur_file->strPutCommand << " " << cur_file->strFileIn << " " << cur_file->strPutPath;
1063 int system_return = system(ssCopyCommand.str().c_str());
1064 if (system_return != 0)
1065 cout << "nica-scheduler$ WARNING: System call (in ThreadLocalProcess) returned non-zero code: "
1066 << system_return << endl;
1067
1068 strRealInputFile = cur_file->strPutPath;
1069 }
1070 // assign real output file on the intermediate storage
1071 if (cur_file->strGetPath != "")
1072 nstrRealOutputFile.SetValue(cur_file->strGetPath);
1073
1074 // get start event for the current file
1075 int* cur_start_event = cur_macro->ptrStartEvent.get();
1076 if (cur_file->ptrStartEvent != nullptr)
1077 cur_start_event = cur_file->ptrStartEvent.get();
1078
1079 // get event number for the current file
1080 int* cur_event_count = cur_macro->ptrEventCount.get();
1081 if (cur_file->ptrEventCount != nullptr)
1082 cur_event_count = cur_file->ptrEventCount.get();
1083
1084 // if parallel mode and start event is skipped then assign it to 0
1085 if ((cur_file->iParallelMode > 1) && (cur_start_event == nullptr)) {
1086 cur_file->ptrStartEvent = make_unique<int>(0);
1087 cur_start_event = cur_file->ptrStartEvent.get();
1088 cout << "nica-scheduler$ WARNING: parallel_mode is used, but start event is not set, so it is assigned "
1089 "to 0"
1090 << endl;
1091 }
1092 // if parallel mode and event coun is skipped then assign it to 0
1093 if ((cur_file->iParallelMode > 1) && (cur_event_count == nullptr)) {
1094 cur_file->ptrEventCount = make_unique<int>(0);
1095 cur_event_count = cur_file->ptrEventCount.get();
1096 cout << "nica-scheduler$ WARNING: parallel_mode is used, but event count is not set, so it is assigned "
1097 "to 0"
1098 << endl;
1099 }
1100
1101 // reset parallel mode if start event or event count is not set
1102 if ((cur_file->iParallelMode > 1) && ((cur_start_event == nullptr) || (cur_event_count == nullptr))) {
1103 cur_file->iParallelMode = 1;
1104 cout << "nica-scheduler$ WARNING: parallel_mode must be used with 'start_event' and 'event_count' "
1105 "parameters, parallel_mode is ignored!"
1106 << endl;
1107 }
1108
1109 int real_thread_count = 1;
1110 // concatenation of output file paths to wait for
1111 string strOutputUnion = nstrRealOutputFile.GetValue();
1112
1113 // variables to combine parallel mode > 1 amd = 1 in one cycle
1114 int start_event = -1, event_count = -1;
1115 if (cur_start_event != nullptr)
1116 start_event = *cur_start_event;
1117 if (cur_event_count != nullptr)
1118 event_count = *cur_event_count;
1119
1120 // if parallel mode and event count is 0, then trying to get event number from the input file
1121 if ((cur_file->iParallelMode > 1) && (event_count == 0)) {
1122 string config_paths = job_par->strConfigPath;
1123 // send output of the configuration files to /dev/null if no log file is set
1124 if (job_par->nstrLogs == nullptr)
1125 replace_string_in_text(config_paths, ";", " > /dev/null 2>&1;");
1126 stringstream ssEventNumberCommand;
1127 ssEventNumberCommand << config_paths << "show_event_count auto \"" << strRealInputFile
1128 << "\" 2> /dev/null";
1129 if (isDebugMode)
1130 cout << "DEBUG nica-scheduler$ trying to get event count from the input file: "
1131 << ssEventNumberCommand.str() << endl;
1132
1133 array<char, 128> bufferCommand;
1134 string str_event_number = "";
1135 FILE* stream = popen(ssEventNumberCommand.str().c_str(), "r");
1136 while (fgets(bufferCommand.data(), static_cast<int>(bufferCommand.size()), stream) != nullptr)
1137 str_event_number += bufferCommand.data();
1138 pclose(stream);
1139 str_event_number = find_first_number(str_event_number);
1140 if (str_event_number == "") {
1141 info_message(TString::Format("ERROR nica-scheduler$ parallel_mode is used, but event_count is not "
1142 "set and cannot be automatically defined. "
1143 "'%s' file will be skipped!",
1144 strRealInputFile.c_str())
1145 .Data(),
1146 1);
1147 break;
1148 }
1149 event_count = atoi(str_event_number.c_str());
1150 if (isDebugMode)
1151 cout << "DEBUG nica-scheduler$ " << event_count << " event(s) found in the input file" << endl;
1152 }
1153
1154 // if parallel mode and start event is 0, then trying to get start event from the input file
1155 vector<nullString> vecPartialOutputFiles;
1156 /***********************************************************************/
1157 /* if parallel mode > 1 - parallel local processing for one input file */
1158 /***********************************************************************/
1159 if (cur_file->iParallelMode > 1) {
1160 // create sub-threads to additionally parallelize by events in case of 'parallel_mode'
1161 vector<thread> vecSubThreads;
1162
1163 // fill sub-threads parameters and run them to parallelize by events
1164 for (int i = 0; i < cur_file->iParallelMode; i++) {
1165 int event_per_thread = (event_count + i) / cur_file->iParallelMode;
1166 if (event_per_thread != 0) {
1167 nullString nstr_output_file = nstrRealOutputFile;
1168 // generate output file name for particular processing by events
1169 if (nstr_output_file != nullptr)
1170 nstr_output_file.SetValue(
1171 GenerateOutputFilePath(nstr_output_file.GetValue(), real_thread_count));
1172
1173 // fill parameters for the current sub-thread (with the current event portion)
1174 shared_ptr<structSubThreadPar> subthread_par = make_shared<structSubThreadPar>();
1175 subthread_par->sptrThreadParameter = thread_par;
1176 subthread_par->sptrMacroParameter = cur_macro;
1177 subthread_par->iStartEvent = start_event;
1178 subthread_par->iEventCount = event_per_thread;
1179 subthread_par->strFileIn = strRealInputFile;
1180 subthread_par->nstrFileOut = nstr_output_file;
1181 subthread_par->iSubthreadCounter = i + 1;
1182
1183 // release the semaphore for the current thread to organize parallelization in sub-threads
1184 if (i == 0)
1185 thread_par->semJobSemaphore->release();
1186
1187 thread_par->semJobSemaphore->acquire();
1188
1189 // create sub-threads to parallelize one file processing by events
1190 try {
1191 vecSubThreads.emplace_back(SubThreadProcessFile, subthread_par);
1192 } catch (const system_error& e) {
1193 info_message((TString::Format("ERROR nica-scheduler$ error while creating sub-thread with "
1194 "code = %d and message = %s",
1195 e.code().value(), e.what()))
1196 .Data(),
1197 1);
1198 exit(-1);
1199 }
1200
1201 if (nstr_output_file != nullptr) {
1202 strOutputUnion += " " + nstr_output_file.GetValue();
1203 vecPartialOutputFiles.push_back(nstr_output_file);
1204 }
1205
1206 real_thread_count++;
1207 } // if (event_per_thread != 0){
1208
1209 start_event = start_event + event_per_thread;
1210 } // for (int i = 0; i < cur_file->iParallelMode; i++)
1211
1212 // waiting for finishing of the child threads
1213 if (isDebugMode)
1214 cout << "nica-scheduler$ Waiting for " << real_thread_count << " subtask(s) to finish..." << endl;
1215 for (auto& sub_thread : vecSubThreads) {
1216 try {
1217 sub_thread.join();
1218 } catch (const system_error& e) {
1220 (TString::Format("ERROR nica-scheduler$ Sub-thread failed with code = %d and message = %s",
1221 e.code().value(), e.what()))
1222 .Data(),
1223 1);
1224 }
1225 }
1226 cout << "nica-scheduler$ Subtasks " << macro_counter << ":[] has been finished" << endl;
1227 thread_par->semJobSemaphore->acquire();
1228
1229 // merge result files or write to single TChain object
1230 // iMerge: 0 - save result TChain; 1 - merge to the origin file name and delete partial files;
1231 // 2 - merge to the origin file name and NOT delete partial files
1232 if ((cur_file->iMerge >= 0) && (strOutputUnion != "")) {
1233 string UNIONc_path = get_app_dir_linux() + "merge.C";
1234
1235 // form command for merging result files
1236 stringstream ssMergeCommand;
1237 ssMergeCommand << job_par->strConfigPath << "root -b -q '" << UNIONc_path << "(\"" << strOutputUnion
1238 << "\", " << to_string(cur_file->iMerge) << ")'";
1239
1240 // save merging log to file if it is set
1241 if (job_par->nstrLogs != nullptr)
1242 ssMergeCommand << " >> " << job_par->nstrLogs.GetValue() << " 2>&1";
1243
1244 // run merge_result.C macro in bash
1245 ofstream myfile;
1246 long int t = time(nullptr);
1247 stringstream ssBashFile;
1248 ssBashFile << "temp_union_" << to_string(t) << ".sh";
1249 myfile.open(ssBashFile.str().c_str());
1250 myfile << ssMergeCommand.str();
1251 myfile.close();
1252 stringstream ssBashCommand;
1253 ssBashCommand << "bash " << ssBashFile.str().c_str();
1254
1255 cout << endl
1256 << "nica-scheduler$ Merging the result of subtasks " << macro_counter << ":[]..." << endl
1257 << endl;
1258 int system_return = system(ssBashCommand.str().c_str());
1259 if (system_return != 0)
1260 cout << "nica-scheduler$ WARNING: System call (in ThreadLocalProcess) returned non-zero code: "
1261 << system_return << endl;
1262
1263 // delete temporary bash file
1264 if (!isDebugMode) {
1265 stringstream ssRemoveCommand;
1266 ssRemoveCommand << "rm -rf " << ssBashFile.str();
1267 system_return = system(ssRemoveCommand.str().c_str());
1268 if (system_return != 0)
1269 cout << "nica-scheduler$ WARNING: System call (in ThreadLocalProcess) returned non-zero "
1270 "code: "
1271 << system_return << endl;
1272 } else
1273 cout << "DEBUG nica-scheduler$ '" << ssBashFile.str() << "' bash file was used " << endl;
1274 } // if - merge result files
1275 } // if (parallel_mode > 1)
1276 /************************/
1277 /* if parallel mode = 1 */
1278 /************************/
1279 else {
1280 // using the parameters to generate job string to be executed
1281 stringstream ssROOTCommand;
1282 ssROOTCommand << job_par->strConfigPath << "root -b -q '" << cur_macro->nstrMacroPath.GetValue() << "(";
1283
1284 // first macro argument - input file
1285 bool is_first = true;
1286 if (strRealInputFile != "") {
1287 ssROOTCommand << "\"" << strRealInputFile << "\"";
1288 is_first = false;
1289 }
1290 // second macro argument - output file
1291 if (nstrRealOutputFile != nullptr) {
1292 if (!is_first)
1293 ssROOTCommand << ", ";
1294 else
1295 is_first = false;
1296 ssROOTCommand << "\"" << nstrRealOutputFile.GetValue() << "\"";
1297 }
1298 // third macro argument - start event number
1299 if (cur_start_event != nullptr) {
1300 if (!is_first)
1301 ssROOTCommand << ", ";
1302 else
1303 is_first = false;
1304 ssROOTCommand << to_string(*cur_start_event);
1305 }
1306 // fourth macro argument - event count
1307 if (cur_event_count != nullptr) {
1308 if (!is_first)
1309 ssROOTCommand << ", ";
1310 else
1311 is_first = false;
1312 ssROOTCommand << to_string(*cur_event_count);
1313 }
1314 // then add other arguments to the macro
1315 if (cur_macro->strAddArgs != "") {
1316 if (!is_first)
1317 ssROOTCommand << ", ";
1318 else
1319 is_first = false;
1320 ssROOTCommand << cur_macro->strAddArgs;
1321 }
1322
1323 // end of the command with writing to the log or not
1324 if (job_par->nstrLogs != nullptr)
1325 ssROOTCommand << ")' > " << job_par->nstrLogs.GetValue() << "_" << to_string(thread_counter)
1326 << " 2>&1";
1327 else
1328 ssROOTCommand << ")'";
1329
1330 // display local ROOT command in debug mode for processing with input files
1331 if (isDebugMode)
1332 cout << "DEBUG nica-scheduler$ Local command = " << ssROOTCommand.str() << endl;
1333
1334 // write and execute temporary bash file
1335 ofstream myfile;
1336 long int t = time(nullptr);
1337 // generate output name for temporary bash file
1338 stringstream ssBashFile;
1339 ssBashFile << "temp_" << to_string(thread_counter) << "_" << "_" << to_string(t) << ".sh";
1340 // open bash file and run the ROOT macro command
1341 myfile.open(ssBashFile.str().c_str());
1342 myfile << "export ROOT_HIST=0 " << endl;
1343 myfile << ssROOTCommand.str();
1344 myfile.close();
1345 stringstream ssRunCommand;
1346 ssRunCommand << "bash " << ssBashFile.str();
1347
1348 // Create output directory and check for failure
1349 if (nstrRealOutputFile != nullptr) {
1350 string outDir = get_directory_path(nstrRealOutputFile.GetValue());
1351 if (!outDir.empty()) {
1352 string mkdirCmd = "mkdir -p \"" + outDir + "\"";
1353 int ret = system(mkdirCmd.c_str());
1354 if (ret != 0) {
1355 thread_par->mutJobMutex->lock();
1356 info_message("ERROR nica-scheduler$ Failed to create output directory for task "
1357 + to_string(thread_counter) + ": " + outDir,
1358 1);
1359 thread_par->mutJobMutex->unlock();
1360
1361 // Remove temporary script file
1362 if (!isDebugMode) {
1363 string rmCmd = "rm -f \"" + ssBashFile.str() + "\"";
1364 system(rmCmd.c_str());
1365 }
1366
1367 thread_par->semJobSemaphore->release();
1368 return;
1369 }
1370 }
1371 }
1372
1373 // lock thread for sequential output
1374 thread_par->mutJobMutex->lock();
1375 cout << "nica-scheduler$ Task " << thread_counter << " is running..." << endl;
1376 if (strRealInputFile != "")
1377 cout << "input data - " << strRealInputFile << endl;
1378 if (nstrRealOutputFile != nullptr)
1379 cout << "output data - " << nstrRealOutputFile.GetValue() << endl;
1380 cout << endl;
1381 thread_par->mutJobMutex->unlock();
1382
1383 // run bash file with the job in the sub-thread
1384 int system_return = system(ssRunCommand.str().c_str());
1385 if (system_return != 0)
1386 cout << "nica-scheduler$ WARNING: System call (in ThreadLocalProcess) returned non-zero code: "
1387 << system_return << endl;
1388
1389 // delete temporary bash file
1390 if (!isDebugMode) {
1391 stringstream ssRemoveCommand;
1392 ssRemoveCommand << "rm -rf " << ssBashFile.str();
1393 system_return = system(ssRemoveCommand.str().c_str());
1394 if (system_return != 0)
1395 cout << "nica-scheduler$ WARNING: System call (in ThreadLocalProcess) returned non-zero code: "
1396 << system_return << endl;
1397 } else
1398 cout << "DEBUG nica-scheduler$ '" << ssBashFile.str() << "' bash file was used " << endl;
1399 } // else (parallel_mode = 1)
1400
1401 // copy file (or files in case of parallel_mode > 1) from the temporary location to the final output path
1402 bool isGetCopyError = false;
1403 if ((cur_file->strGetPath != "") && (cur_file->nstrFileOut != nullptr)) {
1404 string strFinalOutputDir = get_directory_path(cur_file->nstrFileOut.GetValue());
1405 // if the output path is a bare file name, copy to the current directory
1406 if (strFinalOutputDir == "")
1407 strFinalOutputDir = ".";
1408
1409 stringstream ssCopyCommand;
1410 // create the final output directory if it does not exist
1411 ssCopyCommand << "mkdir -p \"" << strFinalOutputDir << "\"; " << cur_file->strGetCommand << " "
1412 << cur_file->strGetPath << " " << cur_file->nstrFileOut.GetValue();
1413 int system_return = system(ssCopyCommand.str().c_str());
1414 if (system_return != 0) {
1415 isGetCopyError = true;
1416 cout << "nica-scheduler$ WARNING: System call (in ThreadLocalProcess) returned non-zero code: "
1417 << system_return << endl;
1418 }
1419
1420 // also copy partial result files for parallel_mode > 1
1421 if ((cur_file->iParallelMode > 1) && ((cur_file->iMerge == 0) || (cur_file->iMerge == 2))) {
1422 for (nullString& cur_partial_file : vecPartialOutputFiles) {
1423 stringstream ssCopyPartialsCommand;
1424 ssCopyPartialsCommand
1425 << cur_file->strGetCommand << " " << cur_partial_file.GetValue() << " "
1426 << strFinalOutputDir + string("/") + get_file_name_with_ext(cur_partial_file.GetValue());
1427 system_return = system(ssCopyPartialsCommand.str().c_str());
1428 if (system_return != 0) {
1429 isGetCopyError = true;
1430 cout << "nica-scheduler$ WARNING: System call (in ThreadLocalProcess) returned non-zero "
1431 "code: "
1432 << system_return << endl;
1433 }
1434 }
1435 } // if iParallelMode > 1 && iMerge = 0,2 then copy all partial result files
1436
1437 // also copy extra result files requested by the <get extras=...> option
1438 CopyExtraGetFiles(cur_file);
1439 } // copy file from the temporary location to the output path
1440
1441 // remove the temporary input files from <put>
1442 if (cur_file->strPutPath != "") {
1443 stringstream ssRemoveCommand;
1444 ssRemoveCommand << "rm -rf " << cur_file->strPutPath;
1445 int system_return = system(ssRemoveCommand.str().c_str());
1446 if (system_return != 0)
1447 cout << "nica-scheduler$ WARNING: System call (in ThreadLocalProcess) returned non-zero code: "
1448 << system_return << endl;
1449 }
1450 // remove the temporary output files from <get> (only if the results have been copied successfully)
1451 if ((cur_file->strGetPath != "") && isGetCopyError)
1452 cout << "nica-scheduler$ ERROR: copying of the results (GET) failed, "
1453 "temporary output files were preserved"
1454 << endl;
1455 if ((cur_file->strGetPath != "") && !isGetCopyError) {
1456 stringstream ssRemoveCommand;
1457 ssRemoveCommand << "rm -rf " << cur_file->strGetPath;
1458 int system_return = system(ssRemoveCommand.str().c_str());
1459 if (system_return != 0)
1460 cout << "nica-scheduler$ WARNING: System call (in ThreadLocalProcess) returned non-zero code: "
1461 << system_return << endl;
1462
1463 // remove partial result files in case of the parallel mode for by-event processing
1464 if ((cur_file->iParallelMode > 1) && ((cur_file->iMerge == 0) || (cur_file->iMerge == 2))) {
1465 for (nullString& cur_partial_file : vecPartialOutputFiles) {
1466 stringstream ssRemovePartialCommand;
1467 ssRemovePartialCommand << "rm -rf " << cur_partial_file.GetValue();
1468 system_return = system(ssRemovePartialCommand.str().c_str());
1469 if (system_return != 0)
1470 cout << "nica-scheduler$ WARNING: System call (in ThreadLocalProcess) returned non-zero "
1471 "code: "
1472 << system_return << endl;
1473 }
1474 } // if iParallelMode > 1 && iMerge = 0,2 then delete all partial result files
1475
1476 // // remove extra temporary result files requested by the <get extras=...> option
1477 // for (const string& cur_extra_path : cur_file->vecGetExtraPath)
1478 // {
1479 // stringstream ssRemoveExtraCommand;
1480 // ssRemoveExtraCommand << "rm -rf " << cur_extra_path;
1481
1482 // system_return = system(ssRemoveExtraCommand.str().c_str());
1483 // if (system_return != 0)
1484 // cout << "nica-scheduler$ WARNING: System call (in ThreadLocalProcess) returned non-zero code:
1485 // "
1486 // << system_return << endl;
1487 // }
1488 } // if (cur_file->strGetPath != "")
1489
1490 // remove the temporary files from <clean>
1491 if (!cur_file->vecCleanPath.empty()) {
1492 stringstream ssRemoveCommand;
1493 for (const string& cur_clean_path : cur_file->vecCleanPath)
1494 ssRemoveCommand << "rm -rf " << cur_clean_path << "; ";
1495 int system_return = system(ssRemoveCommand.str().c_str());
1496 if (system_return != 0)
1497 cout << "nica-scheduler$ WARNING: System call (in ThreadLocalProcess) returned non-zero code: "
1498 << system_return << endl;
1499 }
1500
1501 if (cur_file->iParallelMode == 1)
1502 cout << "nica-scheduler$ Task " << thread_counter << " has been finished" << endl;
1503 } // if (cur_file != nullptr)
1504 } // for (size_t ind_macro = 0; ind_macro < job_par->vecMacros.size(); ind_macro++)
1505
1506 thread_par->semJobSemaphore->release();
1507}
1508
1509// options for running the NICA-Scheduler
1510static struct option long_options[] = {{"help", no_argument, 0, 'h'}, {"file", required_argument, 0, 'f'},
1511 {"debug", no_argument, 0, 'd'}, {"db", required_argument, 0, 0},
1512 {"database", required_argument, 0, 0}, {0, 0, 0, 0}};
1513
1514// help message for the NICA-Scheduler
1516{
1517 cout << "Usage: nica-scheduler job_description.xml [options]" << endl
1518 << endl
1519 << "Options:" << endl
1520 << " -h/--help : produce help message" << endl
1521 << " -f/--file arg : set XML file path with a job description" << endl
1522 << " -d/--debug : debug mode with detailed output" << endl
1523 << " --db/--database arg : predefined settings for the Condition Database (now 'bmn' supported only)"
1524 << endl
1525 << endl;
1526}
1527
1528/* Main Function of the NICA Scheduler*/
1529int main(int argc, char** argv)
1530{
1531 // if no necessary arguments are given for the NICA-Scheduler
1532 if (argc < 2) {
1534 info_message("ERROR nica-scheduler$ XML file path (containing job description) must be set", 1);
1535 return 1;
1536 }
1537
1538 // parsing command line arguments
1539 string xml_file_path = "", exp_database = "";
1540 int option_index = 0, c;
1541 while ((c = getopt_long(argc, argv, "hf:d", long_options, &option_index)) != -1) {
1542 switch (c) {
1543 case 0: {
1544 if (long_options[option_index].flag != 0)
1545 break;
1546 printf("option %s", long_options[option_index].name);
1547 if (optarg)
1548 printf("with arg %s", optarg);
1549 printf("\n");
1550
1551 if (strcmp(long_options[option_index].name, "db") == 0)
1552 exp_database = optarg;
1553 if (strcmp(long_options[option_index].name, "database") == 0)
1554 exp_database = optarg;
1555 break;
1556 }
1557 case 'h':
1559 return 0;
1560 case 'f':
1561 xml_file_path = optarg;
1562 break;
1563 case 'd':
1564 isDebugMode = true;
1565 break;
1566 case '?':
1567 /* getopt_long already printed an error message. */
1568 break;
1569 }
1570 }
1571 // remaining command line arguments (not options)
1572 if (optind < argc) {
1573 if (((argc - optind) == 1) && (xml_file_path == ""))
1574 xml_file_path = argv[optind];
1575 else {
1576 info_message("ERROR nica-scheduler$ Argument list is incorrect.\nPlease, check information on command line "
1577 "arguments in the 'README.md' file",
1578 1);
1579 return 1;
1580 }
1581 }
1582
1583 // check that XML job file is not empty
1584 if (xml_file_path == "") {
1585 info_message("ERROR nica-scheduler$ XML file path (containing job description) must be set", 1);
1586 return 1;
1587 }
1588
1589 // high means the high priority value of the parameter (a console parameter has the highest priority for the
1590 // settings)
1591 bool isDBSettingsHigh = false;
1592 struct_database_settings dbSettingsPre;
1593 if (exp_database != "") {
1594 auto iter_db_settings = predefined_database_settings.find(exp_database);
1595 if (iter_db_settings == predefined_database_settings.end()) {
1596 info_message((TString::Format("ERROR nica-scheduler$ Predefined database settings were not found for '%s'",
1597 exp_database.c_str()))
1598 .Data(),
1599 1);
1600 return 1;
1601 } else {
1602 dbSettingsPre = iter_db_settings->second;
1603 isDBSettingsHigh = true;
1604 }
1605 }
1606
1607 // obtain pointer to XML document for a given input file path
1608 xmlDocPtr doc = xmlReadFile(xml_file_path.c_str(), nullptr, 0);
1609 if (doc == nullptr) {
1611 (TString::Format("ERROR nica-scheduler$ Failed to open input XML file: '%s'", xml_file_path.c_str()))
1612 .Data(),
1613 1);
1614 return 2;
1615 }
1616
1617 // get root element of the XML job description
1618 xmlNodePtr root = xmlDocGetRootElement(doc);
1619
1620 // map with Job Name -> ID
1621 multimap<string, string> mapJobName2ID;
1622 // map with Job Name -> Output List
1623 multimap<string, unique_ptr<vector<nullString>>> mapJobName2FileOut;
1624
1625 // path to executable's directory ('/' - last char)
1626 string exe_dir = get_app_dir_linux();
1627
1628 // initialization before job cycle
1629 srand(time(nullptr));
1630 BATCH_SYSTEM_NAME batch_system = SLURM_BATCH_SYSTEM; // default value: SLURM batch system
1631
1632 // cycle for all jobs in the XML description
1633 xmlNodePtr cur_node = root;
1634 while (cur_node) {
1635 // if tag is XML_ELEMENT_NODE then continue
1636 if (cur_node->type == XML_ELEMENT_NODE) {
1637 char* pc_temp;
1638 int i_temp;
1639 // JOBS TAG (array of jobs) then step down and continue
1640 if (strcmp((char*)cur_node->name, "jobs") == 0) {
1641 // read BATCH system name in the XML description
1642 pc_temp = (char*)xmlGetProp(cur_node, (unsigned char*)"batch");
1643 if (pc_temp != nullptr) {
1644 char* pc_batch_system = convert_pchar_to_lowercase_new(pc_temp);
1645 if (strcmp(pc_batch_system, "sge") == 0)
1646 batch_system = SGE_BATCH_SYSTEM;
1647 else {
1648 if (strcmp(pc_batch_system, "torque") == 0)
1649 batch_system = TORQUE_BATCH_SYSTEM;
1650 }
1651 free(pc_temp);
1652 free(pc_batch_system);
1653 }
1654 // read UniConDa database settings in the XML description
1655 pc_temp = (char*)xmlGetProp(cur_node, (unsigned char*)"database");
1656 if ((pc_temp != nullptr) && (!isDBSettingsHigh)) {
1657 auto iter_db_settings = predefined_database_settings.find(pc_temp);
1658 if (iter_db_settings == predefined_database_settings.end())
1659 info_message((TString::Format("WARNING nica-scheduler$ Predefined database settings were not "
1660 "found for '%s'. The parameter will be ignored.",
1661 pc_temp))
1662 .Data(),
1663 1);
1664 else
1665 dbSettingsPre = iter_db_settings->second;
1666 free(pc_temp);
1667 }
1668
1669 cur_node = cur_node->children;
1670 }
1671
1672 // JOB TAG
1673 if (strcmp((char*)cur_node->name, "job") == 0) {
1674 bool isError = false;
1675 // bool isPuts = false, isGets = false, isCleans = false;
1676 shared_ptr<structJobPar> job_par = make_shared<structJobPar>();
1677
1678 // read Job Name in the XML description
1679 pc_temp = (char*)xmlGetProp(cur_node, (unsigned char*)"name");
1680 if (pc_temp != nullptr) {
1681 job_par->strJobName = pc_temp;
1682 free(pc_temp);
1683 }
1684
1685 struct_database_settings dbSettings(dbSettingsPre);
1686 // read UniConDa database settings in the XML description
1687 pc_temp = (char*)xmlGetProp(cur_node, (unsigned char*)"database");
1688 if ((pc_temp != nullptr) && (!isDBSettingsHigh)) {
1689 auto iter_db_settings = predefined_database_settings.find(pc_temp);
1690 if (iter_db_settings == predefined_database_settings.end())
1691 info_message((TString::Format("WARNING nica-scheduler$ Predefined database settings were not "
1692 "found for '%s'. The parameter will be ignored.",
1693 pc_temp))
1694 .Data(),
1695 1);
1696 else
1697 dbSettings = iter_db_settings->second;
1698 free(pc_temp);
1699 }
1700 if (dbSettings.db_name == "") {
1701 dbSettings.db_host = UNI_DB_HOST;
1702 dbSettings.db_name = UNI_DB_NAME;
1703 dbSettings.db_username = UNI_DB_USERNAME;
1704 dbSettings.db_password = UNI_DB_PASSWORD;
1705 }
1706 job_par->dbSettings = dbSettings;
1707
1708 // read BATCH system name in the XML description
1709 pc_temp = (char*)xmlGetProp(cur_node, (unsigned char*)"batch");
1710 if (pc_temp != nullptr) {
1711 char* pc_batch_system = convert_pchar_to_lowercase_new(pc_temp);
1712 if (strcmp(pc_batch_system, "slurm") == 0)
1713 batch_system = SLURM_BATCH_SYSTEM;
1714 else {
1715 if (strcmp(pc_batch_system, "torque") == 0)
1716 batch_system = TORQUE_BATCH_SYSTEM;
1717 }
1718 free(pc_temp);
1719 free(pc_batch_system);
1720 }
1721 batch_commands batch_com = mapBS[batch_system];
1722
1723 // fill BASH file content to be executed in the BATCH system
1724 // transferring individual parameters to the JOB ARRAY using DECLARE bash variable
1725 // DECLARE method can lead to the overflow of the content of the variables (mb better "CAT << EOF" files
1726 // method)
1727 string strBatchFile = batch_com.job_begin, strBatchInitInputFile = "declare -a InputFileArray=(",
1728 strBatchInitOutputFile = "declare -a OutputFileArray=(",
1729 strBatchInitStartEvent = "declare -a StartEventArray=(",
1730 strBatchInitEventCount = "declare -a EventCountArray=(",
1731 strBatchInitAddArgs = "declare -a AddArgsArray=(", strBatchInitPut = "declare -a PutArray=(",
1732 strBatchInitGet = "declare -a GetArray=(", strBatchInitClean = "declare -a CleanArray=(";
1733
1734 // read Job Dependency in the XML description
1735 pc_temp = (char*)xmlGetProp(cur_node, (unsigned char*)"dependency");
1736 if (pc_temp != nullptr) {
1737 multimap<string, string>::iterator it = mapJobName2ID.find(pc_temp);
1738 if (it == mapJobName2ID.end())
1739 cout << "nica-scheduler$ WARNING: the following dependency for '" << job_par->strJobName
1740 << "' job was not found: " << pc_temp << endl;
1741 else {
1742 // if the parent job was split into portions (by arraylimit), task-to-task correspondence
1743 // is not preserved, so wait for the completion of all its portions instead
1744 if (it->second.find(batch_com.job_dependency_separator) != string::npos)
1745 job_par->strDdependencyName = batch_com.job_dependency_option + it->second;
1746 else
1747 job_par->strDdependencyName = batch_com.array_dependency_option + it->second;
1748 }
1749 free(pc_temp);
1750 }
1751
1752 /* PARSING TAGS of the JOB */
1753 xmlNodePtr sub_node = cur_node->children;
1754 while (sub_node) {
1755 if (sub_node->type == XML_ELEMENT_NODE) {
1756 // COMMAND LINE TAG
1757 if (strcmp((char*)sub_node->name, "command") == 0) {
1758 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"line");
1759 if (pc_temp == nullptr) {
1760 isError = true;
1761 info_message("ERROR nica-scheduler$ 'line' attribute was not set for the command!", 1);
1762 break;
1763 }
1764
1765 shared_ptr<structJobStackPar> stack_par = make_shared<structJobStackPar>((string)pc_temp);
1766 job_par->vecJobStack.push_back(stack_par);
1767 free(pc_temp);
1768
1769 sub_node = sub_node->next;
1770 continue;
1771 }
1772 // MACRO NAME TAG
1773 if (strcmp((char*)sub_node->name, "macro") == 0) {
1774 shared_ptr<structMacroPar> macro_par = make_shared<structMacroPar>();
1775
1776 // read Macro Name in the XML description (used by 'macro_input' to link macros)
1777 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"name");
1778 macro_par->nstrMacroName.SetValue(pc_temp);
1779 free(pc_temp);
1780 // read Macro Path in the XML description
1781 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"path");
1782 macro_par->nstrMacroPath.SetValue(pc_temp);
1783 free(pc_temp);
1784 if (macro_par->nstrMacroPath == nullptr) {
1785 isError = true;
1787 "ERROR nica-scheduler$ Macro path was not set (please, add 'path' attribute)!", 1);
1788 break;
1789 }
1790
1791 // read Global Start Event value in the XML description
1792 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"start_event");
1793 if (pc_temp != nullptr) {
1794 macro_par->ptrStartEvent = make_unique<int>(atoi(pc_temp));
1795 free(pc_temp);
1796 }
1797
1798 // read Global Event Count value in the XML description
1799 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"event_count");
1800 if (pc_temp != nullptr) {
1801 if (!is_string_number(pc_temp)) {
1802 isError = true;
1803 info_message("ERROR nica-scheduler$ Event count must be a number!", 1);
1804 free(pc_temp);
1805 break;
1806 }
1807 macro_par->ptrEventCount = make_unique<int>(atoi(pc_temp));
1808 if (*macro_par->ptrEventCount < 0) {
1809 isError = true;
1810 info_message("ERROR nica-scheduler$ Event count must be a positive number or 0 "
1811 "(for all events)!",
1812 1);
1813 free(pc_temp);
1814 break;
1815 }
1816 free(pc_temp);
1817 }
1818
1819 // read Additional Arguments of the macro in the XML description
1820 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"add_args");
1821 if (pc_temp != nullptr) {
1822 macro_par->strAddArgs = pc_temp;
1823 free(pc_temp);
1824 // replace ''' symbols for '"'
1825 replace_string_in_text(macro_par->strAddArgs, "'", "\"");
1826 }
1827
1828 // read File List to process by the macro in the XML description
1829 if (ParseMacroFiles(sub_node->children, job_par, macro_par, mapJobName2FileOut, batch_com)
1830 == true)
1831 isError = true;
1832
1833 job_par->vecMacros.push_back(macro_par);
1834 job_par->vecJobStack.push_back(make_shared<structJobStackPar>(macro_par));
1835
1836 sub_node = sub_node->next;
1837 continue;
1838 }
1839
1840 // RUN TYPE TAG
1841 if (strcmp((char*)sub_node->name, "run") == 0) {
1842 // read Run Mode in the XML description: local, global or container
1843 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"mode");
1844 if (pc_temp != nullptr) {
1845 if (strcmp(pc_temp, "global") == 0)
1846 job_par->iRunMode = 1;
1847 else if (strcmp(pc_temp, "container") == 0)
1848 job_par->iRunMode = 2;
1849
1850 free(pc_temp);
1851 }
1852
1853 // read Processor Count to use in the XML description
1854 // it will be corrected according to count of subtasks
1855 job_par->nstrProcCount.SetValue((char*)xmlGetProp(sub_node, (unsigned char*)"count"));
1856
1857 // read Queue Limit in the XML description
1858 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"queuelimit");
1859 if (pc_temp != nullptr) {
1860 if (!is_string_number(pc_temp) || (atoi(pc_temp) <= 0)) {
1861 isError = true;
1862 info_message("ERROR nica-scheduler$ Queue limit must be a positive number!", 1);
1863 free(pc_temp);
1864 break;
1865 }
1866 job_par->iQueueLimit = atoi(pc_temp);
1867 free(pc_temp);
1868 }
1869
1870 // read Array Limit in the XML description
1871 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"arraylimit");
1872 if (pc_temp != nullptr) {
1873 if (!is_string_number(pc_temp) || (atoi(pc_temp) <= 0)) {
1874 isError = true;
1875 info_message("ERROR nica-scheduler$ Array limit must be a positive number!", 1);
1876 free(pc_temp);
1877 break;
1878 }
1879 job_par->iArrayLimit = atoi(pc_temp);
1880 free(pc_temp);
1881 }
1882
1883 // read Operative Memory required for 1 task
1884 // the RAM size is converted to MegaBytes
1885 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"memory1");
1886 if (pc_temp != nullptr) {
1887 i_temp = floor(byte_size_to_double(pc_temp, 'M') + 0.5);
1888 if (i_temp > 0)
1889 job_par->strOperativeMemory1 = int_to_string(i_temp);
1890 else
1891 cout << "nica-scheduler$ WARNING: 'memory1' field contains incorrect format, then "
1892 "it is ignored"
1893 << endl;
1894
1895 free(pc_temp);
1896 }
1897 // read Configuration File in the XML description
1898 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"config");
1899 if (pc_temp != nullptr) {
1900 job_par->strConfigPath = pc_temp;
1901 free(pc_temp);
1902 }
1903 // read Job Priority in the XML description
1904 job_par->nstrPriority.SetValue((char*)xmlGetProp(sub_node, (unsigned char*)"priority"));
1905 // read Log File Path in the XML description
1906 job_par->nstrLogs.SetValue((char*)xmlGetProp(sub_node, (unsigned char*)"logs"));
1907 // read Queue Name of the batch system in the XML description
1908 job_par->nstrQueue.SetValue((char*)xmlGetProp(sub_node, (unsigned char*)"queue"));
1909 // read Quality-Of-Service Name of the batch system in the XML description
1910 job_par->nstrQOS.SetValue((char*)xmlGetProp(sub_node, (unsigned char*)"qos"));
1911 // read Working Directory of the batch system in the XML description
1912 job_par->nstrWorkDir.SetValue((char*)xmlGetProp(sub_node, (unsigned char*)"work_dir"));
1913 // read Container OS in the XML description
1914 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"container_os");
1915 if (pc_temp != nullptr) {
1916 job_par->strContainerType = pc_temp;
1917 free(pc_temp);
1918 }
1919
1920 // read Host List, on which the job will be executed
1921 job_par->nstrHosts.SetValue((char*)xmlGetProp(sub_node, (unsigned char*)"hosts"));
1922 // change comma separator with a specific symbol corresponding to the batch system
1923 if ((job_par->nstrHosts != nullptr) && (batch_com.sheduler_hosts_separation != ','))
1924 replace(job_par->nstrHosts.GetValue().begin(), job_par->nstrHosts.GetValue().end(), ',',
1925 batch_com.sheduler_hosts_separation);
1926
1927 // read Core Dump Flag (whether write a core dump in case of errors)
1928 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"core_dump");
1929 if (pc_temp != nullptr) {
1930 if (strcmp(pc_temp, "true") == 0)
1931 job_par->isCoreDump = true;
1932 free(pc_temp);
1933 }
1934
1935 sub_node = sub_node->next;
1936 continue;
1937 }
1938 }
1939
1940 sub_node = sub_node->next;
1941 } // while PARSING TAGS
1942
1943 /*****************************************************/
1944 /* START MAIN EXECUTION AFTER PARSING XML PARAMETERS */
1945 /*****************************************************/
1946 // check that there are macros or commands to execute
1947 if (job_par->vecJobStack.size() == 0) {
1948 info_message("ERROR nica-scheduler$ No macros or commands in the job!", 1);
1949 isError = true;
1950 }
1951 // if errors are present while parsing parameters then continue
1952 if (isError) {
1953 info_message("ERROR nica-scheduler$ This job will be skipped because of errors above!", 1);
1954 cur_node = cur_node->next;
1955 continue;
1956 }
1957
1958 // store list with output file paths for the whole job (the last macro)
1959 unique_ptr<vector<nullString>> vecJobFileOut = make_unique<vector<nullString>>();
1960 // 1. define total processor count and fill 'parallel_mode' parameter for each file of the job
1961 // 2. save list of output files of the whole job (output of the last macro) to mapJobName2FileOut
1962 int globalParallelCount = 0;
1963
1964 // if the job contains only command lines, it should be submitted as one task
1965 if (job_par->vecMacros.size() == 0)
1966 globalParallelCount = 1;
1967
1968 bool isJobSkipped = false;
1969
1970 // cycle by macros of the job
1971 for (size_t ind_macro = 0; ind_macro < job_par->vecMacros.size(); ind_macro++) {
1972 int iTotalParallelCount = 0;
1973
1974 shared_ptr<structMacroPar> cur_macro = job_par->vecMacros[ind_macro];
1975
1976 // cycle by files of the current macro
1977 for (const auto& iter : cur_macro->vecFiles) {
1978 structFilePar* filePar = iter.get();
1979
1980 if (filePar->strParallelMode != "") {
1981 // if parallel mode is set to 0, than all cores are used
1982 if (filePar->strParallelMode == "0") {
1983 // in the local mode, all CPU cores are used
1984 if (job_par->iRunMode == 0)
1986 else {
1987 // in the global mode, all queue slots are used
1988 if (job_par->nstrQueue == nullptr)
1989 filePar->iParallelMode = get_batch_processor_count(batch_system);
1990 else
1991 filePar->iParallelMode =
1992 get_batch_processor_count(batch_system, job_par->nstrQueue.GetValue());
1993 }
1994
1995 // parallel mode is 1 by default (if incorrect values set)
1996 if (filePar->iParallelMode < 1)
1997 filePar->iParallelMode = 1;
1998 } else {
1999 filePar->iParallelMode = atoi(filePar->strParallelMode.c_str());
2000
2001 // parallel mode is 1 by default (if incorrect values set)
2002 if (filePar->iParallelMode < 1)
2003 filePar->iParallelMode = 1;
2004 }
2005 } else
2006 filePar->iParallelMode = 1;
2007
2008 // total processor count for all files of the macro
2009 iTotalParallelCount += filePar->iParallelMode;
2010
2011 // fill list with output file paths for the whole job (the last macro)
2012 if (ind_macro == (job_par->vecMacros.size() - 1))
2013 vecJobFileOut->push_back(filePar->nstrFileOut);
2014 } // for all files of the current macro
2015
2016 if ((ind_macro > 0) && (globalParallelCount != iTotalParallelCount)) {
2017 info_message("ERROR nica-scheduler$ The file count in macros of the job is not the same. This "
2018 "job will be skipped!",
2019 1);
2020 isJobSkipped = true;
2021 break;
2022 }
2023
2024 globalParallelCount = iTotalParallelCount;
2025 } // for job macros
2026
2027 if (isJobSkipped) {
2028 cur_node = cur_node->next;
2029 continue;
2030 }
2031
2032 // deleting vecJobFileOut -> do not use later
2033 mapJobName2FileOut.insert(
2034 pair<string, unique_ptr<vector<nullString>>>(job_par->strJobName, std::move(vecJobFileOut)));
2035
2036 // form command to source ('.' is alias) all configuration files separated by ',' or ';'
2037 // the result form: ". config1; . config2; "
2038 job_par->strConfigPath = trim(job_par->strConfigPath);
2039 if (job_par->strConfigPath != "") {
2040 replace_string_in_text(job_par->strConfigPath, ",", ";");
2041 if (job_par->strConfigPath[job_par->strConfigPath.length() - 1] == ';')
2042 job_par->strConfigPath = job_par->strConfigPath.substr(0, job_par->strConfigPath.length() - 1);
2043 replace_string_in_text(job_par->strConfigPath, ";", ";. ");
2044 job_par->strConfigPath = ". " + job_par->strConfigPath + "; ";
2045 // if log file is set then add redirection of the config output to the log file
2046 if (job_par->nstrLogs != nullptr)
2047 replace_string_in_text(job_par->strConfigPath, ";",
2048 (string) " > " + job_par->nstrLogs.GetValue() + (string) " 2>&1;");
2049 }
2050
2051 /*****************************************/
2052 /* GLOBAL SCHEDULING W/WO CONTAINER MODE */
2053 /*****************************************/
2054 FILE* stream = nullptr;
2055 // if not local run mode
2056 if (job_par->iRunMode > 0) {
2057 // if container mode, define container batch file
2058 string strApptainerRunFile = "#!/bin/bash\n\n";
2059 if (job_par->iRunMode == 2) {
2060 strApptainerRunFile += "echo \"Starting Apptainer Job\"\n";
2061 if (job_par->strConfigPath != "")
2062 strApptainerRunFile += job_par->strConfigPath + "\n\n";
2063 strApptainerRunFile += "RUN_CMD=\"root -q -b \"\n";
2064 strApptainerRunFile += "RUN_CMD+=\"${@}\"\n\n";
2065 if (isDebugMode)
2066 strApptainerRunFile +=
2067 "echo \"DEBUG nica-scheduler: ROOT command in container = $RUN_CMD\"\n";
2068 strApptainerRunFile += "eval $RUN_CMD\n";
2069 strApptainerRunFile += "exit 0\n";
2070 }
2071
2072 // define process count for the job
2073 int proc_count = 1;
2074 if (job_par->nstrProcCount != nullptr) {
2075 // if processor count was set to 0 then use all the available processors
2076 if ((job_par->nstrProcCount.GetValue()[0] == '0')
2077 && (job_par->nstrProcCount.GetValue().length() == 1))
2078 {
2079 if (job_par->nstrQueue == nullptr)
2080 proc_count = get_batch_processor_count(batch_system);
2081 else
2082 proc_count = get_batch_processor_count(batch_system, job_par->nstrQueue.GetValue());
2083
2084 if (proc_count == 0) {
2085 info_message("ERROR nica-scheduler$ Batch processors were not defined. This job will "
2086 "be skipped!",
2087 1);
2088 cur_node = cur_node->next;
2089 continue;
2090 }
2091
2092 cout << "nica-scheduler$ The batch queue has " << proc_count
2093 << " processor cores being available for user jobs." << endl;
2094 } else
2095 proc_count = atoi(job_par->nstrProcCount.GetValue().c_str());
2096 } else
2097 proc_count = globalParallelCount;
2098
2099 if (proc_count == 0) {
2101 "ERROR nica-scheduler$ Processor count can not be equal 0. This job will be skipped!", 1);
2102 cur_node = cur_node->next;
2103 continue;
2104 }
2105
2106 // dump or do not dump core in case of failures
2107 // 'ulimit -c 0' is not enough on the nodes where /proc/sys/kernel/core_pattern pipes the dump to a
2108 // helper program (apport, abrt-hook-ccpp), because the kernel ignores RLIMIT_CORE for a pipe
2109 if (job_par->isCoreDump)
2110 strBatchFile += "export ENABLE_CORE_DUMP=1\n\n";
2111 else
2112 strBatchFile += "ulimit -c 0\n\n";
2113
2114 // load configuration file for environment
2115 if ((job_par->iRunMode != 2) && (job_par->strConfigPath != ""))
2116 strBatchFile += job_par->strConfigPath + "\n\n";
2117
2118 // define priority of the job (default: 0)
2119 int iPriority = 0;
2120 if (job_par->nstrPriority != nullptr)
2121 iPriority = atoi(job_par->nstrPriority.GetValue().c_str());
2122
2123 // process count is assigned to input file count (taking parallel_mode in account) if greater
2124 if ((globalParallelCount > 0) && (proc_count > globalParallelCount))
2125 proc_count = globalParallelCount;
2126
2127 // store merge modes and partial outputs to organize merging for result files
2128 vector<int> vecMergeMode;
2129 vector<nullString> vecPartialOutputFiles;
2130
2131 // if there are files to process then write processing info to the special variables
2132 if (globalParallelCount > 0) {
2133 // cycle by macros of the job
2134 for (const auto& cur_macro : job_par->vecMacros) {
2135 // cycle by files of the current macro
2136 for (const auto& cur_file : cur_macro->vecFiles) {
2137 // input file path for the ROOT macro
2138 string strRealInputFile = cur_file->strFileIn;
2139 // output file path for the ROOT macro
2140 nullString nstrRealOutputFile = cur_file->nstrFileOut;
2141
2142 // in case of using a fast storage
2143 // set real input file path for the ROOT macro
2144 if (cur_file->strPutPath != "")
2145 strRealInputFile = cur_file->strPutPath;
2146 // in case of using a fast storage
2147 // set real output file path for the ROOT macro
2148 if (cur_file->strGetPath != "")
2149 nstrRealOutputFile.SetValue(cur_file->strGetPath);
2150
2151 // get start event for the current file
2152 int* cur_start_event = cur_macro->ptrStartEvent.get();
2153 if (cur_file->ptrStartEvent != nullptr)
2154 cur_start_event = cur_file->ptrStartEvent.get();
2155
2156 // get event count for the current file
2157 int* cur_event_count = cur_macro->ptrEventCount.get();
2158 if (cur_file->ptrEventCount != nullptr)
2159 cur_event_count = cur_file->ptrEventCount.get();
2160
2161 // if parallel mode and start event is skipped then assign it to 0
2162 if ((cur_file->iParallelMode > 1) && (cur_start_event == nullptr)) {
2163 cur_file->ptrStartEvent = make_unique<int>(0);
2164 cur_start_event = cur_file->ptrStartEvent.get();
2165 cout << "nica-scheduler$ WARNING: parallel_mode is used, but start event is not "
2166 "set, so it is assigned to 0"
2167 << endl;
2168 }
2169 // if parallel mode and event count is skipped then assign it to 0
2170 if ((cur_file->iParallelMode > 1) && (cur_event_count == nullptr)) {
2171 cur_file->ptrEventCount = make_unique<int>(0);
2172 cur_event_count = cur_file->ptrEventCount.get();
2173 cout << "nica-scheduler$ WARNING: parallel_mode is used, but event count is not "
2174 "set, so it is assigned to 0"
2175 << endl;
2176 }
2177
2178 // generate output name for output paths
2179 string strOutputUnion = nstrRealOutputFile.GetValue();
2180
2181 // variables to combine parallel mode > 1 amd = 1 in one cycle
2182 int start_event = -1, event_count = -1;
2183 if (cur_start_event != nullptr)
2184 start_event = *cur_start_event;
2185 if (cur_event_count != nullptr)
2186 event_count = *cur_event_count;
2187
2188 // if parallel mode and event count is 0
2189 // then trying to get event number from the input file
2190 if ((cur_file->iParallelMode > 1) && (event_count == 0)) {
2191 string config_paths = job_par->strConfigPath;
2192 // send output of the configuration files to /dev/null if no log file is set
2193 if (job_par->nstrLogs == nullptr)
2194 replace_string_in_text(config_paths, ";", " > /dev/null 2>&1;");
2195 stringstream ssEventNumberCommand;
2196 ssEventNumberCommand << config_paths << "show_event_count auto \""
2197 << strRealInputFile << "\" 2> /dev/null";
2198 if (isDebugMode)
2199 cout << "DEBUG nica-scheduler$ trying to get event count from the input file: "
2200 << ssEventNumberCommand.str() << endl;
2201
2202 array<char, 128> bufferCommand;
2203 string str_event_number = "";
2204 stream = popen(ssEventNumberCommand.str().c_str(), "r");
2205 while (fgets(bufferCommand.data(), static_cast<int>(bufferCommand.size()), stream)
2206 != nullptr)
2207 str_event_number += bufferCommand.data();
2208 pclose(stream);
2209 str_event_number = find_first_number(str_event_number);
2210 if (str_event_number == "") {
2212 "ERROR nica-scheduler$ parallel_mode is used, but event_count "
2213 "is not set and cannot be automatically defined. "
2214 "'%s' file will be skipped!",
2215 strRealInputFile.c_str())
2216 .Data(),
2217 1);
2218 continue;
2219 }
2220 event_count = atoi(str_event_number.c_str());
2221 if (isDebugMode)
2222 cout << "DEBUG nica-scheduler$ " << event_count
2223 << " event(s) found in the input file" << endl;
2224 }
2225
2226 int merge_proc_count = 0;
2227 // fill parameters and parallelize the current file by events (if parallel_mode)
2228 for (int j = 0; j < cur_file->iParallelMode; j++) {
2229 int event_per_proc = event_count;
2230 if (cur_file->iParallelMode > 1) {
2231 event_per_proc = (event_count + j) / cur_file->iParallelMode;
2232 // no events for this parallel part (parallel_mode > event count)
2233 if (event_per_proc == 0) {
2236 "ERROR nica-scheduler$ No events for the parallel part %d of "
2237 "'%s' file, the task indexing can be corrupted!",
2238 j + 1, strRealInputFile.c_str())
2239 .Data(),
2240 1);
2241 start_event = start_event + event_per_proc;
2242 continue;
2243 }
2244 }
2245
2246 merge_proc_count++;
2247
2248 nullString nstr_output_file = nstrRealOutputFile;
2249
2250 // generate variable with input file array
2251 strBatchInitInputFile += "\"";
2252 strBatchInitInputFile += strRealInputFile;
2253 strBatchInitInputFile += "\" ";
2254
2255 // generate variable with output file array
2256 if (nstr_output_file != nullptr) {
2257 // in case of parallel_mode - for particular processing by events
2258 if (cur_file->iParallelMode > 1)
2259 nstr_output_file.SetValue(
2260 GenerateOutputFilePath(nstr_output_file.GetValue(), merge_proc_count));
2261 strBatchInitOutputFile += "\"";
2262 strBatchInitOutputFile += nstr_output_file.GetValue();
2263 strBatchInitOutputFile += "\" ";
2264 } else
2265 strBatchInitOutputFile += "\">\" ";
2266
2267 // generate variable with start event array
2268 if (cur_start_event != nullptr) {
2269 string strInt = int_to_string(start_event);
2270 strBatchInitStartEvent += "\"";
2271 strBatchInitStartEvent += strInt;
2272 strBatchInitStartEvent += "\" ";
2273 } else
2274 strBatchInitStartEvent += "\">\" ";
2275
2276 // generate variable with event number array
2277 if (cur_event_count != nullptr) {
2278 string strInt = int_to_string(event_per_proc);
2279 strBatchInitEventCount += "\"";
2280 strBatchInitEventCount += strInt;
2281 strBatchInitEventCount += "\" ";
2282 } else
2283 strBatchInitEventCount += "\">\" ";
2284
2285 // generate variable with additional_argiments array
2286 if (cur_macro->strAddArgs != "") {
2287 strBatchInitAddArgs += "\"";
2288 string mask_string = cur_macro->strAddArgs;
2289 replace_string_in_text(mask_string, "\"", "\\\"");
2290 strBatchInitAddArgs += mask_string;
2291 strBatchInitAddArgs += "\" ";
2292 } else
2293 strBatchInitAddArgs += "\">\" ";
2294
2295 bool is_clean = false; // flag if there are objects to clean
2296 // in case of using a fast storage
2297 // add commands to put files to the intermediate storage
2298 if (cur_file->strPutPath != "") {
2299 if (j == 0) {
2300 strBatchInitPut += "\"";
2301 // create the temporary directory if it does not exist
2302 strBatchInitPut +=
2303 "mkdir -p " + get_directory_path(cur_file->strPutPath) + "; ";
2304 strBatchInitPut += cur_file->strPutCommand;
2305 strBatchInitPut += " ";
2306 strBatchInitPut += cur_file->strFileIn;
2307 strBatchInitPut += " ";
2308 strBatchInitPut += cur_file->strPutPath;
2309 // create input_file_name.lock to synchronize with other tasks
2310 if (cur_file->iParallelMode > 1) {
2311 strBatchInitPut += "; touch ";
2312 strBatchInitPut += cur_file->strPutPath + (string) ".lock\" ";
2313
2314 if (is_clean == false)
2315 strBatchInitClean += "\"";
2316 else
2317 strBatchInitClean += " ";
2318 strBatchInitClean += cur_file->strPutPath + (string) ".lock\" ";
2319 is_clean = true;
2320 }
2321 strBatchInitPut += "\" ";
2322
2323 }
2324 // wait for the first task will put input file to the fast storage
2325 else {
2326 strBatchInitPut += "\"";
2327 strBatchInitPut += "wait ";
2328 strBatchInitPut += cur_file->strPutPath;
2329 strBatchInitPut += ".lock\" ";
2330 }
2331 } else
2332 strBatchInitPut += "\">\" ";
2333
2334 // generate variable to copy file from the temporary location to the output path
2335 if (cur_file->strGetPath != "") {
2336 string strFileOutDir = get_directory_path(cur_file->nstrFileOut.GetValue());
2337 // if the output path is a bare file name, copy to the current directory
2338 if (strFileOutDir == "")
2339 strFileOutDir = ".";
2340
2341 strBatchInitGet += "\"";
2342 // create the final output directory if it does not exist
2343 strBatchInitGet += "mkdir -p " + strFileOutDir + "; ";
2344
2345 // the extra result files requested by the <get extras=...> option are copied
2346 // before the main result so that the exit code of the whole element
2347 // corresponds to the main copy command
2348 for (const string& cur_extra_path : cur_file->vecGetExtraPath) {
2349 strBatchInitGet += "set +f; for extra_file in ";
2350 strBatchInitGet += cur_extra_path;
2351 strBatchInitGet += "; do if [ -e \\$extra_file ]; then ";
2352 strBatchInitGet += cur_file->strGetCommand;
2353 strBatchInitGet += " \\$extra_file ";
2354 strBatchInitGet +=
2355 strFileOutDir + string("/") + "\\$(basename \\$extra_file)";
2356 strBatchInitGet += "; rm -rf \\$extra_file; fi; done; set -f; ";
2357 }
2358
2359 strBatchInitGet += cur_file->strGetCommand;
2360 strBatchInitGet += " ";
2361 if (cur_file->iParallelMode > 1) {
2362 strBatchInitGet += nstr_output_file.GetValue();
2363 strBatchInitGet += " ";
2364 strBatchInitGet += strFileOutDir + string("/")
2365 + get_file_name_with_ext(nstr_output_file.GetValue());
2366 } else {
2367 strBatchInitGet += cur_file->strGetPath;
2368 strBatchInitGet += " ";
2369 strBatchInitGet += cur_file->nstrFileOut.GetValue();
2370 }
2371 strBatchInitGet += "\" ";
2372 } else
2373 strBatchInitGet += "\">\" ";
2374
2375 // fill variable to clean the temporary files
2376 // from <put>, <get> (always now) and from <clean>
2377 if (cur_file->strPutPath != "") {
2378 if (is_clean == false)
2379 strBatchInitClean += "\"";
2380 else
2381 strBatchInitClean += " ";
2382 strBatchInitClean += cur_file->strPutPath;
2383
2384 is_clean = true;
2385 }
2386 if (cur_file->strGetPath != "") {
2387 if (is_clean == false)
2388 strBatchInitClean += "\"";
2389 else
2390 strBatchInitClean += " ";
2391 strBatchInitClean += cur_file->strGetPath;
2392
2393 is_clean = true;
2394 }
2395 for (const auto& str_clean_path : cur_file->vecCleanPath) {
2396 if (is_clean == false)
2397 strBatchInitClean += "\"";
2398 else
2399 strBatchInitClean += " ";
2400 strBatchInitClean += str_clean_path;
2401
2402 is_clean = true;
2403 }
2404 if (is_clean)
2405 strBatchInitClean += "\" ";
2406
2407 // form output file list (in one string) to merge if necessary
2408 if (nstr_output_file != nullptr)
2409 strOutputUnion += " " + nstr_output_file.GetValue();
2410
2411 start_event = start_event + event_per_proc;
2412 } // for (int j = 0; j < i_parallel_mode; j++)
2413
2414 if ((merge_proc_count > 1) && (cur_file->iMerge >= 0)) {
2415 // if (isDebugMode)
2416 // cout<<"DEBUG nica-scheduler$ Adding "<< strOutputUnion << " to the merge
2417 // list" <<endl;
2418 vecPartialOutputFiles.push_back(strOutputUnion);
2419 vecMergeMode.push_back(cur_file->iMerge);
2420 }
2421 } // for files of the current macro
2422 } // for macros of the current job
2423 } // if there are files to process then write processing info to the special variables
2424
2425 // GENERATE JOB: *.sh file for a batch system
2426 if (job_par->iRunMode == 2) // global container mode
2427 {
2428 strBatchFile += "export CONTAINER_PATH=";
2429 auto it = mapContainerType.find(job_par->strContainerType);
2430 if (it != mapContainerType.end())
2431 strBatchFile += it->second;
2432 else {
2433 info_message((TString::Format("ERROR nica-scheduler$ Undefined container type: '%s'",
2434 job_par->strContainerType.c_str()))
2435 .Data(),
2436 1);
2437 exit(-1);
2438 }
2439 strBatchFile += "\n\n";
2440 }
2441
2442 // add all generated variables to the bash file if the job contains ROOT macros
2443 if (job_par->vecMacros.size() > 0) {
2444 strBatchFile += strBatchInitInputFile;
2445 strBatchFile += ")\n\n";
2446 strBatchFile += strBatchInitOutputFile;
2447 strBatchFile += ")\n\n";
2448 strBatchFile += strBatchInitStartEvent;
2449 strBatchFile += ")\n\n";
2450 strBatchFile += strBatchInitEventCount;
2451 strBatchFile += ")\n\n";
2452 strBatchFile += strBatchInitAddArgs;
2453 strBatchFile += ")\n\n";
2454 strBatchFile += strBatchInitPut, strBatchFile += ")\n\n";
2455 strBatchFile += strBatchInitGet, strBatchFile += ")\n\n";
2456 strBatchFile += strBatchInitClean, strBatchFile += ")\n\n";
2457 strBatchFile += ": ${NICA_ARRAY_OFFSET:=0}\n\n";
2458 }
2459
2460 // cycle by all elements of the job stack
2461 int start_line_number = 0;
2462 for (size_t ind_stack = 0; ind_stack < job_par->vecJobStack.size(); ind_stack++) {
2463 structJobStackPar* cur_stack = job_par->vecJobStack[ind_stack].get();
2464
2465 // if the stack element is a command line, execute it before the next macro
2466 if (cur_stack->iStackType == 1) {
2467 if (isDebugMode) {
2468 strBatchFile += "echo \"DEBUG nica-scheduler: Run Command = ";
2469 strBatchFile += cur_stack->nstrCommandline.GetValue();
2470 strBatchFile += "\"\n";
2471 }
2472
2473 strBatchFile += cur_stack->nstrCommandline.GetValue();
2474 strBatchFile += "\n\n";
2475 continue;
2476 }
2477
2478 structMacroPar* cur_macro = cur_stack->sptrMacroParameter.get();
2479
2480 // get line number in the job script to obtain variables corresponding the task
2481 strBatchFile += "let \"array_index = ";
2482 strBatchFile += batch_com.batch_task_id;
2483 strBatchFile += " - 1 + ${NICA_ARRAY_OFFSET} + ";
2484 strBatchFile += to_string(start_line_number);
2485 strBatchFile += "\"\n\n";
2486 for (const auto& iter : cur_macro->vecFiles)
2487 start_line_number += iter->iParallelMode;
2488
2489 // copy file to the temporary storage if the current element is not equal to '>'
2490 // in case of parallelizing by events - other waits when the first put the input file
2491 strBatchFile += "element=${PutArray[${array_index}]}\n"
2492 "if [ \"${element}\" != \">\" ]\nthen\n"
2493 " # Check if PutElement contains \"wait <file_path>\"\n"
2494 " if [[ \"${element}\" == *\"wait \"* ]]; then\n"
2495 " # Extract file path after \"wait \"\n"
2496 " lock_file_path=\"${1#wait }\"\n"
2497 " # Wait until file appears\n"
2498 " while [ ! -f \"$lock_file_path\" ]; do\n";
2499 if (isDebugMode)
2500 strBatchFile += " echo \"DEBUG nica-scheduler: wait while putting file\"\n";
2501 strBatchFile += " sleep 5\n"
2502 " done\n"
2503 " else\n"
2504 " set -f # avoid globbing (expansion of *)\n"
2505 " # arr=(${element// / })\n"
2506 " # comm=\"${arr[0]} ${arr[1]} ${arr[2]}\"\n"
2507 " # eval $comm\n";
2508 if (isDebugMode)
2509 strBatchFile += " echo \"DEBUG nica-scheduler: PUT command = ${element}\"\n";
2510 strBatchFile += " eval ${element}\n"
2511 " fi\n"
2512 "fi\n\n";
2513
2514 // run macro by ROOT
2515 strBatchFile += "element=${InputFileArray[${array_index}]}\n";
2516
2517 // global run mode on a cluster
2518 if (job_par->iRunMode == 1) {
2519 strBatchFile += "export ROOT_HIST=0\n";
2520 strBatchFile += "comm=\"root -b -q '";
2521 strBatchFile += cur_macro->nstrMacroPath.GetValue();
2522 strBatchFile += "(\\\"${element}\\\"\"\n"
2523 "element=${OutputFileArray[${array_index}]}\n"
2524 "if [ \"${element}\" != \">\" ]\nthen\n"
2525 " mkdir -p \"$(dirname \"${element}\")\"\n"
2526 " comm=\"${comm},\\\"${element}\\\"\"\n";
2527 }
2528 // run mode^ 2 - global container/apptainer mode
2529 else {
2530 strBatchFile += "comm=\"'";
2531 strBatchFile += cur_macro->nstrMacroPath.GetValue();
2532 // using triple escaped quotes to pass it to apptainer_run.sh file
2533 strBatchFile += "(\\\\\\\"${element}\\\\\\\"\"\n"
2534 "element=${OutputFileArray[${array_index}]}\n"
2535 "if [ \"${element}\" != \">\" ]\nthen\n"
2536 " mkdir -p \"$(dirname \"${element}\")\"\n"
2537 " comm=\"${comm},\\\\\\\"${element}\\\\\\\"\"\n";
2538 }
2539
2540 // add start event, event count and additional arguments to ROOT macro
2541 strBatchFile += "fi\n"
2542 "element=${StartEventArray[${array_index}]}\n"
2543 "if [ \"${element}\" != \">\" ]\nthen\n"
2544 " comm=\"${comm},${element}\"\n"
2545 "fi\n"
2546 "element=${EventCountArray[${array_index}]}\n"
2547 "if [ \"${element}\" != \">\" ]\nthen\n"
2548 " comm=\"${comm},${element}\"\n"
2549 "fi\n"
2550 "element=${AddArgsArray[${array_index}]}\n"
2551 "if [ \"${element}\" != \">\" ]\nthen\n"
2552 " comm=\"${comm},${element}\"\n"
2553 "fi\n"
2554 "comm=\"$comm)'\"\n\n";
2555
2556 if (isDebugMode)
2557 strBatchFile += " echo \"DEBUG nica-scheduler: Run Command = $comm\"\n";
2558
2559 if (job_par->iRunMode == 1) // global mode, not container
2560 strBatchFile += "eval $comm\n\n";
2561 else // global container/apptainer mode
2562 {
2563 strBatchFile += batch_com.container_run_command;
2564 strBatchFile += " apptainer run -B \"/eos,/cvmfs\" --fakeroot \"$CONTAINER_PATH\" ";
2565 strBatchFile += exe_dir + "apptainer_run.sh " + "\"$@\" \"$comm\" \n\n";
2566 }
2567
2568 // copy file from the temporary location to the output path
2569 strBatchFile += "GET_FAILED=0\n"
2570 "element=${GetArray[${array_index}]}\n"
2571 "if [ \"${element}\" != \">\" ]\nthen\n"
2572 " set -f # avoid globbing (expansion of *)\n"
2573 "# arr=(${element// / })\n"
2574 "# comm=\"${arr[0]} ${arr[1]} ${arr[2]}\"\n"
2575 "# eval $comm\n";
2576 if (isDebugMode)
2577 strBatchFile += " echo \"DEBUG nica-scheduler: GET command = ${element}\"\n";
2578 strBatchFile += " eval ${element}\n"
2579 " if [ $? -ne 0 ]; then\n"
2580 " echo \"ERROR nica-scheduler: copying of the results (GET) failed, "
2581 "temporary files will be preserved\"\n"
2582 " GET_FAILED=1\n"
2583 " fi\n"
2584 "fi\n\n";
2585
2586 // clean the temporary files from <put>, <get> and from <clean>
2587 // (skipped if the results were not copied to preserve them)
2588 strBatchFile += "element=${CleanArray[${array_index}]}\n"
2589 "if [ \"${element}\" != \">\" ] && [ \"${GET_FAILED}\" != \"1\" ]\nthen\n"
2590 " set -f # avoid globbing (expansion of *)\n"
2591 " arr=(${element// / })\n"
2592 " comm=\"rm -rf \"\n"
2593 " for i in \"${arr[@]}\"\n"
2594 " do\n"
2595 " comm=\"${comm}${i} \"\n"
2596 " done\n";
2597 if (isDebugMode)
2598 strBatchFile += " echo \"DEBUG nica-scheduler: CLEAN command = $comm\"\n";
2599 strBatchFile += " eval $comm\n"
2600 "fi\n\n";
2601 } // for elements in the job stack
2602 // if global mode, echo the current datetime
2603 if (job_par->iRunMode == 1)
2604 // if (isDebugMode) strBatchFile += "echo \" Current DIR ls: `ls -alh`\"\n";
2605 strBatchFile += "echo \" End date: `date`\"\nexit 0\n";
2606
2607 // write the job a to temporary file
2608 if (job_par->iRunMode == 2) // for container/apptainer mode
2609 {
2610 strBatchFile += "echo \" End date: `date`\"\nexit 0\n";
2611
2612 string apptainer_run_file_path = exe_dir + (string) "apptainer_run" + (string) ".sh";
2613 FILE* pJobContFile = fopen(apptainer_run_file_path.c_str(), "wt");
2614 if (pJobContFile == nullptr) {
2615 info_message(TString::Format("ERROR nica-scheduler$ Container file was not created: %s. "
2616 "This job will be skipped!",
2617 apptainer_run_file_path.c_str())
2618 .Data(),
2619 1);
2620 cur_node = cur_node->next;
2621 continue;
2622 }
2623
2624 fwrite(strApptainerRunFile.c_str(), strApptainerRunFile.length(), sizeof(char), pJobContFile);
2625 fclose(pJobContFile);
2626 // Set executable permission
2627 chmod(apptainer_run_file_path.c_str(), S_IRWXU); // 0700
2628 }
2629
2630 // string input_file_path = exe_dir + (string)"input_" + buffer + (string)".txt";
2631 // string output_file_path = exe_dir + (string)"output" + buffer + (string)".txt";
2632
2633 // create main shell file with all the commands to be executed (default path: 'nica-scheduler'
2634 // location)
2635 stringstream ssMainScriptFile;
2636 ssMainScriptFile << "job_" << rand() << rand() << ".sh";
2637 stringstream ssMainScriptPath;
2638 ssMainScriptPath << exe_dir << ssMainScriptFile.str();
2639 FILE* pJobFile = fopen(ssMainScriptPath.str().c_str(), "wt");
2640 if (pJobFile == nullptr) {
2641 string error_path = ssMainScriptPath.str();
2642 // if not created, change to the current location
2643 ssMainScriptPath.str(""); // Clear the content
2644 ssMainScriptPath.clear(); // Clear the state flags
2645 ssMainScriptPath << ssMainScriptFile.str();
2646
2647 pJobFile = fopen(ssMainScriptPath.str().c_str(), "wt");
2648 if (pJobFile == nullptr) {
2649 error_path += (string) " || " + ssMainScriptPath.str();
2650 // if not created, change to the XML-file location
2651 ssMainScriptPath.str(""); // Clear the content
2652 ssMainScriptPath.clear(); // Clear the state flags
2653 ssMainScriptPath << get_directory_path(xml_file_path) << ssMainScriptFile.str();
2654 pJobFile = fopen(ssMainScriptPath.str().c_str(), "wt");
2655 if (pJobFile == nullptr) {
2656 error_path += (string) " || " + ssMainScriptPath.str();
2657 info_message(TString::Format("ERROR nica-scheduler$ Job file was not created at: %s. "
2658 "This job will be skipped!",
2659 error_path.c_str())
2660 .Data(),
2661 1);
2662 cur_node = cur_node->next;
2663 continue;
2664 } // if (pJobFile == nullptr)
2665 } // if (pJobFile == nullptr)
2666 } // if (pJobFile == nullptr)
2667 fwrite(strBatchFile.c_str(), strBatchFile.length(), sizeof(char), pJobFile);
2668 fclose(pJobFile);
2669
2670 // output qsub file content in debug mode
2671 // if (isDebugMode) cout<<"DEBUG nica-scheduler$ QSUB file content:"<<endl<<strBatchFile<<endl;
2672
2673 // define the maximum number of tasks in one batch array job
2674 int array_limit = job_par->iArrayLimit;
2675 if ((array_limit <= 0) || (array_limit > globalParallelCount))
2676 array_limit = globalParallelCount;
2677
2678 vector<string> vecSubmittedJobID;
2679 bool is_submit_error = false;
2680
2681 // submit jobs by portions if arraylimit is set
2682 for (int array_offset = 0, array_part = 1; array_offset < globalParallelCount;
2683 array_offset += array_limit, array_part++)
2684 {
2685 // wait while the current user queue is full according to queuelimit
2686 if (!wait_batch_queue_limit(batch_com, job_par->iQueueLimit)) {
2687 is_submit_error = true;
2688 break;
2689 }
2690
2691 int current_array_count = globalParallelCount - array_offset;
2692 if (current_array_count > array_limit)
2693 current_array_count = array_limit;
2694
2695 int current_proc_count = proc_count;
2696 if (current_proc_count > current_array_count)
2697 current_proc_count = current_array_count;
2698
2699 string strBatchJobName = job_par->strJobName;
2700 if (array_limit < globalParallelCount)
2701 strBatchJobName += "_" + int_to_string(array_part);
2702
2703 // form batch command to run our jobs
2704 stringstream ssBatchCommand;
2705 // fill batch command, job name, dependency, task count, batch slot count
2706 ssBatchCommand << TString::Format(batch_com.scheduler_run_job.c_str(), strBatchJobName.c_str(),
2707 job_par->strDdependencyName.c_str(), current_array_count,
2708 current_proc_count)
2709 .Data();
2710
2711 // add priority of the job if given
2712 if (iPriority != 0)
2713 ssBatchCommand
2714 << TString::Format(batch_com.sheduler_priority_option.c_str(), iPriority).Data();
2715
2716 // add queue name if given
2717 if (job_par->nstrQueue != nullptr)
2718 ssBatchCommand << TString::Format(batch_com.sheduler_queue_option.c_str(),
2719 job_par->nstrQueue.GetValue().c_str())
2720 .Data();
2721
2722 // add Quality-Of-Service if given
2723 if (job_par->nstrQOS != nullptr)
2724 ssBatchCommand << TString::Format(batch_com.sheduler_qos_option.c_str(),
2725 job_par->nstrQOS.GetValue().c_str())
2726 .Data();
2727
2728 // add host list if given
2729 if (job_par->nstrHosts != nullptr) {
2730 // host list to exclude from processing
2731 if (job_par->nstrHosts.GetValue()[0] == '~')
2732 ssBatchCommand << TString::Format(batch_com.sheduler_exclude_hosts_option.c_str(),
2733 job_par->nstrHosts.GetValue().substr(1).c_str())
2734 .Data();
2735 // host list for processing
2736 else
2737 ssBatchCommand << TString::Format(batch_com.sheduler_hosts_option.c_str(),
2738 job_par->nstrHosts.GetValue().c_str())
2739 .Data();
2740 }
2741
2742 // add working directory if given
2743 if (job_par->nstrWorkDir != nullptr)
2744 ssBatchCommand << TString::Format(batch_com.sheduler_workdir_option.c_str(),
2745 job_par->nstrWorkDir.GetValue().c_str())
2746 .Data();
2747
2748 // add maximum operative memory size per one process
2749 if (job_par->strOperativeMemory1 != "")
2750 ssBatchCommand << TString::Format(batch_com.operative_memory1_option.c_str(),
2751 job_par->strOperativeMemory1.c_str())
2752 .Data();
2753
2754 // pass the global array offset to the batch job script
2755 if (batch_system == SLURM_BATCH_SYSTEM)
2756 ssBatchCommand << batch_com.set_job_variables << "\"ALL,NICA_ARRAY_OFFSET=" << array_offset
2757 << "\"";
2758 else
2759 ssBatchCommand << batch_com.set_job_variables << "\"NICA_ARRAY_OFFSET=" << array_offset
2760 << "\"";
2761
2762 // add script path to be executed
2763 ssBatchCommand << " " << ssMainScriptPath.str();
2764
2765 if (isDebugMode)
2766 // main command
2767 cout << "DEBUG nica-scheduler$ Batch command: " << ssBatchCommand.str() << endl;
2768
2769 array<char, 128> bufferBatch;
2770 string localID = "";
2771 stream = popen(ssBatchCommand.str().c_str(), "r");
2772 while (fgets(bufferBatch.data(), static_cast<int>(bufferBatch.size()), stream) != nullptr)
2773 localID += bufferBatch.data();
2774 pclose(stream);
2775
2776 if (isDebugMode)
2777 // batch command result
2778 cout << "DEBUG nica-scheduler$ Batch command output = " << localID << endl;
2779
2780 localID = find_first_number(localID);
2781 if (localID == "") {
2782 info_message("ERROR nica-scheduler$ Job ID was not found: "
2783 "an error occurred or there is no batch system. This job will be skipped!",
2784 1);
2785 is_submit_error = true;
2786 break;
2787 }
2788
2789 vecSubmittedJobID.push_back(localID);
2790 info_message(TString::Format("nica-scheduler$ The job '%s' has been submitted with ID: %s. "
2791 "Enter '%s' command to check status.",
2792 strBatchJobName.c_str(), localID.c_str(),
2793 batch_com.check_status_command.c_str())
2794 .Data());
2795 }
2796
2797 if (is_submit_error || vecSubmittedJobID.empty()) {
2798 // cancel the already submitted portions so that the job is not executed partially
2799 for (const string& submitted_id : vecSubmittedJobID) {
2800 int cancel_return =
2801 system(TString::Format(batch_com.cancel_command.c_str(), submitted_id.c_str()).Data());
2802 if (cancel_return != 0)
2803 info_message(TString::Format("ERROR nica-scheduler$ The submitted job portion with "
2804 "ID: %s was not cancelled, please cancel it manually!",
2805 submitted_id.c_str())
2806 .Data(),
2807 1);
2808 else
2809 info_message(TString::Format("nica-scheduler$ The submitted job portion with ID: %s "
2810 "has been cancelled because of the errors above.",
2811 submitted_id.c_str())
2812 .Data());
2813 }
2814
2815 cur_node = cur_node->next;
2816 continue;
2817 }
2818
2819 // current time
2820 // time_t t = time(nullptr);
2821 // struct tm tm = *localtime(&t);
2822 // printf("now: %d-%d-%d %d:%d:%d\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour,
2823 // tm.tm_min, tm.tm_sec);
2824
2825 string ID = concatenate_job_ids(vecSubmittedJobID, batch_com.job_dependency_separator);
2826 mapJobName2ID.insert(pair<string, string>(job_par->strJobName, ID));
2827 if (!vecPartialOutputFiles.empty()) {
2828 // cycle: one vector record contains output file list separated by space
2829 // where first element of the list is the result output file
2830 for (size_t i = 0; i < vecPartialOutputFiles.size(); i++) {
2831
2832 // wait while the current user queue is full according to queuelimit
2833 if (!wait_batch_queue_limit(batch_com, job_par->iQueueLimit))
2834 continue;
2835
2836 // merge.sh - special batch job to merge files
2837 // !!! need to rewrite - create merge.sh dynamically
2838 string MERGEjob_path = exe_dir + "merge.sh";
2839 stringstream ssMergeCommand;
2840 ssMergeCommand << batch_com.run_command << " " << batch_com.job_dependency_option << ID
2841 << batch_com.set_job_variables << "\"MergeFiles"
2842 << vecPartialOutputFiles.at(i).GetValue() << "\",MergeMode=\""
2843 << vecMergeMode.at(i) << "\",ConfigFiles=\"" << job_par->strConfigPath
2844 << "\" " << MERGEjob_path;
2845
2846 array<char, 128> bufferMerge;
2847 string localID = "";
2848 stream = popen(ssMergeCommand.str().c_str(), "r");
2849 while (fgets(bufferMerge.data(), static_cast<int>(bufferMerge.size()), stream) != nullptr)
2850 localID += bufferMerge.data();
2851 pclose(stream);
2852
2853 localID = find_first_number(localID);
2854 if (localID == "") {
2855 info_message("ERROR nica-scheduler$ Union job ID was not found: an error occurred or "
2856 "there is no batch system.",
2857 1);
2858 continue;
2859 }
2860
2861 cout << "nica-scheduler$ Task for merging parallel files has ID: " << localID << endl;
2862 } // for vecPartialOutputFiles elements
2863 } // if (!vecPartialOutputFiles.empty())
2864 }
2865 /********************/
2866 /* LOCAL SCHEDULING */
2867 /********************/
2868 else {
2869 if ((job_par->iQueueLimit > 0) || (job_par->iArrayLimit > 0))
2870 cout << "nica-scheduler$ WARNING: 'queuelimit' and 'arraylimit' are ignored in local mode"
2871 << endl;
2872 // dummy - no dependency property for local jobs because it waits every time for all child threads
2873 mapJobName2ID.insert(pair<string, string>(job_par->strJobName, "dummy"));
2874
2875 // define process count for the job
2876 int proc_count = 1;
2877 if (job_par->nstrProcCount != nullptr) {
2878 if ((job_par->nstrProcCount.GetValue()[0] == '0')
2879 && (job_par->nstrProcCount.GetValue().length()) == 1)
2880 {
2881 proc_count = get_linux_processor_count();
2882
2883 if (proc_count == 0) {
2885 "ERROR nica-scheduler$ Core number was not defined. This job will be skipped!", 1);
2886 cur_node = cur_node->next;
2887 continue;
2888 }
2889 } else
2890 proc_count = atoi(job_par->nstrProcCount.GetValue().c_str());
2891 } else
2892 proc_count = globalParallelCount == 0 ? 1 : globalParallelCount;
2893
2894 if (proc_count == 0) {
2896 "ERROR nica-scheduler$ Process count can not be equal 0. This job will be skipped!", 1);
2897 cur_node = cur_node->next;
2898 continue;
2899 }
2900
2901 // process count is equal count of input files if more
2902 if (proc_count > globalParallelCount)
2903 proc_count = globalParallelCount;
2904
2905 vector<thread> vecThreads;
2906 // semaphore and mutex to synchronize all parallel threads and sub-threads
2907 shared_ptr<Semaphore> job_semaphore = make_shared<Semaphore>(proc_count);
2908 shared_ptr<mutex> job_mutex = make_shared<mutex>(); // PTHREAD_MUTEX_RECURSIVE_NP
2909
2910 // parallelizing of file processing in ROOT macro or command stack on one multi-core machine
2911 unsigned int thread_count = 1;
2912 if (job_par->vecMacros.size() > 0) {
2913 if (job_par->vecMacros[0]->vecFiles.size() > 1)
2914 thread_count = job_par->vecMacros[0]->vecFiles.size();
2915 }
2916
2917 // fill parameters for processing threads
2918 for (unsigned int i = 0; i < thread_count; i++) {
2919 shared_ptr<structThreadPar> thread_par = make_shared<structThreadPar>();
2920 thread_par->sptrJobParameter = job_par;
2921 thread_par->semJobSemaphore = job_semaphore;
2922 thread_par->mutJobMutex = job_mutex;
2923 thread_par->iThreadCounter = i + 1;
2924
2925 job_semaphore->acquire();
2926
2927 // create and run threads to local processing - function ThreadLocalProcess
2928 try {
2929 vecThreads.emplace_back(ThreadLocalProcess, thread_par);
2930 } catch (const system_error& e) {
2931 info_message((TString::Format("ERROR nica-scheduler$ error while creating thread with code "
2932 "= %d and message = %s",
2933 e.code().value(), e.what()))
2934 .Data(),
2935 1);
2936 exit(-1);
2937 }
2938 }
2939
2940 // waiting for finishing threads
2941 // cout<<"Waiting for finishing threads "<<vecFiles.size()<<endl;
2942 for (auto& cur_thread : vecThreads) {
2943 try {
2944 cur_thread.join();
2945 } catch (const system_error& e) {
2947 (TString::Format("ERROR nica-scheduler$ Thread failed with code = %d and message = %s",
2948 e.code().value(), e.what()))
2949 .Data(),
2950 1);
2951 }
2952 }
2953
2954 cout << "nica-scheduler$ Local job has been finished" << endl;
2955 } // else local scheduling
2956 } // JOB tag was processed
2957 } // if tag means JOB
2958
2959 cur_node = cur_node->next;
2960 } // while - cycle for all jobs
2961
2962 mapJobName2ID.clear();
2963 mapJobName2FileOut.clear();
2964 xmlFreeDoc(doc);
2965 return 0;
2966}
2967
2968// PARSE DATABASE PARAMETERS
2969void ParseDatabaseParameters(string input, TString& sql, bool isSimulation)
2970{
2971 istringstream ss(input);
2972 string token;
2973
2974 if (isSimulation) {
2975 // variables for DB
2976 bool isGen = false, isEnergy = false, isMinEnergy = false, isMaxEnergy = false, isBeam = false,
2977 isTarget = false, isDesc = false, isPath = false;
2978 string strGen, strBeam, strTarget, strDesc, strPath;
2979 double fEnergy, fMaxEnergy;
2980 // parse tokens by comma separated
2981 while (getline(ss, token, ',')) {
2982 // to lowercase
2983 transform(token.begin(), token.end(), token.begin(), ::tolower);
2984
2985 // generator name parsing
2986 if ((token.length() > 4) && (token.substr(0, 4) == "gen=")) {
2987 isGen = true;
2988 strGen = token.substr(4);
2989 } else {
2990 // energy parsing
2991 if ((token.length() > 7) && (token.substr(0, 7) == "energy=")) {
2992 token = token.substr(7);
2993
2994 size_t indDash = token.find_first_of('-');
2995 if (indDash != string::npos) {
2996 stringstream stream;
2997 stream << token.substr(0, indDash);
2998 double dVal;
2999 if (stream >> dVal) {
3000 isEnergy = true;
3001 isMinEnergy = true;
3002 fEnergy = dVal;
3003 }
3004 if (token.length() > indDash) {
3005 stringstream stream2;
3006 stream2 << token.substr(indDash + 1);
3007 if (stream2 >> dVal) {
3008 isEnergy = true;
3009 isMaxEnergy = true;
3010 fMaxEnergy = dVal;
3011 }
3012 }
3013 } // if (indDash > -1)
3014 // if exact energy value
3015 else {
3016 stringstream stream;
3017 stream << token;
3018 double dVal;
3019 if (stream >> dVal) {
3020 isEnergy = true;
3021 fEnergy = dVal;
3022 }
3023 } // else
3024 } // if ((token.length() > 7) && (token.substr(0,7) == "energy="))
3025 // particles' names in collision parsing
3026 else {
3027 if ((token.length() > 5) && (token.substr(0, 5) == "beam=")) {
3028 isBeam = true;
3029 strBeam = token.substr(5);
3030 } else {
3031 // search text in description string
3032 if ((token.length() > 5) && (token.substr(0, 5) == "desc=")) {
3033 isDesc = true;
3034 strDesc = token.substr(5);
3035 } else {
3036 // type of data parsing
3037 if ((token.length() > 7) && (token.substr(0, 7) == "target=")) {
3038 isTarget = true;
3039 strTarget = token.substr(7);
3040 } else {
3041 // path parsing
3042 if ((token.length() > 5) && (token.substr(0, 5) == "path=")) {
3043 isPath = true;
3044 strPath = token.substr(5);
3045 }
3046 } // else TYPE of data
3047 } // else DESC
3048 } // else PARTICLE
3049 } // else ENERGY
3050 } // else GEN
3051 } // while(getline(ss, token, ','))
3052
3053 // READ PATH FROM DATABASE
3054 sql = "select file_path "
3055 "from simulation_file";
3056
3057 bool isWhere = false;
3058 // if event generator selection
3059 if (isGen == true) {
3060 if (isWhere)
3061 sql += TString::Format(" AND lower(generator_name) = '%s'", strGen.data());
3062 else {
3063 isWhere = true;
3064 sql += TString::Format(" "
3065 "where lower(generator_name) = '%s'",
3066 strGen.data());
3067 }
3068 }
3069 // if energy selection
3070 if (isEnergy == true) {
3071 if (isWhere)
3072 sql += " AND ";
3073 else {
3074 isWhere = true;
3075 sql += " "
3076 "where ";
3077 }
3078
3079 if (isMinEnergy) {
3080 sql += TString::Format("energy >= %f", fEnergy);
3081 if (isMaxEnergy)
3082 sql += TString::Format(" AND energy <= %f", fMaxEnergy);
3083 } else {
3084 if (isMaxEnergy)
3085 sql += TString::Format("energy <= %f", fMaxEnergy);
3086 else
3087 sql += TString::Format("energy = %f", fEnergy);
3088 }
3089 }
3090 // if beam particle was selected
3091 if (isBeam == true) {
3092 if (isWhere)
3093 sql += TString::Format(" AND lower(beam_particle) = '%s'", strBeam.data());
3094 else {
3095 isWhere = true;
3096 sql += TString::Format(" "
3097 "where lower(beam_particle) = '%s'",
3098 strBeam.data());
3099 }
3100 }
3101 // if beam particle was selected
3102 if (isTarget == true) {
3103 if (isWhere)
3104 sql += TString::Format(" AND lower(target_particle) = '%s'", strTarget.data());
3105 else {
3106 isWhere = true;
3107 sql += TString::Format(" "
3108 "where lower(target_particle) = '%s'",
3109 strTarget.data());
3110 }
3111 }
3112 if (isDesc == true) {
3113 if (isWhere)
3114 sql += TString::Format(" AND lower(file_desc) like '%%%s%%'", strDesc.data());
3115 else {
3116 isWhere = true;
3117 sql += TString::Format(" "
3118 "where lower(file_desc) like '%%%s%%'",
3119 strDesc.data());
3120 }
3121 }
3122 if (isPath == true) {
3123 if (isWhere)
3124 sql += TString::Format(" AND lower(file_path) like '%%%s%%'", strPath.data());
3125 else {
3126 isWhere = true;
3127 sql += TString::Format(" "
3128 "where lower(file_path) like '%%%s%%'",
3129 strPath.data());
3130 }
3131 }
3132 sql += " order by generator_name,energy";
3133 }
3134 // else if experimental data
3135 else {
3136 bool isPeriod = false, isMinPeriod = false, isMaxPeriod = false, isRun = false, isMinRun = false,
3137 isMaxRun = false, isBeam = false, isTarget = false, isEnergy = false, isMinEnergy = false,
3138 isMaxEnergy = false, isEvents = false, isMinEvents = false, isMaxEvents = false, isTime = false,
3139 isMinTime = false, isMaxTime = false, isField = false, isMinField = false, isMaxField = false,
3140 isSize = false, isMinSize = false, isMaxSize = false, isPath = false;
3141 string strBeam, strTarget, strTime, strMaxTime, strPath;
3142 int iPeriod, iMaxPeriod, iRun, iMaxRun, iEvents, iMaxEvents, iField, iMaxField;
3143 double fEnergy, fMaxEnergy, fSize, fMaxSize;
3144 // parse tokens by comma separated
3145 while (getline(ss, token, ',')) {
3146 // to lowercase
3147 transform(token.begin(), token.end(), token.begin(), ::tolower);
3148
3149 // period number parsing
3150 if ((token.length() > 7) && (token.substr(0, 7) == "period=")) {
3151 token = token.substr(7);
3152
3153 size_t indDash = token.find_first_of('-');
3154 if (indDash != string::npos) {
3155 stringstream stream;
3156 stream << token.substr(0, indDash);
3157 int iVal;
3158 if (stream >> iVal) {
3159 isPeriod = true;
3160 isMinPeriod = true;
3161 iPeriod = iVal;
3162 }
3163 if (token.length() > indDash) {
3164 stringstream stream2;
3165 stream2 << token.substr(indDash + 1);
3166 if (stream2 >> iVal) {
3167 isPeriod = true;
3168 isMaxPeriod = true;
3169 iMaxPeriod = iVal;
3170 }
3171 }
3172 } // if (indDash > -1)
3173 // if exact period number
3174 else {
3175 stringstream stream;
3176 stream << token;
3177 int iVal;
3178 if (stream >> iVal) {
3179 isPeriod = true;
3180 iPeriod = iVal;
3181 }
3182 } // else
3183 } // if ((token.length() > 7) && (token.substr(0,7) == "period="))
3184 else {
3185 // run number parsing
3186 if ((token.length() > 4) && (token.substr(0, 4) == "run=")) {
3187 token = token.substr(4);
3188
3189 size_t indDash = token.find_first_of('-');
3190 if (indDash != string::npos) {
3191 stringstream stream;
3192 stream << token.substr(0, indDash);
3193 int iVal;
3194 if (stream >> iVal) {
3195 isRun = true;
3196 isMinRun = true;
3197 iRun = iVal;
3198 }
3199 if (token.length() > indDash) {
3200 stringstream stream2;
3201 stream2 << token.substr(indDash + 1);
3202 if (stream2 >> iVal) {
3203 isRun = true;
3204 isMaxRun = true;
3205 iMaxRun = iVal;
3206 }
3207 }
3208 } // if (indDash > -1)
3209 // if exact run number
3210 else {
3211 stringstream stream;
3212 stream << token;
3213 int iVal;
3214 if (stream >> iVal) {
3215 isRun = true;
3216 iRun = iVal;
3217 }
3218 } // else
3219 } // if ((token.length() > 4) && (token.substr(0,4) == "run="))
3220 else {
3221 // beam particle's name in collision parsing
3222 if ((token.length() > 5) && (token.substr(0, 5) == "beam=")) {
3223 isBeam = true;
3224 strBeam = token.substr(5);
3225 } else {
3226 // target particle's name in collision parsing
3227 if ((token.length() > 7) && (token.substr(0, 7) == "target=")) {
3228 isTarget = true;
3229 strTarget = token.substr(7);
3230 } else {
3231 // energy parsing
3232 if ((token.length() > 7) && (token.substr(0, 7) == "energy=")) {
3233 token = token.substr(7);
3234
3235 size_t indDash = token.find_first_of('-');
3236 if (indDash != string::npos) {
3237 stringstream stream;
3238 stream << token.substr(0, indDash);
3239 double dVal;
3240 if (stream >> dVal) {
3241 isEnergy = true;
3242 isMinEnergy = true;
3243 fEnergy = dVal;
3244 }
3245 if (token.length() > indDash) {
3246 stringstream stream2;
3247 stream2 << token.substr(indDash + 1);
3248 if (stream2 >> dVal) {
3249 isEnergy = true;
3250 isMaxEnergy = true;
3251 fMaxEnergy = dVal;
3252 }
3253 }
3254 } // if (indDash > -1)
3255 // if exact energy value
3256 else {
3257 stringstream stream;
3258 stream << token;
3259 double dVal;
3260 if (stream >> dVal) {
3261 isEnergy = true;
3262 fEnergy = dVal;
3263 }
3264 } // else
3265 } // if ((token.length() > 7) && (token.substr(0,7) == "energy="))
3266 else {
3267 // event count parsing
3268 if ((token.length() > 6) && (token.substr(0, 6) == "events=")) {
3269 token = token.substr(6);
3270
3271 size_t indDash = token.find_first_of('-');
3272 if (indDash != string::npos) {
3273 stringstream stream;
3274 stream << token.substr(0, indDash);
3275 int iVal;
3276 if (stream >> iVal) {
3277 isEvents = true;
3278 isMinEvents = true;
3279 iEvents = iVal;
3280 }
3281 if (token.length() > indDash) {
3282 stringstream stream2;
3283 stream2 << token.substr(indDash + 1);
3284 if (stream2 >> iVal) {
3285 isEvents = true;
3286 isMaxEvents = true;
3287 iMaxEvents = iVal;
3288 }
3289 }
3290 } // if (indDash > -1)
3291 // if exact event count
3292 else {
3293 stringstream stream;
3294 stream << token;
3295 int iVal;
3296 if (stream >> iVal) {
3297 isEvents = true;
3298 iEvents = iVal;
3299 }
3300 } // else
3301 } // if ((token.length() > 6) && (token.substr(0,6) == "events="))
3302 else {
3303 // time parsing
3304 if ((token.length() > 5) && (token.substr(0, 5) == "time=")) {
3305 token = token.substr(5);
3306
3307 size_t indDash = token.find_first_of('-');
3308 if (indDash != string::npos) {
3309 if (indDash > 0) {
3310 isTime = true;
3311 isMinTime = true;
3312 strTime = token.substr(0, indDash);
3313 }
3314 if (token.length() > indDash) {
3315 isTime = true;
3316 isMaxTime = true;
3317 strMaxTime = token.substr(indDash + 1);
3318 }
3319 } // if (indDash > -1)
3320 // if exact time
3321 else {
3322 isTime = true;
3323 strTime = token;
3324 } // else
3325 } // if ((token.length() > 5) && (token.substr(0,5) == "time="))
3326 else {
3327 // field current parsing
3328 if ((token.length() > 6) && (token.substr(0, 6) == "field=")) {
3329 token = token.substr(6);
3330
3331 size_t indDash = token.find_first_of('-');
3332 if (indDash != string::npos) {
3333 stringstream stream;
3334 stream << token.substr(0, indDash);
3335 int iVal;
3336 if (stream >> iVal) {
3337 isField = true;
3338 isMinField = true;
3339 iField = iVal;
3340 }
3341 if (token.length() > indDash) {
3342 stringstream stream2;
3343 stream2 << token.substr(indDash + 1);
3344 if (stream2 >> iVal) {
3345 isField = true;
3346 isMaxField = true;
3347 iMaxField = iVal;
3348 }
3349 }
3350 } // if (indDash > -1)
3351 // if exact field current
3352 else {
3353 stringstream stream;
3354 stream << token;
3355 int iVal;
3356 if (stream >> iVal) {
3357 isField = true;
3358 iField = iVal;
3359 }
3360 } // else
3361 } // if ((token.length() > 6) && (token.substr(0,6) == "field="))
3362 else {
3363 // file size parsing
3364 if ((token.length() > 5) && (token.substr(0, 5) == "size=")) {
3365 token = token.substr(5);
3366
3367 size_t indDash = token.find_first_of('-');
3368 if (indDash != string::npos) {
3369 stringstream stream;
3370 stream << token.substr(0, indDash);
3371 double dVal;
3372 if (stream >> dVal) {
3373 isSize = true;
3374 isMinSize = true;
3375 fSize = dVal;
3376 }
3377 if (token.length() > indDash) {
3378 stringstream stream2;
3379 stream2 << token.substr(indDash + 1);
3380 if (stream2 >> dVal) {
3381 isSize = true;
3382 isMaxSize = true;
3383 fMaxSize = dVal;
3384 }
3385 }
3386 } // if (indDash > -1)
3387 // if exact file size
3388 else {
3389 stringstream stream;
3390 stream << token;
3391 double dVal;
3392 if (stream >> dVal) {
3393 isSize = true;
3394 fSize = dVal;
3395 }
3396 } // else
3397 } // if ((token.length() > 5) && (token.substr(0,5) == "size="))
3398 else {
3399 // path parsing
3400 if ((token.length() > 5) && (token.substr(0, 5) == "path=")) {
3401 isPath = true;
3402 strPath = token.substr(5);
3403 }
3404 } // else SIZE
3405 } // else FIELD
3406 } // else TIME
3407 } // else EVENTS
3408 } // else ENERGY
3409 } // else TARGER
3410 } // else BEAM
3411 } // else RUN
3412 } // else PERIOD
3413 } // while(getline(ss, token, ','))
3414
3415 // generate query
3416 sql = "select file_path "
3417 "from run_";
3418
3419 bool isWhere = false;
3420 // if period selection
3421 if (isPeriod == true) {
3422 if (isWhere)
3423 sql += " AND ";
3424 else {
3425 isWhere = true;
3426 sql += " "
3427 "where ";
3428 }
3429
3430 if (isMinPeriod) {
3431 sql += TString::Format("period_number >= %d", iPeriod);
3432 if (isMaxPeriod)
3433 sql += TString::Format(" AND period_number <= %d", iMaxPeriod);
3434 } else {
3435 if (isMaxPeriod)
3436 sql += TString::Format("period_number <= %d", iMaxPeriod);
3437 else
3438 sql += TString::Format("period_number = %d", iPeriod);
3439 }
3440 }
3441 // if run selection
3442 if (isRun == true) {
3443 if (isWhere)
3444 sql += " AND ";
3445 else {
3446 isWhere = true;
3447 sql += " "
3448 "where ";
3449 }
3450
3451 if (isMinRun) {
3452 sql += TString::Format("run_number >= %d", iRun);
3453 if (isMaxRun)
3454 sql += TString::Format(" AND run_number <= %d", iMaxRun);
3455 } else {
3456 if (isMaxRun)
3457 sql += TString::Format("run_number <= %d", iMaxRun);
3458 else
3459 sql += TString::Format("run_number = %d", iRun);
3460 }
3461 }
3462 // if beam particle was selected
3463 if (isBeam == true) {
3464 if (isWhere)
3465 sql += TString::Format(" AND lower(beam_particle) = '%s'", strBeam.data());
3466 else {
3467 isWhere = true;
3468 sql += TString::Format(" "
3469 "where lower(beam_particle) = '%s'",
3470 strBeam.data());
3471 }
3472 }
3473 // if target particle was selected
3474 if (isTarget == true) {
3475 if (isWhere)
3476 sql += TString::Format(" AND lower(target_particle) = '%s'", strTarget.data());
3477 else {
3478 isWhere = true;
3479 sql += TString::Format(" "
3480 "where lower(target_particle) = '%s'",
3481 strTarget.data());
3482 }
3483 }
3484 // if energy selection
3485 if (isEnergy == true) {
3486 if (isWhere)
3487 sql += " AND ";
3488 else {
3489 isWhere = true;
3490 sql += " "
3491 "where ";
3492 }
3493
3494 if (isMinEnergy) {
3495 sql += TString::Format("energy >= %f", fEnergy);
3496 if (isMaxEnergy)
3497 sql += TString::Format(" AND energy <= %f", fMaxEnergy);
3498 } else {
3499 if (isMaxEnergy)
3500 sql += TString::Format("energy <= %f", fMaxEnergy);
3501 else
3502 sql += TString::Format("energy = %f", fEnergy);
3503 }
3504 }
3505 // if event count selection
3506 if (isEvents == true) {
3507 if (isWhere)
3508 sql += " AND ";
3509 else {
3510 isWhere = true;
3511 sql += " "
3512 "where ";
3513 }
3514
3515 if (isMinEvents) {
3516 sql += TString::Format("event_count >= %d", iEvents);
3517 if (isMaxEvents)
3518 sql += TString::Format(" AND event_count <= %d", iMaxEvents);
3519 } else {
3520 if (isMaxEvents)
3521 sql += TString::Format("event_count <= %d", iMaxEvents);
3522 else
3523 sql += TString::Format("event_count = %d", iEvents);
3524 }
3525 }
3526 // if time selection
3527 if (isTime == true) {
3528 if (isWhere)
3529 sql += " AND ";
3530 else {
3531 isWhere = true;
3532 sql += " "
3533 "where ";
3534 }
3535
3536 // check correct format
3537 struct tm tm;
3538 if (!strptime(strMaxTime.c_str(), "%Y-%m-%d %H:%M:%S", &tm)) {
3539 isMaxTime = false;
3540 cout << "Error: " << strMaxTime
3541 << " is not correct datetime. DateTime format should be yyyy-mm-dd 24hh:mm:ss." << endl;
3542 }
3543 if (!strptime(strTime.c_str(), "%Y-%m-%d %H:%M:%S", &tm)) {
3544 isMinTime = false;
3545 cout << "Error: " << strTime
3546 << " is not correct datetime. DateTime format should be yyyy-mm-dd 24hh:mm:ss." << endl;
3547 if (!isMaxTime)
3548 isTime = false;
3549 }
3550
3551 if (isMinTime) {
3552 sql += TString::Format("end_datetime >= '%s'", strTime.c_str());
3553 if (isMaxTime)
3554 sql += TString::Format(" AND start_datetime <= '%s'", strMaxTime.c_str());
3555 } else {
3556 if (isMaxTime)
3557 sql += TString::Format("start_datetime <= '%s'", strMaxTime.c_str());
3558 else {
3559 if (isTime)
3560 sql += TString::Format("start_datetime <= '%s' AND end_datetime >= '%s'", strTime.c_str(),
3561 strTime.c_str());
3562 }
3563 }
3564 }
3565 // if field current selection
3566 if (isField == true) {
3567 if (isWhere)
3568 sql += " AND ";
3569 else {
3570 isWhere = true;
3571 sql += " "
3572 "where ";
3573 }
3574
3575 if (isMinField) {
3576 sql += TString::Format("field_voltage >= %d", iField);
3577 if (isMaxField)
3578 sql += TString::Format(" AND field_voltage <= %d", iMaxField);
3579 } else {
3580 if (isMaxField)
3581 sql += TString::Format("field_voltage <= %d", iMaxField);
3582 else
3583 sql += TString::Format("field_voltage = %d", iField);
3584 }
3585 }
3586 // if file size selection
3587 if (isSize == true) {
3588 if (isWhere)
3589 sql += " AND ";
3590 else {
3591 isWhere = true;
3592 sql += " "
3593 "where ";
3594 }
3595
3596 if (isMinSize) {
3597 sql += TString::Format("file_size >= %f", fSize);
3598 if (isMaxSize)
3599 sql += TString::Format(" AND file_size <= %f", fMaxSize);
3600 } else {
3601 if (isMaxSize)
3602 sql += TString::Format("file_size <= %f", fMaxSize);
3603 else
3604 sql += TString::Format("file_size = %f", fSize);
3605 }
3606 }
3607 if (isPath == true) {
3608 if (isWhere)
3609 sql += TString::Format(" AND lower(file_path) like '%%%s%%'", strPath.data());
3610 else {
3611 isWhere = true;
3612 sql += TString::Format(" "
3613 "where lower(file_path) like '%%%s%%'",
3614 strPath.data());
3615 }
3616 }
3617
3618 sql += " order by period_number,run_number";
3619 }
3620
3621 return;
3622}
3623
3624// function to parse file list and parameters for the current macro of the job
3625bool ParseMacroFiles(xmlNodePtr sub_node,
3626 shared_ptr<structJobPar> job_par,
3627 shared_ptr<structMacroPar> macro_par,
3628 multimap<string, unique_ptr<vector<nullString>>>& mapJobName2FileOut,
3629 batch_commands batch_com)
3630{
3631 char* pc_temp = nullptr;
3632 while (sub_node) {
3633 // FILE TAG
3634 if (strcmp((char*)sub_node->name, "file") == 0) {
3635 bool isStartEvent = false, isEventCount = false;
3636 int start_event, event_count;
3637 string sim_db = "", exp_db = "", job_input = "", macro_input = "", file_input = "", json_input = "",
3638 put_command = "cp", put_path = "", get_command = "cp", get_path = "", get_extras = "",
3639 get_output = "", clean_path = "", parallel_mode = "";
3640 nullString nstr_file_in, nstr_file_out;
3641
3642 // read Input File Path(s) to process by the macro in the XML description
3643 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"input");
3644 if (pc_temp != nullptr) {
3645 nstr_file_in.SetValue((string)pc_temp);
3646 free(pc_temp);
3647 }
3648 // read Output File Path(s) to save processing results by the macro
3649 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"output");
3650 if (pc_temp != nullptr) {
3651 nstr_file_out.SetValue((string)pc_temp);
3652 free(pc_temp);
3653 }
3654
3655 // read Start Event value in the XML description
3656 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"start_event");
3657 if (pc_temp != nullptr) {
3658 start_event = atoi(pc_temp);
3659 free(pc_temp);
3660 isStartEvent = true;
3661 }
3662
3663 // read Event Count value in the XML description
3664 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"event_count");
3665 if (pc_temp != nullptr) {
3666 if (!is_string_number(pc_temp)) {
3667 info_message("ERROR nica-scheduler$ Event count must be a number!", 1);
3668 free(pc_temp);
3669 return true;
3670 }
3671 event_count = atoi(pc_temp);
3672 free(pc_temp);
3673 if (event_count < 0) {
3674 info_message("ERROR nica-scheduler$ Event count must be a positive number or 0 (for all events)!",
3675 1);
3676 return true;
3677 }
3678 isEventCount = true;
3679 }
3680
3681 // read Merge Flag: whether merge files containing partial results
3682 // possible values: false = -1 (default), merge to the origin file name and delete partial files = 1
3683 // chain = 0 - store an additional TChain file containing links to all the partial files,
3684 // preserve = 2 - as' true' value, but not delete partial result files
3685 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"merge");
3686 int iMerge = -1; // false - not to merge
3687 if (pc_temp != nullptr) {
3688 char* lower_merge = convert_pchar_to_lowercase_new(pc_temp);
3689 if (strcmp(lower_merge, "true") == 0)
3690 iMerge = 1;
3691 if (strcmp(lower_merge, "chain") == 0)
3692 iMerge = 0;
3693 if (strcmp(lower_merge, "preserve") == 0)
3694 iMerge = 2;
3695
3696 free(pc_temp);
3697 delete lower_merge;
3698 }
3699 // read Parallel Mode value: whether parallelize processing of the file by events
3700 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"parallel_mode");
3701 if (pc_temp != nullptr) {
3702 parallel_mode = pc_temp;
3703 free(pc_temp);
3704 }
3705
3706 // form Input File List from the Simulation Catalogue of the UniConDa database
3707 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"sim_input");
3708 if (pc_temp != nullptr) {
3709 sim_db = pc_temp;
3710 free(pc_temp);
3711 }
3712 // form Input File List from the Experimental File Catalogue of the UniConDa database
3713 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"exp_input");
3714 if (pc_temp != nullptr) {
3715 exp_db = pc_temp;
3716 free(pc_temp);
3717 }
3718 // get Input File List from a previously executed job with the given name
3719 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"job_input");
3720 if (pc_temp != nullptr) {
3721 job_input = pc_temp;
3722 free(pc_temp);
3723 }
3724 // get Input File List from a previously executed macro with the given name
3725 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"macro_input");
3726 if (pc_temp != nullptr) {
3727 macro_input = pc_temp;
3728 free(pc_temp);
3729 }
3730 // form Input File List from the text file containing input file paths by lines
3731 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"file_input");
3732 if (pc_temp != nullptr) {
3733 file_input = pc_temp;
3734 free(pc_temp);
3735 }
3736 // form Input File List from the JSON file containing input file paths as an array of strings
3737 pc_temp = (char*)xmlGetProp(sub_node, (unsigned char*)"json_input");
3738 if (pc_temp != nullptr) {
3739 json_input = pc_temp;
3740 free(pc_temp);
3741 }
3742
3743 // parse information containing preparation of the files to be processed
3744 // operations: '<put>', '<get>', '<clean>'
3745 xmlNodePtr sub_file_node = sub_node->children;
3746 while (sub_file_node) {
3747 // if TAG is XML_ELEMENT_NODE then continue
3748 if (sub_file_node->type == XML_ELEMENT_NODE) {
3749 // if TAG means <put>: put the files to the special place before the processing
3750 if (strcmp((char*)sub_file_node->name, "put") == 0) {
3751 // read Copy Command to put the files on the intermediate storage
3752 pc_temp = (char*)xmlGetProp(sub_file_node, (unsigned char*)"command");
3753 if (pc_temp != nullptr) {
3754 put_command = pc_temp;
3755 free(pc_temp);
3756 }
3757 // read Directory Path, to which put the files on the intermediate storage
3758 pc_temp = (char*)xmlGetProp(sub_file_node, (unsigned char*)"directory");
3759 if (pc_temp == nullptr) {
3760 info_message("ERROR nica-scheduler$ There is no 'directory' property for the <put/> tag!",
3761 1);
3762 sub_file_node = sub_file_node->next;
3763 continue;
3764 }
3765 put_path = pc_temp;
3766 free(pc_temp);
3767 } // if <put/> attribute
3768
3769 // if tag means <get>: get the files from the temporary place after the processing
3770 if (strcmp((char*)sub_file_node->name, "get") == 0) {
3771 // read Copy Command to get result files from the intermediate storage
3772 pc_temp = (char*)xmlGetProp(sub_file_node, (unsigned char*)"command");
3773 if (pc_temp != nullptr) {
3774 get_command = pc_temp;
3775 free(pc_temp);
3776 }
3777 // read Directory Path, to which save the intermediate results and from where copy to the
3778 // destination storage
3779 pc_temp = (char*)xmlGetProp(sub_file_node, (unsigned char*)"directory");
3780 if (pc_temp == nullptr) {
3781 info_message("ERROR nica-scheduler$ There is no 'directory' property for the <get/> tag!",
3782 1);
3783 sub_file_node = sub_file_node->next;
3784 continue;
3785 }
3786 get_path = pc_temp;
3787 free(pc_temp);
3788
3789 // read additional result file path(s) or mask(s) to copy from the intermediate storage
3790 pc_temp = (char*)xmlGetProp(sub_file_node, (unsigned char*)"extras");
3791 if (pc_temp != nullptr) {
3792 get_extras = pc_temp;
3793 free(pc_temp);
3794 }
3795 } // if <get/> attribute
3796
3797 // if tag means <clean>: clean temporary files after the processing
3798 if (strcmp((char*)sub_file_node->name, "clean") == 0) {
3799 pc_temp = (char*)xmlGetProp(sub_file_node, (unsigned char*)"path");
3800 if (pc_temp != nullptr) {
3801 clean_path = pc_temp;
3802 free(pc_temp);
3803 }
3804 } // if <clean> attribute
3805 } // if sub_file_node->type == XML_ELEMENT_NODE
3806
3807 sub_file_node = sub_file_node->next;
3808 } // if file should be pre/post copied
3809
3810 vector<string> vecInputFiles;
3811 // PATH TO <INPUT> FILE (one or with a regular expression)
3812 if (nstr_file_in != nullptr) {
3813 string str_file_in = nstr_file_in.GetValue();
3814 vecInputFiles.push_back(str_file_in);
3815
3816 // check whether file contains wildcards (?,*)
3817 if ((str_file_in.find('?') != std::string::npos) || (str_file_in.find('*') != std::string::npos)
3818 || (str_file_in.find('+') != std::string::npos))
3819 {
3820 vecInputFiles = glob(str_file_in.c_str());
3821 if (isDebugMode)
3822 cout << "DEBUG nica-scheduler$ File count in the regular expression: " << vecInputFiles.size()
3823 << endl;
3824 }
3825 } // if there is <input> file
3826
3827 // LIST OF INPUT FILES FROM THE UNIFIED CONDITION DATABASE
3828 if ((sim_db != "") || (exp_db != "")) {
3829 TString sql, strConnection = "pgsql://" + (TString)job_par->dbSettings.db_host + "/"
3830 + (TString)job_par->dbSettings.db_name;
3831 if (sim_db == "")
3832 ParseDatabaseParameters(exp_db, sql, false);
3833 else
3834 ParseDatabaseParameters(sim_db, sql, true);
3835 TSQLServer* pSQLServer = TSQLServer::Connect(strConnection, job_par->dbSettings.db_username.c_str(),
3836 job_par->dbSettings.db_password.c_str());
3837 if (pSQLServer == 0x00) {
3838 info_message("ERROR nica-scheduler$ Connection to the database was not established!", 1);
3839 return true;
3840 }
3841
3842 if (isDebugMode)
3843 cout << "DEBUG nica-scheduler$ SQL query: " << sql << endl;
3844
3845 TSQLResult* res = pSQLServer->Query(sql);
3846 if (res == 0x00) {
3847 info_message("ERROR nica-scheduler$ An error was occurred during the SQL query!", 1);
3848 return true;
3849 }
3850 int nrows = res->GetRowCount();
3851 if (nrows == 0)
3852 info_message("nica-scheduler$ WARNING: there are no records for these parameters", 1);
3853 else {
3854 TSQLRow* row;
3855 while ((row = res->Next()) != nullptr) {
3856 string cur_file_path((char*)row->GetField(0));
3857#ifdef MICC_NO_MANAGEMENT
3858 if ((simDB != "") && (batch_com.run_command == "sbatch"))
3859 replace_string_in_text(cur_file_path, "eos", "eos/eos.jinr.ru");
3860#endif
3861 vecInputFiles.push_back(cur_file_path);
3862 delete row;
3863 } // while (row = res->Next())
3864 } // else nrows > 0
3865 delete res;
3866 if (pSQLServer)
3867 delete pSQLServer;
3868 } // if ((sim_db != nullptr) || (exp_db != nullptr))
3869
3870 // LIST OF INPUT FILES FROM A PREVIOUS JOB WITH THE GIVEN NAME
3871 if (job_input != "") {
3872 // search for output list in the map
3873 auto it = mapJobName2FileOut.find(job_input);
3874 if (it == mapJobName2FileOut.end())
3875 cout << "nica-scheduler$ WARNING: job (output list) was not found: " << job_input << endl;
3876 else {
3877 vector<nullString>* vec_string = it->second.get();
3878 for (vector<nullString>::iterator iter_output = vec_string->begin();
3879 iter_output != vec_string->end(); iter_output++)
3880 vecInputFiles.push_back((*iter_output).GetValue());
3881 }
3882 } // if (job_input != nullptr)
3883
3884 // LIST OF INPUT FILES FROM A PREVIOUS MACRO WITH THE GIVEN NAME
3885 if (macro_input != "") {
3886 // search for the macro with a given name
3887 bool is_found = false;
3888 for (const auto& iter : job_par->vecMacros) {
3889 structMacroPar* prev_macro = iter.get();
3890 if (strcmp(prev_macro->nstrMacroName.GetValue().c_str(), macro_input.c_str()) == 0) {
3891 for (const auto& iter_output : prev_macro->vecFiles) {
3892 if (iter_output.get()->nstrFileOut.isNull())
3893 continue;
3894 vecInputFiles.push_back(iter_output.get()->nstrFileOut.GetValue());
3895 } // for
3896
3897 is_found = true;
3898 break;
3899 } // if (strcmp(prev_macro->macro_name, macro_input) == 0)
3900 } // search for the macro with a given name
3901
3902 if (!is_found)
3903 cout << "nica-scheduler$ WARNING: macro (output list) was not found: " << macro_input << endl;
3904 } // if (macro_input != nullptr)
3905
3906 // LIST OF INPUT FILES (separated by newline symbols) FROM A TEXT FILE WITH THE GIVEN PATH
3907 if (file_input != "") {
3908 // open text file with input list
3909 string str_file_input = expand_path(file_input);
3910 ifstream listFile(str_file_input.c_str());
3911 if (listFile) {
3912 string list_line;
3913 while (getline(listFile, list_line))
3914 vecInputFiles.push_back(list_line);
3915 // cout<<"Current file: "<<list_line<<endl;
3916 } else
3917 info_message("ERROR nica-scheduler$ Can not open text file with file input list!", 1);
3918 } ////if (file_input != "")
3919
3920 // LIST OF INPUT FILES FROM A JSON FILE WITH THE GIVEN PATH
3921 if (json_input != "") {
3922 if (append_input_files_from_json(json_input, vecInputFiles))
3923 return true;
3924 }
3925
3926 // FORM INFORMATION ON INPUT, OUTPUT and INTERMEDIATE FILES of the macro
3927 bool isFileFound = false;
3928 for (unsigned int i = 0; i < vecInputFiles.size(); i++) {
3929 unique_ptr<structFilePar> filePar = make_unique<structFilePar>();
3930 // add input file
3931 filePar->strFileIn = trim(vecInputFiles[i]);
3932 // add output file
3933 if (nstr_file_out != nullptr)
3934 filePar->nstrFileOut.SetValue(form_file_name(nstr_file_out.GetValue(), filePar->strFileIn, i + 1,
3935 batch_com.batch_temp_directory));
3936 // add start event number
3937 if (isStartEvent)
3938 filePar->ptrStartEvent = make_unique<int>(start_event);
3939 // add event count
3940 if (isEventCount)
3941 filePar->ptrEventCount = make_unique<int>(event_count);
3942 // set parallel mode and the merge option
3943 if (parallel_mode != "")
3944 filePar->strParallelMode = parallel_mode;
3945
3946 filePar->iMerge = iMerge;
3947 // form put path
3948 if (put_path != "") {
3949 filePar->strPutCommand = put_command;
3950 filePar->strPutPath =
3951 form_file_name(put_path, filePar->strFileIn, i + 1, batch_com.batch_temp_directory);
3952 filePar->strPutPath =
3953 filePar->strPutPath + string("/") + get_file_name_with_ext(filePar->strFileIn);
3954 }
3955 // form get path
3956 if (get_path != "") {
3957 filePar->strGetCommand = get_command;
3958
3959 filePar->strGetPath =
3960 form_file_name(get_path, filePar->strFileIn, i + 1, batch_com.batch_temp_directory);
3961
3962 string strGetDirectory = filePar->strGetPath;
3963
3964 if (filePar->nstrFileOut == nullptr) {
3965 info_message("ERROR nica-scheduler$ <get> must employ output file path, but it is not set!", 1);
3966 return true;
3967 }
3968
3969 filePar->strGetPath =
3970 filePar->strGetPath + string("/") + get_file_name_with_ext(filePar->nstrFileOut.GetValue());
3971
3972 if (get_extras != "") {
3973 vector<string> vec_get_extra_path = string_to_vector(get_extras);
3974
3975 for (const string& cur_extra_path : vec_get_extra_path) {
3976 string strExtraPath = form_file_name(cur_extra_path, filePar->strFileIn, i + 1,
3977 batch_com.batch_temp_directory);
3978
3979 if (is_relative_file_path(strExtraPath))
3980 strExtraPath = strGetDirectory + string("/") + strExtraPath;
3981
3982 filePar->vecGetExtraPath.push_back(strExtraPath);
3983 }
3984 }
3985 }
3986 // form clean path
3987 if (clean_path != "") {
3988 vector<string> vec_clean_path = string_to_vector(clean_path);
3989 for (const string& cur_clean_path : vec_clean_path)
3990 filePar->vecCleanPath.push_back(
3991 form_file_name(cur_clean_path, filePar->strFileIn, i + 1, batch_com.batch_temp_directory));
3992 }
3993
3994 macro_par->vecFiles.push_back(std::move(filePar));
3995 isFileFound = true;
3996 }
3997
3998 if (!isFileFound) {
4000 "ERROR nica-scheduler$ No input files were found (possible tags: "
4001 "<input>, <file_input>, <json_input>,<sim_input>, <exp_input>, <job_input>, <macro_input>!",
4002 1);
4003 return true;
4004 }
4005 } // FILE TAG
4006
4007 sub_node = sub_node->next;
4008 } // while (sub_node)
4009
4010 return false;
4011}
int i
Definition P4_F32vec4.h:22
R__EXTERN C unsigned int sleep(unsigned int seconds)
R__EXTERN TSystem * gSystem
map< BATCH_SYSTEM_NAME, batch_commands > mapBS
void release()
Releases a resource from the semaphore.
condition_variable condition_
unsigned int count_
Semaphore(unsigned int resource_count=0)
void acquire()
Acquires a resource from the semaphore.
virtual const char * GetField(Int_t field)=0
virtual TSQLResult * Query(const char *sql)=0
static TSQLServer * Connect(const char *db, const char *uid, const char *pw)
const char * Data() const
static TString Format(const char *fmt,...)
string GetValue()
nullString & operator=(const nullString &str)
nullString(const nullString &str)
void SetValue(string str)
void SetValue(nullptr_t)
bool operator==(nullptr_t) const
void SetValue(char *pchar)
nullString(string str)
nullString(char *pchar)
bool operator!=(nullptr_t) const
nullString(nullptr_t)
map< string, string_view > mapContainerType
const char *const UNI_DB_HOST
const char *const UNI_DB_NAME
const char *const UNI_DB_PASSWORD
const char *const UNI_DB_USERNAME
RVec< PromoteType< T > > floor(const RVec< T > &v)
string int_to_string(int number)
char * convert_pchar_to_lowercase_new(char *input_char_array)
BATCH_SYSTEM_NAME
@ TORQUE_BATCH_SYSTEM
@ SGE_BATCH_SYSTEM
@ SLURM_BATCH_SYSTEM
string find_first_number(string const &str, bool isOnlyPositive=true)
string expand_path(string path)
string get_file_name(string path)
string find_last_number(string const &str, bool isOnlyPositive=true)
string get_directory_path(string file_path)
string get_file_name_with_ext(string path)
bool is_string_number(const string &s)
int get_linux_processor_count()
string get_app_dir_linux()
string trim(const string &str, const string &whitespace=" \t\r")
double byte_size_to_double(string byte_size_in_string, char convert_to='B')
void replace_string_in_text(string &text, string old_substring, string new_substring)
int get_batch_processor_count(BATCH_SYSTEM_NAME batch_system, string queue_name="")
string get_directory_name(string file_path)
void ThreadLocalProcess(shared_ptr< structThreadPar > thread_par)
void replace_string_in_text_ext(string &text, string old_substring, string new_substring)
void ShowHelpMessage()
void info_message(const string msg, bool is_error=0)
void skip_json_spaces(const string &text, size_t &pos)
bool has_wildcards(const string &path)
bool append_input_files_from_json(const string &json_input_path, vector< string > &vecInputFiles)
vector< string > glob(const string &path)
int get_batch_queue_job_count(batch_commands batch_com)
string form_file_name(string out_name, string in_name, int counter, string batch_temp_dir)
bool ParseMacroFiles(xmlNodePtr sub_node, shared_ptr< structJobPar > job_par, shared_ptr< structMacroPar > macro_par, multimap< string, unique_ptr< vector< nullString > > > &map_job_output, batch_commands batch_com)
string concatenate_job_ids(const vector< string > &vec_job_id, char separator)
bool is_relative_file_path(const string &path)
bool wait_batch_queue_limit(batch_commands batch_com, int queue_limit)
string GenerateOutputFilePath(string path, int counter)
static struct option long_options[]
bool parse_json_file_string(const string &text, size_t &pos, string &value, string &error)
void SubThreadProcessFile(shared_ptr< structSubThreadPar > subthread_par)
void CopyExtraGetFiles(structFilePar *cur_file)
bool isDebugMode
bool parse_json_file_list(const string &text, vector< string > &files, string &error)
void RunLocalCommandLine(shared_ptr< structThreadPar > thread_par, string command_line, int stack_counter)
void ParseDatabaseParameters(string input, TString &sql, bool is_simulation)
vector< string > string_to_vector(string str, const string &delimiters=",;")
std::map< string, struct_database_settings > predefined_database_settings
Definition settings.h:16
string sheduler_exclude_hosts_option
string check_status_command
string job_dependency_option
string operative_memory1_option
string sheduler_hosts_option
string array_dependency_option
string container_run_command
char sheduler_hosts_separation
string sheduler_queue_option
string set_job_variables
string sheduler_priority_option
string cancel_command
string batch_temp_directory
string sheduler_qos_option
string scheduler_run_job
string sheduler_workdir_option
char job_dependency_separator
string queue_count_command
vector< string > vecGetExtraPath
unique_ptr< int > ptrEventCount
nullString nstrFileOut
unique_ptr< int > ptrStartEvent
vector< string > vecCleanPath
nullString nstrQOS
nullString nstrProcCount
nullString nstrLogs
nullString nstrWorkDir
nullString nstrHosts
struct_database_settings dbSettings
nullString nstrQueue
vector< shared_ptr< structJobStackPar > > vecJobStack
string strOperativeMemory1
nullString nstrPriority
vector< shared_ptr< structMacroPar > > vecMacros
string strDdependencyName
structJobStackPar(shared_ptr< structMacroPar > macro_par)
structJobStackPar(string command_line)
shared_ptr< structMacroPar > sptrMacroParameter
unique_ptr< int > ptrEventCount
unique_ptr< int > ptrStartEvent
nullString nstrMacroName
vector< unique_ptr< structFilePar > > vecFiles
nullString nstrMacroPath
shared_ptr< structMacroPar > sptrMacroParameter
shared_ptr< structThreadPar > sptrThreadParameter
shared_ptr< Semaphore > semJobSemaphore
shared_ptr< structJobPar > sptrJobParameter
shared_ptr< mutex > mutJobMutex
struct_database_settings & operator=(const struct_database_settings &arg_db_settings)
struct_database_settings(string arg_db_host, string arg_db_name, string arg_db_username, string arg_db_password)
struct_database_settings(const struct_database_settings &arg_db_settings)
int main(void)
Definition test_FC.cpp:33