xref: /freebsd/lib/libmemstat/memstat_malloc.c (revision 94942af266ac119ede0ca836f9aa5a5ac0582938)
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