Coverage Report

Created: 2026-08-13 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/haproxy/src/activity.c
Line
Count
Source
1
/*
2
 * activity measurement functions.
3
 *
4
 * Copyright 2000-2018 Willy Tarreau <w@1wt.eu>
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version
9
 * 2 of the License, or (at your option) any later version.
10
 *
11
 */
12
13
#include <errno.h>
14
#include <haproxy/activity-t.h>
15
#include <haproxy/api.h>
16
#include <haproxy/applet.h>
17
#include <haproxy/cfgparse.h>
18
#include <haproxy/clock.h>
19
#include <haproxy/channel.h>
20
#include <haproxy/cli.h>
21
#include <haproxy/freq_ctr.h>
22
#include <haproxy/listener.h>
23
#include <haproxy/sc_strm.h>
24
#include <haproxy/stconn.h>
25
#include <haproxy/tools.h>
26
27
/* CLI context for the "show profiling" command */
28
struct show_prof_ctx {
29
  int dump_step;  /* 0,1,2,4,5,6; see cli_iohandler_show_profiling() */
30
  int linenum;    /* next line to be dumped (starts at 0) */
31
  int maxcnt;     /* max line count per step (0=not set)  */
32
  int by_what;    /* 0=sort by usage, 1=sort by address, 2=sort by time, 3=sort by ctx */
33
  int aggr;       /* 0=dump raw, 1=aggregate on callee    */
34
  /* 4-byte hole here */
35
  struct sched_activity *tmp_activity; /* dynamically allocated during dumps */
36
  struct memprof_stats *tmp_memstats; /* dynamically allocated during dumps */
37
};
38
39
/* CLI context for the "show activity" command */
40
struct show_activity_ctx {
41
  int thr;         /* thread ID to show or -1 for all */
42
  int line;        /* line number being dumped */
43
  int col;         /* columnline being dumped, 0 to nbt+1 */
44
};
45
46
#if defined(DEBUG_MEM_STATS)
47
/* these ones are macros in bug.h when DEBUG_MEM_STATS is set, and will
48
 * prevent the new ones from being redefined.
49
 */
50
#undef calloc
51
#undef malloc
52
#undef realloc
53
#undef strdup
54
#endif
55
56
/* bit field of profiling options. Beware, may be modified at runtime! */
57
unsigned int profiling __read_mostly = HA_PROF_TASKS_AOFF;
58
59
/* start/stop dates of profiling */
60
uint64_t prof_task_start_ns = 0;
61
uint64_t prof_task_stop_ns = 0;
62
uint64_t prof_mem_start_ns = 0;
63
uint64_t prof_mem_stop_ns = 0;
64
65
/* One struct per thread containing all collected measurements */
66
struct activity activity[MAX_THREADS] = { };
67
68
/* One struct per function pointer hash entry (SCHED_ACT_HASH_BUCKETS values, 0=collision) */
69
struct sched_activity sched_activity[SCHED_ACT_HASH_BUCKETS] = { };
70
71
72
#ifdef USE_MEMORY_PROFILING
73
74
static const char *const memprof_methods[MEMPROF_METH_METHODS] = {
75
  "unknown", "malloc", "calloc", "realloc", "strdup", "free", "p_alloc", "p_free",
76
  "strndup", "valloc", "aligned_valloc", "posix_memalign", "memalign", "pvalloc",
77
};
78
79
/* last one is for hash collisions ("others") and has no caller address */
80
struct memprof_stats memprof_stats[MEMPROF_HASH_BUCKETS + 1] = { };
81
82
/* used to detect recursive calls */
83
#define MEMPROF_IN_INIT (1U << 0)
84
#define MEMPROF_IN_HANDLER (1U << 1)
85
86
static THREAD_LOCAL uint in_memprof = 0;  // arithmetic OR of MEMPROF_IN_*
87
88
/* These ones are used by glibc and will be called early. They are in charge of
89
 * initializing the handlers with the original functions.
90
 */
91
static void *memprof_malloc_initial_handler(size_t size);
92
static void *memprof_calloc_initial_handler(size_t nmemb, size_t size);
93
static void *memprof_realloc_initial_handler(void *ptr, size_t size);
94
static char *memprof_strdup_initial_handler(const char *s);
95
static void  memprof_free_initial_handler(void *ptr);
96
97
/* these ones are optional but may be used by some dependencies */
98
static char *memprof_strndup_initial_handler(const char *s, size_t n);
99
static void *memprof_valloc_initial_handler(size_t sz);
100
static void *memprof_pvalloc_initial_handler(size_t sz);
101
static void *memprof_memalign_initial_handler(size_t al, size_t sz);
102
static void *memprof_aligned_alloc_initial_handler(size_t al, size_t sz);
103
static int   memprof_posix_memalign_initial_handler(void **ptr, size_t al, size_t sz);
104
105
/* Fallback handlers for the main alloc/free functions. They are preset to
106
 * the initializer in order to save a test in the functions's critical path.
107
 */
108
static void *(*memprof_malloc_handler)(size_t size)               = memprof_malloc_initial_handler;
109
static void *(*memprof_calloc_handler)(size_t nmemb, size_t size) = memprof_calloc_initial_handler;
110
static void *(*memprof_realloc_handler)(void *ptr, size_t size)   = memprof_realloc_initial_handler;
111
static char *(*memprof_strdup_handler)(const char *s)             = memprof_strdup_initial_handler;
112
static void  (*memprof_free_handler)(void *ptr)                   = memprof_free_initial_handler;
113
114
/* these ones are optional but may be used by some dependencies */
115
static char *(*memprof_strndup_handler)(const char *s, size_t n)                 = memprof_strndup_initial_handler;
116
static void *(*memprof_valloc_handler)(size_t sz)                                = memprof_valloc_initial_handler;
117
static void *(*memprof_pvalloc_handler)(size_t sz)                               = memprof_pvalloc_initial_handler;
118
static void *(*memprof_memalign_handler)(size_t al, size_t sz)                   = memprof_memalign_initial_handler;
119
static void *(*memprof_aligned_alloc_handler)(size_t al, size_t sz)              = memprof_aligned_alloc_initial_handler;
120
static int   (*memprof_posix_memalign_handler)(void **ptr, size_t al, size_t sz) = memprof_posix_memalign_initial_handler;
121
122
/* Used to force to die if it's not possible to retrieve the allocation
123
 * functions. We cannot even use stdio in this case.
124
 */
125
static __attribute__((noreturn)) void memprof_die(const char *msg)
126
{
127
  DISGUISE(write(2, msg, strlen(msg)));
128
  exit(1);
129
}
130
131
/* Resolve original allocation functions and initialize all handlers.
132
 * This must be called very early at boot, before the very first malloc()
133
 * call, and is not thread-safe! It's not even possible to use stdio there.
134
 * Worse, we have to account for the risk of reentrance from dlsym() when
135
 * it tries to prepare its error messages. Here its ahndled by in_memprof
136
 * that makes allocators return NULL. dlsym() handles it gracefully. An
137
 * alternate approach consists in calling aligned_alloc() from these places
138
 * but that would mean not being able to intercept it later if considered
139
 * useful to do so.
140
 */
141
static void memprof_init()
142
{
143
  in_memprof |= MEMPROF_IN_INIT;
144
  memprof_malloc_handler  = get_sym_next_addr("malloc");
145
  if (!memprof_malloc_handler)
146
    memprof_die("FATAL: malloc() function not found.\n");
147
148
  memprof_calloc_handler  = get_sym_next_addr("calloc");
149
  if (!memprof_calloc_handler)
150
    memprof_die("FATAL: calloc() function not found.\n");
151
152
  memprof_realloc_handler = get_sym_next_addr("realloc");
153
  if (!memprof_realloc_handler)
154
    memprof_die("FATAL: realloc() function not found.\n");
155
156
  memprof_strdup_handler  = get_sym_next_addr("strdup");
157
  if (!memprof_strdup_handler)
158
    memprof_die("FATAL: strdup() function not found.\n");
159
160
  memprof_free_handler    = get_sym_next_addr("free");
161
  if (!memprof_free_handler)
162
    memprof_die("FATAL: free() function not found.\n");
163
164
  /* these ones are not always implemented, rarely used and may not exist
165
   * so we don't fail on them.
166
   */
167
  memprof_strndup_handler        = get_sym_next_addr("strndup");
168
  memprof_valloc_handler         = get_sym_next_addr("valloc");
169
  memprof_pvalloc_handler        = get_sym_next_addr("pvalloc");
170
  memprof_memalign_handler       = get_sym_next_addr("memalign");
171
  memprof_aligned_alloc_handler  = get_sym_next_addr("aligned_alloc");
172
  memprof_posix_memalign_handler = get_sym_next_addr("posix_memalign");
173
174
  in_memprof &= ~MEMPROF_IN_INIT;
175
}
176
177
/* the initial handlers will initialize all regular handlers and will call the
178
 * one they correspond to. A single one of these functions will typically be
179
 * called, though it's unknown which one (as any might be called before main).
180
 */
181
static void *memprof_malloc_initial_handler(size_t size)
182
{
183
  if (in_memprof & MEMPROF_IN_INIT) {
184
    /* it's likely that dlsym() needs malloc(), let's fail */
185
    return NULL;
186
  }
187
188
  memprof_init();
189
  return memprof_malloc_handler(size);
190
}
191
192
static void *memprof_calloc_initial_handler(size_t nmemb, size_t size)
193
{
194
  if (in_memprof & MEMPROF_IN_INIT) {
195
    /* it's likely that dlsym() needs calloc(), let's fail */
196
    return NULL;
197
  }
198
  memprof_init();
199
  return memprof_calloc_handler(nmemb, size);
200
}
201
202
static void *memprof_realloc_initial_handler(void *ptr, size_t size)
203
{
204
  if (in_memprof & MEMPROF_IN_INIT) {
205
    /* it's likely that dlsym() needs realloc(), let's fail */
206
    return NULL;
207
  }
208
209
  memprof_init();
210
  return memprof_realloc_handler(ptr, size);
211
}
212
213
static char *memprof_strdup_initial_handler(const char *s)
214
{
215
  if (in_memprof & MEMPROF_IN_INIT) {
216
    /* probably that dlsym() needs strdup(), let's fail */
217
    return NULL;
218
  }
219
220
  memprof_init();
221
  return memprof_strdup_handler(s);
222
}
223
224
static void  memprof_free_initial_handler(void *ptr)
225
{
226
  memprof_init();
227
  memprof_free_handler(ptr);
228
}
229
230
/* optional handlers */
231
232
static char *memprof_strndup_initial_handler(const char *s, size_t n)
233
{
234
  if (in_memprof & MEMPROF_IN_INIT) {
235
    /* probably that dlsym() needs strndup(), let's fail */
236
    return NULL;
237
  }
238
239
  memprof_init();
240
  return memprof_strndup_handler(s, n);
241
}
242
243
static void *memprof_valloc_initial_handler(size_t sz)
244
{
245
  if (in_memprof & MEMPROF_IN_INIT) {
246
    /* probably that dlsym() needs valloc(), let's fail */
247
    return NULL;
248
  }
249
250
  memprof_init();
251
  return memprof_valloc_handler(sz);
252
}
253
254
static void *memprof_pvalloc_initial_handler(size_t sz)
255
{
256
  if (in_memprof & MEMPROF_IN_INIT) {
257
    /* probably that dlsym() needs pvalloc(), let's fail */
258
    return NULL;
259
  }
260
261
  memprof_init();
262
  return memprof_pvalloc_handler(sz);
263
}
264
265
static void *memprof_memalign_initial_handler(size_t al, size_t sz)
266
{
267
  if (in_memprof & MEMPROF_IN_INIT) {
268
    /* probably that dlsym() needs memalign(), let's fail */
269
    return NULL;
270
  }
271
272
  memprof_init();
273
  return memprof_memalign_handler(al, sz);
274
}
275
276
static void *memprof_aligned_alloc_initial_handler(size_t al, size_t sz)
277
{
278
  if (in_memprof & MEMPROF_IN_INIT) {
279
    /* probably that dlsym() needs aligned_alloc(), let's fail */
280
    return NULL;
281
  }
282
283
  memprof_init();
284
  return memprof_aligned_alloc_handler(al, sz);
285
}
286
287
static int memprof_posix_memalign_initial_handler(void **ptr, size_t al, size_t sz)
288
{
289
  if (in_memprof & MEMPROF_IN_INIT) {
290
    /* probably that dlsym() needs posix_memalign(), let's fail */
291
    return ENOMEM;
292
  }
293
294
  memprof_init();
295
  return memprof_posix_memalign_handler(ptr, al, sz);
296
}
297
298
/* Assign a bin for the memprof_stats to the return address. May perform a few
299
 * attempts before finding the right one, but always succeeds (in the worst
300
 * case, returns a default bin). The caller address is atomically set except
301
 * for the default one which is never set.
302
 */
