changeset 13725:9a3ca91fb74e

2804 psrinfo should not depend on perl Reviewed by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net> Reviewed by: Jerry Jelinek <jerry.jelinek@joyent.com> Approved by: Gordon Ross <gordon.w.ross@gmail.com>
author Garrett D'Amore <garrett@damore.org>
date Thu, 14 Jun 2012 08:00:26 -0700
parents 7740792727e0
children 056b82d21d71
files usr/src/cmd/psrinfo/Makefile usr/src/cmd/psrinfo/psrinfo.c usr/src/cmd/psrinfo/psrinfo.pl
diffstat 3 files changed, 663 insertions(+), 854 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/cmd/psrinfo/Makefile	Wed Jun 13 22:54:43 2012 +0000
+++ b/usr/src/cmd/psrinfo/Makefile	Thu Jun 14 08:00:26 2012 -0700
@@ -21,6 +21,8 @@
 # Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
+# Copyright (c) 2012 DEY Storage Systems, Inc.  All rights reserved.
+#
 # cmd/psrinfo/Makefile
 #
 
@@ -28,6 +30,11 @@
 
 include	../Makefile.cmd
 
+LDLIBS += -lkstat
+XGETFLAGS +=  
+$(PROG).po	:= CPPFLAGS += -DXGETTEXT
+
+
 .KEEP_STATE:
 
 all: $(PROG)
@@ -41,6 +48,6 @@
 $(ROOTUSRSBINBINPROG): $(PROG)
 	$(INS.file)
 
