GCC Code Coverage Report


Directory: src/
File: src/talp/talp_output.c
Date: 2026-09-15 07:37:49
Exec Total Coverage
Lines: 632 765 82.6%
Functions: 44 46 95.7%
Branches: 238 348 68.4%

Line Branch Exec Source
1 /*********************************************************************************/
2 /* Copyright 2009-2024 Barcelona Supercomputing Center */
3 /* */
4 /* This file is part of the DLB library. */
5 /* */
6 /* DLB is free software: you can redistribute it and/or modify */
7 /* it under the terms of the GNU Lesser General Public License as published by */
8 /* the Free Software Foundation, either version 3 of the License, or */
9 /* (at your option) any later version. */
10 /* */
11 /* DLB is distributed in the hope that it will be useful, */
12 /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 /* GNU Lesser General Public License for more details. */
15 /* */
16 /* You should have received a copy of the GNU Lesser General Public License */
17 /* along with DLB. If not, see <https://www.gnu.org/licenses/>. */
18 /*********************************************************************************/
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include "talp/talp_output.h"
25
26 #include "LB_core/spd.h"
27 #include "apis/dlb_talp.h"
28 #include "support/debug.h"
29 #include "support/gslist.h"
30 #include "support/mask_utils.h"
31 #include "support/mytime.h"
32 #include "support/options.h"
33 #include "talp/talp.h"
34 #include "talp/talp_types.h"
35 #include "talp/perf_metrics.h"
36
37 #include <errno.h>
38 #include <float.h>
39 #include <libgen.h>
40 #include <limits.h>
41 #include <locale.h>
42 #include <pthread.h>
43 #include <stddef.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <sys/stat.h>
48 #include <unistd.h>
49
50
51 30 static float sanitized_ipc(float instructions, float cycles) {
52
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 28 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
30 if (instructions > 0 && cycles > 0) {
53 2 return instructions / cycles;
54 } else {
55 28 return 0.0f;
56 }
57 }
58
59 56 static const char* make_header(const char *title) {
60 56 int width = 62;
61 static char buf[80];
62 56 int title_len = strlen(title);
63
64
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 56 times.
56 if (width >= (int)sizeof(buf)) {
65 width = sizeof(buf) - 1; // prevent overflow
66 }
67
68
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 56 times.
56 if (title_len + 2 > width) {
69 // Title too long: just return title
70 snprintf(buf, sizeof(buf), "%s", title);
71 return buf;
72 }
73
74 56 int hashes = width - title_len - 2;
75 56 int left = hashes / 2;
76 56 int right = hashes - left;
77
78 56 memset(buf, '#', left);
79 56 buf[left] = ' ';
80 56 memcpy(buf + left + 1, title, title_len);
81 56 buf[left + 1 + title_len] = ' ';
82 56 memset(buf + left + 1 + title_len + 1, '#', right);
83 56 buf[width] = '\0';
84
85 56 return buf;
86 }
87
88
89 /*********************************************************************************/
90 /* GPU vendor */
91 /*********************************************************************************/
92
93 static gpu_vendor_t gpu_vendor = GPU_VENDOR_NONE;
94
95 void talp_output_record_gpu_vendor(gpu_vendor_t vendor) {
96 gpu_vendor = vendor;
97 }
98
99 /*********************************************************************************/
100 /* Monitoring Region */
101 /*********************************************************************************/
102
103 8 void talp_output_print_monitoring_region(
104 const dlb_monitor_t *monitor, talp_flags_t talp_flags) {
105
106 char elapsed_time_str[16];
107 8 ns_to_human(elapsed_time_str, 16, monitor->elapsed_time);
108
109 8 monitor_data_t *monitor_data = monitor->_data;
110 8 const char *cpuset_str = mu_to_str(&monitor_data->cpu_mask);
111
112 8 info("%s", make_header("Monitoring Region Summary"));
113 8 info("### Name: %s", monitor->name);
114 8 info("### Elapsed Time: %s", elapsed_time_str);
115 8 info("### Useful time: %"PRId64" ns",
116 8 monitor->useful_time);
117
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
8 if (talp_flags.have_mpi) {
118 4 info("### Not useful MPI: %"PRId64" ns",
119 4 monitor->mpi_time);
120
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 if (talp_flags.have_openmp) {
121 1 info("### Not useful MPI in worker threads: %"PRId64" ns",
122 1 monitor->mpi_worker_idle_time);
123 }
124 }
125
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
8 if (talp_flags.have_openmp) {
126 1 info("### Not useful OMP Load Balance: %"PRId64" ns",
127 1 monitor->omp_load_imbalance_time);
128 1 info("### Not useful OMP Scheduling: %"PRId64" ns",
129 1 monitor->omp_scheduling_time);
130 1 info("### Not useful OMP outside parallel: %"PRId64" ns",
131 1 monitor->omp_outside_parallel_time);
132 }
133
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
8 if (talp_flags.have_gpu) {
134 1 info("### Not useful GPU runtime: %"PRId64" ns",
135 1 monitor->gpu_runtime_time);
136 1 info("### Device useful time: %"PRId64" ns",
137 1 monitor->gpu_useful_time);
138 1 info("### Device communication time: %"PRId64" ns",
139 1 monitor->gpu_communication_time);
140 }
141 8 info("### CpuSet: %s", cpuset_str);
142
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
8 if (talp_flags.have_hwc) {
143 1 float ipc = sanitized_ipc(monitor->instructions, monitor->cycles);
144 1 info("### IPC: %.2f ", ipc);
145 }
146
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
8 if (talp_flags.have_mpi) {
147 4 info("### Number of MPI calls: %"PRId64,
148 4 monitor->num_mpi_calls);
149 }
150
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
8 if (talp_flags.have_openmp) {
151 1 info("### Number of OpenMP parallels: %"PRId64,
152 1 monitor->num_omp_parallels);
153 1 info("### Number of OpenMP tasks: %"PRId64,
154 1 monitor->num_omp_tasks);
155 }
156
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
8 if (talp_flags.have_gpu) {
157 1 info("### Number of GPU runtime calls: %"PRId64,
158 1 monitor->num_gpu_runtime_calls);
159 }
160 8 }
161
162
163 /*********************************************************************************/
164 /* POP Metrics */
165 /*********************************************************************************/
166
167 static GSList *pop_metrics_records = NULL;
168
169 46 void talp_output_record_pop_metrics(const dlb_pop_metrics_t *metrics) {
170
171 /* Copy structure */
172 46 dlb_pop_metrics_t *new_record = malloc(sizeof(dlb_pop_metrics_t));
173 46 *new_record = *metrics;
174
175 /* Add record to list */
176 46 pop_metrics_records = g_slist_prepend(pop_metrics_records, new_record);
177 46 }
178
179 25 static void pop_metrics_print(void) {
180
181 25 for (GSList *node = pop_metrics_records;
182
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 25 times.
57 node != NULL;
183 32 node = node->next) {
184
185 32 dlb_pop_metrics_t *record = node->data;
186
187
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 3 times.
32 if (record->elapsed_time > 0) {
188
189 29 float avg_ipc = sanitized_ipc(record->instructions, record->cycles);
190 char elapsed_time_str[16];
191 29 ns_to_human(elapsed_time_str, 16, record->elapsed_time);
192
193
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 1 times.
57 bool have_gpu_activity = record->num_gpu_runtime_calls > 0 ||
194
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
28 record->gpu_useful_time > 0;
195
196 29 info("%s", make_header("Monitoring Region POP Metrics"));
197 29 info("### Name: %s", record->name);
198 29 info("### Elapsed Time: %s", elapsed_time_str);
199
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 28 times.
29 if (have_gpu_activity) {
200 1 info("### Host");
201 1 info("### ----");
202 }
203
1/2
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
29 if (record->mpi_parallel_efficiency > 0.0f &&
204
1/2
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
29 record->omp_parallel_efficiency > 0.0f) {
205 29 info("### Parallel efficiency: %1.2f",
206 29 record->parallel_efficiency);
207 }
208
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 28 times.
29 if (record->num_mpi_calls > 0) {
209 1 info("### - MPI Parallel efficiency: %1.2f",
210 1 record->mpi_parallel_efficiency);
211 1 info("### - Communication efficiency: %1.2f",
212 1 record->mpi_communication_efficiency);
213 1 info("### - Load Balance: %1.2f",
214 1 record->mpi_load_balance);
215 1 info("### - In: %1.2f",
216 1 record->mpi_load_balance_in);
217 1 info("### - Out: %1.2f",
218 1 record->mpi_load_balance_out);
219 }
220
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 27 times.
29 if (record->num_omp_parallels + record->num_omp_tasks > 0) {
221 2 info("### - OpenMP Parallel efficiency: %1.2f",
222 2 record->omp_parallel_efficiency);
223 2 info("### - Load Balance: %1.2f",
224 2 record->omp_load_balance);
225 2 info("### - Scheduling efficiency: %1.2f",
226 2 record->omp_scheduling_efficiency);
227 2 info("### - Coverage efficiency: %1.2f",
228 2 record->omp_coverage_efficiency);
229 }
230
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 28 times.
29 if (have_gpu_activity) {
231 1 info("### - Device Offload efficiency: %1.2f",
232 1 record->device_offload_efficiency);
233 }
234
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 28 times.
29 if (avg_ipc > 0.0f) {
235 1 float avg_freq = record->cycles / record->useful_time;
236 1 info("### Computational metrics:");
237 1 info("### - Average useful IPC: %1.2f", avg_ipc);
238 1 info("### - Average useful frequency: %1.2f GHz", avg_freq);
239 1 info("### - Number of instructions: %1.2E", record->instructions);
240 }
241
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 28 times.
29 if (have_gpu_activity) {
242 1 info("###");
243 1 info("### %s Device",
244
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 gpu_vendor == GPU_VENDOR_NVIDIA ? "NVIDIA"
245 1 : gpu_vendor == GPU_VENDOR_AMD ? "AMD"
246
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 : "GPU");
247 1 info("### %s-------",
248
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 gpu_vendor == GPU_VENDOR_NVIDIA ? "------"
249 : gpu_vendor == GPU_VENDOR_AMD ? "---"
250 : "---");
251 1 info("### Parallel efficiency: %1.2f",
252 1 record->gpu_parallel_efficiency);
253 1 info("### - Load Balance: %1.2f",
254 1 record->gpu_load_balance);
255 1 info("### - Communication efficiency: %1.2f",
256 1 record->gpu_communication_efficiency);
257 1 info("### - Orchestration efficiency: %1.2f",
258 1 record->gpu_orchestration_efficiency);
259 }
260 } else {
261 3 info("%s", make_header("Monitoring Region POP Metrics"));
262 3 info("### Name: %s", record->name);
263 3 info("### No data ###");
264 }
265 }
266 25 }
267
268 6 static void pop_metrics_to_json(FILE *out_file) {
269
270
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (pop_metrics_records != NULL) {
271 6 fprintf(out_file,
272 " \"Application\": {\n");
273
274 6 for (GSList *node = pop_metrics_records;
275
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
12 node != NULL;
276 6 node = node->next) {
277
278 6 dlb_pop_metrics_t *record = node->data;
279
280 6 fprintf(out_file,
281 " \"%s\": {\n"
282 #define PRINT_FMT(var_name, c_type, json_name, fmt) \
283 " \"" #json_name "\": " fmt ",\n"
284 #define PRINT_FMT_LAST(var_name, c_type, json_name, fmt) \
285 " \"" #json_name "\": " fmt "\n"
286 FOR_DLB_POP_METRICS_FIELDS(PRINT_FMT, PRINT_FMT_LAST)
287 #undef PRINT_FMT
288 #undef PRINT_FMT_LAST
289 " }%s\n",
290 6 record->name
291 #define PRINT_ARG(var_name, c_type, json_name, fmt) \
292 , record->var_name
293 6 FOR_DLB_POP_METRICS_FIELDS(PRINT_ARG, PRINT_ARG)
294 #undef PRINT_ARG
295
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 , node->next != NULL ? "," : "");
296 }
297 6 fprintf(out_file,
298 " }"); /* no eol */
299 }
300 6 }
301
302 3 static void pop_metrics_to_txt(FILE *out_file) {
303
304 3 for (GSList *node = pop_metrics_records;
305
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 node != NULL;
306 3 node = node->next) {
307
308 3 dlb_pop_metrics_t *record = node->data;
309
310
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (record->elapsed_time > 0) {
311 3 fprintf(out_file,
312 "%s\n"
313 "### Name: %s\n"
314 "### Number of CPUs: %d\n"
315 "### Number of OpenMP threads: %d\n"
316 "### Number of MPI processes: %d\n"
317 "### Number of nodes: %d\n"
318 "### Number of GPUs: %d\n"
319 "### Cycles: %.0f\n"
320 "### Instructions: %.0f\n"
321 "### Number of measurements: %"PRId64"\n"
322 "### Number of MPI calls: %"PRId64"\n"
323 "### Number of OpenMP parallel regions: %"PRId64"\n"
324 "### Number of OpenMP explicit tasks: %"PRId64"\n"
325 "### Number of GPU runtime calls: %"PRId64"\n"
326 "### Elapsed Time (ns): %"PRId64"\n"
327 "### Useful Time (ns): %"PRId64"\n"
328 "### MPI Time (ns): %"PRId64"\n"
329 "### OpenMP Load Imbalance Time (ns): %"PRId64"\n"
330 "### OpenMP Scheduling Time (ns): %"PRId64"\n"
331 "### OpenMP Outside Parallel Time (ns): %"PRId64"\n"
332 "### GPU Runtime Time (ns): %"PRId64"\n"
333 "### MPI time normalized at process level of\n"
334 "### the process with the max non-MPI time: %.0f\n"
335 "### MPI time normalized at node level of\n"
336 "### the process with the max non-MPI time: %.0f\n"
337 "### Device useful time: %"PRId64"\n"
338 "### Device communication time: %"PRId64"\n"
339 "### Device max useful time: %"PRId64"\n"
340 "### Device max active time: %"PRId64"\n"
341 "### --- Host metrics ---\n"
342 "### Parallel efficiency: %.2f\n"
343 "### MPI Parallel efficiency: %.2f\n"
344 "### - MPI Communication efficiency: %.2f\n"
345 "### - MPI Load Balance: %.2f\n"
346 "### - MPI Load Balance in: %.2f\n"
347 "### - MPI Load Balance out: %.2f\n"
348 "### OpenMP Parallel efficiency: %.2f\n"
349 "### - OpenMP Load Balance: %.2f\n"
350 "### - OpenMP Scheduling efficiency: %.2f\n"
351 "### - OpenMP Coverage efficiency: %.2f\n"
352 "### Device Offload efficiency: %.2f\n"
353 "### --- Device metrics ---\n"
354 "### Device Parallel efficiency: %.2f\n"
355 "### - Device Load Balance: %.2f\n"
356 "### - Device Communication efficiency: %.2f\n"
357 "### - Device Orchestration efficiency: %.2f\n",
358 make_header("Monitoring Region POP Metrics"),
359 3 record->name,
360 record->num_cpus,
361 record->num_omp_threads,
362 record->num_mpi_ranks,
363 record->num_nodes,
364 record->num_gpus,
365 record->cycles,
366 record->instructions,
367 record->num_measurements,
368 record->num_mpi_calls,
369 record->num_omp_parallels,
370 record->num_omp_tasks,
371 record->num_gpu_runtime_calls,
372 record->elapsed_time,
373 record->useful_time,
374 record->mpi_time,
375 record->omp_load_imbalance_time,
376 record->omp_scheduling_time,
377 record->omp_outside_parallel_time,
378 record->gpu_runtime_time,
379 record->min_mpi_normd_proc,
380 record->min_mpi_normd_node,
381 record->gpu_useful_time,
382 record->gpu_communication_time,
383 record->max_gpu_useful_time,
384 record->max_gpu_active_time,
385 3 record->parallel_efficiency,
386 3 record->mpi_parallel_efficiency,
387 3 record->mpi_communication_efficiency,
388 3 record->mpi_load_balance,
389 3 record->mpi_load_balance_in,
390 3 record->mpi_load_balance_out,
391 3 record->omp_parallel_efficiency,
392 3 record->omp_load_balance,
393 3 record->omp_scheduling_efficiency,
394 3 record->omp_coverage_efficiency,
395 3 record->device_offload_efficiency,
396 3 record->gpu_parallel_efficiency,
397 3 record->gpu_load_balance,
398 3 record->gpu_communication_efficiency,
399 3 record->gpu_orchestration_efficiency
400 );
401 } else {
402 fprintf(out_file,
403 "%s\n"
404 "### Name: %s\n"
405 "### No data ###\n",
406 make_header("Monitoring Region POP Metrics"),
407 record->name);
408 }
409 }
410 3 }
411
412 static const char* pop_metrics_csv_schema =
413 "name,"
414 #define PRINT_FMT(var_name, c_type, json_name, fmt) \
415 #json_name ","
416 #define PRINT_FMT_LAST(var_name, c_type, json_name, fmt) \
417 #json_name
418 FOR_DLB_POP_METRICS_FIELDS(PRINT_FMT, PRINT_FMT_LAST);
419 #undef PRINT_FMT
420 #undef PRINT_FMT_LAST
421
422 5 static void pop_metrics_to_csv(FILE *out_file, bool append) {
423
424
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (pop_metrics_records == NULL) return;
425
426
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 if (!append) {
427 /* Print header */
428 3 fprintf(out_file, "%s\n", pop_metrics_csv_schema);
429 }
430
431 5 for (GSList *node = pop_metrics_records;
432
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
10 node != NULL;
433 5 node = node->next) {
434
435 5 dlb_pop_metrics_t *record = node->data;
436
437 5 fprintf(out_file,
438 "\"%s\","
439 #define PRINT_FMT(var_name, c_type, json_name, fmt) \
440 fmt ","
441 #define PRINT_FMT_LAST(var_name, c_type, json_name, fmt) \
442 fmt "\n"
443 FOR_DLB_POP_METRICS_FIELDS(PRINT_FMT, PRINT_FMT_LAST)
444 #undef PRINT_FMT
445 #undef PRINT_FMT_LAST
446 5 , record->name
447 #define PRINT_ARG(var_name, c_type, json_name, fmt) \
448 , record->var_name
449 5 FOR_DLB_POP_METRICS_FIELDS(PRINT_ARG, PRINT_ARG)
450 #undef PRINT_ARG
451 );
452 }
453 }
454
455 39 static void pop_metrics_finalize(void) {
456
457 /* Free every record data */
458 39 for (GSList *node = pop_metrics_records;
459
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 39 times.
85 node != NULL;
460 46 node = node->next) {
461
462 46 dlb_pop_metrics_t *record = node->data;
463 46 free(record);
464 }
465
466 /* Free list */
467 39 g_slist_free(pop_metrics_records);
468 39 pop_metrics_records = NULL;
469 39 }
470
471
472 /*********************************************************************************/
473 /* Node */
474 /*********************************************************************************/
475
476 static GSList *node_records = NULL;
477
478 void talp_output_record_node(const node_record_t *node_record) {
479
480 int nelems = node_record->nelems;
481
482 /* Allocate new record */
483 size_t process_records_size = sizeof(process_in_node_record_t) * nelems;
484 size_t node_record_size = sizeof(node_record_t) + process_records_size;
485 node_record_t *new_record = malloc(node_record_size);
486
487 /* Memcpy the entire struct */
488 memcpy(new_record, node_record, node_record_size);
489
490 /* Insert to list */
491 node_records = g_slist_prepend(node_records, new_record);
492 }
493
494 25 static void node_print(void) {
495
496 25 for (GSList *node = node_records;
497
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
25 node != NULL;
498 node = node->next) {
499
500 node_record_t *node_record = node->data;
501
502 info(" |----------------------------------------------------------|");
503 info(" | Extended Report Node %4d |",
504 node_record->node_id);
505 info(" |----------------------------------------------------------|");
506 info(" | Process | Useful Time | MPI Time |");
507 info(" |------------|----------------------|----------------------|");
508
509 for (int i = 0; i < node_record->nelems; ++i) {
510 info(" | %-10d | %18e s | %18e s |",
511 node_record->processes[i].pid,
512 nsecs_to_secs(node_record->processes[i].useful_time),
513 nsecs_to_secs(node_record->processes[i].mpi_time));
514 info(" |------------|----------------------|----------------------|");
515 }
516
517 if (node_record->nelems > 0) {
518 info(" |------------|----------------------|----------------------|");
519 info(" | %-10s | %18e s | %18e s |", "Node Avg",
520 nsecs_to_secs(node_record->avg_useful_time),
521 nsecs_to_secs(node_record->avg_mpi_time));
522 info(" |------------|----------------------|----------------------|");
523 info(" | %-10s | %18e s | %18e s |", "Node Max",
524 nsecs_to_secs(node_record->max_useful_time),
525 nsecs_to_secs(node_record->max_mpi_time));
526 info(" |------------|----------------------|----------------------|");
527 }
528 }
529 25 }
530
531 6 static void node_to_json(FILE *out_file) {
532
533
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (node_records == NULL) return;
534
535 /* If there are pop_metrics, append to the existing dictionary */
536 if (pop_metrics_records != NULL) {
537 fprintf(out_file,",\n");
538 }
539
540 fprintf(out_file,
541 " \"node\": [\n");
542
543 for (GSList *node = node_records;
544 node != NULL;
545 node = node->next) {
546
547 node_record_t *node_record = node->data;
548
549 fprintf(out_file,
550 " {\n"
551 " \"id\": \"%d\",\n"
552 " \"process\": [\n",
553 node_record->node_id);
554
555 for (int i = 0; i < node_record->nelems; ++i) {
556 fprintf(out_file,
557 " {\n"
558 " \"id\": %d,\n"
559 " \"usefulTime\": %"PRId64",\n"
560 " \"mpiTime\": %"PRId64"\n"
561 " }%s\n",
562 node_record->processes[i].pid,
563 node_record->processes[i].useful_time,
564 node_record->processes[i].mpi_time,
565 i+1 < node_record->nelems ? "," : "");
566 }
567
568 fprintf(out_file,
569 " ],\n"
570 " \"nodeAvg\": {\n"
571 " \"usefulTime\": %"PRId64",\n"
572 " \"mpiTime\": %"PRId64"\n"
573 " },\n"
574 " \"nodeMax\": {\n"
575 " \"usefulTime\": %"PRId64",\n"
576 " \"mpiTime\": %"PRId64"\n"
577 " }\n"
578 " }%s\n",
579 node_record->avg_useful_time,
580 node_record->avg_mpi_time,
581 node_record->max_useful_time,
582 node_record->max_mpi_time,
583 node->next != NULL ? "," : "");
584 }
585 fprintf(out_file,
586 " ]"); /* no eol */
587 }
588
589 4 static void node_to_csv(FILE *out_file, bool append) {
590
591
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (node_records == NULL) return;
592
593 if (!append) {
594 /* Print header */
595 fprintf(out_file,
596 "NodeId,"
597 "ProcessId,"
598 "ProcessUsefulTime,"
599 "ProcessMPITime,"
600 "NodeAvgUsefulTime,"
601 "NodeAvgMPITime,"
602 "NodeMaxUsefulTime,"
603 "NodeMaxMPITime\n");
604 }
605
606 for (GSList *node = node_records;
607 node != NULL;
608 node = node->next) {
609
610 node_record_t *node_record = node->data;
611
612 for (int i = 0; i < node_record->nelems; ++i) {
613 fprintf(out_file,
614 "%d," /* NodeId */
615 "%d," /* ProcessId */
616 "%"PRId64"," /* ProcessUsefulTime */
617 "%"PRId64"," /* ProcessMPITime */
618 "%"PRId64"," /* NodeAvgUsefulTime */
619 "%"PRId64"," /* NodeAvgMPITime*/
620 "%"PRId64"," /* NodeMaxUsefulTime */
621 "%"PRId64"\n", /* NodeMaxMPITime*/
622 node_record->node_id,
623 node_record->processes[i].pid,
624 node_record->processes[i].useful_time,
625 node_record->processes[i].mpi_time,
626 node_record->avg_useful_time,
627 node_record->avg_mpi_time,
628 node_record->max_useful_time,
629 node_record->max_mpi_time);
630
631 }
632 }
633 }
634
635 3 static void node_to_txt(FILE *out_file) {
636
637 3 for (GSList *node = node_records;
638
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 node != NULL;
639 node = node->next) {
640
641 node_record_t *node_record = node->data;
642
643 fprintf(out_file,
644 " |----------------------------------------------------------|\n"
645 " | Extended Report Node %4d |\n"
646 " |----------------------------------------------------------|\n"
647 " | Process | Useful Time | MPI Time |\n"
648 " |------------|----------------------|----------------------|\n",
649 node_record->node_id);
650
651 for (int i = 0; i < node_record->nelems; ++i) {
652 fprintf(out_file,
653 " | %-10d | %18e s | %18e s |\n"
654 " |------------|----------------------|----------------------|\n",
655 node_record->processes[i].pid,
656 nsecs_to_secs(node_record->processes[i].useful_time),
657 nsecs_to_secs(node_record->processes[i].mpi_time));
658 }
659
660 if (node_record->nelems > 0) {
661 fprintf(out_file,
662 " |------------|----------------------|----------------------|\n"
663 " | %-10s | %18e s | %18e s |\n"
664 " |------------|----------------------|----------------------|\n"
665 " | %-10s | %18e s | %18e s |\n"
666 " |------------|----------------------|----------------------|\n",
667 "Node Avg",
668 nsecs_to_secs(node_record->avg_useful_time),
669 nsecs_to_secs(node_record->avg_mpi_time),
670 "Node Max",
671 nsecs_to_secs(node_record->max_useful_time),
672 nsecs_to_secs(node_record->max_mpi_time));
673 }
674 }
675 3 }
676
677 39 static void node_finalize(void) {
678
679 /* Free every record data */
680 39 for (GSList *node = node_records;
681
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 39 times.
39 node != NULL;
682 node = node->next) {
683
684 node_record_t *record = node->data;
685 free(record);
686 }
687
688 /* Free list */
689 39 g_slist_free(node_records);
690 39 node_records = NULL;
691 39 }
692
693
694 /*********************************************************************************/
695 /* Process */
696 /*********************************************************************************/
697
698 typedef struct region_record_t {
699 char name[DLB_MONITOR_NAME_MAX];
700 int num_mpi_ranks;
701 process_record_t process_records[];
702 } region_record_t;
703
704 static GSList *region_records = NULL;
705
706 14 void talp_output_record_process(const char *region_name,
707 const process_record_t *process_record, int num_mpi_ranks) {
708
709 14 int rank = process_record->rank;
710
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 12 times.
14 if (num_mpi_ranks < 1) {
711 /* special value to dissociate record->rank if the argument has a
712 * proper value but still we're only recording one element.
713 * (usually the case if --talp-partial-output) */
714 2 num_mpi_ranks = 1;
715 2 rank = 0;
716 }
717
718 14 region_record_t *region_record = NULL;
719
720 /* Find region or allocate new one */
721 14 for (GSList *node = region_records;
722
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 14 times.
15 node != NULL;
723 1 node = node->next) {
724
725 1 region_record_t *record = node->data;
726
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (strcmp(record->name, region_name) == 0) {
727 region_record = record;
728 break;
729 }
730 }
731
732 /* Allocate if not found */
733
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 if (region_record == NULL) {
734 /* Allocate and initialize new region */
735 14 size_t region_record_size = sizeof(region_record_t) +
736 14 sizeof(process_record_t) * num_mpi_ranks;
737 14 region_record = malloc(region_record_size);
738 14 *region_record = (const region_record_t) {
739 .num_mpi_ranks = num_mpi_ranks,
740 };
741 14 snprintf(region_record->name, DLB_MONITOR_NAME_MAX, "%s",
742 region_name);
743
744 /* Insert to list */
745 14 region_records = g_slist_prepend(region_records, region_record);
746 }
747
748 /* Copy process_record */
749
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 ensure(rank < num_mpi_ranks, "Wrong rank number in %s", __func__);
750 14 memcpy(&region_record->process_records[rank], process_record, sizeof(process_record_t));
751 14 }
752
753 25 static void process_print(void) {
754
755 25 for (GSList *node = region_records;
756
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 25 times.
29 node != NULL;
757 4 node = node->next) {
758
759 4 region_record_t *region_record = node->data;
760
761
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
8 for (int i = 0; i < region_record->num_mpi_ranks; ++i) {
762
763 4 process_record_t *process_record = &region_record->process_records[i];
764
765 4 info("%s", make_header("Monitoring Region Summary"));
766 4 info("### Name: %s",
767 4 region_record->name);
768 4 info("### Process: %d (%s)",
769 4 process_record->pid, process_record->hostname);
770 4 info("### Rank: %d",
771 process_record->rank);
772 4 info("### CpuSet: %s",
773 4 process_record->cpuset);
774 4 info("### Elapsed time: %"PRId64" ns",
775 process_record->monitor.elapsed_time);
776 4 info("### Useful time: %"PRId64" ns",
777 process_record->monitor.useful_time);
778
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 if (process_record->monitor.mpi_time > 0) {
779 1 info("### Not useful MPI: %"PRId64" ns",
780 process_record->monitor.mpi_time);
781 }
782
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (process_record->monitor.mpi_worker_idle_time > 0) {
783 info("### Not useful MPI in worker threads: %"PRId64" ns",
784 process_record->monitor.mpi_worker_idle_time);
785 }
786
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (process_record->monitor.omp_load_imbalance_time > 0
787
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 || process_record->monitor.omp_scheduling_time > 0
788
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 || process_record->monitor.omp_outside_parallel_time > 0) {
789 info("### Not useful OMP Load Imbalance: %"PRId64" ns",
790 process_record->monitor.omp_load_imbalance_time);
791 info("### Not useful OMP Scheduling: %"PRId64" ns",
792 process_record->monitor.omp_scheduling_time);
793 info("### Not useful OMP outside parallel: %"PRId64" ns",
794 process_record->monitor.omp_outside_parallel_time);
795 }
796
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (process_record->monitor.gpu_runtime_time > 0) {
797 info("### Not useful GPU runtime: %"PRId64" ns",
798 process_record->monitor.gpu_runtime_time);
799 info("### Device useful time: %"PRId64" ns",
800 process_record->monitor.gpu_useful_time);
801 info("### Device communication time: %"PRId64" ns",
802 process_record->monitor.gpu_communication_time);
803 }
804
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (process_record->monitor.instructions > 0
805 && process_record->monitor.cycles > 0) {
806 info("### IPC : %.2f",
807 (float)process_record->monitor.instructions
808 / process_record->monitor.cycles);
809 }
810 }
811 }
812 25 }
813
814 6 static void process_to_json(FILE *out_file) {
815
816
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (region_records == NULL) return;
817
818 /* If there are pop_metrics or node_metrics, append to the existing dictionary */
819
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (pop_metrics_records != NULL
820 || node_records != NULL) {
821 6 fprintf(out_file,",\n");
822 }
823
824 6 fprintf(out_file,
825 " \"Process\": {\n");
826
827 6 for (GSList *node = region_records;
828
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
12 node != NULL;
829 6 node = node->next) {
830
831 6 region_record_t *region_record = node->data;
832
833 6 fprintf(out_file,
834 " \"%s\": [\n",
835 6 region_record->name);
836
837
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
12 for (int i = 0; i < region_record->num_mpi_ranks; ++i) {
838
839 6 process_record_t *process_record = &region_record->process_records[i];
840
841 6 fprintf(out_file,
842 " {\n"
843 " \"rank\": %d,\n"
844 " \"pid\": %d,\n"
845 " \"nodeId\": %d,\n"
846 " \"hostname\": \"%s\",\n"
847 " \"cpuset\": %s,\n"
848 #define PRINT_FMT(var_name, c_type, json_name, fmt) \
849 " \"" #json_name "\": " fmt ",\n"
850 #define PRINT_FMT_LAST(var_name, c_type, json_name, fmt) \
851 " \"" #json_name "\": " fmt "\n"
852 FOR_DLB_MONITOR_PRINTABLE_FIELDS(PRINT_FMT, PRINT_FMT_LAST)
853 #undef PRINT_FMT
854 #undef PRINT_FMT_LAST
855 " }%s\n",
856 process_record->rank,
857 process_record->pid,
858 process_record->node_id,
859 6 process_record->hostname,
860 6 process_record->cpuset_quoted,
861 #define PRINT_ARG(var_name, c_type, json_name, fmt) \
862 process_record->monitor.var_name,
863 FOR_DLB_MONITOR_PRINTABLE_FIELDS(PRINT_ARG, PRINT_ARG)
864 #undef PRINT_ARG
865
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 i + 1 < region_record->num_mpi_ranks ? "," : "");
866 }
867 6 fprintf(out_file,
868 " ]%s\n",
869
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 node->next != NULL ? "," : "");
870 }
871 6 fprintf(out_file,
872 " }"); /* no eol */
873 }
874
875 static const char *process_csv_schema =
876 "Region," "Rank," "PID," "NodeId," "Hostname," "CpuSet,"
877 #define PRINT_FMT(var_name, c_type, json_name, fmt) \
878 #json_name ","
879 #define PRINT_FMT_LAST(var_name, c_type, json_name, fmt) \
880 #json_name
881 FOR_DLB_MONITOR_PRINTABLE_FIELDS(PRINT_FMT, PRINT_FMT_LAST);
882 #undef PRINT_FMT
883 #undef PRINT_FMT_LAST
884
885 5 static void process_to_csv(FILE *out_file, bool append) {
886
887
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
5 if (region_records == NULL) return;
888
889
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!append) {
890 /* Print header */
891 1 fprintf(out_file, "%s\n", process_csv_schema);
892 }
893
894 1 for (GSList *node = region_records;
895
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 node != NULL;
896 1 node = node->next) {
897
898 1 region_record_t *region_record = node->data;
899
900
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (int i = 0; i < region_record->num_mpi_ranks; ++i) {
901
902 1 process_record_t *process_record = &region_record->process_records[i];
903
904 1 fprintf(out_file,
905 "%s," /* Region */
906 "%d," /* Rank */
907 "%d," /* PID */
908 "%d," /* NodeId */
909 "%s," /* Hostname */
910 "%s," /* CpuSet */
911 #define PRINT_FMT(var_name, c_type, json_name, fmt) \
912 fmt ","
913 #define PRINT_FMT_LAST(var_name, c_type, json_name, fmt) \
914 fmt "\n"
915 FOR_DLB_MONITOR_PRINTABLE_FIELDS(PRINT_FMT, PRINT_FMT_LAST)
916 #undef PRINT_FMT
917 #undef PRINT_FMT_LAST
918 1 , region_record->name
919 , process_record->rank
920 , process_record->pid
921 , process_record->node_id
922 1 , process_record->hostname
923 1 , process_record->cpuset_quoted
924 #define PRINT_ARG(var_name, c_type, json_name, fmt) \
925 , process_record->monitor.var_name
926 FOR_DLB_MONITOR_PRINTABLE_FIELDS(PRINT_ARG, PRINT_ARG)
927 #undef PRINT_ARG
928 );
929 }
930 }
931 }
932
933 3 static void process_to_txt(FILE *out_file) {
934
935 3 for (GSList *node = region_records;
936
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 node != NULL;
937 3 node = node->next) {
938
939 3 region_record_t *region_record = node->data;
940
941
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 for (int i = 0; i < region_record->num_mpi_ranks; ++i) {
942
943 3 process_record_t *process_record = &region_record->process_records[i];
944
945 6 float ipc = process_record->monitor.cycles > 0
946 ? (float)process_record->monitor.instructions
947 / process_record->monitor.cycles
948
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 : 0.0f;
949
950 3 fprintf(out_file,
951 "%s\n"
952 "### Name: %s\n"
953 "### Process: %d (%s)\n"
954 "### Rank: %d\n"
955 "### CpuSet: %s\n"
956 "### Elapsed time: %"PRId64" ns\n"
957 "### Useful time: %"PRId64" ns\n"
958 "### Not useful MPI: %"PRId64" ns\n"
959 "### Not useful MPI in worker threads: %"PRId64" ns\n"
960 "### Not useful OMP Load Imbalance: %"PRId64" ns\n"
961 "### Not useful OMP Scheduling: %"PRId64" ns\n"
962 "### Not useful OMP outside parallel: %"PRId64" ns\n"
963 "### Not useful GPU runtime: %"PRId64" ns\n"
964 "### Device useful time: %"PRId64" ns\n"
965 "### Device communication time: %"PRId64" ns\n"
966 "### IPC: %.2f\n",
967 make_header("Monitoring Region Summary"),
968 3 region_record->name,
969 3 process_record->pid, process_record->hostname,
970 process_record->rank,
971 3 process_record->cpuset,
972 process_record->monitor.elapsed_time,
973 process_record->monitor.useful_time,
974 process_record->monitor.mpi_time,
975 process_record->monitor.mpi_worker_idle_time,
976 process_record->monitor.omp_load_imbalance_time,
977 process_record->monitor.omp_scheduling_time,
978 process_record->monitor.omp_outside_parallel_time,
979 process_record->monitor.gpu_runtime_time,
980 process_record->monitor.gpu_useful_time,
981 process_record->monitor.gpu_communication_time,
982 ipc);
983 }
984 }
985 3 }
986
987 39 static void process_finalize(void) {
988
989 /* Free every record data */
990 39 for (GSList *node = region_records;
991
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 39 times.
53 node != NULL;
992 14 node = node->next) {
993
994 14 region_record_t *record = node->data;
995 14 free(record);
996 }
997
998 /* Free list */
999 39 g_slist_free(region_records);
1000 39 region_records = NULL;
1001 39 }
1002
1003
1004 /*********************************************************************************/
1005 /* TALP Common */
1006 /*********************************************************************************/
1007 typedef struct TALPCommonRecord {
1008 char *dlb_version; // Version X.Y.Z[-#-hash]
1009 char *time_of_creation; // ISO 8601 string
1010 } talp_common_record_t;
1011 static talp_common_record_t common_record;
1012
1013 39 static void talp_output_record_common(void) {
1014 /* Initialize structure */
1015 39 time_t now = time(NULL);
1016 39 common_record = (const talp_common_record_t) {
1017 .dlb_version = PACKAGE_VERSION,
1018 39 .time_of_creation = get_iso_8601_string(localtime(&now)),
1019 };
1020 39 }
1021
1022 6 static void common_to_json(FILE *out_file) {
1023 6 fprintf(out_file,
1024 " \"dlbVersion\": \"%s\",\n"
1025 " \"timestamp\": \"%s\",\n",
1026 common_record.dlb_version,
1027 common_record.time_of_creation);
1028 6 }
1029
1030 3 static void common_to_txt(FILE *out_file) {
1031
1032 3 fprintf(out_file,
1033 "%s\n"
1034 "### DLB Version: %s\n"
1035 "### Timestamp: %s\n",
1036 make_header("TALP Common Data"),
1037 common_record.dlb_version,
1038 common_record.time_of_creation);
1039 3 }
1040
1041 39 static void common_finalize(void) {
1042 39 free(common_record.time_of_creation);
1043 39 }
1044
1045
1046
1047
1048 /*********************************************************************************/
1049 /* TALP Resources */
1050 /*********************************************************************************/
1051 typedef struct TALPResourcesRecord {
1052 unsigned int num_cpus;
1053 unsigned int num_available_cpus;
1054 unsigned int num_nodes;
1055 unsigned int num_mpi_ranks;
1056 unsigned int num_gpus;
1057 } talp_resources_record_t;
1058 static talp_resources_record_t resources_record;
1059
1060 22 void talp_output_record_resources(int num_cpus, int num_available_cpus, int num_nodes,
1061 int num_mpi_ranks, int num_gpus) {
1062
1063 22 resources_record = (const talp_resources_record_t) {
1064 22 .num_cpus = (unsigned int) num_cpus,
1065 22 .num_available_cpus = (unsigned int) num_available_cpus,
1066 22 .num_nodes = (unsigned int) num_nodes,
1067 22 .num_mpi_ranks = (unsigned int) num_mpi_ranks,
1068 22 .num_gpus = (unsigned int) num_gpus,
1069 };
1070 22 }
1071
1072 6 static void resources_to_json(FILE *out_file) {
1073 6 fprintf(out_file,
1074 " \"resources\": {\n"
1075 " \"numCpus\": %u,\n"
1076 " \"numAvailableCpus\": %u,\n"
1077 " \"numNodes\": %u,\n"
1078 " \"numMpiRanks\": %u,\n"
1079 " \"numGpus\": %u\n"
1080 " },\n",
1081 resources_record.num_cpus,
1082 resources_record.num_available_cpus,
1083 resources_record.num_nodes,
1084 resources_record.num_mpi_ranks,
1085 resources_record.num_gpus);
1086 6 }
1087
1088 3 static void resources_to_txt(FILE *out_file) {
1089
1090 3 fprintf(out_file,
1091 "%s\n"
1092 "### Number of CPUs: %u\n"
1093 "### Number of available CPUs: %u\n"
1094 "### Number of Nodes: %u\n"
1095 "### Number of MPI processes: %u\n"
1096 "### Number of GPUs: %u\n",
1097 make_header("TALP Resources"),
1098 resources_record.num_cpus,
1099 resources_record.num_available_cpus,
1100 resources_record.num_nodes,
1101 resources_record.num_mpi_ranks,
1102 resources_record.num_gpus);
1103 3 }
1104
1105
1106 /*********************************************************************************/
1107 /* TALP Process info */
1108 /*********************************************************************************/
1109
1110 typedef struct talp_process_info_record_t {
1111 char hostname[HOST_NAME_MAX];
1112 pid_t pid;
1113 } talp_process_info_record_t;
1114
1115 static talp_process_info_record_t process_info_record = {0};
1116
1117 31 void talp_output_record_process_info(void) {
1118
1119 31 gethostname(process_info_record.hostname, HOST_NAME_MAX);
1120 31 process_info_record.pid = getpid();
1121 31 }
1122
1123 6 static void process_info_to_json(FILE *out_file) {
1124
1125
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
6 if (process_info_record.pid != 0) {
1126 5 fprintf(out_file,
1127 " \"processInfo\": {\n"
1128 " \"hostname\": \"%s\",\n"
1129 " \"pid\": %d\n"
1130 " },\n",
1131 process_info_record.hostname,
1132 process_info_record.pid);
1133 }
1134 6 }
1135
1136 /*********************************************************************************/
1137 /* Helper functions */
1138 /*********************************************************************************/
1139
1140 6 static void json_header(FILE *out_file) {
1141 6 fprintf(out_file, "{\n");
1142 6 }
1143
1144 6 static void json_footer(FILE *out_file) {
1145 6 fprintf(out_file, "\n}\n");
1146 6 }
1147
1148
1149 /*********************************************************************************/
1150 /* Output directory/file logic */
1151 /*********************************************************************************/
1152
1153 // Helper: recursively create directories (mkdir -p equivalent)
1154 10 static int mkdir_p(const char *path, mode_t mode) {
1155 char tmp[PATH_MAX];
1156 10 char *p = NULL;
1157
1158 10 snprintf(tmp, sizeof(tmp), "%s", path);
1159 10 size_t len = strlen(tmp);
1160
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (len == 0) return -1;
1161
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (tmp[len - 1] == '/') {
1162 tmp[len - 1] = '\0';
1163 }
1164
1165
2/2
✓ Branch 0 taken 472 times.
✓ Branch 1 taken 10 times.
482 for (p = tmp + 1; *p; p++) {
1166
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 444 times.
472 if (*p == '/') {
1167 28 *p = '\0';
1168
2/4
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 28 times.
28 if (mkdir(tmp, mode) != 0 && errno != EEXIST) {
1169 return -1;
1170 }
1171 28 *p = '/';
1172 }
1173 }
1174
1175
4/4
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 8 times.
10 if (mkdir(tmp, mode) != 0 && errno != EEXIST) {
1176 1 return -1;
1177 }
1178
1179 9 return 0;
1180 }
1181
1182 // open file for appending or writing, creating dirs as needed
1183 15 static FILE *open_file_with_dirs(const char *filename, bool *append) {
1184
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 10 times.
15 if (access(filename, F_OK) == 0) {
1185 FILE *f;
1186
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
5 if (append) {
1187 2 *append = true;
1188 2 f = fopen(filename, "a");
1189 } else {
1190 3 f = fopen(filename, "w");
1191 }
1192
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
5 if (!f) {
1193 1 warning("Cannot open existing file %s: %s", filename, strerror(errno));
1194 }
1195 5 return f;
1196 } else {
1197 // Ensure parent directories exist
1198 char pathbuf[PATH_MAX];
1199 10 snprintf(pathbuf, sizeof(pathbuf), "%s", filename);
1200 10 char *dir = dirname(pathbuf);
1201
1202
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 9 times.
10 if (mkdir_p(dir, 0755) != 0) {
1203 1 warning("Cannot create directory %s: %s", dir, strerror(errno));
1204 1 return NULL;
1205 }
1206
1207
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 5 times.
9 if (append) {
1208 4 *append = false;
1209 }
1210 9 FILE *f = fopen(filename, "w");
1211
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (!f) {
1212 warning("Cannot create file %s: %s", filename, strerror(errno));
1213 }
1214 9 return f;
1215 }
1216 }
1217
1218 1 static char* find_old_schema_name(const char *filename) {
1219
1220 1 size_t orig_len = strlen(filename);
1221
1222 // We should have already checked this, but just in case
1223
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (orig_len < 4 || strncmp(&filename[orig_len - 4], ".csv", 4) != 0) {
1224 return NULL;
1225 }
1226
1227 // Allocate enough for appending up to the following string:
1228 1 size_t max_extra_len = strlen(".old-schema.99");
1229 1 size_t len =
1230 1 orig_len +
1231 max_extra_len +
1232 1;
1233
1234 1 char *out = malloc(len);
1235
1236
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 for (int i = 1; i < 99; ++i) {
1237 1 snprintf(out, len,
1238 "%.*s.old-schema.%d.csv",
1239 1 (int)orig_len - 4,
1240 filename,
1241 i);
1242
1243
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 if (access(out, F_OK) != 0) {
1244 1 return out;
1245 }
1246 }
1247
1248 free(out);
1249 return NULL;
1250 }
1251
1252 6 static void ensure_csv_schema(const char *filename, const char *schema) {
1253
1254
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
8 if (access(filename, F_OK) != 0) return;
1255
1256 3 FILE *f = fopen(filename, "r");
1257
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!f) {
1258 warning("Cannot open %s: %s", filename, strerror(errno));
1259 return;
1260 }
1261
1262 // max header is about 800 characters at the time of writing
1263 char header[2048];
1264
1265 // read first line
1266
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (!fgets(header, sizeof(header), f)) {
1267 if (ferror(f)) {
1268 warning("Cannot read %s: %s",
1269 filename, strerror(errno));
1270 fclose(f);
1271 return;
1272 }
1273
1274 // Empty file: consider it an old/incompatible schema.
1275 header[0] = '\0';
1276 }
1277
1278 3 fclose(f);
1279
1280 // Remove CR/LF
1281 3 size_t len = strlen(header);
1282 3 while(len > 0
1283
3/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 3 times.
6 && (header[len - 1] == '\n'
1284
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 || header[len - 1] == '\r')) {
1285 3 header[--len] = '\0';
1286 }
1287
1288
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if (strcmp(header, schema) == 0) {
1289 // same schema
1290 2 return;
1291 }
1292
1293 1 char *old_schema_filename = find_old_schema_name(filename);
1294
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (old_schema_filename == NULL) {
1295 warning("Could not find a suitable old-schema name for %s\n", filename);
1296 return;
1297 }
1298
1299
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if (rename(filename, old_schema_filename) != 0) {
1300 warning("Cannot move %s to %s: %s", filename, old_schema_filename, strerror(errno));
1301 }
1302
1303 1 warning("The CSV schema changed; the existing data was preserved as %s."
1304 " New data is being written to %s.\n",
1305 old_schema_filename, filename);
1306
1307 1 free(old_schema_filename);
1308 }
1309
1310 5 static inline void ensure_pop_metrics_csv_schema(const char *filename) {
1311
1312
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (pop_metrics_records != NULL) {
1313 5 ensure_csv_schema(filename, pop_metrics_csv_schema);
1314 }
1315 5 }
1316
1317 5 static inline void ensure_process_csv_schema(const char *filename) {
1318
1319
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
5 if (region_records != NULL) {
1320 1 ensure_csv_schema(filename, process_csv_schema);
1321 }
1322 5 }
1323
1324 /*********************************************************************************/
1325 /* Finalize */
1326 /*********************************************************************************/
1327
1328 static int warn_count = 0;
1329 enum { MAX_DETAILED_WARNINGS = 10 };
1330
1331 120 static void sanitize_counter(const char *value_name, double *value, const char *region_name) {
1332
1333
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 118 times.
120 if (*value < 0) {
1334
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (warn_count < MAX_DETAILED_WARNINGS) {
1335 2 warning("Region '%s': counter '%s' had value '%f' (set to 0)",
1336 region_name, value_name, *value);
1337 } else if (warn_count == MAX_DETAILED_WARNINGS) {
1338 warning("Further coefficient warnings suppressed (already reported %d).",
1339 warn_count);
1340 }
1341 2 ++warn_count;
1342 2 *value = 0.0;
1343 }
1344
1345 120 }
1346
1347 690 static void sanitize_coefficient(const char *value_name, float *value, const char *region_name) {
1348
1349
4/4
✓ Branch 0 taken 689 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 680 times.
690 if (*value < -FLT_EPSILON || *value > 1.0f + FLT_EPSILON) {
1350
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
10 if (warn_count < MAX_DETAILED_WARNINGS) {
1351 8 warning("Region '%s': metric '%s' = %f is outside the allowed range"
1352 " [0.0, 1.0]. If you think this is unexpected, please report"
1353 8 " it to %s", region_name, value_name, *value, PACKAGE_BUGREPORT);
1354
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 } else if (warn_count == MAX_DETAILED_WARNINGS) {
1355 1 warning("Further coefficient warnings suppressed (already reported %d).",
1356 warn_count);
1357 }
1358 10 ++warn_count;
1359 }
1360 690 }
1361
1362 39 static void sanitize_records(void) {
1363
1364 typedef struct {
1365 const char *name;
1366 size_t offset;
1367 } field_t;
1368
1369 /* pop_metrics_records:
1370 * - instructions and cycles need to be >= 0
1371 * - computed efficiencies need to be [0.0, 1.0]
1372 */
1373
1374 39 const field_t counters_fields[] = {
1375 { "cycles", offsetof(dlb_pop_metrics_t, cycles) },
1376 { "instructions", offsetof(dlb_pop_metrics_t, instructions) },
1377 };
1378 enum { COUNTERS_FIELDS_SIZE = sizeof(counters_fields)/sizeof(counters_fields[0]) };
1379
1380 39 const field_t coeff_fields[] = {
1381 { "parallel_efficiency", offsetof(dlb_pop_metrics_t, parallel_efficiency) },
1382 { "mpi_parallel_efficiency", offsetof(dlb_pop_metrics_t, mpi_parallel_efficiency) },
1383 { "mpi_communication_efficiency", offsetof(dlb_pop_metrics_t, mpi_communication_efficiency) },
1384 { "mpi_load_balance", offsetof(dlb_pop_metrics_t, mpi_load_balance) },
1385 { "mpi_load_balance_in", offsetof(dlb_pop_metrics_t, mpi_load_balance_in) },
1386 { "mpi_load_balance_out", offsetof(dlb_pop_metrics_t, mpi_load_balance_out) },
1387 { "omp_parallel_efficiency", offsetof(dlb_pop_metrics_t, omp_parallel_efficiency) },
1388 { "omp_load_balance", offsetof(dlb_pop_metrics_t, omp_load_balance) },
1389 { "omp_scheduling_efficiency", offsetof(dlb_pop_metrics_t, omp_scheduling_efficiency) },
1390 { "omp_coverage_efficiency", offsetof(dlb_pop_metrics_t, omp_coverage_efficiency) },
1391 { "device_offload_efficiency", offsetof(dlb_pop_metrics_t, device_offload_efficiency) },
1392 { "gpu_parallel_efficiency", offsetof(dlb_pop_metrics_t, gpu_parallel_efficiency) },
1393 { "gpu_load_balance", offsetof(dlb_pop_metrics_t, gpu_load_balance) },
1394 { "gpu_communication_efficiency", offsetof(dlb_pop_metrics_t, gpu_communication_efficiency) },
1395 { "gpu_orchestration_efficiency", offsetof(dlb_pop_metrics_t, gpu_orchestration_efficiency) },
1396 };
1397 enum { COEFF_FIELDS_SIZE = sizeof(coeff_fields)/sizeof(coeff_fields[0]) };
1398
1399 39 for (GSList *node = pop_metrics_records;
1400
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 39 times.
85 node != NULL;
1401 46 node = node->next) {
1402
1403 46 dlb_pop_metrics_t *record = node->data;
1404
1405
2/2
✓ Branch 0 taken 92 times.
✓ Branch 1 taken 46 times.
138 for (size_t i = 0; i < COUNTERS_FIELDS_SIZE; ++i) {
1406 92 double *value = (double *)((char *)record + counters_fields[i].offset);
1407 92 sanitize_counter(counters_fields[i].name, value, record->name);
1408 }
1409
1410
2/2
✓ Branch 0 taken 690 times.
✓ Branch 1 taken 46 times.
736 for (size_t i = 0; i < COEFF_FIELDS_SIZE; ++i) {
1411 690 float *value = (float *)((char *)record + coeff_fields[i].offset);
1412 690 sanitize_coefficient(coeff_fields[i].name, value, record->name);
1413 }
1414 }
1415
1416 /* node_records: nothing to sanitize for now */
1417
1418 /* region_records: */
1419
1420 39 const field_t monitor_counters_fields[] = {
1421 { "cycles", offsetof(dlb_monitor_t, cycles) },
1422 { "instructions", offsetof(dlb_monitor_t, instructions) },
1423 };
1424 enum { MONITOR_COUNTERS_FIELDS_SIZE =
1425 sizeof(monitor_counters_fields)/sizeof(monitor_counters_fields[0]) };
1426
1427 39 for (GSList *node = region_records;
1428
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 39 times.
53 node != NULL;
1429 14 node = node->next) {
1430
1431 14 region_record_t *region_record = node->data;
1432
1433
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 14 times.
28 for (int i = 0; i < region_record->num_mpi_ranks; ++i) {
1434
1435 14 dlb_monitor_t *monitor = &region_record->process_records[i].monitor;
1436
1437
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 14 times.
42 for (size_t j = 0; j < MONITOR_COUNTERS_FIELDS_SIZE; ++j) {
1438 28 double *value = (double *)((char *)monitor + monitor_counters_fields[j].offset);
1439 28 sanitize_counter(counters_fields[i].name, value, monitor->name);
1440 }
1441 }
1442 }
1443 39 }
1444
1445 /* Return an allocated string: base + "_%h_%p.partial" + extension */
1446 1 static char *build_partial_template(const char *filename) {
1447
1448 1 const char *dot = strrchr(filename, '.');
1449 1 size_t base_len = dot - filename;
1450 1 const char *ext = dot;
1451
1452
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 ensure(dot != NULL, "expected filename with extension %s. Please report bug.", __func__);
1453
1454 1 size_t len =
1455 base_len +
1456 1 strlen("_%h_%p.partial") +
1457 1 strlen(ext) +
1458 1;
1459
1460 1 char *out = malloc(len);
1461
1462 1 snprintf(out, len,
1463 "%.*s_%%h_%%p.partial%s",
1464 (int)base_len,
1465 filename,
1466 ext);
1467
1468 1 return out;
1469 }
1470
1471 /* Detect job ID across schedulers */
1472 2 static const char *get_job_id(void) {
1473
1474 2 const char *vars[] = {
1475 "SLURM_JOB_ID",
1476 "SLURM_JOBID",
1477 "FLUX_JOB_ID",
1478 "PBS_JOBID",
1479 "LSB_JOBID",
1480 "JOB_ID",
1481 NULL
1482 };
1483
1484
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 2 times.
14 for (int i = 0; vars[i]; i++) {
1485 12 const char *v = getenv(vars[i]);
1486
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
12 if (v && *v)
1487 return v;
1488 }
1489
1490 2 return NULL;
1491 }
1492
1493 /* Expands output file, e.g.: talp_%p.json -> talp_123.json */
1494 14 static char *expand_output_filename(const char *template)
1495 {
1496
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 2 times.
14 if (strchr(template, '%') == NULL) return NULL;
1497
1498 char hostname[HOST_NAME_MAX];
1499 2 gethostname(hostname, HOST_NAME_MAX);
1500
1501 2 pid_t pid = getpid();
1502 char pid_buf[32];
1503 2 snprintf(pid_buf, sizeof(pid_buf), "%d", pid);
1504
1505 2 const char *job = get_job_id();
1506
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 const char *job_str = job ? job : "0";
1507
1508 2 size_t hst_len = strlen(hostname);
1509 2 size_t pid_len = strlen(pid_buf);
1510 2 size_t job_len = strlen(job_str);
1511
1512 /* Compute output length */
1513
1514 2 size_t out_len = 0;
1515
1516
2/2
✓ Branch 0 taken 140 times.
✓ Branch 1 taken 2 times.
142 for (size_t i = 0; template[i]; i++) {
1517
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 136 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
140 if (template[i] == '%' && template[i+1]) {
1518
2/5
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
4 switch (template[i+1]) {
1519 2 case 'h':
1520 2 out_len += hst_len;
1521 2 i++;
1522 2 continue;
1523 2 case 'p':
1524 2 out_len += pid_len;
1525 2 i++;
1526 2 continue;
1527 case 'j':
1528 out_len += job_len;
1529 i++;
1530 continue;
1531 case '%':
1532 out_len += 1;
1533 i++;
1534 continue;
1535 }
1536 }
1537 136 out_len += 1;
1538 }
1539
1540
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (out_len > PATH_MAX) return NULL;
1541
1542 /* Fill buffer */
1543
1544 2 char *output_filename = malloc(out_len + 1);
1545
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!output_filename) return NULL;
1546
1547 2 char *out = output_filename;
1548
1549
2/2
✓ Branch 0 taken 140 times.
✓ Branch 1 taken 2 times.
142 for (size_t i = 0; template[i]; ++i) {
1550
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 136 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
140 if (template[i] == '%' && template[i+1]) {
1551
2/5
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
4 switch (template[i+1]) {
1552 2 case 'h':
1553 2 memcpy(out, hostname, hst_len);
1554 2 out += hst_len;
1555 2 ++i;
1556 2 continue;
1557
1558 2 case 'p':
1559 2 memcpy(out, pid_buf, pid_len);
1560 2 out += pid_len;
1561 2 ++i;
1562 2 continue;
1563
1564 case 'j':
1565 memcpy(out, job_str, job_len);
1566 out += job_len;
1567 ++i;
1568 continue;
1569
1570 case '%':
1571 *out++ = '%';
1572 ++i;
1573 continue;
1574 }
1575 }
1576
1577 136 *out++ = template[i];
1578 }
1579
1580 2 *out = '\0';
1581
1582 2 return output_filename;
1583 }
1584
1585 54 void talp_output_finalize(const char *output_file, bool partial_output) {
1586
1587 /* Skip output if process has no data */
1588
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 39 times.
54 if (pop_metrics_records == NULL
1589
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 && node_records == NULL
1590
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 && region_records == NULL) return;
1591
1592 /* For efficiency when adding records, they are prepended to their respective lists.
1593 * Then, they are reversed here to print them in alphabetical order. */
1594 39 pop_metrics_records = g_slist_reverse(pop_metrics_records);
1595 39 node_records = g_slist_reverse(node_records);
1596 39 region_records = g_slist_reverse(region_records);
1597
1598 /* Sanitize erroneous values */
1599 39 sanitize_records();
1600
1601 39 talp_output_record_common();
1602
1603 /* If the process has changed the locale, temporarily push the C locale to
1604 * print floats with the expected notation (a comma as a decimal separator
1605 * will break CSV and JSON files). The object associated with the locale
1606 * can be safely freed after it has been set. */
1607 39 locale_t new_locale = newlocale(LC_ALL, "C", 0);
1608 39 uselocale(new_locale);
1609 39 freelocale(new_locale);
1610
1611
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 14 times.
39 if (output_file == NULL) {
1612 /* No output file, just print all records */
1613 25 pop_metrics_print();
1614 25 node_print();
1615 25 process_print();
1616 } else {
1617 /* Check file extension */
1618 typedef enum Extension {
1619 EXT_JSON,
1620 EXT_XML,
1621 EXT_CSV,
1622 EXT_TXT,
1623 } extension_t;
1624 14 extension_t extension = EXT_TXT;
1625 14 const char *ext = strrchr(output_file, '.');
1626
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 1 times.
14 if (ext != NULL) {
1627
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 7 times.
13 if (strcmp(ext+1, "json") == 0) {
1628 6 extension = EXT_JSON;
1629
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 } else if (strcmp(ext+1, "xml") == 0) {
1630 extension = EXT_XML;
1631
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2 times.
7 } else if (strcmp(ext+1, "csv") == 0) {
1632 5 extension = EXT_CSV;
1633 }
1634 }
1635
1636 /* Deprecation warning*/
1637
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if(extension == EXT_XML){
1638 warning("Deprecated: The support for XML output is deprecated.");
1639 }
1640
1641 /* Flag check */
1642
3/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
14 if (extension != EXT_JSON && partial_output) {
1643 warning("Option --talp-partial-output is only supported for JSON format."
1644 " Disabling option");
1645 partial_output = false;
1646 }
1647
1648 /* Obtain the real filename depending on whether output_file is a template,
1649 * or the user requested partial output */
1650 14 char *template = NULL;
1651 14 char *filename = NULL;
1652
1653
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 13 times.
14 if (partial_output) {
1654 1 template = build_partial_template(output_file);
1655 } else {
1656 13 template = strdup(output_file);
1657 }
1658
1659 14 filename = expand_output_filename(template);
1660
1661
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 12 times.
14 if (filename != NULL) {
1662 2 output_file = filename;
1663 }
1664
1665 14 free(template);
1666
1667 /* Specific case where output file needs to be split */
1668
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 9 times.
14 if (extension == EXT_CSV
1669 5 && !!(pop_metrics_records != NULL)
1670 5 + !!(node_records != NULL)
1671
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
6 + !!(region_records != NULL) > 1) {
1672
1673 /* Length without extension */
1674 1 int filename_useful_len = ext - output_file;
1675
1676 /* POP */
1677
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (pop_metrics_records != NULL) {
1678 1 const char *pop_ext = "-pop.csv";
1679 1 size_t pop_file_len = filename_useful_len + strlen(pop_ext) + 1;
1680 1 char *pop_filename = malloc(sizeof(char)*pop_file_len);
1681 1 sprintf(pop_filename, "%.*s%s", filename_useful_len, output_file, pop_ext);
1682 1 ensure_pop_metrics_csv_schema(pop_filename);
1683 bool append_to_csv;
1684 1 FILE *pop_file = open_file_with_dirs(pop_filename, &append_to_csv);
1685
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (pop_file) {
1686 1 pop_metrics_to_csv(pop_file, append_to_csv);
1687 1 fclose(pop_file);
1688 } else {
1689 warning("Writing metrics to stdout instead:");
1690 pop_metrics_to_csv(stdout, /* append: */ false);
1691 }
1692 }
1693
1694 /* Node */
1695
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (node_records != NULL) {
1696 const char *node_ext = "-node.csv";
1697 size_t node_file_len = filename_useful_len + strlen(node_ext) + 1;
1698 char *node_filename = malloc(sizeof(char)*node_file_len);
1699 sprintf(node_filename, "%.*s%s", filename_useful_len, output_file, node_ext);
1700 bool append_to_csv;
1701 FILE *node_file = open_file_with_dirs(node_filename, &append_to_csv);
1702 if (node_file) {
1703 node_to_csv(node_file, append_to_csv);
1704 fclose(node_file);
1705 } else {
1706 warning("Writing metrics to stdout instead:");
1707 node_to_csv(stdout, /* append: */ false);
1708 }
1709 }
1710
1711 /* Process */
1712
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (region_records != NULL) {
1713 1 const char *process_ext = "-process.csv";
1714 1 size_t process_file_len = filename_useful_len + strlen(process_ext) + 1;
1715 1 char *process_filename = malloc(sizeof(char)*process_file_len);
1716 1 sprintf(process_filename, "%.*s%s", filename_useful_len, output_file, process_ext);
1717 1 ensure_process_csv_schema(process_filename);
1718 bool append_to_csv;
1719 1 FILE *process_file = open_file_with_dirs(process_filename, &append_to_csv);
1720
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (process_file) {
1721 1 process_to_csv(process_file, append_to_csv);
1722 1 fclose(process_file);
1723 } else {
1724 warning("Writing metrics to stdout instead:");
1725 process_to_csv(stdout, /* append: */ false);
1726 }
1727 }
1728 }
1729
1730 /* Write to file */
1731 else {
1732 /* If CSV, ensure that if the file is to be appended, belongs
1733 * to the current schema. */
1734
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 9 times.
13 if (extension == EXT_CSV) {
1735 4 ensure_pop_metrics_csv_schema(output_file);
1736 4 ensure_process_csv_schema(output_file);
1737 }
1738
1739 /* Open file */
1740 bool append_to_csv;
1741
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 9 times.
13 FILE *out_file = open_file_with_dirs(output_file,
1742 extension == EXT_CSV ? &append_to_csv : NULL);
1743
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 11 times.
13 if (!out_file) {
1744 2 warning("Writing metrics to stdout instead:");
1745 2 out_file = stdout;
1746 2 append_to_csv = false;
1747 }
1748
1749 /* Write records to file */
1750
3/4
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
13 switch(extension) {
1751 6 case EXT_JSON:
1752 6 json_header(out_file);
1753 6 common_to_json(out_file);
1754 6 resources_to_json(out_file);
1755 6 process_info_to_json(out_file);
1756 6 pop_metrics_to_json(out_file);
1757 6 node_to_json(out_file);
1758 6 process_to_json(out_file);
1759 6 json_footer(out_file);
1760 6 break;
1761 4 case EXT_CSV:
1762 4 pop_metrics_to_csv(out_file, append_to_csv);
1763 4 node_to_csv(out_file, append_to_csv);
1764 4 process_to_csv(out_file, append_to_csv);
1765 4 break;
1766 3 case EXT_XML:
1767 case EXT_TXT:
1768 3 common_to_txt(out_file);
1769 3 resources_to_txt(out_file);
1770 3 pop_metrics_to_txt(out_file);
1771 3 node_to_txt(out_file);
1772 3 process_to_txt(out_file);
1773 3 break;
1774 }
1775
1776 /* Close file */
1777
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 2 times.
13 if (out_file != stdout) {
1778 11 fclose(out_file);
1779 }
1780 }
1781
1782
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 12 times.
14 if (filename != NULL) {
1783 2 free(filename);
1784 2 filename = NULL;
1785 }
1786 }
1787
1788 // Restore locale
1789 39 uselocale(LC_GLOBAL_LOCALE);
1790
1791 // De-allocate all records
1792 39 common_finalize();
1793 39 pop_metrics_finalize();
1794 39 node_finalize();
1795 39 process_finalize();
1796 }
1797