303
struct memprof_stats *memprof_get_bin(const void *ra, enum memprof_method meth)
304
{
305
  int retries = 16; // up to 16 consecutive entries may be tested.
306
  const void *old;
307
  unsigned int bin;
308
  ullong hash;
309
310
  if (unlikely(!ra)) {
311
    bin = MEMPROF_HASH_BUCKETS;
312
    goto leave;
313
  }
314
  hash = _ptr2_hash_arg(ra, th_ctx->exec_ctx.pointer, th_ctx->exec_ctx.type);
315
  for (bin = _ptr_hash_reduce(hash, MEMPROF_HASH_BITS);
316
       memprof_stats[bin].caller != ra ||
317
         memprof_stats[bin].exec_ctx.type != th_ctx->exec_ctx.type ||
318
         memprof_stats[bin].exec_ctx.pointer != th_ctx->exec_ctx.pointer;
319
       bin = (bin + (hash | 1)) & (MEMPROF_HASH_BUCKETS - 1)) {
320
    if (!--retries) {
321
      bin = MEMPROF_HASH_BUCKETS;
322
      break;
323
    }
324
325
    old = NULL;
326
    if (!memprof_stats[bin].caller &&
327
        HA_ATOMIC_CAS(&memprof_stats[bin].caller, &old, ra)) {
328
      memprof_stats[bin].exec_ctx = th_ctx->exec_ctx;
329
      memprof_stats[bin].method = meth;
330
      break;
331
    }
332
  }
333
leave:
334
  return &memprof_stats[bin];
335
}
336
337
/* This is the new global malloc() function. It must optimize for the normal
338
 * case (i.e. profiling disabled) hence the first test to permit a direct jump.
339
 * It must remain simple to guarantee the lack of reentrance. stdio is not
340
 * possible there even for debugging. The reported size is the really allocated
341
 * one as returned by malloc_usable_size(), because this will allow it to be
342
 * compared to the one before realloc() or free(). This is a GNU and jemalloc
343
 * extension but other systems may also store this size in ptr[-1].
344
 */
345
void *malloc(size_t size)
346
{
347
  struct memprof_stats *bin;
348
  void *ret;
349
350
  if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
351
    return memprof_malloc_handler(size);
352
353
  in_memprof |= MEMPROF_IN_HANDLER;
354
  ret = memprof_malloc_handler(size);
355
  size = malloc_usable_size(ret) + sizeof(void *);
356
  in_memprof &= ~MEMPROF_IN_HANDLER;
357
358
  bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_MALLOC);
359
  if (unlikely(th_ctx->lock_level & 0x7F))
360
    _HA_ATOMIC_ADD(&bin->locked_calls, 1);
361
  _HA_ATOMIC_ADD(&bin->alloc_calls, 1);
362
  _HA_ATOMIC_ADD(&bin->alloc_tot, size);
363
  return ret;
364
}
365
366
/* This is the new global calloc() function. It must optimize for the normal
367
 * case (i.e. profiling disabled) hence the first test to permit a direct jump.
368
 * It must remain simple to guarantee the lack of reentrance. stdio is not
369
 * possible there even for debugging. The reported size is the really allocated
370
 * one as returned by malloc_usable_size(), because this will allow it to be
371
 * compared to the one before realloc() or free(). This is a GNU and jemalloc
372
 * extension but other systems may also store this size in ptr[-1].
373
 */
374
void *calloc(size_t nmemb, size_t size)
375
{
376
  struct memprof_stats *bin;
377
  void *ret;
378
379
  if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
380
    return memprof_calloc_handler(nmemb, size);
381
382
  in_memprof |= MEMPROF_IN_HANDLER;
383
  ret = memprof_calloc_handler(nmemb, size);
384
  size = malloc_usable_size(ret) + sizeof(void *);
385
  in_memprof &= ~MEMPROF_IN_HANDLER;
386
387
  bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_CALLOC);
388
  if (unlikely(th_ctx->lock_level & 0x7F))
389
    _HA_ATOMIC_ADD(&bin->locked_calls, 1);
390
  _HA_ATOMIC_ADD(&bin->alloc_calls, 1);
391
  _HA_ATOMIC_ADD(&bin->alloc_tot, size);
392
  return ret;
393
}
394
395
/* This is the new global realloc() function. It must optimize for the normal
396
 * case (i.e. profiling disabled) hence the first test to permit a direct jump.
397
 * It must remain simple to guarantee the lack of reentrance. stdio is not
398
 * possible there even for debugging. The reported size is the really allocated
399
 * one as returned by malloc_usable_size(), because this will allow it to be
400
 * compared to the one before realloc() or free(). This is a GNU and jemalloc
401
 * extension but other systems may also store this size in ptr[-1].
402
 * Depending on the old vs new size, it's considered as an allocation or a free
403
 * (or neither if the size remains the same).
404
 */
405
void *realloc(void *ptr, size_t size)
406
{
407
  struct memprof_stats *bin;
408
  size_t size_before;
409
  void *ret;
410
411
  if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
412
    return memprof_realloc_handler(ptr, size);
413
414
  in_memprof |= MEMPROF_IN_HANDLER;
415
  size_before = malloc_usable_size(ptr);
416
  ret = memprof_realloc_handler(ptr, size);
417
  size = malloc_usable_size(ret);
418
  in_memprof &= ~MEMPROF_IN_HANDLER;
419
420
  /* only count the extra link for new allocations */
421
  if (!ptr)
422
    size += sizeof(void *);
423
424
  bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_REALLOC);
425
  if (unlikely(th_ctx->lock_level & 0x7F))
426
    _HA_ATOMIC_ADD(&bin->locked_calls, 1);
427
  if (size > size_before) {
428
    _HA_ATOMIC_ADD(&bin->alloc_calls, 1);
429
    _HA_ATOMIC_ADD(&bin->alloc_tot, size - size_before);
430
  } else if (size < size_before) {
431
    _HA_ATOMIC_ADD(&bin->free_calls, 1);
432
    _HA_ATOMIC_ADD(&bin->free_tot, size_before - size);
433
  }
434
  return ret;
435
}
436
437
/* This is the new global strdup() function. It must optimize for the normal
438
 * case (i.e. profiling disabled) hence the first test to permit a direct jump.
439
 * It must remain simple to guarantee the lack of reentrance. stdio is not
440
 * possible there even for debugging. The reported size is the really allocated
441
 * one as returned by malloc_usable_size(), because this will allow it to be
442
 * compared to the one before realloc() or free(). This is a GNU and jemalloc
443
 * extension but other systems may also store this size in ptr[-1].
444
 */
445
char *strdup(const char *s)
446
{
447
  struct memprof_stats *bin;
448
  size_t size;
449
  char *ret;
450
451
  if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
452
    return memprof_strdup_handler(s);
453
454
  in_memprof |= MEMPROF_IN_HANDLER;
455
  ret = memprof_strdup_handler(s);
456
  size = malloc_usable_size(ret) + sizeof(void *);
457
  in_memprof &= ~MEMPROF_IN_HANDLER;
458
459
  bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_STRDUP);
460
  if (unlikely(th_ctx->lock_level & 0x7F))
461
    _HA_ATOMIC_ADD(&bin->locked_calls, 1);
462
  _HA_ATOMIC_ADD(&bin->alloc_calls, 1);
463
  _HA_ATOMIC_ADD(&bin->alloc_tot, size);
464
  return ret;
465
}
466
467
/* This is the new global free() function. It must optimize for the normal
468
 * case (i.e. profiling disabled) hence the first test to permit a direct jump.
469
 * It must remain simple to guarantee the lack of reentrance. stdio is not
470
 * possible there even for debugging. The reported size is the really allocated
471
 * one as returned by malloc_usable_size(), because this will allow it to be
472
 * compared to the one before realloc() or free(). This is a GNU and jemalloc
473
 * extension but other systems may also store this size in ptr[-1]. Since
474
 * free() is often called on NULL pointers to collect garbage at the end of
475
 * many functions or during config parsing, as a special case free(NULL)
476
 * doesn't update any stats.
477
 */
478
void free(void *ptr)
479
{
480
  struct memprof_stats *bin;
481
  size_t size_before;
482
483
  if (likely(!(profiling & HA_PROF_MEMORY) || !ptr || (in_memprof & MEMPROF_IN_HANDLER))) {
484
    memprof_free_handler(ptr);
485
    return;
486
  }
487
488
  in_memprof |= MEMPROF_IN_HANDLER;
489
  size_before = malloc_usable_size(ptr) + sizeof(void *);
490
  memprof_free_handler(ptr);
491
  in_memprof &= ~MEMPROF_IN_HANDLER;
492
493
  bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_FREE);
494
  if (unlikely(th_ctx->lock_level & 0x7F))
495
    _HA_ATOMIC_ADD(&bin->locked_calls, 1);
496
  _HA_ATOMIC_ADD(&bin->free_calls, 1);
497
  _HA_ATOMIC_ADD(&bin->free_tot, size_before);
498
}
499
500
/* optional handlers below, essentially to monitor libs activities */
501
502
char *strndup(const char *s, size_t size)
503
{
504
  struct memprof_stats *bin;
505
  char *ret;
506
507
  if (!memprof_strndup_handler)
508
    return NULL;
509
510
  ret = memprof_strndup_handler(s, size);
511
  if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
512
    return ret;
513
514
  in_memprof |= MEMPROF_IN_HANDLER;
515
  size = malloc_usable_size(ret) + sizeof(void *);
516
  in_memprof &= ~MEMPROF_IN_HANDLER;
517
518
  bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_STRNDUP);
519
  if (unlikely(th_ctx->lock_level & 0x7F))
520
    _HA_ATOMIC_ADD(&bin->locked_calls, 1);
521
  _HA_ATOMIC_ADD(&bin->alloc_calls, 1);
522
  _HA_ATOMIC_ADD(&bin->alloc_tot, size);
