Loading...
Searching...
No Matches
ClientEON.cpp File Reference
#include "eon/BaseStructures.h"
#include "eon/Bundling.h"
#include "eon/CommandLine.h"
#include "eon/EpiCenters.h"
#include "eon/HelperFunctions.h"
#include "eon/Job.h"
#include "eon/Parameters.h"
#include "eon/PotRegistry.h"
#include "eon/Potential.h"
#include "version.h"
#include <cstdlib>
#include <exception>
#include <format>
#include <fstream>
#include <iostream>
#include <string_view>
#include <cerrno>
#include <chrono>
#include <cstring>
#include <ctime>
#include <filesystem>
#include "eon/fpe_handler.h"
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/utsname.h>
#include <unistd.h>

Go to the source code of this file.

Functions

void printSystemInfo ()
static int eonClientMain (int argc, char **argv)
int main (int argc, char **argv)

Function Documentation

◆ eonClientMain()

int eonClientMain ( int argc,
char ** argv )
static

Definition at line 132 of file ClientEON.cpp.

132 {
133 // --- Start Logging setup
134 // Configure backend for optimal performance (see BackendOptions.h)
135 quill::BackendOptions backend_options;
136 // Use 10us sleep for balanced performance (10x faster than 100us default)
137 backend_options.sleep_duration = std::chrono::microseconds{10};
138 // Larger initial buffer avoids reallocs in NEB (43 LOG calls/iter)
139 backend_options.transit_event_buffer_initial_capacity = 2048;
140 // eOn is single-threaded; SPSC queue guarantees ordering, no grace needed
141 backend_options.log_timestamp_ordering_grace_period =
142 std::chrono::microseconds{0};
143 // Flush more frequently for better responsiveness
144 backend_options.sink_min_flush_interval = std::chrono::milliseconds{100};
145 // Disable per-string printable char scan (eOn logs numeric data only)
146 backend_options.check_printable_char = {};
147 quill::Backend::start(backend_options);
148 auto console_sink =
149 quill::Frontend::create_or_get_sink<quill::ConsoleSink>("console");
150 auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>(
151 "client_quill.log",
152 []() {
153 quill::FileSinkConfig cfg;
154 cfg.set_open_mode('w');
155 return cfg;
156 }(),
157 quill::FileEventNotifier{});
158 auto *logger = quill::Frontend::create_or_get_logger(
159 "combi", {std::move(console_sink), std::move(file_sink)},
160 quill::PatternFormatterOptions{"%(message)"},
161 quill::ClockSourceType::System);
162 logger->set_log_level(quill::LogLevel::TraceL3);
163 // Traceback logger
164 auto trace_csink =
165 quill::Frontend::create_or_get_sink<quill::ConsoleSink>("trace_console");
166 auto trace_fsink = quill::Frontend::create_or_get_sink<quill::FileSink>(
167 "client_traceback.log",
168 []() {
169 quill::FileSinkConfig cfg;
170 cfg.set_open_mode('w');
171 return cfg;
172 }(),
173 quill::FileEventNotifier{});
174 quill::Frontend::create_or_get_logger(
175 "_traceback", {std::move(trace_csink), std::move(trace_fsink)},
176 quill::PatternFormatterOptions{
177 " [%(log_level)] [%(source_location)] [%(caller_function)] \n "
178 "%(message)\n[end %(log_level)]"},
179 quill::ClockSourceType::System);
180 //--- End logging setup
181 Parameters parameters;
182
183#if defined WITH_ASE_ORCA || EMBED_PYTHON || WITH_ASE_NWCHEM
185#endif
186
187#ifdef EONMPI
188 bool client_standalone = false;
189 if (getenv("EON_CLIENT_STANDALONE") != nullptr) {
190 client_standalone = true;
191 }
192 int number_of_clients;
193 if (!client_standalone) {
194 if (getenv("EON_SERVER_PATH") == nullptr) {
195 QUILL_LOG_ERROR(logger, "error: must set the env var EON_SERVER_PATH");
196 logger->flush_log();
197 return 1;
198 }
199 if (getenv("EON_NUMBER_OF_CLIENTS") == nullptr) {
200 QUILL_LOG_ERROR(logger,
201 "error: must set the env var EON_NUMBER_OF_CLIENTS");
202 logger->flush_log();
203 return 1;
204 }
205 number_of_clients = atoi(getenv("EON_NUMBER_OF_CLIENTS"));
206 } else {
207 number_of_clients = 1;
208 }
209
210 int eon_mpi_inited = 0;
211 MPI_Initialized(&eon_mpi_inited);
212 if (!eon_mpi_inited) {
213 int eon_mpi_provided = 0;
214 MPI_Init_thread(&argc, &argv, MPI_THREAD_MULTIPLE, &eon_mpi_provided);
215 }
216
217 int error;
218 std::string config_file = "config.ini";
219 if (client_standalone) {
220 if (eonc::helpers::existsFile("config_0.ini")) {
221 config_file = "config_0.ini";
222 }
223 QUILL_LOG_INFO(logger, "Loading parameter file {}", config_file);
224 error = parameters.load(config_file);
225 } else {
226 QUILL_LOG_INFO(logger, "Loading parameter file {}",
227 parameters.main_options.iniFilename);
228 error = parameters.load(parameters.main_options.iniFilename);
229 }
230 if (error) {
231 QUILL_LOG_ERROR(logger, "problem loading parameter file");
232 logger->flush_log();
233 MPI_Abort(MPI_COMM_WORLD, 1);
234 }
235
236 // XXX: Barrier for gpaw-python
237 MPI_Barrier(MPI_COMM_WORLD);
238
239 int irank;
240 MPI_Comm_rank(MPI_COMM_WORLD, &irank);
241 int isize;
242 MPI_Comm_size(MPI_COMM_WORLD, &isize);
243
244 std::vector<int> process_types(isize);
245 int process_type;
246
247 process_type = 1;
248
249 MPI_Allgather(&process_type, 1, MPI_INT, &process_types[0], 1, MPI_INT,
250 MPI_COMM_WORLD);
251
252 int i, servers = 0, clients = 0, potentials = 0;
253 int server_rank = -1;
254 int my_client_number = -1;
255 std::vector<int> client_ranks;
256 for (i = 0; i < isize; i++) {
257 switch (process_types[i]) {
258 case 0:
259 servers++;
260 break;
261 case 1:
262 if (i == irank) {
263 my_client_number = clients;
264 }
265 clients++;
266 client_ranks.push_back(i);
267 break;
268 case 2:
269 potentials++;
270 break;
271 }
272 }
273
274 if (clients < number_of_clients) {
275 QUILL_LOG_ERROR(logger,
276 "didn't launch as many mpi client ranks as specified in "
277 "EON_NUMBER_OF_CLIENTS");
278 logger->flush_log();
279 MPI_Abort(MPI_COMM_WORLD, 1);
280 }
281 clients = number_of_clients;
282
283 if (parameters.potential_options.potential == PotType::MPI) {
284 std::vector<int> potential_ranks(potentials);
285 int j;
286 for (i = 0, j = 0; i < isize; i++) {
287 if (process_types[i] == 2) {
288 potential_ranks[j] = i;
289 j++;
290 }
291 }
292 int potential_group_size = potentials / clients;
293
294 for (i = 0; i < clients; i++) {
295 MPI_Group orig_group, new_group;
296 MPI_Comm_group(MPI_COMM_WORLD, &orig_group);
297 int offset = i * potential_group_size;
298 MPI_Group_incl(orig_group, potential_group_size, &potential_ranks[offset],
299 &new_group);
300 MPI_Comm pot_comm;
301 MPI_Comm_create(MPI_COMM_WORLD, new_group, &pot_comm);
302 }
303
304 if (my_client_number < number_of_clients) {
305 parameters.potential_options.MPIPotentialRank =
306 potential_ranks[my_client_number * potential_group_size];
307 }
308 }
309
310 // LAMMPS MPI communicator setup (runtime check, not compile-time)
311 if (parameters.potential_options.potential == PotType::LAMMPS) {
312 for (i = 0; i < static_cast<int>(client_ranks.size()); i++) {
313 MPI_Group world_group, new_group;
314 MPI_Comm_group(MPI_COMM_WORLD, &world_group);
315 int r = client_ranks[i];
316 MPI_Group_incl(world_group, 1, &r, &new_group);
317 MPI_Comm new_comm;
318 MPI_Comm_create(MPI_COMM_WORLD, new_group, &new_comm);
319 if (new_comm != MPI_COMM_NULL) {
320 parameters.potential_options.MPIClientComm = new_comm;
321 }
322 QUILL_LOG_INFO(logger, "creating group with ranks: {}", r);
323 }
324 }
325
326 if (!client_standalone) {
327 server_rank = client_ranks[number_of_clients];
328 if (my_client_number == number_of_clients) {
329 std::ostringstream oss;
330 oss << client_ranks.at(0);
331 for (i = 1; i < number_of_clients; i++) {
332 oss << ":" << client_ranks.at(i);
333 }
334 setenv("EON_CLIENT_RANKS", oss.str().c_str(), 1);
335
336 wchar_t **py_argv =
337 static_cast<wchar_t **>(malloc(sizeof(wchar_t *) * 2));
338 py_argv[0] = Py_DecodeLocale(argv[0], nullptr);
339 char *program = getenv("EON_SERVER_PATH");
340 py_argv[1] = Py_DecodeLocale(program, nullptr);
341 QUILL_LOG_INFO(logger, "rank: {} becoming {}", irank, program);
342 Py_Initialize();
343 Py_Main(2, py_argv);
344 Py_FinalizeEx();
345 // GH
346 MPI_Finalize();
347 return 0;
348 } else if (my_client_number > number_of_clients) {
349 MPI_Finalize();
350 return 0;
351 }
352 }
353#endif
354
355#ifndef EONMPI
356 if (argc > 1) {
357 eonc::commandLine(argc, argv);
358 return 0;
359 }
360#endif
361
362 eonc::enableFPE(); // from ExceptionsEON.h
363
364 auto start_time = std::chrono::steady_clock::now();
365
366#ifdef EONMPI
367 // XXX: When do we stop? The server should probably tell everyone when to
368 // stop.
369 char logfilename[1024];
370 snprintf(logfilename, 1024, "eonclient_%i.log", my_client_number);
371
372 auto orig_path = std::filesystem::current_path();
373 while (true) {
374 std::filesystem::current_path(orig_path);
375 std::string path(1024, '\0');
376 int ready = 1;
377 if (!client_standalone) {
378 QUILL_LOG_INFO(
379 logger, "client: rank {} is ready, posting send to server rank: {}!",
380 irank, server_rank);
381 // Tag "1" is to interrupt the main loop and tell the communicator that a
382 // client is ready
383 {
384 MPI_Request eon_rq;
385 MPI_Isend(&ready, 1, MPI_INT, server_rank, 1, MPI_COMM_WORLD, &eon_rq);
386 MPI_Request_free(&eon_rq);
387 }
388
389 // Get the path we should run in from the server
390 MPI_Recv(&path[0], 1024, MPI_CHAR, server_rank, 0, MPI_COMM_WORLD,
391 MPI_STATUS_IGNORE);
392 if (path.starts_with("STOPCAR")) {
393 QUILL_LOG_INFO(logger, "rank {} got STOPCAR", irank);
394 MPI_Finalize();
395 return 0;
396 }
397 QUILL_LOG_INFO(logger, "client: rank: {} chdir to {}", irank, path);
398
399 try {
400 std::filesystem::current_path(path.c_str());
401 } catch (const std::filesystem::filesystem_error &e) {
402 QUILL_LOG_ERROR(logger, "error: chdir: {}", e.what());
403 }
404 }
405#endif
406
408
409 // XXX(rg): Be more gentle here
410 bool bundlingEnabled = false;
411 int bundleSize = -1; // eonc::getBundleSize();
412 if (bundleSize == 0) {
413 bundleSize = 1;
414 } else if (bundleSize == -1) {
415 // Not using bundling
416 bundleSize = 1;
417 bundlingEnabled = false;
418 }
419
420 std::vector<std::string> bundledFilenames;
421 for (int i = 0; i < bundleSize; i++) {
422 if (bundleSize > 1)
423 QUILL_LOG_INFO(logger, "Beginning Job {} of {}", i + 1, bundleSize);
424 std::vector<std::string> unbundledFilenames;
425 if (bundlingEnabled) {
426 unbundledFilenames = eonc::unbundle(i);
427 }
428
429 // check to see if parameters file exists before loading
430 int error = 0;
431 std::string config_file =
432 eonc::helpers::getRelevantFile(parameters.main_options.iniFilename);
433 QUILL_LOG_INFO(logger, "Loading parameter file {}", config_file);
434 error = parameters.load(config_file);
435
436 if (error) {
437 QUILL_LOG_ERROR(logger, "problem loading parameter file, stopping");
438 logger->flush_log();
439 exit(1);
440 abort();
441 }
442
443 // Determine what type of job we are running according to the parameters
444 // file.
445 auto job =
446 eonc::helpers::makeJob(std::make_unique<Parameters>(parameters));
447 if (job == nullptr) {
448 QUILL_LOG_ERROR(logger, "error: Unknown job: {}",
449 std::string{magic_enum::enum_name<JobType>(
450 parameters.main_options.job)});
451 logger->flush_log();
452 return 1;
453 }
454
455 std::vector<std::string> filenames;
456 try {
457 filenames = job->run();
458 } catch (int e) {
459 QUILL_LOG_CRITICAL(logger, "[ERROR] job exited on error {}", e);
460 logger->flush_log();
461 } catch (const std::exception &e) {
462 QUILL_LOG_CRITICAL(logger, "[ERROR] unhandled exception: {}", e.what());
463 logger->flush_log();
464 std::cerr << "[ERROR] unhandled exception: " << e.what() << "\n";
465 return EXIT_FAILURE;
466 }
467
468 job.reset(); // Force Potential destruction so PotRegistry records entries
470 filenames.push_back(std::string("_potcalls.json"));
471 filenames.push_back(std::string("client_quill.log"));
472 filenames.push_back(std::string("client_traceback.log"));
473
474 {
475 std::ofstream manifest("return_files.dat");
476 if (manifest) {
477 for (const auto &fn : filenames) {
478 manifest << fn << "\n";
479 }
480 }
481 filenames.push_back(std::string("return_files.dat"));
482 }
483
484 // Finalize Timing Information
485 auto end_time = std::chrono::steady_clock::now();
486 std::chrono::duration<double> elapsed = end_time - start_time;
487
488 double utime = 0, stime = 0, rtime = 0;
489 eonc::helpers::getTime(&rtime, &utime, &stime);
490
491 QUILL_LOG_INFO(logger, "Timing Information:");
492 QUILL_LOG_INFO(logger, " Real time: {:.3f} seconds", elapsed.count());
493 QUILL_LOG_INFO(logger, " User time: {:.3f} seconds", utime);
494 QUILL_LOG_INFO(logger, " System time: {:.3f} seconds", stime);
495
496 // results.dat contract is "<value> <key>" (same as all job writers and
497 // eon.fileio.parse_results / eon_schema.jobs adapters).
498 std::ofstream result_file("results.dat", std::ios::app);
499 if (result_file.is_open()) {
500 result_file << std::format("{:.12e} time_seconds\n", elapsed.count());
501#ifndef _WIN32
502 result_file << std::format("{:.12e} user_time\n", utime);
503 result_file << std::format("{:.12e} system_time\n", stime);
504#endif
505 } else {
506 QUILL_LOG_ERROR(logger, "Failed to write timing to results.dat");
507 }
508
509 if (bundlingEnabled) {
510 eonc::bundle(i, filenames, &bundledFilenames);
511 eonc::deleteUnbundledFiles(unbundledFilenames);
512 } else {
513 bundledFilenames = filenames;
514 }
515 }
516
517#ifdef EONMPI
518 if (client_standalone) {
519 break;
520 }
521 {
522 MPI_Request eon_rq;
523 MPI_Isend(&path[0], 1024, MPI_CHAR, server_rank, 0, MPI_COMM_WORLD,
524 &eon_rq);
525 MPI_Request_free(&eon_rq);
526 }
527
528 // End of MPI while loop
529 }
530#endif
531
532#ifdef OSX
533#ifndef __aarch64__
534 print_memory_usage();
535#endif
536#endif
537
538#ifdef EONMPI
539 // Clean shutdown for both the standalone single-rank case and the
540 // client/server case; the standalone path previously aborted MPI_COMM_WORLD.
541 MPI_Finalize();
542#endif
543
544 // Ensure all queued log messages are flushed before exiting
545 quill::Backend::stop();
546 return EXIT_SUCCESS;
547}
void printSystemInfo()
Definition ClientEON.cpp:99
static PotRegistry & get() noexcept
Process-lifetime singleton.
void write_summary(const std::string &path="_potcalls.json") const
std::string error()
Definition DynLib.h:82
std::string getRelevantFile(std::string filename)
std::unique_ptr< Job > makeJob(std::unique_ptr< Parameters > params)
Definition Job.cpp:39
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)
Definition Bundling.cpp:175
void deleteUnbundledFiles(const std::vector< std::string > &unbundledFilenames)
Definition Bundling.cpp:140
std::vector< std::string > unbundle(int number)
Definition Bundling.cpp:80
void ensure_interpreter()
Definition NbGuard.h:21
void commandLine(int argc, char **argv)
void enableFPE()

