memstat_uma.c revision 295451
1249259Sdim/*- 2249259Sdim * Copyright (c) 2005-2006 Robert N. M. Watson 3249259Sdim * All rights reserved. 4249259Sdim * 5249259Sdim * Redistribution and use in source and binary forms, with or without 6249259Sdim * modification, are permitted provided that the following conditions 7249259Sdim * are met: 8249259Sdim * 1. Redistributions of source code must retain the above copyright 9249259Sdim * notice, this list of conditions and the following disclaimer. 10249259Sdim * 2. Redistributions in binary form must reproduce the above copyright 11249259Sdim * notice, this list of conditions and the following disclaimer in the 12249259Sdim * documentation and/or other materials provided with the distribution. 13249259Sdim * 14249259Sdim * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15249259Sdim * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16263508Sdim * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17263508Sdim * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18251662Sdim * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19249259Sdim * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20251662Sdim * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21249259Sdim * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22249259Sdim * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23249259Sdim * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24249259Sdim * SUCH DAMAGE. 25249259Sdim * 26249259Sdim * $FreeBSD: head/lib/libmemstat/memstat_uma.c 295451 2016-02-09 20:22:35Z glebius $ 27249259Sdim */ 28249259Sdim 29249259Sdim#include <sys/param.h> 30249259Sdim#include <sys/cpuset.h> 31249259Sdim#include <sys/sysctl.h> 32263508Sdim 33249259Sdim#include <vm/vm.h> 34251662Sdim#include <vm/vm_page.h> 35249259Sdim 36249259Sdim#include <vm/uma.h> 37249259Sdim#include <vm/uma_int.h> 38249259Sdim 39249259Sdim#include <err.h> 40249259Sdim#include <errno.h> 41249259Sdim#include <kvm.h> 42263508Sdim#include <nlist.h> 43249259Sdim#include <stddef.h> 44263508Sdim#include <stdio.h> 45249259Sdim#include <stdlib.h> 46249259Sdim#include <string.h> 47249259Sdim#include <unistd.h> 48251662Sdim 49251662Sdim#include "memstat.h" 50251662Sdim#include "memstat_internal.h" 51251662Sdim 52251662Sdimstatic struct nlist namelist[] = { 53263508Sdim#define X_UMA_KEGS 0 54263508Sdim { .n_name = "_uma_kegs" }, 55263508Sdim#define X_MP_MAXID 1 56263508Sdim { .n_name = "_mp_maxid" }, 57263508Sdim#define X_ALL_CPUS 2 58263508Sdim { .n_name = "_all_cpus" }, 59263508Sdim { .n_name = "" }, 60263508Sdim}; 61263508Sdim 62263508Sdim/* 63263508Sdim * Extract uma(9) statistics from the running kernel, and store all memory 64249259Sdim * type information in the passed list. For each type, check the list for an 65249259Sdim * existing entry with the right name/allocator -- if present, update that 66263508Sdim * entry. Otherwise, add a new entry. On error, the entire list will be 67263508Sdim * cleared, as entries will be in an inconsistent state. 68263508Sdim * 69263508Sdim * To reduce the level of work for a list that starts empty, we keep around a 70263508Sdim * hint as to whether it was empty when we began, so we can avoid searching 71263508Sdim * the list for entries to update. Updates are O(n^2) due to searching for 72263508Sdim * each entry before adding it. 73263508Sdim */ 74263508Sdimint 75263508Sdimmemstat_sysctl_uma(struct memory_type_list *list, int flags) 76263508Sdim{ 77263508Sdim struct uma_stream_header *ushp; 78263508Sdim struct uma_type_header *uthp; 79263508Sdim struct uma_percpu_stat *upsp; 80263508Sdim struct memory_type *mtp; 81263508Sdim int count, hint_dontsearch, i, j, maxcpus, maxid; 82263508Sdim char *buffer, *p; 83263508Sdim size_t size; 84263508Sdim 85263508Sdim hint_dontsearch = LIST_EMPTY(&list->mtl_list); 86263508Sdim 87263508Sdim /* 88263508Sdim * Query the number of CPUs, number of malloc types so that we can 89263508Sdim * guess an initial buffer size. We loop until we succeed or really 90263508Sdim * fail. Note that the value of maxcpus we query using sysctl is not 91263508Sdim * the version we use when processing the real data -- that is read 92263508Sdim * from the header. 93263508Sdim */ 94263508Sdimretry: 95263508Sdim size = sizeof(maxid); 96263508Sdim if (sysctlbyname("kern.smp.maxid", &maxid, &size, NULL, 0) < 0) { 97263508Sdim if (errno == EACCES || errno == EPERM) 98263508Sdim list->mtl_error = MEMSTAT_ERROR_PERMISSION; 99263508Sdim else 100263508Sdim list->mtl_error = MEMSTAT_ERROR_DATAERROR; 101263508Sdim return (-1); 102263508Sdim } 103263508Sdim if (size != sizeof(maxid)) { 104263508Sdim list->mtl_error = MEMSTAT_ERROR_DATAERROR; 105263508Sdim return (-1); 106263508Sdim } 107263508Sdim 108263508Sdim size = sizeof(count); 109263508Sdim if (sysctlbyname("vm.zone_count", &count, &size, NULL, 0) < 0) { 110263508Sdim if (errno == EACCES || errno == EPERM) 111263508Sdim list->mtl_error = MEMSTAT_ERROR_PERMISSION; 112263508Sdim else 113263508Sdim list->mtl_error = MEMSTAT_ERROR_VERSION; 114263508Sdim return (-1); 115263508Sdim } 116263508Sdim if (size != sizeof(count)) { 117263508Sdim list->mtl_error = MEMSTAT_ERROR_DATAERROR; 118263508Sdim return (-1); 119263508Sdim } 120263508Sdim 121263508Sdim size = sizeof(*uthp) + count * (sizeof(*uthp) + sizeof(*upsp) * 122263508Sdim (maxid + 1)); 123263508Sdim 124263508Sdim buffer = malloc(size); 125263508Sdim if (buffer == NULL) { 126263508Sdim list->mtl_error = MEMSTAT_ERROR_NOMEMORY; 127263508Sdim return (-1); 128263508Sdim } 129263508Sdim 130263508Sdim if (sysctlbyname("vm.zone_stats", buffer, &size, NULL, 0) < 0) { 131263508Sdim /* 132263508Sdim * XXXRW: ENOMEM is an ambiguous return, we should bound the 133263508Sdim * number of loops, perhaps. 134263508Sdim */ 135263508Sdim if (errno == ENOMEM) { 136263508Sdim free(buffer); 137263508Sdim goto retry; 138263508Sdim } 139263508Sdim if (errno == EACCES || errno == EPERM) 140263508Sdim list->mtl_error = MEMSTAT_ERROR_PERMISSION; 141263508Sdim else 142263508Sdim list->mtl_error = MEMSTAT_ERROR_VERSION; 143263508Sdim free(buffer); 144263508Sdim return (-1); 145263508Sdim } 146263508Sdim 147263508Sdim if (size == 0) { 148263508Sdim free(buffer); 149263508Sdim return (0); 150263508Sdim } 151263508Sdim 152263508Sdim if (size < sizeof(*ushp)) { 153263508Sdim list->mtl_error = MEMSTAT_ERROR_VERSION; 154263508Sdim free(buffer); 155263508Sdim return (-1); 156263508Sdim } 157263508Sdim p = buffer; 158263508Sdim ushp = (struct uma_stream_header *)p; 159263508Sdim p += sizeof(*ushp); 160263508Sdim 161263508Sdim if (ushp->ush_version != UMA_STREAM_VERSION) { 162263508Sdim list->mtl_error = MEMSTAT_ERROR_VERSION; 163263508Sdim free(buffer); 164263508Sdim return (-1); 165263508Sdim } 166263508Sdim 167263508Sdim /* 168263508Sdim * For the remainder of this function, we are quite trusting about 169263508Sdim * the layout of structures and sizes, since we've determined we have 170263508Sdim * a matching version and acceptable CPU count. 171263508Sdim */ 172263508Sdim maxcpus = ushp->ush_maxcpus; 173263508Sdim count = ushp->ush_count; 174263508Sdim for (i = 0; i < count; i++) { 175263508Sdim uthp = (struct uma_type_header *)p; 176263508Sdim p += sizeof(*uthp); 177263508Sdim 178263508Sdim if (hint_dontsearch == 0) { 179263508Sdim mtp = memstat_mtl_find(list, ALLOCATOR_UMA, 180263508Sdim uthp->uth_name); 181263508Sdim } else 182263508Sdim mtp = NULL; 183263508Sdim if (mtp == NULL) 184263508Sdim mtp = _memstat_mt_allocate(list, ALLOCATOR_UMA, 185263508Sdim uthp->uth_name, maxid + 1); 186263508Sdim if (mtp == NULL) { 187263508Sdim _memstat_mtl_empty(list); 188263508Sdim free(buffer); 189263508Sdim list->mtl_error = MEMSTAT_ERROR_NOMEMORY; 190263508Sdim return (-1); 191263508Sdim } 192263508Sdim 193263508Sdim /* 194263508Sdim * Reset the statistics on a current node. 195263508Sdim */ 196263508Sdim _memstat_mt_reset_stats(mtp, maxid + 1); 197263508Sdim 198263508Sdim mtp->mt_numallocs = uthp->uth_allocs; 199263508Sdim mtp->mt_numfrees = uthp->uth_frees; 200263508Sdim mtp->mt_failures = uthp->uth_fails; 201263508Sdim mtp->mt_sleeps = uthp->uth_sleeps; 202263508Sdim 203263508Sdim for (j = 0; j < maxcpus; j++) { 204263508Sdim upsp = (struct uma_percpu_stat *)p; 205263508Sdim p += sizeof(*upsp); 206263508Sdim 207263508Sdim mtp->mt_percpu_cache[j].mtp_free = 208263508Sdim upsp->ups_cache_free; 209263508Sdim mtp->mt_free += upsp->ups_cache_free; 210263508Sdim mtp->mt_numallocs += upsp->ups_allocs; 211263508Sdim mtp->mt_numfrees += upsp->ups_frees; 212263508Sdim } 213263508Sdim 214263508Sdim mtp->mt_size = uthp->uth_size; 215263508Sdim mtp->mt_rsize = uthp->uth_rsize; 216263508Sdim mtp->mt_memalloced = mtp->mt_numallocs * uthp->uth_size; 217263508Sdim mtp->mt_memfreed = mtp->mt_numfrees * uthp->uth_size; 218263508Sdim mtp->mt_bytes = mtp->mt_memalloced - mtp->mt_memfreed; 219263508Sdim mtp->mt_countlimit = uthp->uth_limit; 220263508Sdim mtp->mt_byteslimit = uthp->uth_limit * uthp->uth_size; 221263508Sdim 222263508Sdim mtp->mt_count = mtp->mt_numallocs - mtp->mt_numfrees; 223263508Sdim mtp->mt_zonefree = uthp->uth_zone_free; 224263508Sdim 225263508Sdim /* 226263508Sdim * UMA secondary zones share a keg with the primary zone. To 227263508Sdim * avoid double-reporting of free items, report keg free 228263508Sdim * items only in the primary zone. 229263508Sdim */ 230263508Sdim if (!(uthp->uth_zone_flags & UTH_ZONE_SECONDARY)) { 231263508Sdim mtp->mt_kegfree = uthp->uth_keg_free; 232263508Sdim mtp->mt_free += mtp->mt_kegfree; 233263508Sdim } 234263508Sdim mtp->mt_free += mtp->mt_zonefree; 235263508Sdim } 236263508Sdim 237263508Sdim free(buffer); 238263508Sdim 239263508Sdim return (0); 240263508Sdim} 241263508Sdim 242263508Sdimstatic int 243263508Sdimkread(kvm_t *kvm, void *kvm_pointer, void *address, size_t size, 244263508Sdim size_t offset) 245263508Sdim{ 246263508Sdim ssize_t ret; 247263508Sdim 248263508Sdim ret = kvm_read(kvm, (unsigned long)kvm_pointer + offset, address, 249263508Sdim size); 250263508Sdim if (ret < 0) 251263508Sdim return (MEMSTAT_ERROR_KVM); 252263508Sdim if ((size_t)ret != size) 253263508Sdim return (MEMSTAT_ERROR_KVM_SHORTREAD); 254263508Sdim return (0); 255263508Sdim} 256263508Sdim 257263508Sdimstatic int 258263508Sdimkread_string(kvm_t *kvm, const void *kvm_pointer, char *buffer, int buflen) 259263508Sdim{ 260263508Sdim ssize_t ret; 261263508Sdim int i; 262263508Sdim 263263508Sdim for (i = 0; i < buflen; i++) { 264263508Sdim ret = kvm_read(kvm, (unsigned long)kvm_pointer + i, 265249259Sdim &(buffer[i]), sizeof(char)); 266249259Sdim if (ret < 0) 267249259Sdim return (MEMSTAT_ERROR_KVM); 268249259Sdim if ((size_t)ret != sizeof(char)) 269249259Sdim return (MEMSTAT_ERROR_KVM_SHORTREAD); 270249259Sdim if (buffer[i] == '\0') 271249259Sdim return (0); 272249259Sdim } 273249259Sdim /* Truncate. */ 274249259Sdim buffer[i-1] = '\0'; 275249259Sdim return (0); 276249259Sdim} 277249259Sdim 278249259Sdimstatic int 279263508Sdimkread_symbol(kvm_t *kvm, int index, void *address, size_t size, 280263508Sdim size_t offset) 281263508Sdim{ 282263508Sdim ssize_t ret; 283263508Sdim 284263508Sdim ret = kvm_read(kvm, namelist[index].n_value + offset, address, size); 285251662Sdim if (ret < 0) 286251662Sdim return (MEMSTAT_ERROR_KVM); 287251662Sdim if ((size_t)ret != size) 288249259Sdim return (MEMSTAT_ERROR_KVM_SHORTREAD); 289249259Sdim return (0); 290249259Sdim} 291263508Sdim 292263508Sdim/* 293249259Sdim * memstat_kvm_uma() is similar to memstat_sysctl_uma(), only it extracts 294263508Sdim * UMA(9) statistics from a kernel core/memory file. 295263508Sdim */ 296263508Sdimint 297263508Sdimmemstat_kvm_uma(struct memory_type_list *list, void *kvm_handle) 298263508Sdim{ 299263508Sdim LIST_HEAD(, uma_keg) uma_kegs; 300263508Sdim struct memory_type *mtp; 301263508Sdim struct uma_bucket *ubp, ub; 302249259Sdim struct uma_cache *ucp, *ucp_array; 303251662Sdim struct uma_zone *uzp, uz; 304251662Sdim struct uma_keg *kzp, kz; 305249259Sdim int hint_dontsearch, i, mp_maxid, ret; 306249259Sdim char name[MEMTYPE_MAXNAME]; 307249259Sdim cpuset_t all_cpus; 308251662Sdim long cpusetsize; 309249259Sdim kvm_t *kvm; 310249259Sdim 311251662Sdim kvm = (kvm_t *)kvm_handle; 312249259Sdim hint_dontsearch = LIST_EMPTY(&list->mtl_list); 313249259Sdim if (kvm_nlist(kvm, namelist) != 0) { 314249259Sdim list->mtl_error = MEMSTAT_ERROR_KVM; 315249259Sdim return (-1); 316249259Sdim } 317249259Sdim if (namelist[X_UMA_KEGS].n_type == 0 || 318249259Sdim namelist[X_UMA_KEGS].n_value == 0) { 319249259Sdim list->mtl_error = MEMSTAT_ERROR_KVM_NOSYMBOL; 320249259Sdim return (-1); 321249259Sdim } 322249259Sdim ret = kread_symbol(kvm, X_MP_MAXID, &mp_maxid, sizeof(mp_maxid), 0); 323249259Sdim if (ret != 0) { 324249259Sdim list->mtl_error = ret; 325249259Sdim return (-1); 326249259Sdim } 327249259Sdim ret = kread_symbol(kvm, X_UMA_KEGS, &uma_kegs, sizeof(uma_kegs), 0); 328249259Sdim if (ret != 0) { 329263508Sdim list->mtl_error = ret; 330249259Sdim return (-1); 331263508Sdim } 332249259Sdim cpusetsize = sysconf(_SC_CPUSET_SIZE); 333249259Sdim if (cpusetsize == -1 || (u_long)cpusetsize > sizeof(cpuset_t)) { 334249259Sdim list->mtl_error = MEMSTAT_ERROR_KVM_NOSYMBOL; 335249259Sdim return (-1); 336249259Sdim } 337263508Sdim CPU_ZERO(&all_cpus); 338249259Sdim ret = kread_symbol(kvm, X_ALL_CPUS, &all_cpus, cpusetsize, 0); 339263508Sdim if (ret != 0) { 340249259Sdim list->mtl_error = ret; 341249259Sdim return (-1); 342249259Sdim } 343249259Sdim ucp_array = malloc(sizeof(struct uma_cache) * (mp_maxid + 1)); 344249259Sdim if (ucp_array == NULL) { 345249259Sdim list->mtl_error = MEMSTAT_ERROR_NOMEMORY; 346249259Sdim return (-1); 347249259Sdim } 348249259Sdim for (kzp = LIST_FIRST(&uma_kegs); kzp != NULL; kzp = 349249259Sdim LIST_NEXT(&kz, uk_link)) { 350249259Sdim ret = kread(kvm, kzp, &kz, sizeof(kz), 0); 351249259Sdim if (ret != 0) { 352249259Sdim free(ucp_array); 353249259Sdim _memstat_mtl_empty(list); 354249259Sdim list->mtl_error = ret; 355249259Sdim return (-1); 356249259Sdim } 357249259Sdim for (uzp = LIST_FIRST(&kz.uk_zones); uzp != NULL; uzp = 358249259Sdim LIST_NEXT(&uz, uz_link)) { 359249259Sdim ret = kread(kvm, uzp, &uz, sizeof(uz), 0); 360249259Sdim if (ret != 0) { 361249259Sdim free(ucp_array); 362249259Sdim _memstat_mtl_empty(list); 363249259Sdim list->mtl_error = ret; 364249259Sdim return (-1); 365249259Sdim } 366249259Sdim ret = kread(kvm, uzp, ucp_array, 367249259Sdim sizeof(struct uma_cache) * (mp_maxid + 1), 368249259Sdim offsetof(struct uma_zone, uz_cpu[0])); 369249259Sdim if (ret != 0) { 370249259Sdim free(ucp_array); 371249259Sdim _memstat_mtl_empty(list); 372249259Sdim list->mtl_error = ret; 373249259Sdim return (-1); 374249259Sdim } 375249259Sdim ret = kread_string(kvm, uz.uz_name, name, 376249259Sdim MEMTYPE_MAXNAME); 377249259Sdim if (ret != 0) { 378249259Sdim free(ucp_array); 379249259Sdim _memstat_mtl_empty(list); 380249259Sdim list->mtl_error = ret; 381249259Sdim return (-1); 382249259Sdim } 383249259Sdim if (hint_dontsearch == 0) { 384249259Sdim mtp = memstat_mtl_find(list, ALLOCATOR_UMA, 385249259Sdim name); 386249259Sdim } else 387249259Sdim mtp = NULL; 388249259Sdim if (mtp == NULL) 389249259Sdim mtp = _memstat_mt_allocate(list, ALLOCATOR_UMA, 390249259Sdim name, mp_maxid + 1); 391249259Sdim if (mtp == NULL) { 392249259Sdim free(ucp_array); 393249259Sdim _memstat_mtl_empty(list); 394249259Sdim list->mtl_error = MEMSTAT_ERROR_NOMEMORY; 395249259Sdim return (-1); 396249259Sdim } 397249259Sdim /* 398249259Sdim * Reset the statistics on a current node. 399263508Sdim */ 400249259Sdim _memstat_mt_reset_stats(mtp, mp_maxid + 1); 401249259Sdim mtp->mt_numallocs = uz.uz_allocs; 402249259Sdim mtp->mt_numfrees = uz.uz_frees; 403249259Sdim mtp->mt_failures = uz.uz_fails; 404249259Sdim mtp->mt_sleeps = uz.uz_sleeps; 405249259Sdim if (kz.uk_flags & UMA_ZFLAG_INTERNAL) 406249259Sdim goto skip_percpu; 407249259Sdim for (i = 0; i < mp_maxid + 1; i++) { 408249259Sdim if (!CPU_ISSET(i, &all_cpus)) 409249259Sdim continue; 410249259Sdim ucp = &ucp_array[i]; 411249259Sdim mtp->mt_numallocs += ucp->uc_allocs; 412249259Sdim mtp->mt_numfrees += ucp->uc_frees; 413249259Sdim 414249259Sdim if (ucp->uc_allocbucket != NULL) { 415249259Sdim ret = kread(kvm, ucp->uc_allocbucket, 416249259Sdim &ub, sizeof(ub), 0); 417249259Sdim if (ret != 0) { 418263508Sdim free(ucp_array); 419263508Sdim _memstat_mtl_empty(list); 420249259Sdim list->mtl_error = ret; 421249259Sdim return (-1); 422249259Sdim } 423249259Sdim mtp->mt_free += ub.ub_cnt; 424249259Sdim } 425249259Sdim if (ucp->uc_freebucket != NULL) { 426249259Sdim ret = kread(kvm, ucp->uc_freebucket, 427249259Sdim &ub, sizeof(ub), 0); 428249259Sdim if (ret != 0) { 429249259Sdim free(ucp_array); 430249259Sdim _memstat_mtl_empty(list); 431249259Sdim list->mtl_error = ret; 432249259Sdim return (-1); 433249259Sdim } 434249259Sdim mtp->mt_free += ub.ub_cnt; 435249259Sdim } 436263508Sdim } 437263508Sdimskip_percpu: 438263508Sdim mtp->mt_size = kz.uk_size; 439263508Sdim mtp->mt_rsize = kz.uk_rsize; 440263508Sdim mtp->mt_memalloced = mtp->mt_numallocs * mtp->mt_size; 441251662Sdim mtp->mt_memfreed = mtp->mt_numfrees * mtp->mt_size; 442249259Sdim mtp->mt_bytes = mtp->mt_memalloced - mtp->mt_memfreed; 443263508Sdim if (kz.uk_ppera > 1) 444251662Sdim mtp->mt_countlimit = kz.uk_maxpages / 445249259Sdim kz.uk_ipers; 446251662Sdim else 447249259Sdim mtp->mt_countlimit = kz.uk_maxpages * 448263508Sdim kz.uk_ipers; 449263508Sdim mtp->mt_byteslimit = mtp->mt_countlimit * mtp->mt_size; 450263508Sdim mtp->mt_count = mtp->mt_numallocs - mtp->mt_numfrees; 451263508Sdim for (ubp = LIST_FIRST(&uz.uz_buckets); ubp != 452263508Sdim NULL; ubp = LIST_NEXT(&ub, ub_link)) { 453263508Sdim ret = kread(kvm, ubp, &ub, sizeof(ub), 0); 454263508Sdim mtp->mt_zonefree += ub.ub_cnt; 455263508Sdim } 456263508Sdim if (!((kz.uk_flags & UMA_ZONE_SECONDARY) && 457263508Sdim LIST_FIRST(&kz.uk_zones) != uzp)) { 458263508Sdim mtp->mt_kegfree = kz.uk_free; 459263508Sdim mtp->mt_free += mtp->mt_kegfree; 460263508Sdim } 461263508Sdim mtp->mt_free += mtp->mt_zonefree; 462263508Sdim } 463263508Sdim } 464263508Sdim free(ucp_array); 465263508Sdim return (0); 466263508Sdim} 467263508Sdim