523
  return ret;
524
}
525
526
void *valloc(size_t size)
527
{
528
  struct memprof_stats *bin;
529
  void *ret;
530
531
  if (!memprof_valloc_handler)
532
    return NULL;
533
534
  ret = memprof_valloc_handler(size);
535
  if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
536
    return ret;
537
538
  in_memprof |= MEMPROF_IN_HANDLER;
539
  size = malloc_usable_size(ret) + sizeof(void *);
540
  in_memprof &= ~MEMPROF_IN_HANDLER;
541
542
  bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_VALLOC);
543
  if (unlikely(th_ctx->lock_level & 0x7F))
544
    _HA_ATOMIC_ADD(&bin->locked_calls, 1);
545
  _HA_ATOMIC_ADD(&bin->alloc_calls, 1);
546
  _HA_ATOMIC_ADD(&bin->alloc_tot, size);
547
  return ret;
548
}
549
550
void *pvalloc(size_t size)
551
{
552
  struct memprof_stats *bin;
553
  void *ret;
554
555
  if (!memprof_pvalloc_handler)
556
    return NULL;
557
558
  ret = memprof_pvalloc_handler(size);
559
  if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
560
    return ret;
561
562
  in_memprof |= MEMPROF_IN_HANDLER;
563
  size = malloc_usable_size(ret) + sizeof(void *);
564
  in_memprof &= ~MEMPROF_IN_HANDLER;
565
566
  bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_PVALLOC);
567
  if (unlikely(th_ctx->lock_level & 0x7F))
568
    _HA_ATOMIC_ADD(&bin->locked_calls, 1);
569
  _HA_ATOMIC_ADD(&bin->alloc_calls, 1);
570
  _HA_ATOMIC_ADD(&bin->alloc_tot, size);
571
  return ret;
572
}
573
574
void *memalign(size_t align, size_t size)
575
{
576
  struct memprof_stats *bin;
577
  void *ret;
578
579
  if (!memprof_memalign_handler)
580
    return NULL;
581
582
  ret = memprof_memalign_handler(align, size);
583
  if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
584
    return ret;
585
586
  in_memprof |= MEMPROF_IN_HANDLER;
587
  size = malloc_usable_size(ret) + sizeof(void *);
588
  in_memprof &= ~MEMPROF_IN_HANDLER;
589
590
  bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_MEMALIGN);
591
  if (unlikely(th_ctx->lock_level & 0x7F))
592
    _HA_ATOMIC_ADD(&bin->locked_calls, 1);
593
  _HA_ATOMIC_ADD(&bin->alloc_calls, 1);
594
  _HA_ATOMIC_ADD(&bin->alloc_tot, size);
595
  return ret;
596
}
597
598
void *aligned_alloc(size_t align, size_t size)
599
{
600
  struct memprof_stats *bin;
601
  void *ret;
602
603
  if (!memprof_aligned_alloc_handler)
604
    return NULL;
605
606
  ret = memprof_aligned_alloc_handler(align, size);
607
  if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
608
    return ret;
609
610
  in_memprof |= MEMPROF_IN_HANDLER;
611
  size = malloc_usable_size(ret) + sizeof(void *);
612
  in_memprof &= ~MEMPROF_IN_HANDLER;
613
614
  bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_ALIGNED_ALLOC);
615
  if (unlikely(th_ctx->lock_level & 0x7F))
616
    _HA_ATOMIC_ADD(&bin->locked_calls, 1);
617
  _HA_ATOMIC_ADD(&bin->alloc_calls, 1);
618
  _HA_ATOMIC_ADD(&bin->alloc_tot, size);
619
  return ret;
620
}
621
622
int posix_memalign(void **ptr, size_t align, size_t size)
623
{
624
  struct memprof_stats *bin;
625
  int ret;
626
627
  if (!memprof_posix_memalign_handler)
628
    return ENOMEM;
629
630
  ret = memprof_posix_memalign_handler(ptr, align, size);
631
  if (likely(!(profiling & HA_PROF_MEMORY) || (in_memprof & MEMPROF_IN_HANDLER)))
632
    return ret;
633
634
  if (ret != 0) // error
635
    return ret;
636
637
  in_memprof |= MEMPROF_IN_HANDLER;
638
  size = malloc_usable_size(*ptr) + sizeof(void *);
639
  in_memprof &= ~MEMPROF_IN_HANDLER;
640
641
  bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_POSIX_MEMALIGN);
642
  if (unlikely(th_ctx->lock_level & 0x7F))
643
    _HA_ATOMIC_ADD(&bin->locked_calls, 1);
644
  _HA_ATOMIC_ADD(&bin->alloc_calls, 1);
645
  _HA_ATOMIC_ADD(&bin->alloc_tot, size);
646
  return ret;
647
}
648
649
/* remove info from entries matching <info>. This needs to be used by callers
650
 * of pool_destroy() so that we don't keep a reference to a dead pool. Nothing
651
 * is done if <info> is NULL.
652
 */
653
void memprof_remove_stale_info(const void *info)
654
{
655
  int i;
656
657
  if (!info)
658
    return;
659
660
  for (i = 0; i < MEMPROF_HASH_BUCKETS; i++) {
661
    if (_HA_ATOMIC_LOAD(&memprof_stats[i].info) == info)
662
      _HA_ATOMIC_STORE(&memprof_stats[i].info, NULL);
663
  }
664
}
665
666
#endif // USE_MEMORY_PROFILING
667
668
/* Updates the current thread's statistics about stolen CPU time. The unit for
669
 * <stolen> is half-milliseconds.
670
 */
671
void report_stolen_time(uint64_t stolen)
672
0
{
673
0
  activity[tid].cpust_total += stolen;
674
0
  update_freq_ctr(&activity[tid].cpust_1s, stolen);
675
0
  update_freq_ctr_period(&activity[tid].cpust_15s, 15000, stolen);
676
0
}
677
678
/* Update avg_loop value for the current thread and possibly decide to enable
679
 * task-level profiling on the current thread based on its average run time.
680
 * The <run_time> argument is the number of microseconds elapsed since the
681
 * last time poll() returned.
682
 */
683
void activity_count_runtime(uint32_t run_time)
684
0
{
685
0
  uint32_t up, down;
686
687
  /* 1 millisecond per loop on average over last 1024 iterations is
688
   * enough to turn on profiling.
689
   */
690
0
  up = 1000;
691
0
  down = up * 99 / 100;
692
693
0
  run_time = swrate_add(&activity[tid].avg_loop_us, TIME_STATS_SAMPLES, run_time);
694
695
  /* In automatic mode, reaching the "up" threshold on average switches
696
   * profiling to "on" when automatic, and going back below the "down"
697
   * threshold switches to off. The forced modes don't check the load.
698
   */
699
0
  if (!(_HA_ATOMIC_LOAD(&th_ctx->flags) & TH_FL_TASK_PROFILING)) {
700
0
    if (unlikely((profiling & HA_PROF_TASKS_MASK) == HA_PROF_TASKS_ON ||
701
0
                 ((profiling & HA_PROF_TASKS_MASK) == HA_PROF_TASKS_AON &&
702
0
            swrate_avg(run_time, TIME_STATS_SAMPLES) >= up))) {
703
704
0
      if (profiling & HA_PROF_TASKS_LOCK)
705
0
        _HA_ATOMIC_OR(&th_ctx->flags, TH_FL_TASK_PROFILING_L);
706
0
      else
707
0
        _HA_ATOMIC_AND(&th_ctx->flags, ~TH_FL_TASK_PROFILING_L);
708
709
0
      if (profiling & HA_PROF_TASKS_MEM)
710
0
        _HA_ATOMIC_OR(&th_ctx->flags, TH_FL_TASK_PROFILING_M);
711
0
      else
712
0
        _HA_ATOMIC_AND(&th_ctx->flags, ~TH_FL_TASK_PROFILING_M);
713
714
0
      _HA_ATOMIC_OR(&th_ctx->flags, TH_FL_TASK_PROFILING);
715
0
    }
716
0
  } else {
717
0
    if (unlikely((profiling & HA_PROF_TASKS_MASK) == HA_PROF_TASKS_OFF ||
718
0
                 ((profiling & HA_PROF_TASKS_MASK) == HA_PROF_TASKS_AOFF &&
719
0
                 swrate_avg(run_time, TIME_STATS_SAMPLES) <= down)))
720
0
      _HA_ATOMIC_AND(&th_ctx->flags, ~TH_FL_TASK_PROFILING);
721
0
  }
722
0
}
723
724
#ifdef USE_MEMORY_PROFILING
725
/* config parser for global "profiling.memory", accepts "on" or "off" */
726
static int cfg_parse_prof_memory(char **args, int section_type, struct proxy *curpx,
727
                                const struct proxy *defpx, const char *file, int line,
728
                                char **err)
729
{
730
  if (too_many_args(1, args, err, NULL))
731
    return -1;
732
733
  if (strcmp(args[1], "on") == 0) {
734
    profiling |= HA_PROF_MEMORY;
735
    HA_ATOMIC_STORE(&prof_mem_start_ns, now_ns);
736
  }
737
  else if (strcmp(args[1], "off") == 0)
738
    profiling &= ~HA_PROF_MEMORY;
739
  else {
740
    memprintf(err, "'%s' expects either 'on' or 'off' but got '%s'.", args[0], args[1]);
741
    return -1;
742
  }
743
  return 0;
744
}
745
#endif // USE_MEMORY_PROFILING
746
747
/* config parser for global "profiling.tasks", accepts "on", "off", 'auto",
748
 * "lock", "no-lock", "memory", "no-memory".
749
 */
750
static int cfg_parse_prof_tasks(char **args, int section_type, struct proxy *curpx,
751
                                const struct proxy *defpx, const char *file, int line,
752
                                char **err)
