YAC 3.20.0
Yet Another Coupler
Loading...
Searching...
No Matches
io_utils.c
Go to the documentation of this file.
1// Copyright (c) 2024 The YAC Authors
2//
3// SPDX-License-Identifier: BSD-3-Clause
4
5#include <string.h>
6#include <stdio.h>
7#include <dirent.h>
8#include <sys/stat.h>
9#include <unistd.h>
10#include "yac_mpi_internal.h"
11#include "io_utils.h"
12#include "math.h"
13
14#ifdef YAC_NETCDF_ENABLED
15#include <netcdf.h>
16#endif
17
18#define IO_RANK_LIST_STR "YAC_IO_RANK_LIST"
19#define IO_MAX_NUM_RANKS_STR "YAC_IO_MAX_NUM_RANKS"
20#define IO_RANK_EXCLUDE_LIST_STR "YAC_IO_RANK_EXCLUDE_LIST"
21#define IO_MAX_NUM_RANKS_PER_NODE "YAC_IO_MAX_NUM_RANKS_PER_NODE"
22#define DEFAULT_MAX_NUM_IO_RANK_PER_NODE (1)
23
24static inline int compare_int(const void * a, const void * b) {
25
26 int const * a_ = a, * b_ = b;
27
28 return (*a_ > *b_) - (*b_ > *a_);
29}
30
31static void read_rank_list(
32 char const * env_name, MPI_Comm comm, size_t * num_ranks_, int ** ranks_) {
33
34 int rank;
35 yac_mpi_call(MPI_Comm_rank(comm, &rank), comm);
36
37 size_t num_ranks = 0;
38 int * ranks = NULL;
39
40 // environment is only checked on rank 0, results are broadcasted
41 // to other processes
42 if (rank == 0) {
43
44 // check whether the user provided a list of ranks for IO
45 char * rank_list_str = getenv(env_name);
46 if ((rank_list_str != NULL) && (rank_list_str[0] != '\0')) {
47
48 int temp_num_ranks = 1;
49 for (char * curr_char = rank_list_str + 1; *curr_char != '\0'; ++curr_char)
50 if (*curr_char == ',') ++temp_num_ranks;
51
52 char * rank_list_copy = xstrdup(rank_list_str);
53 num_ranks = 0;
54 int * world_ranks = xmalloc(temp_num_ranks * sizeof(*world_ranks));
55 int world_size;
56 yac_mpi_call(MPI_Comm_size(MPI_COMM_WORLD, &world_size), MPI_COMM_WORLD);
57
58 // parse rank list
59 char * rank_str = strtok(rank_list_copy, ",");
60 while (rank_str != NULL) {
61 int curr_rank = atoi(rank_str);
62 YAC_ASSERT_F(curr_rank >= 0, "\"%s\" is not a valid rank", rank_str);
64 curr_rank < world_size, "rank %d exceeds size of MPI_COMM_WORLD (%d)",
65 curr_rank, world_size);
66 world_ranks[num_ranks++] = curr_rank;
67 rank_str = strtok(NULL, ",");
68 }
69 free(rank_list_copy);
70
71 // sort ranks
72 qsort(world_ranks, num_ranks, sizeof(*world_ranks), compare_int);
73
74 // remove duplicated ranks
75 yac_remove_duplicates_int(world_ranks, &num_ranks);
76
77 // translate rank list from MPI_COMM_WORLD to comm
78 ranks = xmalloc(num_ranks * sizeof(ranks));
79 MPI_Group world_group, comm_group;
81 MPI_Comm_group(MPI_COMM_WORLD, &world_group), MPI_COMM_WORLD);
82 yac_mpi_call(MPI_Comm_group(comm, &comm_group), comm);
84 MPI_Group_translate_ranks(
85 world_group, (int)num_ranks, world_ranks,
86 comm_group, ranks), MPI_COMM_WORLD);
87 yac_mpi_call(MPI_Group_free(&comm_group), comm);
88 yac_mpi_call(MPI_Group_free(&world_group), MPI_COMM_WORLD);
89 free(world_ranks);
90
91 // remove ranks, which are not avaible in comm
92 size_t new_num_ranks = 0;
93 for (size_t i = 0; i < num_ranks; ++i) {
94 if (ranks[i] != MPI_UNDEFINED) {
95 if (i != new_num_ranks)
96 ranks[new_num_ranks] = ranks[i];
97 ++new_num_ranks;
98 }
99 }
100 num_ranks = new_num_ranks;
101
102 // sort ranks
103 qsort(ranks, num_ranks, sizeof(*ranks), compare_int);
104 }
105
106 // broadcast ranks
108 MPI_Bcast(&num_ranks, 1, YAC_MPI_SIZE_T, 0, comm), comm);
109 if (num_ranks > 0) {
110 ranks = xrealloc(ranks, num_ranks * sizeof(*ranks));
112 MPI_Bcast(ranks, (int)num_ranks, MPI_INT, 0, comm), comm);
113 } else {
114 free(ranks);
115 }
116 } else {
117
118 // receive ranks from root
120 MPI_Bcast(&num_ranks, 1, YAC_MPI_SIZE_T, 0, comm), comm);
121 if (num_ranks > 0) {
122 ranks = xmalloc(num_ranks * sizeof(*ranks));
124 MPI_Bcast(ranks, (int)num_ranks, MPI_INT, 0, comm), comm);
125 }
126 }
127 *num_ranks_ = num_ranks;
128 *ranks_ = ranks;
129}
130
132 MPI_Comm comm, size_t * num_io_ranks, int ** io_ranks) {
133
134 read_rank_list(IO_RANK_LIST_STR, comm, num_io_ranks, io_ranks);
135}
136
138 MPI_Comm comm, size_t * num_io_ranks_, int ** io_ranks_) {
139
140 int rank, size;
141 yac_mpi_call(MPI_Comm_rank(comm, &rank), comm);
142 yac_mpi_call(MPI_Comm_size(comm, &size), comm);
143
144 int max_num_io_rank_per_node;
145 // get the maximum number of io ranks per node from the environment variable
146 max_num_io_rank_per_node =
149
150 // if there is no limit on the number of io ranks per node
151 if (max_num_io_rank_per_node == -1) return;
152
153 // create one communicator per node
154 MPI_Comm node_comm;
156 MPI_Comm_split_type(
157 comm, MPI_COMM_TYPE_SHARED, rank, MPI_INFO_NULL, &node_comm), comm);
158
159 // determine rank within node comm
160 int node_rank;
161 yac_mpi_call(MPI_Comm_rank(node_comm, &node_rank), node_comm);
162
163 // translate io ranks to node ranks
164 int * node_ranks = xmalloc(*num_io_ranks_ * sizeof(*node_ranks));
165 MPI_Group io_group, node_group;
166 yac_mpi_call(MPI_Comm_group(comm, &io_group), comm);
167 yac_mpi_call(MPI_Comm_group(node_comm, &node_group), node_comm);
169 MPI_Group_translate_ranks(
170 io_group, (int)*num_io_ranks_, *io_ranks_,
171 node_group, node_ranks), comm);
172 yac_mpi_call(MPI_Group_free(&node_group), node_comm);
173 yac_mpi_call(MPI_Group_free(&io_group), comm);
174 yac_mpi_call(MPI_Comm_free(&node_comm), comm);
175
176 // remove MPI_UNDEFINED entries
177 size_t num_node_io_ranks = 0;
178 for (size_t i = 0; i < *num_io_ranks_; ++i) {
179 if (node_ranks[i] != MPI_UNDEFINED) {
180 if (i != num_node_io_ranks)
181 node_ranks[num_node_io_ranks] = node_ranks[i];
182 ++num_node_io_ranks;
183 }
184 }
185
186 // sort ranks
187 qsort(node_ranks, num_node_io_ranks, sizeof(*node_ranks), compare_int);
188
189 // check whether local process is within the first n io ranks on its node
190 int local_is_io_rank = 0;
191 for (size_t i = 0;
192 (i < num_node_io_ranks) &&
193 (i < (size_t)max_num_io_rank_per_node) && !local_is_io_rank; ++i)
194 if (node_ranks[i] == node_rank) local_is_io_rank = 1;
195 free(node_ranks);
196
197 // determine io ranks on all nodes
198 int * is_io_rank = xmalloc((size_t)size * sizeof(*is_io_rank));
200 MPI_Allgather(
201 &local_is_io_rank, 1, MPI_INT, is_io_rank, 1, MPI_INT, comm), comm);
202
203 // compress is_io_rank into io_ranks
204 size_t num_io_ranks = 0;
205 for (int i = 0; i < size; ++i)
206 if (is_io_rank[i]) is_io_rank[num_io_ranks++] = i;
207
208 free(*io_ranks_);
209 *num_io_ranks_ = num_io_ranks;
210 *io_ranks_ = xrealloc(is_io_rank, num_io_ranks * sizeof(**io_ranks_));
211}
212
214 MPI_Comm comm, size_t * num_io_ranks, int ** io_ranks) {
215
216 int rank;
217 yac_mpi_call(MPI_Comm_rank(comm, &rank), comm);
218
219 int max_num_ranks = yac_getenv_int(IO_MAX_NUM_RANKS_STR, -1, 1, comm);
220
221 // if there is no limit on the number of io ranks
222 if (max_num_ranks == -1) return;
223
224 if ((max_num_ranks > 0) && ((size_t)max_num_ranks < *num_io_ranks)) {
225 *io_ranks =
226 xrealloc(*io_ranks, (size_t)max_num_ranks * sizeof(**io_ranks));
227 *num_io_ranks = (size_t)max_num_ranks;
228 }
229}
230
232 MPI_Comm comm, size_t * num_io_ranks, int ** io_ranks) {
233
234 // read in the list of ranks which are not to be used for io
235 size_t num_io_ranks_excluded;
236 int * io_ranks_excluded;
238 IO_RANK_EXCLUDE_LIST_STR, comm, &num_io_ranks_excluded, &io_ranks_excluded);
239
240 // if there are ranks to be excluded
241 if (num_io_ranks_excluded > 0) {
242
243 // sort ranks
244 qsort(*io_ranks, *num_io_ranks, sizeof(**io_ranks), compare_int);
245
246 // match exclude list with io rank list
247 size_t new_num_io_ranks = 0;
248 for (size_t i = 0, j = 0; i < *num_io_ranks; ++i) {
249
250 while ((j < num_io_ranks_excluded) &&
251 (io_ranks_excluded[j] < (*io_ranks)[i])) ++j;
252
253 if ((j >= num_io_ranks_excluded) ||
254 ((*io_ranks)[i] != io_ranks_excluded[j])) {
255
256 if (i != new_num_io_ranks)
257 (*io_ranks)[new_num_io_ranks] = (*io_ranks)[i];
258 ++new_num_io_ranks;
259 }
260 }
261
262 if (new_num_io_ranks != *num_io_ranks) {
263 *io_ranks = xrealloc(*io_ranks, new_num_io_ranks * sizeof(**io_ranks));
264 *num_io_ranks = new_num_io_ranks;
265 }
266 }
267
268 free(io_ranks_excluded);
269}
270
272 MPI_Comm comm, int * local_is_io_, int ** io_ranks_, int * num_io_ranks_) {
273
274 int rank, size;
275 yac_mpi_call(MPI_Comm_rank(comm, &rank), comm);
276 yac_mpi_call(MPI_Comm_size(comm, &size), comm);
277
278 size_t num_io_ranks = 0;
279 int * io_ranks = NULL;
280
281 // check environment for io rank list
282 read_io_rank_list(comm, &num_io_ranks, &io_ranks);
283
284 // if no rank list was provided -> generate default rank list (all processes)
285 if (num_io_ranks == 0) {
286 num_io_ranks = (size_t)size;
287 io_ranks = xmalloc(num_io_ranks * sizeof(*io_ranks));
288 for (int i = 0; i < size; ++i) io_ranks[i] = i;
289 }
290
291 // check whether we have to exclude some ranks
292 check_io_rank_exclude_list(comm, &num_io_ranks, &io_ranks);
293
294 // check for the maximum number of io ranks per node
295 check_io_max_num_ranks_per_node(comm, &num_io_ranks, &io_ranks);
296
297 // check maximum number of io ranks
298 check_io_max_num_ranks(comm, &num_io_ranks, &io_ranks);
299
300 YAC_ASSERT(num_io_ranks > 0, "could not determine io ranks");
301
302 int local_is_io = 0;
303 for (size_t i = 0; (i < num_io_ranks) && !local_is_io; ++i)
304 if (io_ranks[i] == rank) local_is_io = 1;
305
306 *local_is_io_ = local_is_io;
307 *io_ranks_ = io_ranks;
308 *num_io_ranks_ = (int)num_io_ranks;
309}
310
311void yac_nc_open(const char * path, int omode, int * ncidp) {
312
313#ifndef YAC_NETCDF_ENABLED
314
315 UNUSED(path);
316 UNUSED(omode);
317 UNUSED(ncidp);
318 die("ERROR(yac_nc_open): YAC is built without the NetCDF support");
319#else
320
321 YAC_ASSERT_F(yac_file_exists(path), "file \"%s\" does not exist", path);
322 YAC_HANDLE_ERROR(nc_open(path, omode, ncidp));
323#endif
324}
325
326void yac_nc_create(const char * path, int cmode, int * ncidp) {
327
328#ifndef YAC_NETCDF_ENABLED
329
330 UNUSED(path);
331 UNUSED(cmode);
332 UNUSED(ncidp);
333 die("ERROR(yac_nc_create): YAC is built without the NetCDF support");
334#else
335
336 int status = nc_create(path, cmode, ncidp);
338 status == NC_NOERR,
339 "failed to create file \"%s\" (NetCDF error message: \"%s\")",
340 path, nc_strerror(status));
341#endif
342}
343
344void yac_nc_inq_dimid(int ncid, char const * name, int * dimidp) {
345
346#ifndef YAC_NETCDF_ENABLED
347
348 UNUSED(ncid);
349 UNUSED(name);
350 UNUSED(dimidp);
351 die("ERROR(yac_nc_inq_dimid): YAC is built without the NetCDF support");
352#else
353
354 int status = nc_inq_dimid(ncid, name, dimidp);
355
356 if (status == NC_EBADDIM) {
357 // GCOVR_EXCL_START
358 size_t pathlen;
359 YAC_HANDLE_ERROR(nc_inq_path(ncid, &pathlen, NULL));
360 char * path = xmalloc(pathlen * sizeof(*path));
361 YAC_HANDLE_ERROR(nc_inq_path(ncid, NULL, path));
363 0, "dimension \"%s\" could not be found in file \"%s\"", name, path);
364 // GCOVR_EXCL_STOP
365 } else YAC_HANDLE_ERROR(status);
366#endif
367}
368
369void yac_nc_inq_varid(int ncid, char const * name, int * varidp) {
370
371#ifndef YAC_NETCDF_ENABLED
372
373 UNUSED(ncid);
374 UNUSED(name);
375 UNUSED(varidp);
376 die("ERROR(yac_nc_inq_varid): YAC is built without the NetCDF support");
377#else
378
379 int status = nc_inq_varid(ncid, name, varidp);
380
381 if (status == NC_ENOTVAR) {
382 // GCOVR_EXCL_START
383 size_t pathlen;
384 YAC_HANDLE_ERROR(nc_inq_path(ncid, &pathlen, NULL));
385 char * path = xmalloc(pathlen * sizeof(*path));
386 YAC_HANDLE_ERROR(nc_inq_path(ncid, NULL, path));
388 0, "variable \"%s\" could not be found in file \"%s\"", name, path);
389 // GCOVR_EXCL_STOP
390 } else YAC_HANDLE_ERROR(status);
391#endif
392}
393
394int yac_file_exists(const char * filename) {
395 struct stat buffer;
396 return !stat(filename,&buffer);
397}
398
399int yac_file_contains(char const *path, char const *needle) {
400
401 YAC_ASSERT(path != NULL, "path must not be NULL");
402 YAC_ASSERT(path[0] != '\0', "path must not be an empty string");
403
404 FILE *f = fopen(path, "r");
405
406 YAC_ASSERT_F(f, "failed to open file \"%s\" for reading", path);
407 YAC_ASSERT(needle != NULL, "needle must not be NULL");
408 YAC_ASSERT(needle[0] != '\0', "needle must not be an empty string");
409
410 char *line = NULL;
411 size_t line_cap = 0;
412 int found = 0;
413
414 while (!found && (getline(&line, &line_cap, f) != -1)) {
415 found = strstr(line, needle) != NULL;
416 }
417
418 YAC_ASSERT_F(!ferror(f), "failed to read file \"%s\"", path);
419
420 fclose(f);
421 free(line);
422
423 return found;
424}
425
427 char const *path_prefix, char const *path_suffix, char const *extension) {
428
429 YAC_ASSERT(path_prefix != NULL, "path_prefix must not be NULL");
430 YAC_ASSERT(path_prefix[0] != '\0', "path_prefix must not be empty");
431 YAC_ASSERT(path_suffix != NULL, "path_suffix must not be NULL");
432 YAC_ASSERT(path_suffix[0] != '\0', "path_suffix must not be empty");
433
434 extension = extension ? extension : "";
435
436 size_t len_prefix = strlen(path_prefix);
437 size_t len_suffix = strlen(path_suffix);
438 size_t len_extension = strlen(extension);
439 size_t len = len_prefix + len_suffix + len_extension + 2;
440 char * path = xmalloc(len * sizeof(*path));
441
442 int nw = snprintf(path, len, "%s/%s%s", path_prefix, path_suffix, extension);
443
445 (nw >= 0) && ((size_t)nw < len),
446 "failed to construct path from \"%s\", \"%s\", [and \"%s\"]",
447 path_prefix, path_suffix, extension);
448
449 return path;
450}
451
452static void remove_directory_recursive(char const *path) {
453
454 YAC_ASSERT(path != NULL, "path must not be NULL");
455 YAC_ASSERT(path[0] != '\0', "path must not be an empty string");
456
457 DIR *dir = opendir(path);
458
459 YAC_ASSERT_F(dir != NULL, "failed to open directory \"%s\"", path);
460
461 struct dirent *entry;
462
463 while ((entry = readdir(dir)) != NULL) {
464
465 // skip "." and ".." entries
466 if (!strcmp(entry->d_name, ".") ||
467 !strcmp(entry->d_name, "..")) {
468 continue;
469 }
470
471 char * child = yac_file_path_join(path, entry->d_name, NULL);
472
473 struct stat statbuf;
474
475 YAC_ASSERT_F(stat(child, &statbuf) == 0, "failed to stat \"%s\"", child);
476
477 // if the entry is a directory, recursively remove it,
478 // otherwise remove the file
479 if (S_ISDIR(statbuf.st_mode)) {
480
482
483 } else {
484
485 int status = unlink(child);
486
487 YAC_ASSERT_F(status == 0, "failed to remove file \"%s\"", child);
488 }
489
490 free(child);
491 }
492
493 int status = closedir(dir);
494 YAC_ASSERT_F(status == 0, "failed to close directory \"%s\"", path);
495
496 status = rmdir(path);
497 YAC_ASSERT_F(status == 0, "failed to remove directory \"%s\"", path);
498}
499
500void yac_remove_directory(char const *path) {
501
502 YAC_ASSERT(path != NULL, "path must not be NULL");
503 YAC_ASSERT(path[0] != '\0', "path must not be an empty string");
505 yac_file_exists(path), "directory \"%s\" does not exist", path);
506
508}
#define YAC_ASSERT(exp, msg)
#define UNUSED(x)
Definition core.h:72
#define IO_MAX_NUM_RANKS_PER_NODE
Definition io_utils.c:21
#define IO_RANK_LIST_STR
Definition io_utils.c:18
char * yac_file_path_join(char const *path_prefix, char const *path_suffix, char const *extension)
Join two path components with a '/' and optional extension.
Definition io_utils.c:426
static void read_io_rank_list(MPI_Comm comm, size_t *num_io_ranks, int **io_ranks)
Definition io_utils.c:131
#define DEFAULT_MAX_NUM_IO_RANK_PER_NODE
Definition io_utils.c:22
static void remove_directory_recursive(char const *path)
Definition io_utils.c:452
void yac_get_io_ranks(MPI_Comm comm, int *local_is_io_, int **io_ranks_, int *num_io_ranks_)
Definition io_utils.c:271
static void read_rank_list(char const *env_name, MPI_Comm comm, size_t *num_ranks_, int **ranks_)
Definition io_utils.c:31
void yac_nc_create(const char *path, int cmode, int *ncidp)
Definition io_utils.c:326
void yac_remove_directory(char const *path)
Remove a directory and its contents recursively.
Definition io_utils.c:500
void yac_nc_inq_varid(int ncid, char const *name, int *varidp)
Definition io_utils.c:369
#define IO_RANK_EXCLUDE_LIST_STR
Definition io_utils.c:20
static void check_io_max_num_ranks(MPI_Comm comm, size_t *num_io_ranks, int **io_ranks)
Definition io_utils.c:213
int yac_file_contains(char const *path, char const *needle)
Check whether a file contains a specific string.
Definition io_utils.c:399
static void check_io_max_num_ranks_per_node(MPI_Comm comm, size_t *num_io_ranks_, int **io_ranks_)
Definition io_utils.c:137
#define IO_MAX_NUM_RANKS_STR
Definition io_utils.c:19
int yac_file_exists(const char *filename)
Check whether a file exists.
Definition io_utils.c:394
static int compare_int(const void *a, const void *b)
Definition io_utils.c:24
void yac_nc_open(const char *path, int omode, int *ncidp)
Definition io_utils.c:311
void yac_nc_inq_dimid(int ncid, char const *name, int *dimidp)
Definition io_utils.c:344
static void check_io_rank_exclude_list(MPI_Comm comm, size_t *num_io_ranks, int **io_ranks)
Definition io_utils.c:231
#define xstrdup(s)
Definition ppm_xfuncs.h:84
#define xrealloc(ptr, size)
Definition ppm_xfuncs.h:67
#define xmalloc(size)
Definition ppm_xfuncs.h:66
double * buffer
#define YAC_HANDLE_ERROR(exp)
Definition toy_output.c:13
char const * name
Definition toy_scrip.c:114
int yac_getenv_int(const char *name, int default_value, int threshold, MPI_Comm comm)
Definition utils_core.c:89
static void yac_remove_duplicates_int(int *array, size_t *n)
#define YAC_ASSERT_F(exp, format,...)
Definition yac_assert.h:39
#define die(msg)
Definition yac_assert.h:14
#define yac_mpi_call(call, comm)
#define YAC_MPI_SIZE_T