ddb: Add CSV option, sorting to 'show (malloc|uma)'

Add /i option for machine-parseable CSV output.  This allows ready copy/
pasting into more sophisticated tooling outside of DDB.

Add total zone size ("Memory Use") as a new column for UMA.

For both, sort the displayed list on size (print the largest zones/types
first).  This is handy for quickly diagnosing "where has my memory gone?" at
a high level.

Submitted by:	Emily Pettigrew <Emily.Pettigrew AT isilon.com> (earlier version)
Sponsored by:	Dell EMC Isilon
This commit is contained in:
cem 2019-10-11 01:31:31 +00:00
parent 5a42f81ca8
commit 43181b339c
3 changed files with 191 additions and 58 deletions

View File

@ -60,7 +60,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd September 9, 2019
.Dd October 10, 2019
.Dt DDB 4
.Os
.Sh NAME
@ -806,11 +806,15 @@ is included in the kernel.
.It Ic show Cm locktree
.\"
.Pp
.It Ic show Cm malloc
.It Ic show Cm malloc Ns Op Li / Ns Cm i
Prints
.Xr malloc 9
memory allocator statistics.
The output format is as follows:
If the
.Cm i
modifier is specified, format output as machine-parseable comma-separated
values ("CSV").
The output columns are as follows:
.Pp
.Bl -tag -compact -offset indent -width "Requests"
.It Ic Type
@ -1076,11 +1080,15 @@ Currently, those are:
.Xr rmlock 9 .
.\"
.Pp
.It Ic show Cm uma
.It Ic show Cm uma Ns Op Li / Ns Cm i
Show UMA allocator statistics.
Output consists five columns:
If the
.Cm i
modifier is specified, format output as machine-parseable comma-separated
values ("CSV").
The output contains the following columns:
.Pp
.Bl -tag -compact -offset indent -width "Requests"
.Bl -tag -compact -offset indent -width "Total Mem"
.It Cm "Zone"
Name of the UMA zone.
The same string that was passed to
@ -1094,9 +1102,18 @@ Number of slabs being currently used.
Number of free slabs within the UMA zone.
.It Cm "Requests"
Number of allocations requests to the given zone.
.It Cm "Total Mem"
Total memory in use (either allocated or free) by a zone, in bytes.
.It Cm "XFree"
Number of free slabs within the UMA zone that were freed on a different NUMA
domain than allocated.
(The count in the
.Cm "Free"
column is inclusive of
.Cm "XFree" . )
.El
.Pp
The very same information might be gathered in the userspace
The same information might be gathered in the userspace
with the help of
.Dq Nm vmstat Fl z .
.\"

View File