753
0
{
754
0
  int arg;
755
756
0
  for (arg = 1; *args[arg]; arg++) {
757
0
    if (strcmp(args[arg], "on") == 0) {
758
0
      profiling = (profiling & ~HA_PROF_TASKS_MASK) | HA_PROF_TASKS_ON;
759
0
      HA_ATOMIC_STORE(&prof_task_start_ns, now_ns);
760
0
    }
761
0
    else if (strcmp(args[arg], "auto") == 0) {
762
0
      profiling = (profiling & ~HA_PROF_TASKS_MASK) | HA_PROF_TASKS_AOFF;
763
0
      HA_ATOMIC_STORE(&prof_task_start_ns, now_ns);
764
0
    }
765
0
    else if (strcmp(args[arg], "off") == 0)
766
0
      profiling = (profiling & ~HA_PROF_TASKS_MASK) | HA_PROF_TASKS_OFF;
767
0
    else if (strcmp(args[arg], "lock") == 0)
768
0
      profiling |= HA_PROF_TASKS_LOCK;
769
0
    else if (strcmp(args[arg], "no-lock") == 0)
770
0
      profiling &= ~HA_PROF_TASKS_LOCK;
771
0
    else if (strcmp(args[arg], "memory") == 0)
772
0
      profiling |= HA_PROF_TASKS_MEM;
773
0
    else if (strcmp(args[arg], "no-memory") == 0)
774
0
      profiling &= ~HA_PROF_TASKS_MEM;
775
0
    else
776
0
      break;
777
0
  }
778
779
  /* either no arg or invalid arg */
780
0
  if (arg == 1 || *args[arg]) {
781
0
    memprintf(err, "'%s' expects a combination of either 'on', 'auto', 'off', 'lock', 'no-lock', 'memory', or 'no-memory', but got '%s'.", args[0], args[arg]);
782
0
    return -1;
783
0
  }
784
0
  return 0;
785
0
}
786
787
/* parse a "set profiling" command. It always returns 1. */
788
static int cli_parse_set_profiling(char **args, char *payload, struct appctx *appctx, void *private)
789
0
{
790
0
  int arg;
791
792
0
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
793
0
    return 1;
794
795
0
  if (strcmp(args[2], "memory") == 0) {
796
#ifdef USE_MEMORY_PROFILING
797
    if (strcmp(args[3], "on") == 0) {
798
      unsigned int old = profiling;
799
      int i;
800
801
      while (!_HA_ATOMIC_CAS(&profiling, &old, old | HA_PROF_MEMORY))
802
        ;
803
804
      HA_ATOMIC_STORE(&prof_mem_start_ns, now_ns);
805
      HA_ATOMIC_STORE(&prof_mem_stop_ns, 0);
806
807
      /* also flush current profiling stats */
808
      for (i = 0; i < sizeof(memprof_stats) / sizeof(memprof_stats[0]); i++) {
809
        HA_ATOMIC_STORE(&memprof_stats[i].locked_calls, 0);
810
        HA_ATOMIC_STORE(&memprof_stats[i].alloc_calls, 0);
811
        HA_ATOMIC_STORE(&memprof_stats[i].free_calls, 0);
812
        HA_ATOMIC_STORE(&memprof_stats[i].alloc_tot, 0);
813
        HA_ATOMIC_STORE(&memprof_stats[i].free_tot, 0);
814
        HA_ATOMIC_STORE(&memprof_stats[i].caller, NULL);
815
      }
816
    }
817
    else if (strcmp(args[3], "off") == 0) {
818
      unsigned int old = profiling;
819
820
      while (!_HA_ATOMIC_CAS(&profiling, &old, old & ~HA_PROF_MEMORY))
821
        ;
822
823
      if (HA_ATOMIC_LOAD(&prof_mem_start_ns))
824
        HA_ATOMIC_STORE(&prof_mem_stop_ns, now_ns);
825
    }
826
    else
827
      return cli_err(appctx, "Expects either 'on' or 'off'.\n");
828
    return 1;
829
#else
830
0
    return cli_err(appctx, "Memory profiling not compiled in.\n");
831
0
#endif
832
0
  }
833
834
0
  if (strcmp(args[2], "tasks") != 0)
835
0
    return cli_err(appctx, "Expects either 'tasks' or 'memory'.\n");
836
837
0
  for (arg = 3; *args[arg]; arg++) {
838
0
    if (strcmp(args[arg], "on") == 0) {
839
0
      unsigned int old = profiling;
840
0
      int i;
841
842
0
      while (!_HA_ATOMIC_CAS(&profiling, &old, (old & ~HA_PROF_TASKS_MASK) | HA_PROF_TASKS_ON))
843
0
        ;
844
845
0
      HA_ATOMIC_STORE(&prof_task_start_ns, now_ns);
846
0
      HA_ATOMIC_STORE(&prof_task_stop_ns, 0);
847
848
      /* also flush current profiling stats */
849
0
      for (i = 0; i < SCHED_ACT_HASH_BUCKETS; i++) {
850
0
        HA_ATOMIC_STORE(&sched_activity[i].calls, 0);
851
0
        HA_ATOMIC_STORE(&sched_activity[i].cpu_time, 0);
852
0
        HA_ATOMIC_STORE(&sched_activity[i].lat_time, 0);
853
0
        HA_ATOMIC_STORE(&sched_activity[i].lkw_time, 0);
854
0
        HA_ATOMIC_STORE(&sched_activity[i].lkd_time, 0);
855
0
        HA_ATOMIC_STORE(&sched_activity[i].mem_time, 0);
856
0
        HA_ATOMIC_STORE(&sched_activity[i].func, NULL);
857
0
        HA_ATOMIC_STORE(&sched_activity[i].caller, NULL);
858
0
      }
859
0
    }
860
0
    else if (strcmp(args[arg], "auto") == 0) {
861
0
      unsigned int old = profiling;
862
0
      unsigned int new;
863
864
0
      do {
865
0
        if ((old & HA_PROF_TASKS_MASK) >= HA_PROF_TASKS_AON)
866
0
          new = (old & ~HA_PROF_TASKS_MASK) | HA_PROF_TASKS_AON;
867
0
        else
868
0
          new = (old & ~HA_PROF_TASKS_MASK) | HA_PROF_TASKS_AOFF;
869
0
      } while (!_HA_ATOMIC_CAS(&profiling, &old, new));
870
871
0
      HA_ATOMIC_STORE(&prof_task_start_ns, now_ns);
872
0
      HA_ATOMIC_STORE(&prof_task_stop_ns, 0);
873
0
    }
874
0
    else if (strcmp(args[arg], "off") == 0) {
875
0
      unsigned int old = profiling;
876
0
      while (!_HA_ATOMIC_CAS(&profiling, &old, (old & ~HA_PROF_TASKS_MASK) | HA_PROF_TASKS_OFF))
877
0
        ;
878
879
0
      if (HA_ATOMIC_LOAD(&prof_task_start_ns))
880
0
        HA_ATOMIC_STORE(&prof_task_stop_ns, now_ns);
881
0
    }
882
0
    else if (strcmp(args[arg], "lock") == 0)
883
0
      HA_ATOMIC_OR(&profiling, HA_PROF_TASKS_LOCK);
884
0
    else if (strcmp(args[arg], "no-lock") == 0)
885
0
      HA_ATOMIC_AND(&profiling, ~HA_PROF_TASKS_LOCK);
886
0
    else if (strcmp(args[arg], "memory") == 0)
887
0
      HA_ATOMIC_OR(&profiling, HA_PROF_TASKS_MEM);
888
0
    else if (strcmp(args[arg], "no-memory") == 0)
889
0
      HA_ATOMIC_AND(&profiling, ~HA_PROF_TASKS_MEM);
890
0
    else
891
0
      break; // unknown arg
892
0
  }
893
894
  /* either no arg or invalid one */
895
0
  if (arg == 3 || *args[arg])
896
0
    return cli_err(appctx, "Expects a combination of either 'on', 'auto', 'off', 'lock', 'no-lock', 'memory' or 'no-memory'.\n");
897
898
0
  return 1;
899
0
}
900
901
static int cmp_sched_activity_calls(const void *a, const void *b)
902
0
{
903
0
  const struct sched_activity *l = (const struct sched_activity *)a;
904
0
  const struct sched_activity *r = (const struct sched_activity *)b;
905
906
0
  if (l->calls > r->calls)
907
0
    return -1;
908
0
  else if (l->calls < r->calls)
909
0
    return 1;
910
0
  else
911
0
    return 0;
912
0
}
913
914
/* sort by address first, then by call count */
915
static int cmp_sched_activity_addr(const void *a, const void *b)
916
0
{
917
0
  const struct sched_activity *l = (const struct sched_activity *)a;
918
0
  const struct sched_activity *r = (const struct sched_activity *)b;
919
920
0
  if (l->func > r->func)
921
0
    return -1;
922
0
  else if (l->func < r->func)
923
0
    return 1;
924
0
  else if (l->calls > r->calls)
925
0
    return -1;
926
0
  else if (l->calls < r->calls)
927
0
    return 1;
928
0
  else
929
0
    return 0;
930
0
}
931
932
/* sort by cpu time first, then by inverse call count (to spot highest offenders) */
933
static int cmp_sched_activity_cpu(const void *a, const void *b)
934
0
{
935
0
  const struct sched_activity *l = (const struct sched_activity *)a;
936
0
  const struct sched_activity *r = (const struct sched_activity *)b;
937
938
0
  if (l->cpu_time > r->cpu_time)
939
0
    return -1;
940
0
  else if (l->cpu_time < r->cpu_time)
941
0
    return 1;
942
0
  else if (l->calls < r->calls)
943
0
    return -1;
944
0
  else if (l->calls > r->calls)
945
0
    return 1;
946
0
  else
947
0
    return 0;
948
0
}
949
950
#ifdef USE_MEMORY_PROFILING
951
/* used by qsort below */
952
static int cmp_memprof_stats(const void *a, const void *b)
953
{
954
  const struct memprof_stats *l = (const struct memprof_stats *)a;
955
  const struct memprof_stats *r = (const struct memprof_stats *)b;
956
957
  if (l->alloc_tot + l->free_tot > r->alloc_tot + r->free_tot)
958
    return -1;
959
  else if (l->alloc_tot + l->free_tot < r->alloc_tot + r->free_tot)
960
    return 1;
961
  else if (l->exec_ctx.type > r->exec_ctx.type)
962
    return -1;
963
  else if (l->exec_ctx.type < r->exec_ctx.type)
964
    return 1;
965
  else if (l->exec_ctx.pointer > r->exec_ctx.pointer)
966
    return -1;
967
  else if (l->exec_ctx.pointer < r->exec_ctx.pointer)
968
    return 1;
969
  else
970
    return 0;
971
}
972
973
static int cmp_memprof_addr(const void *a, const void *b)
974
{
975
  const struct memprof_stats *l = (const struct memprof_stats *)a;
976
  const struct memprof_stats *r = (const struct memprof_stats *)b;
977
978
  if (l->caller > r->caller)
979
    return -1;
980
  else if (l->caller < r->caller)
981
    return 1;
982
  else if (l->exec_ctx.type > r->exec_ctx.type)
983
    return -1;
984
  else if (l->exec_ctx.type < r->exec_ctx.type)
985
    return 1;
986
  else if (l->exec_ctx.pointer > r->exec_ctx.pointer)
987
    return -1;
988
  else if (l->exec_ctx.pointer < r->exec_ctx.pointer)
989
    return 1;
990
  else
991
    return 0;
992
}
993
994
static int cmp_memprof_ctx(const void *a, const void *b)
995
{
996
  const struct memprof_stats *l = (const struct memprof_stats *)a;
997
  const struct memprof_stats *r = (const struct memprof_stats *)b;
998
  const void *ptrl = l->exec_ctx.pointer;
999
  const void *ptrr = r->exec_ctx.pointer;
1000
1001
  /* in case of a mux, we'll use the always-present ->subscribe()
1002
   * function as a sorting key so that mux-ops and other mux functions
1003
   * appear grouped together.
1004
   */
1005
  if (l->exec_ctx.type == TH_EX_CTX_MUX)
1006
    ptrl = l->exec_ctx.mux_ops->subscribe;
1007
1008
  if (r->exec_ctx.type == TH_EX_CTX_MUX)
1009
    ptrr = r->exec_ctx.mux_ops->subscribe;
1010
1011
  if (ptrl > ptrr)
1012
    return -1;
1013
  else if (ptrl < ptrr)
1014
    return 1;
1015
  else if (l->exec_ctx.type > r->exec_ctx.type)
1016
    return -1;
1017
  else if (l->exec_ctx.type < r->exec_ctx.type)
1018
    return 1;
1019
  else if (l->caller > r->caller)
1020
    return -1;
1021
  else if (l->caller < r->caller)
1022
    return 1;
1023
  else
1024
    return 0;
1025
}
1026
#endif // USE_MEMORY_PROFILING
1027
1028
/* Computes the index of function pointer <func> and caller <caller> for use
1029
 * with sched_activity[] or any other similar array passed in <array>, and
1030
 * returns a pointer to the entry after having atomically assigned it to this
1031
 * function pointer and caller combination. Note that in case of collision,
1032
 * the first entry is returned instead ("other").
1033
 */
