GCC Code Coverage Report


Directory: src/
File: src/talp/talp_mpi.c
Date: 2026-09-15 07:37:49
Exec Total Coverage
Lines: 43 43 100.0%
Functions: 4 4 100.0%
Branches: 25 38 65.8%

Line Branch Exec Source
1 /*********************************************************************************/
2 /* Copyright 2009-2026 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 #include "talp/talp_mpi.h"
21
22 #include "LB_comm/shmem_talp.h"
23 #include "LB_core/node_barrier.h"
24 #include "LB_core/spd.h"
25 #include "LB_core/thread_ctx.h"
26 #include "apis/dlb_talp.h"
27 #include "support/debug.h"
28 #include "talp/regions.h"
29 #include "talp/sample.h"
30 #include "talp/talp.h"
31 #include "talp/talp_record.h"
32 #include "talp/talp_types.h"
33 #ifdef MPI_LIB
34 #include "mpi/mpi_core.h"
35 #endif
36
37 #include <stdio.h>
38 #include <string.h>
39
40
41 #ifdef MPI_LIB
42 /* Communicate among all MPI processes so that everyone has the same monitoring regions */
43 static void talp_register_common_mpi_regions(const subprocess_descriptor_t *spd) {
44 /* Note: there's a potential race condition if this function is called
45 * (which happens on talp_mpi_finalize or talp_finalize) while another
46 * thread creates a monitoring region. The solution would be to lock the
47 * entire routine and call a specialized registering function that does not
48 * lock, or use a recursive lock. The situation is strange enough to not
49 * support it */
50
51 talp_info_t *talp_info = spd->talp_info;
52
53 /* Warn about open regions */
54 for (GSList *node = talp_info->open_regions;
55 node != NULL;
56 node = node->next) {
57 const dlb_monitor_t *monitor = node->data;
58 warning("Region %s is still open during MPI_Finalize."
59 " Collected data may be incomplete.",
60 monitor->name);
61 }
62
63 /* Gather recvcounts for each process
64 * (Each process may have different number of monitors) */
65 int nregions = g_tree_nnodes(talp_info->regions);
66 int chars_to_send = nregions * DLB_MONITOR_NAME_MAX;
67 int *recvcounts = malloc(_mpi_size * sizeof(int));
68 PMPI_Allgather(&chars_to_send, 1, MPI_INT,
69 recvcounts, 1, MPI_INT, getWorldComm());
70
71 /* Compute total characters to gather via MPI */
72 int i;
73 int total_chars = 0;
74 for (i=0; i<_mpi_size; ++i) {
75 total_chars += recvcounts[i];
76 }
77
78 if (total_chars > 0) {
79 /* Prepare sendbuffer */
80 char *sendbuffer = malloc(nregions * DLB_MONITOR_NAME_MAX * sizeof(char));
81 char *sendptr = sendbuffer;
82 for (GTreeNode *node = g_tree_node_first(talp_info->regions);
83 node != NULL;
84 node = g_tree_node_next(node)) {
85 const dlb_monitor_t *monitor = g_tree_node_value(node);
86 strcpy(sendptr, monitor->name);
87 sendptr += DLB_MONITOR_NAME_MAX;
88 }
89
90 /* Prepare recvbuffer */
91 char *recvbuffer = malloc(total_chars * sizeof(char));
92
93 /* Compute displacements */
94 int *displs = malloc(_mpi_size * sizeof(int));
95 int next_disp = 0;
96 for (i=0; i<_mpi_size; ++i) {
97 displs[i] = next_disp;
98 next_disp += recvcounts[i];
99 }
100
101 /* Gather all regions */
102 PMPI_Allgatherv(sendbuffer, nregions * DLB_MONITOR_NAME_MAX, MPI_CHAR,
103 recvbuffer, recvcounts, displs, MPI_CHAR, getWorldComm());
104
105 /* Register all regions. Existing ones will be skipped. */
106 for (i=0; i<total_chars; i+=DLB_MONITOR_NAME_MAX) {
107 region_register(spd, &recvbuffer[i]);
108 }
109
110 free(sendbuffer);
111 free(recvbuffer);
112 free(displs);
113 }
114
115 free(recvcounts);
116 }
117 #endif
118
119
120 /*********************************************************************************/
121 /* TALP MPI functions */
122 /*********************************************************************************/
123
124 /* Start global monitoring region (if not already started) */
125 13 void talp_mpi_init(const subprocess_descriptor_t *spd) {
126
127
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
13 ensure(thread_is_profiled(), "A non-profiled thread cannot call talp_mpi_init");
128
129 13 talp_info_t *talp_info = spd->talp_info;
130
131
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 if (talp_info) {
132 13 talp_info->flags.have_mpi = true;
133
134 /* Start global region (no-op if already started) */
135 13 region_start(spd, talp_info->monitor);
136
137 /* Add MPI_Init statistic and set useful state */
138 13 talp_sample_t *sample = talp_sample_get(talp_info);
139 13 ++sample->stats.num_mpi_calls;
140 13 talp_sample_set_state(talp_info, TALP_STATE_USEFUL);
141 }
142 13 }
143
144 /* Stop global monitoring region and gather APP data if needed */
145 10 void talp_mpi_finalize(const subprocess_descriptor_t *spd) {
146
147
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 ensure(thread_is_profiled(), "A non-profiled thread cannot call talp_mpi_finalize");
148
149 10 talp_info_t *talp_info = spd->talp_info;
150
151
2/4
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
10 if (talp_info == NULL || !talp_info->flags.have_mpi) return;
152
153 #ifdef MPI_LIB
154 /* We also need to measure the waiting time of an MPI_Finalize.
155 * For this, we call an MPI_Barrier and the appropriate TALP functions.
156 * The num_mpi_calls variable is also incremented inside those. */
157 sync_call_flags_t flags = { .is_mpi = true, .is_blocking = true, .is_collective = true };
158 talp_into_sync_call(spd, flags);
159 PMPI_Barrier(getWorldComm());
160 talp_out_of_sync_call(spd, flags);
161 #else
162 /* Add MPI_Finalize to the number of MPI calls.
163 * Even though talp_mpi_finalize should never be called if no MPI_LIB,
164 * we keep this case for testing purposes. */
165 10 talp_sample_t *sample = talp_sample_get(talp_info);
166 10 ++sample->stats.num_mpi_calls;
167 #endif
168
169 /* Stop global region */
170 10 region_stop(spd, talp_info->monitor);
171
172 10 monitor_data_t *monitor_data = talp_info->monitor->_data;
173
174 /* Update shared memory values */
175
3/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
10 if (talp_info->flags.have_shmem || talp_info->flags.have_minimal_shmem) {
176 // TODO: is it needed? isn't it updated when stopped?
177 5 shmem_talp__set_times(monitor_data->node_shared_id,
178 5 talp_info->monitor->mpi_time,
179 5 talp_info->monitor->useful_time);
180 }
181
182 /* If TALP partial output is enabled, metrics are not merged here.
183 * Output is written per process in talp_finalize() */
184
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (spd->options.talp_partial_output) return;
185
186 #ifdef MPI_LIB
187 /* If performing any kind of TALP summary, check that the number of processes
188 * registered in the shared memory matches with the number of MPI processes in the node.
189 * This check is needed to avoid deadlocks on finalize. */
190 if (spd->options.talp_summary) {
191 verbose(VB_TALP, "Gathering TALP metrics");
192
193 /* Gather data among processes in the node if node summary is enabled */
194 if (spd->options.talp_summary & SUMMARY_NODE) {
195 talp_record_node_summary(spd);
196 }
197
198 /* Gather data among MPIs if any of these summaries is enabled */
199 if (spd->options.talp_summary
200 & (SUMMARY_POP_METRICS | SUMMARY_PROCESS)) {
201 /* Ensure everyone has the same monitoring regions */
202 talp_register_common_mpi_regions(spd);
203
204 /* Finally, reduce data */
205 for (GTreeNode *node = g_tree_node_first(talp_info->regions);
206 node != NULL;
207 node = g_tree_node_next(node)) {
208 const dlb_monitor_t *monitor = g_tree_node_value(node);
209 if (spd->options.talp_summary & SUMMARY_POP_METRICS) {
210 talp_record_pop_summary(spd, monitor);
211 }
212 if (spd->options.talp_summary & SUMMARY_PROCESS) {
213 talp_record_process_summary(spd, monitor);
214 }
215 }
216 }
217 }
218 #endif
219 }
220
221 24 void talp_into_sync_call(const subprocess_descriptor_t *spd, sync_call_flags_t flags) {
222
223 /* Observer and unknown threads may call MPI functions, but TALP must ignore them */
224
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 23 times.
24 if (unlikely(!thread_is_profiled())) return;
225
226 23 talp_info_t *talp_info = spd->talp_info;
227
228
2/4
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 23 times.
23 if (talp_info == NULL || !talp_info->flags.have_mpi) return;
229
230 /* Update sample */
231 23 talp_sample_update(talp_info);
232
233 /* Into Sync call -> not_useful_mpi */
234 23 talp_sample_set_state(talp_info, TALP_STATE_NOT_USEFUL_MPI);
235 }
236
237 24 void talp_out_of_sync_call(const subprocess_descriptor_t *spd, sync_call_flags_t flags) {
238
239 /* Observer and unknown threads may call MPI functions, but TALP must ignore them */
240
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 23 times.
24 if (unlikely(!thread_is_profiled())) return;
241
242 23 talp_info_t *talp_info = spd->talp_info;
243
244
2/4
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 23 times.
23 if (talp_info == NULL || !talp_info->flags.have_mpi) return;
245
246 /* Update sample */
247 23 talp_sample_update(talp_info);
248
249 /* Add statistic only if this is a real MPI call and not a DLB_Barrier */
250
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 21 times.
23 if (flags.is_mpi) {
251 2 talp_sample_t *sample = talp_sample_get(talp_info);
252 2 ++sample->stats.num_mpi_calls;
253 }
254
255 /* Out of Sync call -> useful */
256 23 talp_sample_set_state(talp_info, TALP_STATE_USEFUL);
257
258 /* Only when needed, update all regions */
259
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 5 times.
23 if (talp_info->flags.external_profiler
260
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 && thread_is_main_sequential()
261
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 3 times.
18 && flags.is_blocking
262
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 && flags.is_collective) {
263 15 talp_aggregate_samples_to_regions(talp_info);
264 }
265 }
266