Module Name: src Committed By: riastradh Date: Mon May 30 20:28:30 UTC 2022
Modified Files: src/sys/kern: subr_kmem.c Log Message: kmem(9): Create dtrace sdt probes for each kmem cache size. The names of the probes correspond to the names shown in vmstat -m. This should make it much easier to track down who's allocating memory when there's a leak, e.g. by getting a histogram of stack traces for the matching kmem cache pool: # vmstat -m Memory resource pool statistics Name Size Requests Fail Releases Pgreq Pgrel Npage Hiwat Minpg Maxpg Idle ... kmem-00128 256 62242 0 0 3891 0 3891 3891 0 inf 0 ... # dtrace -n 'sdt:kmem:*:kmem-00128 { @[probefunc, stack()] = count() }' ^C When there's no leak, the allocs and frees (probefunc) will be roughly matched; when there's a leak, the allocs will far outnumber the frees. To generate a diff of this commit: cvs rdiff -u -r1.84 -r1.85 src/sys/kern/subr_kmem.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/kern/subr_kmem.c diff -u src/sys/kern/subr_kmem.c:1.84 src/sys/kern/subr_kmem.c:1.85 --- src/sys/kern/subr_kmem.c:1.84 Sat Mar 12 22:20:34 2022 +++ src/sys/kern/subr_kmem.c Mon May 30 20:28:30 2022 @@ -1,4 +1,4 @@ -/* $NetBSD: subr_kmem.c,v 1.84 2022/03/12 22:20:34 riastradh Exp $ */ +/* $NetBSD: subr_kmem.c,v 1.85 2022/05/30 20:28:30 riastradh Exp $ */ /* * Copyright (c) 2009-2020 The NetBSD Foundation, Inc. @@ -78,7 +78,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: subr_kmem.c,v 1.84 2022/03/12 22:20:34 riastradh Exp $"); +__KERNEL_RCSID(0, "$NetBSD: subr_kmem.c,v 1.85 2022/05/30 20:28:30 riastradh Exp $"); #ifdef _KERNEL_OPT #include "opt_kmem.h" @@ -93,6 +93,7 @@ __KERNEL_RCSID(0, "$NetBSD: subr_kmem.c, #include <sys/cpu.h> #include <sys/asan.h> #include <sys/msan.h> +#include <sys/sdt.h> #include <uvm/uvm_extern.h> #include <uvm/uvm_map.h> @@ -102,42 +103,97 @@ __KERNEL_RCSID(0, "$NetBSD: subr_kmem.c, struct kmem_cache_info { size_t kc_size; const char * kc_name; +#ifdef KDTRACE_HOOKS + const id_t *kc_alloc_probe_id; + const id_t *kc_free_probe_id; +#endif }; +#define KMEM_CACHE_SIZES(F) \ + F(8, kmem-00008, kmem__00008) \ + F(16, kmem-00016, kmem__00016) \ + F(24, kmem-00024, kmem__00024) \ + F(32, kmem-00032, kmem__00032) \ + F(40, kmem-00040, kmem__00040) \ + F(48, kmem-00048, kmem__00048) \ + F(56, kmem-00056, kmem__00056) \ + F(64, kmem-00064, kmem__00064) \ + F(80, kmem-00080, kmem__00080) \ + F(96, kmem-00096, kmem__00096) \ + F(112, kmem-00112, kmem__00112) \ + F(128, kmem-00128, kmem__00128) \ + F(160, kmem-00160, kmem__00160) \ + F(192, kmem-00192, kmem__00192) \ + F(224, kmem-00224, kmem__00224) \ + F(256, kmem-00256, kmem__00256) \ + F(320, kmem-00320, kmem__00320) \ + F(384, kmem-00384, kmem__00384) \ + F(448, kmem-00448, kmem__00448) \ + F(512, kmem-00512, kmem__00512) \ + F(768, kmem-00768, kmem__00768) \ + F(1024, kmem-01024, kmem__01024) \ + /* end of KMEM_CACHE_SIZES */ + +#define KMEM_CACHE_BIG_SIZES(F) \ + F(2048, kmem-02048, kmem__02048) \ + F(4096, kmem-04096, kmem__04096) \ + F(8192, kmem-08192, kmem__08192) \ + F(16384, kmem-16384, kmem__16384) \ + /* end of KMEM_CACHE_BIG_SIZES */ + +/* sdt:kmem:alloc:kmem-* probes */ +#define F(SZ, NAME, PROBENAME) \ + SDT_PROBE_DEFINE4(sdt, kmem, alloc, PROBENAME, \ + "void *"/*ptr*/, \ + "size_t"/*requested_size*/, \ + "size_t"/*allocated_size*/, \ + "km_flag_t"/*kmflags*/); +KMEM_CACHE_SIZES(F); +KMEM_CACHE_BIG_SIZES(F); +#undef F + +/* sdt:kmem:free:kmem-* probes */ +#define F(SZ, NAME, PROBENAME) \ + SDT_PROBE_DEFINE3(sdt, kmem, free, PROBENAME, \ + "void *"/*ptr*/, \ + "size_t"/*requested_size*/, \ + "size_t"/*allocated_size*/); +KMEM_CACHE_SIZES(F); +KMEM_CACHE_BIG_SIZES(F); +#undef F + +/* sdt:kmem:alloc:large, sdt:kmem:free:large probes */ +SDT_PROBE_DEFINE4(sdt, kmem, alloc, large, + "void *"/*ptr*/, + "size_t"/*requested_size*/, + "size_t"/*allocated_size*/, + "km_flag_t"/*kmflags*/); +SDT_PROBE_DEFINE3(sdt, kmem, free, large, + "void *"/*ptr*/, + "size_t"/*requested_size*/, + "size_t"/*allocated_size*/); + +#ifdef KDTRACE_HOOKS +#define F(SZ, NAME, PROBENAME) \ + { SZ, #NAME, \ + &sdt_sdt_kmem_alloc_##PROBENAME->id, \ + &sdt_sdt_kmem_free_##PROBENAME->id }, +#else +#define F(SZ, NAME, PROBENAME) { SZ, #NAME }, +#endif + static const struct kmem_cache_info kmem_cache_sizes[] = { - { 8, "kmem-00008" }, - { 16, "kmem-00016" }, - { 24, "kmem-00024" }, - { 32, "kmem-00032" }, - { 40, "kmem-00040" }, - { 48, "kmem-00048" }, - { 56, "kmem-00056" }, - { 64, "kmem-00064" }, - { 80, "kmem-00080" }, - { 96, "kmem-00096" }, - { 112, "kmem-00112" }, - { 128, "kmem-00128" }, - { 160, "kmem-00160" }, - { 192, "kmem-00192" }, - { 224, "kmem-00224" }, - { 256, "kmem-00256" }, - { 320, "kmem-00320" }, - { 384, "kmem-00384" }, - { 448, "kmem-00448" }, - { 512, "kmem-00512" }, - { 768, "kmem-00768" }, - { 1024, "kmem-01024" }, + KMEM_CACHE_SIZES(F) { 0, NULL } }; static const struct kmem_cache_info kmem_cache_big_sizes[] = { - { 2048, "kmem-02048" }, - { 4096, "kmem-04096" }, - { 8192, "kmem-08192" }, - { 16384, "kmem-16384" }, + KMEM_CACHE_BIG_SIZES(F) { 0, NULL } }; +#undef F + /* * KMEM_ALIGN is the smallest guaranteed alignment and also the * smallest allocateable quantum. @@ -177,6 +233,49 @@ static void kmem_size_check(void *, size #define kmem_size_check(p, sz) /* nothing */ #endif +#ifndef KDTRACE_HOOKS + +static const id_t **const kmem_cache_alloc_probe_id = NULL; +static const id_t **const kmem_cache_big_alloc_probe_id = NULL; +static const id_t **const kmem_cache_free_probe_id = NULL; +static const id_t **const kmem_cache_big_free_probe_id = NULL; + +#define KMEM_CACHE_PROBE(ARRAY, INDEX, PTR, REQSIZE, ALLOCSIZE, FLAGS) \ + __nothing + +#else + +static const id_t *kmem_cache_alloc_probe_id[KMEM_CACHE_COUNT]; +static const id_t *kmem_cache_big_alloc_probe_id[KMEM_CACHE_COUNT]; +static const id_t *kmem_cache_free_probe_id[KMEM_CACHE_COUNT]; +static const id_t *kmem_cache_big_free_probe_id[KMEM_CACHE_COUNT]; + +#define KMEM_CACHE_PROBE(ARRAY, INDEX, PTR, REQSIZE, ALLOCSIZE, FLAGS) do \ +{ \ + id_t id; \ + \ + KDASSERT((INDEX) < __arraycount(ARRAY)); \ + if (__predict_false((id = *(ARRAY)[INDEX]) != 0)) { \ + (*sdt_probe_func)(id, \ + (uintptr_t)(PTR), \ + (uintptr_t)(REQSIZE), \ + (uintptr_t)(ALLOCSIZE), \ + (uintptr_t)(FLAGS), \ + (uintptr_t)0); \ + } \ +} while (0) + +#endif /* KDTRACE_HOOKS */ + +#define KMEM_CACHE_ALLOC_PROBE(I, P, RS, AS, F) \ + KMEM_CACHE_PROBE(kmem_cache_alloc_probe_id, I, P, RS, AS, F) +#define KMEM_CACHE_BIG_ALLOC_PROBE(I, P, RS, AS, F) \ + KMEM_CACHE_PROBE(kmem_cache_big_alloc_probe_id, I, P, RS, AS, F) +#define KMEM_CACHE_FREE_PROBE(I, P, RS, AS) \ + KMEM_CACHE_PROBE(kmem_cache_free_probe_id, I, P, RS, AS, 0) +#define KMEM_CACHE_BIG_FREE_PROBE(I, P, RS, AS) \ + KMEM_CACHE_PROBE(kmem_cache_big_free_probe_id, I, P, RS, AS, 0) + CTASSERT(KM_SLEEP == PR_WAITOK); CTASSERT(KM_NOSLEEP == PR_NOWAIT); @@ -203,17 +302,25 @@ kmem_intr_alloc(size_t requested_size, k size = kmem_roundup_size(requested_size); allocsz = size + SIZE_SIZE; - if ((index = ((allocsz -1) >> KMEM_SHIFT)) + if ((index = ((allocsz - 1) >> KMEM_SHIFT)) < kmem_cache_maxidx) { pc = kmem_cache[index]; + p = pool_cache_get(pc, kmflags); + KMEM_CACHE_ALLOC_PROBE(index, + p, requested_size, allocsz, kmflags); } else if ((index = ((allocsz - 1) >> KMEM_BIG_SHIFT)) < kmem_cache_big_maxidx) { pc = kmem_cache_big[index]; + p = pool_cache_get(pc, kmflags); + KMEM_CACHE_BIG_ALLOC_PROBE(index, + p, requested_size, allocsz, kmflags); } else { int ret = uvm_km_kmem_alloc(kmem_va_arena, (vsize_t)round_page(size), ((kmflags & KM_SLEEP) ? VM_SLEEP : VM_NOSLEEP) | VM_INSTANTFIT, (vmem_addr_t *)&p); + SDT_PROBE4(sdt, kmem, alloc, large, + ret ? NULL : p, requested_size, round_page(size), kmflags); if (ret) { return NULL; } @@ -221,8 +328,6 @@ kmem_intr_alloc(size_t requested_size, k return p; } - p = pool_cache_get(pc, kmflags); - if (__predict_true(p != NULL)) { FREECHECK_OUT(&kmem_freecheck, p); kmem_size_set(p, requested_size); @@ -264,14 +369,18 @@ kmem_intr_free(void *p, size_t requested size = kmem_roundup_size(requested_size); allocsz = size + SIZE_SIZE; - if ((index = ((allocsz -1) >> KMEM_SHIFT)) + if ((index = ((allocsz - 1) >> KMEM_SHIFT)) < kmem_cache_maxidx) { + KMEM_CACHE_FREE_PROBE(index, p, requested_size, allocsz); pc = kmem_cache[index]; } else if ((index = ((allocsz - 1) >> KMEM_BIG_SHIFT)) < kmem_cache_big_maxidx) { + KMEM_CACHE_BIG_FREE_PROBE(index, p, requested_size, allocsz); pc = kmem_cache_big[index]; } else { FREECHECK_IN(&kmem_freecheck, p); + SDT_PROBE3(sdt, kmem, free, large, + p, requested_size, round_page(size)); uvm_km_kmem_free(kmem_va_arena, (vaddr_t)p, round_page(size)); return; @@ -339,6 +448,7 @@ kmem_free(void *p, size_t size) static size_t kmem_create_caches(const struct kmem_cache_info *array, + const id_t *alloc_probe_table[], const id_t *free_probe_table[], pool_cache_t alloc_table[], size_t maxsize, int shift, int ipl) { size_t maxidx = 0; @@ -388,6 +498,14 @@ kmem_create_caches(const struct kmem_cac while (size <= cache_size) { alloc_table[(size - 1) >> shift] = pc; + if (alloc_probe_table) { + alloc_probe_table[(size - 1) >> shift] = + array[i].kc_alloc_probe_id; + } + if (free_probe_table) { + free_probe_table[(size - 1) >> shift] = + array[i].kc_free_probe_id; + } size += table_unit; } } @@ -398,8 +516,10 @@ void kmem_init(void) { kmem_cache_maxidx = kmem_create_caches(kmem_cache_sizes, + kmem_cache_alloc_probe_id, kmem_cache_free_probe_id, kmem_cache, KMEM_MAXSIZE, KMEM_SHIFT, IPL_VM); kmem_cache_big_maxidx = kmem_create_caches(kmem_cache_big_sizes, + kmem_cache_big_alloc_probe_id, kmem_cache_big_free_probe_id, kmem_cache_big, PAGE_SIZE, KMEM_BIG_SHIFT, IPL_VM); }