1034
struct sched_activity *sched_activity_entry(struct sched_activity *array, const void *func, const void *caller)
1035
0
{
1036
0
  uint32_t hash = ptr2_hash(func, caller, SCHED_ACT_HASH_BITS);
1037
0
  struct sched_activity *ret;
1038
0
  const void *old;
1039
0
  int tries = 16;
1040
1041
0
  for (tries = 16; tries > 0; tries--, hash++) {
1042
0
    ret = &array[hash];
1043
1044
0
    while (1) {
1045
0
      if (likely(ret->func)) {
1046
0
        if (likely(ret->func == func && ret->caller == caller))
1047
0
          return ret;
1048
0
        break;
1049
0
      }
1050
1051
      /* try to create the new entry. Func is sufficient to
1052
       * reserve the node.
1053
       */
1054
0
      old = NULL;
1055
0
      if (HA_ATOMIC_CAS(&ret->func, &old, func)) {
1056
0
        ret->caller = caller;
1057
0
        return ret;
1058
0
      }
1059
      /* changed in parallel, check again */
1060
0
    }
1061
0
  }
1062
1063
0
  return array;
1064
0
}
1065
1066
/* This function dumps all profiling settings. It returns 0 if the output
1067
 * buffer is full and it needs to be called again, otherwise non-zero.
1068
 * It dumps some parts depending on the following states from show_prof_ctx:
1069
 *    dump_step:
1070
 *       0, 4: dump status, then jump to 1 if 0
1071
 *       1, 5: dump tasks, then jump to 2 if 1
1072
 *       2, 6: dump memory, then stop
1073
 *    linenum:
1074
 *       restart line for each step (starts at zero)
1075
 *    maxcnt:
1076
 *       may contain a configured max line count for each step (0=not set)
1077
 *    byaddr:
1078
 *       0: sort by usage
1079
 *       1: sort by address
1080
 */
