memstat_uma.c revision 155549
1147997Srwatson/*- 2147997Srwatson * Copyright (c) 2005 Robert N. M. Watson 3147997Srwatson * All rights reserved. 4147997Srwatson * 5147997Srwatson * Redistribution and use in source and binary forms, with or without 6147997Srwatson * modification, are permitted provided that the following conditions 7147997Srwatson * are met: 8147997Srwatson * 1. Redistributions of source code must retain the above copyright 9147997Srwatson * notice, this list of conditions and the following disclaimer. 10147997Srwatson * 2. Redistributions in binary form must reproduce the above copyright 11147997Srwatson * notice, this list of conditions and the following disclaimer in the 12147997Srwatson * documentation and/or other materials provided with the distribution. 13147997Srwatson * 14147997Srwatson * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15147997Srwatson * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16147997Srwatson * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17147997Srwatson * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18147997Srwatson * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19147997Srwatson * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20147997Srwatson * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21147997Srwatson * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22147997Srwatson * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23147997Srwatson * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24147997Srwatson * SUCH DAMAGE. 25147997Srwatson * 26147997Srwatson * $FreeBSD: head/lib/libmemstat/memstat_uma.c 155549 2006-02-11 18:55:03Z rwatson $ 27147997Srwatson */ 28147997Srwatson 29147997Srwatson#include <sys/param.h> 30147997Srwatson#include <sys/sysctl.h> 31147997Srwatson 32148693Srwatson#define LIBMEMSTAT /* Cause vm_page.h not to include opt_vmpage.h */ 33148627Srwatson#include <vm/vm.h> 34148627Srwatson#include <vm/vm_page.h> 35148627Srwatson 36147997Srwatson#include <vm/uma.h> 37148627Srwatson#include <vm/uma_int.h> 38147997Srwatson 39147997Srwatson#include <err.h> 40147997Srwatson#include <errno.h> 41148627Srwatson#include <kvm.h> 42148627Srwatson#include <nlist.h> 43147997Srwatson#include <stdio.h> 44147997Srwatson#include <stdlib.h> 45147997Srwatson#include <string.h> 46147997Srwatson 47147997Srwatson#include "memstat.h" 48147997Srwatson#include "memstat_internal.h" 49147997Srwatson 50148627Srwatsonstatic struct nlist namelist[] = { 51148627Srwatson#define X_UMA_KEGS 0 52148627Srwatson { .n_name = "_uma_kegs" }, 53148627Srwatson#define X_MP_MAXID 1 54148627Srwatson { .n_name = "_mp_maxid" }, 55155547Srwatson#define X_ALL_CPUS 2 56155547Srwatson { .n_name = "_all_cpus" }, 57148627Srwatson { .n_name = "" }, 58148627Srwatson}; 59148627Srwatson 60147997Srwatson/* 61147997Srwatson * Extract uma(9) statistics from the running kernel, and store all memory 62147997Srwatson * type information in the passed list. For each type, check the list for an 63147997Srwatson * existing entry with the right name/allocator -- if present, update that 64147997Srwatson * entry. Otherwise, add a new entry. On error, the entire list will be 65147997Srwatson * cleared, as entries will be in an inconsistent state. 66147997Srwatson * 67147997Srwatson * To reduce the level of work for a list that starts empty, we keep around a 68147997Srwatson * hint as to whether it was empty when we began, so we can avoid searching 69147997Srwatson * the list for entries to update. Updates are O(n^2) due to searching for 70147997Srwatson * each entry before adding it. 71147997Srwatson */ 72147997Srwatsonint 73147997Srwatsonmemstat_sysctl_uma(struct memory_type_list *list, int flags) 74147997Srwatson{ 75147997Srwatson struct uma_stream_header *ushp; 76147997Srwatson struct uma_type_header *uthp; 77147997Srwatson struct uma_percpu_stat *upsp; 78147997Srwatson struct memory_type *mtp; 79148357Srwatson int count, hint_dontsearch, i, j, maxcpus; 80147997Srwatson char *buffer, *p; 81147997Srwatson size_t size; 82147997Srwatson 83148357Srwatson hint_dontsearch = LIST_EMPTY(&list->mtl_list); 84147997Srwatson 85147997Srwatson /* 86147997Srwatson * Query the number of CPUs, number of malloc types so that we can 87147997Srwatson * guess an initial buffer size. We loop until we succeed or really 88147997Srwatson * fail. Note that the value of maxcpus we query using sysctl is not 89147997Srwatson * the version we use when processing the real data -- that is read 90147997Srwatson * from the header. 91147997Srwatson */ 92147997Srwatsonretry: 93147997Srwatson size = sizeof(maxcpus); 94147997Srwatson if (sysctlbyname("kern.smp.maxcpus", &maxcpus, &size, NULL, 0) < 0) { 95148357Srwatson if (errno == EACCES || errno == EPERM) 96148357Srwatson list->mtl_error = MEMSTAT_ERROR_PERMISSION; 97148357Srwatson else 98148357Srwatson list->mtl_error = MEMSTAT_ERROR_DATAERROR; 99147997Srwatson return (-1); 100147997Srwatson } 101147997Srwatson if (size != sizeof(maxcpus)) { 102148357Srwatson list->mtl_error = MEMSTAT_ERROR_DATAERROR; 103147997Srwatson return (-1); 104147997Srwatson } 105147997Srwatson 106147997Srwatson if (maxcpus > MEMSTAT_MAXCPU) { 107148357Srwatson list->mtl_error = MEMSTAT_ERROR_TOOMANYCPUS; 108147997Srwatson return (-1); 109147997Srwatson } 110147997Srwatson 111147997Srwatson size = sizeof(count); 112147997Srwatson if (sysctlbyname("vm.zone_count", &count, &size, NULL, 0) < 0) { 113148357Srwatson if (errno == EACCES || errno == EPERM) 114148357Srwatson list->mtl_error = MEMSTAT_ERROR_PERMISSION; 115148357Srwatson else 116148357Srwatson list->mtl_error = MEMSTAT_ERROR_VERSION; 117147997Srwatson return (-1); 118147997Srwatson } 119147997Srwatson if (size != sizeof(count)) { 120148357Srwatson list->mtl_error = MEMSTAT_ERROR_DATAERROR; 121147997Srwatson return (-1); 122147997Srwatson } 123147997Srwatson 124147997Srwatson size = sizeof(*uthp) + count * (sizeof(*uthp) + sizeof(*upsp) * 125147997Srwatson maxcpus); 126147997Srwatson 127147997Srwatson buffer = malloc(size); 128147997Srwatson if (buffer == NULL) { 129148357Srwatson list->mtl_error = MEMSTAT_ERROR_NOMEMORY; 130147997Srwatson return (-1); 131147997Srwatson } 132147997Srwatson 133147997Srwatson if (sysctlbyname("vm.zone_stats", buffer, &size, NULL, 0) < 0) { 134147997Srwatson /* 135147997Srwatson * XXXRW: ENOMEM is an ambiguous return, we should bound the 136147997Srwatson * number of loops, perhaps. 137147997Srwatson */ 138147997Srwatson if (errno == ENOMEM) { 139147997Srwatson free(buffer); 140147997Srwatson goto retry; 141147997Srwatson } 142148357Srwatson if (errno == EACCES || errno == EPERM) 143148357Srwatson list->mtl_error = MEMSTAT_ERROR_PERMISSION; 144148357Srwatson else 145148357Srwatson list->mtl_error = MEMSTAT_ERROR_VERSION; 146147997Srwatson free(buffer); 147147997Srwatson return (-1); 148147997Srwatson } 149147997Srwatson 150147997Srwatson if (size == 0) { 151147997Srwatson free(buffer); 152147997Srwatson return (0); 153147997Srwatson } 154147997Srwatson 155147997Srwatson if (size < sizeof(*ushp)) { 156148357Srwatson list->mtl_error = MEMSTAT_ERROR_VERSION; 157147997Srwatson free(buffer); 158147997Srwatson return (-1); 159147997Srwatson } 160147997Srwatson p = buffer; 161147997Srwatson ushp = (struct uma_stream_header *)p; 162147997Srwatson p += sizeof(*ushp); 163147997Srwatson 164147997Srwatson if (ushp->ush_version != UMA_STREAM_VERSION) { 165148357Srwatson list->mtl_error = MEMSTAT_ERROR_VERSION; 166147997Srwatson free(buffer); 167147997Srwatson return (-1); 168147997Srwatson } 169147997Srwatson 170147997Srwatson if (ushp->ush_maxcpus > MEMSTAT_MAXCPU) { 171148357Srwatson list->mtl_error = MEMSTAT_ERROR_TOOMANYCPUS; 172147997Srwatson free(buffer); 173147997Srwatson return (-1); 174147997Srwatson } 175147997Srwatson 176147997Srwatson /* 177147997Srwatson * For the remainder of this function, we are quite trusting about 178147997Srwatson * the layout of structures and sizes, since we've determined we have 179147997Srwatson * a matching version and acceptable CPU count. 180147997Srwatson */ 181147997Srwatson maxcpus = ushp->ush_maxcpus; 182147997Srwatson count = ushp->ush_count; 183147997Srwatson for (i = 0; i < count; i++) { 184147997Srwatson uthp = (struct uma_type_header *)p; 185147997Srwatson p += sizeof(*uthp); 186147997Srwatson 187147997Srwatson if (hint_dontsearch == 0) { 188147997Srwatson mtp = memstat_mtl_find(list, ALLOCATOR_UMA, 189147997Srwatson uthp->uth_name); 190147997Srwatson } else 191147997Srwatson mtp = NULL; 192147997Srwatson if (mtp == NULL) 193148354Srwatson mtp = _memstat_mt_allocate(list, ALLOCATOR_UMA, 194147997Srwatson uthp->uth_name); 195147997Srwatson if (mtp == NULL) { 196148619Srwatson _memstat_mtl_empty(list); 197147997Srwatson free(buffer); 198148357Srwatson list->mtl_error = MEMSTAT_ERROR_NOMEMORY; 199147997Srwatson return (-1); 200147997Srwatson } 201147997Srwatson 202147997Srwatson /* 203147997Srwatson * Reset the statistics on a current node. 204147997Srwatson */ 205148354Srwatson _memstat_mt_reset_stats(mtp); 206147997Srwatson 207148007Srwatson mtp->mt_numallocs = uthp->uth_allocs; 208148007Srwatson mtp->mt_numfrees = uthp->uth_frees; 209148071Srwatson mtp->mt_failures = uthp->uth_fails; 210148007Srwatson 211147997Srwatson for (j = 0; j < maxcpus; j++) { 212147997Srwatson upsp = (struct uma_percpu_stat *)p; 213147997Srwatson p += sizeof(*upsp); 214147997Srwatson 215147997Srwatson mtp->mt_percpu_cache[j].mtp_free = 216147997Srwatson upsp->ups_cache_free; 217147997Srwatson mtp->mt_free += upsp->ups_cache_free; 218147997Srwatson mtp->mt_numallocs += upsp->ups_allocs; 219147997Srwatson mtp->mt_numfrees += upsp->ups_frees; 220147997Srwatson } 221147997Srwatson 222147997Srwatson mtp->mt_size = uthp->uth_size; 223148007Srwatson mtp->mt_memalloced = mtp->mt_numallocs * uthp->uth_size; 224148007Srwatson mtp->mt_memfreed = mtp->mt_numfrees * uthp->uth_size; 225147997Srwatson mtp->mt_bytes = mtp->mt_memalloced - mtp->mt_memfreed; 226147997Srwatson mtp->mt_countlimit = uthp->uth_limit; 227147997Srwatson mtp->mt_byteslimit = uthp->uth_limit * uthp->uth_size; 228147997Srwatson 229147997Srwatson mtp->mt_count = mtp->mt_numallocs - mtp->mt_numfrees; 230148170Srwatson mtp->mt_zonefree = uthp->uth_zone_free; 231148381Srwatson 232148381Srwatson /* 233148381Srwatson * UMA secondary zones share a keg with the primary zone. To 234148381Srwatson * avoid double-reporting of free items, report keg free 235148381Srwatson * items only in the primary zone. 236148381Srwatson */ 237148381Srwatson if (!(uthp->uth_zone_flags & UTH_ZONE_SECONDARY)) { 238148619Srwatson mtp->mt_kegfree = uthp->uth_keg_free; 239148381Srwatson mtp->mt_free += mtp->mt_kegfree; 240148381Srwatson } 241147997Srwatson mtp->mt_free += mtp->mt_zonefree; 242147997Srwatson } 243147997Srwatson 244147997Srwatson free(buffer); 245147997Srwatson 246147997Srwatson return (0); 247147997Srwatson} 248148627Srwatson 249148627Srwatsonstatic int 250148627Srwatsonkread(kvm_t *kvm, void *kvm_pointer, void *address, size_t size, 251148627Srwatson size_t offset) 252148627Srwatson{ 253148627Srwatson ssize_t ret; 254148627Srwatson 255148627Srwatson ret = kvm_read(kvm, (unsigned long)kvm_pointer + offset, address, 256148627Srwatson size); 257148627Srwatson if (ret < 0) 258148627Srwatson return (MEMSTAT_ERROR_KVM); 259148627Srwatson if ((size_t)ret != size) 260148627Srwatson return (MEMSTAT_ERROR_KVM_SHORTREAD); 261148627Srwatson return (0); 262148627Srwatson} 263148627Srwatson 264148627Srwatsonstatic int 265148627Srwatsonkread_string(kvm_t *kvm, void *kvm_pointer, char *buffer, int buflen) 266148627Srwatson{ 267148627Srwatson ssize_t ret; 268148627Srwatson int i; 269148627Srwatson 270148627Srwatson for (i = 0; i < buflen; i++) { 271148627Srwatson ret = kvm_read(kvm, (unsigned long)kvm_pointer + i, 272148627Srwatson &(buffer[i]), sizeof(char)); 273148627Srwatson if (ret < 0) 274148627Srwatson return (MEMSTAT_ERROR_KVM); 275148627Srwatson if ((size_t)ret != sizeof(char)) 276148627Srwatson return (MEMSTAT_ERROR_KVM_SHORTREAD); 277148627Srwatson if (buffer[i] == '\0') 278148627Srwatson return (0); 279148627Srwatson } 280148627Srwatson /* Truncate. */ 281148627Srwatson buffer[i-1] = '\0'; 282148627Srwatson return (0); 283148627Srwatson} 284148627Srwatson 285148627Srwatsonstatic int 286148627Srwatsonkread_symbol(kvm_t *kvm, int index, void *address, size_t size, 287148627Srwatson size_t offset) 288148627Srwatson{ 289148627Srwatson ssize_t ret; 290148627Srwatson 291148627Srwatson ret = kvm_read(kvm, namelist[index].n_value + offset, address, size); 292148627Srwatson if (ret < 0) 293148627Srwatson return (MEMSTAT_ERROR_KVM); 294148627Srwatson if ((size_t)ret != size) 295148627Srwatson return (MEMSTAT_ERROR_KVM_SHORTREAD); 296148627Srwatson return (0); 297148627Srwatson} 298148627Srwatson 299148627Srwatson/* 300148627Srwatson * memstat_kvm_uma() is similar to memstat_sysctl_uma(), only it extracts 301148627Srwatson * UMA(9) statistics from a kernel core/memory file. 302148627Srwatson */ 303148627Srwatsonint 304148627Srwatsonmemstat_kvm_uma(struct memory_type_list *list, void *kvm_handle) 305148627Srwatson{ 306154416Srwatson LIST_HEAD(, uma_keg) uma_kegs; 307148627Srwatson struct memory_type *mtp; 308148627Srwatson struct uma_bucket *ubp, ub; 309148627Srwatson struct uma_cache *ucp; 310148627Srwatson struct uma_zone *uzp, uz; 311148627Srwatson struct uma_keg *kzp, kz; 312148627Srwatson int hint_dontsearch, i, mp_maxid, ret; 313148627Srwatson char name[MEMTYPE_MAXNAME]; 314155547Srwatson __cpumask_t all_cpus; 315148627Srwatson kvm_t *kvm; 316148627Srwatson 317148627Srwatson kvm = (kvm_t *)kvm_handle; 318148627Srwatson hint_dontsearch = LIST_EMPTY(&list->mtl_list); 319148627Srwatson if (kvm_nlist(kvm, namelist) != 0) { 320148627Srwatson list->mtl_error = MEMSTAT_ERROR_KVM; 321148627Srwatson return (-1); 322148627Srwatson } 323148627Srwatson if (namelist[X_UMA_KEGS].n_type == 0 || 324148627Srwatson namelist[X_UMA_KEGS].n_value == 0) { 325148627Srwatson list->mtl_error = MEMSTAT_ERROR_KVM_NOSYMBOL; 326148627Srwatson return (-1); 327148627Srwatson } 328148627Srwatson ret = kread_symbol(kvm, X_MP_MAXID, &mp_maxid, sizeof(mp_maxid), 0); 329148627Srwatson if (ret != 0) { 330148627Srwatson list->mtl_error = ret; 331148627Srwatson return (-1); 332148627Srwatson } 333148627Srwatson ret = kread_symbol(kvm, X_UMA_KEGS, &uma_kegs, sizeof(uma_kegs), 0); 334148627Srwatson if (ret != 0) { 335148627Srwatson list->mtl_error = ret; 336148627Srwatson return (-1); 337148627Srwatson } 338155547Srwatson ret = kread_symbol(kvm, X_ALL_CPUS, &all_cpus, sizeof(all_cpus), 0); 339155547Srwatson if (ret != 0) { 340155547Srwatson list->mtl_error = ret; 341155547Srwatson return (-1); 342155547Srwatson } 343148627Srwatson for (kzp = LIST_FIRST(&uma_kegs); kzp != NULL; kzp = 344148627Srwatson LIST_NEXT(&kz, uk_link)) { 345148627Srwatson ret = kread(kvm, kzp, &kz, sizeof(kz), 0); 346148627Srwatson if (ret != 0) { 347148627Srwatson _memstat_mtl_empty(list); 348148627Srwatson list->mtl_error = ret; 349148627Srwatson return (-1); 350148627Srwatson } 351148627Srwatson for (uzp = LIST_FIRST(&kz.uk_zones); uzp != NULL; uzp = 352148627Srwatson LIST_NEXT(&uz, uz_link)) { 353148627Srwatson ret = kread(kvm, uzp, &uz, sizeof(uz), 0); 354148627Srwatson if (ret != 0) { 355148627Srwatson _memstat_mtl_empty(list); 356148627Srwatson list->mtl_error = ret; 357148627Srwatson return (-1); 358148627Srwatson } 359148627Srwatson ret = kread_string(kvm, uz.uz_name, name, 360148627Srwatson MEMTYPE_MAXNAME); 361148627Srwatson if (ret != 0) { 362148627Srwatson _memstat_mtl_empty(list); 363148627Srwatson list->mtl_error = ret; 364148627Srwatson return (-1); 365148627Srwatson } 366148627Srwatson if (hint_dontsearch == 0) { 367148627Srwatson mtp = memstat_mtl_find(list, ALLOCATOR_UMA, 368148627Srwatson name); 369148627Srwatson } else 370148627Srwatson mtp = NULL; 371148627Srwatson if (mtp == NULL) 372148627Srwatson mtp = _memstat_mt_allocate(list, ALLOCATOR_UMA, 373148627Srwatson name); 374148627Srwatson if (mtp == NULL) { 375148627Srwatson _memstat_mtl_empty(list); 376148627Srwatson list->mtl_error = MEMSTAT_ERROR_NOMEMORY; 377148627Srwatson return (-1); 378148627Srwatson } 379148627Srwatson /* 380148627Srwatson * Reset the statistics on a current node. 381148627Srwatson */ 382148627Srwatson _memstat_mt_reset_stats(mtp); 383148627Srwatson mtp->mt_numallocs = uz.uz_allocs; 384148627Srwatson mtp->mt_numfrees = uz.uz_frees; 385148627Srwatson mtp->mt_failures = uz.uz_fails; 386148627Srwatson if (kz.uk_flags & UMA_ZFLAG_INTERNAL) 387148627Srwatson goto skip_percpu; 388148627Srwatson for (i = 0; i < mp_maxid + 1; i++) { 389155547Srwatson if ((all_cpus & (1 << i)) == 0) 390155547Srwatson continue; 391148627Srwatson ucp = &uz.uz_cpu[i]; 392148627Srwatson mtp->mt_numallocs += ucp->uc_allocs; 393148627Srwatson mtp->mt_numfrees += ucp->uc_frees; 394148627Srwatson 395148627Srwatson if (ucp->uc_allocbucket != NULL) { 396148627Srwatson ret = kread(kvm, ucp->uc_allocbucket, 397148627Srwatson &ub, sizeof(ub), 0); 398148627Srwatson if (ret != 0) { 399148627Srwatson _memstat_mtl_empty(list); 400155549Srwatson list->mtl_error = ret; 401148627Srwatson return (-1); 402148627Srwatson } 403148627Srwatson mtp->mt_free += ub.ub_cnt; 404148627Srwatson } 405148627Srwatson if (ucp->uc_freebucket != NULL) { 406148627Srwatson ret = kread(kvm, ucp->uc_freebucket, 407148627Srwatson &ub, sizeof(ub), 0); 408148627Srwatson if (ret != 0) { 409148627Srwatson _memstat_mtl_empty(list); 410155549Srwatson list->mtl_error = ret; 411148627Srwatson return (-1); 412148627Srwatson } 413148627Srwatson mtp->mt_free += ub.ub_cnt; 414148627Srwatson } 415148627Srwatson } 416148627Srwatsonskip_percpu: 417148627Srwatson mtp->mt_size = kz.uk_size; 418148627Srwatson mtp->mt_memalloced = mtp->mt_numallocs * mtp->mt_size; 419148627Srwatson mtp->mt_memfreed = mtp->mt_numfrees * mtp->mt_size; 420155542Srwatson mtp->mt_bytes = mtp->mt_memalloced - mtp->mt_memfreed; 421148627Srwatson if (kz.uk_ppera > 1) 422148627Srwatson mtp->mt_countlimit = kz.uk_maxpages / 423148627Srwatson kz.uk_ipers; 424148627Srwatson else 425148627Srwatson mtp->mt_countlimit = kz.uk_maxpages * 426148627Srwatson kz.uk_ipers; 427148627Srwatson mtp->mt_byteslimit = mtp->mt_countlimit * mtp->mt_size; 428148627Srwatson mtp->mt_count = mtp->mt_numallocs - mtp->mt_numfrees; 429148627Srwatson for (ubp = LIST_FIRST(&uz.uz_full_bucket); ubp != 430148627Srwatson NULL; ubp = LIST_NEXT(&ub, ub_link)) { 431148627Srwatson ret = kread(kvm, ubp, &ub, sizeof(ub), 0); 432148627Srwatson mtp->mt_zonefree += ub.ub_cnt; 433148627Srwatson } 434148627Srwatson if (!((kz.uk_flags & UMA_ZONE_SECONDARY) && 435148627Srwatson LIST_FIRST(&kz.uk_zones) != uzp)) { 436148627Srwatson mtp->mt_kegfree = kz.uk_free; 437148627Srwatson mtp->mt_free += mtp->mt_kegfree; 438148627Srwatson } 439148627Srwatson mtp->mt_free += mtp->mt_zonefree; 440148627Srwatson } 441148627Srwatson } 442148627Srwatson return (0); 443148627Srwatson} 444