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$ 27 */ 28 29 #ifdef FSTACK 30 #include <stdint.h> 31 #endif 32 33 #include <sys/cdefs.h> 34 #include <sys/param.h> 35 #include <sys/malloc.h> 36 #include <sys/sysctl.h> 37 38 #include <err.h> 39 #include <errno.h> 40 #ifndef FSTACK 41 #include <kvm.h> 42 #endif 43 #include <nlist.h> 44 #include <stdio.h> 45 #include <stdlib.h> 46 #include <string.h> 47 48 #include "memstat.h" 49 #include "memstat_internal.h" 50 51 #ifndef FSTACK 52 static struct nlist namelist[] = { 53 #define X_KMEMSTATISTICS 0 54 { .n_name = "_kmemstatistics" }, 55 #define X_MP_MAXCPUS 1 56 { .n_name = "_mp_maxcpus" }, 57 { .n_name = "" }, 58 }; 59 #endif 60 61 /* 62 * Extract malloc(9) statistics from the running kernel, and store all memory 63 * type information in the passed list. For each type, check the list for an 64 * existing entry with the right name/allocator -- if present, update that 65 * entry. Otherwise, add a new entry. On error, the entire list will be 66 * cleared, as entries will be in an inconsistent state. 67 * 68 * To reduce the level of work for a list that starts empty, we keep around a 69 * hint as to whether it was empty when we began, so we can avoid searching 70 * the list for entries to update. Updates are O(n^2) due to searching for 71 * each entry before adding it. 72 */ 73 int 74 memstat_sysctl_malloc(struct memory_type_list *list, int flags) 75 { 76 struct malloc_type_stream_header *mtshp; 77 struct malloc_type_header *mthp; 78 struct malloc_type_stats *mtsp; 79 struct memory_type *mtp; 80 int count, hint_dontsearch, i, j, maxcpus; 81 char *buffer, *p; 82 size_t size; 83 84 hint_dontsearch = LIST_EMPTY(&list->mtl_list); 85 86 /* 87 * Query the number of CPUs, number of malloc types so that we can 88 * guess an initial buffer size. We loop until we succeed or really 89 * fail. Note that the value of maxcpus we query using sysctl is not 90 * the version we use when processing the real data -- that is read 91 * from the header. 92 */ 93 retry: 94 size = sizeof(maxcpus); 95 if (sysctlbyname("kern.smp.maxcpus", &maxcpus, &size, NULL, 0) < 0) { 96 if (errno == EACCES || errno == EPERM) 97 list->mtl_error = MEMSTAT_ERROR_PERMISSION; 98 else 99 list->mtl_error = MEMSTAT_ERROR_DATAERROR; 100 return (-1); 101 } 102 if (size != sizeof(maxcpus)) { 103 list->mtl_error = MEMSTAT_ERROR_DATAERROR; 104 return (-1); 105 } 106 107 size = sizeof(count); 108 if (sysctlbyname("kern.malloc_count", &count, &size, NULL, 0) < 0) { 109 if (errno == EACCES || errno == EPERM) 110 list->mtl_error = MEMSTAT_ERROR_PERMISSION; 111 else 112 list->mtl_error = MEMSTAT_ERROR_VERSION; 113 return (-1); 114 } 115 if (size != sizeof(count)) { 116 list->mtl_error = MEMSTAT_ERROR_DATAERROR; 117 return (-1); 118 } 119 120 size = sizeof(*mthp) + count * (sizeof(*mthp) + sizeof(*mtsp) * 121 maxcpus); 122 123 buffer = malloc(size); 124 if (buffer == NULL) { 125 list->mtl_error = MEMSTAT_ERROR_NOMEMORY; 126 return (-1); 127 } 128 129 if (sysctlbyname("kern.malloc_stats", buffer, &size, NULL, 0) < 0) { 130 /* 131 * XXXRW: ENOMEM is an ambiguous return, we should bound the 132 * number of loops, perhaps. 133 */ 134 if (errno == ENOMEM) { 135 free(buffer); 136 goto retry; 137 } 138 if (errno == EACCES || errno == EPERM) 139 list->mtl_error = MEMSTAT_ERROR_PERMISSION; 140 else 141 list->mtl_error = MEMSTAT_ERROR_VERSION; 142 free(buffer); 143 return (-1); 144 } 145 146 if (size == 0) { 147 free(buffer); 148 return (0); 149 } 150 151 if (size < sizeof(*mtshp)) { 152 list->mtl_error = MEMSTAT_ERROR_VERSION; 153 free(buffer); 154 return (-1); 155 } 156 p = buffer; 157 mtshp = (struct malloc_type_stream_header *)p; 158 p += sizeof(*mtshp); 159 160 if (mtshp->mtsh_version != MALLOC_TYPE_STREAM_VERSION) { 161 list->mtl_error = MEMSTAT_ERROR_VERSION; 162 free(buffer); 163 return (-1); 164 } 165 166 /* 167 * For the remainder of this function, we are quite trusting about 168 * the layout of structures and sizes, since we've determined we have 169 * a matching version and acceptable CPU count. 170 */ 171 maxcpus = mtshp->mtsh_maxcpus; 172 count = mtshp->mtsh_count; 173 for (i = 0; i < count; i++) { 174 mthp = (struct malloc_type_header *)p; 175 p += sizeof(*mthp); 176 177 if (hint_dontsearch == 0) { 178 mtp = memstat_mtl_find(list, ALLOCATOR_MALLOC, 179 mthp->mth_name); 180 } else 181 mtp = NULL; 182 if (mtp == NULL) 183 mtp = _memstat_mt_allocate(list, ALLOCATOR_MALLOC, 184 mthp->mth_name, maxcpus); 185 if (mtp == NULL) { 186 _memstat_mtl_empty(list); 187 free(buffer); 188 list->mtl_error = MEMSTAT_ERROR_NOMEMORY; 189 return (-1); 190 } 191 192 /* 193 * Reset the statistics on a current node. 194 */ 195 _memstat_mt_reset_stats(mtp, maxcpus); 196 197 for (j = 0; j < maxcpus; j++) { 198 mtsp = (struct malloc_type_stats *)p; 199 p += sizeof(*mtsp); 200 201 /* 202 * Sumarize raw statistics across CPUs into coalesced 203 * statistics. 204 */ 205 mtp->mt_memalloced += mtsp->mts_memalloced; 206 mtp->mt_memfreed += mtsp->mts_memfreed; 207 mtp->mt_numallocs += mtsp->mts_numallocs; 208 mtp->mt_numfrees += mtsp->mts_numfrees; 209 mtp->mt_sizemask |= mtsp->mts_size; 210 211 /* 212 * Copies of per-CPU statistics. 213 */ 214 mtp->mt_percpu_alloc[j].mtp_memalloced = 215 mtsp->mts_memalloced; 216 mtp->mt_percpu_alloc[j].mtp_memfreed = 217 mtsp->mts_memfreed; 218 mtp->mt_percpu_alloc[j].mtp_numallocs = 219 mtsp->mts_numallocs; 220 mtp->mt_percpu_alloc[j].mtp_numfrees = 221 mtsp->mts_numfrees; 222 mtp->mt_percpu_alloc[j].mtp_sizemask = 223 mtsp->mts_size; 224 } 225 226 /* 227 * Derived cross-CPU statistics. 228 */ 229 mtp->mt_bytes = mtp->mt_memalloced - mtp->mt_memfreed; 230 mtp->mt_count = mtp->mt_numallocs - mtp->mt_numfrees; 231 } 232 233 free(buffer); 234 235 return (0); 236 } 237 238 #ifndef FSTACK 239 static int 240 kread(kvm_t *kvm, void *kvm_pointer, void *address, size_t size, 241 size_t offset) 242 { 243 ssize_t ret; 244 245 ret = kvm_read(kvm, (unsigned long)kvm_pointer + offset, address, 246 size); 247 if (ret < 0) 248 return (MEMSTAT_ERROR_KVM); 249 if ((size_t)ret != size) 250 return (MEMSTAT_ERROR_KVM_SHORTREAD); 251 return (0); 252 } 253 254 static int 255 kread_string(kvm_t *kvm, const void *kvm_pointer, char *buffer, int buflen) 256 { 257 ssize_t ret; 258 int i; 259 260 for (i = 0; i < buflen; i++) { 261 ret = kvm_read(kvm, __DECONST(unsigned long, kvm_pointer) + 262 i, &(buffer[i]), sizeof(char)); 263 if (ret < 0) 264 return (MEMSTAT_ERROR_KVM); 265 if ((size_t)ret != sizeof(char)) 266 return (MEMSTAT_ERROR_KVM_SHORTREAD); 267 if (buffer[i] == '\0') 268 return (0); 269 } 270 /* Truncate. */ 271 buffer[i-1] = '\0'; 272 return (0); 273 } 274 275 static int 276 kread_symbol(kvm_t *kvm, int index, void *address, size_t size, 277 size_t offset) 278 { 279 ssize_t ret; 280 281 ret = kvm_read(kvm, namelist[index].n_value + offset, address, size); 282 if (ret < 0) 283 return (MEMSTAT_ERROR_KVM); 284 if ((size_t)ret != size) 285 return (MEMSTAT_ERROR_KVM_SHORTREAD); 286 return (0); 287 } 288 289 int 290 memstat_kvm_malloc(struct memory_type_list *list, void *kvm_handle) 291 { 292 struct memory_type *mtp; 293 void *kmemstatistics; 294 int hint_dontsearch, j, mp_maxcpus, ret; 295 char name[MEMTYPE_MAXNAME]; 296 struct malloc_type_stats *mts, *mtsp; 297 struct malloc_type_internal *mtip; 298 struct malloc_type type, *typep; 299 kvm_t *kvm; 300 301 kvm = (kvm_t *)kvm_handle; 302 303 hint_dontsearch = LIST_EMPTY(&list->mtl_list); 304 305 if (kvm_nlist(kvm, namelist) != 0) { 306 list->mtl_error = MEMSTAT_ERROR_KVM; 307 return (-1); 308 } 309 310 if (namelist[X_KMEMSTATISTICS].n_type == 0 || 311 namelist[X_KMEMSTATISTICS].n_value == 0) { 312 list->mtl_error = MEMSTAT_ERROR_KVM_NOSYMBOL; 313 return (-1); 314 } 315 316 ret = kread_symbol(kvm, X_MP_MAXCPUS, &mp_maxcpus, 317 sizeof(mp_maxcpus), 0); 318 if (ret != 0) { 319 list->mtl_error = ret; 320 return (-1); 321 } 322 323 ret = kread_symbol(kvm, X_KMEMSTATISTICS, &kmemstatistics, 324 sizeof(kmemstatistics), 0); 325 if (ret != 0) { 326 list->mtl_error = ret; 327 return (-1); 328 } 329 330 mts = malloc(sizeof(struct malloc_type_stats) * mp_maxcpus); 331 if (mts == NULL) { 332 list->mtl_error = MEMSTAT_ERROR_NOMEMORY; 333 return (-1); 334 } 335 336 for (typep = kmemstatistics; typep != NULL; typep = type.ks_next) { 337 ret = kread(kvm, typep, &type, sizeof(type), 0); 338 if (ret != 0) { 339 _memstat_mtl_empty(list); 340 free(mts); 341 list->mtl_error = ret; 342 return (-1); 343 } 344 ret = kread_string(kvm, (void *)type.ks_shortdesc, name, 345 MEMTYPE_MAXNAME); 346 if (ret != 0) { 347 _memstat_mtl_empty(list); 348 free(mts); 349 list->mtl_error = ret; 350 return (-1); 351 } 352 353 /* 354 * Since our compile-time value for MAXCPU may differ from the 355 * kernel's, we populate our own array. 356 */ 357 mtip = type.ks_handle; 358 ret = kread(kvm, mtip->mti_stats, mts, mp_maxcpus * 359 sizeof(struct malloc_type_stats), 0); 360 if (ret != 0) { 361 _memstat_mtl_empty(list); 362 free(mts); 363 list->mtl_error = ret; 364 return (-1); 365 } 366 367 if (hint_dontsearch == 0) { 368 mtp = memstat_mtl_find(list, ALLOCATOR_MALLOC, name); 369 } else 370 mtp = NULL; 371 if (mtp == NULL) 372 mtp = _memstat_mt_allocate(list, ALLOCATOR_MALLOC, 373 name, mp_maxcpus); 374 if (mtp == NULL) { 375 _memstat_mtl_empty(list); 376 free(mts); 377 list->mtl_error = MEMSTAT_ERROR_NOMEMORY; 378 return (-1); 379 } 380 381 /* 382 * This logic is replicated from kern_malloc.c, and should 383 * be kept in sync. 384 */ 385 _memstat_mt_reset_stats(mtp, mp_maxcpus); 386 for (j = 0; j < mp_maxcpus; j++) { 387 mtsp = &mts[j]; 388 mtp->mt_memalloced += mtsp->mts_memalloced; 389 mtp->mt_memfreed += mtsp->mts_memfreed; 390 mtp->mt_numallocs += mtsp->mts_numallocs; 391 mtp->mt_numfrees += mtsp->mts_numfrees; 392 mtp->mt_sizemask |= mtsp->mts_size; 393 394 mtp->mt_percpu_alloc[j].mtp_memalloced = 395 mtsp->mts_memalloced; 396 mtp->mt_percpu_alloc[j].mtp_memfreed = 397 mtsp->mts_memfreed; 398 mtp->mt_percpu_alloc[j].mtp_numallocs = 399 mtsp->mts_numallocs; 400 mtp->mt_percpu_alloc[j].mtp_numfrees = 401 mtsp->mts_numfrees; 402 mtp->mt_percpu_alloc[j].mtp_sizemask = 403 mtsp->mts_size; 404 } 405 406 mtp->mt_bytes = mtp->mt_memalloced - mtp->mt_memfreed; 407 mtp->mt_count = mtp->mt_numallocs - mtp->mt_numfrees; 408 } 409 410 return (0); 411 } 412 #endif 413 414