/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); |