diff --git a/lib/libmemstat/libmemstat.3 b/lib/libmemstat/libmemstat.3 index 0bc341035c32..a851e5e40cf2 100644 --- a/lib/libmemstat/libmemstat.3 +++ b/lib/libmemstat/libmemstat.3 @@ -53,6 +53,8 @@ .Fn memstat_mtl_geterror "struct memory_type_list *list" .Ss Allocator Query Functions .Ft int +.Fn memstat_kvm_uma "struct memory_type_list *list" "void *kvm_handle" +.Ft int .Fn memstat_sysctl_all "struct memory_type_list *list" "int flags" .Ft int .Fn memstat_sysctl_malloc "struct memory_type_list *list" "int flags" @@ -145,6 +147,7 @@ and freed on completion using .Fn memstat_mtl_free . Lists of memory types are populated via calls that query the kernel for statistics information; currently: +.Fn memstat_kvm_uma , .Fn memstat_sysctl_all , .Fn memstat_sysctl_uma , and @@ -443,8 +446,10 @@ monitoring library, along with the library, were written by .An Robert Watson Aq rwatson@FreeBSD.org .Sh BUGS .Nm -cannot yet extract statistics from kernel core dumps, although this should be -straight forward to implement. +cannot yet extract +.Xr malloc 9 +statistics from kernel core dumps, although this should be straight forward +to implement. .Pp Once a memory type is present on a memory type list, it will not be removed even if the kernel no longer presents information on the type via its diff --git a/lib/libmemstat/memstat.c b/lib/libmemstat/memstat.c index de8ae6599ea1..28e48138642b 100644 --- a/lib/libmemstat/memstat.c +++ b/lib/libmemstat/memstat.c @@ -53,6 +53,12 @@ memstat_strerror(int error) return ("Too many CPUs"); case MEMSTAT_ERROR_DATAERROR: return ("Data format error"); + case MEMSTAT_ERROR_KVM: + return ("KVM error"); + case MEMSTAT_ERROR_KVM_NOSYMBOL: + return ("KVM unable to find symbol"); + case MEMSTAT_ERROR_KVM_SHORTREAD: + return ("KVM short read"); case MEMSTAT_ERROR_UNDEFINED: default: return ("Unknown error"); diff --git a/lib/libmemstat/memstat.h b/lib/libmemstat/memstat.h index f46640876caf..561f6eedbdde 100644 --- a/lib/libmemstat/memstat.h +++ b/lib/libmemstat/memstat.h @@ -72,6 +72,9 @@ #define MEMSTAT_ERROR_PERMISSION 3 /* Permission denied. */ #define MEMSTAT_ERROR_TOOMANYCPUS 4 /* Too many CPUs. */ #define MEMSTAT_ERROR_DATAERROR 5 /* Error in stat data. */ +#define MEMSTAT_ERROR_KVM 6 /* See kvm_geterr() for err. */ +#define MEMSTAT_ERROR_KVM_NOSYMBOL 7 /* Symbol not available. */ +#define MEMSTAT_ERROR_KVM_SHORTREAD 8 /* Short kvm_read return. */ /* * Forward declare struct memory_type, which holds per-type properties and @@ -112,6 +115,12 @@ int memstat_sysctl_all(struct memory_type_list *list, int flags); int memstat_sysctl_malloc(struct memory_type_list *list, int flags); int memstat_sysctl_uma(struct memory_type_list *list, int flags); +/* + * Functions to retrieve data from a kernel core (or /dev/kmem). + */ +int memstat_kvm_all(struct memory_type_list *list, void *kvm_handle); +int memstat_kvm_uma(struct memory_type_list *list, void *kvm_handle); + /* * Accessor methods for struct memory_type. */ diff --git a/lib/libmemstat/memstat_all.c b/lib/libmemstat/memstat_all.c index e32047cebfb8..b363ab91afe0 100644 --- a/lib/libmemstat/memstat_all.c +++ b/lib/libmemstat/memstat_all.c @@ -45,3 +45,16 @@ memstat_sysctl_all(struct memory_type_list *mtlp, int flags) return (-1); return (0); } + +int +memstat_kvm_all(struct memory_type_list *mtlp, void *kvm_handle) +{ + +#if NOTYET + if (memstat_kvm_malloc(mtlp, kvm_handle) < 0) + return (-1); +#endif + if (memstat_kvm_uma(mtlp, kvm_handle) < 0) + return (-1); + return (0); +} diff --git a/lib/libmemstat/memstat_uma.c b/lib/libmemstat/memstat_uma.c index f1fb71059ca2..9572384c4349 100644 --- a/lib/libmemstat/memstat_uma.c +++ b/lib/libmemstat/memstat_uma.c @@ -29,10 +29,25 @@ #include #include +/* + * XXX: Grubbing around in UMA(9) using libkvm requires internal knowledge of + * a number of VM-related bits. The ifdefs around those bits are not + * designed with a nosy user-space consumer in mind. + */ +#include +#define _KERNEL /* XXX: vm_page.h confusion. */ +#define KLD_MODULE /* XXX: vm_page.h shouldn't include opt_vmpage.h. */ +#include +#undef KLD_MODULE +#undef _KERNEL + #include +#include #include #include +#include +#include #include #include #include @@ -40,6 +55,14 @@ #include "memstat.h" #include "memstat_internal.h" +static struct nlist namelist[] = { +#define X_UMA_KEGS 0 + { .n_name = "_uma_kegs" }, +#define X_MP_MAXID 1 + { .n_name = "_mp_maxid" }, + { .n_name = "" }, +}; + /* * Extract uma(9) statistics from the running kernel, and store all memory * type information in the passed list. For each type, check the list for an @@ -228,3 +251,193 @@ retry: return (0); } + +static int +kread(kvm_t *kvm, void *kvm_pointer, void *address, size_t size, + size_t offset) +{ + ssize_t ret; + + ret = kvm_read(kvm, (unsigned long)kvm_pointer + offset, address, + size); + if (ret < 0) + return (MEMSTAT_ERROR_KVM); + if ((size_t)ret != size) + return (MEMSTAT_ERROR_KVM_SHORTREAD); + return (0); +} + +static int +kread_string(kvm_t *kvm, void *kvm_pointer, char *buffer, int buflen) +{ + ssize_t ret; + int i; + + for (i = 0; i < buflen; i++) { + ret = kvm_read(kvm, (unsigned long)kvm_pointer + i, + &(buffer[i]), sizeof(char)); + if (ret < 0) + return (MEMSTAT_ERROR_KVM); + if ((size_t)ret != sizeof(char)) + return (MEMSTAT_ERROR_KVM_SHORTREAD); + if (buffer[i] == '\0') + return (0); + } + /* Truncate. */ + buffer[i-1] = '\0'; + return (0); +} + +static int +kread_symbol(kvm_t *kvm, int index, void *address, size_t size, + size_t offset) +{ + ssize_t ret; + + ret = kvm_read(kvm, namelist[index].n_value + offset, address, size); + if (ret < 0) + return (MEMSTAT_ERROR_KVM); + if ((size_t)ret != size) + return (MEMSTAT_ERROR_KVM_SHORTREAD); + return (0); +} + +/* + * memstat_kvm_uma() is similar to memstat_sysctl_uma(), only it extracts + * UMA(9) statistics from a kernel core/memory file. + */ +int +memstat_kvm_uma(struct memory_type_list *list, void *kvm_handle) +{ + static LIST_HEAD(, uma_keg) uma_kegs; + struct memory_type *mtp; + struct uma_bucket *ubp, ub; + struct uma_cache *ucp; + struct uma_zone *uzp, uz; + struct uma_keg *kzp, kz; + int hint_dontsearch, i, mp_maxid, ret; + char name[MEMTYPE_MAXNAME]; + kvm_t *kvm; + + kvm = (kvm_t *)kvm_handle; + hint_dontsearch = LIST_EMPTY(&list->mtl_list); + if (kvm_nlist(kvm, namelist) != 0) { + list->mtl_error = MEMSTAT_ERROR_KVM; + return (-1); + } + if (namelist[X_UMA_KEGS].n_type == 0 || + namelist[X_UMA_KEGS].n_value == 0) { + list->mtl_error = MEMSTAT_ERROR_KVM_NOSYMBOL; + return (-1); + } + ret = kread_symbol(kvm, X_MP_MAXID, &mp_maxid, sizeof(mp_maxid), 0); + if (ret != 0) { + list->mtl_error = ret; + return (-1); + } + ret = kread_symbol(kvm, X_UMA_KEGS, &uma_kegs, sizeof(uma_kegs), 0); + if (ret != 0) { + list->mtl_error = ret; + return (-1); + } + for (kzp = LIST_FIRST(&uma_kegs); kzp != NULL; kzp = + LIST_NEXT(&kz, uk_link)) { + ret = kread(kvm, kzp, &kz, sizeof(kz), 0); + if (ret != 0) { + _memstat_mtl_empty(list); + list->mtl_error = ret; + return (-1); + } + for (uzp = LIST_FIRST(&kz.uk_zones); uzp != NULL; uzp = + LIST_NEXT(&uz, uz_link)) { + ret = kread(kvm, uzp, &uz, sizeof(uz), 0); + if (ret != 0) { + _memstat_mtl_empty(list); + list->mtl_error = ret; + return (-1); + } + ret = kread_string(kvm, uz.uz_name, name, + MEMTYPE_MAXNAME); + if (ret != 0) { + _memstat_mtl_empty(list); + list->mtl_error = ret; + return (-1); + } + if (hint_dontsearch == 0) { + mtp = memstat_mtl_find(list, ALLOCATOR_UMA, + name); + } else + mtp = NULL; + if (mtp == NULL) + mtp = _memstat_mt_allocate(list, ALLOCATOR_UMA, + name); + if (mtp == NULL) { + _memstat_mtl_empty(list); + list->mtl_error = MEMSTAT_ERROR_NOMEMORY; + return (-1); + } + /* + * Reset the statistics on a current node. + */ + _memstat_mt_reset_stats(mtp); + mtp->mt_numallocs = uz.uz_allocs; + mtp->mt_numfrees = uz.uz_frees; + mtp->mt_failures = uz.uz_fails; + if (kz.uk_flags & UMA_ZFLAG_INTERNAL) + goto skip_percpu; + for (i = 0; i < mp_maxid + 1; i++) { + ucp = &uz.uz_cpu[i]; + mtp->mt_numallocs += ucp->uc_allocs; + mtp->mt_numfrees += ucp->uc_frees; + + if (ucp->uc_allocbucket != NULL) { + ret = kread(kvm, ucp->uc_allocbucket, + &ub, sizeof(ub), 0); + if (ret != 0) { + _memstat_mtl_empty(list); + list->mtl_error = + MEMSTAT_ERROR_NOMEMORY; + return (-1); + } + mtp->mt_free += ub.ub_cnt; + } + if (ucp->uc_freebucket != NULL) { + ret = kread(kvm, ucp->uc_freebucket, + &ub, sizeof(ub), 0); + if (ret != 0) { + _memstat_mtl_empty(list); + list->mtl_error = + MEMSTAT_ERROR_NOMEMORY; + return (-1); + } + mtp->mt_free += ub.ub_cnt; + } + } +skip_percpu: + mtp->mt_size = kz.uk_size; + mtp->mt_memalloced = mtp->mt_numallocs * mtp->mt_size; + mtp->mt_memfreed = mtp->mt_numfrees * mtp->mt_size; + mtp->mt_bytes = mtp->mt_memalloced = mtp->mt_memfreed; + if (kz.uk_ppera > 1) + mtp->mt_countlimit = kz.uk_maxpages / + kz.uk_ipers; + else + mtp->mt_countlimit = kz.uk_maxpages * + kz.uk_ipers; + mtp->mt_byteslimit = mtp->mt_countlimit * mtp->mt_size; + mtp->mt_count = mtp->mt_numallocs - mtp->mt_numfrees; + for (ubp = LIST_FIRST(&uz.uz_full_bucket); ubp != + NULL; ubp = LIST_NEXT(&ub, ub_link)) { + ret = kread(kvm, ubp, &ub, sizeof(ub), 0); + mtp->mt_zonefree += ub.ub_cnt; + } + if (!((kz.uk_flags & UMA_ZONE_SECONDARY) && + LIST_FIRST(&kz.uk_zones) != uzp)) { + mtp->mt_kegfree = kz.uk_free; + mtp->mt_free += mtp->mt_kegfree; + } + mtp->mt_free += mtp->mt_zonefree; + } + } + return (0); +}