GCC Code Coverage Report


Directory: src/
File: src/talp/sample.c
Date: 2026-09-15 07:37:49
Exec Total Coverage
Lines: 157 175 89.7%
Functions: 13 13 100.0%
Branches: 36 55 65.5%

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/sample.h"
21
22 #include "LB_core/thread_ctx.h"
23 #include "support/debug.h"
24 #include "support/dlb_common.h"
25 #include "support/mytime.h"
26 #include "support/tracing.h"
27 #include "talp/backend.h"
28 #include "talp/talp_hwc.h"
29
30 #include <pthread.h>
31 #include <stdlib.h>
32 #include <string.h>
33
34
35 static __thread talp_sample_t* _tls_sample = NULL;
36
37 static inline void record_cpuid(talp_sample_t *sample);
38
39 static inline void set_state(const talp_info_t *talp_info,
40 talp_sample_t *sample, talp_sample_state_t new_state);
41
42
43 /*********************************************************************************/
44 /* Init / Finalize */
45 /*********************************************************************************/
46
47 38 void talp_sample_init(talp_info_t *talp_info) {
48
49 38 talp_info->sample_registry = (sample_registry_t){
50 .mutex = PTHREAD_MUTEX_INITIALIZER,
51 };
52 38 }
53
54 38 void talp_sample_finalize(talp_info_t *talp_info) {
55
56 /* Warning about _tls_sample in worker threads:
57 * worker threads do not call this function, so currently they are
58 * not deallocating their sample.
59 * In some cases, it might happen that a worker thread exits without
60 * the main thread reducing its sample, so in these cases the sample
61 * needs to outlive the thread.
62 * The main thread could deallocate it at this point, but then the
63 * TLS variable would be broken if TALP is reinitialized again.
64 * For now we will keep it like this and will revisit if needed. */
65
66 /* Deallocate main thread sample */
67 38 free(_tls_sample);
68 38 _tls_sample = NULL;
69
70 /* Deallocate samples list */
71 38 sample_registry_t *registry = &talp_info->sample_registry;
72 38 pthread_mutex_lock(&registry->mutex);
73 {
74 38 free(registry->samples);
75 38 registry->samples = NULL;
76 38 registry->num_samples = 0;
77 }
78 38 pthread_mutex_unlock(&registry->mutex);
79 38 }
80
81
82 /*********************************************************************************/
83 /* Sample getters & setters */
84 /*********************************************************************************/
85
86 /* Get the TLS associated sample */
87 16513 talp_sample_t* talp_sample_get(talp_info_t *talp_info) {
88
89 /* Thread already has an allocated sample, return it */
90
2/2
✓ Branch 0 taken 16472 times.
✓ Branch 1 taken 41 times.
16513 if (likely(_tls_sample != NULL)) return _tls_sample;
91
92 /* Observer and unknown threads don't have a valid sample */
93
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 40 times.
41 if (unlikely(!thread_is_profiled())) return NULL;
94
95 /* Otherwise, allocate */
96 40 sample_registry_t *registry = &talp_info->sample_registry;
97 40 pthread_mutex_lock(&registry->mutex);
98 {
99 40 int num_samples = ++registry->num_samples;
100 40 void *samples = realloc(registry->samples, sizeof(talp_sample_t*)*num_samples);
101
1/2
✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
40 if (samples) {
102 40 void *new_sample = NULL;
103
1/2
✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
40 if (posix_memalign(&new_sample, DLB_CACHE_LINE, sizeof(talp_sample_t)) == 0) {
104 40 _tls_sample = new_sample;
105 40 *_tls_sample = (talp_sample_t){0};
106 40 registry->samples = samples;
107 40 registry->samples[num_samples-1] = new_sample;
108 } else {
109 // error
110 free(new_sample);
111 _tls_sample = NULL;
112 }
113 }
114 }
115 40 pthread_mutex_unlock(&registry->mutex);
116
117
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 40 times.
40 fatal_cond(_tls_sample == NULL, "TALP: could not allocate thread sample");
118
119 /* If a thread is created mid-region, its initial time is that of the
120 * innermost open region, otherwise it is the current time */
121 int64_t last_updated_ts;
122
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 38 times.
40 if (talp_info->open_regions) {
123 2 const dlb_monitor_t *innermost_monitor = talp_info->open_regions->data;
124 2 last_updated_ts = innermost_monitor->start_time;
125 } else {
126 38 last_updated_ts = get_time_in_ns();
127 }
128
129 40 _tls_sample->last_updated_ts = last_updated_ts;
130
131 40 set_state(talp_info, _tls_sample, TALP_STATE_DISABLED);
132
133 #ifdef INSTRUMENTATION_VERSION
134 unsigned events[] = {MONITOR_CYCLES, MONITOR_INSTR};
135 long long hwc_values[] = {0, 0};
136 instrument_nevent(2, events, hwc_values);
137 #endif
138
139 40 return _tls_sample;
140 }
141
142 /* Reset sample metrics */
143 5413 static inline void reset_sample(talp_sample_t *sample) {
144 5413 memset(&sample->timers, 0, sizeof(sample->timers));
145 5413 memset(&sample->counters, 0, sizeof(sample->counters));
146 5413 memset(&sample->stats, 0, sizeof(sample->stats));
147 5413 CPU_ZERO(&sample->cpu_mask);
148 5413 record_cpuid(sample);
149 5413 }
150
151
152 /*********************************************************************************/
153 /* Sample update */
154 /*********************************************************************************/
155
156 5468 static inline void ensure_generation(talp_sample_t *sample, int64_t current_generation_ts) {
157
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5466 times.
5468 if (unlikely(sample->generation_ts < current_generation_ts)) {
158 2 reset_sample(sample);
159 2 sample->generation_ts = current_generation_ts;
160 2 sample->last_updated_ts = current_generation_ts;
161 }
162 5468 }
163
164 /* Compute new microsample (time since last update) and update sample values */
165 5468 void talp_sample_update(talp_info_t *talp_info) {
166
167 5468 talp_sample_t *sample = talp_sample_get(talp_info);
168 5468 sample_registry_t *registry = &talp_info->sample_registry;
169
170 /* Observer and unknown threads ignore this function */
171
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5468 times.
5468 if (unlikely(sample == NULL)) return;
172
173 5468 int64_t current_generation = DLB_ATOMIC_LD_RLX(&registry->current_generation_ts);
174
175 /* Generation helps us to identify whether this sample belongs to the
176 * current generation (i.e., region has yet to be updated) or if we need to
177 * reset it */
178 5468 ensure_generation(sample, current_generation);
179
180 /* Compute duration and set new last_updated_ts */
181 5468 int64_t now = get_time_in_ns();
182 5468 int64_t microsample_duration = now - sample->last_updated_ts;
183 5468 sample->last_updated_ts = now;
184
185 /* Update the appropriate sample timer */
186
4/7
✗ Branch 0 not taken.
✓ Branch 1 taken 5435 times.
✓ Branch 2 taken 23 times.
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
5468 switch(sample->state) {
187 case TALP_STATE_DISABLED:
188 break;
189 5435 case TALP_STATE_USEFUL:
190 5435 sample->timers.useful += microsample_duration;
191 5435 break;
192 23 case TALP_STATE_NOT_USEFUL_MPI:
193 23 sample->timers.not_useful_mpi += microsample_duration;
194
1/2
✓ Branch 1 taken 23 times.
✗ Branch 2 not taken.
23 if (thread_is_main_sequential()
195
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
23 && talp_info->flags.have_openmp) {
196 // Add unused CPUs' time to special timer
197 int num_unused_cpus = talp_info->num_cpus - 1;
198 sample->timers.not_useful_omp_during_mpi
199 += microsample_duration * num_unused_cpus;
200 }
201 23 break;
202 8 case TALP_STATE_NOT_USEFUL_OMP_IN:
203 8 sample->timers.not_useful_omp_in += microsample_duration;
204 8 break;
205 2 case TALP_STATE_NOT_USEFUL_OMP_OUT:
206 2 sample->timers.not_useful_omp_out += microsample_duration;
207 2 break;
208 case TALP_STATE_NOT_USEFUL_GPU:
209 sample->timers.not_useful_gpu += microsample_duration;
210 break;
211 }
212
213
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5468 times.
5468 if (talp_info->flags.have_hwc) {
214 hw_counters_t measurements = {0};
215 if (talp_hwc_collect(&measurements)) {
216 sample->counters.cycles += measurements.cycles;
217 sample->counters.instructions += measurements.instructions;
218 }
219
220 #ifdef INSTRUMENTATION_VERSION
221 // We want to emit even if talp_hwc_collect returned false,
222 // that's why measurements is init'd to 0 above.
223 unsigned events[] = {MONITOR_CYCLES, MONITOR_INSTR};
224 long long hwc_values[] = {measurements.cycles, measurements.instructions};
225 instrument_nevent(2, events, hwc_values);
226 #endif
227 }
228 }
229
230 5455 static inline void record_cpuid(talp_sample_t *sample) {
231 5455 int cpuid = sched_getcpu();
232
1/2
✓ Branch 0 taken 5455 times.
✗ Branch 1 not taken.
5455 CPU_SET(cpuid, &sample->cpu_mask);
233 5455 }
234
235 42 void talp_sample_record_cpuid(talp_info_t *talp_info) {
236 42 talp_sample_t *sample = talp_sample_get(talp_info);
237 42 record_cpuid(sample);
238 42 }
239
240 165 static inline void set_state(const talp_info_t *restrict talp_info,
241 talp_sample_t *restrict sample, talp_sample_state_t new_state) {
242
243
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 165 times.
165 if (talp_info->flags.have_hwc) {
244 talp_sample_state_t old = sample->state;
245 talp_hwc_on_state_change(old, new_state);
246 }
247
248 165 sample->state = new_state;
249
250 instrument_event(MONITOR_STATE,
251 new_state == TALP_STATE_DISABLED ? MONITOR_STATE_DISABLED
252 : new_state == TALP_STATE_USEFUL ? MONITOR_STATE_USEFUL
253 : new_state == TALP_STATE_NOT_USEFUL_MPI ? MONITOR_STATE_NOT_USEFUL_MPI
254 : new_state == TALP_STATE_NOT_USEFUL_OMP_IN ? MONITOR_STATE_NOT_USEFUL_OMP_IN
255 : new_state == TALP_STATE_NOT_USEFUL_OMP_OUT ? MONITOR_STATE_NOT_USEFUL_OMP_OUT
256 : new_state == TALP_STATE_NOT_USEFUL_GPU ? MONITOR_STATE_NOT_USEFUL_GPU
257 : 0,
258 EVENT_BEGIN);
259 165 }
260
261 125 void talp_sample_set_state(talp_info_t *talp_info, talp_sample_state_t new_state) {
262
263 125 talp_sample_t *sample = talp_sample_get(talp_info);
264 125 set_state(talp_info, sample, new_state);
265 125 }
266
267
268 /*********************************************************************************/
269 /* Sample aggregation */
270 /*********************************************************************************/
271
272 1 talp_sample_t talp_sample_delta(const talp_sample_t *end, const talp_sample_t *start) {
273 2 return (talp_sample_t) {
274 .timers = {
275 1 .useful = end->timers.useful
276 1 - start->timers.useful,
277 1 .not_useful_mpi = end->timers.not_useful_mpi
278 1 - start->timers.not_useful_mpi,
279 1 .not_useful_omp_during_mpi = end->timers.not_useful_omp_during_mpi
280 1 - start->timers.not_useful_omp_during_mpi,
281 1 .not_useful_omp_in = end->timers.not_useful_omp_in
282 1 - start->timers.not_useful_omp_in,
283 1 .not_useful_omp_in_lb = end->timers.not_useful_omp_in_lb
284 1 - start->timers.not_useful_omp_in_lb,
285 1 .not_useful_omp_in_sched = end->timers.not_useful_omp_in_sched
286 1 - start->timers.not_useful_omp_in_sched,
287 1 .not_useful_omp_out = end->timers.not_useful_omp_out
288 1 - start->timers.not_useful_omp_out,
289 1 .not_useful_gpu = end->timers.not_useful_gpu
290 1 - start->timers.not_useful_gpu,
291 },
292 .counters = {
293 1 .cycles = end->counters.cycles - start->counters.cycles,
294 1 .instructions = end->counters.instructions - start->counters.instructions,
295 },
296 .stats = {
297 1 .num_mpi_calls = end->stats.num_mpi_calls - start->stats.num_mpi_calls,
298 1 .num_omp_parallels = end->stats.num_omp_parallels - start->stats.num_omp_parallels,
299 1 .num_omp_tasks = end->stats.num_omp_tasks - start->stats.num_omp_tasks,
300 1 .num_gpu_runtime_calls = end->stats.num_gpu_runtime_calls - start->stats.num_gpu_runtime_calls,
301 },
302 };
303 }
304
305
306 /* Aggregate a single sample into a macrosample */
307 5413 static inline void aggregate_sample_to_macrosample(const talp_sample_t *restrict sample,
308 talp_macrosample_t *restrict macrosample) {
309
310
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 5413 times.
5413 ensure(CPU_COUNT(&sample->cpu_mask)>0, "Updating macrosample with 0 CPUs. Please report.");
311
312 /* Timers */
313 5413 macrosample->timers.useful += sample->timers.useful;
314 5413 macrosample->timers.not_useful_mpi += sample->timers.not_useful_mpi;
315 5413 macrosample->timers.not_useful_omp_during_mpi += sample->timers.not_useful_omp_during_mpi;
316 5413 macrosample->timers.not_useful_omp_in_lb += sample->timers.not_useful_omp_in_lb;
317 5413 macrosample->timers.not_useful_omp_in_sched += sample->timers.not_useful_omp_in_sched;
318 5413 macrosample->timers.not_useful_omp_out += sample->timers.not_useful_omp_out;
319 5413 macrosample->timers.not_useful_gpu += sample->timers.not_useful_gpu;
320
321 /* Counters */
322 5413 macrosample->counters.cycles += sample->counters.cycles;
323 5413 macrosample->counters.instructions += sample->counters.instructions;
324
325 /* Stats */
326 5413 macrosample->stats.num_mpi_calls += sample->stats.num_mpi_calls;
327 5413 macrosample->stats.num_omp_parallels += sample->stats.num_omp_parallels;
328 5413 macrosample->stats.num_omp_tasks += sample->stats.num_omp_tasks;
329 5413 macrosample->stats.num_gpu_runtime_calls += sample->stats.num_gpu_runtime_calls;
330
331 /* CPU mask */
332
2/2
✓ Branch 0 taken 86608 times.
✓ Branch 1 taken 5413 times.
92021 CPU_OR(&macrosample->cpu_mask, &macrosample->cpu_mask, &sample->cpu_mask);
333 5413 }
334
335
336 /* Aggregate all samples. Don't update any. */
337 5411 void talp_sample_aggregate_all_to_macrosample(
338 talp_info_t *restrict talp_info, talp_macrosample_t *restrict macrosample) {
339
340 /* Warning: observer threads can call this function although is prone to
341 * produce some race conditions while reading samples. Still, we cannot
342 * assume samples[0] is our sample */
343 5411 talp_sample_t *my_sample = talp_sample_get(talp_info);
344
345 /* If this function is called by the main thread (expected),
346 * re-use the timestamp that was just set updating the sample. */
347 5411 int64_t now = my_sample != NULL
348 ? my_sample->last_updated_ts
349
1/2
✓ Branch 0 taken 5411 times.
✗ Branch 1 not taken.
5411 : get_time_in_ns();
350
351 5411 sample_registry_t *registry = &talp_info->sample_registry;
352 5411 int64_t current_generation_ts = DLB_ATOMIC_LD_RLX(&registry->current_generation_ts);
353 5411 int64_t generation_duration = now - current_generation_ts;
354
355 /* Accumulate samples from all threads */
356 5411 pthread_mutex_lock(&registry->mutex);
357 {
358 5411 int num_samples = registry->num_samples;
359 5411 macrosample->num_samples = num_samples;
360
361 /* Aggregate samples */
362
2/2
✓ Branch 0 taken 5415 times.
✓ Branch 1 taken 5411 times.
10826 for (int i = 0; i < num_samples; ++i) {
363 5415 const talp_sample_t *sample = registry->samples[i];
364
365
2/2
✓ Branch 0 taken 5411 times.
✓ Branch 1 taken 4 times.
5415 if (sample == my_sample) {
366 /* Our sample is just aggregated because we know it's updated */
367 5411 aggregate_sample_to_macrosample(sample, macrosample);
368 5411 continue;
369 }
370
371 /* Note: By contract, we only aggregate samples in sequential code.
372 * So at this point, we only look for threads that are probably
373 * stopped after a parallel region.
374 * Since implicit-task-end event is not reliable, we use the
375 * last_parallel_end_ts set by the primary to compute the missing
376 * not-useful-omp-out here */
377
378
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if (likely(sample->state == TALP_STATE_NOT_USEFUL_OMP_IN
379 || sample->state == TALP_STATE_NOT_USEFUL_OMP_OUT)) {
380
381 int64_t last_parallel_end_ts = DLB_ATOMIC_LD_RLX(&sample->last_parallel_end_ts);
382 macrosample->timers.not_useful_omp_out += min_int64(
383 generation_duration,
384 now - last_parallel_end_ts);
385 }
386
387
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (sample->generation_ts < current_generation_ts) {
388 /* Sample not updated in the current generation.
389 * Skip aggregation of any other timer, but still account for the CPU.*/
390
391
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 2 times.
34 CPU_OR(&macrosample->cpu_mask, &macrosample->cpu_mask, &sample->cpu_mask);
392 2 continue;
393 }
394
395 /* Aggregate the values computed by the thread */
396 2 aggregate_sample_to_macrosample(sample, macrosample);
397 }
398 }
399 5411 pthread_mutex_unlock(&registry->mutex);
400
401 /* Start generation for the next sample aggregation */
402 5411 DLB_ATOMIC_ST_RLX(&registry->current_generation_ts, now);
403
404 /* If this function is called by the main thread (expected),
405 * reset sample and set the new generation time-stamp. */
406
1/2
✓ Branch 0 taken 5411 times.
✗ Branch 1 not taken.
5411 if (my_sample != NULL) {
407 5411 reset_sample(my_sample);
408 5411 my_sample->generation_ts = now;
409 }
410 5411 }
411