| 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 | /* | ||
| 21 | * Here we describe the logic of the DLB module MNGO. | ||
| 22 | * | ||
| 23 | * The concept of MNGO is that it is in charge of periodically sampling the | ||
| 24 | * applications performance and take action acordingly by | ||
| 25 | * activating/deactivating other other DLB modules to improve the overall | ||
| 26 | * applications efficiency. | ||
| 27 | * | ||
| 28 | * The general idea is to use TALP to collect different metrics (both within | ||
| 29 | * the same node, and across). Then use these metrics to decide wheather or not | ||
| 30 | * activating LeWI or DROM would improve the applications performance or | ||
| 31 | * efficiency, and activate the corresponding module based on different | ||
| 32 | * polices. | ||
| 33 | */ | ||
| 34 | |||
| 35 | /* | ||
| 36 | * For activating the DLB modules in a coordinated fashion we have implemented a | ||
| 37 | * shared memory interface. | ||
| 38 | */ | ||
| 39 | #include "LB_comm/shmem_barrier.h" | ||
| 40 | #include "LB_comm/shmem_mngo.h" | ||
| 41 | |||
| 42 | /* | ||
| 43 | * To use DROM we use the procinfo shared memory interface to change the process | ||
| 44 | * masks according to polices. | ||
| 45 | */ | ||
| 46 | #include "LB_comm/shmem_procinfo.h" | ||
| 47 | |||
| 48 | /* | ||
| 49 | * To activate and deactivate LeWI we use the `set_lewi_enabled` function | ||
| 50 | * included in the DLB_kernel.h | ||
| 51 | */ | ||
| 52 | #include "LB_core/DLB_kernel.h" | ||
| 53 | |||
| 54 | // Used to set the thread to observer when using the helper-thread mode | ||
| 55 | #include "LB_core/thread_ctx.h" | ||
| 56 | |||
| 57 | /* | ||
| 58 | * The MODULE communicates with TALP to gather performance metrics of the | ||
| 59 | * application. | ||
| 60 | */ | ||
| 61 | #include "LB_core/node_barrier.h" | ||
| 62 | #include "LB_core/thread_ctx.h" | ||
| 63 | #include "apis/dlb_mngo.h" | ||
| 64 | #include "dlb_talp.h" | ||
| 65 | #include "mngo/mngo_drom.h" | ||
| 66 | #include "mngo/mngo_resources.h" | ||
| 67 | #include "talp/regions.h" | ||
| 68 | #include "talp/talp.h" | ||
| 69 | /* | ||
| 70 | * | ||
| 71 | */ | ||
| 72 | #include "LB_core/spd.h" | ||
| 73 | /* | ||
| 74 | * | ||
| 75 | */ | ||
| 76 | #include "mngo/mngo.h" | ||
| 77 | #include "mngo/mngo_balancer.h" | ||
| 78 | /* | ||
| 79 | * All the functions where something could go wrong will report an error defined | ||
| 80 | * as an enum value in DLBErrorCodes. | ||
| 81 | */ | ||
| 82 | #include "apis/dlb_errors.h" | ||
| 83 | /* | ||
| 84 | * We have used the verbose function to report extra information about what the | ||
| 85 | * MODULE is doing when the debug flag is enabled. | ||
| 86 | */ | ||
| 87 | #include "support/debug.h" | ||
| 88 | /* | ||
| 89 | * Similarly we use the Extrae API to generate MNGO events to visualize the | ||
| 90 | * MODULE in action in Paraver traces. | ||
| 91 | */ | ||
| 92 | #include "support/queues.h" | ||
| 93 | #include "support/tracing.h" | ||
| 94 | /* | ||
| 95 | * When we use DROM we need to transmit information to the threads that are part | ||
| 96 | * of the OpenMP team so them can change the mask. To communicate between | ||
| 97 | * different threads of the same process we have used atomic variables. | ||
| 98 | */ | ||
| 99 | #include "apis/dlb_types.h" | ||
| 100 | #include "support/types.h" | ||
| 101 | |||
| 102 | #include "support/mask_utils.h" | ||
| 103 | |||
| 104 | #include <sched.h> | ||
| 105 | #include <stdlib.h> | ||
| 106 | #include <string.h> | ||
| 107 | #include <unistd.h> | ||
| 108 | |||
| 109 | /* | ||
| 110 | * To ceil the drom balancing. | ||
| 111 | */ | ||
| 112 | #include <math.h> | ||
| 113 | |||
| 114 | #include "mngo/mngo_talp.h" | ||
| 115 | |||
| 116 | /* | ||
| 117 | * True when mngo has been initialized correctly | ||
| 118 | */ | ||
| 119 | static bool have_mngo = false; | ||
| 120 | static bool have_drom = false; | ||
| 121 | static bool have_lewi = false; | ||
| 122 | |||
| 123 | static bool have_helper_thread = false; | ||
| 124 | static __thread bool im_helper_thread = false; | ||
| 125 | |||
| 126 | static const char *helper_thread_region_name = "helper-thread"; | ||
| 127 | |||
| 128 | /* | ||
| 129 | * To take decisions we need a history of performance to make an educated guess | ||
| 130 | * on what is the best action to improve performance. | ||
| 131 | */ | ||
| 132 | |||
| 133 | #include <LB_comm/shmem_cpuinfo.h> | ||
| 134 | |||
| 135 | 12 | static mngo_actions_t mngo_actions(const subprocess_descriptor_t *spd, | |
| 136 | const mngo_reduced_metric_t *metrics) { | ||
| 137 | 12 | mngo_info_t *mngo_info = spd->mngo_info; | |
| 138 | |||
| 139 | // Load balance in deviation: is the percentage of parallel efficiency | ||
| 140 | // difference of this process with relation to the node average. | ||
| 141 | 12 | float lb_in_deviation = (metrics->self_pe / metrics->shared_pe) - 1; | |
| 142 | |||
| 143 | // We consider that there is LB_IN if the deviation absolute value of | ||
| 144 | // deviation is greater than deviation. | ||
| 145 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
|
18 | bool lb_in = (lb_in_deviation > +mngo_info->lb_in_threshold || |
| 146 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | lb_in_deviation < -mngo_info->lb_in_threshold); |
| 147 | |||
| 148 | /* | ||
| 149 | * For now only one decision can be taken per process each time. So when | ||
| 150 | * any policy is activated we return. | ||
| 151 | */ | ||
| 152 | 12 | mngo_actions_t action = MNGO_ACTION_NONE; | |
| 153 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (lb_in) { |
| 154 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
|
12 | if (have_drom) { |
| 155 | // TODO: in the event where both LeWI and DROM are enabled we try | ||
| 156 | // first with DROM and then if we observe performance degradation/no | ||
| 157 | // performance increase we skip this and try with LeWI. | ||
| 158 | 6 | action = MNGO_ACTION_DROM; | |
| 159 | 6 | goto policy_applied; | |
| 160 | } | ||
| 161 | |||
| 162 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (have_lewi) { |
| 163 | 6 | action = MNGO_ACTION_LEWI_START; | |
| 164 | 6 | goto policy_applied; | |
| 165 | } | ||
| 166 | } | ||
| 167 | ✗ | policy_applied: | |
| 168 | |||
| 169 | // Share the actions accross nodes to aggree on the new decission | ||
| 170 | 12 | shmem_mngo__alltoall_actions(mngo_info->mid, &action); | |
| 171 | |||
| 172 | 12 | return action; | |
| 173 | } | ||
| 174 | |||
| 175 | /* | ||
| 176 | * Finally we take the actions that have been collectively decided and we apply | ||
| 177 | * them. | ||
| 178 | */ | ||
| 179 | 12 | static void mngo_apply_actions(subprocess_descriptor_t *spd, | |
| 180 | mngo_actions_t *actions, cpu_set_t *next_mask) { | ||
| 181 | 12 | mngo_info_t *mngo_info = spd->mngo_info; | |
| 182 | mngo_state_t *state = | ||
| 183 | 12 | mngo_regions__get_current_resources(&mngo_info->region_handler); | |
| 184 | |||
| 185 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
12 | switch (*actions) { |
| 186 | 6 | case MNGO_ACTION_LEWI_START: { | |
| 187 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | verbose(VB_MNGO, "Starting LeWI"); |
| 188 | instrument_event(MNGO_MANAGER, EVENT_MNGO_LEWI_ON, EVENT_BEGINEND); | ||
| 189 | 6 | state->lewi_on = true; | |
| 190 | 6 | break; | |
| 191 | } | ||
| 192 | ✗ | case MNGO_ACTION_LEWI_STOP: { | |
| 193 | ✗ | verbose(VB_MNGO, "Stopping LeWI"); | |
| 194 | instrument_event(MNGO_MANAGER, EVENT_MNGO_LEWI_OFF, EVENT_BEGINEND); | ||
| 195 | ✗ | state->lewi_on = false; | |
| 196 | ✗ | break; | |
| 197 | } | ||
| 198 | 6 | case MNGO_ACTION_DROM: { | |
| 199 | // instrument_event done outside this function, in mngo_manger | ||
| 200 | 6 | memcpy(&state->drom_mask, next_mask, sizeof(cpu_set_t)); | |
| 201 | 6 | break; | |
| 202 | } | ||
| 203 | ✗ | default: | |
| 204 | ✗ | break; | |
| 205 | } | ||
| 206 | 12 | } | |
| 207 | |||
| 208 | /* | ||
| 209 | * The MPI collectives mode will run the mngo_manager in the same thread that | ||
| 210 | * is executing while an MPI collective call with the MPI_COMM_WORLD | ||
| 211 | * communicator is used. Since the most applications usually have some | ||
| 212 | * consecutive collective calls. To prevent calling the mngo_manager to | ||
| 213 | * continuously we use the mngo interval option as a cool-down time while all | ||
| 214 | * collectives will be ignored and no manager will be called. | ||
| 215 | * | ||
| 216 | * This mode will directly call the mngo_manager function to run the MNGO core | ||
| 217 | * functionality. | ||
| 218 | */ | ||
| 219 | 32 | int mngo_manager(subprocess_descriptor_t *spd, dlb_mngo_region_t *region, | |
| 220 | mngo_manager_entrypoint_t entrypoint) { | ||
| 221 | |||
| 222 | 32 | int error = DLB_SUCCESS; | |
| 223 | |||
| 224 | 32 | mngo_info_t *mngo_info = spd->mngo_info; | |
| 225 | 32 | mngo_regions_manager_t *region_manager = &mngo_info->region_handler; | |
| 226 | |||
| 227 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 16 times.
|
32 | if (entrypoint & MANAGER_END) { |
| 228 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 12 times.
|
16 | if (!mngo_regions__check_top(region_manager, region)) { |
| 229 | 4 | return DLB_NOUPDT; | |
| 230 | } | ||
| 231 | |||
| 232 | 12 | mngo_region_info_t *region_info = ®ion->region_info; | |
| 233 | |||
| 234 | // Collect metrics and update metrics history | ||
| 235 | { | ||
| 236 | instrument_event(MNGO_MANAGER, EVENT_MNGO_TALP, EVENT_BEGIN); | ||
| 237 | 12 | region_stop(spd, region_info->talp.monitor); // TALP region stop | |
| 238 | mngo_talp_metrics_t *metrics = | ||
| 239 | 12 | mngo_metrics__history_emplace(®ion_info->metrics_history); | |
| 240 | 12 | mngo_talp__take_metrics(spd, ®ion_info->talp, metrics); | |
| 241 | instrument_event(MNGO_MANAGER, EVENT_MNGO_TALP, EVENT_END); | ||
| 242 | } | ||
| 243 | |||
| 244 | { | ||
| 245 | instrument_event(MNGO_MANAGER, EVENT_MNGO_HISTORY, EVENT_BEGIN); | ||
| 246 | mngo_reduced_metric_t metrics; | ||
| 247 | 12 | mngo_metrics__history_tendency(®ion_info->metrics_history, | |
| 248 | &metrics); | ||
| 249 | instrument_event(MNGO_MANAGER, EVENT_MNGO_HISTORY, EVENT_END); | ||
| 250 | |||
| 251 | instrument_event(MNGO_MANAGER, EVENT_MNGO_DECIDE, EVENT_BEGIN); | ||
| 252 | 12 | mngo_actions_t actions = mngo_actions(spd, &metrics); | |
| 253 | instrument_event(MNGO_MANAGER, EVENT_MNGO_DECIDE, EVENT_END); | ||
| 254 | |||
| 255 | cpu_set_t new_cpu_mask; | ||
| 256 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
|
12 | if (MNGO_ACTION_DROM == actions) { |
| 257 | instrument_event(MNGO_MANAGER, EVENT_MNGO_DROM_IN, EVENT_BEGIN); | ||
| 258 | 6 | mngo_drom__balance(spd, metrics.self_pe, metrics.shared_pe, | |
| 259 | &new_cpu_mask); | ||
| 260 | } | ||
| 261 | |||
| 262 | 12 | mngo_apply_actions(spd, &actions, &new_cpu_mask); | |
| 263 | |||
| 264 | 12 | if (MNGO_ACTION_DROM == actions) { | |
| 265 | instrument_event(MNGO_MANAGER, EVENT_MNGO_DROM_IN, EVENT_END); | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | 12 | mngo_regions__pop_active(region_manager, region); | |
| 270 | 12 | mngo_state__load(spd, | |
| 271 | mngo_regions__get_current_resources(region_manager)); | ||
| 272 | |||
| 273 | 12 | shmem_mngo_barrier_wait(); | |
| 274 | } | ||
| 275 | |||
| 276 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 12 times.
|
28 | if (entrypoint & MANAGER_BEGIN) { |
| 277 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 12 times.
|
16 | if (mngo_regions__check_top(region_manager, region)) { |
| 278 | 4 | return DLB_NOUPDT; | |
| 279 | } | ||
| 280 | |||
| 281 | 12 | mngo_regions__push_active(region_manager, region); | |
| 282 | 12 | mngo_state__load(spd, | |
| 283 | mngo_regions__get_current_resources(region_manager)); | ||
| 284 | |||
| 285 | 12 | mngo_region_info_t *region_info = ®ion->region_info; | |
| 286 | |||
| 287 | 12 | region_start(spd, region_info->talp.monitor); // TALP region start | |
| 288 | |||
| 289 | 12 | shmem_mngo_barrier_wait(); | |
| 290 | } | ||
| 291 | |||
| 292 | 24 | return error; // TODO: track errors | |
| 293 | } | ||
| 294 | |||
| 295 | /* | ||
| 296 | * The regions mode will implement two calls, one to start the regions and the | ||
| 297 | * other to end it. Performance information will then be stored for each | ||
| 298 | * region, and each region will have its own profile. The profile will contain | ||
| 299 | * the information about which DLB modules are being used in the specific | ||
| 300 | * region. | ||
| 301 | */ | ||
| 302 | |||
| 303 | 12 | dlb_mngo_region_t *mngo_region_register(const subprocess_descriptor_t *spd, | |
| 304 | const char *name) { | ||
| 305 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (!have_mngo) return NULL; |
| 306 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
12 | if (have_helper_thread && !im_helper_thread) return NULL; |
| 307 | |||
| 308 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | verbose(VB_MNGO, "Registering region %s", name); |
| 309 | |||
| 310 | 12 | mngo_info_t *mngo_info = spd->mngo_info; | |
| 311 | 12 | mngo_regions_manager_t *region_manager = &mngo_info->region_handler; | |
| 312 | |||
| 313 | 12 | dlb_mngo_region_t *region_found = mngo_regions__find(region_manager, name); | |
| 314 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 8 times.
|
12 | if (NULL != region_found) { |
| 315 | 4 | return region_found; | |
| 316 | } | ||
| 317 | |||
| 318 | /** | ||
| 319 | * If the region does not exists we create a new one. | ||
| 320 | */ | ||
| 321 | mngo_state_t *current_state = | ||
| 322 | 8 | mngo_regions__get_current_resources(region_manager); | |
| 323 | |||
| 324 | 8 | dlb_mngo_region_t *region = mngo_regions__alloc(region_manager); | |
| 325 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (NULL == region) { |
| 326 | ✗ | return NULL; | |
| 327 | } | ||
| 328 | |||
| 329 | // The new region inherits the current state | ||
| 330 | 8 | region->region_info.state = *current_state; | |
| 331 | |||
| 332 | // We create a monitoring region for the new region | ||
| 333 | int region_register_error = | ||
| 334 | 8 | mngo_talp__region_register(spd, name, ®ion->region_info.talp); | |
| 335 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | if (DLB_SUCCESS != region_register_error) { |
| 336 | ✗ | return NULL; | |
| 337 | } | ||
| 338 | |||
| 339 | // The region name lives in the TALP monitor. | ||
| 340 | 8 | region->name = mngo_talp__get_region_name(®ion->region_info.talp); | |
| 341 | |||
| 342 | // We initialize the performance history of the region | ||
| 343 | 8 | mngo_metrics__history_init(®ion->region_info.metrics_history); | |
| 344 | |||
| 345 | 8 | return region; | |
| 346 | } | ||
| 347 | |||
| 348 | 16 | int mngo_region_start(subprocess_descriptor_t *spd, dlb_mngo_region_t *region) { | |
| 349 | 16 | int error = DLB_SUCCESS; | |
| 350 | |||
| 351 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
|
16 | if (!have_mngo) return DLB_ERR_NOMNGO; |
| 352 | |||
| 353 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | if (!have_helper_thread) { |
| 354 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | verbose(VB_MNGO, "Starting region %s", region->name); |
| 355 | |||
| 356 | 16 | error = mngo_manager(spd, region, MANAGER_BEGIN); | |
| 357 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 12 times.
|
16 | if (error != DLB_SUCCESS) goto bail_on_error; |
| 358 | |||
| 359 | // the if stops here because if we are running with helper-thread we | ||
| 360 | // will take the oportunity to call poll_drom_update. | ||
| 361 | } | ||
| 362 | |||
| 363 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
|
12 | if (have_drom) { |
| 364 | 6 | error = poll_drom_update(spd); | |
| 365 | // No change is spossible so we translate to success | ||
| 366 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (error == DLB_NOUPDT) error = DLB_SUCCESS; |
| 367 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (error != DLB_SUCCESS) goto bail_on_error; |
| 368 | } | ||
| 369 | |||
| 370 | 12 | bail_on_error: | |
| 371 | 16 | return error; | |
| 372 | } | ||
| 373 | |||
| 374 | 16 | int mngo_region_stop(subprocess_descriptor_t *spd, dlb_mngo_region_t *region) { | |
| 375 | 16 | int error = DLB_SUCCESS; | |
| 376 | |||
| 377 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
|
16 | if (!have_mngo) return DLB_ERR_NOMNGO; |
| 378 | |||
| 379 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | if (!have_helper_thread) { |
| 380 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | verbose(VB_MNGO, "Stopping region %s", region->name); |
| 381 | 16 | mngo_info_t *mngo_info = spd->mngo_info; | |
| 382 | 16 | shmem_barrier__barrier(mngo_info->dlb_node_barrier); | |
| 383 | |||
| 384 | 16 | error = mngo_manager(spd, region, MANAGER_END); | |
| 385 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 12 times.
|
16 | if (error != DLB_SUCCESS) goto bail_on_error; |
| 386 | |||
| 387 | // the if stops here because if we are running with helper-thread we | ||
| 388 | // will take the oportunity to call poll_drom_update. | ||
| 389 | } | ||
| 390 | |||
| 391 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
|
12 | if (have_drom) { |
| 392 | 6 | error = poll_drom_update(spd); | |
| 393 | // No change is spossible so we translate to success | ||
| 394 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (error == DLB_NOUPDT) error = DLB_SUCCESS; |
| 395 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (error != DLB_SUCCESS) goto bail_on_error; |
| 396 | } | ||
| 397 | |||
| 398 | 12 | bail_on_error: | |
| 399 | 16 | return error; | |
| 400 | } | ||
| 401 | |||
| 402 | /* | ||
| 403 | * The helper thread mode will spawn a pthread that will sleep for an interval | ||
| 404 | * of time defined in the options. After the interval of time the thread will | ||
| 405 | * wakeup, execute the mngo_manger, which will apply selected MNGO policy. | ||
| 406 | */ | ||
| 407 | ✗ | static void mngo_helper_thread(subprocess_descriptor_t *spd) { | |
| 408 | |||
| 409 | ✗ | im_helper_thread = true; | |
| 410 | ✗ | thread_spd = spd; | |
| 411 | |||
| 412 | ✗ | thread_ctx_set_observer(true); | |
| 413 | |||
| 414 | ✗ | mngo_info_t *mngo_info = spd->mngo_info; | |
| 415 | |||
| 416 | ✗ | verbose(VB_MNGO, "Rank %d started helper thread", mngo_info->rank); | |
| 417 | |||
| 418 | dlb_mngo_region_t *region = | ||
| 419 | ✗ | mngo_region_register(spd, helper_thread_region_name); | |
| 420 | |||
| 421 | while (1) { | ||
| 422 | /** | ||
| 423 | * Only the node representative manager gathers the metrics, takes | ||
| 424 | * decisions and sends them to the other managers. | ||
| 425 | */ | ||
| 426 | ✗ | if (mngo_info->mid == 0) { | |
| 427 | /** Time between MNGO managment events. | ||
| 428 | */ | ||
| 429 | struct timeval now; | ||
| 430 | ✗ | gettimeofday(&now, NULL); | |
| 431 | |||
| 432 | ✗ | struct timespec abstime = { | |
| 433 | ✗ | .tv_sec = now.tv_sec + thread_spd->options.mngo_interval_time, | |
| 434 | .tv_nsec = 0, | ||
| 435 | }; | ||
| 436 | |||
| 437 | ✗ | shmem_mngo_manager_wait(&abstime); | |
| 438 | } | ||
| 439 | |||
| 440 | ✗ | shmem_mngo_barrier_wait(); | |
| 441 | |||
| 442 | ✗ | if (shmem_mngo_check_stop_flag()) { | |
| 443 | ✗ | break; | |
| 444 | } | ||
| 445 | |||
| 446 | ✗ | verbose(VB_MNGO, "Rank %d helper thread has woken up", mngo_info->rank); | |
| 447 | |||
| 448 | ✗ | mngo_manager(spd, region, (mngo_manager_entrypoint_t)(MANAGER_BEGIN | MANAGER_END)); | |
| 449 | } | ||
| 450 | |||
| 451 | ✗ | verbose(VB_MNGO, "Rank %d helper thread stopping", mngo_info->rank); | |
| 452 | |||
| 453 | /** | ||
| 454 | * Wait all the managers from the node to finalize. | ||
| 455 | */ | ||
| 456 | ✗ | shmem_mngo_fini_sync(); | |
| 457 | |||
| 458 | /** | ||
| 459 | * Close the shared memory used by the manager thread. | ||
| 460 | */ | ||
| 461 | ✗ | shmem_mngo_fini(mngo_info->mid); | |
| 462 | |||
| 463 | // Finalize thread | ||
| 464 | ✗ | pthread_exit(0); | |
| 465 | } | ||
| 466 | |||
| 467 | /* | ||
| 468 | * For the MODULE to work properly some initialization steps have to be taken. | ||
| 469 | * We provide the mngo_init function to prepare the MODULE for use. This | ||
| 470 | * function will first allocate and initialize the mngo_info, initialize the | ||
| 471 | * shared memory, and provide a unique (locally in the node) identifier to each | ||
| 472 | * caller, create a TALP region for the MODULE, and also a private MPI | ||
| 473 | * communicator for the MODULE. | ||
| 474 | * | ||
| 475 | * This module will finally spawn a helper thread if the `mngo-mode` option is | ||
| 476 | * set to `helper-thread`. | ||
| 477 | */ | ||
| 478 | static void mngo_helper_thread(subprocess_descriptor_t *spd); | ||
| 479 | /* | ||
| 480 | * For now the `helper-thread` option is the only one available, and the helper | ||
| 481 | * thread is created always. In the future other modes will implemented such a | ||
| 482 | * `mpi-collectives` mode which will call the mngo manager on MPI collective | ||
| 483 | * calls, or the `region` mode, where using an API the developer will define | ||
| 484 | * different regions in the execution, and then MNGO will create automatic | ||
| 485 | * profiles for the different regions. | ||
| 486 | */ | ||
| 487 | 14 | void mngo_init(subprocess_descriptor_t *spd) { | |
| 488 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
|
14 | if (have_mngo) return; |
| 489 | |||
| 490 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
|
14 | if (spd->options.lewi) { |
| 491 | 4 | have_lewi = true; | |
| 492 | } | ||
| 493 | |||
| 494 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 4 times.
|
14 | if (spd->options.drom) { |
| 495 | 10 | have_drom = true; | |
| 496 | } | ||
| 497 | |||
| 498 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
|
14 | if (spd->options.mngo_mode == MNGO_HELPER_THREAD) { |
| 499 | ✗ | have_helper_thread = true; | |
| 500 | #ifdef MPI_LIB | ||
| 501 | int any_abort_init, abort_init = 0; | ||
| 502 | int mpi_thread_provided; | ||
| 503 | int error = PMPI_Query_thread(&mpi_thread_provided); | ||
| 504 | |||
| 505 | if (error != MPI_SUCCESS) { | ||
| 506 | warning0("Could not check the MPI thread level, required for " | ||
| 507 | "helper-thread mode."); | ||
| 508 | abort_init = 1; | ||
| 509 | } | ||
| 510 | |||
| 511 | if (mpi_thread_provided < MPI_THREAD_MULTIPLE) { | ||
| 512 | abort_init = 1; | ||
| 513 | } | ||
| 514 | |||
| 515 | PMPI_Allreduce(&abort_init, &any_abort_init, 1, MPI_INT, MPI_SUM, | ||
| 516 | MPI_COMM_WORLD); | ||
| 517 | if (any_abort_init > 0) { | ||
| 518 | warning0("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" | ||
| 519 | "!!!!!!!!!"); | ||
| 520 | warning0("!!! Trying to initialize MNGO with helper-thread mode, " | ||
| 521 | "but the !!!"); | ||
| 522 | warning0("!!! MPI thread level is lower than MPI_THREAD_MULTIPLE. " | ||
| 523 | " !!!"); | ||
| 524 | warning0("!!! ABORTING MNGO INITIALIZATION " | ||
| 525 | " !!!"); | ||
| 526 | warning0("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" | ||
| 527 | "!!!!!!!!!"); | ||
| 528 | spd->mngo_info = NULL; | ||
| 529 | return; | ||
| 530 | } | ||
| 531 | #endif // MPI_LIB | ||
| 532 | } | ||
| 533 | |||
| 534 | 14 | mngo_info_t *mngo_info = (mngo_info_t *)calloc(1, sizeof(mngo_info_t)); | |
| 535 | |||
| 536 | #ifdef MPI_LIB | ||
| 537 | int rank; | ||
| 538 | PMPI_Comm_rank(MPI_COMM_WORLD, &rank); | ||
| 539 | PMPI_Comm_dup(MPI_COMM_WORLD, &mngo_info->comm); | ||
| 540 | #else | ||
| 541 | 14 | int rank = 0; | |
| 542 | #endif | ||
| 543 | |||
| 544 | /** | ||
| 545 | * Initialize this process manegers shared memory. | ||
| 546 | */ | ||
| 547 | 14 | pid_t pid = spd->id; | |
| 548 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
|
14 | if (shmem_mngo_init(spd->options.shm_key, pid, rank, &mngo_info->mid) != |
| 549 | DLB_SUCCESS) { | ||
| 550 | // DO SOMETHING | ||
| 551 | ✗ | fatal("MNGO Shared memory initialization failed."); | |
| 552 | } | ||
| 553 | |||
| 554 | 14 | mngo_info->rank = rank; | |
| 555 | |||
| 556 | cpu_set_t mask; | ||
| 557 | 14 | shmem_procinfo__getprocessmask(shmem_mngo_get_pid(mngo_info->mid), &mask, | |
| 558 | DLB_DROM_FLAGS_NONE); | ||
| 559 | |||
| 560 | 14 | mngo_info->default_state = (const mngo_state_t){ | |
| 561 | .lewi_on = false, | ||
| 562 | }; | ||
| 563 | 14 | memcpy(&mngo_info->default_state.drom_mask, &mask, sizeof(cpu_set_t)); | |
| 564 | |||
| 565 | 14 | mngo_regions__manager_init(&mngo_info->region_handler, | |
| 566 | &mngo_info->default_state); | ||
| 567 | |||
| 568 | /** | ||
| 569 | * Configure MNGO based on options. | ||
| 570 | */ | ||
| 571 | 14 | mngo_info->lb_in_threshold = ((float) spd->options.mngo_lb_in_threshold) / 100.0; | |
| 572 | 14 | mngo_info->lb_out_threshold = ((float) spd->options.mngo_lb_out_threshold) / 100.0; | |
| 573 | |||
| 574 | /** | ||
| 575 | * Create a barrier_t to limit regions | ||
| 576 | */ | ||
| 577 | 14 | mngo_info->dlb_node_barrier = | |
| 578 | 14 | node_barrier_register(spd, "__mngo", DLB_BARRIER_LEWI_ON); | |
| 579 | 14 | shmem_mngo_barrier_wait(); // Ensure that all processes of the node attached | |
| 580 | |||
| 581 | /** | ||
| 582 | * Store the MNGO info to the SubProcessDescriptor. | ||
| 583 | */ | ||
| 584 | 14 | spd->mngo_info = mngo_info; | |
| 585 | |||
| 586 | /** | ||
| 587 | * Initialize the MNGO manager thread for this process. | ||
| 588 | */ | ||
| 589 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
|
14 | if (spd->options.mngo_mode == MNGO_HELPER_THREAD) { |
| 590 | ✗ | pthread_create(&mngo_info->thread, NULL, | |
| 591 | (void *(*)(void *))mngo_helper_thread, (void *)spd); | ||
| 592 | } | ||
| 593 | |||
| 594 | // MNGO initialized successfully | ||
| 595 | 14 | have_mngo = 1; | |
| 596 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 6 times.
|
14 | verbose(VB_MNGO, "MNGO Initialized successfully."); |
| 597 | } | ||
| 598 | |||
| 599 | 14 | void mngo_fini(const subprocess_descriptor_t *spd) { | |
| 600 | |||
| 601 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
|
14 | if (!have_mngo) return; |
| 602 | 14 | have_mngo = false; | |
| 603 | |||
| 604 | 14 | mngo_info_t *mngo_info = spd->mngo_info; | |
| 605 | |||
| 606 | // If MNGO was never initialized just return. | ||
| 607 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
|
14 | if (mngo_info == NULL) { |
| 608 | ✗ | return; | |
| 609 | } | ||
| 610 | |||
| 611 | if (true /* TODO mngo_info->shmem_active */) { | ||
| 612 | |||
| 613 | #ifdef MPI_LIB | ||
| 614 | PMPI_Barrier(MPI_COMM_WORLD); | ||
| 615 | #endif | ||
| 616 | |||
| 617 | /** | ||
| 618 | * Send the stop message throug the shm and signal the manager thread to | ||
| 619 | * wake up. | ||
| 620 | */ | ||
| 621 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 7 times.
|
14 | if (mngo_info->mid == 0) { |
| 622 | 7 | shmem_mngo_manager_wake_finalize(); | |
| 623 | } | ||
| 624 | } | ||
| 625 | |||
| 626 | /** | ||
| 627 | * Join with the manager thread. | ||
| 628 | */ | ||
| 629 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
|
14 | if (spd->options.mngo_mode == MNGO_HELPER_THREAD) { |
| 630 | int *retval; | ||
| 631 | ✗ | pthread_join(mngo_info->thread, (void **)&retval); | |
| 632 | } | ||
| 633 | |||
| 634 | /** | ||
| 635 | * Free the TALP monitoring regions. | ||
| 636 | */ | ||
| 637 | queue_iter_head2tail_t iter = | ||
| 638 | 14 | queue__into_head2tail_iter(mngo_info->region_handler.regions); | |
| 639 | 14 | dlb_mngo_region_t **current = NULL; | |
| 640 |
2/2✓ Branch 1 taken 8 times.
✓ Branch 2 taken 14 times.
|
22 | while ((current = queue_iter__get_nth(&iter, 0)) != NULL) { |
| 641 | 8 | region_stop(spd, | |
| 642 | 8 | (*current)->region_info.talp.monitor); // TALP region stop | |
| 643 | 8 | mngo_metrics__history_fini(&(*current)->region_info.metrics_history); | |
| 644 | } | ||
| 645 | |||
| 646 | 14 | mngo_regions__manager_finalize(&mngo_info->region_handler); | |
| 647 | |||
| 648 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | if (!have_helper_thread) { |
| 649 | // The shared memory is managed by the helper-thread if available | ||
| 650 | |||
| 651 | /** | ||
| 652 | * Wait all the managers from the node to finalize. | ||
| 653 | */ | ||
| 654 | 14 | shmem_mngo_fini_sync(); | |
| 655 | |||
| 656 | /** | ||
| 657 | * Close the shared memory used by the manager thread. | ||
| 658 | */ | ||
| 659 | 14 | shmem_mngo_fini(mngo_info->mid); | |
| 660 | } | ||
| 661 | |||
| 662 | /** | ||
| 663 | * Free the mngo_info_t struct from the sub-process descriptor. | ||
| 664 | */ | ||
| 665 | 14 | free(mngo_info); | |
| 666 | 14 | mngo_info = NULL; | |
| 667 | } | ||
| 668 | |||
| 669 | ✗ | int mngo_manager_wake(const subprocess_descriptor_t *spd) { | |
| 670 | |||
| 671 | ✗ | mngo_info_t *mngo_info = spd->mngo_info; | |
| 672 | |||
| 673 | ✗ | if (mngo_info->mid == 0) { | |
| 674 | ✗ | shmem_mngo_manager_wake(); | |
| 675 | } | ||
| 676 | |||
| 677 | ✗ | return DLB_SUCCESS; | |
| 678 | } | ||
| 679 |