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