xref: /illumos-gate/usr/src/cmd/psrinfo/psrinfo.c (revision b6805bf78d2bbbeeaea8909a05623587b42d58b3)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright (c) 2012 DEY Storage Systems, Inc.  All rights reserved.
14  * Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
15  */
16 
17 /*
18  * This implements psrinfo(1M), a utility to report various information
19  * about processors, cores, and threads (virtual cpus).  This is mostly
20  * intended for human consumption - this utility doesn't do much more than
21  * simply process kstats for human readability.
22  *
23  * All the relevant kstats are in the cpu_info kstat module.
24  */
25 
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <string.h>
30 #include <kstat.h>
31 #include <libintl.h>
32 #include <locale.h>
33 #include <libgen.h>
34 #include <ctype.h>
35 #include <errno.h>
36 
37 #define	_(x)	gettext(x)
38 #if XGETTEXT
39 /* These CPU states are here for benefit of xgettext */
40 _("on-line")
41 _("off-line")
42 _("faulted")
43 _("powered-off")
44 _("no-intr")
45 _("spare")
46 _("unknown")
47 #endif
48 
49 /*
50  * We deal with sorted linked lists, where the sort key is usually the
51  * cpu id, core id, or chip id.  We generalize this with simple node.
52  */
53 struct link {
54 	long		l_id;
55 	struct link	*l_next;
56 	void		*l_ptr;
57 };
58 
59 /*
60  * A physical chip.  A chip can contain multiple cores and virtual cpus.
61  */
62 struct pchip {
63 	struct link	p_link;
64 	int		p_ncore;
65 	int		p_nvcpu;
66 	struct link	*p_cores;
67 	struct link	*p_vcpus;
68 	int		p_doit;
69 };
70 
71 struct core {
72 	struct link	c_link;
73 	struct link	c_link_pchip;
74 
75 	int		c_nvcpu;
76 	int		c_doit;
77 
78 	struct pchip	*c_pchip;
79 	struct link	*c_vcpus;
80 };
81 
82 struct vcpu {
83 	struct link	v_link;
84 
85 	struct link	v_link_core;
86 	struct link	v_link_pchip;
87 
88 	int		v_doit;
89 
90 	struct pchip	*v_pchip;
91 	struct core	*v_core;
92 
93 	char		*v_state;
94 	long		v_state_begin;
95 	char		*v_cpu_type;
96 	char		*v_fpu_type;
97 	long		v_clock_mhz;
98 	long		v_pchip_id;	/* 1 per socket */
99 	char		*v_impl;
100 	char		*v_brand;
101 	char		*v_socket;
102 	long		v_core_id;	/* n per chip_id */
103 };
104 
105 static struct link *pchips = NULL;
106 static struct link *cores = NULL;
107 static struct link *vcpus = NULL;
108 
109 static const char *cmdname;
110 
111 static void
112 usage(char *msg)
113 {
114 	if (msg != NULL)
115 		(void) fprintf(stderr, "%s: %s\n", cmdname, msg);
116 	(void) fprintf(stderr, _("usage: \n" \
117 	    "\t%s [-v] [-p] [processor_id ...]\n" \
118 	    "\t%s -s [-p] processor_id\n"), cmdname, cmdname);
119 	exit(2);
120 }
121 
122 /* like perror, but includes the command name */
123 static void
124 die(const char *msg)
125 {
126 	(void) fprintf(stderr, "%s: %s: %s\n", cmdname, msg, strerror(errno));
127 	exit(2);
128 }
129 
130 static char *
131 mystrdup(const char *src)
132 {
133 	char *dst;
134 
135 	if ((dst = strdup(src)) == NULL)
136 		die(_("strdup() failed"));
137 	return (dst);
138 }
139 
140 static void *
141 zalloc(size_t size)
142 {
143 	void *ptr;
144 
145 	if ((ptr = calloc(1, size)) == NULL)
146 		die(_("calloc() failed"));
147 	return (ptr);
148 }
149 
150 /*
151  * Insert a new node on a list, at the insertion point given.
152  */
153 static void
154 ins_link(struct link **ins, struct link *item)
155 {
156 	item->l_next = *ins;
157 	*ins = item;
158 }
159 
160 /*
161  * Find an id on a sorted list.  If the requested id is not found,
162  * then the insertpt will be set (if not null) to the location where
163  * a new node should be inserted with ins_link (see above).
164  */
165 static void *
166 find_link(void *list, int id, struct link ***insertpt)
167 {
168 	struct link **ins = list;
169 	struct link *l;
170 
171 	while ((l = *ins) != NULL) {
172 		if (l->l_id == id)
173 			return (l->l_ptr);
174 		if (l->l_id > id)
175 			break;
176 		ins = &l->l_next;
177 	}
178 	if (insertpt != NULL)
179 		*insertpt = ins;
180 	return (NULL);
181 }
182 
183 /*
184  * Print the linked list of ids in parens, taking care to collapse
185  * ranges, so instead of (0 1 2 3) it should print (0-3).
186  */
187 static void
188 print_links(struct link *l)
189 {
190 	int	start = -1;
191 	int	end = 0;
192 
193 	(void) printf(" (");
194 	while (l != NULL) {
195 		if (start < 0) {
196 			start = l->l_id;
197 		}
198 		end = l->l_id;
199 		if ((l->l_next == NULL) ||
200 		    (l->l_next->l_id > (l->l_id + 1))) {
201 			/* end of the contiguous group */
202 			if (start == end) {
203 				(void) printf("%d", start);
204 			} else {
205 				(void) printf("%d-%d", start, end);
206 			}
207 			if (l->l_next)
208 				(void) printf(" ");
209 			start = -1;
210 		}
211 		l = l->l_next;
212 	}
213 	(void) printf(")");
214 }
215 
216 static const char *
217 timestr(long t)
218 {
219 	static char buffer[256];
220 	(void) strftime(buffer, sizeof (buffer), _("%m/%d/%Y %T"),
221 	    localtime(&t));
222 	return (buffer);
223 }
224 
225 static void
226 print_vp(int nspec)
227 {
228 	struct pchip *chip;
229 	struct core *core;
230 	struct vcpu *vcpu;
231 	struct link *l1, *l2;
232 	int len;
233 	for (l1 = pchips; l1; l1 = l1->l_next) {
234 
235 		chip = l1->l_ptr;
236 
237 		if ((nspec != 0) && (chip->p_doit == 0))
238 			continue;
239 
240 		vcpu = chip->p_vcpus->l_ptr;
241 
242 		/*
243 		 * Note that some of the way these strings are broken up are
244 		 * to accommodate the legacy translations so that we won't
245 		 * have to retranslate for this utility.
246 		 */
247 		if ((chip->p_ncore == 1) || (chip->p_ncore == chip->p_nvcpu)) {
248 			(void) printf(_("%s has %d virtual %s"),
249 			    _("The physical processor"),
250 			    chip->p_nvcpu,
251 			    chip->p_nvcpu > 1 ?
252 			    _("processors") :
253 			    _("processor"));
254 		} else {
255 			(void) printf(_("%s has %d %s and %d virtual %s"),
256 			    _("The physical processor"),
257 			    chip->p_ncore, _("cores"),
258 			    chip->p_nvcpu,
259 			    chip->p_nvcpu > 1 ?
260 			    _("processors") : _("processor"));
261 		}
262 
263 		print_links(chip->p_vcpus);
264 		(void) putchar('\n');
265 
266 		if ((chip->p_ncore == 1) || (chip->p_ncore == chip->p_nvcpu)) {
267 			if (strlen(vcpu->v_impl)) {
268 				(void) printf("  %s\n", vcpu->v_impl);
269 			}
270 			if (((len = strlen(vcpu->v_brand)) != 0) &&
271 			    (strncmp(vcpu->v_brand, vcpu->v_impl, len) != 0))
272 				(void) printf("\t%s", vcpu->v_brand);
273 			if (strcmp(vcpu->v_socket, "Unknown") != 0)
274 				(void) printf("\t[ %s: %s ]", _("Socket"),
275 				    vcpu->v_socket);
276 			(void) putchar('\n');
277 		} else {
278 			for (l2 = chip->p_cores; l2; l2 = l2->l_next) {
279 				core = l2->l_ptr;
280 				(void) printf(_("  %s has %d virtual %s"),
281 				    _("The core"),
282 				    core->c_nvcpu,
283 				    chip->p_nvcpu > 1 ?
284 				    _("processors") : _("processor"));
285 				print_links(core->c_vcpus);
286 				(void) putchar('\n');
287 			}
288 			if (strlen(vcpu->v_impl)) {
289 				(void) printf("    %s\n", vcpu->v_impl);
290 			}
291 			if (((len = strlen(vcpu->v_brand)) != 0) &&
292 			    (strncmp(vcpu->v_brand, vcpu->v_impl, len) != 0))
293 				(void) printf("      %s\n", vcpu->v_brand);
294 		}
295 	}
296 }
297 
298 static void
299 print_ps(void)
300 {
301 	int online = 1;
302 	struct pchip *p;
303 	struct vcpu *v;
304 	struct link *l;
305 
306 	/*
307 	 * Report "1" if all cpus colocated on the same chip are online.
308 	 */
309 	for (l = pchips; l != NULL; l = l->l_next) {
310 		p = l->l_ptr;
311 		if (p->p_doit)
312 			break;
313 	}
314 	if (p == NULL)
315 		return;	/* should never happen! */
316 	for (l = p->p_vcpus; l != NULL; l = l->l_next) {
317 		v = l->l_ptr;
318 		if (strcmp(v->v_state, "on-line") != 0) {
319 			online = 0;
320 			break;
321 		}
322 	}
323 
324 	(void) printf("%d\n", online);
325 }
326 
327 static void
328 print_s(void)
329 {
330 	struct link *l;
331 
332 	/*
333 	 * Find the processor (there will be only one) that we selected,
334 	 * and report whether or not it is online.
335 	 */
336 	for (l = vcpus; l != NULL; l = l->l_next) {
337 		struct vcpu *v = l->l_ptr;
338 		if (v->v_doit) {
339 			(void) printf("%d\n",
340 			    strcmp(v->v_state, "on-line") == 0 ? 1 : 0);
341 			return;
342 		}
343 	}
344 }
345 
346 static void
347 print_p(int nspec)
348 {
349 	struct		link *l1, *l2;
350 	int		online = 0;
351 
352 	/*
353 	 * Print the number of physical packages with at least one processor
354 	 * online.
355 	 */
356 	for (l1 = pchips; l1 != NULL; l1 = l1->l_next) {
357 		struct pchip *p = l1->l_ptr;
358 		if ((nspec == 0) || (p->p_doit)) {
359 
360 			for (l2 = p->p_vcpus; l2 != NULL; l2 = l2->l_next) {
361 				struct vcpu *v = l2->l_ptr;
362 				if (strcmp(v->v_state, "on-line") == 0) {
363 					online++;
364 					break;
365 				}
366 			}
367 		}
368 	}
369 	(void) printf("%d\n", online);
370 }
371 
372 static void
373 print_v(int nspec)
374 {
375 	struct link	*l;
376 
377 	for (l = vcpus; l != NULL; l = l->l_next) {
378 		struct vcpu *v = l->l_ptr;
379 
380 		if ((nspec != 0) && (!v->v_doit))
381 			continue;
382 		(void) printf(_("Status of virtual processor %d as of: "),
383 		    l->l_id);
384 		(void) printf("%s\n", timestr(time(NULL)));
385 		(void) printf(_("  %s since %s.\n"),
386 		    _(v->v_state), timestr(v->v_state_begin));
387 		if (v->v_clock_mhz) {
388 			(void) printf(
389 			    _("  The %s processor operates at %llu MHz,\n"),
390 			    v->v_cpu_type, (unsigned long long)v->v_clock_mhz);
391 		} else {
392 			(void) printf(
393 			    _("  The %s processor operates at " \
394 			    "an unknown frequency,\n"), v->v_cpu_type);
395 		}
396 		switch (*v->v_fpu_type) {
397 		case '\0':
398 			(void) printf(
399 			    _("\tand has no floating point processor.\n"));
400 			break;
401 		case 'a': case 'A':
402 		case 'e': case 'E':
403 		case 'i': case 'I':
404 		case 'o': case 'O':
405 		case 'u': case 'U':
406 		case 'y': case 'Y':
407 			(void) printf(
408 			    _("\tand has an %s floating point processor.\n"),
409 			    v->v_fpu_type);
410 			break;
411 		default:
412 			(void) printf(
413 			    _("\tand has a %s floating point processor.\n"),
414 			    v->v_fpu_type);
415 			break;
416 		}
417 	}
418 }
419 
420 static void
421 print_normal(int nspec)
422 {
423 	struct link	*l;
424 	struct vcpu	*v;
425 
426 	for (l = vcpus; l != NULL; l = l->l_next) {
427 		v = l->l_ptr;
428 		if ((nspec == 0) || (v->v_doit)) {
429 			(void) printf(_("%d\t%-8s  since %s\n"),
430 			    l->l_id, _(v->v_state), timestr(v->v_state_begin));
431 		}
432 	}
433 }
434 
435 int
436 main(int argc, char **argv)
437 {
438 	kstat_ctl_t	*kc;
439 	kstat_t		*ksp;
440 	kstat_named_t	*knp;
441 	struct vcpu	*vc;
442 	struct core	*core;
443 	struct pchip	*chip;
444 	struct link	**ins;
445 	char		*s;
446 	int		nspec;
447 	int		optc;
448 	int		opt_s = 0;
449 	int		opt_p = 0;
450 	int		opt_v = 0;
451 	int		ex = 0;
452 
453 	cmdname = basename(argv[0]);
454 
455 
456 	(void) setlocale(LC_ALL, "");
457 #if !defined(TEXT_DOMAIN)
458 #define	TEXT_DOMAIN	"SYS_TEST"
459 #endif
460 	(void) textdomain(TEXT_DOMAIN);
461 
462 	/* collect the kstats */
463 	if ((kc = kstat_open()) == NULL)
464 		die(_("kstat_open() failed"));
465 
466 	if ((ksp = kstat_lookup(kc, "cpu_info", -1, NULL)) == NULL)
467 		die(_("kstat_lookup() failed"));
468 
469 	for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
470 
471 		if (strcmp(ksp->ks_module, "cpu_info") != 0)
472 			continue;
473 		if (kstat_read(kc, ksp, NULL) == NULL)
474 			die(_("kstat_read() failed"));
475 
476 		vc = find_link(&vcpus, ksp->ks_instance, &ins);
477 		if (vc == NULL) {
478 			vc = zalloc(sizeof (struct vcpu));
479 			vc->v_link.l_id = ksp->ks_instance;
480 			vc->v_link_core.l_id = ksp->ks_instance;
481 			vc->v_link_pchip.l_id = ksp->ks_instance;
482 			vc->v_link.l_ptr = vc;
483 			vc->v_link_core.l_ptr = vc;
484 			vc->v_link_pchip.l_ptr = vc;
485 			ins_link(ins, &vc->v_link);
486 		}
487 
488 		if ((knp = kstat_data_lookup(ksp, "state")) != NULL) {
489 			vc->v_state = mystrdup(knp->value.c);
490 		} else {
491 			vc->v_state = "unknown";
492 		}
493 
494 		if ((knp = kstat_data_lookup(ksp, "cpu_type")) != NULL) {
495 			vc->v_cpu_type = mystrdup(knp->value.c);
496 		}
497 		if ((knp = kstat_data_lookup(ksp, "fpu_type")) != NULL) {
498 			vc->v_fpu_type = mystrdup(knp->value.c);
499 		}
500 
501 		if ((knp = kstat_data_lookup(ksp, "state_begin")) != NULL) {
502 			vc->v_state_begin = knp->value.l;
503 		}
504 
505 		if ((knp = kstat_data_lookup(ksp, "clock_MHz")) != NULL) {
506 			vc->v_clock_mhz = knp->value.l;
507 		}
508 
509 		if ((knp = kstat_data_lookup(ksp, "brand")) == NULL) {
510 			vc->v_brand = _("(unknown)");
511 		} else {
512 			vc->v_brand = mystrdup(knp->value.str.addr.ptr);
513 		}
514 
515 		if ((knp = kstat_data_lookup(ksp, "socket_type")) == NULL) {
516 			vc->v_socket = "Unknown";
517 		} else {
518 			vc->v_socket = mystrdup(knp->value.str.addr.ptr);
519 		}
520 
521 		if ((knp = kstat_data_lookup(ksp, "implementation")) == NULL) {
522 			vc->v_impl = _("(unknown)");
523 		} else {
524 			vc->v_impl = mystrdup(knp->value.str.addr.ptr);
525 		}
526 		/*
527 		 * Legacy code removed the chipid and cpuid fields... we
528 		 * do the same for compatibility.  Note that the original
529 		 * pattern is a bit strange, and we have to emulate this because
530 		 * on SPARC we *do* emit these.  The original pattern we are
531 		 * emulating is: $impl =~ s/(cpuid|chipid)\s*\w+\s+//;
532 		 */
533 		if ((s = strstr(vc->v_impl, "chipid")) != NULL) {
534 			char *x = s + strlen("chipid");
535 			while (isspace(*x))
536 				x++;
537 			if ((!isalnum(*x)) && (*x != '_'))
538 				goto nochipid;
539 			while (isalnum(*x) || (*x == '_'))
540 				x++;
541 			if (!isspace(*x))
542 				goto nochipid;
543 			while (isspace(*x))
544 				x++;
545 			(void) strcpy(s, x);
546 		}
547 nochipid:
548 		if ((s = strstr(vc->v_impl, "cpuid")) != NULL) {
549 			char *x = s + strlen("cpuid");
550 			while (isspace(*x))
551 				x++;
552 			if ((!isalnum(*x)) && (*x != '_'))
553 				goto nocpuid;
554 			while (isalnum(*x) || (*x == '_'))
555 				x++;
556 			if (!isspace(*x))
557 				goto nocpuid;
558 			while (isspace(*x))
559 				x++;
560 			(void) strcpy(s, x);
561 		}
562 nocpuid:
563 
564 		if ((knp = kstat_data_lookup(ksp, "chip_id")) != NULL)
565 			vc->v_pchip_id = knp->value.l;
566 		chip = find_link(&pchips, vc->v_pchip_id, &ins);
567 		if (chip == NULL) {
568 			chip = zalloc(sizeof (struct pchip));
569 			chip->p_link.l_id = vc->v_pchip_id;
570 			chip->p_link.l_ptr = chip;
571 			ins_link(ins, &chip->p_link);
572 		}
573 		vc->v_pchip = chip;
574 
575 		if ((knp = kstat_data_lookup(ksp, "core_id")) != NULL)
576 			vc->v_core_id = knp->value.l;
577 		core = find_link(&cores, vc->v_core_id, &ins);
578 		if (core == NULL) {
579 			core = zalloc(sizeof (struct core));
580 			core->c_link.l_id = vc->v_core_id;
581 			core->c_link.l_ptr = core;
582 			core->c_link_pchip.l_id = vc->v_core_id;
583 			core->c_link_pchip.l_ptr = core;
584 			core->c_pchip = chip;
585 			ins_link(ins, &core->c_link);
586 			chip->p_ncore++;
587 			(void) find_link(&chip->p_cores, core->c_link.l_id,
588 			    &ins);
589 			ins_link(ins, &core->c_link_pchip);
590 		}
591 		vc->v_core = core;
592 
593 
594 
595 		/* now put other linkages in place */
596 		(void) find_link(&chip->p_vcpus, vc->v_link.l_id, &ins);
597 		ins_link(ins, &vc->v_link_pchip);
598 		chip->p_nvcpu++;
599 
600 		(void) find_link(&core->c_vcpus, vc->v_link.l_id, &ins);
601 		ins_link(ins, &vc->v_link_core);
602 		core->c_nvcpu++;
603 	}
604 
605 	(void) kstat_close(kc);
606 
607 	nspec = 0;
608 
609 	while ((optc = getopt(argc, argv, "pvs")) != EOF) {
610 		switch (optc) {
611 		case 's':
612 			opt_s = 1;
613 			break;
614 		case 'p':
615 			opt_p = 1;
616 			break;
617 		case 'v':
618 			opt_v = 1;
619 			break;
620 		default:
621 			usage(NULL);
622 		}
623 	}
624 
625 	while (optind < argc) {
626 		long id;
627 		char *eptr;
628 		struct link *l;
629 		id = strtol(argv[optind], &eptr, 10);
630 		l = find_link(&vcpus, id, NULL);
631 		if ((*eptr != '\0') || (l == NULL)) {
632 			(void) fprintf(stderr,
633 			    _("%s: processor %s: Invalid argument\n"),
634 			    cmdname, argv[optind]);
635 			ex = 2;
636 		} else {
637 			((struct vcpu *)l->l_ptr)->v_doit = 1;
638 			((struct vcpu *)l->l_ptr)->v_pchip->p_doit = 1;
639 			((struct vcpu *)l->l_ptr)->v_core->c_doit = 1;
640 		}
641 		nspec++;
642 		optind++;
643 	}
644 
645 	if (opt_s && opt_v) {
646 		usage(_("options -s and -v are mutually exclusive"));
647 	}
648 	if (opt_s && nspec != 1) {
649 		usage(_("must specify exactly one processor if -s used"));
650 	}
651 	if (opt_v && opt_p) {
652 		print_vp(nspec);
653 	} else if (opt_s && opt_p) {
654 		print_ps();
655 	} else if (opt_p) {
656 		print_p(nspec);
657 	} else if (opt_v) {
658 		print_v(nspec);
659 	} else if (opt_s) {
660 		print_s();
661 	} else {
662 		print_normal(nspec);
663 	}
664 
665 	return (ex);
666 }
667