1081
static int cli_io_handler_show_profiling(struct appctx *appctx)
1082
0
{
1083
0
  struct show_prof_ctx *ctx = appctx->svcctx;
1084
0
  struct sched_activity *tmp_activity = ctx->tmp_activity;
1085
#ifdef USE_MEMORY_PROFILING
1086
  struct memprof_stats *tmp_memstats = ctx->tmp_memstats;
1087
  unsigned long long tot_alloc_calls, tot_free_calls;
1088
  unsigned long long tot_alloc_bytes, tot_free_bytes;
1089
#endif
1090
0
  struct buffer *name_buffer = get_trash_chunk();
1091
0
  const struct ha_caller *caller;
1092
0
  const char *str;
1093
0
  int max_lines;
1094
0
  int i, j, max;
1095
0
  int dumped;
1096
1097
0
  chunk_reset(&trash);
1098
1099
0
  switch (profiling & HA_PROF_TASKS_MASK) {
1100
0
  case HA_PROF_TASKS_AOFF: str="auto-off"; break;
1101
0
  case HA_PROF_TASKS_AON:  str="auto-on"; break;
1102
0
  case HA_PROF_TASKS_ON:   str="on"; break;
1103
0
  default:                 str="off"; break;
1104
0
  }
1105
1106
0
  if ((ctx->dump_step & 3) != 0)
1107
0
    goto skip_status;
1108
1109
0
  chunk_printf(&trash,
1110
0
               "Per-task CPU profiling              : %-8s      # set profiling tasks {on|auto|off}\n"
1111
0
               "Memory usage profiling              : %-8s      # set profiling memory {on|off}\n",
1112
0
               str, (profiling & HA_PROF_MEMORY) ? "on" : "off");
1113
1114
0
  if (applet_putchk(appctx, &trash) == -1) {
1115
    /* failed, try again */
1116
0
    return 0;
1117
0
  }
1118
1119
0
  ctx->linenum = 0; // reset first line to dump
1120
0
  if ((ctx->dump_step & 4) == 0)
1121
0
    ctx->dump_step++; // next step
1122
1123
0
 skip_status:
1124
0
  if ((ctx->dump_step & 3) != 1)
1125
0
    goto skip_tasks;
1126
1127
0
  if (tmp_activity)
1128
0
    goto tasks_resume;
1129
1130
  /* first call for show profiling tasks: we have to allocate a tmp
1131
   * array for sorting and processing, and possibly perform some
1132
   * sorting and aggregation.
1133
   */
1134
0
  tmp_activity = ha_aligned_alloc(__alignof__(*tmp_activity), sizeof(sched_activity));
1135
0
  if (!tmp_activity)
1136
0
    goto end_tasks;
1137
1138
0
  ctx->tmp_activity = tmp_activity;
1139
0
  memcpy(tmp_activity, sched_activity, sizeof(sched_activity));
1140
1141
  /* for addr sort and for callee aggregation we have to first sort by address */
1142
0
  if (ctx->aggr || ctx->by_what == 1) // sort by addr
1143
0
    qsort(tmp_activity, SCHED_ACT_HASH_BUCKETS, sizeof(tmp_activity[0]), cmp_sched_activity_addr);  
1144
1145
0
  if (ctx->aggr) {
1146
    /* merge entries for the same callee and reset their count */
1147
0
    for (i = j = 0; i < SCHED_ACT_HASH_BUCKETS; i = j) {
1148
0
      for (j = i + 1; j < SCHED_ACT_HASH_BUCKETS && tmp_activity[j].func == tmp_activity[i].func; j++) {
1149
0
        tmp_activity[i].calls    += tmp_activity[j].calls;
1150
0
        tmp_activity[i].cpu_time += tmp_activity[j].cpu_time;
1151
0
        tmp_activity[i].lat_time += tmp_activity[j].lat_time;
1152
0
        tmp_activity[i].lkw_time += tmp_activity[j].lkw_time;
1153
0
        tmp_activity[i].lkd_time += tmp_activity[j].lkd_time;
1154
0
        tmp_activity[i].mem_time += tmp_activity[j].mem_time;
1155
0
        tmp_activity[j].calls = 0;
1156
0
      }
1157
0
    }
1158
0
  }
1159
1160
0
  if (!ctx->by_what) // sort by usage
1161
0
    qsort(tmp_activity, SCHED_ACT_HASH_BUCKETS, sizeof(tmp_activity[0]), cmp_sched_activity_calls);
1162
0
  else if (ctx->by_what == 2) // by cpu_tot
1163
0
    qsort(tmp_activity, SCHED_ACT_HASH_BUCKETS, sizeof(tmp_activity[0]), cmp_sched_activity_cpu);
1164
1165
0
 tasks_resume:
1166
0
  if (!ctx->linenum)
1167
0
    chunk_appendf(&trash, "Tasks activity over %.3f sec till %.3f sec ago:\n"
1168
0
                          "  function                      calls   cpu_tot   cpu_avg   lkw_avg   lkd_avg   mem_avg   lat_avg\n",
1169
0
            (prof_task_start_ns ? (prof_task_stop_ns ? prof_task_stop_ns : now_ns) - prof_task_start_ns : 0) / 1000000000.0,
1170
0
            (prof_task_stop_ns ? now_ns - prof_task_stop_ns : 0) / 1000000000.0);
1171
1172
0
  max_lines = ctx->maxcnt;
1173
0
  if (!max_lines)
1174
0
    max_lines = SCHED_ACT_HASH_BUCKETS;
1175
1176
0
  dumped = 0;
1177
0
  for (i = ctx->linenum; i < max_lines; i++) {
1178
0
    if (!tmp_activity[i].calls)
1179
0
      continue; // skip aggregated or empty entries
1180
1181
0
    ctx->linenum = i;
1182
1183
    /* resolve_sym_name() may be slow, better dump a few entries at a time */
1184
0
    if (dumped >= 10)
1185
0
      return 0;
1186
1187
0
    chunk_reset(name_buffer);
1188
0
    caller = HA_ATOMIC_LOAD(&tmp_activity[i].caller);
1189
1190
0
    if (!tmp_activity[i].func)
1191
0
      chunk_printf(name_buffer, "other");
1192
0
    else
1193
0
      resolve_sym_name(name_buffer, "", tmp_activity[i].func);
1194
1195
    /* reserve 35 chars for name+' '+#calls, knowing that longer names
1196
     * are often used for less often called functions.
1197
     */
1198
0
    max = 35 - name_buffer->data;
1199
0
    if (max < 1)
1200
0
      max = 1;
1201
0
    chunk_appendf(&trash, "  %s%*llu", name_buffer->area, max, (unsigned long long)tmp_activity[i].calls);
1202
1203
0
    print_time_short(&trash, "   ", tmp_activity[i].cpu_time, "");
1204
0
    print_time_short(&trash, "   ", tmp_activity[i].cpu_time / tmp_activity[i].calls, "");
1205
0
    print_time_short(&trash, "   ", tmp_activity[i].lkw_time / tmp_activity[i].calls, "");
1206
0
    print_time_short(&trash, "   ", tmp_activity[i].lkd_time / tmp_activity[i].calls, "");
1207
0
    print_time_short(&trash, "   ", tmp_activity[i].mem_time / tmp_activity[i].calls, "");
1208
0
    print_time_short(&trash, "   ", tmp_activity[i].lat_time / tmp_activity[i].calls, "");
1209
1210
0
    if (caller && !ctx->aggr && caller->what <= WAKEUP_TYPE_APPCTX_WAKEUP)
1211
0
      chunk_appendf(&trash, " <- %s@%s:%d %s",
1212
0
              caller->func, caller->file, caller->line,
1213
0
              task_wakeup_type_str(caller->what));
1214
1215
0
    b_putchr(&trash, '\n');
1216
1217
0
    if (applet_putchk(appctx, &trash) == -1) {
1218
      /* failed, try again */
1219
0
      return 0;
1220
0
    }
1221
0
    dumped++;
1222
0
  }
1223
1224
0
  if (applet_putchk(appctx, &trash) == -1) {
1225
    /* failed, try again */
1226
0
    return 0;
1227
0
  }
1228
1229
0
 end_tasks:
1230
0
  ha_free(&ctx->tmp_activity);
1231
0
  ctx->linenum = 0; // reset first line to dump
1232
0
  if ((ctx->dump_step & 4) == 0)
1233
0
    ctx->dump_step++; // next step
1234
1235
0
 skip_tasks:
1236
1237
#ifdef USE_MEMORY_PROFILING
1238
  if ((ctx->dump_step & 3) != 2)
1239
    goto skip_mem;
1240
1241
  if (tmp_memstats)
1242
    goto memstats_resume;
1243
1244
  /* first call for show profiling memory: we have to allocate a tmp
1245
   * array for sorting and processing, and possibly perform some sorting
1246
   * and aggregation.
1247
   */
1248
  tmp_memstats = ha_aligned_alloc(__alignof__(*tmp_memstats), sizeof(memprof_stats));
1249
  if (!tmp_memstats)
1250
    goto end_memstats;
1251
1252
  ctx->tmp_memstats = tmp_memstats;
1253
  memcpy(tmp_memstats, memprof_stats, sizeof(memprof_stats));
1254
1255
  if (ctx->by_what == 1)
1256
    qsort(tmp_memstats, MEMPROF_HASH_BUCKETS+1, sizeof(tmp_memstats[0]), cmp_memprof_addr);
1257
  else if (ctx->by_what == 3)
1258
    qsort(tmp_memstats, MEMPROF_HASH_BUCKETS+1, sizeof(tmp_memstats[0]), cmp_memprof_ctx);
1259
  else
1260
    qsort(tmp_memstats, MEMPROF_HASH_BUCKETS+1, sizeof(tmp_memstats[0]), cmp_memprof_stats);
1261
1262
  if (ctx->aggr) {
1263
    /* merge entries for the same caller and reset the exec_ctx */
1264
    for (i = j = 0; i < MEMPROF_HASH_BUCKETS; i++) {
1265
      if ((tmp_memstats[i].alloc_calls | tmp_memstats[i].free_calls) == 0)
1266
        continue;
1267
      for (j = i + 1; j < MEMPROF_HASH_BUCKETS; j++) {
1268
        if ((tmp_memstats[j].alloc_calls | tmp_memstats[j].free_calls) == 0)
1269
          continue;
1270
        if (tmp_memstats[j].caller != tmp_memstats[i].caller ||
1271
            tmp_memstats[j].method != tmp_memstats[i].method ||
1272
            tmp_memstats[j].info   != tmp_memstats[i].info)
1273
          continue;
1274
        tmp_memstats[i].locked_calls  += tmp_memstats[j].locked_calls;
1275
        tmp_memstats[i].alloc_calls   += tmp_memstats[j].alloc_calls;
1276
        tmp_memstats[i].free_calls    += tmp_memstats[j].free_calls;
1277
        tmp_memstats[i].alloc_tot     += tmp_memstats[j].alloc_tot;
1278
        tmp_memstats[i].free_tot      += tmp_memstats[j].free_tot;
1279
        /* don't dump the ctx */
1280
        tmp_memstats[i].exec_ctx.type = 0;
1281
        /* don't dump the merged entry */
1282
        tmp_memstats[j].alloc_calls = tmp_memstats[j].free_calls = 0;
1283
      }
1284
    }
1285
  }
1286
1287
 memstats_resume:
1288
  if (!ctx->linenum)
1289
    chunk_appendf(&trash,
1290
                  "Alloc/Free statistics by call place over %.3f sec till %.3f sec ago:\n"
1291
                  "         Calls         |         Tot Bytes           |       Caller, method, extra info\n"
1292
                  "<- alloc -> <- free  ->|<-- alloc ---> <-- free ---->|\n",
1293
            (prof_mem_start_ns ? (prof_mem_stop_ns ? prof_mem_stop_ns : now_ns) - prof_mem_start_ns : 0) / 1000000000.0,
1294
            (prof_mem_stop_ns ? now_ns - prof_mem_stop_ns : 0) / 1000000000.0);
1295
1296
  max_lines = ctx->maxcnt;
1297
  if (!max_lines)
1298
    max_lines = MEMPROF_HASH_BUCKETS + 1;
1299
1300
  dumped = 0;
1301
  for (i = ctx->linenum; i < max_lines; i++) {
1302
    struct memprof_stats *entry = &tmp_memstats[i];
1303
1304
    ctx->linenum = i;
1305
    if (!entry->alloc_calls && !entry->free_calls)
1306
      continue;
1307
1308
    /* resolve_sym_name() may be slow, better dump a few entries at a time */
1309
    if (dumped >= 10)
1310
      return 0;
1311
1312
    chunk_appendf(&trash, "%11llu %11llu %14llu %14llu| %16p ",
1313
            entry->alloc_calls, entry->free_calls,
1314
            entry->alloc_tot, entry->free_tot,
1315
            entry->caller);
1316
1317
    if (entry->caller)
1318
      resolve_sym_name(&trash, NULL, entry->caller);
1319
    else
1320
      chunk_appendf(&trash, "[other]");
1321
1322
    if (((1UL << tmp_memstats[i].method) & MEMPROF_FREE_MASK) || !entry->alloc_calls) {
1323
      chunk_appendf(&trash," %s(%lld)", memprof_methods[entry->method],
1324
        (long long)(entry->alloc_tot - entry->free_tot) / (long long)(entry->alloc_calls + entry->free_calls));
1325
    } else
1326
      chunk_appendf(&trash," %s(%lld)", memprof_methods[entry->method],
1327
        (long long)(entry->alloc_tot) / (long long)(entry->alloc_calls));
1328
1329
    if (entry->alloc_tot && entry->free_tot) {
1330
      /* that's a realloc, show the total diff to help spot leaks */
1331
      chunk_appendf(&trash," [delta=%lld]", (long long)(entry->alloc_tot - entry->free_tot));
1332
    }
1333
1334
    if (entry->info) {
1335
      /* that's a pool name */
1336
      const struct pool_head *pool = entry->info;
1337
      chunk_appendf(&trash," [pool=%s]", pool->name);
1338
    }
1339
1340
    if (entry->locked_calls) {
1341
      unsigned long long tot_calls = entry->alloc_calls + entry->free_calls;
1342
1343
      chunk_appendf(&trash," [locked=%llu (%d.%1d %%)]",
1344
              entry->locked_calls,
1345
              (int)(100ULL * entry->locked_calls / tot_calls),
1346
              (int)((1000ULL * entry->locked_calls / tot_calls) % 10));
1347
    }
1348
1349
    chunk_append_thread_ctx(&trash, &entry->exec_ctx, " [via ", "]");
1350
    chunk_appendf(&trash, "\n");
1351
1352
    if (applet_putchk(appctx, &trash) == -1)
1353
      return 0;
1354
1355
    dumped++;
1356
  }
1357
1358
  if (applet_putchk(appctx, &trash) == -1)
1359
    return 0;
1360
1361
  tot_alloc_calls = tot_free_calls = tot_alloc_bytes = tot_free_bytes = 0;
1362
  for (i = 0; i < max_lines; i++) {
1363
    tot_alloc_calls += tmp_memstats[i].alloc_calls;
1364
    tot_alloc_bytes += tmp_memstats[i].alloc_tot;
1365
    if ((1UL << tmp_memstats[i].method) & MEMPROF_FREE_MASK) {
1366
      tot_free_calls  += tmp_memstats[i].free_calls;
1367
      tot_free_bytes  += tmp_memstats[i].free_tot;
1368
    }
1369
  }
1370
1371
  /* last step: summarize by DSO. We create one entry per new DSO in
1372
   * tmp_memstats, which is thus destroyed. The DSO's name is allocated
1373
   * and stored into tmp_stats.info. Must be freed at the end. We store
1374
   * <max> dso entries total. There are very few so we do that in a single
1375
   * pass and append it after the total.
1376
   */
1377
  for (i = max = 0; i < max_lines; i++) {
1378
    struct memprof_stats *entry = &tmp_memstats[i];
1379
1380
    if (!entry->alloc_calls && !entry->free_calls)
1381
      continue;
1382
1383
    chunk_reset(name_buffer);
1384
    if (!entry->caller)
1385
      chunk_printf(name_buffer, "other");
1386
    else
1387
      resolve_dso_name(name_buffer, "", entry->caller);
1388
1389
    /* look it up among known names (0..max) */
1390
    for (j = 0; j < max; j++) {
1391
      if (tmp_memstats[j].info && strcmp(name_buffer->area, tmp_memstats[j].info) == 0)
1392
        break;
1393
    }
1394
1395
    if (j == max) {
1396
      /* not found, create a new entry at <j>. We need to be
1397
       * careful as it could be the same as <entry> (i)!
1398
       */
1399
      max++;
1400
1401
      if (j != i) // set max to keep min caller's address
1402
        tmp_memstats[j].caller = (void*)-1;
1403
1404
      tmp_memstats[j].info = strdup(name_buffer->area);   // may fail, but checked when used
1405
      tmp_memstats[j].alloc_calls = entry->alloc_calls;
1406
      tmp_memstats[j].alloc_tot   = entry->alloc_tot;
1407
      if ((1UL << entry->method) & MEMPROF_FREE_MASK) {
1408
        tmp_memstats[j].free_calls  = entry->free_calls;
1409
        tmp_memstats[j].free_tot    = entry->free_tot;
1410
      } else {
1411
        tmp_memstats[j].free_calls  = 0;
1412
        tmp_memstats[j].free_tot    = 0;
1413
      }
1414
    } else {
1415
      tmp_memstats[j].alloc_calls += entry->alloc_calls;
1416
      tmp_memstats[j].alloc_tot += entry->alloc_tot;
1417
      if ((1UL << entry->method) & MEMPROF_FREE_MASK) {
1418
        tmp_memstats[j].free_calls  += entry->free_calls;
1419
        tmp_memstats[j].free_tot  += entry->free_tot;
1420
      }
1421
    }
1422
1423
    if (entry->caller &&
1424
        tmp_memstats[j].caller > entry->caller)
1425
      tmp_memstats[j].caller = entry->caller; // keep lowest address
1426
  }
1427
1428
  /* now we have entries 0..max-1 that are filled with per-DSO stats. This is
1429
   * compact enough to fit next to the total line in one buffer, hence no
1430
   * state kept.
1431
   */
1432
  chunk_appendf(&trash,
1433
                "-----------------------|-----------------------------| "
1434
          " - min caller - | -- by DSO below --\n");
1435
1436
  for (i = 0; i < max; i++) {
1437
    struct memprof_stats *entry = &tmp_memstats[i];
1438
1439
    chunk_appendf(&trash, "%11llu %11llu %14llu %14llu| %16p DSO:%s;",
1440
            entry->alloc_calls, entry->free_calls,
1441
            entry->alloc_tot, entry->free_tot,
1442
            entry->caller == (void*)-1 ? 0 : entry->caller, entry->info ? (const char*)entry->info : "other");
1443
1444
    if (entry->alloc_tot != entry->free_tot)
1445
      chunk_appendf(&trash, " delta_calls=%lld; delta_bytes=%lld",
1446
              (long long)(entry->alloc_calls - entry->free_calls),
1447
              (long long)(entry->alloc_tot - entry->free_tot));
1448
    chunk_appendf(&trash, "\n");
1449
  }
1450
1451
  chunk_appendf(&trash,
1452
                "-----------------------|-----------------------------|\n"
1453
          "%11llu %11llu %14llu %14llu| <- Total; Delta_calls=%lld; Delta_bytes=%lld\n",
1454
          tot_alloc_calls, tot_free_calls,
1455
          tot_alloc_bytes, tot_free_bytes,
1456
          tot_alloc_calls - tot_free_calls,
1457
          tot_alloc_bytes - tot_free_bytes);
1458
1459
  /* release optional buffer name */
1460
  for (i = 0; i < max; i++)
1461
    ha_free(&tmp_memstats[i].info);
1462
1463
  if (applet_putchk(appctx, &trash) == -1)
1464
    return 0;
1465
1466
 end_memstats:
1467
  ha_free(&ctx->tmp_memstats);
1468
  ctx->linenum = 0; // reset first line to dump
1469
  if ((ctx->dump_step & 4) == 0)
1470
    ctx->dump_step++; // next step
1471
1472
 skip_mem:
1473
#endif // USE_MEMORY_PROFILING
1474
1475
0
  return 1;
1476
0
}
1477
1478
/* release structs allocated by "show profiling" */
1479
static void cli_release_show_profiling(struct appctx *appctx)
1480
0
{
1481
0
  struct show_prof_ctx *ctx = appctx->svcctx;
1482
1483
0
  ha_free(&ctx->tmp_activity);
1484
0
  ha_free(&ctx->tmp_memstats);
1485
0
}
1486
1487
/* parse a "show profiling" command. It returns 1 on failure, 0 if it starts to dump.
1488
 *  - cli.i0 is set to the first state (0=all, 4=status, 5=tasks, 6=memory)
1489
 *  - cli.o1 is set to 1 if the output must be sorted by addr instead of usage
1490
 *  - cli.o0 is set to the number of lines of output
1491
 */
