|  | /* 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, | 
|  | }; |