xref: /freebsd/usr.bin/procstat/procstat.c (revision 718cf2ccb9956613756ab15d7a0e28f2c8e91cab)
1 /*-
2  * Copyright (c) 2007, 2011 Robert N. M. Watson
3  * Copyright (c) 2015 Allan Jude <allanjude@freebsd.org>
4  * Copyright (c) 2017 Dell EMC
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/param.h>
32 #include <sys/sysctl.h>
33 #include <sys/user.h>
34 
35 #include <err.h>
36 #include <libprocstat.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <sysexits.h>
41 #include <unistd.h>
42 
43 #include "procstat.h"
44 
45 enum {
46 	PS_CMP_NORMAL = 0x00,
47 	PS_CMP_PLURAL = 0x01,
48 	PS_CMP_SUBSTR = 0x02
49 };
50 
51 struct procstat_cmd {
52 	const char *command;
53 	const char *xocontainer;
54 	const char *usage;
55 	void (*cmd)(struct procstat *, struct kinfo_proc *);
56 	void (*opt)(int, char * const *);
57 	int cmp;
58 };
59 
60 int procstat_opts = 0;
61 
62 static void cmdopt_none(int argc, char * const argv[]);
63 static void cmdopt_verbose(int argc, char * const argv[]);
64 static void cmdopt_signals(int argc, char * const argv[]);
65 static void cmdopt_rusage(int argc, char * const argv[]);
66 static void cmdopt_files(int argc, char * const argv[]);
67 static void cmdopt_cpuset(int argc, char * const argv[]);
68 
69 static const struct procstat_cmd cmd_table[] = {
70 	{ "argument", "arguments", NULL, &procstat_args, &cmdopt_none,
71 	    PS_CMP_PLURAL | PS_CMP_SUBSTR },
72 	{ "auxv", "auxv", NULL, &procstat_auxv, &cmdopt_none, PS_CMP_NORMAL },
73 	{ "basic", "basic", NULL, &procstat_basic, &cmdopt_none,
74 	    PS_CMP_NORMAL },
75 	{ "binary", "binary", NULL, &procstat_bin, &cmdopt_none,
76 	    PS_CMP_SUBSTR },
77 	{ "cpuset", "cs", NULL, &procstat_cs, &cmdopt_cpuset, PS_CMP_NORMAL },
78 	{ "cs", "cs", NULL, &procstat_cs, &cmdopt_cpuset, PS_CMP_NORMAL },
79 	{ "credential", "credentials", NULL, &procstat_cred, &cmdopt_none,
80 	    PS_CMP_PLURAL | PS_CMP_SUBSTR },
81 	{ "environment", "environment", NULL, &procstat_env, &cmdopt_none,
82 	    PS_CMP_SUBSTR },
83 	{ "fd", "files", "[-C]", &procstat_files, &cmdopt_files,
84 	    PS_CMP_PLURAL },
85 	{ "file", "files", "[-C]", &procstat_files, &cmdopt_files,
86 	    PS_CMP_PLURAL },
87 	{ "kstack", "kstack", "[-v]", &procstat_kstack, &cmdopt_verbose,
88 	    PS_CMP_NORMAL },
89 	{ "ptlwpinfo", "ptlwpinfo", NULL, &procstat_ptlwpinfo, &cmdopt_none,
90 	    PS_CMP_NORMAL },
91 	{ "rlimit", "rlimit", NULL, &procstat_rlimit, &cmdopt_none,
92 	    PS_CMP_NORMAL },
93 	{ "rusage", "rusage", "[-Ht]", &procstat_rusage, &cmdopt_rusage,
94 	    PS_CMP_NORMAL },
95 	{ "signal", "signals", "[-n]", &procstat_sigs, &cmdopt_signals,
96 	    PS_CMP_PLURAL | PS_CMP_SUBSTR },
97 	{ "thread", "threads", NULL, &procstat_threads, &cmdopt_none,
98 	    PS_CMP_PLURAL },
99 	{ "tsignal", "thread_signals", "[-n]", &procstat_threads_sigs,
100 	    &cmdopt_signals, PS_CMP_PLURAL | PS_CMP_SUBSTR },
101 	{ "vm", "vm", NULL, &procstat_vm, &cmdopt_none, PS_CMP_NORMAL }
102 };
103 
104 static void
105 usage(void)
106 {
107 	size_t i, l;
108 	int multi;
109 
110 	xo_error("usage: procstat [--libxo] [-h] [-M core] [-N system]"
111 	    " [-w interval] command\n"
112 	    "                [pid ... | core ...]\n"
113 	    "       procstat [--libxo] -a [-h] [-M core] [-N system] "
114 	    " [-w interval] command\n"
115 	    "       procstat [--libxo] [-h] [-M core] [-N system]"
116 	    " [-w interval]\n"
117 	    "                [-S | -b | -c | -e | -f [-C] | -i [-n] | "
118 	    "-j [-n] | -k [-k] |\n"
119 	    "                 -l | -r [-H] | -s | -t | -v | -x] "
120 	    "[pid ... | core ...]\n"
121 	    "       procstat [--libxo] -a [-h] [-M core] [-N system]"
122 	    " [-w interval]\n"
123 	    "                [-S | -b | -c | -e | -f [-C] | -i [-n] | "
124 	    "-j [-n] | -k [-k] |\n"
125 	    "                 -l | -r [-H] | -s | -t | -v | -x]\n"
126 	    "       procstat [--libxo] -L [-h] [-M core] [-N system] core ...\n"
127 	    "Available commands:\n");
128 	for (i = 0, l = nitems(cmd_table); i < l; i++) {
129 		multi = i + 1 < l && cmd_table[i].cmd == cmd_table[i + 1].cmd;
130 		xo_error("       %s%s%s", multi ? "[" : "",
131 		    cmd_table[i].command, (cmd_table[i].cmp & PS_CMP_PLURAL) ?
132 		    "(s)" : "");
133 		for (; i + 1 < l && cmd_table[i].cmd == cmd_table[i + 1].cmd;
134 		    i++)
135 			xo_error(" | %s%s", cmd_table[i + 1].command,
136 			    (cmd_table[i].cmp & PS_CMP_PLURAL) ? "(s)" : "");
137 		if (multi)
138 			xo_error("]");
139 		if (cmd_table[i].usage != NULL)
140 			xo_error(" %s", cmd_table[i].usage);
141 		xo_error("\n");
142 	}
143 	xo_finish();
144 	exit(EX_USAGE);
145 }
146 
147 static void
148 procstat(const struct procstat_cmd *cmd, struct procstat *prstat,
149     struct kinfo_proc *kipp)
150 {
151 	char *pidstr = NULL;
152 
153 	asprintf(&pidstr, "%d", kipp->ki_pid);
154 	if (pidstr == NULL)
155 		xo_errc(1, ENOMEM, "Failed to allocate memory in procstat()");
156 	xo_open_container(pidstr);
157 	cmd->cmd(prstat, kipp);
158 	xo_close_container(pidstr);
159 	free(pidstr);
160 }
161 
162 /*
163  * Sort processes first by pid and then tid.
164  */
165 static int
166 kinfo_proc_compare(const void *a, const void *b)
167 {
168 	int i;
169 
170 	i = ((const struct kinfo_proc *)a)->ki_pid -
171 	    ((const struct kinfo_proc *)b)->ki_pid;
172 	if (i != 0)
173 		return (i);
174 	i = ((const struct kinfo_proc *)a)->ki_tid -
175 	    ((const struct kinfo_proc *)b)->ki_tid;
176 	return (i);
177 }
178 
179 void
180 kinfo_proc_sort(struct kinfo_proc *kipp, int count)
181 {
182 
183 	qsort(kipp, count, sizeof(*kipp), kinfo_proc_compare);
184 }
185 
186 const char *
187 kinfo_proc_thread_name(const struct kinfo_proc *kipp)
188 {
189 	static char name[MAXCOMLEN+1];
190 
191 	strlcpy(name, kipp->ki_tdname, sizeof(name));
192 	strlcat(name, kipp->ki_moretdname, sizeof(name));
193 	if (name[0] == '\0' || strcmp(kipp->ki_comm, name) == 0) {
194 		name[0] = '-';
195 		name[1] = '\0';
196 	}
197 
198 	return (name);
199 }
200 
201 static const struct procstat_cmd *
202 getcmd(const char *str)
203 {
204 	const struct procstat_cmd *cmd;
205 	size_t i, l;
206 	int cmp, s;
207 
208 	if (str == NULL)
209 		return (NULL);
210 	cmd = NULL;
211 	if ((l = strlen(str)) == 0)
212 		return (getcmd("basic"));
213 	s = l > 1 && strcasecmp(str + l - 1, "s") == 0;
214 	for (i = 0; i < nitems(cmd_table); i++) {
215 		/*
216 		 * After the first match substring matches are disabled,
217 		 * allowing subsequent full matches to take precedence.
218 		 */
219 		if (cmd == NULL && (cmd_table[i].cmp & PS_CMP_SUBSTR))
220 			cmp = strncasecmp(str, cmd_table[i].command, l -
221 			    ((cmd_table[i].cmp & PS_CMP_PLURAL) && s ? 1 : 0));
222 		else if ((cmd_table[i].cmp & PS_CMP_PLURAL) && s &&
223 		    l == strlen(cmd_table[i].command) + 1)
224 			cmp = strncasecmp(str, cmd_table[i].command, l - 1);
225 		else
226 			cmp = strcasecmp(str, cmd_table[i].command);
227 		if (cmp == 0)
228 			cmd = &cmd_table[i];
229 	}
230 	return (cmd);
231 }
232 
233 int
234 main(int argc, char *argv[])
235 {
236 	int ch, interval;
237 	int i;
238 	struct kinfo_proc *p;
239 	const struct procstat_cmd *cmd;
240 	struct procstat *prstat, *cprstat;
241 	long l;
242 	pid_t pid;
243 	char *dummy;
244 	char *nlistf, *memf;
245 	int aflag;
246 	int cnt;
247 
248 	interval = 0;
249 	cmd = NULL;
250 	memf = nlistf = NULL;
251 	aflag = 0;
252 	argc = xo_parse_args(argc, argv);
253 
254 	while ((ch = getopt(argc, argv, "abCcefHhijkLlM:N:nrSstvw:x")) != -1) {
255 		switch (ch) {
256 		case 'a':
257 			aflag++;
258 			break;
259 		case 'b':
260 			if (cmd != NULL)
261 				usage();
262 			cmd = getcmd("binary");
263 			break;
264 		case 'C':
265 			procstat_opts |= PS_OPT_CAPABILITIES;
266 			break;
267 		case 'c':
268 			if (cmd != NULL)
269 				usage();
270 			cmd = getcmd("arguments");
271 			break;
272 		case 'e':
273 			if (cmd != NULL)
274 				usage();
275 			cmd = getcmd("environment");
276 			break;
277 		case 'f':
278 			if (cmd != NULL)
279 				usage();
280 			cmd = getcmd("files");
281 			break;
282 		case 'H':
283 			procstat_opts |= PS_OPT_PERTHREAD;
284 			break;
285 		case 'h':
286 			procstat_opts |= PS_OPT_NOHEADER;
287 			break;
288 		case 'i':
289 			if (cmd != NULL)
290 				usage();
291 			cmd = getcmd("signals");
292 			break;
293 		case 'j':
294 			if (cmd != NULL)
295 				usage();
296 			cmd = getcmd("tsignals");
297 			break;
298 		case 'k':
299 			if (cmd != NULL && cmd->cmd == procstat_kstack) {
300 				if ((procstat_opts & PS_OPT_VERBOSE) != 0)
301 					usage();
302 				procstat_opts |= PS_OPT_VERBOSE;
303 			} else {
304 				if (cmd != NULL)
305 					usage();
306 				cmd = getcmd("kstack");
307 			}
308 			break;
309 		case 'L':
310 			if (cmd != NULL)
311 				usage();
312 			cmd = getcmd("ptlwpinfo");
313 			break;
314 		case 'l':
315 			if (cmd != NULL)
316 				usage();
317 			cmd = getcmd("rlimit");
318 			break;
319 		case 'M':
320 			memf = optarg;
321 			break;
322 		case 'N':
323 			nlistf = optarg;
324 			break;
325 		case 'n':
326 			procstat_opts |= PS_OPT_SIGNUM;
327 			break;
328 		case 'r':
329 			if (cmd != NULL)
330 				usage();
331 			cmd = getcmd("rusage");
332 			break;
333 		case 'S':
334 			if (cmd != NULL)
335 				usage();
336 			cmd = getcmd("cpuset");
337 			break;
338 		case 's':
339 			if (cmd != NULL)
340 				usage();
341 			cmd = getcmd("credentials");
342 			break;
343 		case 't':
344 			if (cmd != NULL)
345 				usage();
346 			cmd = getcmd("threads");
347 			break;
348 		case 'v':
349 			if (cmd != NULL)
350 				usage();
351 			cmd = getcmd("vm");
352 			break;
353 		case 'w':
354 			l = strtol(optarg, &dummy, 10);
355 			if (*dummy != '\0')
356 				usage();
357 			if (l < 1 || l > INT_MAX)
358 				usage();
359 			interval = l;
360 			break;
361 		case 'x':
362 			if (cmd != NULL)
363 				usage();
364 			cmd = getcmd("auxv");
365 			break;
366 		case '?':
367 		default:
368 			usage();
369 		}
370 
371 	}
372 	argc -= optind;
373 	argv += optind;
374 
375 	if (cmd == NULL && argv[0] != NULL && (cmd = getcmd(argv[0])) != NULL) {
376 		if ((procstat_opts & PS_SUBCOMMAND_OPTS) != 0)
377 			usage();
378 		if (cmd->opt != NULL) {
379 			optreset = 1;
380 			optind = 1;
381 			cmd->opt(argc, argv);
382 			argc -= optind;
383 			argv += optind;
384 		} else {
385 			argc -= 1;
386 			argv += 1;
387 		}
388 	} else {
389 		if (cmd == NULL)
390 			cmd = getcmd("basic");
391 		if (cmd->cmd != procstat_files &&
392 		    (procstat_opts & PS_OPT_CAPABILITIES) != 0)
393 			usage();
394 	}
395 
396 	/* Must specify either the -a flag or a list of pids. */
397 	if (!(aflag == 1 && argc == 0) && !(aflag == 0 && argc > 0))
398 		usage();
399 
400 	if (memf != NULL)
401 		prstat = procstat_open_kvm(nlistf, memf);
402 	else
403 		prstat = procstat_open_sysctl();
404 	if (prstat == NULL)
405 		xo_errx(1, "procstat_open()");
406 	do {
407 		xo_set_version(PROCSTAT_XO_VERSION);
408 		xo_open_container("procstat");
409 		xo_open_container(cmd->xocontainer);
410 
411 		if (aflag) {
412 			p = procstat_getprocs(prstat, KERN_PROC_PROC, 0, &cnt);
413 			if (p == NULL)
414 				xo_errx(1, "procstat_getprocs()");
415 			kinfo_proc_sort(p, cnt);
416 			for (i = 0; i < cnt; i++) {
417 				procstat(cmd, prstat, &p[i]);
418 
419 				/* Suppress header after first process. */
420 				procstat_opts |= PS_OPT_NOHEADER;
421 				xo_flush();
422 			}
423 			procstat_freeprocs(prstat, p);
424 		}
425 		for (i = 0; i < argc; i++) {
426 			l = strtol(argv[i], &dummy, 10);
427 			if (*dummy == '\0') {
428 				if (l < 0)
429 					usage();
430 				pid = l;
431 
432 				p = procstat_getprocs(prstat, KERN_PROC_PID,
433 				    pid, &cnt);
434 				if (p == NULL)
435 					xo_errx(1, "procstat_getprocs()");
436 				if (cnt != 0)
437 					procstat(cmd, prstat, p);
438 				procstat_freeprocs(prstat, p);
439 			} else {
440 				cprstat = procstat_open_core(argv[i]);
441 				if (cprstat == NULL) {
442 					warnx("procstat_open()");
443 					continue;
444 				}
445 				p = procstat_getprocs(cprstat, KERN_PROC_PID,
446 				    -1, &cnt);
447 				if (p == NULL)
448 					xo_errx(1, "procstat_getprocs()");
449 				if (cnt != 0)
450 					procstat(cmd, cprstat, p);
451 				procstat_freeprocs(cprstat, p);
452 				procstat_close(cprstat);
453 			}
454 			/* Suppress header after first process. */
455 			procstat_opts |= PS_OPT_NOHEADER;
456 		}
457 
458 		xo_close_container(cmd->xocontainer);
459 		xo_close_container("procstat");
460 		xo_finish();
461 		if (interval)
462 			sleep(interval);
463 	} while (interval);
464 
465 	procstat_close(prstat);
466 
467 	exit(0);
468 }
469 
470 void
471 cmdopt_none(int argc, char * const argv[])
472 {
473 	int ch;
474 
475 	while ((ch = getopt(argc, argv, "")) != -1) {
476 		switch (ch) {
477 		case '?':
478 		default:
479 			usage();
480 		}
481 	}
482 }
483 
484 void
485 cmdopt_verbose(int argc, char * const argv[])
486 {
487 	int ch;
488 
489 	while ((ch = getopt(argc, argv, "v")) != -1) {
490 		switch (ch) {
491 		case 'v':
492 			procstat_opts |= PS_OPT_VERBOSE;
493 			break;
494 		case '?':
495 		default:
496 			usage();
497 		}
498 	}
499 }
500 
501 void
502 cmdopt_signals(int argc, char * const argv[])
503 {
504 	int ch;
505 
506 	while ((ch = getopt(argc, argv, "n")) != -1) {
507 		switch (ch) {
508 		case 'n':
509 			procstat_opts |= PS_OPT_SIGNUM;
510 			break;
511 		case '?':
512 		default:
513 			usage();
514 		}
515 	}
516 }
517 
518 void
519 cmdopt_rusage(int argc, char * const argv[])
520 {
521 	int ch;
522 
523 	while ((ch = getopt(argc, argv, "Ht")) != -1) {
524 		switch (ch) {
525 		case 'H':
526 			/* FALLTHROUGH */
527 		case 't':
528 			procstat_opts |= PS_OPT_PERTHREAD;
529 			break;
530 		case '?':
531 		default:
532 			usage();
533 		}
534 	}
535 }
536 
537 void
538 cmdopt_files(int argc, char * const argv[])
539 {
540 	int ch;
541 
542 	while ((ch = getopt(argc, argv, "C")) != -1) {
543 		switch (ch) {
544 		case 'C':
545 			procstat_opts |= PS_OPT_CAPABILITIES;
546 			break;
547 		case '?':
548 		default:
549 			usage();
550 		}
551 	}
552 }
553 
554 void
555 cmdopt_cpuset(int argc, char * const argv[])
556 {
557 
558 	procstat_opts |= PS_OPT_PERTHREAD;
559 	cmdopt_none(argc, argv);
560 }
561