1492
static int cli_parse_show_profiling(char **args, char *payload, struct appctx *appctx, void *private)
1493
0
{
1494
0
  struct show_prof_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
1495
0
  int arg;
1496
1497
0
  if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
1498
0
    return 1;
1499
1500
0
  for (arg = 2; *args[arg]; arg++) {
1501
0
    if (strcmp(args[arg], "all") == 0) {
1502
0
      ctx->dump_step = 0; // will cycle through 0,1,2; default
1503
0
    }
1504
0
    else if (strcmp(args[arg], "status") == 0) {
1505
0
      ctx->dump_step = 4; // will visit status only
1506
0
    }
1507
0
    else if (strcmp(args[arg], "tasks") == 0) {
1508
0
      ctx->dump_step = 5; // will visit tasks only
1509
0
    }
1510
0
    else if (strcmp(args[arg], "memory") == 0) {
1511
0
      ctx->dump_step = 6; // will visit memory only
1512
0
    }
1513
0
    else if (strcmp(args[arg], "byaddr") == 0) {
1514
0
      ctx->by_what = 1; // sort output by address instead of usage
1515
0
    }
1516
0
    else if (strcmp(args[arg], "bytime") == 0) {
1517
0
      ctx->by_what = 2; // sort output by total time instead of usage
1518
0
    }
1519
0
    else if (strcmp(args[arg], "byctx") == 0) {
1520
0
      ctx->by_what = 3; // sort output by caller context instead of usage
1521
0
    }
1522
0
    else if (strcmp(args[arg], "aggr") == 0) {
1523
0
      ctx->aggr = 1;    // aggregate output by callee
1524
0
    }
1525
0
    else if (isdigit((unsigned char)*args[arg])) {
1526
0
      ctx->maxcnt = atoi(args[arg]); // number of entries to dump
1527
0
    }
1528
0
    else
1529
0
      return cli_err(appctx, "Expects either 'all', 'status', 'tasks', 'memory', 'byaddr', 'bytime', 'byctx', 'aggr' or a max number of output lines.\n");
1530
0
  }
1531
0
  return 0;
1532
0
}
1533
1534
/* This function scans all threads' run queues and collects statistics about
1535
 * running tasks. It returns 0 if the output buffer is full and it needs to be
1536
 * called again, otherwise non-zero.
1537
 */
1538
static int cli_io_handler_show_tasks(struct appctx *appctx)
1539
0
{
1540
0
  struct sched_activity tmp_activity[SCHED_ACT_HASH_BUCKETS];
1541
0
  struct buffer *name_buffer = get_trash_chunk();
1542
0
  struct sched_activity *entry;
1543
0
  const struct tasklet *tl;
1544
0
  const struct task *t;
1545
0
  uint64_t now_ns, lat;
1546
0
  struct eb32_node *rqnode;
1547
0
  uint64_t tot_calls, tot_cpu;
1548
0
  int thr, queue;
1549
0
  int i, max;
1550
1551
  /* It's not possible to scan queues in small chunks and yield in the
1552
   * middle of the dump and come back again. So what we're doing instead
1553
   * is to freeze all threads and inspect their queues at once as fast as
1554
   * possible, using a sched_activity array to collect metrics with
1555
   * limited collision, then we'll report statistics only. The tasks'
1556
   * #calls will reflect the number of occurrences, and the lat_time will
1557
   * reflect the latency when set. We prefer to take the time before
1558
   * calling thread_isolate() so that the wait time doesn't impact the
1559
   * measurement accuracy. However this requires to take care of negative
1560
   * times since tasks might be queued after we retrieve it. The cpu_time
1561
   * will store the total number of calls per task, allowing to sort out
1562
   * the most vs least busy ones.
1563
   */
1564
1565
0
  now_ns = now_mono_time();
1566
0
  memset(tmp_activity, 0, sizeof(tmp_activity));
1567
1568
0
  thread_isolate();
1569
1570
  /* 1. global run queue */
1571
1572
#ifdef USE_THREAD
1573
  for (thr = 0; thr < global.nbthread; thr++) {
1574
    /* task run queue */
1575
    rqnode = eb32_first(&ha_thread_ctx[thr].rqueue_shared);
1576
    while (rqnode) {
1577
      t = eb32_entry(rqnode, struct task, rq);
1578
      entry = sched_activity_entry(tmp_activity, t->process, NULL);
1579
      if (t->wake_date) {
1580
        lat = (uint32_t)now_ns - t->wake_date;
1581
        if ((int64_t)lat > 0)
1582
          entry->lat_time += lat;
1583
      }
1584
      entry->cpu_time += t->calls;
1585
      entry->calls++;
1586
      rqnode = eb32_next(rqnode);
1587
    }
1588
  }
1589
#endif
1590
  /* 2. all threads's local run queues */
1591
0
  for (thr = 0; thr < global.nbthread; thr++) {
1592
    /* task run queue */
1593
0
    rqnode = eb32_first(&ha_thread_ctx[thr].rqueue);
1594
0
    while (rqnode) {
1595
0
      t = eb32_entry(rqnode, struct task, rq);
1596
0
      entry = sched_activity_entry(tmp_activity, t->process, NULL);
1597
0
      if (t->wake_date) {
1598
0
        lat = (uint32_t)now_ns - t->wake_date;
1599
0
        if ((int64_t)lat > 0)
1600
0
          entry->lat_time += lat;
1601
0
      }
1602
0
      entry->cpu_time += t->calls;
1603
0
      entry->calls++;
1604
0
      rqnode = eb32_next(rqnode);
1605
0
    }
1606
1607
    /* shared tasklet list */
1608
0
    list_for_each_entry(tl, mt_list_to_list(&ha_thread_ctx[thr].shared_tasklet_list), list) {
1609
0
      t = (const struct task *)tl;
1610
0
      entry = sched_activity_entry(tmp_activity, t->process, NULL);
1611
0
      if (!TASK_IS_TASKLET(t) && t->wake_date) {
1612
0
        lat = (uint32_t)now_ns - t->wake_date;
1613
0
        if ((int64_t)lat > 0)
1614
0
          entry->lat_time += lat;
1615
0
      }
1616
0
      entry->cpu_time += t->calls;
1617
0
      entry->calls++;
1618
0
    }
1619
1620
    /* classful tasklets */
1621
0
    for (queue = 0; queue < TL_CLASSES; queue++) {
1622
0
      list_for_each_entry(tl, &ha_thread_ctx[thr].tasklets[queue], list) {
1623
0
        t = (const struct task *)tl;
1624
0
        entry = sched_activity_entry(tmp_activity, t->process, NULL);
1625
0
        if (!TASK_IS_TASKLET(t) && t->wake_date) {
1626
0
          lat = (uint32_t)now_ns - t->wake_date;
1627
0
          if ((int64_t)lat > 0)
1628
0
            entry->lat_time += lat;
1629
0
        }
1630
0
        entry->cpu_time += t->calls;
1631
0
        entry->calls++;
1632
0
      }
1633
0
    }
1634
0
  }
1635
1636
  /* hopefully we're done */
1637
0
  thread_release();
1638
1639
0
  chunk_reset(&trash);
1640
1641
0
  tot_calls = tot_cpu = 0;
1642
0
  for (i = 0; i < SCHED_ACT_HASH_BUCKETS; i++) {
1643
0
    tot_calls += tmp_activity[i].calls;
1644
0
    tot_cpu += tmp_activity[i].cpu_time;
1645
0
  }
1646
0
  tot_cpu = tot_cpu ? tot_cpu : 1; // prepare for the divide
1647
1648
0
  qsort(tmp_activity, SCHED_ACT_HASH_BUCKETS, sizeof(tmp_activity[0]), cmp_sched_activity_calls);
1649
1650
0
  chunk_appendf(&trash, "Running tasks: %d (%d threads)\n"
1651
0
          "  function                     places     %%    lat_tot   lat_avg  calls_tot  calls_avg calls%%\n",
1652
0
          (int)tot_calls, global.nbthread);
1653
1654
0
  for (i = 0; i < SCHED_ACT_HASH_BUCKETS && tmp_activity[i].calls; i++) {
1655
0
    chunk_reset(name_buffer);
1656
1657
0
    if (!tmp_activity[i].func)
1658
0
      chunk_printf(name_buffer, "other");
1659
0
    else
1660
0
      resolve_sym_name(name_buffer, "", tmp_activity[i].func);
1661
1662
    /* reserve 35 chars for name+' '+#calls, knowing that longer names
1663
     * are often used for less often called functions.
1664
     */
1665
0
    max = 35 - name_buffer->data;
1666
0
    if (max < 1)
1667
0
      max = 1;
1668
0
    chunk_appendf(&trash, "  %s%*llu  %3d.%1d",
1669
0
                  name_buffer->area, max, (unsigned long long)tmp_activity[i].calls,
1670
0
                  (int)(100ULL * tmp_activity[i].calls / tot_calls),
1671
0
                  (int)((1000ULL * tmp_activity[i].calls / tot_calls)%10));
1672
0
    print_time_short(&trash, "   ", tmp_activity[i].lat_time, "");
1673
0
    print_time_short(&trash, "   ", tmp_activity[i].lat_time / tmp_activity[i].calls, "");
1674
0
    chunk_appendf(&trash, " %10llu %10llu  %3d.%1d\n",
1675
0
            (ullong)tmp_activity[i].cpu_time, (ullong)tmp_activity[i].cpu_time / tmp_activity[i].calls,
1676
0
                  (int)(100ULL * tmp_activity[i].cpu_time / tot_cpu),
1677
0
                  (int)((1000ULL * tmp_activity[i].cpu_time / tot_cpu)%10));
1678
0
  }
1679
1680
0
  if (applet_putchk(appctx, &trash) == -1) {
1681
    /* failed, try again */
1682
0
    return 0;
1683
0
  }
1684
0
  return 1;
1685
0
}
1686
1687
/* This function dumps some activity counters used by developers and support to
1688
 * rule out some hypothesis during bug reports. It returns 0 if the output
1689
 * buffer is full and it needs to be called again, otherwise non-zero. It dumps
1690
 * everything at once in the buffer and is not designed to do it in multiple
1691
 * passes.
1692
 */
