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