blob: 8ea218951456dd6f21a85b9bf200ffb94e5bb3aa [file] [log] [blame]
/* Copyright (c) 2016 Google Inc
* Barret Rhoden <brho@cs.berkeley.edu>
* See LICENSE for details.
*
* #mem, memory diagnostics (arenas and slabs)
*/
#include <ns.h>
#include <kmalloc.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <error.h>
#include <syscall.h>
#include <sys/queue.h>
struct dev mem_devtab;
static char *devname(void)
{
return mem_devtab.name;
}
enum {
Qdir,
Qarena_stats,
Qslab_stats,
Qfree,
Qkmemstat,
Qslab_trace,
};
static struct dirtab mem_dir[] = {
{".", {Qdir, 0, QTDIR}, 0, DMDIR | 0555},
{"arena_stats", {Qarena_stats, 0, QTFILE}, 0, 0444},
{"slab_stats", {Qslab_stats, 0, QTFILE}, 0, 0444},
{"free", {Qfree, 0, QTFILE}, 0, 0444},
{"kmemstat", {Qkmemstat, 0, QTFILE}, 0, 0444},
{"slab_trace", {Qslab_trace, 0, QTFILE}, 0, 0444},
};
/* Protected by the arenas_and_slabs_lock */
static struct sized_alloc *slab_trace_data;
static struct chan *mem_attach(char *spec)
{
return devattach(devname(), spec);
}
static struct walkqid *mem_walk(struct chan *c, struct chan *nc, char **name,
unsigned int nname)
{
return devwalk(c, nc, name, nname, mem_dir, ARRAY_SIZE(mem_dir),
devgen);
}
static size_t mem_stat(struct chan *c, uint8_t *db, size_t n)
{
return devstat(c, db, n, mem_dir, ARRAY_SIZE(mem_dir), devgen);
}
/* Prints arena's stats to the sza, adjusting the sza's sofar. */
static void fetch_arena_stats(struct arena *arena, struct sized_alloc *sza)
{
struct btag *bt_i;
struct rb_node *rb_i;
struct arena *a_i;
struct kmem_cache *kc_i;
size_t nr_allocs = 0;
size_t nr_imports = 0;
size_t amt_alloc = 0;
size_t amt_free = 0;
size_t amt_imported = 0;
size_t empty_hash_chain = 0;
size_t longest_hash_chain = 0;
sza_printf(sza, "Arena: %s (%p)\n--------------\n", arena->name, arena);
sza_printf(sza, "\tquantum: %d, qcache_max: %d\n", arena->quantum,
arena->qcache_max);
sza_printf(sza, "\tsource: %s\n",
arena->source ? arena->source->name : "none");
spin_lock_irqsave(&arena->lock);
for (int i = 0; i < ARENA_NR_FREE_LISTS; i++) {
int j = 0;
if (!BSD_LIST_EMPTY(&arena->free_segs[i])) {
sza_printf(sza, "\tList of [2^%d - 2^%d):\n", i, i + 1);
BSD_LIST_FOREACH(bt_i, &arena->free_segs[i], misc_link)
j++;
sza_printf(sza, "\t\tNr free segs: %d\n", j);
}
}
for (int i = 0; i < arena->hh.nr_hash_lists; i++) {
int j = 0;
if (BSD_LIST_EMPTY(&arena->alloc_hash[i]))
empty_hash_chain++;
BSD_LIST_FOREACH(bt_i, &arena->alloc_hash[i], misc_link)
j++;
longest_hash_chain = MAX(longest_hash_chain, j);
}
sza_printf(sza, "\tSegments:\n\t--------------\n");
for (rb_i = rb_first(&arena->all_segs); rb_i; rb_i = rb_next(rb_i)) {
bt_i = container_of(rb_i, struct btag, all_link);
if (bt_i->status == BTAG_SPAN) {
nr_imports++;
amt_imported += bt_i->size;
}
if (bt_i->status == BTAG_FREE)
amt_free += bt_i->size;
if (bt_i->status == BTAG_ALLOC) {
nr_allocs++;
amt_alloc += bt_i->size;
}
}
sza_printf(sza, "\tStats:\n\t-----------------\n");
sza_printf(sza, "\t\tAmt free: %llu (%p)\n", amt_free, amt_free);
sza_printf(sza, "\t\tAmt alloc: %llu (%p), nr allocs %d\n",
amt_alloc, amt_alloc, nr_allocs);
sza_printf(sza, "\t\tAmt total segs: %llu, amt alloc segs %llu\n",
arena->amt_total_segs, arena->amt_alloc_segs);
sza_printf(sza, "\t\tAmt imported: %llu (%p), nr imports %d\n",
amt_imported, amt_imported, nr_imports);
sza_printf(sza, "\t\tNr hash %d, empty hash: %d, longest hash %d\n",
arena->hh.nr_hash_lists, empty_hash_chain,
longest_hash_chain);
spin_unlock_irqsave(&arena->lock);
sza_printf(sza, "\tImporting Arenas:\n\t-----------------\n");
TAILQ_FOREACH(a_i, &arena->__importing_arenas, import_link)
sza_printf(sza, "\t\t%s\n", a_i->name);
sza_printf(sza, "\tImporting Slabs:\n\t-----------------\n");
TAILQ_FOREACH(kc_i, &arena->__importing_slabs, import_link)
sza_printf(sza, "\t\t%s\n", kc_i->name);
}
static struct sized_alloc *build_arena_stats(void)
{
struct sized_alloc *sza;
size_t alloc_amt = 0;
struct arena *a_i;
qlock(&arenas_and_slabs_lock);
/* Rough guess about how many chars per arena we'll need. */
TAILQ_FOREACH(a_i, &all_arenas, next)
alloc_amt += 1000;
sza = sized_kzmalloc(alloc_amt, MEM_WAIT);
TAILQ_FOREACH(a_i, &all_arenas, next)
fetch_arena_stats(a_i, sza);
qunlock(&arenas_and_slabs_lock);
return sza;
}
/* Prints arena's stats to the sza, updating its sofar. */
static void fetch_slab_stats(struct kmem_cache *kc, struct sized_alloc *sza)
{
struct kmem_slab *s_i;
struct kmem_bufctl *bc_i;
size_t nr_unalloc_objs = 0;
size_t empty_hash_chain = 0;
size_t longest_hash_chain = 0;
spin_lock_irqsave(&kc->cache_lock);
sza_printf(sza, "\nKmem_cache: %s\n---------------------\n", kc->name);
sza_printf(sza, "Source: %s\n", kc->source->name);
sza_printf(sza, "Objsize (incl align): %d\n", kc->obj_size);
sza_printf(sza, "Align: %d\n", kc->align);
TAILQ_FOREACH(s_i, &kc->empty_slab_list, link) {
assert(!s_i->num_busy_obj);
nr_unalloc_objs += s_i->num_total_obj;
}
TAILQ_FOREACH(s_i, &kc->partial_slab_list, link)
nr_unalloc_objs += s_i->num_total_obj - s_i->num_busy_obj;
sza_printf(sza, "Nr unallocated in slab layer: %lu\n", nr_unalloc_objs);
sza_printf(sza, "Nr allocated from slab layer: %d\n", kc->nr_cur_alloc);
for (int i = 0; i < kc->hh.nr_hash_lists; i++) {
int j = 0;
if (BSD_LIST_EMPTY(&kc->alloc_hash[i]))
empty_hash_chain++;
BSD_LIST_FOREACH(bc_i, &kc->alloc_hash[i], link)
j++;
longest_hash_chain = MAX(longest_hash_chain, j);
}
sza_printf(sza,
"Nr hash %d, empty hash: %d, longest hash %d, loadlim %d\n",
kc->hh.nr_hash_lists, empty_hash_chain,
longest_hash_chain, kc->hh.load_limit);
spin_unlock_irqsave(&kc->cache_lock);
spin_lock_irqsave(&kc->depot.lock);
sza_printf(sza, "Depot magsize: %d\n", kc->depot.magsize);
sza_printf(sza, "Nr empty mags: %d\n", kc->depot.nr_empty);
sza_printf(sza, "Nr non-empty mags: %d\n", kc->depot.nr_not_empty);
spin_unlock_irqsave(&kc->depot.lock);
}
static struct sized_alloc *build_slab_stats(void)
{
struct sized_alloc *sza;
size_t alloc_amt = 0;
struct kmem_cache *kc_i;
qlock(&arenas_and_slabs_lock);
TAILQ_FOREACH(kc_i, &all_kmem_caches, all_kmc_link)
alloc_amt += 500;
sza = sized_kzmalloc(alloc_amt, MEM_WAIT);
TAILQ_FOREACH(kc_i, &all_kmem_caches, all_kmc_link)
fetch_slab_stats(kc_i, sza);
qunlock(&arenas_and_slabs_lock);
return sza;
}
static struct sized_alloc *build_free(void)
{
struct arena *a_i;
struct sized_alloc *sza;
size_t amt_total = 0;
size_t amt_alloc = 0;
sza = sized_kzmalloc(500, MEM_WAIT);
qlock(&arenas_and_slabs_lock);
TAILQ_FOREACH(a_i, &all_arenas, next) {
if (!a_i->is_base)
continue;
amt_total += a_i->amt_total_segs;
amt_alloc += a_i->amt_alloc_segs;
}
qunlock(&arenas_and_slabs_lock);
sza_printf(sza, "Total Memory : %15llu\n", amt_total);
sza_printf(sza, "Used Memory : %15llu\n", amt_alloc);
sza_printf(sza, "Free Memory : %15llu\n", amt_total - amt_alloc);
return sza;
}
#define KMEMSTAT_NAME 30
#define KMEMSTAT_OBJSIZE 8
#define KMEMSTAT_TOTAL 15
#define KMEMSTAT_ALLOCED 15
#define KMEMSTAT_NR_ALLOCS 12
#define KMEMSTAT_LINE_LN (8 + KMEMSTAT_NAME + KMEMSTAT_OBJSIZE + KMEMSTAT_TOTAL\
+ KMEMSTAT_ALLOCED + KMEMSTAT_NR_ALLOCS)
const char kmemstat_fmt[] = "%-*s: %c :%*llu:%*llu:%*llu:%*llu\n";
const char kmemstat_hdr_fmt[] = "%-*s:Typ:%*s:%*s:%*s:%*s\n";
static void fetch_arena_line(struct arena *arena, struct sized_alloc *sza,
int indent)
{
for (int i = 0; i < indent; i++)
sza_printf(sza, " ");
sza_printf(sza, kmemstat_fmt,
KMEMSTAT_NAME - indent * 4, arena->name,
'A',
KMEMSTAT_OBJSIZE, arena->quantum,
KMEMSTAT_TOTAL, arena->amt_total_segs,
KMEMSTAT_ALLOCED, arena->amt_alloc_segs,
KMEMSTAT_NR_ALLOCS, arena->nr_allocs_ever);
}
static void fetch_slab_line(struct kmem_cache *kc, struct sized_alloc *sza,
int indent)
{
struct kmem_pcpu_cache *pcc;
struct kmem_slab *s_i;
size_t nr_unalloc_objs = 0;
size_t nr_allocs_ever = 0;
spin_lock_irqsave(&kc->cache_lock);
TAILQ_FOREACH(s_i, &kc->empty_slab_list, link)
nr_unalloc_objs += s_i->num_total_obj;
TAILQ_FOREACH(s_i, &kc->partial_slab_list, link)
nr_unalloc_objs += s_i->num_total_obj - s_i->num_busy_obj;
nr_allocs_ever = kc->nr_direct_allocs_ever;
spin_unlock_irqsave(&kc->cache_lock);
/* Lockless peak at the pcpu state */
for (int i = 0; i < kmc_nr_pcpu_caches(); i++) {
pcc = &kc->pcpu_caches[i];
nr_allocs_ever += pcc->nr_allocs_ever;
}
for (int i = 0; i < indent; i++)
sza_printf(sza, " ");
sza_printf(sza, kmemstat_fmt,
KMEMSTAT_NAME - indent * 4, kc->name,
'S',
KMEMSTAT_OBJSIZE, kc->obj_size,
KMEMSTAT_TOTAL, kc->obj_size * (nr_unalloc_objs +
kc->nr_cur_alloc),
KMEMSTAT_ALLOCED, kc->obj_size * kc->nr_cur_alloc,
KMEMSTAT_NR_ALLOCS, nr_allocs_ever);
}
static void fetch_arena_and_kids(struct arena *arena, struct sized_alloc *sza,
int indent)
{
struct arena *a_i;
struct kmem_cache *kc_i;
fetch_arena_line(arena, sza, indent);
TAILQ_FOREACH(a_i, &arena->__importing_arenas, import_link)
fetch_arena_and_kids(a_i, sza, indent + 1);
TAILQ_FOREACH(kc_i, &arena->__importing_slabs, import_link)
fetch_slab_line(kc_i, sza, indent + 1);
}
static struct sized_alloc *build_kmemstat(void)
{
struct arena *a_i;
struct kmem_cache *kc_i;
struct sized_alloc *sza;
size_t alloc_amt = 100;
qlock(&arenas_and_slabs_lock);
TAILQ_FOREACH(a_i, &all_arenas, next)
alloc_amt += 100;
TAILQ_FOREACH(kc_i, &all_kmem_caches, all_kmc_link)
alloc_amt += 100;
sza = sized_kzmalloc(alloc_amt, MEM_WAIT);
sza_printf(sza, kmemstat_hdr_fmt,
KMEMSTAT_NAME, "Arena/Slab Name",
KMEMSTAT_OBJSIZE, "Objsize",
KMEMSTAT_TOTAL, "Total Amt",
KMEMSTAT_ALLOCED, "Alloc Amt",
KMEMSTAT_NR_ALLOCS, "Allocs Ever");
for (int i = 0; i < KMEMSTAT_LINE_LN; i++)
sza_printf(sza, "-");
sza_printf(sza, "\n");
TAILQ_FOREACH(a_i, &all_arenas, next) {
if (a_i->source)
continue;
fetch_arena_and_kids(a_i, sza, 0);
}
qunlock(&arenas_and_slabs_lock);
return sza;
}
static struct chan *mem_open(struct chan *c, int omode)
{
if (c->qid.type & QTDIR) {
if (openmode(omode) != O_READ)
error(EPERM, "Tried opening directory not read-only");
}
switch (c->qid.path) {
case Qarena_stats:
c->synth_buf = build_arena_stats();
break;
case Qslab_stats:
c->synth_buf = build_slab_stats();
break;
case Qfree:
c->synth_buf = build_free();
break;
case Qkmemstat:
c->synth_buf = build_kmemstat();
break;
}
c->mode = openmode(omode);
c->flag |= COPEN;
c->offset = 0;
return c;
}
static void mem_close(struct chan *c)
{
if (!(c->flag & COPEN))
return;
switch (c->qid.path) {
case Qarena_stats:
case Qslab_stats:
case Qfree:
case Qkmemstat:
kfree(c->synth_buf);
c->synth_buf = NULL;
break;
}
}
static size_t slab_trace_read(struct chan *c, void *ubuf, size_t n,
off64_t offset)
{
size_t ret = 0;
qlock(&arenas_and_slabs_lock);
if (slab_trace_data)
ret = readstr(offset, ubuf, n, slab_trace_data->buf);
qunlock(&arenas_and_slabs_lock);
return ret;
}
static size_t mem_read(struct chan *c, void *ubuf, size_t n, off64_t offset)
{
struct sized_alloc *sza;
switch (c->qid.path) {
case Qdir:
return devdirread(c, ubuf, n, mem_dir, ARRAY_SIZE(mem_dir),
devgen);
case Qarena_stats:
case Qslab_stats:
case Qfree:
case Qkmemstat:
sza = c->synth_buf;
return readstr(offset, ubuf, n, sza->buf);
case Qslab_trace:
return slab_trace_read(c, ubuf, n, offset);
default:
panic("Bad Qid %p!", c->qid.path);
}
return -1;
}
/* start, then stop, then print, then read to get the trace */
#define SLAB_TRACE_USAGE "start|stop|print|reset SLAB_NAME"
static void slab_trace_cmd(struct chan *c, struct cmdbuf *cb)
{
ERRSTACK(1);
struct sized_alloc *sza, *old_sza;
struct kmem_cache *kc;
if (cb->nf < 2)
error(EFAIL, SLAB_TRACE_USAGE);
qlock(&arenas_and_slabs_lock);
if (waserror()) {
qunlock(&arenas_and_slabs_lock);
nexterror();
}
TAILQ_FOREACH(kc, &all_kmem_caches, all_kmc_link)
if (!strcmp(kc->name, cb->f[1]))
break;
if (!kc)
error(ENOENT, "No such slab %s", cb->f[1]);
/* Note that the only time we have a real sza is when printing.
* Otherwise, it's NULL. We still want this to be the chan's sza, since
* the reader should get nothing back until they ask for a print. */
sza = NULL;
if (!strcmp(cb->f[0], "start")) {
if (kmem_trace_start(kc))
error(EFAIL, "Unable to trace slab %s", kc->name);
} else if (!strcmp(cb->f[0], "stop")) {
kmem_trace_stop(kc);
} else if (!strcmp(cb->f[0], "print")) {
sza = kmem_trace_print(kc);
} else if (!strcmp(cb->f[0], "reset")) {
kmem_trace_reset(kc);
} else {
error(EFAIL, SLAB_TRACE_USAGE);
}
old_sza = slab_trace_data;
slab_trace_data = sza;
qunlock(&arenas_and_slabs_lock);
poperror();
kfree(old_sza);
}
static size_t mem_write(struct chan *c, void *ubuf, size_t n, off64_t unused)
{
ERRSTACK(1);
struct cmdbuf *cb = parsecmd(ubuf, n);
if (waserror()) {
kfree(cb);
nexterror();
}
switch (c->qid.path) {
case Qslab_trace:
slab_trace_cmd(c, cb);
break;
default:
error(EFAIL, "Unable to write to %s", devname());
}
kfree(cb);
poperror();
return n;
}
struct dev mem_devtab __devtab = {
.name = "mem",
.reset = devreset,
.init = devinit,
.shutdown = devshutdown,
.attach = mem_attach,
.walk = mem_walk,
.stat = mem_stat,
.open = mem_open,
.create = devcreate,
.close = mem_close,
.read = mem_read,
.bread = devbread,
.write = mem_write,
.bwrite = devbwrite,
.remove = devremove,
.wstat = devwstat,
.power = devpower,
.chaninfo = devchaninfo,
};