1693
static int cli_io_handler_show_activity(struct appctx *appctx)
1694
0
{
1695
0
  struct show_activity_ctx *actctx = appctx->svcctx;
1696
0
  int tgt = actctx->thr; // target thread, -1 for all, 0 for total only
1697
0
  uint up_sec, up_usec;
1698
0
  int base_line;
1699
0
  ullong up;
1700
1701
  /* this macro is used below to dump values. The thread number is "thr",
1702
   * and runs from 0 to nbt-1 when values are printed using the formula.
1703
   * We normally try to dmup integral lines in order to keep counters
1704
   * consistent. If we fail once on a line, we'll detect it next time
1705
   * because we'll have committed actctx->col=1 thanks to the header
1706
   * always being dumped individually. We'll be called again thanks to
1707
   * the header being present, leaving some data in the buffer. In this
1708
   * case once we restart we'll proceed one column at a time to make sure
1709
   * we don't overflow the buffer again.
1710
   */
1711
0
#undef SHOW_VAL
1712
0
#define SHOW_VAL(header, x, formula)          \
1713
0
  do {               \
1714
0
    unsigned int _v[MAX_THREADS];       \
1715
0
    unsigned int _tot;          \
1716
0
    const int _nbt = global.nbthread;     \
1717
0
    int restarted = actctx->col > 0;      \
1718
0
    int thr;            \
1719
0
    _tot = thr = 0;           \
1720
0
    do {             \
1721
0
      _tot += _v[thr] = (x);        \
1722
0
    } while (++thr < _nbt);          \
1723
0
    for (thr = actctx->col - 2; thr <= _nbt; thr++) { \
1724
0
      if (thr == -2) {       \
1725
        /* line header */      \
1726
0
        chunk_appendf(&trash, "%s", header);  \
1727
0
      }           \
1728
0
      else if (thr == -1) {       \
1729
        /* aggregate value only for multi-thread: all & 0 */ \
1730
0
        if (_nbt > 1 && tgt <= 0)   \
1731
0
          chunk_appendf(&trash, " %u%s",  \
1732
0
                  (formula),  \
1733
0
                  (tgt < 0) ?  \
1734
0
                  " [" : "");  \
1735
0
      }           \
1736
0
      else if (thr < _nbt) {       \
1737
        /* individual value only for all or exact value */ \
1738
0
        if (tgt == -1 || tgt == thr+1)   \
1739
0
          chunk_appendf(&trash, " %u",  \
1740
0
                  _v[thr]);   \
1741
0
      }           \
1742
0
      else /* thr == _nbt */ {     \
1743
0
        chunk_appendf(&trash, "%s\n",   \
1744
0
                (_nbt > 1 && tgt < 0) ? \
1745
0
                " ]" : "");    \
1746
0
      }           \
1747
0
      if (thr == -2 || restarted) {     \
1748
        /* failed once, emit one column at a time */\
1749
0
        if (applet_putchk(appctx, &trash) == -1) \
1750
0
          break; /* main loop handles it */ \
1751
0
        chunk_reset(&trash);      \
1752
0
        actctx->col = thr + 3;      \
1753
0
      }            \
1754
0
    }             \
1755
0
    if (applet_putchk(appctx, &trash) == -1)   \
1756
0
      break; /* main loop will handle it */    \
1757
    /* OK dump done for this line */      \
1758
0
    chunk_reset(&trash);          \
1759
0
    if (thr > _nbt)           \
1760
0
      actctx->col = 0;       \
1761
0
  } while (0)
1762
1763
  /* retrieve uptime */
1764
0
  up = now_ns - start_time_ns;
1765
0
  up_sec = ns_to_sec(up);
1766
0
  up_usec = (up / 1000U) % 1000000U;
1767
1768
  /* iterate over all dump lines. It happily skips over holes so it's
1769
   * not a problem not to have an exact match, we just need to have
1770
   * stable and consistent lines during a dump.
1771
   */
1772
0
  base_line = __LINE__;
1773
0
  do {
1774
0
    chunk_reset(&trash);
1775
1776
0
    switch (actctx->line + base_line) {
1777
0
    case __LINE__: chunk_appendf(&trash, "thread_id: %u (%u..%u)\n", tid + 1, 1, global.nbthread); break;
1778
0
    case __LINE__: chunk_appendf(&trash, "date_now: %lu.%06lu\n", (ulong)date.tv_sec, (ulong)date.tv_usec); break;
1779
0
    case __LINE__: chunk_appendf(&trash, "uptime_now: %u.%06u\n", up_sec, up_usec); break;
1780
0
    case __LINE__: SHOW_VAL("ctxsw:",        activity[thr].ctxsw, _tot); break;
1781
0
    case __LINE__: SHOW_VAL("tasksw:",       activity[thr].tasksw, _tot); break;
1782
0
    case __LINE__: SHOW_VAL("empty_rq:",     activity[thr].empty_rq, _tot); break;
1783
0
    case __LINE__: SHOW_VAL("long_rq:",      activity[thr].long_rq, _tot); break;
1784
0
    case __LINE__: SHOW_VAL("curr_rq:",      _HA_ATOMIC_LOAD(&ha_thread_ctx[thr].rq_total), _tot); break;
1785
0
    case __LINE__: SHOW_VAL("loops:",        activity[thr].loops, _tot); break;
1786
0
    case __LINE__: SHOW_VAL("wake_tasks:",   activity[thr].wake_tasks, _tot); break;
1787
0
    case __LINE__: SHOW_VAL("wake_signal:",  activity[thr].wake_signal, _tot); break;
1788
0
    case __LINE__: SHOW_VAL("poll_io:",      activity[thr].poll_io, _tot); break;
1789
0
    case __LINE__: SHOW_VAL("poll_exp:",     activity[thr].poll_exp, _tot); break;
1790
0
    case __LINE__: SHOW_VAL("poll_drop_fd:", activity[thr].poll_drop_fd, _tot); break;
1791
0
    case __LINE__: SHOW_VAL("poll_skip_fd:", activity[thr].poll_skip_fd, _tot); break;
1792
0
    case __LINE__: SHOW_VAL("conn_dead:",    activity[thr].conn_dead, _tot); break;
1793
0
    case __LINE__: SHOW_VAL("stream_calls:", activity[thr].stream_calls, _tot); break;
1794
0
    case __LINE__: SHOW_VAL("pool_fail:",    activity[thr].pool_fail, _tot); break;
1795
0
    case __LINE__: SHOW_VAL("buf_wait:",     activity[thr].buf_wait, _tot); break;
1796
0
    case __LINE__: SHOW_VAL("cpust_ms_tot:", activity[thr].cpust_total / 2, _tot); break;
1797
0
    case __LINE__: SHOW_VAL("cpust_ms_1s:",  read_freq_ctr(&activity[thr].cpust_1s) / 2, _tot); break;
1798
0
    case __LINE__: SHOW_VAL("cpust_ms_15s:", read_freq_ctr_period(&activity[thr].cpust_15s, 15000) / 2, _tot); break;
1799
0
    case __LINE__: SHOW_VAL("avg_cpu_pct:",  (100 - ha_thread_ctx[thr].idle_pct), (_tot + _nbt/2) / _nbt); break;
1800
0
    case __LINE__: SHOW_VAL("avg_loop_us:",  swrate_avg(activity[thr].avg_loop_us, TIME_STATS_SAMPLES), (_tot + _nbt/2) / _nbt); break;
1801
0
    case __LINE__: SHOW_VAL("accepted:",     activity[thr].accepted, _tot); break;
1802
0
    case __LINE__: SHOW_VAL("accq_pushed:",  activity[thr].accq_pushed, _tot); break;
1803
0
    case __LINE__: SHOW_VAL("accq_full:",    activity[thr].accq_full, _tot); break;
1804
#ifdef USE_THREAD
1805
    case __LINE__: SHOW_VAL("accq_ring:",    accept_queue_ring_len(&accept_queue_rings[thr]), _tot); break;
1806
    case __LINE__: SHOW_VAL("fd_takeover:",  activity[thr].fd_takeover, _tot); break;
1807
    case __LINE__: SHOW_VAL("check_adopted:",activity[thr].check_adopted, _tot); break;
1808
#endif
1809
0
    case __LINE__: SHOW_VAL("check_started:",activity[thr].check_started, _tot); break;
1810
0
    case __LINE__: SHOW_VAL("check_active:", _HA_ATOMIC_LOAD(&ha_thread_ctx[thr].active_checks), _tot); break;
1811
0
    case __LINE__: SHOW_VAL("check_running:",_HA_ATOMIC_LOAD(&ha_thread_ctx[thr].running_checks), _tot); break;
1812
1813
#if defined(DEBUG_DEV)
1814
      /* keep these ones at the end */
1815
    case __LINE__: SHOW_VAL("ctr0:",         activity[thr].ctr0, _tot); break;
1816
    case __LINE__: SHOW_VAL("ctr1:",         activity[thr].ctr1, _tot); break;
1817
    case __LINE__: SHOW_VAL("ctr2:",         activity[thr].ctr2, _tot); break;
1818
#endif
1819
0
    }
1820
0
#undef SHOW_VAL
1821
1822
    /* try to dump what was possibly not dumped yet */
1823
1824
0
    if (applet_putchk(appctx, &trash) == -1) {
1825
      /* buffer full, retry later */
1826
0
      return 0;
1827
0
    }
1828
    /* line was dumped, let's commit it */
1829
0
    actctx->line++;
1830
0
  } while (actctx->line + base_line < __LINE__);
1831
1832
  /* dump complete */
1833
0
  return 1;
1834
0
}
1835
1836
/* parse a "show activity" CLI request. Returns 0 if it needs to continue, 1 if it
1837
 * wants to stop here. It sets a show_activity_ctx context where, if a specific
1838
 * thread is requested, it puts the thread number into ->thr otherwise sets it to
1839
 * -1.
1840
 */
1841
static int cli_parse_show_activity(char **args, char *payload, struct appctx *appctx, void *private)
1842
0
{
1843
0
  struct show_activity_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
1844
1845
0
  if (!cli_has_level(appctx, ACCESS_LVL_OPER))
1846
0
    return 1;
1847
1848
0
  ctx->thr = -1; // show all by default
1849
0
  if (*args[2])
1850
0
    ctx->thr = atoi(args[2]);
1851
1852
0
  if (ctx->thr < -1 || ctx->thr > global.nbthread)
1853
0
    return cli_err(appctx, "Thread ID number must be between -1 and nbthread\n");
1854
1855
0
  return 0;
1856
0
}
1857
1858
/* config keyword parsers */
1859
static struct cfg_kw_list cfg_kws = {ILH, {
1860
#ifdef USE_MEMORY_PROFILING
1861
  { CFG_GLOBAL, "profiling.memory",     cfg_parse_prof_memory     },
1862
#endif
1863
  { CFG_GLOBAL, "profiling.tasks",      cfg_parse_prof_tasks      },
1864
  { 0, NULL, NULL }
1865
}};
1866
1867
INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
1868
1869
/* register cli keywords */
1870
static struct cli_kw_list cli_kws = {{ },{
1871
  { { "set",  "profiling", NULL }, "set profiling <what> {auto|on|off}      : enable/disable resource profiling (tasks,memory)", cli_parse_set_profiling,  NULL },
1872
  { { "show", "activity", NULL },  "show activity [-1|0|thread_num]         : show per-thread activity stats (for support/developers)", cli_parse_show_activity, cli_io_handler_show_activity, NULL },
1873
  { { "show", "profiling", NULL }, "show profiling [<what>|<#lines>|<opts>]*: show profiling state (all,status,tasks,memory)",   cli_parse_show_profiling, cli_io_handler_show_profiling, cli_release_show_profiling },
1874
  { { "show", "tasks", NULL },     "show tasks                              : show running tasks",                               NULL, cli_io_handler_show_tasks,     NULL },
1875
  {{},}
1876
}};
1877
1878
INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);