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
27#include <sys/param.h>
28
29#include <vm/uma.h>
30#include <vm/uma_int.h>
31
32#include <err.h>
33#include <kvm.h>
34#include <limits.h>
35#include <memstat.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <unistd.h>
39
40static struct nlist namelist[] = {
41#define X_UMA_KEGS	0
42	{ .n_name = "_uma_kegs" },
43#define X_MP_MAXCPUS	1
44	{ .n_name = "_mp_maxcpus" },
45#define X_MP_MAXID	2
46	{ .n_name = "_mp_maxid" },
47#define	X_ALLCPU	3
48	{ .n_name = "_all_cpus" },
49	{ .n_name = "" },
50};
51
52static void
53usage(void)
54{
55
56	fprintf(stderr, "umastat [-M core [-N system]]\n");
57	exit(-1);
58}
59
60static int
61kread(kvm_t *kvm, void *kvm_pointer, void *address, size_t size,
62    size_t offset)
63{
64	ssize_t ret;
65
66	ret = kvm_read(kvm, (unsigned long)kvm_pointer + offset, address,
67	    size);
68	if (ret < 0)
69		return (MEMSTAT_ERROR_KVM);
70	if ((size_t)ret != size)
71		return (MEMSTAT_ERROR_KVM_SHORTREAD);
72	return (0);
73}
74
75static int
76kread_string(kvm_t *kvm, const void *kvm_pointer, char *buffer, int buflen)
77{
78	ssize_t ret;
79	int i;
80
81	for (i = 0; i < buflen; i++) {
82		ret = kvm_read(kvm, (unsigned long)kvm_pointer + i,
83		    &(buffer[i]), sizeof(char));
84		if (ret < 0)
85			return (MEMSTAT_ERROR_KVM);
86		if ((size_t)ret != sizeof(char))
87			return (MEMSTAT_ERROR_KVM_SHORTREAD);
88		if (buffer[i] == '\0')
89			return (0);
90	}
91	/* Truncate. */
92	buffer[i-1] = '\0';
93	return (0);
94}
95
96static int
97kread_symbol(kvm_t *kvm, int index, void *address, size_t size,
98    size_t offset)
99{
100	ssize_t ret;
101
102	ret = kvm_read(kvm, namelist[index].n_value + offset, address, size);
103	if (ret < 0)
104		return (MEMSTAT_ERROR_KVM);
105	if ((size_t)ret != size)
106		return (MEMSTAT_ERROR_KVM_SHORTREAD);
107	return (0);
108}
109
110static const struct flaginfo {
111	u_int32_t	 fi_flag;
112	const char	*fi_name;
113} flaginfo[] = {
114	{ UMA_ZFLAG_MULTI, "multi" },
115	{ UMA_ZFLAG_DRAINING, "draining" },
116	{ UMA_ZFLAG_BUCKET, "bucket" },
117	{ UMA_ZFLAG_INTERNAL, "internal" },
118	{ UMA_ZFLAG_FULL, "full" },
119	{ UMA_ZFLAG_CACHEONLY, "cacheonly" },
120	{ UMA_ZONE_PAGEABLE, "pageable" },
121	{ UMA_ZONE_ZINIT, "zinit" },
122	{ UMA_ZONE_STATIC, "static" },
123	{ UMA_ZONE_OFFPAGE, "offpage" },
124	{ UMA_ZONE_MALLOC, "malloc" },
125	{ UMA_ZONE_NOFREE, "nofree" },
126	{ UMA_ZONE_MTXCLASS, "mtxclass" },
127	{ UMA_ZONE_VM, "vm" },
128	{ UMA_ZONE_HASH, "hash" },
129	{ UMA_ZONE_SECONDARY, "secondary" },
130	{ UMA_ZONE_MAXBUCKET, "maxbucket" },
131	{ UMA_ZONE_CACHESPREAD, "cachespread" },
132	{ UMA_ZONE_VTOSLAB, "vtoslab" },
133	{ UMA_ZONE_NODUMP, "nodump" },
134	{ UMA_ZONE_PCPU, "pcpu" },
135};
136static const int flaginfo_count = sizeof(flaginfo) / sizeof(struct flaginfo);
137
138static void
139uma_print_keg_flags(struct uma_keg *ukp, const char *spaces)
140{
141	int count, i;
142
143	if (!ukp->uk_flags) {
144		printf("%suk_flags = 0;\n", spaces);
145		return;
146	}
147
148	printf("%suk_flags = ", spaces);
149	for (i = 0, count = 0; i < flaginfo_count; i++) {
150		if (ukp->uk_flags & flaginfo[i].fi_flag) {
151			if (count++ > 0)
152				printf(" | ");
153			printf("%s", flaginfo[i].fi_name);
154		}
155
156	}
157	printf(";\n");
158}
159
160static void
161uma_print_keg_align(struct uma_keg *ukp, const char *spaces)
162{
163
164	switch(ukp->uk_align) {
165	case UMA_ALIGN_PTR:
166		printf("%suk_align = UMA_ALIGN_PTR;\n", spaces);
167		break;
168
169#if 0
170	case UMA_ALIGN_LONG:
171		printf("%suk_align = UMA_ALIGN_LONG;\n", spaces);
172		break;
173
174	case UMA_ALIGN_INT:
175		printf("%suk_align = UMA_ALIGN_INT;\n", spaces);
176		break;
177#endif
178
179	case UMA_ALIGN_SHORT:
180		printf("%suk_align = UMA_ALIGN_SHORT;\n", spaces);
181		break;
182
183	case UMA_ALIGN_CHAR:
184		printf("%suk_align = UMA_ALIGN_CHAR;\n", spaces);
185		break;
186
187	case UMA_ALIGN_CACHE:
188		printf("%suk_align = UMA_ALIGN_CACHE;\n", spaces);
189		break;
190
191	default:
192		printf("%suk_align = %d\n", spaces, ukp->uk_align);
193	}
194}
195
196LIST_HEAD(bucketlist, uma_bucket);
197
198static void
199uma_print_bucket(struct uma_bucket *ubp, const char *spaces __unused)
200{
201
202	printf("{ ub_cnt = %d, ub_entries = %d }", ubp->ub_cnt,
203	    ubp->ub_entries);
204}
205
206static void
207uma_print_bucketlist(kvm_t *kvm, struct bucketlist *bucketlist,
208    const char *name, const char *spaces)
209{
210	struct uma_bucket *ubp, ub;
211	uint64_t total_entries, total_cnt;
212	int count, ret;
213
214	printf("%s%s {", spaces, name);
215
216	total_entries = total_cnt = 0;
217	count = 0;
218	for (ubp = LIST_FIRST(bucketlist); ubp != NULL; ubp =
219	    LIST_NEXT(&ub, ub_link)) {
220		ret = kread(kvm, ubp, &ub, sizeof(ub), 0);
221		if (ret != 0)
222			errx(-1, "uma_print_bucketlist: %s", kvm_geterr(kvm));
223		if (count % 2 == 0)
224			printf("\n%s  ", spaces);
225		uma_print_bucket(&ub, "");
226		printf(" ");
227		total_entries += ub.ub_entries;
228		total_cnt += ub.ub_cnt;
229		count++;
230	}
231
232	printf("\n");
233	printf("%s};  // total cnt %ju, total entries %ju\n", spaces,
234	    total_cnt, total_entries);
235}
236
237static void
238uma_print_cache(kvm_t *kvm, struct uma_cache *cache, const char *name,
239    int cpu, const char *spaces, int *ub_cnt_add, int *ub_entries_add)
240{
241	struct uma_bucket ub;
242	int ret;
243
244	printf("%s%s[%d] = {\n", spaces, name, cpu);
245	printf("%s  uc_frees = %ju;\n", spaces, cache->uc_frees);
246	printf("%s  uc_allocs = %ju;\n", spaces, cache->uc_allocs);
247
248	if (cache->uc_freebucket != NULL) {
249		ret = kread(kvm, cache->uc_freebucket, &ub, sizeof(ub), 0);
250		if (ret != 0)
251			errx(-1, "uma_print_cache: %s", kvm_geterr(kvm));
252		printf("%s  uc_freebucket ", spaces);
253		uma_print_bucket(&ub, spaces);
254		printf(";\n");
255		if (ub_cnt_add != NULL)
256			*ub_cnt_add += ub.ub_cnt;
257		if (ub_entries_add != NULL)
258			*ub_entries_add += ub.ub_entries;
259	} else
260		printf("%s  uc_freebucket = NULL;\n", spaces);
261	if (cache->uc_allocbucket != NULL) {
262		ret = kread(kvm, cache->uc_allocbucket, &ub, sizeof(ub), 0);
263		if (ret != 0)
264			errx(-1, "uma_print_cache: %s", kvm_geterr(kvm));
265		printf("%s  uc_allocbucket ", spaces);
266		uma_print_bucket(&ub, spaces);
267		printf(";\n");
268		if (ub_cnt_add != NULL)
269			*ub_cnt_add += ub.ub_cnt;
270		if (ub_entries_add != NULL)
271			*ub_entries_add += ub.ub_entries;
272	} else
273		printf("%s  uc_allocbucket = NULL;\n", spaces);
274	printf("%s};\n", spaces);
275}
276
277int
278main(int argc, char *argv[])
279{
280	LIST_HEAD(, uma_keg) uma_kegs;
281	char name[MEMTYPE_MAXNAME];
282	struct uma_keg *kzp, kz;
283	struct uma_zone *uzp, *uzp_userspace;
284	kvm_t *kvm;
285	int all_cpus, cpu, mp_maxcpus, mp_maxid, ret, ub_cnt, ub_entries;
286	size_t uzp_userspace_len;
287	char *memf, *nlistf;
288	int ch;
289	char errbuf[_POSIX2_LINE_MAX];
290
291	memf = nlistf = NULL;
292	while ((ch = getopt(argc, argv, "M:N:")) != -1) {
293		switch (ch) {
294		case 'M':
295			memf = optarg;
296			break;
297		case 'N':
298			nlistf = optarg;
299			break;
300		default:
301			usage();
302		}
303	}
304	argc -= optind;
305	argv += optind;
306
307	if (argc != 0)
308		usage();
309	if (nlistf != NULL && memf == NULL)
310		usage();
311
312	kvm = kvm_openfiles(nlistf, memf, NULL, 0, errbuf);
313	if (kvm == NULL)
314		errx(-1, "kvm_openfiles: %s", errbuf);
315
316	if (kvm_nlist(kvm, namelist) != 0)
317		err(-1, "kvm_nlist");
318
319	if (namelist[X_UMA_KEGS].n_type == 0 ||
320	    namelist[X_UMA_KEGS].n_value == 0)
321		errx(-1, "kvm_nlist return");
322
323	ret = kread_symbol(kvm, X_MP_MAXCPUS, &mp_maxcpus, sizeof(mp_maxcpus),
324	    0);
325	if (ret != 0)
326		errx(-1, "kread_symbol: %s", kvm_geterr(kvm));
327
328	printf("mp_maxcpus = %d\n", mp_maxcpus);
329
330	ret = kread_symbol(kvm, X_MP_MAXID, &mp_maxid, sizeof(mp_maxid), 0);
331	if (ret != 0)
332		errx(-1, "kread_symbol: %s", kvm_geterr(kvm));
333
334	printf("mp_maxid = %d\n", mp_maxid);
335
336	ret = kread_symbol(kvm, X_ALLCPU, &all_cpus, sizeof(all_cpus), 0);
337	if (ret != 0)
338		errx(-1, "kread_symbol: %s", kvm_geterr(kvm));
339
340	printf("all_cpus = %x\n", all_cpus);
341
342	ret = kread_symbol(kvm, X_UMA_KEGS, &uma_kegs, sizeof(uma_kegs), 0);
343	if (ret != 0)
344		errx(-1, "kread_symbol: %s", kvm_geterr(kvm));
345
346	/*
347	 * uma_zone_t ends in an array of mp_maxid cache entries.  However,
348	 * it is statically declared as an array of size 1, so we need to
349	 * provide additional space.
350	 */
351	uzp_userspace_len = sizeof(struct uma_zone) + mp_maxid *
352	    sizeof(struct uma_cache);
353	uzp_userspace = malloc(uzp_userspace_len);
354	if (uzp_userspace == NULL)
355		err(-1, "malloc");
356
357	for (kzp = LIST_FIRST(&uma_kegs); kzp != NULL; kzp =
358	    LIST_NEXT(&kz, uk_link)) {
359		ret = kread(kvm, kzp, &kz, sizeof(kz), 0);
360		if (ret != 0) {
361			free(uzp_userspace);
362			errx(-1, "kread: %s", kvm_geterr(kvm));
363		}
364		printf("Keg {\n");
365
366		uma_print_keg_align(&kz, "  ");
367		printf("  uk_pages = %d\n", kz.uk_pages);
368		printf("  uk_free = %d\n", kz.uk_free);
369		printf("  uk_reserve = %d\n", kz.uk_reserve);
370		printf("  uk_size = %d\n", kz.uk_size);
371		printf("  uk_rsize = %d\n", kz.uk_rsize);
372		printf("  uk_maxpages = %d\n", kz.uk_maxpages);
373
374		printf("  uk_pgoff = %d\n", kz.uk_pgoff);
375		printf("  uk_ppera = %d\n", kz.uk_ppera);
376		printf("  uk_ipers = %d\n", kz.uk_ipers);
377		uma_print_keg_flags(&kz, "  ");
378
379		if (LIST_FIRST(&kz.uk_zones) == NULL) {
380			printf("; No zones.\n");
381			printf("};\n");
382			continue;
383		}
384		for (uzp = LIST_FIRST(&kz.uk_zones); uzp != NULL; uzp =
385		    LIST_NEXT(uzp_userspace, uz_link)) {
386			/*
387			 * We actually copy in twice: once with the base
388			 * structure, so that we can then decide if we also
389			 * need to copy in the caches.  This prevents us
390			 * from reading past the end of the base UMA zones,
391			 * which is unlikely to cause problems but could.
392			 */
393			ret = kread(kvm, uzp, uzp_userspace,
394			    sizeof(struct uma_zone), 0);
395			if (ret != 0) {
396				free(uzp_userspace);
397				errx(-1, "kread: %s", kvm_geterr(kvm));
398			}
399			if (!(kz.uk_flags & UMA_ZFLAG_INTERNAL)) {
400				ret = kread(kvm, uzp, uzp_userspace,
401				    uzp_userspace_len, 0);
402				if (ret != 0) {
403					free(uzp_userspace);
404					errx(-1, "kread: %s",
405					    kvm_geterr(kvm));
406				}
407			}
408			ret = kread_string(kvm, uzp_userspace->uz_name, name,
409			    MEMTYPE_MAXNAME);
410			if (ret != 0) {
411				free(uzp_userspace);
412				errx(-1, "kread_string: %s", kvm_geterr(kvm));
413			}
414			printf("  Zone {\n");
415			printf("    uz_name = \"%s\";\n", name);
416			printf("    uz_allocs = %lu;\n",
417			    uzp_userspace->uz_allocs);
418			printf("    uz_frees = %lu;\n",
419			    uzp_userspace->uz_frees);
420			printf("    uz_fails = %lu;\n",
421			    uzp_userspace->uz_fails);
422			printf("    uz_sleeps = %ju;\n",
423			    uzp_userspace->uz_sleeps);
424			printf("    uz_count = %u;\n",
425			    uzp_userspace->uz_count);
426			uma_print_bucketlist(kvm, (void *)
427			    &uzp_userspace->uz_buckets, "uz_buckets",
428			    "    ");
429
430			if (!(kz.uk_flags & UMA_ZFLAG_INTERNAL)) {
431				ub_cnt = ub_entries = 0;
432				for (cpu = 0; cpu <= mp_maxid; cpu++) {
433					/* if (CPU_ABSENT(cpu)) */
434					if ((all_cpus & (1 << cpu)) == 0)
435						continue;
436					uma_print_cache(kvm,
437					    &uzp_userspace->uz_cpu[cpu],
438					    "uc_cache", cpu, "    ", &ub_cnt,
439					    &ub_entries);
440				}
441				printf("    // %d cache total cnt, %d total "
442				    "entries\n", ub_cnt, ub_entries);
443			}
444
445			printf("  };\n");
446		}
447		printf("};\n");
448	}
449
450	free(uzp_userspace);
451	return (0);
452}
453