129 {
130
131
132 quill::BackendOptions backend_options;
133
134 backend_options.sleep_duration = std::chrono::microseconds{10};
135
136 backend_options.transit_event_buffer_initial_capacity = 2048;
137
138 backend_options.log_timestamp_ordering_grace_period =
139 std::chrono::microseconds{0};
140
141 backend_options.sink_min_flush_interval = std::chrono::milliseconds{100};
142
143 backend_options.check_printable_char = {};
144 quill::Backend::start(backend_options);
145 auto console_sink =
146 quill::Frontend::create_or_get_sink<quill::ConsoleSink>("console");
147 auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
148 "client_quill.log",
149 []() {
150 quill::FileSinkConfig cfg;
151 cfg.set_open_mode('w');
152 return cfg;
153 }(),
154 quill::FileEventNotifier{});
155 auto *logger = quill::Frontend::create_or_get_logger(
156 "combi", {std::move(console_sink), std::move(file_sink)},
157 quill::PatternFormatterOptions{"%(message)"},
158 quill::ClockSourceType::System);
159 logger->set_log_level(quill::LogLevel::TraceL3);
160
161 auto trace_csink =
162 quill::Frontend::create_or_get_sink<quill::ConsoleSink>("trace_console");
163 auto trace_fsink = quill::Frontend::create_or_get_sink<quill::FileSink>(
164 "client_traceback.log",
165 []() {
166 quill::FileSinkConfig cfg;
167 cfg.set_open_mode('w');
168 return cfg;
169 }(),
170 quill::FileEventNotifier{});
171 quill::Frontend::create_or_get_logger(
172 "_traceback", {std::move(trace_csink), std::move(trace_fsink)},
173 quill::PatternFormatterOptions{
174 " [%(log_level)] [%(source_location)] [%(caller_function)] \n "
175 "%(message)\n[end %(log_level)]"},
176 quill::ClockSourceType::System);
177
178
179 const auto logHome = std::filesystem::current_path();
181
182#if defined WITH_ASE_ORCA || EMBED_PYTHON || WITH_ASE_NWCHEM
184#endif
185
186#ifdef EONMPI
187 bool client_standalone = false;
188 if (getenv("EON_CLIENT_STANDALONE") != nullptr) {
189 client_standalone = true;
190 }
191 int number_of_clients;
192 if (!client_standalone) {
193 if (getenv("EON_SERVER_PATH") == nullptr) {
194 QUILL_LOG_ERROR(logger, "error: must set the env var EON_SERVER_PATH");
195 logger->flush_log();
196 return 1;
197 }
198 if (getenv("EON_NUMBER_OF_CLIENTS") == nullptr) {
199 QUILL_LOG_ERROR(logger,
200 "error: must set the env var EON_NUMBER_OF_CLIENTS");
201 logger->flush_log();
202 return 1;
203 }
204 number_of_clients = atoi(getenv("EON_NUMBER_OF_CLIENTS"));
205 } else {
206 number_of_clients = 1;
207 }
208
209 int eon_mpi_inited = 0;
210 MPI_Initialized(&eon_mpi_inited);
211 if (!eon_mpi_inited) {
212 int eon_mpi_provided = 0;
213 MPI_Init_thread(&argc, &argv, MPI_THREAD_MULTIPLE, &eon_mpi_provided);
214 }
215
217 std::string config_file = "config.ini";
218 if (client_standalone) {
220 config_file = "config_0.ini";
221 }
222 QUILL_LOG_INFO(logger, "Loading parameter file {}", config_file);
224 } else {
225 QUILL_LOG_INFO(logger, "Loading parameter file {}",
228 }
229 if (error) {
230 QUILL_LOG_ERROR(logger, "problem loading parameter file");
231 logger->flush_log();
232 MPI_Abort(MPI_COMM_WORLD, 1);
233 }
234
235
236 MPI_Barrier(MPI_COMM_WORLD);
237
238 int irank;
239 MPI_Comm_rank(MPI_COMM_WORLD, &irank);
240 int isize;
241 MPI_Comm_size(MPI_COMM_WORLD, &isize);
242
243 std::vector<int> process_types(isize);
244 int process_type;
245
246 process_type = 1;
247
248 MPI_Allgather(&process_type, 1, MPI_INT, &process_types[0], 1, MPI_INT,
249 MPI_COMM_WORLD);
250
251 int i, servers = 0, clients = 0, potentials = 0;
252 int server_rank = -1;
253 int my_client_number = -1;
254 std::vector<int> client_ranks;
255 for (i = 0; i < isize; i++) {
256 switch (process_types[i]) {
257 case 0:
258 servers++;
259 break;
260 case 1:
261 if (i == irank) {
262 my_client_number = clients;
263 }
264 clients++;
265 client_ranks.push_back(i);
266 break;
267 case 2:
268 potentials++;
269 break;
270 }
271 }
272
273 if (clients < number_of_clients) {
274 QUILL_LOG_ERROR(logger,
275 "didn't launch as many mpi client ranks as specified in "
276 "EON_NUMBER_OF_CLIENTS");
277 logger->flush_log();
278 MPI_Abort(MPI_COMM_WORLD, 1);
279 }
280 clients = number_of_clients;
281
283 if (potentials == 0 || potentials % clients != 0) {
284 QUILL_LOG_ERROR(logger,
285 "the MPI potential needs a nonzero number of potential "
286 "ranks divisible by EON_NUMBER_OF_CLIENTS ({} potential "
287 "ranks, {} clients)",
288 potentials, clients);
289 logger->flush_log();
290 MPI_Abort(MPI_COMM_WORLD, 1);
291 }
292 std::vector<int> potential_ranks(potentials);
293 int j;
294 for (i = 0, j = 0; i < isize; i++) {
295 if (process_types[i] == 2) {
296 potential_ranks[j] = i;
297 j++;
298 }
299 }
300 int potential_group_size = potentials / clients;
301
302 for (i = 0; i < clients; i++) {
303 MPI_Group orig_group, new_group;
304 MPI_Comm_group(MPI_COMM_WORLD, &orig_group);
305 int offset = i * potential_group_size;
306 MPI_Group_incl(orig_group, potential_group_size, &potential_ranks[offset],
307 &new_group);
308 MPI_Comm pot_comm;
309 MPI_Comm_create(MPI_COMM_WORLD, new_group, &pot_comm);
310 }
311
312 if (my_client_number < number_of_clients) {
314 potential_ranks[my_client_number * potential_group_size]);
315 }
316 }
317
318
320 for (i = 0; i < static_cast<int>(client_ranks.size()); i++) {
321 MPI_Group world_group, new_group;
322 MPI_Comm_group(MPI_COMM_WORLD, &world_group);
323 int r = client_ranks[i];
324 MPI_Group_incl(world_group, 1, &r, &new_group);
325 MPI_Comm new_comm;
326 MPI_Comm_create(MPI_COMM_WORLD, new_group, &new_comm);
327 if (new_comm != MPI_COMM_NULL) {
328 eonc::setMpiClientComm(parameters, new_comm);
329 }
330 QUILL_LOG_INFO(logger, "creating group with ranks: {}", r);
331 }
332 }
333
334 if (!client_standalone) {
335 server_rank = client_ranks[number_of_clients];
336 if (my_client_number == number_of_clients) {
337 std::ostringstream oss;
338 oss << client_ranks.at(0);
339 for (i = 1; i < number_of_clients; i++) {
340 oss << ":" << client_ranks.at(i);
341 }
342 setenv("EON_CLIENT_RANKS", oss.str().c_str(), 1);
343
344 wchar_t **py_argv =
345 static_cast<wchar_t **>(malloc(sizeof(wchar_t *) * 2));
346 py_argv[0] = Py_DecodeLocale(argv[0], nullptr);
347 char *program = getenv("EON_SERVER_PATH");
348 py_argv[1] = Py_DecodeLocale(program, nullptr);
349 QUILL_LOG_INFO(logger, "rank: {} becoming {}", irank, program);
350 Py_Initialize();
351 Py_Main(2, py_argv);
352 Py_FinalizeEx();
353
354 MPI_Finalize();
355 return 0;
356 } else if (my_client_number > number_of_clients) {
357 MPI_Finalize();
358 return 0;
359 }
360 }
361#endif
362
363#ifndef EONMPI
364 if (argc > 1) {
366 return 0;
367 }
368#endif
369
371
372#ifdef EONMPI
373
374 char logfilename[1024];
375 snprintf(logfilename, 1024, "eonclient_%i.log", my_client_number);
376
377 auto orig_path = std::filesystem::current_path();
378
379
380
381 const bool keepServing = !client_standalone;
382 while (true) {
383 std::filesystem::current_path(orig_path);
384 std::string path(1024, '\0');
385 int ready = 1;
386 if (!client_standalone) {
387 QUILL_LOG_INFO(
388 logger, "client: rank {} is ready, posting send to server rank: {}!",
389 irank, server_rank);
390
391
392 {
393 MPI_Request eon_rq;
394 MPI_Isend(&ready, 1, MPI_INT, server_rank, 1, MPI_COMM_WORLD, &eon_rq);
395 MPI_Request_free(&eon_rq);
396 }
397
398
399 MPI_Recv(&path[0], 1024, MPI_CHAR, server_rank, 0, MPI_COMM_WORLD,
400 MPI_STATUS_IGNORE);
401 if (path.starts_with("STOPCAR")) {
402 QUILL_LOG_INFO(logger, "rank {} got STOPCAR", irank);
403 MPI_Finalize();
404 return 0;
405 }
406 QUILL_LOG_INFO(logger, "client: rank: {} chdir to {}", irank, path);
407
408 if (const auto chdirError =
410 QUILL_LOG_ERROR(logger, "error: chdir: {}", *chdirError);
411 logger->flush_log();
412 MPI_Send(&path[0], 1024, MPI_CHAR, server_rank, 0, MPI_COMM_WORLD);
413 continue;
414 }
415 }
416#else
417 constexpr bool keepServing = false;
418#endif
419
420
421 auto stageFailedJobLogs = [&] {
422 logger->flush_log();
423 if (auto *trace =
424 quill::Frontend::get_logger(std::string{"_traceback"})) {
425 trace->flush_log();
426 }
427 for (const std::string_view logName :
428 {std::string_view{"client_quill.log"},
429 std::string_view{"client_traceback.log"}}) {
431 }
432 };
433
435
436 bool bundlingEnabled = false;
438 if (bundleSize <= 0) {
439 bundleSize = 1;
440 bundlingEnabled = false;
441 } else {
442 bundlingEnabled = true;
443 }
444
445 std::vector<std::string> bundledFilenames;
446 for (int i = 0; i < bundleSize; i++) {
447
448
449 const auto start_time = std::chrono::steady_clock::now();
450 if (bundleSize > 1)
451 QUILL_LOG_INFO(logger, "Beginning Job {} of {}", i + 1, bundleSize);
452 std::vector<std::string> unbundledFilenames;
453 if (bundlingEnabled) {
455 }
456
457
459 std::string config_file =
461 QUILL_LOG_INFO(logger, "Loading parameter file {}", config_file);
463
464 if (error) {
465 QUILL_LOG_ERROR(logger, "problem loading parameter file, stopping");
466 logger->flush_log();
467 if (keepServing) {
468 stageFailedJobLogs();
469 continue;
470 }
471 return 1;
472 }
473
474
475
478 std::make_unique<eonc::Parameters>(parameters), rt);
479 if (job == nullptr) {
480 QUILL_LOG_ERROR(logger, "error: Unknown job: {}",
481 std::string{magic_enum::enum_name<eonc::JobType>(
483 logger->flush_log();
484 if (keepServing) {
485 stageFailedJobLogs();
486 continue;
487 }
488 return 1;
489 }
490
491 std::vector<std::string> filenames;
492 try {
493 filenames = job->run();
494 } catch (int e) {
495 QUILL_LOG_CRITICAL(logger, "[ERROR] job exited on error {}", e);
496 logger->flush_log();
497 if (keepServing) {
498 stageFailedJobLogs();
499 continue;
500 }
501 return EXIT_FAILURE;
502 } catch (const std::exception &e) {
503 QUILL_LOG_CRITICAL(logger, "[ERROR] unhandled exception: {}", e.what());
504 logger->flush_log();
505 std::cerr << "[ERROR] unhandled exception: " << e.what() << "\n";
506 if (keepServing) {
507 stageFailedJobLogs();
508 continue;
509 }
510 return EXIT_FAILURE;
511 }
512
513 job->releasePotential();
515 job.reset();
516 filenames.push_back(std::string("_potcalls.json"));
517
518
519 auto end_time = std::chrono::steady_clock::now();
520 std::chrono::duration<double> elapsed = end_time - start_time;
521
522 double utime = 0, stime = 0, rtime = 0;
524
525 QUILL_LOG_INFO(logger, "Timing Information:");
526 QUILL_LOG_INFO(logger, " Real time: {:.3f} seconds", elapsed.count());
527 QUILL_LOG_INFO(logger, " User time: {:.3f} seconds", utime);
528 QUILL_LOG_INFO(logger, " System time: {:.3f} seconds", stime);
529
530
531
532 std::ofstream result_file("results.dat", std::ios::app);
533 if (result_file.is_open()) {
534 result_file << std::format("{:.12e} time_seconds\n", elapsed.count());
535#ifndef _WIN32
536 result_file << std::format("{:.12e} user_time\n", utime);
537 result_file << std::format("{:.12e} system_time\n", stime);
538#endif
539 } else {
540 QUILL_LOG_ERROR(logger, "Failed to write timing to results.dat");
541 }
542 result_file.close();
543
544 logger->flush_log();
545 if (auto *trace =
546 quill::Frontend::get_logger(std::string{"_traceback"})) {
547 trace->flush_log();
548 }
549
550
551 constexpr std::string_view jobLogs[] = {"client_quill.log",
552 "client_traceback.log"};
553 for (const std::string_view logName : jobLogs) {
555 filenames.push_back(std::string{logName});
556 }
557 }
558
559 {
560 std::ofstream manifest("return_files.dat");
561 if (manifest) {
562 for (const auto &fn : filenames) {
563 manifest << fn << "\n";
564 }
565 }
566 filenames.push_back(std::string("return_files.dat"));
567 }
568
569 if (bundlingEnabled) {
572 } else {
573 bundledFilenames = filenames;
574 }
575 }
576
577#ifdef EONMPI
578 if (client_standalone) {
579 break;
580 }
581 {
582 MPI_Request eon_rq;
583 MPI_Isend(&path[0], 1024, MPI_CHAR, server_rank, 0, MPI_COMM_WORLD,
584 &eon_rq);
585
586
587 MPI_Wait(&eon_rq, MPI_STATUS_IGNORE);
588 }
589
590
591 }
592#endif
593
594#ifdef OSX
595#ifndef __aarch64__
596 print_memory_usage();
597#endif
598#endif
599
600#ifdef EONMPI
601
602
603 MPI_Finalize();
604#endif
605
606
607 quill::Backend::stop();
608 return EXIT_SUCCESS;
609}
void set_mpi_potential_rank(int rank)
const potential_options_t & potential_options() const
const main_options_t & main_options() const
int load(std::string_view filename)
void write_summary(const std::string &path="_potcalls.json") const
Move-only composition root for process resources (dlopen loaders and the potential registry).
PotRegistry & pots() noexcept
std::string getRelevantFile(std::string filename)
bool stageReturnLog(std::string_view logHome, std::string_view name)
Copy name from logHome into the current directory when that file is not already this directory's copy...
std::optional< std::string > enterJobDirectory(std::string_view jobPath)
Enter jobPath as the working directory.
std::unique_ptr< Job > makeJob(std::unique_ptr< Parameters > params, Runtime &runtime)
Borrow: caller keeps Runtime alive.
void getTime(double *real, double *user, double *sys)
bool existsFile(std::string filename)
void bundle(int number, const std::vector< std::string > &filenames, std::vector< std::string > *bundledFilenames)
void deleteUnbundledFiles(const std::vector< std::string > &unbundledFilenames)
std::vector< std::string > unbundle(int number)
void ensure_interpreter()
void commandLine(int argc, char **argv)