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