@ -1205,35 +1205,90 @@ malloc_type_list(malloc_type_list_func_t *func, void *arg)
}
#ifdef DDB
DB_SHOW_COMMAND(malloc, db_show_malloc)
static int64_t
get_malloc_stats(const struct malloc_type_internal *mtip, uint64_t *allocs,
uint64_t *inuse)
{
struct malloc_type_internal *mtip;
struct malloc_type_stats *mtsp;
struct malloc_type *mtp;
uint64_t allocs, frees;
uint64_t alloced, freed;
const struct malloc_type_stats *mtsp;
uint64_t frees, alloced, freed;
int i;
db_printf("%18s %12s %12s %12s\n", "Type", "InUse", "MemUse",
"Requests");
for (mtp = kmemstatistics; mtp != NULL; mtp = mtp->ks_next) {
mtip = (struct malloc_type_internal *)mtp->ks_handle;
allocs = 0;
frees = 0;
alloced = 0;
freed = 0;
for (i = 0; i <= mp_maxid; i++) {
mtsp = zpcpu_get_cpu(mtip->mti_stats, i);
allocs += mtsp->mts_numallocs;
frees += mtsp->mts_numfrees;
alloced += mtsp->mts_memalloced;
freed += mtsp->mts_memfreed;
*allocs = 0;
frees = 0;
alloced = 0;
freed = 0;
for (i = 0; i <= mp_maxid; i++) {
mtsp = zpcpu_get_cpu(mtip->mti_stats, i);
*allocs += mtsp->mts_numallocs;
frees += mtsp->mts_numfrees;
alloced += mtsp->mts_memalloced;
freed += mtsp->mts_memfreed;
}
*inuse = *allocs - frees;
return (alloced - freed);
}
DB_SHOW_COMMAND(malloc, db_show_malloc)
{
const char *fmt_hdr, *fmt_entry;
struct malloc_type *mtp;
uint64_t allocs, inuse;
int64_t size;
/* variables for sorting */
struct malloc_type *last_mtype, *cur_mtype;
int64_t cur_size, last_size;
int ties;
if (modif[0] == 'i') {
fmt_hdr = "%s,%s,%s,%s\n";
fmt_entry = "\"%s\",%ju,%jdK,%ju\n";
} else {
fmt_hdr = "%18s %12s %12s %12s\n";
fmt_entry = "%18s %12ju %12jdK %12ju\n";
}
db_printf(fmt_hdr, "Type", "InUse", "MemUse", "Requests");
/* Select sort, largest size first. */
last_mtype = NULL;
last_size = INT64_MAX;
for (;;) {
cur_mtype = NULL;
cur_size = -1;
ties = 0;
for (mtp = kmemstatistics; mtp != NULL; mtp = mtp->ks_next) {
/*
* In the case of size ties, print out mtypes
* in the order they are encountered. That is,
* when we encounter the most recently output
* mtype, we have already printed all preceding
* ties, and we must print all following ties.
*/
if (mtp == last_mtype) {
ties = 1;
continue;
}
size = get_malloc_stats(mtp->ks_handle, &allocs,
&inuse);
if (size > cur_size && size < last_size + ties) {
cur_size = size;
cur_mtype = mtp;
}
}
db_printf("%18s %12ju %12juK %12ju\n",
mtp->ks_shortdesc, allocs - frees,
(alloced - freed + 1023) / 1024, allocs);
if (cur_mtype == NULL)
break;
size = get_malloc_stats(cur_mtype->ks_handle, &allocs, &inuse);
db_printf(fmt_entry, cur_mtype->ks_shortdesc, inuse,
howmany(size, 1024), allocs);
if (db_pager_quit)
break;
last_mtype = cur_mtype;
last_size = cur_size;
}
}

View File

@ -4341,39 +4341,100 @@ uma_dbg_free(uma_zone_t zone, uma_slab_t slab, void *item)
#endif /* INVARIANTS */
#ifdef DDB
DB_SHOW_COMMAND(uma, db_show_uma)
static int64_t
get_uma_stats(uma_keg_t kz, uma_zone_t z, uint64_t *allocs, uint64_t *used,
uint64_t *sleeps, uint64_t *xdomain, long *cachefree)
{
uma_keg_t kz;
uma_zone_t z;
uint64_t allocs, frees, sleeps, xdomain;
long cachefree;
uint64_t frees;
int i;
db_printf("%18s %8s %8s %8s %12s %8s %8s %8s\n", "Zone", "Size", "Used",
"Free", "Requests", "Sleeps", "Bucket", "XFree");
LIST_FOREACH(kz, &uma_kegs, uk_link) {
LIST_FOREACH(z, &kz->uk_zones, uz_link) {
if (kz->uk_flags & UMA_ZFLAG_INTERNAL) {
allocs = counter_u64_fetch(z->uz_allocs);
frees = counter_u64_fetch(z->uz_frees);
sleeps = z->uz_sleeps;
cachefree = 0;
} else
uma_zone_sumstat(z, &cachefree, &allocs,
&frees, &sleeps, &xdomain);
if (!((z->uz_flags & UMA_ZONE_SECONDARY) &&
(LIST_FIRST(&kz->uk_zones) != z)))
cachefree += kz->uk_free;
for (i = 0; i < vm_ndomains; i++)
cachefree += z->uz_domain[i].uzd_nitems;
if (kz->uk_flags & UMA_ZFLAG_INTERNAL) {
*allocs = counter_u64_fetch(z->uz_allocs);
frees = counter_u64_fetch(z->uz_frees);
*sleeps = z->uz_sleeps;
*cachefree = 0;
*xdomain = 0;
} else
uma_zone_sumstat(z, cachefree, allocs, &frees, sleeps,
xdomain);
if (!((z->uz_flags & UMA_ZONE_SECONDARY) &&
(LIST_FIRST(&kz->uk_zones) != z)))
*cachefree += kz->uk_free;
for (i = 0; i < vm_ndomains; i++)
*cachefree += z->uz_domain[i].uzd_nitems;
*used = *allocs - frees;
return (((int64_t)*used + *cachefree) * kz->uk_size);
}
db_printf("%18s %8ju %8jd %8ld %12ju %8ju %8u %8ju\n",
z->uz_name, (uintmax_t)kz->uk_size,
(intmax_t)(allocs - frees), cachefree,
(uintmax_t)allocs, sleeps, z->uz_count, xdomain);
if (db_pager_quit)
return;
DB_SHOW_COMMAND(uma, db_show_uma)
{
const char *fmt_hdr, *fmt_entry;
uma_keg_t kz;
uma_zone_t z;
uint64_t allocs, used, sleeps, xdomain;
long cachefree;
/* variables for sorting */
uma_keg_t cur_keg;
uma_zone_t cur_zone, last_zone;
int64_t cur_size, last_size, size;
int ties;
/* /i option produces machine-parseable CSV output */
if (modif[0] == 'i') {
fmt_hdr = "%s,%s,%s,%s,%s,%s,%s,%s,%s\n";
fmt_entry = "\"%s\",%ju,%jd,%ld,%ju,%ju,%u,%jd,%ju\n";
} else {
fmt_hdr = "%18s %6s %7s %7s %11s %7s %7s %10s %8s\n";
fmt_entry = "%18s %6ju %7jd %7ld %11ju %7ju %7u %10jd %8ju\n";
}
db_printf(fmt_hdr, "Zone", "Size", "Used", "Free", "Requests",
"Sleeps", "Bucket", "Total Mem", "XFree");
/* Sort the zones with largest size first. */
last_zone = NULL;
last_size = INT64_MAX;
for (;;) {
cur_zone = NULL;
cur_size = -1;
ties = 0;
LIST_FOREACH(kz, &uma_kegs, uk_link) {
LIST_FOREACH(z, &kz->uk_zones, uz_link) {
/*
* In the case of size ties, print out zones
* in the order they are encountered. That is,
* when we encounter the most recently output
* zone, we have already printed all preceding
* ties, and we must print all following ties.
*/
if (z == last_zone) {
ties = 1;
continue;
}
size = get_uma_stats(kz, z, &allocs, &used,
&sleeps, &cachefree, &xdomain);
if (size > cur_size && size < last_size + ties)
{
cur_size = size;
cur_zone = z;
cur_keg = kz;
}
}
}
if (cur_zone == NULL)
break;
size = get_uma_stats(cur_keg, cur_zone, &allocs, &used,
&sleeps, &cachefree, &xdomain);
db_printf(fmt_entry, cur_zone->uz_name,
(uintmax_t)cur_keg->uk_size, (intmax_t)used, cachefree,
(uintmax_t)allocs, (uintmax_t)sleeps,
(unsigned)cur_zone->uz_count, (intmax_t)size, xdomain);
if (db_pager_quit)
return;
last_zone = cur_zone;
last_size = cur_size;
}
}