-lint:
+lint:	lint_PROG
 
 include ../Makefile.targ
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/psrinfo/psrinfo.c	Thu Jun 14 08:00:26 2012 -0700
@@ -0,0 +1,655 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright (c) 2012 DEY Storage Systems, Inc.  All rights reserved.
+ */
+
+/*
+ * This implements psrinfo(1M), a utility to report various information
+ * about processors, cores, and threads (virtual cpus).  This is mostly
+ * intended for human consumption - this utility doesn't do much more than
+ * simply process kstats for human readability.
+ *
+ * All the relevant kstats are in the cpu_info kstat module.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <kstat.h>
+#include <libintl.h>
+#include <locale.h>
+#include <libgen.h>
+#include <ctype.h>
+#include <errno.h>
+
+#define	_(x)	gettext(x)
+#if XGETTEXT
+/* These CPU states are here for benefit of xgettext */
+_("on-line")
+_("off-line")
+_("faulted")
+_("powered-off")
+_("no-intr")
+_("spare")
+_("unknown")
+#endif
+
+/*
+ * We deal with sorted linked lists, where the sort key is usually the
+ * cpu id, core id, or chip id.  We generalize this with simple node.
+ */
+struct link {
+	long		l_id;
+	struct link	*l_next;
+	void		*l_ptr;
+};
+
+/*
+ * A physical chip.  A chip can contain multiple cores and virtual cpus.
+ */
+struct pchip {
+	struct link	p_link;
+	int		p_ncore;
+	int		p_nvcpu;
+	struct link	*p_cores;
+	struct link	*p_vcpus;
+	int		p_doit;
+};
+
+struct core {
+	struct link	c_link;
+	struct link	c_link_pchip;
+
+	int		c_nvcpu;
+	int		c_doit;
+
+	struct pchip	*c_pchip;
+	struct link	*c_vcpus;
+};
+
+struct vcpu {
+	struct link	v_link;
+
+	struct link	v_link_core;
+	struct link	v_link_pchip;
+
+	int		v_doit;
+
+	struct pchip	*v_pchip;
+	struct core	*v_core;
+
+	char		*v_state;
+	long		v_state_begin;
+	char		*v_cpu_type;
+	char		*v_fpu_type;
+	long		v_clock_mhz;
+	long		v_pchip_id;	/* 1 per socket */
+	char		*v_impl;
+	char		*v_brand;
+	long		v_core_id;	/* n per chip_id */
+};
+
+static struct link *pchips = NULL;
+static struct link *cores = NULL;
+static struct link *vcpus = NULL;
+
+static const char *cmdname;
+
+static void
+usage(char *msg)
+{
+	if (msg != NULL)
+		(void) fprintf(stderr, "%s: %s\n", cmdname, msg);
+	(void) fprintf(stderr, _("usage: \n" \
+	    "\t%s [-v] [-p] [processor_id ...]\n" \
+	    "\t%s -s [-p] processor_id\n"), cmdname, cmdname);
+	exit(2);
+}
+
+/* like perror, but includes the command name */
+static void
+die(const char *msg)
+{
+	(void) fprintf(stderr, "%s: %s: %s\n", cmdname, msg, strerror(errno));
+	exit(2);
+}
+
+static char *
+mystrdup(const char *src)
+{
+	char *dst;
+
+	if ((dst = strdup(src)) == NULL)
+		die(_("strdup() failed"));
+	return (dst);
+}
+
+static void *
+zalloc(size_t size)
+{
+	void *ptr;
+
+	if ((ptr = calloc(1, size)) == NULL)
+		die(_("calloc() failed"));
+	return (ptr);
+}
+
+/*
+ * Insert a new node on a list, at the insertion point given.
+ */
+static void
+ins_link(struct link **ins, struct link *item)
+{
+	item->l_next = *ins;
+	*ins = item;
+}
+
+/*
+ * Find an id on a sorted list.  If the requested id is not found,
+ * then the insertpt will be set (if not null) to the location where
+ * a new node should be inserted with ins_link (see above).
+ */
+static void *
+find_link(void *list, int id, struct link ***insertpt)
+{
+	struct link **ins = list;
+	struct link *l;
+
+	while ((l = *ins) != NULL) {
+		if (l->l_id == id)
+			return (l->l_ptr);
+		if (l->l_id > id)
+			break;
+		ins = &l->l_next;
+	}
+	if (insertpt != NULL)
+		*insertpt = ins;
+	return (NULL);
+}
+
+/*
+ * Print the linked list of ids in parens, taking care to collapse
+ * ranges, so instead of (0 1 2 3) it should print (0-3).
+ */
+static void
+print_links(struct link *l)
+{
+	int	start = -1;
+	int	end = 0;
+
+	(void) printf(" (");
+	while (l != NULL) {
+		if (start < 0) {
+			start = l->l_id;
+		}
+		end = l->l_id;
+		if ((l->l_next == NULL) ||
+		    (l->l_next->l_id > (l->l_id + 1))) {
+			/* end of the contiguous group */
+			if (start == end) {
+				(void) printf("%d", start);
+			} else {
+				(void) printf("%d-%d", start, end);
+			}
+			if (l->l_next)
+				(void) printf(" ");
+			start = -1;
+		}
+		l = l->l_next;
+	}
+	(void) printf(")");
+}
+
+static const char *
+timestr(long t)
+{
+	static char buffer[256];
+	(void) strftime(buffer, sizeof (buffer), _("%m/%d/%Y %T"),
+	    localtime(&t));
+	return (buffer);
+}
+
+static void
+print_vp(int nspec)
+{
+	struct pchip *chip;
+	struct core *core;
+	struct vcpu *vcpu;
+	struct link *l1, *l2;
+	int len;
+	for (l1 = pchips; l1; l1 = l1->l_next) {
+
+		chip = l1->l_ptr;
+
+		if ((nspec != 0) && (chip->p_doit == 0))
+			continue;
+
+		vcpu = chip->p_vcpus->l_ptr;
+
+		/*
+		 * Note that some of the way these strings are broken up are
+		 * to accommodate the legacy translations so that we won't
+		 * have to retranslate for this utility.
+		 */
+		if ((chip->p_ncore == 1) || (chip->p_ncore == chip->p_nvcpu)) {
+			(void) printf(_("%s has %d virtual %s"),
+			    _("The physical processor"),
+			    chip->p_nvcpu,
+			    chip->p_nvcpu > 1 ?
+			    _("processors") :
+			    _("processor"));
+		} else {
+			(void) printf(_("%s has %d %s and %d virtual %s"),
+			    _("The physical processor"),
+			    chip->p_ncore, _("cores"),
+			    chip->p_nvcpu,
+			    chip->p_nvcpu > 1 ?
+			    _("processors") : _("processor"));
+		}
+
+		print_links(chip->p_vcpus);
+		(void) putchar('\n');
+
+		if ((chip->p_ncore == 1) || (chip->p_ncore == chip->p_nvcpu)) {
+			if (strlen(vcpu->v_impl)) {
+				(void) printf("  %s\n", vcpu->v_impl);
+			}
+			if (((len = strlen(vcpu->v_brand)) != 0) &&
+			    (strncmp(vcpu->v_brand, vcpu->v_impl, len) != 0))
+				(void) printf("\t%s", vcpu->v_brand);
+			(void) putchar('\n');
+		} else {
+			for (l2 = chip->p_cores; l2; l2 = l2->l_next) {
+				core = l2->l_ptr;
+				(void) printf(_("  %s has %d virtual %s"),
+				    _("The core"),
+				    core->c_nvcpu,
+				    chip->p_nvcpu > 1 ?
+				    _("processors") : _("processor"));
+				print_links(core->c_vcpus);
+				(void) putchar('\n');
+			}
+			if (strlen(vcpu->v_impl)) {
+				(void) printf("    %s\n", vcpu->v_impl);
+			}
+			if (((len = strlen(vcpu->v_brand)) != 0) &&
+			    (strncmp(vcpu->v_brand, vcpu->v_impl, len) != 0))
+				(void) printf("      %s\n", vcpu->v_brand);
+		}
+	}
+}
+
+static void
+print_ps(void)
+{
+	int online = 1;
+	struct pchip *p;
+	struct vcpu *v;
+	struct link *l;
+
+	/*
+	 * Report "1" if all cpus colocated on the same chip are online.
+	 */
+	for (l = pchips; l != NULL; l = l->l_next) {
+		p = l->l_ptr;
+		if (p->p_doit)
+			break;
+	}
+	if (p == NULL)
+		return;	/* should never happen! */
+	for (l = p->p_vcpus; l != NULL; l = l->l_next) {
+		v = l->l_ptr;
+		if (strcmp(v->v_state, "on-line") != 0) {
+			online = 0;
+			break;
+		}
+	}
+
+	(void) printf("%d\n", online);
+}
+
+static void
+print_s(void)
+{
+	struct link *l;
+
+	/*
+	 * Find the processor (there will be only one) that we selected,
+	 * and report whether or not it is online.
+	 */
+	for (l = vcpus; l != NULL; l = l->l_next) {
+		struct vcpu *v = l->l_ptr;
+		if (v->v_doit) {
+			(void) printf("%d\n",
+			    strcmp(v->v_state, "on-line") == 0 ? 1 : 0);
+			return;
+		}
+	}
+}
+
+static void
+print_p(int nspec)
+{
+	struct		link *l1, *l2;
+	int		online = 0;
+
+	/*
+	 * Print the number of physical packages with at least one processor
+	 * online.
+	 */
+	for (l1 = pchips; l1 != NULL; l1 = l1->l_next) {
+		struct pchip *p = l1->l_ptr;
+		if ((nspec == 0) || (p->p_doit)) {
+
+			for (l2 = p->p_vcpus; l2 != NULL; l2 = l2->l_next) {
+				struct vcpu *v = l2->l_ptr;
+				if (strcmp(v->v_state, "on-line") == 0) {
+					online++;
+					break;
+				}
+			}
+		}
+	}
+	(void) printf("%d\n", online);
+}
+
+static void
+print_v(int nspec)
+{
+	struct link	*l;
+
+	for (l = vcpus; l != NULL; l = l->l_next) {
+		struct vcpu *v = l->l_ptr;
+
+		if ((nspec != 0) && (!v->v_doit))
+			continue;
+		(void) printf(_("Status of virtual processor %d as of: "),
+		    l->l_id);
+		(void) printf("%s\n", timestr(time(NULL)));
+		(void) printf(_("  %s since %s.\n"),
+		    _(v->v_state), timestr(v->v_state_begin));
+		if (v->v_clock_mhz) {
+			(void) printf(
+			    _("  The %s processor operates at %llu MHz,\n"),
+			    v->v_cpu_type, (unsigned long long)v->v_clock_mhz);
+		} else {
+			(void) printf(
+			    _("  The %s processor operates at " \
+			    "an unknown frequency,\n"), v->v_cpu_type);
+		}
+		switch (*v->v_fpu_type) {
+		case '\0':
+			(void) printf(
+			    _("\tand has no floating point processor.\n"));
+			break;
+		case 'a': case 'A':
+		case 'e': case 'E':
+		case 'i': case 'I':
+		case 'o': case 'O':
+		case 'u': case 'U':
+		case 'y': case 'Y':
+			(void) printf(
+			    _("\tand has an %s floating point processor.\n"),
+			    v->v_fpu_type);
+			break;
+		default:
+			(void) printf(
+			    _("\tand has a %s floating point processor.\n"),
+			    v->v_fpu_type);
+			break;
+		}
+	}
+}
+
+static void
+print_normal(int nspec)
+{
+	struct link	*l;
+	struct vcpu	*v;
+
+	for (l = vcpus; l != NULL; l = l->l_next) {
+		v = l->l_ptr;
+		if ((nspec == 0) || (v->v_doit)) {
+			(void) printf(_("%d\t%-8s  since %s\n"),
+			    l->l_id, _(v->v_state), timestr(v->v_state_begin));
+		}
+	}
+}
+
+int
+main(int argc, char **argv)
+{
+	kstat_ctl_t	*kc;
+	kstat_t		*ksp;
+	kstat_named_t	*knp;
+	struct vcpu	*vc;
+	struct core	*core;
+	struct pchip	*chip;
+	struct link	**ins;
+	char		*s;
+	int		nspec;
+	int		optc;
+	int		opt_s = 0;
+	int		opt_p = 0;
+	int		opt_v = 0;
+	int		ex = 0;
+
+	cmdname = basename(argv[0]);
+
+
+	(void) setlocale(LC_ALL, "");
+#if !defined(TEXT_DOMAIN)
+#define	TEXT_DOMAIN	"SYS_TEST"
+#endif
+	(void) textdomain(TEXT_DOMAIN);
+
+	/* collect the kstats */
+	if ((kc = kstat_open()) == NULL)
+		die(_("kstat_open() failed"));
+
+	if ((ksp = kstat_lookup(kc, "cpu_info", -1, NULL)) == NULL)
+		die(_("kstat_lookup() failed"));
+
+	for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
+
+		if (strcmp(ksp->ks_module, "cpu_info") != 0)
+			continue;
+		if (kstat_read(kc, ksp, NULL) == NULL)
+			die(_("kstat_read() failed"));
+
+		vc = find_link(&vcpus, ksp->ks_instance, &ins);
+		if (vc == NULL) {
+			vc = zalloc(sizeof (struct vcpu));
+			vc->v_link.l_id = ksp->ks_instance;
+			vc->v_link_core.l_id = ksp->ks_instance;
+			vc->v_link_pchip.l_id = ksp->ks_instance;
+			vc->v_link.l_ptr = vc;
+			vc->v_link_core.l_ptr = vc;
+			vc->v_link_pchip.l_ptr = vc;
+			ins_link(ins, &vc->v_link);
+		}
+
+		if ((knp = kstat_data_lookup(ksp, "state")) != NULL) {
+			vc->v_state = mystrdup(knp->value.c);
+		} else {
+			vc->v_state = "unknown";
+		}
+
+		if ((knp = kstat_data_lookup(ksp, "cpu_type")) != NULL) {
+			vc->v_cpu_type = mystrdup(knp->value.c);
+		}
+		if ((knp = kstat_data_lookup(ksp, "fpu_type")) != NULL) {
+			vc->v_fpu_type = mystrdup(knp->value.c);
+		}
+
+		if ((knp = kstat_data_lookup(ksp, "state_begin")) != NULL) {
+			vc->v_state_begin = knp->value.l;
+		}
+
+		if ((knp = kstat_data_lookup(ksp, "clock_MHz")) != NULL) {
+			vc->v_clock_mhz = knp->value.l;
+		}
+
+		if ((knp = kstat_data_lookup(ksp, "brand")) == NULL) {
+			vc->v_brand = _("(unknown)");
+		} else {
+			vc->v_brand = mystrdup(knp->value.str.addr.ptr);
+		}
+
+		if ((knp = kstat_data_lookup(ksp, "implementation")) == NULL) {
+			vc->v_impl = _("(unknown)");
+		} else {
+			vc->v_impl = mystrdup(knp->value.str.addr.ptr);
+		}
+		/*
+		 * Legacy code removed the chipid and cpuid fields... we
+		 * do the same for compatibility.  Note that the original
+		 * pattern is a bit strange, and we have to emulate this because
+		 * on SPARC we *do* emit these.  The original pattern we are
+		 * emulating is: $impl =~ s/(cpuid|chipid)\s*\w+\s+//;
+		 */
+		if ((s = strstr(vc->v_impl, "chipid")) != NULL) {
+			char *x = s + strlen("chipid");
+			while (isspace(*x))
+				x++;
+			if ((!isalnum(*x)) && (*x != '_'))
+				goto nochipid;
+			while (isalnum(*x) || (*x == '_'))
+				x++;
+			if (!isspace(*x))
+				goto nochipid;
+			while (isspace(*x))
+				x++;
+			(void) strcpy(s, x);
+		}
+nochipid:
+		if ((s = strstr(vc->v_impl, "cpuid")) != NULL) {
+			char *x = s + strlen("cpuid");
+			while (isspace(*x))
+				x++;
+			if ((!isalnum(*x)) && (*x != '_'))
+				goto nocpuid;
+			while (isalnum(*x) || (*x == '_'))
+				x++;
+			if (!isspace(*x))
+				goto nocpuid;
+			while (isspace(*x))
+				x++;
+			(void) strcpy(s, x);
+		}
+nocpuid:
+
+		if ((knp = kstat_data_lookup(ksp, "chip_id")) != NULL)
+			vc->v_pchip_id = knp->value.l;
+		chip = find_link(&pchips, vc->v_pchip_id, &ins);
+		if (chip == NULL) {
+			chip = zalloc(sizeof (struct pchip));
+			chip->p_link.l_id = vc->v_pchip_id;
+			chip->p_link.l_ptr = chip;
+			ins_link(ins, &chip->p_link);
+		}
+		vc->v_pchip = chip;
+
+		if ((knp = kstat_data_lookup(ksp, "core_id")) != NULL)
+			vc->v_core_id = knp->value.l;
+		core = find_link(&cores, vc->v_core_id, &ins);
+		if (core == NULL) {
+			core = zalloc(sizeof (struct core));
+			core->c_link.l_id = vc->v_core_id;
+			core->c_link.l_ptr = core;
+			core->c_link_pchip.l_id = vc->v_core_id;
+			core->c_link_pchip.l_ptr = core;
+			core->c_pchip = chip;
+			ins_link(ins, &core->c_link);
+			chip->p_ncore++;
+			(void) find_link(&chip->p_cores, core->c_link.l_id,
+			    &ins);
+			ins_link(ins, &core->c_link_pchip);
+		}
+		vc->v_core = core;
+
+
+
+		/* now put other linkages in place */
+		(void) find_link(&chip->p_vcpus, vc->v_link.l_id, &ins);
+		ins_link(ins, &vc->v_link_pchip);
+		chip->p_nvcpu++;
+
+		(void) find_link(&core->c_vcpus, vc->v_link.l_id, &ins);
+		ins_link(ins, &vc->v_link_core);
+		core->c_nvcpu++;
+	}
+
+	(void) kstat_close(kc);
+
+	nspec = 0;
+
+	while ((optc = getopt(argc, argv, "pvs")) != EOF) {
+		switch (optc) {
+		case 's':
+			opt_s = 1;
+			break;
+		case 'p':
+			opt_p = 1;
+			break;
+		case 'v':
+			opt_v = 1;
+			break;
+		default:
+			usage(NULL);
+		}
+	}
+
+	while (optind < argc) {
+		long id;
+		char *eptr;
+		struct link *l;
+		id = strtol(argv[optind], &eptr, 10);
+		l = find_link(&vcpus, id, NULL);
+		if ((*eptr != '\0') || (l == NULL)) {
+			(void) fprintf(stderr,
+			    _("%s: processor %s: Invalid argument\n"),
+			    cmdname, argv[optind]);
+			ex = 2;
+		} else {
+			((struct vcpu *)l->l_ptr)->v_doit = 1;
+			((struct vcpu *)l->l_ptr)->v_pchip->p_doit = 1;
+			((struct vcpu *)l->l_ptr)->v_core->c_doit = 1;
+		}
+		nspec++;
+		optind++;
+	}
+
+	if (opt_s && opt_v) {
+		usage(_("options -s and -v are mutually exclusive"));
+	}
+	if (opt_s && nspec != 1) {
+		usage(_("must specify exactly one processor if -s used"));
+	}
+	if (opt_v && opt_p) {
+		print_vp(nspec);
+	} else if (opt_s && opt_p) {
+		print_ps();
+	} else if (opt_p) {
+		print_p(nspec);
+	} else if (opt_v) {
+		print_v(nspec);
+	} else if (opt_s) {
+		print_s();
+	} else {
+		print_normal(nspec);
+	}
+
+	return (ex);
+}
--- a/usr/src/cmd/psrinfo/psrinfo.pl	Wed Jun 13 22:54:43 2012 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,853 +0,0 @@
-#!/usr/perl5/bin/perl
-
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
-# Use is subject to license terms.
-#
-# psrinfo: displays information about processors
-#
-# See detailed comment in the end of this file.
-#
-
-use strict;
-use warnings;
-use locale;
-use POSIX qw(locale_h strftime);
-use File::Basename;
-use Getopt::Long qw(:config no_ignore_case bundling auto_version);
-use Sun::Solaris::Utils qw(textdomain gettext);
-use Sun::Solaris::Kstat;
-
-# Set message locale
-setlocale(LC_ALL, "");
-textdomain(TEXT_DOMAIN);
-
-######################################################################
-# Configuration variables
-######################################################################
-
-# Regexp describing cpu_info kstat fields describing CPU hierarchy.
-my $valid_id_exp = qr{^(?:chip|core)_id$};
-
-# Translation of kstat name to human-readable form
-my %translations = ('chip_id' => gettext("The physical processor"),
-		    'core_id' => gettext("The core"));
-
-# Localized version of plural forms
-my %pluralized_names = ('processor'	=> gettext("processor"),
-			'processors'	=> gettext("processors"),
-			'chip'		=> gettext("chip"),
-			'chips'		=> gettext("chips"),
-			'core'		=> gettext("core"),
-			'cores'		=> gettext("cores"));
-
-# Localized CPU states
-my %cpu_states = ('on-line'	=> gettext("on-line"),
-		  'off-line'	=> gettext("off-line"),
-		  'faulted'	=> gettext("faulted"),
-		  'powered-off' => gettext("powered-off"),
-		  'no-intr'	=> gettext("no-intr"),
-		  'spare'	=> gettext("spare"),
-		  'unknown'	=> gettext("unknown"));
-
-######################################################################
-# Global variables
-######################################################################
-
-# Hash with CPU ID as a key and specific per-cpu kstat hash as a value
-our %cpu_list;
-
-# Command name without path and trailing .pl - used for error messages.
-our $cmdname = basename($0, ".pl");
-
-# Return value
-our $errors = 0;
-
-######################################################################
-# Helper subroutines
-######################################################################
-
-#
-# Print help string if specified or the standard help message and exit setting
-# errno.
-#
-sub usage
-{
-	my (@msg) = @_;
-	print STDERR $cmdname, ": @msg\n" if (@msg);
-	print STDERR gettext("usage: \n" .
-			 "\tpsrinfo [-v] [-p] [processor_id ...]\n" .
-			 "\tpsrinfo -s [-p] processor_id\n");
-	exit(2);
-}
-
-#
-# Return the input list with duplicates removed.
-# Count how many times we've seen each element and remove elements seen more
-# than once.
-#
-sub uniq
-{
-	my %seen;	# Have we seen this element already?
-	return (grep { ++$seen{$_} == 1 } @_);
-}
-
-#
-# Return the intersection of two lists passed by reference
-# Convert the first list to a hash with seen entries marked as 1-values
-# Then grep only elements present in the first list from the second list.
-# As a little optimization, use the shorter list to build a hash.
-#
-sub intersect
-{
-	my ($left, $right) = @_;
-	my %seen;	# Set to 1 for everything in the first list
-	# Put the shortest list in $left
-	scalar @$left <= scalar @$right or ($right, $left) = ($left, $right);
-
-	# Create a hash indexed by elements in @left with ones as a value.
-	map { $seen{$_} = 1 } @$left;
-	# Find members of @right present in @left
-	return (grep { $seen{$_} } @$right);
-}
-
-#
-# Return elements of the second list not present in the first list. Both lists
-# are passed by reference.
-#
-sub set_subtract
-{
-	my ($left, $right) = @_;
-	my %seen;	# Set to 1 for everything in the first list
-	# Create a hash indexed by elements in @left with ones as a value.
-	map { $seen{$_} = 1 } @$left;
-	# Find members of @right present in @left
-	return (grep { ! $seen{$_} } @$right);
-}
-
-#
-# Sort the list numerically
-# Should be called in list context
-#
-sub nsort
-{
-	return (sort { $a <=> $b } @_);
-}
-
-#
-# Sort list numerically and remove duplicates
-# Should be called in list context
-#
-sub uniqsort
-{
-	return (sort { $a <=> $b } uniq(@_));
-}
-
-#
-# Return the maximum value of its arguments
-#
-sub max
-{
-	my $m = shift;
-
-	foreach my $el (@_) {
-		$m = $el if $m < $el;
-	}
-	return ($m);
-}
-
-#
-# Pluralize name if there is more than one instance
-# Arguments: name, ninstances
-#
-sub pluralize
-{
-	my ($name, $count) = @_;
-	# Remove trailing '_id' from the name.
-	$name =~ s/_id$//;
-	my $plural_name = $count > 1 ? "${name}s" : $name;
-	return ($pluralized_names{$plural_name} || $plural_name)
-}
-
-#
-# Translate id name into printable form
-# Look at the %translations table and replace everything found there
-# Remove trailing _id from the name if there is no translation
-#
-sub id_translate
-{
-	my $name = shift or return;
-	my $translated_name = $translations{$name};
-	$name =~ s/_id$// unless $translated_name;
-	return ($translated_name || $name);
-}
-
-#
-# Consolidate consequtive CPU ids as start-end
-# Input: list of CPUs
-# Output: string with space-sepated cpu values with CPU ranges
-#   collapsed as x-y
-#
-sub collapse
-{
-	return ('') unless @_;
-	my @args = uniqsort(@_);
-	my $start = shift(@args);
-	my $result = '';
-	my $end = $start;	# Initial range consists of the first element
-	foreach my $el (@args) {
-		if ($el == ($end + 1)) {
-			#
-			# Got consecutive ID, so extend end of range without
-			# printing anything since the range may extend further
-			#
-			$end = $el;
-		} else {
-			#
-			# Next ID is not consecutive, so print IDs gotten so
-			# far.
-			#
-			if ($end > $start + 1) {	# range
-				$result = "$result $start-$end";
-			} elsif ($end > $start) {	# different values
-				$result = "$result $start $end";
-			} else {	# same value
-				$result = "$result $start";
-			}
-
-			# Try finding consecutive range starting from this ID
-			$start = $end = $el;
-		}
-	}
-
-	# Print last ID(s)
-	if ($end > $start + 1) {
-		$result = "$result $start-$end";
-	} elsif ($end > $start) {
-		$result = "$result $start $end";
-	} else {
-		$result = "$result $start";
-	}
-	# Remove any spaces in the beginning
-	$result =~ s/^\s+//;
-	return ($result);
-}
-
-#
-# Expand start-end into the list of values
-# Input: string containing a single numeric ID or x-y range
-# Output: single value or a list of values
-# Ranges with start being more than end are inverted
-#
-sub expand
-{
-	my $arg = shift;
-
-	if ($arg =~ m/^\d+$/) {
-		# single number
-		return ($_);
-	} elsif ($arg =~ m/^(\d+)\-(\d+)$/) {
-		my ($start, $end) = ($1, $2);	# $start-$end
-		# Reverse the interval if start > end
-		($start, $end) = ($end, $start) if $start > $end;
-		return ($start .. $end);
-	} elsif ($arg =~ m/-/) {
-		printf STDERR
-		  gettext("%s: invalid processor range %s\n"),
-		    $cmdname, $_;
-	} else {
-		printf STDERR
-		  gettext("%s: processor %s: Invalid argument\n"),
-		    $cmdname, $_;
-	}
-	$errors = 2;
-	return ();
-}
-
-#
-# Functions for constructing CPU hierarchy. Only used with -vp option.
-#
-
-#
-# Return numerically sorted list of distinct values of a given cpu_info kstat
-# field, spanning given CPU set.
-#
-# Arguments:
-#   Property name
-#   list of CPUs
-#
-# Treat undefined values as zeroes.
-sub property_list
-{
-	my $prop_name = shift;
-	return (grep {$_ >= 0} uniqsort(map { $cpu_list{$_}->{$prop_name} || 0 } @_));
-}
-
-#
-# Return subset of CPUs sharing specified value of a given cpu_info kstat field.
-# Arguments:
-#   Property name
-#   Property value
-#   List of CPUs to select from
-#
-# Treat undefined values as zeroes.
-sub cpus_by_prop
-{
-	my $prop_name = shift;
-	my $prop_val = shift;
-
-	return (grep { ($cpu_list{$_}->{$prop_name} || 0) == $prop_val } @_);
-}
-
-#
-# Build component tree
-#
-# Arguments:
-#    Reference to the list of CPUs sharing the component
-#    Reference to the list of sub-components
-#
-sub build_component_tree
-{
-	my ($cpus, $comp_list) = @_;
-	# Get the first component and the rest
-	my ($comp_name, @comps) = @$comp_list;
-	my $tree = {};
-	if (!$comp_name) {
-		$tree->{cpus} = $cpus;
-		return ($tree);
-	}
-
-	# Get all possible component values
-	foreach my $v (property_list($comp_name, @$cpus)) {
-		my @comp_cpus = cpus_by_prop ($comp_name, $v, @$cpus);
-		$tree->{name} = $comp_name;
-		$tree->{cpus} = $cpus;
-		$tree->{values}->{$v} = build_component_tree(\@comp_cpus,
-							     \@comps);
-	}
-	return ($tree);
-}
-
-#
-# Print the component tree
-# Arguments:
-#   Reference to a tree
-#   indentation
-# Output: maximum indentation
-#
-sub print_component_tree
-{
-	my ($tree, $ind) = @_;
-	my $spaces = ' ' x $ind; # indentation string
-	my $vals = $tree->{values};
-	my $retval = $ind;
-	if ($vals) {
-		# This is not a leaf node
-		# Get node name and translate it to printable format
-		my $id_name = id_translate($tree->{name});
-		# Examine each sub-node
-		foreach my $comp_val (nsort(keys %$vals)) {
-			my $child_tree = $vals->{$comp_val}; # Sub-tree
-			my $child_id = $child_tree->{name}; # Name of child node
-			my @cpus = @{$child_tree->{cpus}}; # CPUs for the child
-			my $ncpus = scalar @cpus; # Number of CPUs
-			my $cpuname = pluralize('processor', $ncpus);
-			my $cl = collapse(@cpus); # Printable CPU list
-			if (!$child_id) {
-				# Child is a leaf node
-				print $spaces;
-				printf gettext("%s has %d virtual %s"),
-				       $id_name, $ncpus, $cpuname;
-				print " ($cl)\n";
-				$retval = max($retval, $ind + 2);
-			} else {
-				# Child has several values. Let's see how many
-				my $grandchild_tree = $child_tree->{values};
-				my $nvals = scalar(keys %$grandchild_tree);
-				my $child_id_name = pluralize($child_id,
-							      $nvals);
-				print $spaces;
-				printf
-				  gettext("%s has %d %s and %d virtual %s"),
-				    $id_name, $nvals, $child_id_name, $ncpus,
-				      $cpuname;
-				print " ($cl)\n";
-				# Print the tree for the child
-				$retval = max($retval,
-					      print_component_tree($child_tree,
-								   $ind + 2));
-			}
-		}
-	}
-	return ($retval);
-}
-
-
-############################
-# Main part of the program
-############################
-
-#
-# Option processing
-#
-my ($opt_v, $opt_p, $opt_silent);
-
-GetOptions("p" => \$opt_p,
- 	   "v" => \$opt_v,
- 	   "s" => \$opt_silent) || usage();
-
-
-my $verbosity = 1;
-my $phys_view;
-
-$verbosity |= 2 if $opt_v;
-$verbosity &= ~1 if $opt_silent;
-$phys_view = 1 if $opt_p;
-
-# Set $phys_verbose if -vp is specified
-my $phys_verbose = $phys_view && ($verbosity > 1);
-
-# Verify options
-usage(gettext("options -s and -v are mutually exclusive")) if $verbosity == 2;
-
-usage(gettext("must specify exactly one processor if -s used")) if
-  (($verbosity == 0) && scalar @ARGV != 1);
-
-#
-# Read cpu_info kstats
-#
-my $ks = Sun::Solaris::Kstat->new(strip_strings => 1) or
-  (printf STDERR gettext("%s: kstat_open() failed: %s\n"),
-   $cmdname, $!),
-    exit(2);
-my $cpu_info = $ks->{cpu_info} or
-  (printf STDERR gettext("%s: can not read cpu_info kstats\n"),
-   $cmdname),
-    exit(2);
-
-my (
-    @all_cpus,	# List of all CPUs in the system
-    @cpu_args,	# CPUs to look at
-    @cpus,	# List of CPUs to process
-    @id_list,	# list of various xxx_id kstats representing CPU topology
-    %chips,	# Hash with chip ID as a key and reference to the list of
-		# virtual CPU IDs, belonging to the chip as a value
-    @chip_list,	# List of all chip_id values
-    $ctree,	# The component tree
-   );
-
-#
-# Get information about each CPU.
-#
-#   Collect list of all CPUs in @cpu_list array
-#
-#   Construct %cpu_list hash keyed by CPU ID with cpu_info kstat hash as its
-#   value.
-#
-#   Construct %chips hash keyed by chip ID. It has a 'cpus' entry, which is
-#   a reference to a list of CPU IDs within a chip.
-#
-foreach my $id (nsort(keys %$cpu_info)) {
-	# $id is CPU id
-	my $info = $cpu_info->{$id};
-
-	#
-	# The name part of the cpu_info kstat should always be a string
-	# cpu_info$id.
-	#
-	# The $ci hash reference holds all data for a specific CPU id.
-	#
-	my $ci = $info->{"cpu_info$id"} or next;
-	# Save CPU-specific information in cpu_list hash, indexed by CPU ID.
-	$cpu_list{$id} = $ci;
-	my $chip_id = $ci->{'chip_id'};
-	# Collect CPUs within the chip.
-	# $chips{$chip_id} is a reference to a list of CPU IDs belonging to thie
-	# chip. It is automatically created when first referenced.
-	push (@{$chips{$chip_id}}, $id) if (defined($chip_id));
-	# Collect list of CPU IDs in @cpus
-	push (@all_cpus, $id);
-}
-
-#
-# Figure out what CPUs to examine.
-# Look at specific CPUs if any are specified on the command line or at all CPUs
-# CPU ranges specified in the command line are expanded into lists of CPUs
-#
-if (scalar(@ARGV) == 0) {
-	@cpu_args = @all_cpus;
-} else {
-	# Expand all x-y intervals in the argument list
-	@cpu_args = map { expand($_) } @ARGV;
-
-	usage(gettext("must specify exactly one processor if -s used")) if
-	    (($verbosity == 0) && scalar @cpu_args != 1);
-
-	# Detect invalid CPUs in the arguments
-	my @bad_args = set_subtract(\@all_cpus, \@cpu_args);
-	my $nbadargs = scalar @bad_args;
-
-	if ($nbadargs != 0) {
-		# Warn user about bad CPUs in the command line
-		my $argstr = collapse(@bad_args);
-
-		if ($nbadargs > 1) {
-			printf STDERR gettext("%s: Invalid processors %s\n"),
-			  $cmdname, $argstr;
-		} else {
-			printf STDERR
-			  gettext("%s: processor %s: Invalid argument\n"),
-			  $cmdname, $argstr;
-		}
-		$errors = 2;
-	}
-
-	@cpu_args = uniqsort(intersect(\@all_cpus, \@cpu_args));
-}
-
-#
-# In physical view, CPUs specified in the command line are only used to identify
-# chips. The actual CPUs are all CPUs belonging to these chips.
-#
-if (! $phys_view) {
-	@cpus = @cpu_args;
-} else {
-	# Get list of chips spanning all CPUs specified
-	@chip_list = property_list('chip_id', @cpu_args);
-	if (!scalar @chip_list && $errors == 0) {
-		printf STDERR
-		  gettext("%s: Physical processor view not supported\n"),
-		    $cmdname;
-		exit(1);
-	}
-
-	# Get list of all CPUs within these chips
-	@cpus = uniqsort(map { @{$chips{$_}} } @chip_list);
-}
-
-
-if ($phys_verbose) {
-	#
-	# 1) Look at all possible xxx_id properties and remove those that have
-	#    NCPU values or one value. Sort the rest.
-	#
-	# 2) Drop ids which have the same number of entries as number of CPUs or
-	#    number of chips.
-	#
-	# 3) Build the component tree for the system
-	#
-	foreach my $id (keys %$cpu_info) {
-		my $info = $cpu_info->{$id};
-		my $name = "cpu_info$id";
-		my $ci = $info->{$name}; # cpu_info kstat for this CPU
-
-		# Collect all statistic names matching $valid_id_exp
-		push @id_list, grep(/$valid_id_exp/, keys(%$ci));
-	}
-
-	# Remove duplicates
-	@id_list = uniq(@id_list);
-
-	my $ncpus = scalar @cpus;
-	my %prop_nvals;		# Number of instances of each property
-	my $nchips = scalar @chip_list;
-
-	#
-	# Get list of properties which have more than ncpus and less than nchips
-	# instances.
-	# Also collect number of instances for each property.
-	#
-	@id_list = grep {
-		my @ids = property_list($_, @cpus);
-		my $nids = scalar @ids;
-		$prop_nvals{$_} = $nids;
-		($_ eq "chip_id") ||
-		  (($nids > $nchips) && ($nids > 1) && ($nids < $ncpus));
-	} @id_list;
-
-	# Sort @id_list by number of instances for each property
-	@id_list = sort { $prop_nvals{$a} <=> $prop_nvals{$b} } @id_list;
-
-	$ctree = build_component_tree(\@cpus, \@id_list);
-}
-
-
-#
-# Walk all CPUs specified and print information about them.
-# Do nothing for physical view - will do everything later.
-#
-foreach my $id (@cpus) {
-	last if $phys_view;	# physical view is handled later
-	my $cpu = $cpu_list{$id} or next;
-
-	# Get CPU state and its modification time
-	my $mtime = $cpu->{'state_begin'};
-	my $mstring = strftime(gettext("%m/%d/%Y %T"), localtime($mtime));
-	my $status = $cpu->{'state'} || gettext("unknown");
-	# Get localized version of CPU status
-	$status = $cpu_states{$status} || $status;
-
-	if ($verbosity == 0) {
-		# Print 1 if CPU is online, 0 if offline.
-		printf "%d\n", $status eq 'on-line';
-	} elsif (! ($verbosity & 2)) {
-		printf gettext("%d\t%-8s  since %s\n"),
-			$id, $status, $mstring;
-	} else {
-		printf gettext("Status of virtual processor %d as of: "), $id;
-		print strftime(gettext("%m/%d/%Y %T"), localtime());
-		print "\n";
-		printf gettext("  %s since %s.\n"), $status, $mstring;
-		my $clock_speed =  $cpu->{'clock_MHz'};
-		my $cpu_type = $cpu->{'cpu_type'};
-
-		# Display clock speed
-		if ($clock_speed ) {
-			printf
-			  gettext("  The %s processor operates at %s MHz,\n"),
-			       $cpu_type, $clock_speed;
-		} else {
-			printf
-	      gettext("  the %s processor operates at an unknown frequency,\n"),
-			$cpu_type;
-		}
-
-		# Display FPU type
-		my $fpu = $cpu->{'fpu_type'};
-		if (! $fpu) {
-			print
-			  gettext("\tand has no floating point processor.\n");
-		} elsif ($fpu =~ m/^[aeiouy]/) {
-			printf
-			 gettext("\tand has an %s floating point processor.\n"),
-			   $fpu;
-		} else {
-			printf
-			  gettext("\tand has a %s floating point processor.\n"),
-			    $fpu;
-		}
-	}
-}
-
-#
-# Physical view print
-#
-if ($phys_view) {
-	if ($verbosity == 1) {
-		print scalar @chip_list, "\n";
-	} elsif ($verbosity == 0) {
-		# Print 1 if all CPUs are online, 0 otherwise.
-		foreach my $chip_id (@chip_list) {
-			# Get CPUs on a chip
-			my @chip_cpus = uniqsort(@{$chips{$chip_id}});
-			# List of all on-line CPUs on a chip
-			my @online_cpus = grep { 
-				($cpu_list{$_}->{state}) eq 'on-line'
-			} @chip_cpus;
-
-			#
-			# Print 1 if number of online CPUs equals number of all
-			# CPUs
-			#
-			printf
-			  "%d\n", scalar @online_cpus == scalar @chip_cpus;
-		}
-	} else {
-		# Walk the property tree and print everything in it.
-		my $tcores = $ctree->{values};
-		my $cname = id_translate($ctree->{name});
-		foreach my $chip (nsort(keys %$tcores)) {
-			my $chipref = $tcores->{$chip};
-			my @chip_cpus = @{$chipref->{cpus}};
-			my $ncpus = scalar @chip_cpus;
-			my $cpu_id = $chip_cpus[0];
-			my $cpu = $cpu_list{$cpu_id};
-			my $brand = $cpu->{brand} ||  gettext("(unknown)");
-			my $impl = $cpu->{implementation} ||
-			  gettext("(unknown)");
-			my $socket = $cpu->{socket_type};
-			#
-			# Remove cpuid and chipid information from
-			# implementation string and print it.
-			#
-			$impl =~ s/(cpuid|chipid)\s*\w+\s+//;
-			$brand = '' if $impl && $impl =~ /^$brand/;
-			# List of CPUs on a chip
-			my $cpu_name = pluralize('processor', $ncpus);
-			# Collapse range of CPUs into a-b string
-			my $cl = collapse(@chip_cpus);
-			my $childname = $chipref->{name};
-			if (! $childname) {
-				printf gettext("%s has %d virtual %s "),
-				       $cname, $ncpus, $cpu_name;
-				print "($cl)\n";
-				print "  $impl\n" if $impl;
-				print "\t$brand" if $brand;
-				print "\t[ Socket: $socket ]" if $socket &&
-				  $socket ne "Unknown";
-				print "\n";
-			} else {
-				# Get child count
-				my $nchildren =
-				  scalar(keys(%{$chipref->{values}}));
-				$childname = pluralize($childname, $nchildren);
-				printf
-				  gettext("%s has %d %s and %d virtual %s "),
-				       $cname, $nchildren, $childname, $ncpus,
-				       $cpu_name;
-				print "($cl)\n";
-				my $ident = print_component_tree ($chipref, 2);
-				my $spaces = ' ' x $ident;
-				print "$spaces$impl\n" if $impl;
-				print "$spaces  $brand\n" if $brand;
-			}
-		}
-	}
-}
-
-exit($errors);
-
-__END__
-
-# The psrinfo command displays information about virtual and physical processors
-# in a system. It gets all the information from the 'cpu_info' kstat.
-#
-# See detailed comment in the end of this file.
-#
-#
-#
-# This kstat
-# has the following components:
-#
-# module:	cpu_info
-# instance:	CPU ID
-# name:		cpu_infoID where ID is CPU ID
-# class:	misc
-#
-# The psrinfo command translates this information from kstat-specific
-# representation to user-friendly format.
-#
-# The psrinfo command has several basic modes of operations:
-#
-# 1) Without options, it displays a line per CPU with CPU ID and its status and
-#    the time the status was last set in the following format:
-#
-#	0       on-line  since MM/DD/YYYY HH:MM:SS
-#	1	on-line  since MM/DD/YYYY HH:MM:SS
-#	...
-#
-#    In this mode, the psrinfo command walks the list of CPUs (either from a
-#    command line or all CPUs) and prints the 'state' and 'state_begin' fields
-#    of cpu_info kstat structure for each CPU. The 'state_begin' is converted to
-#    local time.
-#
-# 2) With -s option and a single CPU ID as an argument, it displays 1 if the CPU
-#    is online and 0 otherwise.
-#
-# 3) With -p option, it displays the number of physical processors in a system.
-#    If any CPUs are specified in the command line, it displays the number of
-#    physical processors containing all virtual CPUs specified. The physical
-#    processor is identified by the 'chip_id' field of the cpu_info kstat.
-#
-#    The code just walks over all CPUs specified and checks how many different
-#    core_id values they span.
-#
-# 4) With -v option, it displays several lines of information per virtual CPU,
-#    including its status, type, operating speed and FPU type. For example:
-#
-#	Status of virtual processor 0 as of: MM/DD/YYYY HH:MM:SS
-#	  on-line since MM/DD/YYYY HH:MM:SS.
-#	  The i386 processor operates at XXXX MHz,
-#	        and has an i387 compatible floating point processor.
-#	Status of virtual processor 1 as of: MM/DD/YYYY HH:MM:SS
-#	  on-line since MM/DD/YYYY HH:MM:SS.
-#	  The i386 processor operates at XXXX MHz,
-#	        and has an i387 compatible floating point processor.
-#
-# This works in the same way as 1), just more kstat fields are massaged in the
-# output.
-#
-# 5) With -vp option, it reports additional information about each physical
-#    processor. This information includes information about sub-components of
-#    each physical processor and virtual CPUs in each sub-component. For
-#    example:
-#
-#	The physical processor has 2 cores and 4 virtual processors (0-3)
-#	  The core has 2 virtual processors (0 1)
-#	  The core has 2 virtual processors (2 3)
-#	    x86 (GenuineIntel family 15 model 4 step 4 clock 3211 MHz)
-#	      Intel(r) Pentium(r) D CPU 3.20GHz
-#
-#    The implementation does not know anything about physical CPU components
-#    such as cores. Instead it looks at various cpu_info kstat statistics that
-#    look like xxx_id and tries to reconstruct the CPU hierarchy based on these
-#    fields. This works as follows:
-#
-#    a) All kstats statistic names matching the $valid_id_exp regular expression
-#       are examined and each kstat statistic name is associated with the number
-#       of distinct entries in it.
-#
-#    b) The resulting list of kstat statistic names is sorted according to the
-#       number of distinct entries, matching each name. For example, there are
-#       fewer chip_id values than core_id values. This implies that the core is
-#	a sub-component of a chip.
-#
-#    c) All kstat names that have the same number of values as the number of
-#       physical processors ('chip_id' values) or the number of virtual
-#       processors are removed from the list.
-#
-#    d) The resulting list represents the CPU hierarchy of the machine. It is
-#       translated into a tree showing the hardware hierarchy. Each level of the
-#       hierarchy contains the name, reference to a list of CPUs at this level
-#       and subcomponents, indexed by the value of each component.
-#       The example system above is represented by the following tree:
-#
-#	$tree =
-#	{
-#	 'name' => 'chip_id',
-#	 'cpus' => [ '0', '1', '2', '3' ]
-#	 'values' =>
-#	 {
-#	  '0' =>
-#	  {
-#	   'name' => 'core_id',
-#	   'cpus' => [ '0', '1', '2', '3' ]
-#	   'values' =>
-#	   {
-#	    '0' => { 'cpus' => [ '0', '1' ] }
-#	    '1' => { 'cpus' => [ '2', '3' ] },
-#	   },
-#	  }
-#	 },
-#	};
-#
-#       Each node contains reference to a list of virtual CPUs at this level of
-#       hierarchy - one list for a system as a whole, one for chip 0 and one two
-#       for each cores. node. Non-leaf nodes also contain the symbolic name of
-#       the component as represented in the cpu_info kstat and a hash of
-#       subnodes, indexed by the value of the component. The tree is built by
-#       the build_component_tree() function.
-#
-#    e) The resulting tree is pretty-printed showing the number of
-#       sub-components and virtual CPUs in each sub-component. The tree is
-#       printed by the print_component_tree() function.
-#