memstat_uma.c revision 155549
1/*- 2 * Copyright (c) 2005 Robert N. M. Watson 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 * 26 * $FreeBSD: head/lib/libmemstat/memstat_uma.c 155549 2006-02-11 18:55:03Z rwatson $ 27 */ 28 29#include <sys/param.h> 30#include <sys/sysctl.h> 31 32#define LIBMEMSTAT /* Cause vm_page.h not to include opt_vmpage.h */ 33#include <vm/vm.h> 34#include <vm/vm_page.h> 35 36#include <vm/uma.h> 37#include <vm/uma_int.h> 38 39#include <err.h> 40#include <errno.h> 41#include <kvm.h> 42#include <nlist.h> 43#include <stdio.h> 44#include <stdlib.h> 45#include <string.h> 46 47#include "memstat.h" 48#include "memstat_internal.h" 49 50static struct nlist namelist[] = { 51#define X_UMA_KEGS 0 52 { .n_name = "_uma_kegs" }, 53#define X_MP_MAXID 1 54 { .n_name = "_mp_maxid" }, 55#define X_ALL_CPUS 2 56 { .n_name = "_all_cpus" }, 57 { .n_name = "" }, 58}; 59 60/* 61 * Extract uma(9) statistics from the running kernel, and store all memory 62 * type information in the passed list. For each type, check the list for an 63 * existing entry with the right name/allocator -- if present, update that 64 * entry. Otherwise, add a new entry. On error, the entire list will be 65 * cleared, as entries will be in an inconsistent state. 66 * 67 * To reduce the level of work for a list that starts empty, we keep around a 68 * hint as to whether it was empty when we began, so we can avoid searching 69 * the list for entries to update. Updates are O(n^2) due to searching for 70 * each entry before adding it. 71 */ 72int 73memstat_sysctl_uma(struct memory_type_list *list, int flags) 74{ 75 struct uma_stream_header *ushp; 76 struct uma_type_header *uthp; 77 struct uma_percpu_stat *upsp; 78 struct memory_type *mtp; 79 int count, hint_dontsearch, i, j, maxcpus; 80 char *buffer, *p; 81 size_t size; 82 83 hint_dontsearch = LIST_EMPTY(&list->mtl_list); 84 85 /* 86 * Query the number of CPUs, number of malloc types so that we can 87 * guess an initial buffer size. We loop until we succeed or really 88 * fail. Note that the value of maxcpus we query using sysctl is not 89 * the version we use when processing the real data -- that is read 90 * from the header. 91 */ 92retry: 93 size = sizeof(maxcpus); 94 if (sysctlbyname("kern.smp.maxcpus", &maxcpus, &size, NULL, 0) < 0) { 95 if (errno == EACCES || errno == EPERM) 96 list->mtl_error = MEMSTAT_ERROR_PERMISSION; 97 else 98 list->mtl_error = MEMSTAT_ERROR_DATAERROR; 99 return (-1); 100 } 101 if (size != sizeof(maxcpus)) { 102 list->mtl_error = MEMSTAT_ERROR_DATAERROR; 103 return (-1); 104 } 105 106 if (maxcpus > MEMSTAT_MAXCPU) { 107 list->mtl_error = MEMSTAT_ERROR_TOOMANYCPUS; 108 return (-1); 109 } 110 111 size = sizeof(count); 112 if (sysctlbyname("vm.zone_count", &count, &size, NULL, 0) < 0) { 113 if (errno == EACCES || errno == EPERM) 114 list->mtl_error = MEMSTAT_ERROR_PERMISSION; 115 else 116 list->mtl_error = MEMSTAT_ERROR_VERSION; 117 return (-1); 118 } 119 if (size != sizeof(count)) { 120 list->mtl_error = MEMSTAT_ERROR_DATAERROR; 121 return (-1); 122 } 123 124 size = sizeof(*uthp) + count * (sizeof(*uthp) + sizeof(*upsp) * 125 maxcpus); 126 127 buffer = malloc(size); 128 if (buffer == NULL) { 129 list->mtl_error = MEMSTAT_ERROR_NOMEMORY; 130 return (-1); 131 } 132 133 if (sysctlbyname("vm.zone_stats", buffer, &size, NULL, 0) < 0) { 134 /* 135 * XXXRW: ENOMEM is an ambiguous return, we should bound the 136 * number of loops, perhaps. 137 */ 138 if (errno == ENOMEM) { 139 free(buffer); 140 goto retry; 141 } 142 if (errno == EACCES || errno == EPERM) 143 list->mtl_error = MEMSTAT_ERROR_PERMISSION; 144 else 145 list->mtl_error = MEMSTAT_ERROR_VERSION; 146 free(buffer); 147 return (-1); 148 } 149 150 if (size == 0) { 151 free(buffer); 152 return (0); 153 } 154 155 if (size < sizeof(*ushp)) { 156 list->mtl_error = MEMSTAT_ERROR_VERSION; 157 free(buffer); 158 return (-1); 159 } 160 p = buffer; 161 ushp = (struct uma_stream_header *)p; 162 p += sizeof(*ushp); 163 164 if (ushp->ush_version != UMA_STREAM_VERSION) { 165 list->mtl_error = MEMSTAT_ERROR_VERSION; 166 free(buffer); 167 return (-1); 168 } 169 170 if (ushp->ush_maxcpus > MEMSTAT_MAXCPU) { 171 list->mtl_error = MEMSTAT_ERROR_TOOMANYCPUS; 172 free(buffer); 173 return (-1); 174 } 175 176 /* 177 * For the remainder of this function, we are quite trusting about 178 * the layout of structures and sizes, since we've determined we have 179 * a matching version and acceptable CPU count. 180 */ 181 maxcpus = ushp->ush_maxcpus; 182 count = ushp->ush_count; 183 for (i = 0; i < count; i++) { 184 uthp = (struct uma_type_header *)p; 185 p += sizeof(*uthp); 186 187 if (hint_dontsearch == 0) { 188 mtp = memstat_mtl_find(list, ALLOCATOR_UMA, 189 uthp->uth_name); 190 } else 191 mtp = NULL; 192 if (mtp == NULL) 193 mtp = _memstat_mt_allocate(list, ALLOCATOR_UMA, 194 uthp->uth_name); 195 if (mtp == NULL) { 196 _memstat_mtl_empty(list); 197 free(buffer); 198 list->mtl_error = MEMSTAT_ERROR_NOMEMORY; 199 return (-1); 200 } 201 202 /* 203 * Reset the statistics on a current node. 204 */ 205 _memstat_mt_reset_stats(mtp); 206 207 mtp->mt_numallocs = uthp->uth_allocs; 208 mtp->mt_numfrees = uthp->uth_frees; 209 mtp->mt_failures = uthp->uth_fails; 210 211 for (j = 0; j < maxcpus; j++) { 212 upsp = (struct uma_percpu_stat *)p; 213 p += sizeof(*upsp); 214 215 mtp->mt_percpu_cache[j].mtp_free = 216 upsp->ups_cache_free; 217 mtp->mt_free += upsp->ups_cache_free; 218 mtp->mt_numallocs += upsp->ups_allocs; 219 mtp->mt_numfrees += upsp->ups_frees; 220 } 221 222 mtp->mt_size = uthp->uth_size; 223 mtp->mt_memalloced = mtp->mt_numallocs * uthp->uth_size; 224 mtp->mt_memfreed = mtp->mt_numfrees * uthp->uth_size; 225 mtp->mt_bytes = mtp->mt_memalloced - mtp->mt_memfreed; 226 mtp->mt_countlimit = uthp->uth_limit; 227 mtp->mt_byteslimit = uthp->uth_limit * uthp->uth_size; 228 229 mtp->mt_count = mtp->mt_numallocs - mtp->mt_numfrees; 230 mtp->mt_zonefree = uthp->uth_zone_free; 231 232 /* 233 * UMA secondary zones share a keg with the primary zone. To 234 * avoid double-reporting of free items, report keg free 235 * items only in the primary zone. 236 */ 237 if (!(uthp->uth_zone_flags & UTH_ZONE_SECONDARY)) { 238 mtp->mt_kegfree = uthp->uth_keg_free; 239 mtp->mt_free += mtp->mt_kegfree; 240 } 241 mtp->mt_free += mtp->mt_zonefree; 242 } 243 244 free(buffer); 245 246 return (0); 247} 248 249static int 250kread(kvm_t *kvm, void *kvm_pointer, void *address, size_t size, 251 size_t offset) 252{ 253 ssize_t ret; 254 255 ret = kvm_read(kvm, (unsigned long)kvm_pointer + offset, address, 256 size); 257 if (ret < 0) 258 return (MEMSTAT_ERROR_KVM); 259 if ((size_t)ret != size) 260 return (MEMSTAT_ERROR_KVM_SHORTREAD); 261 return (0); 262} 263 264static int 265kread_string(kvm_t *kvm, void *kvm_pointer, char *buffer, int buflen) 266{ 267 ssize_t ret; 268 int i; 269 270 for (i = 0; i < buflen; i++) { 271 ret = kvm_read(kvm, (unsigned long)kvm_pointer + i, 272 &(buffer[i]), sizeof(char)); 273 if (ret < 0) 274 return (MEMSTAT_ERROR_KVM); 275 if ((size_t)ret != sizeof(char)) 276 return (MEMSTAT_ERROR_KVM_SHORTREAD); 277 if (buffer[i] == '\0') 278 return (0); 279 } 280 /* Truncate. */ 281 buffer[i-1] = '\0'; 282 return (0); 283} 284 285static int 286kread_symbol(kvm_t *kvm, int index, void *address, size_t size, 287 size_t offset) 288{ 289 ssize_t ret; 290 291 ret = kvm_read(kvm, namelist[index].n_value + offset, address, size); 292 if (ret < 0) 293 return (MEMSTAT_ERROR_KVM); 294 if ((size_t)ret != size) 295 return (MEMSTAT_ERROR_KVM_SHORTREAD); 296 return (0); 297} 298 299/* 300 * memstat_kvm_uma() is similar to memstat_sysctl_uma(), only it extracts 301 * UMA(9) statistics from a kernel core/memory file. 302 */ 303int 304memstat_kvm_uma(struct memory_type_list *list, void *kvm_handle) 305{ 306 LIST_HEAD(, uma_keg) uma_kegs; 307 struct memory_type *mtp; 308 struct uma_bucket *ubp, ub; 309 struct uma_cache *ucp; 310 struct uma_zone *uzp, uz; 311 struct uma_keg *kzp, kz; 312 int hint_dontsearch, i, mp_maxid, ret; 313 char name[MEMTYPE_MAXNAME]; 314 __cpumask_t all_cpus; 315 kvm_t *kvm; 316 317 kvm = (kvm_t *)kvm_handle; 318 hint_dontsearch = LIST_EMPTY(&list->mtl_list); 319 if (kvm_nlist(kvm, namelist) != 0) { 320 list->mtl_error = MEMSTAT_ERROR_KVM; 321 return (-1); 322 } 323 if (namelist[X_UMA_KEGS].n_type == 0 || 324 namelist[X_UMA_KEGS].n_value == 0) { 325 list->mtl_error = MEMSTAT_ERROR_KVM_NOSYMBOL; 326 return (-1); 327 } 328 ret = kread_symbol(kvm, X_MP_MAXID, &mp_maxid, sizeof(mp_maxid), 0); 329 if (ret != 0) { 330 list->mtl_error = ret; 331 return (-1); 332 } 333 ret = kread_symbol(kvm, X_UMA_KEGS, &uma_kegs, sizeof(uma_kegs), 0); 334 if (ret != 0) { 335 list->mtl_error = ret; 336 return (-1); 337 } 338 ret = kread_symbol(kvm, X_ALL_CPUS, &all_cpus, sizeof(all_cpus), 0); 339 if (ret != 0) { 340 list->mtl_error = ret; 341 return (-1); 342 } 343 for (kzp = LIST_FIRST(&uma_kegs); kzp != NULL; kzp = 344 LIST_NEXT(&kz, uk_link)) { 345 ret = kread(kvm, kzp, &kz, sizeof(kz), 0); 346 if (ret != 0) { 347 _memstat_mtl_empty(list); 348 list->mtl_error = ret; 349 return (-1); 350 } 351 for (uzp = LIST_FIRST(&kz.uk_zones); uzp != NULL; uzp = 352 LIST_NEXT(&uz, uz_link)) { 353 ret = kread(kvm, uzp, &uz, sizeof(uz), 0); 354 if (ret != 0) { 355 _memstat_mtl_empty(list); 356 list->mtl_error = ret; 357 return (-1); 358 } 359 ret = kread_string(kvm, uz.uz_name, name, 360 MEMTYPE_MAXNAME); 361 if (ret != 0) { 362 _memstat_mtl_empty(list); 363 list->mtl_error = ret; 364 return (-1); 365 } 366 if (hint_dontsearch == 0) { 367 mtp = memstat_mtl_find(list, ALLOCATOR_UMA, 368 name); 369 } else 370 mtp = NULL; 371 if (mtp == NULL) 372 mtp = _memstat_mt_allocate(list, ALLOCATOR_UMA, 373 name); 374 if (mtp == NULL) { 375 _memstat_mtl_empty(list); 376 list->mtl_error = MEMSTAT_ERROR_NOMEMORY; 377 return (-1); 378 } 379 /* 380 * Reset the statistics on a current node. 381 */ 382 _memstat_mt_reset_stats(mtp); 383 mtp->mt_numallocs = uz.uz_allocs; 384 mtp->mt_numfrees = uz.uz_frees; 385 mtp->mt_failures = uz.uz_fails; 386 if (kz.uk_flags & UMA_ZFLAG_INTERNAL) 387 goto skip_percpu; 388 for (i = 0; i < mp_maxid + 1; i++) { 389 if ((all_cpus & (1 << i)) == 0) 390 continue; 391 ucp = &uz.uz_cpu[i]; 392 mtp->mt_numallocs += ucp->uc_allocs; 393 mtp->mt_numfrees += ucp->uc_frees; 394 395 if (ucp->uc_allocbucket != NULL) { 396 ret = kread(kvm, ucp->uc_allocbucket, 397 &ub, sizeof(ub), 0); 398 if (ret != 0) { 399 _memstat_mtl_empty(list); 400 list->mtl_error = ret; 401 return (-1); 402 } 403 mtp->mt_free += ub.ub_cnt; 404 } 405 if (ucp->uc_freebucket != NULL) { 406 ret = kread(kvm, ucp->uc_freebucket, 407 &ub, sizeof(ub), 0); 408 if (ret != 0) { 409 _memstat_mtl_empty(list); 410 list->mtl_error = ret; 411 return (-1); 412 } 413 mtp->mt_free += ub.ub_cnt; 414 } 415 } 416skip_percpu: 417 mtp->mt_size = kz.uk_size; 418 mtp->mt_memalloced = mtp->mt_numallocs * mtp->mt_size; 419 mtp->mt_memfreed = mtp->mt_numfrees * mtp->mt_size; 420 mtp->mt_bytes = mtp->mt_memalloced - mtp->mt_memfreed; 421 if (kz.uk_ppera > 1) 422 mtp->mt_countlimit = kz.uk_maxpages / 423 kz.uk_ipers; 424 else 425 mtp->mt_countlimit = kz.uk_maxpages * 426 kz.uk_ipers; 427 mtp->mt_byteslimit = mtp->mt_countlimit * mtp->mt_size; 428 mtp->mt_count = mtp->mt_numallocs - mtp->mt_numfrees; 429 for (ubp = LIST_FIRST(&uz.uz_full_bucket); ubp != 430 NULL; ubp = LIST_NEXT(&ub, ub_link)) { 431 ret = kread(kvm, ubp, &ub, sizeof(ub), 0); 432 mtp->mt_zonefree += ub.ub_cnt; 433 } 434 if (!((kz.uk_flags & UMA_ZONE_SECONDARY) && 435 LIST_FIRST(&kz.uk_zones) != uzp)) { 436 mtp->mt_kegfree = kz.uk_free; 437 mtp->mt_free += mtp->mt_kegfree; 438 } 439 mtp->mt_free += mtp->mt_zonefree; 440 } 441 } 442 return (0); 443} 444