| 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 |