xref: /onnv-gate/usr/src/cmd/psrinfo/psrinfo.pl (revision 9482:42f3d60af7ca)
1#!/usr/perl5/bin/perl
2
3#
4# CDDL HEADER START
5#
6# The contents of this file are subject to the terms of the
7# Common Development and Distribution License (the "License").
8# You may not use this file except in compliance with the License.
9#
10# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
11# or http://www.opensolaris.org/os/licensing.
12# See the License for the specific language governing permissions
13# and limitations under the License.
14#
15# When distributing Covered Code, include this CDDL HEADER in each
16# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17# If applicable, add the following below this CDDL HEADER, with the
18# fields enclosed by brackets "[]" replaced with your own identifying
19# information: Portions Copyright [yyyy] [name of copyright owner]
20#
21# CDDL HEADER END
22#
23# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24# Use is subject to license terms.
25#
26# psrinfo: displays information about processors
27#
28# See detailed comment in the end of this file.
29#
30
31use strict;
32use warnings;
33use locale;
34use POSIX qw(locale_h strftime);
35use File::Basename;
36use Getopt::Long qw(:config no_ignore_case bundling auto_version);
37use Sun::Solaris::Utils qw(textdomain gettext);
38use Sun::Solaris::Kstat;
39
40# Set message locale
41setlocale(LC_ALL, "");
42textdomain(TEXT_DOMAIN);
43
44######################################################################
45# Configuration variables
46######################################################################
47
48# Regexp describing cpu_info kstat fields describing CPU hierarchy.
49my $valid_id_exp = qr{^(?:chip|core)_id$};
50
51# Translation of kstat name to human-readable form
52my %translations = ('chip_id' => gettext("The physical processor"),
53		    'core_id' => gettext("The core"));
54
55# Localized version of plural forms
56my %pluralized_names = ('processor'	=> gettext("processor"),
57			'processors'	=> gettext("processors"),
58			'chip'		=> gettext("chip"),
59			'chips'		=> gettext("chips"),
60			'core'		=> gettext("core"),
61			'cores'		=> gettext("cores"));
62
63# Localized CPU states
64my %cpu_states = ('on-line'	=> gettext("on-line"),
65		  'off-line'	=> gettext("off-line"),
66		  'faulted'	=> gettext("faulted"),
67		  'powered-off' => gettext("powered-off"),
68		  'no-intr'	=> gettext("no-intr"),
69		  'spare'	=> gettext("spare"),
70		  'unknown'	=> gettext("unknown"));
71
72######################################################################
73# Global variables
74######################################################################
75
76# Hash with CPU ID as a key and specific per-cpu kstat hash as a value
77our %cpu_list;
78
79# Command name without path and trailing .pl - used for error messages.
80our $cmdname = basename($0, ".pl");
81
82# Return value
83our $errors = 0;
84
85######################################################################
86# Helper subroutines
87######################################################################
88
89#
90# Print help string if specified or the standard help message and exit setting
91# errno.
92#
93sub usage
94{
95	my (@msg) = @_;
96	print STDERR $cmdname, ": @msg\n" if (@msg);
97	print STDERR gettext("usage: \n" .
98			 "\tpsrinfo [-v] [-p] [processor_id ...]\n" .
99			 "\tpsrinfo -s [-p] processor_id\n");
100	exit(2);
101}
102
103#
104# Return the input list with duplicates removed.
105# Count how many times we've seen each element and remove elements seen more
106# than once.
107#
108sub uniq
109{
110	my %seen;	# Have we seen this element already?
111	return (grep { ++$seen{$_} == 1 } @_);
112}
113
114#
115# Return the intersection of two lists passed by reference
116# Convert the first list to a hash with seen entries marked as 1-values
117# Then grep only elements present in the first list from the second list.
118# As a little optimization, use the shorter list to build a hash.
119#
120sub intersect
121{
122	my ($left, $right) = @_;
123	my %seen;	# Set to 1 for everything in the first list
124	# Put the shortest list in $left
125	scalar @$left <= scalar @$right or ($right, $left) = ($left, $right);
126
127	# Create a hash indexed by elements in @left with ones as a value.
128	map { $seen{$_} = 1 } @$left;
129	# Find members of @right present in @left
130	return (grep { $seen{$_} } @$right);
131}
132
133#
134# Return elements of the second list not present in the first list. Both lists
135# are passed by reference.
136#
137sub set_subtract
138{
139	my ($left, $right) = @_;
140	my %seen;	# Set to 1 for everything in the first list
141	# Create a hash indexed by elements in @left with ones as a value.
142	map { $seen{$_} = 1 } @$left;
143	# Find members of @right present in @left
144	return (grep { ! $seen{$_} } @$right);
145}
146
147#
148# Sort the list numerically
149# Should be called in list context
150#
151sub nsort
152{
153	return (sort { $a <=> $b } @_);
154}
155
156#
157# Sort list numerically and remove duplicates
158# Should be called in list context
159#
160sub uniqsort
161{
162	return (sort { $a <=> $b } uniq(@_));
163}
164
165#
166# Return the maximum value of its arguments
167#
168sub max
169{
170	my $m = shift;
171
172	foreach my $el (@_) {
173		$m = $el if $m < $el;
174	}
175	return ($m);
176}
177
178#
179# Pluralize name if there is more than one instance
180# Arguments: name, ninstances
181#
182sub pluralize
183{
184	my ($name, $count) = @_;
185	# Remove trailing '_id' from the name.
186	$name =~ s/_id$//;
187	my $plural_name = $count > 1 ? "${name}s" : $name;
188	return ($pluralized_names{$plural_name} || $plural_name)
189}
190
191#
192# Translate id name into printable form
193# Look at the %translations table and replace everything found there
194# Remove trailing _id from the name if there is no translation
195#
196sub id_translate
197{
198	my $name = shift or return;
199	my $translated_name = $translations{$name};
200	$name =~ s/_id$// unless $translated_name;
201	return ($translated_name || $name);
202}
203
204#
205# Consolidate consequtive CPU ids as start-end
206# Input: list of CPUs
207# Output: string with space-sepated cpu values with CPU ranges
208#   collapsed as x-y
209#
210sub collapse
211{
212	return ('') unless @_;
213	my @args = uniqsort(@_);
214	my $start = shift(@args);
215	my $result = '';
216	my $end = $start;	# Initial range consists of the first element
217	foreach my $el (@args) {
218		if ($el == ($end + 1)) {
219			#
220			# Got consecutive ID, so extend end of range without
221			# printing anything since the range may extend further
222			#
223			$end = $el;
224		} else {
225			#
226			# Next ID is not consecutive, so print IDs gotten so
227			# far.
228			#
229			if ($end > $start + 1) {	# range
230				$result = "$result $start-$end";
231			} elsif ($end > $start) {	# different values
232				$result = "$result $start $end";
233			} else {	# same value
234				$result = "$result $start";
235			}
236
237			# Try finding consecutive range starting from this ID
238			$start = $end = $el;
239		}
240	}
241
242	# Print last ID(s)
243	if ($end > $start + 1) {
244		$result = "$result $start-$end";
245	} elsif ($end > $start) {
246		$result = "$result $start $end";
247	} else {
248		$result = "$result $start";
249	}
250	# Remove any spaces in the beginning
251	$result =~ s/^\s+//;
252	return ($result);
253}
254
255#
256# Expand start-end into the list of values
257# Input: string containing a single numeric ID or x-y range
258# Output: single value or a list of values
259# Ranges with start being more than end are inverted
260#
261sub expand
262{
263	my $arg = shift;
264
265	if ($arg =~ m/^\d+$/) {
266		# single number
267		return ($_);
268	} elsif ($arg =~ m/^(\d+)\-(\d+)$/) {
269		my ($start, $end) = ($1, $2);	# $start-$end
270		# Reverse the interval if start > end
271		($start, $end) = ($end, $start) if $start > $end;
272		return ($start .. $end);
273	} elsif ($arg =~ m/-/) {
274		printf STDERR
275		  gettext("%s: invalid processor range %s\n"),
276		    $cmdname, $_;
277	} else {
278		printf STDERR
279		  gettext("%s: processor %s: Invalid argument\n"),
280		    $cmdname, $_;
281	}
282	$errors = 2;
283	return ();
284}
285
286#
287# Functions for constructing CPU hierarchy. Only used with -vp option.
288#
289
290#
291# Return numerically sorted list of distinct values of a given cpu_info kstat
292# field, spanning given CPU set.
293#
294# Arguments:
295#   Property name
296#   list of CPUs
297#
298# Treat undefined values as zeroes.
299sub property_list
300{
301	my $prop_name = shift;
302	return (grep {$_ >= 0} uniqsort(map { $cpu_list{$_}->{$prop_name} || 0 } @_));
303}
304
305#
306# Return subset of CPUs sharing specified value of a given cpu_info kstat field.
307# Arguments:
308#   Property name
309#   Property value
310#   List of CPUs to select from
311#
312# Treat undefined values as zeroes.
313sub cpus_by_prop
314{
315	my $prop_name = shift;
316	my $prop_val = shift;
317
318	return (grep { ($cpu_list{$_}->{$prop_name} || 0) == $prop_val } @_);
319}
320
321#
322# Build component tree
323#
324# Arguments:
325#    Reference to the list of CPUs sharing the component
326#    Reference to the list of sub-components
327#
328sub build_component_tree
329{
330	my ($cpus, $comp_list) = @_;
331	# Get the first component and the rest
332	my ($comp_name, @comps) = @$comp_list;
333	my $tree = {};
334	if (!$comp_name) {
335		$tree->{cpus} = $cpus;
336		return ($tree);
337	}
338
339	# Get all possible component values
340	foreach my $v (property_list($comp_name, @$cpus)) {
341		my @comp_cpus = cpus_by_prop ($comp_name, $v, @$cpus);
342		$tree->{name} = $comp_name;
343		$tree->{cpus} = $cpus;
344		$tree->{values}->{$v} = build_component_tree(\@comp_cpus,
345							     \@comps);
346	}
347	return ($tree);
348}
349
350#
351# Print the component tree
352# Arguments:
353#   Reference to a tree
354#   indentation
355# Output: maximum indentation
356#
357sub print_component_tree
358{
359	my ($tree, $ind) = @_;
360	my $spaces = ' ' x $ind; # indentation string
361	my $vals = $tree->{values};
362	my $retval = $ind;
363	if ($vals) {
364		# This is not a leaf node
365		# Get node name and translate it to printable format
366		my $id_name = id_translate($tree->{name});
367		# Examine each sub-node
368		foreach my $comp_val (nsort(keys %$vals)) {
369			my $child_tree = $vals->{$comp_val}; # Sub-tree
370			my $child_id = $child_tree->{name}; # Name of child node
371			my @cpus = @{$child_tree->{cpus}}; # CPUs for the child
372			my $ncpus = scalar @cpus; # Number of CPUs
373			my $cpuname = pluralize('processor', $ncpus);
374			my $cl = collapse(@cpus); # Printable CPU list
375			if (!$child_id) {
376				# Child is a leaf node
377				print $spaces;
378				printf gettext("%s has %d virtual %s"),
379				       $id_name, $ncpus, $cpuname;
380				print " ($cl)\n";
381				$retval = max($retval, $ind + 2);
382			} else {
383				# Child has several values. Let's see how many
384				my $grandchild_tree = $child_tree->{values};
385				my $nvals = scalar(keys %$grandchild_tree);
386				my $child_id_name = pluralize($child_id,
387							      $nvals);
388				print $spaces;
389				printf
390				  gettext("%s has %d %s and %d virtual %s"),
391				    $id_name, $nvals, $child_id_name, $ncpus,
392				      $cpuname;
393				print " ($cl)\n";
394				# Print the tree for the child
395				$retval = max($retval,
396					      print_component_tree($child_tree,
397								   $ind + 2));
398			}
399		}
400	}
401	return ($retval);
402}
403
404
405############################
406# Main part of the program
407############################
408
409#
410# Option processing
411#
412my ($opt_v, $opt_p, $opt_silent);
413
414GetOptions("p" => \$opt_p,
415 	   "v" => \$opt_v,
416 	   "s" => \$opt_silent) || usage();
417
418
419my $verbosity = 1;
420my $phys_view;
421
422$verbosity |= 2 if $opt_v;
423$verbosity &= ~1 if $opt_silent;
424$phys_view = 1 if $opt_p;
425
426# Set $phys_verbose if -vp is specified
427my $phys_verbose = $phys_view && ($verbosity > 1);
428
429# Verify options
430usage(gettext("options -s and -v are mutually exclusive")) if $verbosity == 2;
431
432usage(gettext("must specify exactly one processor if -s used")) if
433  (($verbosity == 0) && scalar @ARGV != 1);
434
435#
436# Read cpu_info kstats
437#
438my $ks = Sun::Solaris::Kstat->new(strip_strings => 1) or
439  (printf STDERR gettext("%s: kstat_open() failed: %s\n"),
440   $cmdname, $!),
441    exit(2);
442my $cpu_info = $ks->{cpu_info} or
443  (printf STDERR gettext("%s: can not read cpu_info kstats\n"),
444   $cmdname),
445    exit(2);
446
447my (
448    @all_cpus,	# List of all CPUs in the system
449    @cpu_args,	# CPUs to look at
450    @cpus,	# List of CPUs to process
451    @id_list,	# list of various xxx_id kstats representing CPU topology
452    %chips,	# Hash with chip ID as a key and reference to the list of
453		# virtual CPU IDs, belonging to the chip as a value
454    @chip_list,	# List of all chip_id values
455    $ctree,	# The component tree
456   );
457
458#
459# Get information about each CPU.
460#
461#   Collect list of all CPUs in @cpu_list array
462#
463#   Construct %cpu_list hash keyed by CPU ID with cpu_info kstat hash as its
464#   value.
465#
466#   Construct %chips hash keyed by chip ID. It has a 'cpus' entry, which is
467#   a reference to a list of CPU IDs within a chip.
468#
469foreach my $id (nsort(keys %$cpu_info)) {
470	# $id is CPU id
471	my $info = $cpu_info->{$id};
472
473	#
474	# The name part of the cpu_info kstat should always be a string
475	# cpu_info$id.
476	#
477	# The $ci hash reference holds all data for a specific CPU id.
478	#
479	my $ci = $info->{"cpu_info$id"} or next;
480	# Save CPU-specific information in cpu_list hash, indexed by CPU ID.
481	$cpu_list{$id} = $ci;
482	my $chip_id = $ci->{'chip_id'};
483	# Collect CPUs within the chip.
484	# $chips{$chip_id} is a reference to a list of CPU IDs belonging to thie
485	# chip. It is automatically created when first referenced.
486	push (@{$chips{$chip_id}}, $id) if (defined($chip_id));
487	# Collect list of CPU IDs in @cpus
488	push (@all_cpus, $id);
489}
490
491#
492# Figure out what CPUs to examine.
493# Look at specific CPUs if any are specified on the command line or at all CPUs
494# CPU ranges specified in the command line are expanded into lists of CPUs
495#
496if (scalar(@ARGV) == 0) {
497	@cpu_args = @all_cpus;
498} else {
499	# Expand all x-y intervals in the argument list
500	@cpu_args = map { expand($_) } @ARGV;
501
502	usage(gettext("must specify exactly one processor if -s used")) if
503	    (($verbosity == 0) && scalar @cpu_args != 1);
504
505	# Detect invalid CPUs in the arguments
506	my @bad_args = set_subtract(\@all_cpus, \@cpu_args);
507	my $nbadargs = scalar @bad_args;
508
509	if ($nbadargs != 0) {
510		# Warn user about bad CPUs in the command line
511		my $argstr = collapse(@bad_args);
512
513		if ($nbadargs > 1) {
514			printf STDERR gettext("%s: Invalid processors %s\n"),
515			  $cmdname, $argstr;
516		} else {
517			printf STDERR
518			  gettext("%s: processor %s: Invalid argument\n"),
519			  $cmdname, $argstr;
520		}
521		$errors = 2;
522	}
523
524	@cpu_args = uniqsort(intersect(\@all_cpus, \@cpu_args));
525}
526
527#
528# In physical view, CPUs specified in the command line are only used to identify
529# chips. The actual CPUs are all CPUs belonging to these chips.
530#
531if (! $phys_view) {
532	@cpus = @cpu_args;
533} else {
534	# Get list of chips spanning all CPUs specified
535	@chip_list = property_list('chip_id', @cpu_args);
536	if (!scalar @chip_list && $errors == 0) {
537		printf STDERR
538		  gettext("%s: Physical processor view not supported\n"),
539		    $cmdname;
540		exit(1);
541	}
542
543	# Get list of all CPUs within these chips
544	@cpus = uniqsort(map { @{$chips{$_}} } @chip_list);
545}
546
547
548if ($phys_verbose) {
549	#
550	# 1) Look at all possible xxx_id properties and remove those that have
551	#    NCPU values or one value. Sort the rest.
552	#
553	# 2) Drop ids which have the same number of entries as number of CPUs or
554	#    number of chips.
555	#
556	# 3) Build the component tree for the system
557	#
558	foreach my $id (keys %$cpu_info) {
559		my $info = $cpu_info->{$id};
560		my $name = "cpu_info$id";
561		my $ci = $info->{$name}; # cpu_info kstat for this CPU
562
563		# Collect all statistic names matching $valid_id_exp
564		push @id_list, grep(/$valid_id_exp/, keys(%$ci));
565	}
566
567	# Remove duplicates
568	@id_list = uniq(@id_list);
569
570	my $ncpus = scalar @cpus;
571	my %prop_nvals;		# Number of instances of each property
572	my $nchips = scalar @chip_list;
573
574	#
575	# Get list of properties which have more than ncpus and less than nchips
576	# instances.
577	# Also collect number of instances for each property.
578	#
579	@id_list = grep {
580		my @ids = property_list($_, @cpus);
581		my $nids = scalar @ids;
582		$prop_nvals{$_} = $nids;
583		($_ eq "chip_id") ||
584		  (($nids > $nchips) && ($nids > 1) && ($nids < $ncpus));
585	} @id_list;
586
587	# Sort @id_list by number of instances for each property
588	@id_list = sort { $prop_nvals{$a} <=> $prop_nvals{$b} } @id_list;
589
590	$ctree = build_component_tree(\@cpus, \@id_list);
591}
592
593
594#
595# Walk all CPUs specified and print information about them.
596# Do nothing for physical view - will do everything later.
597#
598foreach my $id (@cpus) {
599	last if $phys_view;	# physical view is handled later
600	my $cpu = $cpu_list{$id} or next;
601
602	# Get CPU state and its modification time
603	my $mtime = $cpu->{'state_begin'};
604	my $mstring = strftime(gettext("%m/%d/%Y %T"), localtime($mtime));
605	my $status = $cpu->{'state'} || gettext("unknown");
606	# Get localized version of CPU status
607	$status = $cpu_states{$status} || $status;
608
609	if ($verbosity == 0) {
610		# Print 1 if CPU is online, 0 if offline.
611		printf "%d\n", $status eq 'on-line';
612	} elsif (! ($verbosity & 2)) {
613		printf gettext("%d\t%-8s  since %s\n"),
614			$id, $status, $mstring;
615	} else {
616		printf gettext("Status of virtual processor %d as of: "), $id;
617		print strftime(gettext("%m/%d/%Y %T"), localtime());
618		print "\n";
619		printf gettext("  %s since %s.\n"), $status, $mstring;
620		my $clock_speed =  $cpu->{'clock_MHz'};
621		my $cpu_type = $cpu->{'cpu_type'};
622
623		# Display clock speed
624		if ($clock_speed ) {
625			printf
626			  gettext("  The %s processor operates at %s MHz,\n"),
627			       $cpu_type, $clock_speed;
628		} else {
629			printf
630	      gettext("  the %s processor operates at an unknown frequency,\n"),
631			$cpu_type;
632		}
633
634		# Display FPU type
635		my $fpu = $cpu->{'fpu_type'};
636		if (! $fpu) {
637			print
638			  gettext("\tand has no floating point processor.\n");
639		} elsif ($fpu =~ m/^[aeiouy]/) {
640			printf
641			 gettext("\tand has an %s floating point processor.\n"),
642			   $fpu;
643		} else {
644			printf
645			  gettext("\tand has a %s floating point processor.\n"),
646			    $fpu;
647		}
648	}
649}
650
651#
652# Physical view print
653#
654if ($phys_view) {
655	if ($verbosity == 1) {
656		print scalar @chip_list, "\n";
657	} elsif ($verbosity == 0) {
658		# Print 1 if all CPUs are online, 0 otherwise.
659		foreach my $chip_id (@chip_list) {
660			# Get CPUs on a chip
661			my @chip_cpus = uniqsort(@{$chips{$chip_id}});
662			# List of all on-line CPUs on a chip
663			my @online_cpus = grep {
664				($cpu_list{$_}->{state}) eq 'on-line'
665			} @chip_cpus;
666
667			#
668			# Print 1 if number of online CPUs equals number of all
669			# CPUs
670			#
671			printf
672			  "%d\n", scalar @online_cpus == scalar @chip_cpus;
673		}
674	} else {
675		# Walk the property tree and print everything in it.
676		my $tcores = $ctree->{values};
677		my $cname = id_translate($ctree->{name});
678		foreach my $chip (nsort(keys %$tcores)) {
679			my $chipref = $tcores->{$chip};
680			my @chip_cpus = @{$chipref->{cpus}};
681			my $ncpus = scalar @chip_cpus;
682			my $cpu_id = $chip_cpus[0];
683			my $cpu = $cpu_list{$cpu_id};
684			my $brand = $cpu->{brand} ||  gettext("(unknown)");
685			my $impl = $cpu->{implementation} ||
686			  gettext("(unknown)");
687			my $socket = $cpu->{socket_type};
688			#
689			# Remove cpuid and chipid information from
690			# implementation string and print it.
691			#
692			$impl =~ s/(cpuid|chipid)\s*\w+\s+//;
693			$brand = '' if $impl && $impl =~ /^$brand/;
694			# List of CPUs on a chip
695			my $cpu_name = pluralize('processor', $ncpus);
696			# Collapse range of CPUs into a-b string
697			my $cl = collapse(@chip_cpus);
698			my $childname = $chipref->{name};
699			if (! $childname) {
700				printf gettext("%s has %d virtual %s "),
701				       $cname, $ncpus, $cpu_name;
702				print "($cl)\n";
703				print "  $impl\n" if $impl;
704				print "\t$brand" if $brand;
705				print "\t[ Socket: $socket ]" if $socket &&
706				  $socket ne "Unknown";
707				print "\n";
708			} else {
709				# Get child count
710				my $nchildren =
711				  scalar(keys(%{$chipref->{values}}));
712				$childname = pluralize($childname, $nchildren);
713				printf
714				  gettext("%s has %d %s and %d virtual %s "),
715				       $cname, $nchildren, $childname, $ncpus,
716				       $cpu_name;
717				print "($cl)\n";
718				my $ident = print_component_tree ($chipref, 2);
719				my $spaces = ' ' x $ident;
720				print "$spaces$impl\n" if $impl;
721				print "$spaces  $brand\n" if $brand;
722			}
723		}
724	}
725}
726
727exit($errors);
728
729__END__
730
731# The psrinfo command displays information about virtual and physical processors
732# in a system. It gets all the information from the 'cpu_info' kstat.
733#
734# See detailed comment in the end of this file.
735#
736#
737#
738# This kstat
739# has the following components:
740#
741# module:	cpu_info
742# instance:	CPU ID
743# name:		cpu_infoID where ID is CPU ID
744# class:	misc
745#
746# The psrinfo command translates this information from kstat-specific
747# representation to user-friendly format.
748#
749# The psrinfo command has several basic modes of operations:
750#
751# 1) Without options, it displays a line per CPU with CPU ID and its status and
752#    the time the status was last set in the following format:
753#
754#	0       on-line  since MM/DD/YYYY HH:MM:SS
755#	1	on-line  since MM/DD/YYYY HH:MM:SS
756#	...
757#
758#    In this mode, the psrinfo command walks the list of CPUs (either from a
759#    command line or all CPUs) and prints the 'state' and 'state_begin' fields
760#    of cpu_info kstat structure for each CPU. The 'state_begin' is converted to
761#    local time.
762#
763# 2) With -s option and a single CPU ID as an argument, it displays 1 if the CPU
764#    is online and 0 otherwise.
765#
766# 3) With -p option, it displays the number of physical processors in a system.
767#    If any CPUs are specified in the command line, it displays the number of
768#    physical processors containing all virtual CPUs specified. The physical
769#    processor is identified by the 'chip_id' field of the cpu_info kstat.
770#
771#    The code just walks over all CPUs specified and checks how many different
772#    core_id values they span.
773#
774# 4) With -v option, it displays several lines of information per virtual CPU,
775#    including its status, type, operating speed and FPU type. For example:
776#
777#	Status of virtual processor 0 as of: MM/DD/YYYY HH:MM:SS
778#	  on-line since MM/DD/YYYY HH:MM:SS.
779#	  The i386 processor operates at XXXX MHz,
780#	        and has an i387 compatible floating point processor.
781#	Status of virtual processor 1 as of: MM/DD/YYYY HH:MM:SS
782#	  on-line since MM/DD/YYYY HH:MM:SS.
783#	  The i386 processor operates at XXXX MHz,
784#	        and has an i387 compatible floating point processor.
785#
786# This works in the same way as 1), just more kstat fields are massaged in the
787# output.
788#
789# 5) With -vp option, it reports additional information about each physical
790#    processor. This information includes information about sub-components of
791#    each physical processor and virtual CPUs in each sub-component. For
792#    example:
793#
794#	The physical processor has 2 cores and 4 virtual processors (0-3)
795#	  The core has 2 virtual processors (0 1)
796#	  The core has 2 virtual processors (2 3)
797#	    x86 (GenuineIntel family 15 model 4 step 4 clock 3211 MHz)
798#	      Intel(r) Pentium(r) D CPU 3.20GHz
799#
800#    The implementation does not know anything about physical CPU components
801#    such as cores. Instead it looks at various cpu_info kstat statistics that
802#    look like xxx_id and tries to reconstruct the CPU hierarchy based on these
803#    fields. This works as follows:
804#
805#    a) All kstats statistic names matching the $valid_id_exp regular expression
806#       are examined and each kstat statistic name is associated with the number
807#       of distinct entries in it.
808#
809#    b) The resulting list of kstat statistic names is sorted according to the
810#       number of distinct entries, matching each name. For example, there are
811#       fewer chip_id values than core_id values. This implies that the core is
812#	a sub-component of a chip.
813#
814#    c) All kstat names that have the same number of values as the number of
815#       physical processors ('chip_id' values) or the number of virtual
816#       processors are removed from the list.
817#
818#    d) The resulting list represents the CPU hierarchy of the machine. It is
819#       translated into a tree showing the hardware hierarchy. Each level of the
820#       hierarchy contains the name, reference to a list of CPUs at this level
821#       and subcomponents, indexed by the value of each component.
822#       The example system above is represented by the following tree:
823#
824#	$tree =
825#	{
826#	 'name' => 'chip_id',
827#	 'cpus' => [ '0', '1', '2', '3' ]
828#	 'values' =>
829#	 {
830#	  '0' =>
831#	  {
832#	   'name' => 'core_id',
833#	   'cpus' => [ '0', '1', '2', '3' ]
834#	   'values' =>
835#	   {
836#	    '0' => { 'cpus' => [ '0', '1' ] }
837#	    '1' => { 'cpus' => [ '2', '3' ] },
838#	   },
839#	  }
840#	 },
841#	};
842#
843#       Each node contains reference to a list of virtual CPUs at this level of
844#       hierarchy - one list for a system as a whole, one for chip 0 and one two
845#       for each cores. node. Non-leaf nodes also contain the symbolic name of
846#       the component as represented in the cpu_info kstat and a hash of
847#       subnodes, indexed by the value of the component. The tree is built by
848#       the build_component_tree() function.
849#
850#    e) The resulting tree is pretty-printed showing the number of
851#       sub-components and virtual CPUs in each sub-component. The tree is
852#       printed by the print_component_tree() function.
853#
854