◆ main()

int main ( int argc,
char ** argv )

Definition at line 568 of file ClientEON.cpp.

568 {
569 try {
570 return eonClientMain(argc, argv);
571 } catch (const std::exception &e) {
572 return reportFatal(e.what());
573 } catch (...) {
574 return reportFatal("exception of unknown type");
575 }
576}
static int eonClientMain(int argc, char **argv)

◆ printSystemInfo()

void printSystemInfo ( )

Definition at line 99 of file ClientEON.cpp.

99 {
100 auto *log = eonc::log::get();
101 QUILL_LOG_INFO(log, "eOn Client");
102 QUILL_LOG_INFO(log, "{}", VERSION_STRING);
103#ifndef __aarch64__
104 QUILL_LOG_INFO(log, "OS: {}", OS_INFO);
105 QUILL_LOG_INFO(log, "Arch: {}", ARCH);
106#endif
107
108#ifdef _WIN32
109 TCHAR hostname[MAX_COMPUTERNAME_LENGTH + 1];
110 DWORD size = sizeof(hostname) / sizeof(hostname[0]);
111 if (GetComputerName(hostname, &size)) {
112 QUILL_LOG_INFO(log, "Hostname: {}", hostname);
113 } else {
114 QUILL_LOG_ERROR(log, "Failed to get hostname");
115 }
116 QUILL_LOG_INFO(log, "PID: {}", GetCurrentProcessId());
117#else
118 struct utsname systemInfo;
119 int status = uname(&systemInfo);
120 if (status == 0) {
121 QUILL_LOG_INFO(log, "Hostname: {}", systemInfo.nodename);
122 QUILL_LOG_INFO(log, "PID: {}", getpid());
123 } else {
124 QUILL_LOG_ERROR(log, "Failed to get system information");
125 }
126#endif
127
128 std::filesystem::path cwd = std::filesystem::current_path();
129 QUILL_LOG_INFO(log, "DIR: {}", cwd.string());
130}
quill::Logger * get() noexcept
Get or create the default "combi" logger.
Definition EonLogger.h:44