xref: /f-stack/tools/libmemstat/memstat_malloc.c (revision 8cf1d457)
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