table:0x55871b5b46a0 id=stkt update=1 localupdate=0 \
commitupdate=0 syncing=0
-show pools [byname|bysize|byusage] [match <pfx>] [<nb>]
+show pools [byname|bysize|byusage] [detailed] [match <pfx>] [<nb>]
Dump the status of internal memory pools. This is useful to track memory
usage when suspecting a memory leak for example. It does exactly the same
as the SIGQUIT when running in foreground except that it does not flush the
sorted by pool name; if "bysize" is specified, it is sorted by item size in
reverse order; if "byusage" is specified, it is sorted by total usage in
reverse order, and only used entries are shown. It is also possible to limit
- the output to the <nb> first entries (e.g. when sorting by usage). Finally,
- if "match" followed by a prefix is specified, then only pools whose name
- starts with this prefix will be shown. The reported total only concerns pools
- matching the filtering criteria. Example:
+ the output to the <nb> first entries (e.g. when sorting by usage). It is
+ possible to also dump more internal details, including the list of all pools
+ that were merged together, by specifying "detailed". Finally, if "match"
+ followed by a prefix is specified, then only pools whose name starts with
+ this prefix will be shown. The reported total only concerns pools matching
+ the filtering criteria. Example:
$ socat - /tmp/haproxy.sock <<< "show pools match quic byusage"
Dumping pools usage. Use SIGQUIT to flush them.
ulong fill_pattern; /* pattern used to fill the area on free */
} THREAD_ALIGNED(64);
+/* This describes a pool registration, which is what was passed to
+ * create_pool() and that might have been merged with an existing pool.
+ */
+struct pool_registration {
+ struct list list; /* link element */
+ char name[12]; /* name of the pool */
+ unsigned int size; /* expected object size */
+ unsigned int flags; /* MEM_F_* */
+ unsigned int align; /* expected alignment; 0=unspecified */
+};
+
/* This represents one item stored in the thread-local cache. <by_pool> links
* the object to the list of objects in the pool, and <by_lru> links the object
* to the local thread's list of hottest objects. This way it's possible to
struct list list; /* list of all known pools */
void *base_addr; /* allocation address, for free() */
char name[12]; /* name of the pool */
+ struct list regs; /* registrations: alt names for this pool */
/* heavily read-write part */
THREAD_ALIGN(64);
struct pool_head *create_pool(char *name, unsigned int size, unsigned int flags)
{
unsigned int extra_mark, extra_caller, extra;
+ struct pool_registration *reg;
struct pool_head *pool;
struct pool_head *entry;
struct list *start;
unsigned int align;
int thr __maybe_unused;
+ pool = NULL;
+ reg = calloc(1, sizeof(*reg));
+ if (!reg)
+ goto fail;
+
+ strlcpy2(reg->name, name, sizeof(reg->name));
+ reg->size = size;
+ reg->flags = flags;
+ reg->align = 0;
+
extra_mark = (pool_debugging & POOL_DBG_TAG) ? POOL_EXTRA_MARK : 0;
extra_caller = (pool_debugging & POOL_DBG_CALLER) ? POOL_EXTRA_CALLER : 0;
extra = extra_mark + extra_caller;
/* TODO: thread: we do not lock pool list for now because all pools are
* created during HAProxy startup (so before threads creation) */
start = &pools;
- pool = NULL;
list_for_each_entry(entry, &pools, list) {
if (entry->size == size) {
pool_addr = calloc(1, sizeof(*pool) + __alignof__(*pool));
if (!pool_addr)
- return NULL;
+ goto fail;
/* always provide an aligned pool */
pool = (struct pool_head*)((((size_t)pool_addr) + __alignof__(*pool)) & -(size_t)__alignof__(*pool));
pool->size = size;
pool->flags = flags;
LIST_APPEND(start, &pool->list);
+ LIST_INIT(&pool->regs);
if (!(pool_debugging & POOL_DBG_NO_CACHE)) {
/* update per-thread pool cache if necessary */
}
}
}
+
+ LIST_APPEND(&pool->regs, ®->list);
pool->users++;
return pool;
+ fail:
+ free(reg);
+ return NULL;
}
/* Tries to allocate an object for the pool <pool> using the system's allocator
return pool;
pool->users--;
if (!pool->users) {
+ /* remove all registrations at once */
+ struct pool_registration *reg, *back;
+
+ list_for_each_entry_safe(reg, back, &pool->regs, list) {
+ LIST_DELETE(®->list);
+ free(reg);
+ }
+
LIST_DELETE(&pool->list);
+
/* note that if used == 0, the cache is empty */
free(pool->base_addr);
}
uint cached = 0;
uint alloc_items;
int by_what = how & 0xF; // bits 0..3 = sorting criterion
+ int detailed = !!(how & 0x10); // print details
allocated = used = nbpools = 0;
cached_bytes += pool_info[i].cached_items * (ulong)pool_info[i].entry->size;
allocated += pool_info[i].alloc_items * (ulong)pool_info[i].entry->size;
used += pool_info[i].used_items * (ulong)pool_info[i].entry->size;
+
+ if (detailed) {
+ struct pool_registration *reg;
+ list_for_each_entry(reg, &pool_info[i].entry->regs, list)
+ chunk_appendf(&trash, " > %-12s: size=%u flags=%#x align=%u\n", reg->name, reg->size, reg->flags, reg->align);
+ }
}
chunk_appendf(&trash, "Total: %d pools, %llu bytes allocated, %llu used"
else if (strcmp(args[arg], "byusage") == 0) {
ctx->how = (ctx->how & ~0xF) | 3; // sort output by total allocated size
}
+ else if (strcmp(args[arg], "detailed") == 0) {
+ ctx->how |= 0x10; // print detailed registrations
+ }
else if (strcmp(args[arg], "match") == 0 && *args[arg+1]) {
ctx->prefix = strdup(args[arg+1]); // only pools starting with this
if (!ctx->prefix)
ctx->maxcnt = atoi(args[arg]); // number of entries to dump
}
else
- return cli_err(appctx, "Expects either 'byname', 'bysize', 'byusage', 'match <pfx>', or a max number of output lines.\n");
+ return cli_err(appctx, "Expects either 'byname', 'bysize', 'byusage', 'match <pfx>', 'detailed', or a max number of output lines.\n");
}
return 0;
}