GCC Code Coverage Report


Directory: src/
File: src/talp/talp_openmp.c
Date: 2026-09-15 07:37:49
Exec Total Coverage
Lines: 162 177 91.5%
Functions: 16 16 100.0%
Branches: 53 96 55.2%

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_openmp.h"
21
22 #include "LB_numThreads/omptool.h"
23 #include "LB_comm/shmem_talp.h"
24 #include "LB_core/DLB_kernel.h"
25 #include "LB_core/thread_ctx.h"
26 #include "apis/dlb_talp.h"
27 #include "support/atomic.h"
28 #include "support/debug.h"
29 #include "support/small_array.h"
30 #include "talp/regions.h"
31 #include "talp/sample.h"
32 #include "talp/talp.h"
33 #include "talp/talp_hwc.h"
34 #include "talp/talp_types.h"
35
36 #include <unistd.h>
37
38
39 /* Update all open nested regions (so, excluding the innermost) and add the
40 * time since its start time until the sample last timestamp (which is the time
41 * that has yet not been added to the regions) as omp_outside_parallel_time */
42 1 static void update_outside_parallel_time_in_nested_regions(
43 const subprocess_descriptor_t *spd,
44 const talp_sample_t *sample) {
45
46 1 talp_info_t *talp_info = spd->talp_info;
47
48 /* Update all open nested regions */
49 1 pthread_mutex_lock(&talp_info->regions_mutex);
50 {
51 2 GSList *nested_open_regions = talp_info->open_regions
52 1 ? talp_info->open_regions->next
53
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 : NULL;
54
55 1 for (GSList *node = nested_open_regions;
56
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 node != NULL;
57 1 node = node->next) {
58
59 1 dlb_monitor_t *monitor = node->data;
60 1 monitor->omp_outside_parallel_time +=
61 1 sample->last_updated_ts - monitor->start_time;
62 }
63 }
64 1 pthread_mutex_unlock(&talp_info->regions_mutex);
65 1 }
66
67
68 2 static void compute_parallel_not_useful(
69 talp_sample_t **parallel_samples,
70 unsigned int num_samples,
71 int64_t now,
72 int64_t *not_useful_omp_in_lb,
73 int64_t *not_useful_omp_in_sched) {
74
75 2 int64_t min_not_useful_omp_in = INT64_MAX;
76
77 2 SMALL_ARRAY(int64_t, workers_not_useful_omp_in, num_samples);
78
79 /* Iterate first to compute the minimum not-useful-omp-in among all samples */
80
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 for (unsigned int i = 0; i < num_samples; ++i) {
81 // for each sample, we need to compute its time in not_useful_omp_in
82 // for that we need to add each (now - sample->last_updated_ts)
83 3 const talp_sample_t *worker_sample = parallel_samples[i];
84 3 int64_t worker_not_useful_omp_in = worker_sample->timers.not_useful_omp_in +
85 3 (now - worker_sample->last_updated_ts);
86 3 min_not_useful_omp_in = min_int64(min_not_useful_omp_in, worker_not_useful_omp_in);
87 3 workers_not_useful_omp_in[i] = worker_not_useful_omp_in;
88 }
89
90 2 int64_t sched_timer = min_not_useful_omp_in * num_samples;
91 2 int64_t lb_timer = 0;
92
93 /* Iterate again to accumulate Load Balance */
94
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 for (unsigned int i = 0; i < num_samples; ++i) {
95 3 lb_timer += workers_not_useful_omp_in[i] - min_not_useful_omp_in;
96 }
97
98 2 *not_useful_omp_in_lb = lb_timer;
99 2 *not_useful_omp_in_sched = sched_timer;
100 2 }
101
102
103 /*********************************************************************************/
104 /* TALP OpenMP functions */
105 /*********************************************************************************/
106
107 /* Private TALP parallel data:
108 * For level 1, static storage for the struct, and parallel_samples realloc'ing if needed
109 * For other levels, struct is dynamically allocated */
110 typedef struct talp_parallel_data_t {
111 talp_sample_t** parallel_samples;
112 int64_t previous_not_useful_omp_in;
113 } talp_parallel_data_t;
114
115 static talp_parallel_data_t talp_parallel_data_l1 = {0};
116 static unsigned int parallel_samples_l1_capacity = 0;
117
118
119 1 void talp_openmp_init(pid_t pid, const options_t* options) {
120
121
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 ensure(!thread_is_observer(), "An observer thread cannot call talp_openmp_init");
122
123 1 const subprocess_descriptor_t *spd = thread_spd;
124 1 talp_info_t *talp_info = spd->talp_info;
125
126
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (talp_info) {
127 1 monitor_data_t *monitor_data = talp_info->monitor->_data;
128 1 talp_info->flags.have_openmp = true;
129
130 /* Fix up number of CPUs for the global region */
131 1 float avg_cpus = CPU_COUNT(&spd->process_mask);
132 1 talp_info->monitor->avg_cpus = avg_cpus;
133 1 shmem_talp__set_avg_cpus(monitor_data->node_shared_id, avg_cpus);
134
135 /* Start global region (no-op if already started) */
136 1 region_start(spd, talp_info->monitor);
137
138 /* Set useful state */
139 1 talp_sample_set_state(talp_info, TALP_STATE_USEFUL);
140 }
141 1 }
142
143 1 void talp_openmp_finalize(void) {
144
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (talp_parallel_data_l1.parallel_samples != NULL) {
145 1 free(talp_parallel_data_l1.parallel_samples);
146 1 talp_parallel_data_l1.parallel_samples = NULL;
147 1 parallel_samples_l1_capacity = 0;
148 }
149 1 }
150
151 // native-thread-begin event
152 2 void talp_openmp_thread_begin(ompt_thread_t thread_type) {
153
154 2 const subprocess_descriptor_t *spd = thread_spd;
155 2 talp_info_t *talp_info = spd->talp_info;
156
157
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (talp_info == NULL || !talp_info->flags.have_openmp) return;
158
159 /* Initial thread has already a valid sample, no-op here */
160 2 talp_sample_t *sample = talp_sample_get(talp_info);
161
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (sample != NULL) return;
162
163 /* Worker thread: */
164 1 thread_ctx_set_worker();
165
166 /* Ask again, a new sample will be created now */
167 1 sample = talp_sample_get(talp_info);
168
169
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (talp_info->flags.have_hwc) {
170 talp_hwc_thread_init();
171 }
172
173 1 talp_sample_set_state(talp_info, TALP_STATE_NOT_USEFUL_OMP_OUT);
174
175 /* The initial time of the sample is set to match the start time of
176 * the innermost open region, but other nested open regions need to
177 * be fixed */
178 1 update_outside_parallel_time_in_nested_regions(spd, sample);
179 }
180
181 // native-thread-end event
182 2 void talp_openmp_thread_end(void) {
183
184 2 const subprocess_descriptor_t *spd = thread_spd;
185 2 talp_info_t *talp_info = spd->talp_info;
186
187
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
2 if (talp_info == NULL || !talp_info->flags.have_openmp) return;
188
189 /* Update thread sample */
190 1 talp_sample_update(talp_info);
191
192 /* Update state */
193 1 talp_sample_set_state(talp_info, TALP_STATE_DISABLED);
194
195 /* Finalize PAPI per-thread state */
196
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (talp_info->flags.have_hwc) {
197 talp_hwc_thread_finalize();
198 }
199 }
200
201 // parallel-begin event
202 2 void talp_openmp_parallel_begin(omptool_parallel_data_t *parallel_data) {
203
204
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 fatal_cond(parallel_data->requested_parallelism < 1,
205 "Requested parallel region of invalid size in %s. Please report bug at %s.",
206 __func__, PACKAGE_BUGREPORT);
207
208 2 const subprocess_descriptor_t *spd = thread_spd;
209 2 talp_info_t *talp_info = spd->talp_info;
210
211
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (talp_info == NULL || !talp_info->flags.have_openmp) return;
212
213 2 int parallel_level = parallel_data->level;
214
215
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (parallel_level == 1) {
216 /* Resize samples of parallel 1 if needed */
217 2 unsigned int requested_parallelism = parallel_data->requested_parallelism;
218
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (requested_parallelism > parallel_samples_l1_capacity) {
219 2 void *ptr = realloc(talp_parallel_data_l1.parallel_samples,
220 sizeof(talp_sample_t*)*requested_parallelism);
221
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 fatal_cond(!ptr, "realloc failed in %s", __func__);
222 2 talp_parallel_data_l1.parallel_samples = ptr;
223 2 parallel_samples_l1_capacity = requested_parallelism;
224 }
225
226 /* Assign local data */
227 2 parallel_data->talp_parallel_data = &talp_parallel_data_l1;
228
229 } else if (parallel_level > 1) {
230 /* Allocate parallel samples array */
231 unsigned int requested_parallelism = parallel_data->requested_parallelism;
232 talp_parallel_data_t *talp_parallel_data = malloc(sizeof(talp_parallel_data_t));
233 fatal_cond(!talp_parallel_data, "malloc failed in %s", __func__);
234 *talp_parallel_data = (talp_parallel_data_t) {
235 .parallel_samples = malloc(sizeof(talp_sample_t*)*requested_parallelism),
236 };
237 fatal_cond(!talp_parallel_data->parallel_samples, "malloc failed in %s", __func__);
238
239 /* Assign local data */
240 parallel_data->talp_parallel_data = talp_parallel_data;
241 }
242
243 /* Update stats */
244 2 talp_sample_t *sample = talp_sample_get(talp_info);
245 2 ++sample->stats.num_omp_parallels;
246
247 /* Update main thread sequential mode if this is the outermost parallel region */
248
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (parallel_level == 1) {
249 2 thread_ctx_set_main(THREAD_MAIN_PARALLEL);
250 }
251 }
252
253 // parallel-end event
254 2 void talp_openmp_parallel_end(omptool_parallel_data_t *parallel_data) {
255
256 2 const subprocess_descriptor_t *spd = thread_spd;
257 2 talp_info_t *talp_info = spd->talp_info;
258
259
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (talp_info == NULL || !talp_info->flags.have_openmp) return;
260
261 /* Update thread */
262 2 talp_sample_update(talp_info);
263 2 talp_sample_t *sample = talp_sample_get(talp_info);
264 2 int64_t now = sample->last_updated_ts;
265
266 /* Compute not_useful_omp_in_lb and not_useful_omp_in_sched for this
267 * parallel region. The primary thread reads samples from all participant
268 * threads and writes the computed timings into its sample. */
269 2 talp_parallel_data_t *talp_parallel_data = parallel_data->talp_parallel_data;
270 2 talp_sample_t **parallel_samples = talp_parallel_data->parallel_samples;
271 2 unsigned int num_samples = parallel_data->actual_parallelism;
272 int64_t not_useful_omp_in_lb;
273 int64_t not_useful_omp_in_sched;
274 2 compute_parallel_not_useful(parallel_samples, num_samples, now,
275 &not_useful_omp_in_lb, &not_useful_omp_in_sched);
276
277 2 sample->timers.not_useful_omp_in_lb += not_useful_omp_in_lb;
278 2 sample->timers.not_useful_omp_in_sched += not_useful_omp_in_sched;
279
280 /* Iterate all participants' samples and record the timestamp of the parallel-end event.
281 * Note that this is needed because we don't know if the OpenMP implementation
282 * wakes up worker threads to notify the implicit-task-end event in time. */
283
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 for (unsigned int i = 1; i < num_samples; ++i) {
284 1 talp_sample_t *worker_sample = parallel_samples[i];
285 1 DLB_ATOMIC_ST_RLX(&worker_sample->last_parallel_end_ts, now);
286 }
287
288 /* Update current thread's state */
289 2 talp_sample_set_state(talp_info, TALP_STATE_USEFUL);
290
291
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (parallel_data->level == 1) {
292 /* Update main thread sequential mode if this was the outermost parallel region */
293 2 thread_ctx_set_main(THREAD_MAIN_SEQUENTIAL);
294 } else {
295 /* Restore previously pushed not-useful-omp-in */
296 sample->timers.not_useful_omp_in = talp_parallel_data->previous_not_useful_omp_in;
297
298 /* free local data */
299 free(talp_parallel_data);
300 parallel_data->talp_parallel_data = NULL;
301 }
302 }
303
304 // implicit-task-begin event
305 3 void talp_openmp_into_parallel_function(
306 omptool_parallel_data_t *parallel_data, unsigned int index) {
307
308 3 const subprocess_descriptor_t *spd = thread_spd;
309 3 talp_info_t *talp_info = spd->talp_info;
310
311
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 if (talp_info == NULL || !talp_info->flags.have_openmp) return;
312
313 /* Assign thread sample as team-worker of this parallel */
314 3 talp_parallel_data_t *talp_parallel_data = parallel_data->talp_parallel_data;
315 3 talp_sample_t *sample = talp_sample_get(talp_info);
316 3 talp_sample_t **parallel_samples = talp_parallel_data->parallel_samples;
317 /* Probably optimized, but try to avoid invalidating
318 * the cache line on reused parallel data */
319
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if (parallel_samples[index] != sample) {
320 2 parallel_samples[index] = sample;
321 }
322
323
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (index > 0) {
324 /* For non-primary threads, the next sample update will add time to
325 * not-useful-omp-out, but we need to fix the initial timestamp to not
326 * count the not-useful-omp-in already aggregated by the initial thread. */
327 1 int64_t last_parallel_end_ts = DLB_ATOMIC_LD_RLX(&sample->last_parallel_end_ts);
328
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (sample->last_updated_ts < last_parallel_end_ts) {
329 sample->last_updated_ts = last_parallel_end_ts;
330 }
331
332
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 } else if (parallel_data->level > 1) {
333 /* For primary threads on nested regions, we need to save the accumulated time
334 * in not_useful_omp_in that belongs to the previous parallel region. */
335 talp_parallel_data->previous_not_useful_omp_in = sample->timers.not_useful_omp_in;
336 }
337
338 /* We always start parallel regions with this timer reset to 0. */
339 3 sample->timers.not_useful_omp_in = 0;
340
341 /* Update thread sample */
342 3 talp_sample_update(talp_info);
343
344 /* Each thread records its CPU when beginning the parallel region */
345 3 talp_sample_record_cpuid(talp_info);
346
347 /* Update state */
348 3 talp_sample_set_state(talp_info, TALP_STATE_USEFUL);
349 }
350
351 // implicit-task-end event (thread 0 skips this function)
352 1 void talp_openmp_outof_parallel_function(void) {
353
354 1 const subprocess_descriptor_t *spd = thread_spd;
355 1 talp_info_t *talp_info = spd->talp_info;
356
357
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (talp_info == NULL || !talp_info->flags.have_openmp) return;
358
359 /* Ideally, we should be updating the microsample at this point:
360 * not_useful_omp_in += now - last_updated_ts
361 *
362 * But, we don't know if the OpemMP implementation is calling this function
363 * on time, so the primary thread will be the responsible to reconstruct
364 * the missing not_useful_omp_in from us in the parallel-end event.
365 *
366 * Since measuring from this function is not consistent, we'll just change
367 * the state to correctly compute not-useful-omp-out later, and to add an
368 * instrumentation event. HWC readings should also be skipped since we are
369 * changing not_useful -> not_useful anyway. */
370
371 /* Update state */
372 1 talp_sample_set_state(talp_info, TALP_STATE_NOT_USEFUL_OMP_OUT);
373 }
374
375 // implicit-barrier-begin event (sync_region_barrier_implicit_parallel)
376 3 void talp_openmp_into_parallel_implicit_barrier(omptool_parallel_data_t *parallel_data) {
377
378 3 const subprocess_descriptor_t *spd = thread_spd;
379 3 talp_info_t *talp_info = spd->talp_info;
380
381
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 if (talp_info == NULL || !talp_info->flags.have_openmp) return;
382
383 /* Update thread sample */
384 3 talp_sample_update(talp_info);
385
386 /* Update state */
387 3 talp_sample_set_state(talp_info, TALP_STATE_NOT_USEFUL_OMP_IN);
388 }
389
390 // {*-barrier,taskwait,taskgroup}-begin event
391 3 void talp_openmp_into_parallel_sync(omptool_parallel_data_t *parallel_data) {
392
393 3 const subprocess_descriptor_t *spd = thread_spd;
394 3 talp_info_t *talp_info = spd->talp_info;
395
396
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 if (talp_info == NULL || !talp_info->flags.have_openmp) return;
397
398 /* Update thread sample */
399 3 talp_sample_update(talp_info);
400
401 /* Update state */
402 3 talp_sample_set_state(talp_info, TALP_STATE_NOT_USEFUL_OMP_IN);
403 }
404
405 // {*-barrier,taskwait,taskgroup}-end event
406 3 void talp_openmp_outof_parallel_sync(omptool_parallel_data_t *parallel_data) {
407
408 3 const subprocess_descriptor_t *spd = thread_spd;
409 3 talp_info_t *talp_info = spd->talp_info;
410
411
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 if (talp_info == NULL || !talp_info->flags.have_openmp) return;
412
413 /* Update thread sample */
414 3 talp_sample_update(talp_info);
415
416 /* Update state */
417 3 talp_sample_set_state(talp_info, TALP_STATE_USEFUL);
418 }
419
420 // task-create event
421 3 void talp_openmp_task_create(void) {
422
423 3 const subprocess_descriptor_t *spd = thread_spd;
424 3 talp_info_t *talp_info = spd->talp_info;
425
426
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 if (talp_info == NULL || !talp_info->flags.have_openmp) return;
427
428 /* Just update stats */
429 3 talp_sample_t *sample = talp_sample_get(talp_info);
430 3 ++sample->stats.num_omp_tasks;
431 }
432
433 // task-schedule event: task complete
434 3 void talp_openmp_task_complete(void) {
435
436 3 const subprocess_descriptor_t *spd = thread_spd;
437 3 talp_info_t *talp_info = spd->talp_info;
438
439
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 if (talp_info == NULL || !talp_info->flags.have_openmp) return;
440
441 /* Update thread sample */
442 3 talp_sample_update(talp_info);
443
444 /* Update state (FIXME: tasks outside of parallels?) */
445 3 talp_sample_set_state(talp_info, TALP_STATE_NOT_USEFUL_OMP_IN);
446 }
447
448 // task-schedule event: task switch
449 6 void talp_openmp_task_switch(void) {
450
451 6 const subprocess_descriptor_t *spd = thread_spd;
452 6 talp_info_t *talp_info = spd->talp_info;
453
454
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
6 if (talp_info == NULL || !talp_info->flags.have_openmp) return;
455
456 /* Update thread sample */
457 6 talp_sample_update(talp_info);
458
459 /* Update state */
460 6 talp_sample_set_state(talp_info, TALP_STATE_USEFUL);
461 }
462
463
464 /*********************************************************************************/
465 /* Vtable for handling omptool events */
466 /*********************************************************************************/
467
468 const omptool_event_funcs_t talp_events_vtable = (const omptool_event_funcs_t) {
469 .init = talp_openmp_init,
470 .finalize = talp_openmp_finalize,
471 .into_mpi = NULL,
472 .outof_mpi = NULL,
473 .lend_from_api = NULL,
474 .thread_begin = talp_openmp_thread_begin,
475 .thread_end = talp_openmp_thread_end,
476 .thread_role_shift = NULL,
477 .parallel_begin = talp_openmp_parallel_begin,
478 .parallel_end = talp_openmp_parallel_end,
479 .into_parallel_function = talp_openmp_into_parallel_function,
480 .outof_parallel_function = talp_openmp_outof_parallel_function,
481 .into_parallel_implicit_barrier = talp_openmp_into_parallel_implicit_barrier,
482 .into_parallel_sync = talp_openmp_into_parallel_sync,
483 .outof_parallel_sync = talp_openmp_outof_parallel_sync,
484 .task_create = talp_openmp_task_create,
485 .task_complete = talp_openmp_task_complete,
486 .task_switch = talp_openmp_task_switch,
487 };
488