Home | History | Annotate | Download | only in psrinfo
      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 
     31 use strict;
     32 use warnings;
     33 use locale;
     34 use POSIX qw(locale_h strftime);
     35 use File::Basename;
     36 use Getopt::Long qw(:config no_ignore_case bundling auto_version);
     37 use Sun::Solaris::Utils qw(textdomain gettext);
     38 use Sun::Solaris::Kstat;
     39 
     40 # Set message locale
     41 setlocale(LC_ALL, "");
     42 textdomain(TEXT_DOMAIN);
     43 
     44 ######################################################################
     45 # Configuration variables
     46 ######################################################################
     47 
     48 # Regexp describing cpu_info kstat fields describing CPU hierarchy.
     49 my $valid_id_exp = qr{^(?:chip|core)_id$};
     50 
     51 # Translation of kstat name to human-readable form
     52 my %translations = ('chip_id' => gettext("The physical processor"),
     53 		    'core_id' => gettext("The core"));
     54 
     55 # Localized version of plural forms
     56 my %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
     64 my %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
     77 our %cpu_list;
     78 
     79 # Command name without path and trailing .pl - used for error messages.
     80 our $cmdname = basename($0, ".pl");
     81 
     82 # Return value
     83 our $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 #
     93 sub 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 #
    108 sub 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 #
    120 sub 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 #
    137 sub 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 #
    151 sub 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 #
    160 sub uniqsort
    161 {
    162 	return (sort { $a <=> $b } uniq(@_));
    163 }
    164 
    165 #
    166 # Return the maximum value of its arguments
    167 #
    168 sub 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 #
    182 sub 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 #
    196 sub 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 #
    210 sub 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 #
    261 sub 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.
    299 sub 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.
    313 sub 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 #
    328 sub 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 #
    357 sub 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 #
    412 my ($opt_v, $opt_p, $opt_silent);
    413 
    414 GetOptions("p" => \$opt_p,
    415  	   "v" => \$opt_v,
    416  	   "s" => \$opt_silent) || usage();
    417 
    418 
    419 my $verbosity = 1;
    420 my $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
    427 my $phys_verbose = $phys_view && ($verbosity > 1);
    428 
    429 # Verify options
    430 usage(gettext("options -s and -v are mutually exclusive")) if $verbosity == 2;
    431 
    432 usage(gettext("must specify exactly one processor if -s used")) if
    433   (($verbosity == 0) && scalar @ARGV != 1);
    434 
    435 #
    436 # Read cpu_info kstats
    437 #
    438 my $ks = Sun::Solaris::Kstat->new(strip_strings => 1) or
    439   (printf STDERR gettext("%s: kstat_open() failed: %s\n"),
    440    $cmdname, $!),
    441     exit(2);
    442 my $cpu_info = $ks->{cpu_info} or
    443   (printf STDERR gettext("%s: can not read cpu_info kstats\n"),
    444    $cmdname),
    445     exit(2);
    446 
    447 my (
    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 #
    469 foreach 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 #
    496 if (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 #
    531 if (! $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 
    548 if ($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 #
    598 foreach 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 #
    654 if ($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 
    727 exit($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