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